diff --git a/DEPS b/DEPS index 5d615f1..7b3e1e2a 100644 --- a/DEPS +++ b/DEPS
@@ -217,7 +217,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': '1c8311278ae8bfbb4b4f86895899cabf48f67542', + 'pdfium_revision': 'a15da341200e10f12262331da42fc81b3c4389ef', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling BoringSSL # and whatever else without interference from each other. @@ -272,7 +272,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling devtools-frontend # and whatever else without interference from each other. - 'devtools_frontend_revision': 'd347b31c0e2ec5337a08f7bcf3a0a9bf7d83f459', + 'devtools_frontend_revision': 'a4cfc762b19482760a1bb868d0c342e5f4b0dd59', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libprotobuf-mutator # and whatever else without interference from each other. @@ -324,7 +324,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. - 'dawn_revision': 'da1f66c8cee18677bf921181613732a9378e9864', + 'dawn_revision': 'aabde6c88f91f450b5845c8a720356bc4382ed01', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. @@ -551,7 +551,7 @@ }, 'src/ios/third_party/material_components_ios/src': { - 'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + 'd9e294ab0002f2c8569e0dc0e74825e4284d13ee', + 'url': Var('chromium_git') + '/external/github.com/material-components/material-components-ios.git' + '@' + 'ada54161e9302ec4d28a2cd27f3fae358bd49203', 'condition': 'checkout_ios', }, @@ -1341,7 +1341,7 @@ 'packages': [ { 'package': 'fuchsia/third_party/aemu/linux-amd64', - 'version': 'BisfFk67ojvPEsXt398o1XuIEAeGMM6KsrXCD1gY2RIC' + 'version': '2_kHlztQ5lFU-IhCP021uv4v5Ms-aBhhsqtZ1V02tbIC' }, ], 'condition': 'host_os == "linux" and checkout_fuchsia', @@ -1506,7 +1506,7 @@ }, 'src/third_party/webrtc': - Var('webrtc_git') + '/src.git' + '@' + 'f42a94a7f50d697bdd1f94b584c94f86c5fff67a', + Var('webrtc_git') + '/src.git' + '@' + '99df1af2aed6a8d10bc62dc348f63d6c084c1949', 'src/third_party/libgifcodec': Var('skia_git') + '/libgifcodec' + '@'+ Var('libgifcodec_revision'), @@ -1578,7 +1578,7 @@ Var('chromium_git') + '/v8/v8.git' + '@' + Var('v8_revision'), 'src-internal': { - 'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@01909e01660410274727b773dc3387c802e2cb8b', + 'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@ca62d830fc00873996bf96aa67ccc51107d573ea', 'condition': 'checkout_src_internal', }, @@ -1597,7 +1597,7 @@ 'packages': [ { 'package': 'chromeos_internal/apps/media_app/app', - 'version': 'lncFJYEwVu27fQaQFf8XRHbjYztYJXFXPbFvQaiqj_0C', + 'version': 'rTSxyOBRhg8Kuak4SK_kmsuxt14L_08GfLdW4Vm5OFMC', }, ], 'condition': 'checkout_chromeos and checkout_src_internal',
diff --git a/ash/BUILD.gn b/ash/BUILD.gn index 046deea..0e191cc 100644 --- a/ash/BUILD.gn +++ b/ash/BUILD.gn
@@ -129,10 +129,15 @@ "accelerators/pre_target_accelerator_handler.h", "accelerators/spoken_feedback_toggler.cc", "accelerators/spoken_feedback_toggler.h", + "accelerometer/accelerometer_constants.h", "accelerometer/accelerometer_file_reader.cc", "accelerometer/accelerometer_file_reader.h", + "accelerometer/accelerometer_provider_mojo.cc", + "accelerometer/accelerometer_provider_mojo.h", "accelerometer/accelerometer_reader.cc", "accelerometer/accelerometer_reader.h", + "accelerometer/accelerometer_samples_observer.cc", + "accelerometer/accelerometer_samples_observer.h", "accelerometer/accelerometer_types.cc", "accelerometer/accelerometer_types.h", "accessibility/accessibility_controller_impl.cc", @@ -1763,6 +1768,8 @@ # TODO(https://crbug.com/644336): Make CrasAudioHandler Chrome or Ash only. "//chromeos/audio", "//chromeos/components/multidevice/logging", + "//chromeos/components/sensors:sensors", + "//chromeos/components/sensors/mojom", "//chromeos/constants", "//chromeos/dbus", @@ -1920,6 +1927,8 @@ "accelerators/accelerator_unittest.cc", "accelerators/magnifier_key_scroller_unittest.cc", "accelerators/spoken_feedback_toggler_unittest.cc", + "accelerometer/accelerometer_provider_mojo_unittest.cc", + "accelerometer/accelerometer_samples_observer_unittest.cc", "accessibility/accessibility_controller_unittest.cc", "accessibility/accessibility_focus_ring_controller_unittest.cc", "accessibility/accessibility_focus_ring_group_unittest.cc", @@ -2366,6 +2375,9 @@ "//chromeos/components/bloom/public/cpp", "//chromeos/components/phonehub:test_support", "//chromeos/components/quick_answers:quick_answers", + "//chromeos/components/sensors:sensors", + "//chromeos/components/sensors:test_support", + "//chromeos/components/sensors/mojom", "//chromeos/constants", "//chromeos/dbus:test_support", "//chromeos/dbus/audio",
diff --git a/ash/DEPS b/ash/DEPS index a197460..425f400a 100644 --- a/ash/DEPS +++ b/ash/DEPS
@@ -62,6 +62,7 @@ "+chromeos/components/proximity_auth/public/mojom", "+chromeos/components/quick_answers", "+chromeos/components/security_token_pin", + "+chromeos/components/sensors", "+chromeos/constants", # TODO(https://crbug.com/940810): Eliminate this. "+chromeos/dbus/initialize_dbus_client.h",
diff --git a/ash/accelerometer/accelerometer_constants.h b/ash/accelerometer/accelerometer_constants.h new file mode 100644 index 0000000..1005e80e --- /dev/null +++ b/ash/accelerometer/accelerometer_constants.h
@@ -0,0 +1,23 @@ +// Copyright 2020 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 ASH_ACCELEROMETER_ACCELEROMETER_CONSTANTS_H_ +#define ASH_ACCELEROMETER_ACCELEROMETER_CONSTANTS_H_ + +#include "ash/accelerometer/accelerometer_types.h" + +namespace ash { + +const char kAccelerometerChannels[][8] = {"accel_x", "accel_y", "accel_z"}; + +// The number of axes for which there are accelerometer readings. +constexpr uint32_t kNumberOfAxes = 3u; + +// The names of the accelerometers. Matches up with the enum AccelerometerSource +// in ash/accelerometer/accelerometer_types.h. +const char kLocationStrings[ACCELEROMETER_SOURCE_COUNT][5] = {"lid", "base"}; + +} // namespace ash + +#endif // ASH_ACCELEROMETER_ACCELEROMETER_CONSTANTS_H_
diff --git a/ash/accelerometer/accelerometer_file_reader.cc b/ash/accelerometer/accelerometer_file_reader.cc index 805f4818..e6cc865 100644 --- a/ash/accelerometer/accelerometer_file_reader.cc +++ b/ash/accelerometer/accelerometer_file_reader.cc
@@ -10,6 +10,7 @@ #include <string> #include <vector> +#include "ash/accelerometer/accelerometer_constants.h" #include "ash/public/cpp/tablet_mode_observer.h" #include "ash/shell.h" #include "ash/wm/tablet_mode/tablet_mode_controller.h" @@ -26,6 +27,8 @@ #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/system/sys_info.h" +#include "base/task/task_traits.h" +#include "base/task/thread_pool.h" #include "base/task_runner.h" #include "base/task_runner_util.h" #include "base/threading/platform_thread.h" @@ -78,11 +81,6 @@ constexpr char kAccelerometerScanIndexPathFormatString[] = "scan_elements/in_accel_%s_index"; -// The names of the accelerometers. Matches up with the enum AccelerometerSource -// in ash/accelerometer/accelerometer_types.h. -constexpr char kAccelerometerNames[ACCELEROMETER_SOURCE_COUNT][5] = {"lid", - "base"}; - // The axes on each accelerometer. The order was changed on kernel 3.18+. constexpr char kAccelerometerAxes[][2] = {"x", "y", "z"}; constexpr char kLegacyAccelerometerAxes[][2] = {"y", "x", "z"}; @@ -93,9 +91,6 @@ // The size of individual values. constexpr size_t kDataSize = 2; -// The number of axes for which there are acceleration readings. -constexpr int kNumberOfAxes = 3; - // The size of data in one reading of the accelerometers. constexpr int kSizeOfReading = kDataSize * kNumberOfAxes; @@ -150,9 +145,14 @@ : observers_( new base::ObserverListThreadSafe<AccelerometerReader::Observer>()) {} -void AccelerometerFileReader::PrepareAndInitialize( - scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner) { - task_runner_ = sequenced_task_runner; +void AccelerometerFileReader::PrepareAndInitialize() { + // AccelerometerReader is important for screen orientation so we need + // USER_VISIBLE priority. + // Use CONTINUE_ON_SHUTDOWN to avoid blocking shutdown since the datareading + // could get blocked on certain devices. See https://crbug.com/1023989. + task_runner_ = base::ThreadPool::CreateSequencedTaskRunner( + {base::MayBlock(), base::TaskPriority::USER_VISIBLE, + base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}); initialization_state_ = State::INITIALIZING; @@ -287,8 +287,8 @@ 3 * static_cast<int>(configuration_.count)) { const char* axis = legacy_cross_accel ? kLegacyAccelerometerAxes[j] : kAccelerometerAxes[j]; - LOG(ERROR) << "Field index for " << kAccelerometerNames[i] << " " - << axis << " axis out of bounds."; + LOG(ERROR) << "Field index for " << kLocationStrings[i] << " " << axis + << " axis out of bounds."; initialization_state_ = State::FAILED; return; } @@ -438,12 +438,12 @@ const base::FilePath& name, const std::string& location) { size_t config_index = 0; - for (; config_index < base::size(kAccelerometerNames); ++config_index) { - if (location == kAccelerometerNames[config_index]) + for (; config_index < base::size(kLocationStrings); ++config_index) { + if (location == kLocationStrings[config_index]) break; } - if (config_index >= base::size(kAccelerometerNames)) { + if (config_index >= base::size(kLocationStrings)) { LOG(ERROR) << "Unrecognized location: " << location << " for device " << name.MaybeAsASCII() << "\n"; return false; @@ -487,11 +487,11 @@ base::FilePath(kAccelerometerDevicePath).Append(name.BaseName()); // Read configuration of each accelerometer axis from each accelerometer from // /sys/bus/iio/devices/iio:deviceX/. - for (size_t i = 0; i < base::size(kAccelerometerNames); ++i) { + for (size_t i = 0; i < base::size(kLocationStrings); ++i) { configuration_.has[i] = false; // Read scale of accelerometer. - std::string accelerometer_scale_path = base::StringPrintf( - kLegacyScaleNameFormatString, kAccelerometerNames[i]); + std::string accelerometer_scale_path = + base::StringPrintf(kLegacyScaleNameFormatString, kLocationStrings[i]); // Read the scale for all axes. int scale_divisor = 0; if (!ReadFileToInt(iio_path.Append(accelerometer_scale_path.c_str()), @@ -507,9 +507,9 @@ configuration_.has[i] = true; for (size_t j = 0; j < base::size(kLegacyAccelerometerAxes); ++j) { configuration_.scale[i][j] = base::kMeanGravityFloat / scale_divisor; - std::string accelerometer_index_path = base::StringPrintf( - kLegacyAccelerometerScanIndexPathFormatString, - kLegacyAccelerometerAxes[j], kAccelerometerNames[i]); + std::string accelerometer_index_path = + base::StringPrintf(kLegacyAccelerometerScanIndexPathFormatString, + kLegacyAccelerometerAxes[j], kLocationStrings[i]); if (!ReadFileToInt(iio_path.Append(accelerometer_index_path.c_str()), &(configuration_.index[i][j]))) { configuration_.has[i] = false;
diff --git a/ash/accelerometer/accelerometer_file_reader.h b/ash/accelerometer/accelerometer_file_reader.h index eb45be9..e8814c5 100644 --- a/ash/accelerometer/accelerometer_file_reader.h +++ b/ash/accelerometer/accelerometer_file_reader.h
@@ -15,8 +15,6 @@ namespace ash { -enum class State { INITIALIZING, SUCCESS, FAILED }; - // Work that runs on a base::TaskRunner. It determines the accelerometer // configuration, and reads the data. Upon a successful read it will notify // all observers. @@ -28,8 +26,7 @@ AccelerometerFileReader& operator=(const AccelerometerFileReader&) = delete; // AccelerometerProviderInterface: - void PrepareAndInitialize( - scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner) override; + void PrepareAndInitialize() override; void AddObserver(AccelerometerReader::Observer* observer) override; void RemoveObserver(AccelerometerReader::Observer* observer) override; void StartListenToTabletModeController() override; @@ -48,8 +45,9 @@ // Tracks if accelerometer initialization is completed. void CheckInitStatus(); - // With ChromeOS EC lid angle driver present, accelerometer read is cancelled - // in clamshell mode, and triggered when entering tablet mode. + // With ChromeOS EC lid angle driver present, it's triggered when the device + // is physically used as a tablet (even thought its UI might be in clamshell + // mode), cancelled otherwise. void TriggerRead(); void CancelRead(); @@ -130,9 +128,6 @@ void SetEmitEventsInternal(bool emit_events); - // The current initialization state of reader. - State initialization_state_ = State::INITIALIZING; - // True if periodical accelerometer read is on. bool accelerometer_read_on_ = false;
diff --git a/ash/accelerometer/accelerometer_provider_mojo.cc b/ash/accelerometer/accelerometer_provider_mojo.cc new file mode 100644 index 0000000..4b62136 --- /dev/null +++ b/ash/accelerometer/accelerometer_provider_mojo.cc
@@ -0,0 +1,568 @@ +// Copyright 2020 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 "ash/accelerometer/accelerometer_provider_mojo.h" + +#include <iterator> +#include <utility> + +#include "ash/accelerometer/accelerometer_constants.h" +#include "ash/shell.h" +#include "ash/wm/tablet_mode/tablet_mode_controller.h" +#include "base/bind.h" +#include "base/observer_list_threadsafe.h" +#include "base/ranges/algorithm.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/time/time.h" +#include "chromeos/components/sensors/sensor_hal_dispatcher.h" + +namespace ash { + +namespace { + +// Delay of the reconnection to Sensor Hal Dispatcher. +constexpr base::TimeDelta kDelayReconnect = + base::TimeDelta::FromMilliseconds(1000); + +} // namespace + +AccelerometerProviderMojo::AccelerometerProviderMojo() + : sensor_hal_client_(this), + observers_( + new base::ObserverListThreadSafe<AccelerometerReader::Observer>()), + update_(new AccelerometerUpdate()) {} + +void AccelerometerProviderMojo::PrepareAndInitialize() { + // This function should only be called once. + DCHECK(!task_runner_); + + task_runner_ = base::SequencedTaskRunnerHandle::Get(); + + task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&AccelerometerProviderMojo::RegisterSensorClient, + base::Unretained(this))); +} + +void AccelerometerProviderMojo::AddObserver( + AccelerometerReader::Observer* observer) { + DCHECK(task_runner_); + + // Do not move this into AddObserverOnThread, as the current task runner will + // be used in ObserverListThreadSafe::Notify. + observers_->AddObserver(observer); + + task_runner_->PostNonNestableTask( + FROM_HERE, base::BindOnce(&AccelerometerProviderMojo::AddObserverOnThread, + base::Unretained(this), observer)); +} + +void AccelerometerProviderMojo::RemoveObserver( + AccelerometerReader::Observer* observer) { + DCHECK(task_runner_); + + observers_->RemoveObserver(observer); +} + +void AccelerometerProviderMojo::StartListenToTabletModeController() { + Shell::Get()->tablet_mode_controller()->AddObserver(this); +} + +void AccelerometerProviderMojo::StopListenToTabletModeController() { + Shell::Get()->tablet_mode_controller()->RemoveObserver(this); +} + +void AccelerometerProviderMojo::SetEmitEvents(bool emit_events) { + task_runner_->PostNonNestableTask( + FROM_HERE, + base::BindOnce(&AccelerometerProviderMojo::SetEmitEventsOnThread, this, + emit_events)); +} + +void AccelerometerProviderMojo::OnTabletPhysicalStateChanged() { + // Wait until the existence of the driver is determined. + if (ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::UNKNOWN) { + pending_on_tablet_physical_state_changed_ = true; + return; + } + + // When CrOS EC lid angle driver is not present, accelerometer read is always + // ON and can't be tuned. Thus AccelerometerProviderMojo no longer listens + // to tablet mode event. + auto* tablet_mode_controller = Shell::Get()->tablet_mode_controller(); + if (ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::NOT_SUPPORTED) { + tablet_mode_controller->RemoveObserver(this); + return; + } + + // Auto rotation is turned on when the device is physically used as a tablet + // (i.e. flipped or detached), regardless of the UI state (i.e. whether tablet + // mode is turned on or off). + const bool is_auto_rotation_on = + tablet_mode_controller->is_in_tablet_physical_state(); + + task_runner_->PostNonNestableTask( + FROM_HERE, + is_auto_rotation_on + ? base::BindOnce(&AccelerometerProviderMojo::TriggerRead, this) + : base::BindOnce(&AccelerometerProviderMojo::CancelRead, this)); +} + +void AccelerometerProviderMojo::SetUpChannel( + mojo::PendingRemote<chromeos::sensors::mojom::SensorService> + pending_remote) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (sensor_service_remote_.is_bound()) { + LOG(ERROR) << "Ignoring the second Remote<SensorService>"; + return; + } + + sensor_service_remote_.Bind(std::move(pending_remote), task_runner_); + sensor_service_remote_.set_disconnect_handler( + base::BindOnce(&AccelerometerProviderMojo::OnSensorServiceDisconnect, + base::Unretained(this))); + if (ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::UNKNOWN) { + sensor_service_remote_->GetDeviceIds( + chromeos::sensors::mojom::DeviceType::ANGL, + base::BindOnce(&AccelerometerProviderMojo::GetLidAngleIdsCallback, + base::Unretained(this))); + } + + sensor_service_remote_->GetDeviceIds( + chromeos::sensors::mojom::DeviceType::ACCEL, + base::BindOnce(&AccelerometerProviderMojo::GetAccelerometerIdsCallback, + base::Unretained(this))); +} + +void AccelerometerProviderMojo::TriggerRead() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::SUPPORTED) + EnableAccelerometerReading(); +} + +void AccelerometerProviderMojo::CancelRead() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::SUPPORTED) + DisableAccelerometerReading(); +} + +State AccelerometerProviderMojo::GetInitializationStateForTesting() const { + return initialization_state_; +} + +AccelerometerProviderMojo::AccelerometerData::AccelerometerData() = default; +AccelerometerProviderMojo::AccelerometerData::~AccelerometerData() = default; + +AccelerometerProviderMojo::~AccelerometerProviderMojo() = default; + +void AccelerometerProviderMojo::RegisterSensorClient() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterClient( + sensor_hal_client_.BindNewPipeAndPassRemote()); + + sensor_hal_client_.set_disconnect_handler( + base::BindOnce(&AccelerometerProviderMojo::OnSensorHalClientFailure, + base::Unretained(this))); +} + +void AccelerometerProviderMojo::OnSensorHalClientFailure() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + LOG(ERROR) << "OnSensorHalClientFailure"; + + ResetSensorService(); + sensor_hal_client_.reset(); + + task_runner_->PostDelayedTask( + FROM_HERE, + base::BindOnce(&AccelerometerProviderMojo::RegisterSensorClient, + base::Unretained(this)), + kDelayReconnect); +} + +void AccelerometerProviderMojo::OnSensorServiceDisconnect() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + LOG(ERROR) << "OnSensorServiceDisconnect"; + + ResetSensorService(); +} + +void AccelerometerProviderMojo::ResetSensorService() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + for (auto& accelerometer : accelerometers_) { + accelerometer.second.remote.reset(); + accelerometer.second.samples_observer.reset(); + } + sensor_service_remote_.reset(); +} + +void AccelerometerProviderMojo::GetLidAngleIdsCallback( + const std::vector<int32_t>& lid_angle_ids) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK_EQ(ec_lid_angle_driver_status_, ECLidAngleDriverStatus::UNKNOWN); + + if (!lid_angle_ids.empty()) { + ec_lid_angle_driver_status_ = ECLidAngleDriverStatus::SUPPORTED; + } else { + ec_lid_angle_driver_status_ = ECLidAngleDriverStatus::NOT_SUPPORTED; + EnableAccelerometerReading(); + } + + if (pending_on_tablet_physical_state_changed_) + OnTabletPhysicalStateChanged(); +} + +void AccelerometerProviderMojo::GetAccelerometerIdsCallback( + const std::vector<int32_t>& accelerometer_ids) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (accelerometer_ids.empty()) { + FailedToInitialize(); + return; + } + + for (int32_t id : accelerometer_ids) + RegisterAccelerometerWithId(id); +} + +void AccelerometerProviderMojo::RegisterAccelerometerWithId(int32_t id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + auto& accelerometer = accelerometers_[id]; + + if (accelerometer.ignored) { + // Something went wrong in the previous initialization. Ignoring this accel. + return; + } + + DCHECK(!accelerometer.remote.is_bound()); + DCHECK(!accelerometer.samples_observer.get()); + + if (!sensor_service_remote_.is_bound()) { + // Something went wrong. Skipping here. + return; + } + + accelerometer.remote.reset(); + + sensor_service_remote_->GetDevice( + id, accelerometer.remote.BindNewPipeAndPassReceiver()); + accelerometer.remote.set_disconnect_handler(base::BindOnce( + &AccelerometerProviderMojo::OnAccelerometerRemoteDisconnect, + base::Unretained(this), id)); + + std::vector<std::string> attr_names; + if (!accelerometer.location.has_value()) + attr_names.push_back(chromeos::sensors::mojom::kLocation); + if (!accelerometer.scale.has_value()) + attr_names.push_back(chromeos::sensors::mojom::kScale); + + if (!attr_names.empty()) { + accelerometer.remote->GetAttributes( + attr_names, + base::BindOnce(&AccelerometerProviderMojo::GetAttributesCallback, + base::Unretained(this), id)); + } else { + // Create the observer directly if the attributes have already been + // retrieved. + CreateAccelerometerSamplesObserver(id); + } +} + +void AccelerometerProviderMojo::OnAccelerometerRemoteDisconnect(int32_t id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + auto& accelerometer = accelerometers_[id]; + + LOG(ERROR) << "Accelerometer with id: " << id << " disconnected"; + accelerometer.remote.reset(); + accelerometer.samples_observer.reset(); + + if (!sensor_service_remote_.is_bound()) + return; + + RegisterAccelerometerWithId(id); +} + +void AccelerometerProviderMojo::GetAttributesCallback( + int32_t id, + const std::vector<base::Optional<std::string>>& values) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + auto& accelerometer = accelerometers_[id]; + DCHECK(accelerometer.remote.is_bound()); + + size_t index = 0; + if (!accelerometer.location.has_value()) { + if (index >= values.size()) { + LOG(ERROR) << "values doesn't contain location attribute."; + IgnoreAccelerometer(id); + return; + } + + if (!values[index].has_value()) { + LOG(WARNING) << "No location attribute for accel with id: " << id; + IgnoreAccelerometer(id); + return; + } + + auto* it = base::ranges::find(kLocationStrings, values[index]); + if (it == std::end(kLocationStrings)) { + LOG(WARNING) << "Unrecognized location: " << values[index].value() + << " for device with id: "; + IgnoreAccelerometer(id); + return; + } + + AccelerometerSource source = static_cast<AccelerometerSource>( + std::distance(std::begin(kLocationStrings), it)); + accelerometer.location = source; + + if (location_to_accelerometer_id_.find(source) != + location_to_accelerometer_id_.end()) { + LOG(ERROR) << "Duplicated location source " << source + << " of accel id: " << id + << ", and accel id: " << location_to_accelerometer_id_[source]; + FailedToInitialize(); + return; + } + + location_to_accelerometer_id_[source] = id; + ++index; + } + + if (!accelerometer.scale.has_value()) { + if (index >= values.size()) { + LOG(ERROR) << "values doesn't contain scale attribute."; + IgnoreAccelerometer(id); + return; + } + + double scale = 0.0; + if (!values[index].has_value() || + !base::StringToDouble(values[index].value(), &scale)) { + LOG(ERROR) << "Invalid scale: " << values[index].value_or("") + << ", for accel with id: " << id; + IgnoreAccelerometer(id); + return; + } + accelerometer.scale = scale; + + ++index; + } + + CheckInitialization(); + + CreateAccelerometerSamplesObserver(id); +} + +void AccelerometerProviderMojo::IgnoreAccelerometer(int32_t id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + auto& accelerometer = accelerometers_[id]; + + LOG(WARNING) << "Ignoring accel with id: " << id; + accelerometer.ignored = true; + accelerometer.remote.reset(); + + CheckInitialization(); +} + +void AccelerometerProviderMojo::CheckInitialization() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK_NE(ec_lid_angle_driver_status_, ECLidAngleDriverStatus::UNKNOWN); + + if (initialization_state_ != State::INITIALIZING) + return; + + bool has_accelerometer_lid = false; + for (const auto& accelerometer : accelerometers_) { + if (accelerometer.second.ignored) { + if (!accelerometer.second.location.has_value()) + continue; + + if (accelerometer.second.location == ACCELEROMETER_SOURCE_SCREEN || + ec_lid_angle_driver_status_ == + ECLidAngleDriverStatus::NOT_SUPPORTED) { + // This ignored accelerometer is essential. + FailedToInitialize(); + return; + } + + continue; + } + + if (!accelerometer.second.scale.has_value() || + !accelerometer.second.location.has_value()) + return; + + if (accelerometer.second.location == ACCELEROMETER_SOURCE_SCREEN) + has_accelerometer_lid = true; + else + has_accelerometer_base_ = true; + } + + if (has_accelerometer_lid) { + if (!has_accelerometer_base_) { + LOG(WARNING) + << "Initialization succeeded without an accelerometer on the base"; + } + + initialization_state_ = State::SUCCESS; + } else { + FailedToInitialize(); + } +} + +void AccelerometerProviderMojo::CreateAccelerometerSamplesObserver(int32_t id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + auto& accelerometer = accelerometers_[id]; + DCHECK(accelerometer.remote.is_bound()); + DCHECK(!accelerometer.ignored); + DCHECK(accelerometer.scale.has_value() && accelerometer.location.has_value()); + + if (accelerometer.location == ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD && + ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::SUPPORTED) { + // Skipping as it's only needed if lid-angle is not supported. + // |GetLidAngleIdsCallback| will call this function with this |id| again if + // it's found not supported. + return; + } + + accelerometer.samples_observer = + std::make_unique<AccelerometerSamplesObserver>( + id, std::move(accelerometer.remote), accelerometer.scale.value(), + base::BindRepeating( + &AccelerometerProviderMojo::OnSampleUpdatedCallback, + base::Unretained(this))); + + if (accelerometer_read_on_ || one_time_read_) + accelerometer.samples_observer->SetEnabled(true); +} + +void AccelerometerProviderMojo::EnableAccelerometerReading() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK_NE(ec_lid_angle_driver_status_, ECLidAngleDriverStatus::UNKNOWN); + if (accelerometer_read_on_) + return; + + accelerometer_read_on_ = true; + for (auto& accelerometer : accelerometers_) { + if (!accelerometer.second.samples_observer.get()) + continue; + + accelerometer.second.samples_observer->SetEnabled(true); + } +} + +void AccelerometerProviderMojo::DisableAccelerometerReading() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK_EQ(ec_lid_angle_driver_status_, ECLidAngleDriverStatus::SUPPORTED); + if (!accelerometer_read_on_) + return; + + accelerometer_read_on_ = false; + + // Allow one more read and let |OnSampleUpdatedCallback| disable the + // observers. + if (one_time_read_) + return; + + for (auto& accelerometer : accelerometers_) { + if (!accelerometer.second.samples_observer.get()) + continue; + + accelerometer.second.samples_observer->SetEnabled(false); + } +} + +void AccelerometerProviderMojo::OnSampleUpdatedCallback( + int iio_device_id, + std::vector<float> sample) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK_EQ(sample.size(), kNumberOfAxes); + + auto& accelerometer = accelerometers_[iio_device_id]; + DCHECK(accelerometer.location.has_value()); + + bool need_two_accelerometers = + (ec_lid_angle_driver_status_ == ECLidAngleDriverStatus::NOT_SUPPORTED && + has_accelerometer_base_); + + if (!one_time_read_ && !accelerometer_read_on_) { + // This sample is not needed. + return; + } + + if (!emit_events_) + return; + + update_->Set(accelerometers_[iio_device_id].location.value(), sample[0], + sample[1], sample[2]); + + if (need_two_accelerometers && + (!update_->has(ACCELEROMETER_SOURCE_SCREEN) || + !update_->has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD))) { + // Wait for the other accel to be updated. + return; + } + + observers_->Notify(FROM_HERE, + &AccelerometerReader::Observer::OnAccelerometerUpdated, + update_); + update_ = new AccelerometerUpdate(); + + one_time_read_ = false; + if (accelerometer_read_on_) + return; + + // This was a one time read. Disable observers. + for (auto& accelerometer : accelerometers_) { + if (!accelerometer.second.samples_observer.get()) + continue; + + accelerometer.second.samples_observer->SetEnabled(false); + } +} + +void AccelerometerProviderMojo::SetEmitEventsOnThread(bool emit_events) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + emit_events_ = emit_events; +} + +void AccelerometerProviderMojo::FailedToInitialize() { + DCHECK_NE(initialization_state_, State::SUCCESS); + + LOG(ERROR) << "Failed to initialize for accelerometer read."; + initialization_state_ = State::FAILED; + + accelerometers_.clear(); + ResetSensorService(); + sensor_hal_client_.reset(); +} + +void AccelerometerProviderMojo::AddObserverOnThread( + AccelerometerReader::Observer* observer) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + one_time_read_ = true; + + if (accelerometer_read_on_) + return; + + for (auto& accelerometer : accelerometers_) { + if (!accelerometer.second.samples_observer.get()) + continue; + + accelerometer.second.samples_observer->SetEnabled(true); + } +} + +} // namespace ash
diff --git a/ash/accelerometer/accelerometer_provider_mojo.h b/ash/accelerometer/accelerometer_provider_mojo.h new file mode 100644 index 0000000..30212a7 --- /dev/null +++ b/ash/accelerometer/accelerometer_provider_mojo.h
@@ -0,0 +1,178 @@ +// Copyright 2020 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 ASH_ACCELEROMETER_ACCELEROMETER_PROVIDER_MOJO_H_ +#define ASH_ACCELEROMETER_ACCELEROMETER_PROVIDER_MOJO_H_ + +#include <stdint.h> + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "ash/accelerometer/accelerometer_reader.h" +#include "ash/accelerometer/accelerometer_samples_observer.h" +#include "ash/ash_export.h" +#include "ash/public/cpp/tablet_mode_observer.h" +#include "base/memory/ref_counted.h" +#include "base/observer_list_threadsafe.h" +#include "base/optional.h" +#include "base/sequence_checker.h" +#include "base/sequenced_task_runner.h" +#include "mojo/public/cpp/bindings/receiver.h" +#include "mojo/public/cpp/bindings/remote.h" + +namespace ash { + +// Work that runs on the UI thread. As a sensor client, it communicates with IIO +// Service, determines the accelerometers' configuration, and waits for the +// accelerometers' samples. Upon receiving a sample, it will notify all +// observers. +class ASH_EXPORT AccelerometerProviderMojo + : public AccelerometerProviderInterface, + public TabletModeObserver, + public chromeos::sensors::mojom::SensorHalClient { + public: + AccelerometerProviderMojo(); + AccelerometerProviderMojo(const AccelerometerProviderMojo&) = delete; + AccelerometerProviderMojo& operator=(const AccelerometerProviderMojo&) = + delete; + + // AccelerometerProviderInterface: + void PrepareAndInitialize() override; + void AddObserver(AccelerometerReader::Observer* observer) override; + void RemoveObserver(AccelerometerReader::Observer* observer) override; + void StartListenToTabletModeController() override; + void StopListenToTabletModeController() override; + void SetEmitEvents(bool emit_events) override; + + // TabletModeObserver: + void OnTabletPhysicalStateChanged() override; + + // chromeos::sensors::mojom::SensorHalClient: + void SetUpChannel(mojo::PendingRemote<chromeos::sensors::mojom::SensorService> + pending_remote) override; + + // With ChromeOS EC lid angle driver present, it's triggered when the device + // is physically used as a tablet (even thought its UI might be in clamshell + // mode), cancelled otherwise. + void TriggerRead(); + void CancelRead(); + + State GetInitializationStateForTesting() const; + + private: + struct AccelerometerData { + AccelerometerData(); + ~AccelerometerData(); + + bool ignored = false; + // Temporarily stores the accelerometer remote, waiting for it's scale and + // location information. It'll be passed to |samples_observer| as an + // argument after all information is collected. + mojo::Remote<chromeos::sensors::mojom::SensorDevice> remote; + base::Optional<AccelerometerSource> location; + base::Optional<float> scale; + std::unique_ptr<AccelerometerSamplesObserver> samples_observer; + }; + + ~AccelerometerProviderMojo() override; + + // Registers chromeos::sensors::mojom::SensorHalClient to Sensor Hal + // Dispatcher, waiting for the Mojo connection to IIO Service. + void RegisterSensorClient(); + void OnSensorHalClientFailure(); + + void OnSensorServiceDisconnect(); + void ResetSensorService(); + + // Callback of GetDeviceIds(ANGL), containing the lid-angle device's id if it + // exists. + void GetLidAngleIdsCallback(const std::vector<int32_t>& lid_angle_ids); + + // Callback of GetDeviceIds(ACCEL), containing all iio_device_ids of + // accelerometers. + void GetAccelerometerIdsCallback( + const std::vector<int32_t>& accelerometer_ids); + + // Creates the Mojo channel for the accelerometer, and requests the + // accelerometer's required attributes before creating the + // AccelerometerSamplesObserver of it. + void RegisterAccelerometerWithId(int32_t id); + void OnAccelerometerRemoteDisconnect(int32_t id); + void GetAttributesCallback( + int32_t id, + const std::vector<base::Optional<std::string>>& values); + + // Ignores the accelerometer as the attributes are not expected. + void IgnoreAccelerometer(int32_t id); + // Checks and sets |initialization_state_| if all information is retrieved. + void CheckInitialization(); + + // Creates the AccelerometerSamplesObserver for the accelerometer with |id|. + void CreateAccelerometerSamplesObserver(int32_t id); + + // Controls accelerometer reading. + void EnableAccelerometerReading(); + void DisableAccelerometerReading(); + + // Called by |observers_|, containing a sample of the accelerometer. + void OnSampleUpdatedCallback(int iio_device_id, std::vector<float> sample); + + void SetEmitEventsOnThread(bool emit_events); + + // Sets FAILED to |initialization_state_| due to an error. + void FailedToInitialize(); + + void AddObserverOnThread(AccelerometerReader::Observer* observer); + + // The task runner to use for blocking tasks. + scoped_refptr<base::SequencedTaskRunner> task_runner_; + + // The Mojo channel connecting to Sensor Hal Dispatcher. + mojo::Receiver<chromeos::sensors::mojom::SensorHalClient> sensor_hal_client_; + + // The Mojo channel to query and request for devices. + mojo::Remote<chromeos::sensors::mojom::SensorService> sensor_service_remote_; + + // The existence of the accelerometer on the base. + bool has_accelerometer_base_ = false; + + // First is the accelerometer's iio device id, second is it's data, mojo + // remote and samples observer. + std::map<int32_t, AccelerometerData> accelerometers_; + + // First is the location index, second is the id of the accelerometer being + // used in this reader. + std::map<AccelerometerSource, int32_t> location_to_accelerometer_id_; + + // The flag to delay |OnTabletPhysicalStateChanged| until + // |ec_lid_angle_driver_status_| is set. + bool pending_on_tablet_physical_state_changed_ = false; + + // One time read upon |AddObserverOnThread|. + // Some observers need to know ECLidAngleDriverStatus, and it's guaranteed to + // be set before reading samples. When adding an observer, trigger at least + // one sample to notify observers that ECLidAngleDriverStatus has been set. + bool one_time_read_ = false; + + // True if periodical accelerometer read is on. + bool accelerometer_read_on_ = false; + + bool emit_events_ = true; + + // The observers to notify of accelerometer updates. + scoped_refptr<base::ObserverListThreadSafe<AccelerometerReader::Observer>> + observers_; + + // The last seen accelerometer data. + scoped_refptr<AccelerometerUpdate> update_; + + SEQUENCE_CHECKER(sequence_checker_); +}; + +} // namespace ash + +#endif // ASH_ACCELEROMETER_ACCELEROMETER_PROVIDER_MOJO_H_
diff --git a/ash/accelerometer/accelerometer_provider_mojo_unittest.cc b/ash/accelerometer/accelerometer_provider_mojo_unittest.cc new file mode 100644 index 0000000..6109fb4 --- /dev/null +++ b/ash/accelerometer/accelerometer_provider_mojo_unittest.cc
@@ -0,0 +1,274 @@ +// Copyright 2020 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 "ash/accelerometer/accelerometer_provider_mojo.h" + +#include <memory> +#include <utility> + +#include "ash/accelerometer/accelerometer_constants.h" +#include "ash/accelerometer/accelerometer_reader.h" +#include "base/memory/scoped_refptr.h" +#include "base/run_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/test/task_environment.h" +#include "chromeos/components/sensors/fake_sensor_device.h" +#include "chromeos/components/sensors/fake_sensor_hal_server.h" +#include "chromeos/components/sensors/sensor_hal_dispatcher.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace ash { + +namespace { + +constexpr double kFakeScaleValue = 10.0; + +constexpr int kFakeLidAccelerometerId = 1; +constexpr int kFakeBaseAccelerometerId = 2; +constexpr int kFakeLidAngleId = 3; + +constexpr int64_t kFakeSampleData[] = {1, 2, 3}; + +class FakeObserver : public AccelerometerReader::Observer { + public: + void OnAccelerometerUpdated( + scoped_refptr<const AccelerometerUpdate> update) override { + if (!update.get()) + return; + + for (uint32_t index = 0; index < ACCELEROMETER_SOURCE_COUNT; ++index) { + auto source = static_cast<AccelerometerSource>(index); + if (!update->has(source)) + continue; + + EXPECT_EQ(update->get(source).x, kFakeSampleData[0] * kFakeScaleValue); + EXPECT_EQ(update->get(source).y, kFakeSampleData[1] * kFakeScaleValue); + EXPECT_EQ(update->get(source).z, kFakeSampleData[2] * kFakeScaleValue); + } + + update_ = update; + } + + scoped_refptr<const AccelerometerUpdate> update_; +}; + +class AccelerometerProviderMojoTest : public ::testing::Test { + protected: + void SetUp() override { + sensor_hal_server_ = + std::make_unique<chromeos::sensors::FakeSensorHalServer>(); + provider_ = new AccelerometerProviderMojo(); + + chromeos::sensors::SensorHalDispatcher::Initialize(); + provider_->PrepareAndInitialize(); + provider_->AddObserver(&observer_); + + AddDevice(kFakeLidAccelerometerId, + chromeos::sensors::mojom::DeviceType::ACCEL, + base::NumberToString(kFakeScaleValue), + kLocationStrings[ACCELEROMETER_SOURCE_SCREEN]); + } + + void TearDown() override { + chromeos::sensors::SensorHalDispatcher::Shutdown(); + } + + void AddDevice(int32_t iio_device_id, + chromeos::sensors::mojom::DeviceType type, + base::Optional<std::string> scale, + base::Optional<std::string> location) { + std::set<chromeos::sensors::mojom::DeviceType> types; + types.emplace(type); + + std::vector<chromeos::sensors::FakeSensorDevice::ChannelData> channels_data; + if (type == chromeos::sensors::mojom::DeviceType::ACCEL) { + channels_data.resize(kNumberOfAxes); + for (uint32_t i = 0; i < kNumberOfAxes; ++i) { + channels_data[i].id = kAccelerometerChannels[i]; + channels_data[i].sample_data = kFakeSampleData[i]; + } + } + + std::unique_ptr<chromeos::sensors::FakeSensorDevice> sensor_device( + new chromeos::sensors::FakeSensorDevice(std::move(channels_data))); + if (scale.has_value()) + sensor_device->SetAttribute(chromeos::sensors::mojom::kScale, + scale.value()); + if (location.has_value()) + sensor_device->SetAttribute(chromeos::sensors::mojom::kLocation, + location.value()); + + sensor_hal_server_->GetSensorService()->SetDevice( + iio_device_id, std::move(types), std::move(sensor_device)); + } + + FakeObserver observer_; + std::unique_ptr<chromeos::sensors::FakeSensorHalServer> sensor_hal_server_; + scoped_refptr<AccelerometerProviderMojo> provider_; + + base::test::SingleThreadTaskEnvironment task_environment; +}; + +TEST_F(AccelerometerProviderMojoTest, CheckNoScale) { + AddDevice(kFakeBaseAccelerometerId, + chromeos::sensors::mojom::DeviceType::ACCEL, base::nullopt, + kLocationStrings[ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD]); + + chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer( + sensor_hal_server_->PassRemote()); + + // Wait until initialization failed. + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(provider_->GetInitializationStateForTesting(), State::FAILED); +} + +TEST_F(AccelerometerProviderMojoTest, CheckNoLocation) { + AddDevice(kFakeBaseAccelerometerId, + chromeos::sensors::mojom::DeviceType::ACCEL, + base::NumberToString(kFakeScaleValue), base::nullopt); + + chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer( + sensor_hal_server_->PassRemote()); + + // Wait until initialization failed. + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(provider_->GetInitializationStateForTesting(), State::SUCCESS); +} + +TEST_F(AccelerometerProviderMojoTest, GetSamplesOfOneAccel) { + chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer( + sensor_hal_server_->PassRemote()); + + // Wait until a sample is received. + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(provider_->GetInitializationStateForTesting(), State::SUCCESS); + + EXPECT_TRUE(observer_.update_.get()); + EXPECT_TRUE(observer_.update_->has(ACCELEROMETER_SOURCE_SCREEN)); + EXPECT_FALSE(observer_.update_->has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD)); + + observer_.update_.reset(); + + // Simulate a disconnection of the accelerometer's mojo channel in IIO + // Service. + AddDevice(kFakeLidAccelerometerId, + chromeos::sensors::mojom::DeviceType::ACCEL, + base::NumberToString(kFakeScaleValue), + kLocationStrings[ACCELEROMETER_SOURCE_SCREEN]); + + // Wait until the accelerometer's mojo channel is re-established and a sample + // is received. + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(observer_.update_.get()); + EXPECT_TRUE(observer_.update_->has(ACCELEROMETER_SOURCE_SCREEN)); + EXPECT_FALSE(observer_.update_->has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD)); +} + +TEST_F(AccelerometerProviderMojoTest, GetSamplesWithNoLidAngle) { + AddDevice(kFakeBaseAccelerometerId, + chromeos::sensors::mojom::DeviceType::ACCEL, + base::NumberToString(kFakeScaleValue), + kLocationStrings[ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD]); + + chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer( + sensor_hal_server_->PassRemote()); + + // Wait until samples are received. + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(provider_->GetInitializationStateForTesting(), State::SUCCESS); + + EXPECT_TRUE(observer_.update_.get()); + EXPECT_TRUE(observer_.update_->has(ACCELEROMETER_SOURCE_SCREEN)); + EXPECT_TRUE(observer_.update_->has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD)); + + observer_.update_.reset(); + + // Simulate a disconnection of the accelerometer's mojo channel in IIO + // Service. + AddDevice(kFakeLidAccelerometerId, + chromeos::sensors::mojom::DeviceType::ACCEL, + base::NumberToString(kFakeScaleValue), + kLocationStrings[ACCELEROMETER_SOURCE_SCREEN]); + + // Wait until the accelerometer's mojo channel is re-established and a sample + // is received. + base::RunLoop().RunUntilIdle(); + + // Get the second sample from only one accelerometer. + EXPECT_FALSE(observer_.update_.get()); + + // Simulate a disconnection of the other accelerometer's mojo channel in IIO + // Service. + AddDevice(kFakeBaseAccelerometerId, + chromeos::sensors::mojom::DeviceType::ACCEL, + base::NumberToString(kFakeScaleValue), + kLocationStrings[ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD]); + + // Wait until the other accelerometer's mojo channel is re-established and a + // sample is received. + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(observer_.update_.get()); + EXPECT_TRUE(observer_.update_->has(ACCELEROMETER_SOURCE_SCREEN)); + EXPECT_TRUE(observer_.update_->has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD)); +} + +TEST_F(AccelerometerProviderMojoTest, GetSamplesWithLidAngle) { + AddDevice(kFakeBaseAccelerometerId, + chromeos::sensors::mojom::DeviceType::ACCEL, + base::NumberToString(kFakeScaleValue), + kLocationStrings[ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD]); + AddDevice(kFakeLidAngleId, chromeos::sensors::mojom::DeviceType::ANGL, + base::nullopt, base::nullopt); + + chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer( + sensor_hal_server_->PassRemote()); + + // Wait until all setups are finished and the one time read is done. + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(provider_->GetInitializationStateForTesting(), State::SUCCESS); + EXPECT_TRUE(observer_.update_.get()); + EXPECT_TRUE(observer_.update_->has(ACCELEROMETER_SOURCE_SCREEN)); + EXPECT_FALSE(observer_.update_->has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD)); + + observer_.update_.reset(); + + provider_->TriggerRead(); + + // Wait until samples are received. + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(observer_.update_.get()); + EXPECT_TRUE(observer_.update_->has(ACCELEROMETER_SOURCE_SCREEN)); + EXPECT_FALSE(observer_.update_->has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD)); + + observer_.update_.reset(); + + // Simulate a disconnection of IIO Service. + sensor_hal_server_->GetSensorService()->OnServiceDisconnect(); + sensor_hal_server_->OnServerDisconnect(); + + // Wait until the disconnect arrives at the dispatcher. + base::RunLoop().RunUntilIdle(); + + chromeos::sensors::SensorHalDispatcher::GetInstance()->RegisterServer( + sensor_hal_server_->PassRemote()); + + // Wait until samples are received. + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(observer_.update_.get()); + EXPECT_TRUE(observer_.update_->has(ACCELEROMETER_SOURCE_SCREEN)); + EXPECT_FALSE(observer_.update_->has(ACCELEROMETER_SOURCE_ATTACHED_KEYBOARD)); +} + +} // namespace + +} // namespace ash
diff --git a/ash/accelerometer/accelerometer_reader.cc b/ash/accelerometer/accelerometer_reader.cc index 0d0e9f6..c26ef06 100644 --- a/ash/accelerometer/accelerometer_reader.cc +++ b/ash/accelerometer/accelerometer_reader.cc
@@ -4,22 +4,31 @@ #include "ash/accelerometer/accelerometer_reader.h" +#include <grp.h> + #include "ash/accelerometer/accelerometer_file_reader.h" +#include "ash/accelerometer/accelerometer_provider_mojo.h" #include "base/memory/singleton.h" +#include "base/posix/eintr_wrapper.h" #include "base/sequenced_task_runner.h" +#include "base/threading/sequenced_task_runner_handle.h" namespace ash { +namespace { + +// Group name of IIO Service, used to check if IIO Service exists. +constexpr char kIioServiceGroupName[] = "iioservice"; + +} // namespace + // static AccelerometerReader* AccelerometerReader::GetInstance() { return base::Singleton<AccelerometerReader>::get(); } -void AccelerometerReader::Initialize( - scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner) { - DCHECK(sequenced_task_runner.get()); - - accelerometer_provider_->PrepareAndInitialize(sequenced_task_runner); +void AccelerometerReader::Initialize() { + accelerometer_provider_->PrepareAndInitialize(); } void AccelerometerReader::AddObserver(Observer* observer) { @@ -52,8 +61,19 @@ ec_lid_angle_driver_status); } -AccelerometerReader::AccelerometerReader() - : accelerometer_provider_(new AccelerometerFileReader()) {} +AccelerometerReader::AccelerometerReader() { + char buf[1024]; + struct group result; + struct group* resultp; + + if (HANDLE_EINTR(getgrnam_r(kIioServiceGroupName, &result, buf, sizeof(buf), + &resultp)) < 0 || + !resultp) { + accelerometer_provider_ = new AccelerometerFileReader(); + } else { + accelerometer_provider_ = new AccelerometerProviderMojo(); + } +} AccelerometerReader::~AccelerometerReader() = default;
diff --git a/ash/accelerometer/accelerometer_reader.h b/ash/accelerometer/accelerometer_reader.h index e1630a2..d2b5170 100644 --- a/ash/accelerometer/accelerometer_reader.h +++ b/ash/accelerometer/accelerometer_reader.h
@@ -19,6 +19,8 @@ namespace ash { +enum class State { INITIALIZING, SUCCESS, FAILED }; + enum class ECLidAngleDriverStatus { UNKNOWN, SUPPORTED, NOT_SUPPORTED }; class AccelerometerProviderInterface; @@ -39,8 +41,7 @@ static AccelerometerReader* GetInstance(); - void Initialize( - scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner); + void Initialize(); // Add/Remove observers. void AddObserver(Observer* observer); @@ -79,10 +80,8 @@ class AccelerometerProviderInterface : public base::RefCountedThreadSafe<AccelerometerProviderInterface> { public: - // Prepare and start async initialization. SetSensorClient function - // contains actual code for initialization. - virtual void PrepareAndInitialize( - scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner) = 0; + // Prepare and start async initialization. + virtual void PrepareAndInitialize() = 0; // Add/Remove observers. virtual void AddObserver(AccelerometerReader::Observer* observer) = 0; @@ -104,6 +103,9 @@ protected: virtual ~AccelerometerProviderInterface() = default; + // The current initialization state of reader. + State initialization_state_ = State::INITIALIZING; + // State of ChromeOS EC lid angle driver, if SUPPORTED, it means EC can handle // lid angle calculation. ECLidAngleDriverStatus ec_lid_angle_driver_status_ =
diff --git a/ash/accelerometer/accelerometer_samples_observer.cc b/ash/accelerometer/accelerometer_samples_observer.cc new file mode 100644 index 0000000..e383bf8 --- /dev/null +++ b/ash/accelerometer/accelerometer_samples_observer.cc
@@ -0,0 +1,249 @@ +// Copyright 2020 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 "ash/accelerometer/accelerometer_samples_observer.h" + +#include <utility> + +#include "base/bind.h" + +namespace ash { + +namespace { + +constexpr int kTimeoutToleranceInMilliseconds = 500; +constexpr double kReadFrequencyInHz = 10.0; + +} // namespace + +AccelerometerSamplesObserver::AccelerometerSamplesObserver( + int iio_device_id, + mojo::Remote<chromeos::sensors::mojom::SensorDevice> sensor_device_remote, + float scale, + OnSampleUpdatedCallback on_sample_updated_callback) + : iio_device_id_(iio_device_id), + sensor_device_remote_(std::move(sensor_device_remote)), + scale_(scale), + on_sample_updated_callback_(std::move(on_sample_updated_callback)) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(sensor_device_remote_.is_bound()); + + sensor_device_remote_->GetAllChannelIds( + base::BindOnce(&AccelerometerSamplesObserver::GetAllChannelIdsCallback, + weak_factory_.GetWeakPtr())); +} + +AccelerometerSamplesObserver::~AccelerometerSamplesObserver() = default; + +void AccelerometerSamplesObserver::SetEnabled(bool enabled) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (enabled_ == enabled) + return; + + enabled_ = enabled; + + UpdateSensorDeviceFrequency(); +} + +void AccelerometerSamplesObserver::OnSampleUpdated( + const base::flat_map<int32_t, int64_t>& sample) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (sample.size() != kNumberOfAxes) { + LOG(ERROR) << "Invalid sample with size: " << sample.size(); + OnErrorOccurred(chromeos::sensors::mojom::ObserverErrorType::READ_FAILED); + return; + } + + std::vector<float> output_sample; + for (size_t axes = 0; axes < kNumberOfAxes; ++axes) { + auto it = sample.find(channel_indices_[axes]); + if (it == sample.end()) { + LOG(ERROR) << "Missing channel: " << kAccelerometerChannels[axes] + << " in sample"; + OnErrorOccurred(chromeos::sensors::mojom::ObserverErrorType::READ_FAILED); + return; + } + + output_sample.push_back(it->second * scale_); + } + + on_sample_updated_callback_.Run(iio_device_id_, output_sample); +} + +void AccelerometerSamplesObserver::OnErrorOccurred( + chromeos::sensors::mojom::ObserverErrorType type) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + switch (type) { + case chromeos::sensors::mojom::ObserverErrorType::ALREADY_STARTED: + LOG(ERROR) << "Device " << iio_device_id_ + << ": Another observer has already started to read samples"; + Reset(); + break; + + case chromeos::sensors::mojom::ObserverErrorType::FREQUENCY_INVALID: + if (!enabled_) // It's normal if this observer is not enabled + break; + + LOG(ERROR) << "Device " << iio_device_id_ + << ": Observer started with an invalid frequency"; + UpdateSensorDeviceFrequency(); + + break; + + case chromeos::sensors::mojom::ObserverErrorType::NO_ENABLED_CHANNELS: + LOG(ERROR) << "Device " << iio_device_id_ + << ": Observer started with no channels enabled"; + if (sensor_device_remote_.is_bound()) { + sensor_device_remote_->SetChannelsEnabled( + std::vector<int32_t>(channel_indices_, + channel_indices_ + kNumberOfAxes), + /*enable=*/true, + base::BindOnce( + &AccelerometerSamplesObserver::SetChannelsEnabledCallback, + weak_factory_.GetWeakPtr())); + } + + break; + + case chromeos::sensors::mojom::ObserverErrorType::SET_FREQUENCY_IO_FAILED: + LOG(ERROR) << "Device " << iio_device_id_ + << ": Failed to set frequency to the physical device"; + break; + + case chromeos::sensors::mojom::ObserverErrorType::GET_FD_FAILED: + LOG(ERROR) << "Device " << iio_device_id_ + << ": Failed to get the device's fd to poll on"; + break; + + case chromeos::sensors::mojom::ObserverErrorType::READ_FAILED: + LOG(ERROR) << "Device " << iio_device_id_ << ": Failed to read a sample"; + break; + + case chromeos::sensors::mojom::ObserverErrorType::READ_TIMEOUT: + LOG(ERROR) << "Device " << iio_device_id_ << ": A read timed out"; + break; + + default: + LOG(ERROR) << "Device " << iio_device_id_ << ": error " + << static_cast<int>(type); + break; + } +} + +void AccelerometerSamplesObserver::Reset() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + LOG(ERROR) << "Resetting AccelerometerSamplesObserver: " << iio_device_id_; + receiver_.reset(); + sensor_device_remote_.reset(); +} + +void AccelerometerSamplesObserver::GetAllChannelIdsCallback( + const std::vector<std::string>& iio_channel_ids) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(sensor_device_remote_.is_bound()); + + iio_channel_ids_ = std::move(iio_channel_ids); + + for (size_t axis = 0; axis < kNumberOfAxes; ++axis) { + bool found = false; + for (size_t channel_index = 0; channel_index < iio_channel_ids_.size(); + ++channel_index) { + if (iio_channel_ids_[channel_index].compare( + kAccelerometerChannels[axis]) == 0) { + found = true; + channel_indices_[axis] = channel_index; + break; + } + } + + if (!found) { + LOG(ERROR) << "Missing channel: " << kAccelerometerChannels[axis]; + Reset(); + return; + } + } + + sensor_device_remote_->SetChannelsEnabled( + std::vector<int32_t>(channel_indices_, channel_indices_ + kNumberOfAxes), + /*enable=*/true, + base::BindOnce(&AccelerometerSamplesObserver::SetChannelsEnabledCallback, + weak_factory_.GetWeakPtr())); + + StartReading(); +} + +void AccelerometerSamplesObserver::StartReading() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(sensor_device_remote_.is_bound()); + + sensor_device_remote_->SetTimeout(kTimeoutToleranceInMilliseconds); + + sensor_device_remote_->StartReadingSamples(GetPendingRemote()); +} + +void AccelerometerSamplesObserver::UpdateSensorDeviceFrequency() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (!sensor_device_remote_.is_bound()) + return; + + sensor_device_remote_->SetFrequency( + enabled_ ? kReadFrequencyInHz : 0.0, + base::BindOnce(&AccelerometerSamplesObserver::SetFrequencyCallback, + weak_factory_.GetWeakPtr())); +} + +mojo::PendingRemote<chromeos::sensors::mojom::SensorDeviceSamplesObserver> +AccelerometerSamplesObserver::GetPendingRemote() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + auto pending_remote = receiver_.BindNewPipeAndPassRemote(); + + receiver_.set_disconnect_handler(base::BindOnce( + &AccelerometerSamplesObserver::ObserverConnectionErrorCallback, + weak_factory_.GetWeakPtr())); + return pending_remote; +} + +void AccelerometerSamplesObserver::ObserverConnectionErrorCallback() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + LOG(ERROR) << "Observer connection error"; + receiver_.reset(); + + if (sensor_device_remote_.is_bound()) + StartReading(); +} + +void AccelerometerSamplesObserver::SetFrequencyCallback( + double result_frequency) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if ((result_frequency > 0.0 && enabled_) || + (result_frequency == 0.0 && !enabled_)) { + return; + } + + LOG(ERROR) << "Failed to set frequency: " << result_frequency + << " with the samples observer enabled: " + << (enabled_ ? "true" : "false"); + Reset(); +} + +void AccelerometerSamplesObserver::SetChannelsEnabledCallback( + const std::vector<int32_t>& failed_indices) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + for (int32_t index : failed_indices) + LOG(ERROR) << "Failed to enable " << iio_channel_ids_[index]; + + if (!failed_indices.empty()) + Reset(); +} + +} // namespace ash
diff --git a/ash/accelerometer/accelerometer_samples_observer.h b/ash/accelerometer/accelerometer_samples_observer.h new file mode 100644 index 0000000..6f84bf27 --- /dev/null +++ b/ash/accelerometer/accelerometer_samples_observer.h
@@ -0,0 +1,100 @@ +// Copyright 2020 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 ASH_ACCELEROMETER_ACCELEROMETER_SAMPLES_OBSERVER_H_ +#define ASH_ACCELEROMETER_ACCELEROMETER_SAMPLES_OBSERVER_H_ + +#include <stdint.h> +#include <memory> +#include <string> +#include <vector> + +#include "ash/accelerometer/accelerometer_constants.h" +#include "ash/ash_export.h" +#include "base/sequence_checker.h" +#include "base/sequenced_task_runner.h" +#include "chromeos/components/sensors/sensor_hal_dispatcher.h" +#include "mojo/public/cpp/bindings/receiver.h" +#include "mojo/public/cpp/bindings/remote.h" + +namespace ash { + +// A SamplesObserver for an accelerometer device. When a sample is updated from +// IIO Service, it's sent to the AccelerometerProviderMojo via the callback +// |on_sample_udpated_callback_| registered in the constructor. +// AccelerometerSamplesObserver should only be used on the UI thread. +class ASH_EXPORT AccelerometerSamplesObserver + : public chromeos::sensors::mojom::SensorDeviceSamplesObserver { + public: + using OnSampleUpdatedCallback = + base::RepeatingCallback<void(int iio_device_id, + std::vector<float> sample)>; + + AccelerometerSamplesObserver( + int iio_device_id, + mojo::Remote<chromeos::sensors::mojom::SensorDevice> sensor_device_remote, + float scale, + OnSampleUpdatedCallback on_sample_updated_callback); + AccelerometerSamplesObserver(const AccelerometerSamplesObserver&) = delete; + AccelerometerSamplesObserver& operator=(const AccelerometerSamplesObserver&) = + delete; + ~AccelerometerSamplesObserver() override; + + // Sets the observer |enabled| by setting the frequency to iioservice. + // Should be called on |task_runner_|. + void SetEnabled(bool enabled); + + // chromeos::sensors::mojom::SensorDeviceSamplesObserver overrides: + void OnSampleUpdated(const base::flat_map<int32_t, int64_t>& sample) override; + void OnErrorOccurred( + chromeos::sensors::mojom::ObserverErrorType type) override; + + private: + void Reset(); + + void GetAllChannelIdsCallback( + const std::vector<std::string>& iio_channel_ids); + void StartReading(); + + // Update this sensor device's frequency to kReadFrequencyInHz if |enabled_| + // is true, and to 0 if |enabled_| is false. + void UpdateSensorDeviceFrequency(); + + mojo::PendingRemote<chromeos::sensors::mojom::SensorDeviceSamplesObserver> + GetPendingRemote(); + + void ObserverConnectionErrorCallback(); + void SetFrequencyCallback(double result_frequency); + void SetChannelsEnabledCallback(const std::vector<int32_t>& failed_indices); + + int iio_device_id_; + mojo::Remote<chromeos::sensors::mojom::SensorDevice> sensor_device_remote_; + + double scale_; + + // Callback to send samples to the owner of this class. + OnSampleUpdatedCallback on_sample_updated_callback_; + + // Boolean to indicate if this accelerometer should set a valid frequency and + // keep reading samples. + bool enabled_ = false; + + // The list of channel ids retrieved from iioservice. Use channels' indices + // in this list to identify them. + std::vector<std::string> iio_channel_ids_; + // Channel indices (of accel_x, accel_y, and accel_z respectively) to + // enable. + int32_t channel_indices_[kNumberOfAxes]; + + mojo::Receiver<chromeos::sensors::mojom::SensorDeviceSamplesObserver> + receiver_{this}; + + SEQUENCE_CHECKER(sequence_checker_); + + base::WeakPtrFactory<AccelerometerSamplesObserver> weak_factory_{this}; +}; + +} // namespace ash + +#endif // ASH_ACCELEROMETER_ACCELEROMETER_SAMPLES_OBSERVER_H_
diff --git a/ash/accelerometer/accelerometer_samples_observer_unittest.cc b/ash/accelerometer/accelerometer_samples_observer_unittest.cc new file mode 100644 index 0000000..de02b5f --- /dev/null +++ b/ash/accelerometer/accelerometer_samples_observer_unittest.cc
@@ -0,0 +1,149 @@ +// Copyright 2020 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 "ash/accelerometer/accelerometer_samples_observer.h" + +#include <memory> +#include <utility> + +#include "ash/accelerometer/accelerometer_constants.h" +#include "base/run_loop.h" +#include "base/test/task_environment.h" +#include "chromeos/components/sensors/fake_sensor_device.h" +#include "mojo/public/cpp/bindings/remote.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace ash { + +namespace { + +constexpr int kFakeAccelerometerId = 1; + +constexpr int64_t kFakeSampleData[] = {1, 2, 3}; + +constexpr double kFakeScaleValue = 10.0; + +class AccelerometerSamplesObserverTest : public ::testing::Test { + protected: + void SetChannels(uint32_t num_of_axes) { + CHECK_LE(num_of_axes, kNumberOfAxes); + std::vector<chromeos::sensors::FakeSensorDevice::ChannelData> channels_data( + num_of_axes); + for (uint32_t i = 0; i < num_of_axes; ++i) { + channels_data[i].id = kAccelerometerChannels[i]; + channels_data[i].sample_data = kFakeSampleData[i]; + } + + sensor_device_ = std::make_unique<chromeos::sensors::FakeSensorDevice>( + std::move(channels_data)); + } + + void SetObserver( + mojo::Remote<chromeos::sensors::mojom::SensorDevice> accelerometer) { + observer_ = std::make_unique<AccelerometerSamplesObserver>( + kFakeAccelerometerId, std::move(accelerometer), kFakeScaleValue, + base::BindRepeating( + &AccelerometerSamplesObserverTest::OnSampleUpdatedCallback, + base::Unretained(this))); + } + + void OnSampleUpdatedCallback(int iio_device_id, std::vector<float> sample) { + EXPECT_EQ(iio_device_id, kFakeAccelerometerId); + EXPECT_EQ(sample.size(), kNumberOfAxes); + for (uint32_t i = 0; i < kNumberOfAxes; ++i) + EXPECT_EQ(sample[i], kFakeSampleData[i] * kFakeScaleValue); + + ++num_samples_; + } + + void DisableFirstChannel() { + sensor_device_->SetChannelsEnabled( + {0}, false, + base::BindOnce( + &AccelerometerSamplesObserverTest::SetChannelsEnabledCallback, + base::Unretained(this))); + } + + void SetChannelsEnabledCallback(const std::vector<int32_t>& failed_indices) { + EXPECT_EQ(failed_indices.size(), 0u); + } + + std::unique_ptr<chromeos::sensors::FakeSensorDevice> sensor_device_; + std::unique_ptr<AccelerometerSamplesObserver> observer_; + + int num_samples_ = 0; + + base::test::SingleThreadTaskEnvironment task_environment; +}; + +TEST_F(AccelerometerSamplesObserverTest, MissingChannels) { + SetChannels(kNumberOfAxes - 1); + + mojo::Remote<chromeos::sensors::mojom::SensorDevice> accelerometer; + sensor_device_->AddReceiver(accelerometer.BindNewPipeAndPassReceiver()); + SetObserver(std::move(accelerometer)); + + // Wait until the mojo connection is reset. + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(sensor_device_->HasReceivers()); +} + +TEST_F(AccelerometerSamplesObserverTest, StartReadingTwiceError) { + SetChannels(kNumberOfAxes); + + mojo::Remote<chromeos::sensors::mojom::SensorDevice> accelerometer; + sensor_device_->AddReceiver(accelerometer.BindNewPipeAndPassReceiver()); + + mojo::PendingRemote<chromeos::sensors::mojom::SensorDeviceSamplesObserver> + pending_remote; + auto null_receiver = pending_remote.InitWithNewPipeAndPassReceiver(); + accelerometer->StartReadingSamples(std::move(pending_remote)); + + SetObserver(std::move(accelerometer)); + + // Wait until the mojo connection is reset. + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(sensor_device_->HasReceivers()); +} + +TEST_F(AccelerometerSamplesObserverTest, GetSamples) { + SetChannels(kNumberOfAxes); + + mojo::Remote<chromeos::sensors::mojom::SensorDevice> accelerometer; + sensor_device_->AddReceiver(accelerometer.BindNewPipeAndPassReceiver()); + + SetObserver(std::move(accelerometer)); + observer_->SetEnabled(true); + + // Wait until a sample is received. + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(sensor_device_->HasReceivers()); + EXPECT_EQ(num_samples_, 1); + + // Simulate a disconnection of the observer's mojo channel in IIO Service. + sensor_device_->StopReadingSamples(); + + // Wait until the observer's mojo channel is re-established and a sample is + // received. + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(sensor_device_->HasReceivers()); + EXPECT_EQ(num_samples_, 2); + + DisableFirstChannel(); + + // Wait until a sample is received. + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(sensor_device_->HasReceivers()); + // The updated sample is not sent to |OnSampleUpdatedCallback|. + EXPECT_EQ(num_samples_, 2); +} + +} // namespace + +} // namespace ash
diff --git a/ash/shell.cc b/ash/shell.cc index c6cba6f..1f3a868 100644 --- a/ash/shell.cc +++ b/ash/shell.cc
@@ -175,8 +175,6 @@ #include "base/notreached.h" #include "base/system/sys_info.h" #include "base/task/post_task.h" -#include "base/task/task_traits.h" -#include "base/task/thread_pool.h" #include "base/trace_event/trace_event.h" #include "chromeos/components/bloom/public/cpp/bloom_controller.h" #include "chromeos/constants/chromeos_features.h" @@ -566,14 +564,7 @@ // Ash doesn't properly remove pre-target-handlers. ui::EventHandler::DisableCheckTargets(); - // AccelerometerReader is important for screen orientation so we need - // USER_VISIBLE priority. - // Use CONTINUE_ON_SHUTDOWN to avoid blocking shutdown since the data reading - // could get blocked on certain devices. See https://crbug.com/1023989. - AccelerometerReader::GetInstance()->Initialize( - base::ThreadPool::CreateSequencedTaskRunner( - {base::MayBlock(), base::TaskPriority::USER_VISIBLE, - base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})); + AccelerometerReader::GetInstance()->Initialize(); login_screen_controller_ = std::make_unique<LoginScreenController>(system_tray_notifier_.get());
diff --git a/base/allocator/partition_allocator/address_pool_manager.cc b/base/allocator/partition_allocator/address_pool_manager.cc index b6681df..a95de280 100644 --- a/base/allocator/partition_allocator/address_pool_manager.cc +++ b/base/allocator/partition_allocator/address_pool_manager.cc
@@ -15,7 +15,7 @@ #include "base/allocator/partition_allocator/page_allocator_constants.h" #include "base/allocator/partition_allocator/page_allocator_internal.h" #include "base/allocator/partition_allocator/partition_alloc_check.h" -#include "base/bits.h" +#include "base/allocator/partition_allocator/partition_alloc_constants.h" #include "base/lazy_instance.h" #include "base/notreached.h" #include "base/stl_util.h" @@ -142,8 +142,8 @@ uintptr_t AddressPoolManager::Pool::FindChunk(size_t requested_size) { base::AutoLock scoped_lock(lock_); - const size_t required_size = bits::Align(requested_size, kSuperPageSize); - const size_t need_bits = required_size >> kSuperPageShift; + PA_CHECK(!(requested_size & kSuperPageOffsetMask)); + const size_t need_bits = requested_size >> kSuperPageShift; // Use first-fit policy to find an available chunk from free chunks. Start // from |bit_hint_|, because we know there are no free chunks before. @@ -183,7 +183,7 @@ } uintptr_t address = address_begin_ + beg_bit * kSuperPageSize; #if DCHECK_IS_ON() - PA_DCHECK(address + required_size <= address_end_); + PA_DCHECK(address + requested_size <= address_end_); #endif return address; } @@ -196,16 +196,16 @@ void AddressPoolManager::Pool::FreeChunk(uintptr_t address, size_t free_size) { base::AutoLock scoped_lock(lock_); - PA_DCHECK(!(address & kSuperPageOffsetMask)); + PA_CHECK(!(address & kSuperPageOffsetMask)); + PA_CHECK(!(free_size & kSuperPageOffsetMask)); - const size_t size = bits::Align(free_size, kSuperPageSize); DCHECK_LE(address_begin_, address); #if DCHECK_IS_ON() - PA_DCHECK(address + size <= address_end_); + PA_DCHECK(address + free_size <= address_end_); #endif const size_t beg_bit = (address - address_begin_) / kSuperPageSize; - const size_t end_bit = beg_bit + size / kSuperPageSize; + const size_t end_bit = beg_bit + free_size / kSuperPageSize; for (size_t i = beg_bit; i < end_bit; ++i) { PA_DCHECK(alloc_bitset_.test(i)); alloc_bitset_.reset(i);
diff --git a/base/allocator/partition_allocator/partition_alloc_constants.h b/base/allocator/partition_allocator/partition_alloc_constants.h index 4ae2ca6..c147394 100644 --- a/base/allocator/partition_allocator/partition_alloc_constants.h +++ b/base/allocator/partition_allocator/partition_alloc_constants.h
@@ -256,7 +256,9 @@ // crbug.com/998048 for details. PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR ALWAYS_INLINE size_t MaxDirectMapped() { - return (1UL << 31) - PageAllocationGranularity(); + // Subtract kSuperPageSize to accommodate for alignment inside + // PartitionRoot::GetDirectMapReservedSize. + return (1UL << 31) - kSuperPageSize; } static const size_t kBitsPerSizeT = sizeof(void*) * CHAR_BIT;
diff --git a/base/allocator/partition_allocator/partition_alloc_unittest.cc b/base/allocator/partition_allocator/partition_alloc_unittest.cc index 8bb1c9ca..34b4d169 100644 --- a/base/allocator/partition_allocator/partition_alloc_unittest.cc +++ b/base/allocator/partition_allocator/partition_alloc_unittest.cc
@@ -22,6 +22,7 @@ #include "base/allocator/partition_allocator/partition_ref_count.h" #include "base/allocator/partition_allocator/partition_tag.h" #include "base/allocator/partition_allocator/partition_tag_bitmap.h" +#include "base/bits.h" #include "base/logging.h" #include "base/rand_util.h" #include "base/stl_util.h" @@ -95,8 +96,7 @@ 100, base::SystemPageSize(), base::SystemPageSize() + 1, - base::PartitionRoot< - base::internal::ThreadSafe>::Bucket::get_direct_map_size(100), + base::PartitionRoot<base::internal::ThreadSafe>::GetDirectMapSlotSize(100), 1 << 20, 1 << 21, }; @@ -1599,36 +1599,26 @@ #endif // !ENABLE_REF_COUNT_FOR_BACKUP_REF_PTR // Check that guard pages are present where expected. -TEST_F(PartitionAllocDeathTest, GuardPages) { -// PartitionAlloc adds PartitionPageSize() to the requested size -// (for metadata), and then rounds that size to PageAllocationGranularity(). -// To be able to reliably write one past a direct allocation, choose a size -// that's -// a) larger than kMaxBucketed (to make the allocation direct) -// b) aligned at PageAllocationGranularity() boundaries after -// PartitionPageSize() has been added to it. -// (On 32-bit, PartitionAlloc adds another SystemPageSize() to the -// allocation size before rounding, but there it marks the memory right -// after size as inaccessible, so it's fine to write 1 past the size we -// hand to PartitionAlloc and we don't need to worry about allocation -// granularities.) -#define ALIGN(N, A) (((N) + (A)-1) / (A) * (A)) - const size_t kSize = ALIGN(kMaxBucketed + 1 + PartitionPageSize(), - PageAllocationGranularity()) - - PartitionPageSize(); -#undef ALIGN - EXPECT_GT(kSize, kMaxBucketed) - << "allocation not large enough for direct allocation"; - size_t size = kSize - kExtraAllocSize; - void* ptr = allocator.root()->Alloc(size, type_name); +TEST_F(PartitionAllocDeathTest, DirectMapGuardPages) { + const size_t kSizes[] = { + kMaxBucketed + kExtraAllocSize + 1, kMaxBucketed + SystemPageSize(), + kMaxBucketed + PartitionPageSize(), + bits::Align(kMaxBucketed + kSuperPageSize, kSuperPageSize) - + PartitionRoot<ThreadSafe>::GetDirectMapMetadataAndGuardPagesSize()}; + for (size_t size : kSizes) { + size -= kExtraAllocSize; + EXPECT_GT(size, kMaxBucketed) + << "allocation not large enough for direct allocation"; + void* ptr = allocator.root()->Alloc(size, type_name); - EXPECT_TRUE(ptr); - char* char_ptr = reinterpret_cast<char*>(ptr) - kPointerOffset; + EXPECT_TRUE(ptr); + char* char_ptr = reinterpret_cast<char*>(ptr) - kPointerOffset; - EXPECT_DEATH(*(char_ptr - 1) = 'A', ""); - EXPECT_DEATH(*(char_ptr + size + kExtraAllocSize) = 'A', ""); + EXPECT_DEATH(*(char_ptr - 1) = 'A', ""); + EXPECT_DEATH(*(char_ptr + bits::Align(size, SystemPageSize())) = 'A', ""); - allocator.root()->Free(ptr); + allocator.root()->Free(ptr); + } } #endif // !defined(OS_ANDROID) && !defined(OS_IOS) @@ -2591,6 +2581,132 @@ } } +TEST_F(PartitionAllocTest, Bookkeeping) { + auto& root = *allocator.root(); + + EXPECT_EQ(0U, root.total_size_of_committed_pages); + EXPECT_EQ(0U, root.total_size_of_super_pages); + size_t small_size = 1000 - kExtraAllocSize; + + // A full slot span of size 1 partition page is committed. + void* ptr = root.Alloc(small_size - kExtraAllocSize, type_name); + size_t expected_committed_size = PartitionPageSize(); + size_t expected_super_pages_size = kSuperPageSize; + EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages); + EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages); + + // Freeing memory doesn't result in decommitting pages right away. + root.Free(ptr); + EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages); + EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages); + + // Allocating the same size lands it in the same slot span. + ptr = root.Alloc(small_size - kExtraAllocSize, type_name); + EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages); + EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages); + + // Freeing memory doesn't result in decommitting pages right away. + root.Free(ptr); + EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages); + EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages); + + // Allocating another size commits another slot span. + ptr = root.Alloc(2 * small_size - kExtraAllocSize, type_name); + expected_committed_size += PartitionPageSize(); + EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages); + EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages); + + // Freeing memory doesn't result in decommitting pages right away. + root.Free(ptr); + EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages); + EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages); + + // Single-slot slot spans... + size_t big_size = kMaxBucketed - SystemPageSize(); + size_t bucket_index = SizeToIndex(big_size + kExtraAllocSize); + PartitionBucket<base::internal::ThreadSafe>* bucket = + &root.buckets[bucket_index]; + ASSERT_LT(big_size, bucket->get_bytes_per_span()); + ASSERT_NE(big_size % PartitionPageSize(), 0U); + ptr = root.Alloc(big_size - kExtraAllocSize, type_name); + expected_committed_size += bucket->get_bytes_per_span(); + EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages); + EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages); + + // Allocating 2nd time doesn't overflow the super page... + void* ptr2 = root.Alloc(big_size - kExtraAllocSize, type_name); + expected_committed_size += bucket->get_bytes_per_span(); + EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages); + EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages); + + // ... but 3rd time does. + void* ptr3 = root.Alloc(big_size - kExtraAllocSize, type_name); + expected_committed_size += bucket->get_bytes_per_span(); + expected_super_pages_size += kSuperPageSize; + EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages); + EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages); + + // Freeing memory doesn't result in decommitting pages right away. + root.Free(ptr); + root.Free(ptr2); + root.Free(ptr3); + EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages); + EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages); + + // Now everything should be decommitted. The reserved space for super pages + // stays the same and will never go away (by design). + root.PurgeMemory(PartitionPurgeDecommitEmptySlotSpans); + expected_committed_size = 0; + EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages); + EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages); + + // None of the above should affect the direct map space. + EXPECT_EQ(0U, root.total_size_of_direct_mapped_pages); + + size_t huge_sizes[] = { + kMaxBucketed + SystemPageSize(), + kMaxBucketed + SystemPageSize() + 123, + kSuperPageSize - PageAllocationGranularity(), + kSuperPageSize - SystemPageSize() - PartitionPageSize(), + kSuperPageSize - PartitionPageSize(), + kSuperPageSize - SystemPageSize(), + kSuperPageSize, + kSuperPageSize + SystemPageSize(), + kSuperPageSize + PartitionPageSize(), + kSuperPageSize + SystemPageSize() + PartitionPageSize(), + kSuperPageSize + PageAllocationGranularity(), + }; + for (size_t huge_size : huge_sizes) { + // For direct map, we commit only as many pages as needed. + size_t aligned_size = bits::Align(huge_size, SystemPageSize()); + ptr = root.Alloc(huge_size - kExtraAllocSize, type_name); + expected_committed_size += aligned_size; + size_t surrounding_pages_size = PartitionPageSize(); +#if !defined(PA_HAS_64_BITS_POINTERS) + surrounding_pages_size += SystemPageSize(); +#endif + size_t alignment = PageAllocationGranularity(); +#if defined(PA_HAS_64_BITS_POINTERS) + if (root.UsesGigaCage()) + alignment = kSuperPageSize; +#endif + size_t expected_direct_map_size = + bits::Align(aligned_size + surrounding_pages_size, alignment); + EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages); + EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages); + EXPECT_EQ(expected_direct_map_size, root.total_size_of_direct_mapped_pages); + + // Freeing memory in the diret map decommits pages right away. The address + // space is released for re-use too. + root.Free(ptr); + expected_committed_size -= aligned_size; + expected_direct_map_size = 0; + EXPECT_EQ(expected_committed_size, root.total_size_of_committed_pages); + EXPECT_EQ(expected_super_pages_size, root.total_size_of_super_pages); + EXPECT_EQ(expected_direct_map_size, root.total_size_of_direct_mapped_pages); + } +} + #if ENABLE_REF_COUNT_FOR_BACKUP_REF_PTR TEST_F(PartitionAllocTest, RefCountBasic) {
diff --git a/base/allocator/partition_allocator/partition_bucket.cc b/base/allocator/partition_allocator/partition_bucket.cc index 83c593e..bc0c289 100644 --- a/base/allocator/partition_allocator/partition_bucket.cc +++ b/base/allocator/partition_allocator/partition_bucket.cc
@@ -32,28 +32,11 @@ ALWAYS_INLINE SlotSpanMetadata<thread_safe>* PartitionDirectMap(PartitionRoot<thread_safe>* root, int flags, size_t raw_size) EXCLUSIVE_LOCKS_REQUIRED(root->lock_) { - size_t slot_size = - PartitionBucket<thread_safe>::get_direct_map_size(raw_size); - - // Because we need to fake looking like a super page, we need to allocate - // a bunch of system pages more than |slot_size|: - // - The first few system pages are the partition page in which the super - // page metadata is stored. We commit just one system page out of a partition - // page sized clump. - // - We add a trailing guard page on 32-bit (on 64-bit we rely on the - // massive address space plus randomization instead; additionally GigaCage - // guarantees that the region is in the company of regions that have leading - // guard pages). - size_t reserved_size = slot_size + PartitionPageSize(); -#if !defined(ARCH_CPU_64_BITS) - reserved_size += SystemPageSize(); -#endif - // Round up to the allocation granularity. - reserved_size = bits::Align(reserved_size, PageAllocationGranularity()); - size_t map_size = reserved_size - PartitionPageSize(); -#if !defined(ARCH_CPU_64_BITS) - map_size -= SystemPageSize(); -#endif + size_t slot_size = PartitionRoot<thread_safe>::GetDirectMapSlotSize(raw_size); + size_t reserved_size = root->GetDirectMapReservedSize(raw_size); + size_t map_size = + reserved_size - + PartitionRoot<thread_safe>::GetDirectMapMetadataAndGuardPagesSize(); PA_DCHECK(slot_size <= map_size); char* ptr = nullptr; @@ -71,22 +54,24 @@ if (UNLIKELY(!ptr)) return nullptr; - size_t committed_page_size = slot_size + SystemPageSize(); - root->total_size_of_direct_mapped_pages.fetch_add(committed_page_size, + root->total_size_of_direct_mapped_pages.fetch_add(reserved_size, std::memory_order_relaxed); - root->IncreaseCommittedPages(committed_page_size); + root->IncreaseCommittedPages(slot_size); - char* slot = ptr + PartitionPageSize(); + // Decommit everything in the initial partition page, except one system page + // for metadata. SetSystemPagesAccess(ptr, SystemPageSize(), PageInaccessible); SetSystemPagesAccess(ptr + (SystemPageSize() * 2), PartitionPageSize() - (SystemPageSize() * 2), PageInaccessible); -#if !defined(ARCH_CPU_64_BITS) - // TODO(bartekn): Uncommit all the way up to reserved_size, or in case of - // GigaCage, all the way up to 2MB boundary. - PA_DCHECK(slot + slot_size + SystemPageSize() <= ptr + reserved_size); - SetSystemPagesAccess(slot + slot_size, SystemPageSize(), PageInaccessible); -#endif + char* slot = ptr + PartitionPageSize(); + // Decommit everything past the slot, until the end of the reserved region. + PA_DCHECK(slot + slot_size <= ptr + reserved_size); + if (slot + slot_size < ptr + reserved_size) { + SetSystemPagesAccess(slot + slot_size, + (ptr + reserved_size) - (slot + slot_size), + PageInaccessible); + } auto* metadata = reinterpret_cast<PartitionDirectMapMetadata<thread_safe>*>( PartitionSuperPageToMetadataArea(ptr)); @@ -676,9 +661,12 @@ PA_DCHECK(new_slot_span); } else { // Third. If we get here, we need a brand new slot span. + // TODO(bartekn): For single-slot slot spans, we can use rounded raw_size + // as slot_span_committed_size. uint16_t num_partition_pages = get_pages_per_slot_span(); - void* raw_memory = AllocNewSlotSpan(root, flags, num_partition_pages, - get_bytes_per_span()); + void* raw_memory = + AllocNewSlotSpan(root, flags, num_partition_pages, + /* slot_span_committed_size= */ get_bytes_per_span()); if (LIKELY(raw_memory != nullptr)) { new_slot_span = SlotSpanMetadata<thread_safe>::FromPointerNoAlignmentCheck(
diff --git a/base/allocator/partition_allocator/partition_bucket.h b/base/allocator/partition_allocator/partition_bucket.h index 125c4c877..107417c 100644 --- a/base/allocator/partition_allocator/partition_bucket.h +++ b/base/allocator/partition_allocator/partition_bucket.h
@@ -87,14 +87,6 @@ NumSystemPagesPerPartitionPage(); } - static ALWAYS_INLINE size_t get_direct_map_size(size_t size) { - // Caller must check that the size is not above the MaxDirectMapped() - // limit before calling. This also guards against integer overflow in the - // calculation here. - PA_DCHECK(size <= MaxDirectMapped()); - return (size + SystemPageOffsetMask()) & SystemPageBaseMask(); - } - // This helper function scans a bucket's active slot span list for a suitable // new active slot span. When it finds a suitable new active slot span (one // that has free slots and is not empty), it is set as the new active slot
diff --git a/base/allocator/partition_allocator/partition_page.cc b/base/allocator/partition_allocator/partition_page.cc index 3cdf091a..22725f6 100644 --- a/base/allocator/partition_allocator/partition_page.cc +++ b/base/allocator/partition_allocator/partition_page.cc
@@ -27,7 +27,6 @@ auto* root = PartitionRoot<thread_safe>::FromSlotSpan(slot_span); root->lock_.AssertAcquired(); auto* extent = PartitionDirectMapExtent<thread_safe>::FromSlotSpan(slot_span); - size_t unmap_size = extent->map_size; // Maintain the doubly-linked list of all direct mappings. if (extent->prev_extent) { @@ -41,27 +40,22 @@ extent->next_extent->prev_extent = extent->prev_extent; } - // Add the size of the trailing guard page (32-bit only) and preceding - // partition page. - unmap_size += PartitionPageSize(); -#if !defined(ARCH_CPU_64_BITS) - unmap_size += SystemPageSize(); -#endif + root->DecreaseCommittedPages(slot_span->bucket->slot_size); - size_t uncommitted_page_size = - slot_span->bucket->slot_size + SystemPageSize(); - root->DecreaseCommittedPages(uncommitted_page_size); - PA_DCHECK(root->total_size_of_direct_mapped_pages >= uncommitted_page_size); - root->total_size_of_direct_mapped_pages -= uncommitted_page_size; - - PA_DCHECK(!(unmap_size & PageAllocationGranularityOffsetMask())); + size_t reserved_size = + extent->map_size + + PartitionRoot<thread_safe>::GetDirectMapMetadataAndGuardPagesSize(); + PA_DCHECK(!(reserved_size & PageAllocationGranularityOffsetMask())); + PA_DCHECK(root->total_size_of_direct_mapped_pages >= reserved_size); + root->total_size_of_direct_mapped_pages -= reserved_size; + PA_DCHECK(!(reserved_size & PageAllocationGranularityOffsetMask())); char* ptr = reinterpret_cast<char*>( SlotSpanMetadata<thread_safe>::ToPointer(slot_span)); // Account for the mapping starting a partition page before the actual // allocation address. ptr -= PartitionPageSize(); - return {ptr, unmap_size}; + return {ptr, reserved_size}; } template <bool thread_safe>
diff --git a/base/allocator/partition_allocator/partition_root.cc b/base/allocator/partition_allocator/partition_root.cc index d28e984..08cd35b 100644 --- a/base/allocator/partition_allocator/partition_root.cc +++ b/base/allocator/partition_allocator/partition_root.cc
@@ -444,7 +444,7 @@ internal::PartitionSizeAdjustAdd(allow_extras, requested_size); // Note that the new size isn't a bucketed size; this function is called // whenever we're reallocating a direct mapped allocation. - size_t new_slot_size = Bucket::get_direct_map_size(raw_size); + size_t new_slot_size = GetDirectMapSlotSize(raw_size); if (new_slot_size < kMinDirectMappedDownsize) return false; @@ -453,13 +453,17 @@ char* char_ptr = static_cast<char*>(SlotSpan::ToPointer(slot_span)); if (new_slot_size == current_slot_size) { // No need to move any memory around, but update size and cookie below. + // That's because raw_size may have changed. } else if (new_slot_size < current_slot_size) { - size_t map_size = DirectMapExtent::FromSlotSpan(slot_span)->map_size; + size_t current_map_size = + DirectMapExtent::FromSlotSpan(slot_span)->map_size; + size_t new_map_size = GetDirectMapReservedSize(raw_size) - + GetDirectMapMetadataAndGuardPagesSize(); - // Don't reallocate in-place if new size is less than 80 % of the full - // map size, to avoid holding on to too much unused address space. - if ((new_slot_size / SystemPageSize()) * 5 < - (map_size / SystemPageSize()) * 4) + // Don't reallocate in-place if new map size would be less than 80 % of the + // current map size, to avoid holding on to too much unused address space. + if ((new_map_size / SystemPageSize()) * 5 < + (current_map_size / SystemPageSize()) * 4) return false; // Shrink by decommitting unneeded pages and making them inaccessible.
diff --git a/base/allocator/partition_allocator/partition_root.h b/base/allocator/partition_allocator/partition_root.h index e6cc60c..28b70e5 100644 --- a/base/allocator/partition_allocator/partition_root.h +++ b/base/allocator/partition_allocator/partition_root.h
@@ -33,6 +33,7 @@ #include <atomic> #include "base/allocator/partition_allocator/page_allocator.h" +#include "base/allocator/partition_allocator/page_allocator_constants.h" #include "base/allocator/partition_allocator/partition_alloc-inl.h" #include "base/allocator/partition_allocator/partition_alloc_check.h" #include "base/allocator/partition_allocator/partition_alloc_constants.h" @@ -47,6 +48,7 @@ #include "base/allocator/partition_allocator/partition_tag.h" #include "base/allocator/partition_allocator/pcscan.h" #include "base/allocator/partition_allocator/thread_cache.h" +#include "base/bits.h" #include "base/optional.h" #include "build/build_config.h" @@ -148,7 +150,14 @@ #endif // Bookkeeping. - // Invariant: total_size_of_committed_pages <= + // - total_size_of_super_pages - total virtual address space for normal bucket + // super pages + // - total_size_of_direct_mapped_pages - total virtual address space for + // direct-map regions + // - total_size_of_committed_pages - total committed pages for slots (doesn't + // include metadata, bitmaps (if any), or any data outside or regions + // described in #1 and #2) + // Invariant: total_size_of_committed_pages < // total_size_of_super_pages + // total_size_of_direct_mapped_pages. // Since all operations on these atomic variables have relaxed semantics, we @@ -317,6 +326,49 @@ } } + static PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR ALWAYS_INLINE size_t + GetDirectMapMetadataAndGuardPagesSize() { + // Because we need to fake a direct-map region to look like a super page, we + // need to allocate a bunch of system pages more around the payload: + // - The first few system pages are the partition page in which the super + // page metadata is stored. + // - We add a trailing guard page on 32-bit (on 64-bit we rely on the + // massive address space plus randomization instead; additionally GigaCage + // guarantees that the region is followed by region with a preceding guard + // page or inaccessible in the direct map-pool). + size_t ret = PartitionPageSize(); +#if !defined(PA_HAS_64_BITS_POINTERS) + ret += SystemPageSize(); +#endif + return ret; + } + + static PAGE_ALLOCATOR_CONSTANTS_DECLARE_CONSTEXPR ALWAYS_INLINE size_t + GetDirectMapSlotSize(size_t raw_size) { + // Caller must check that the size is not above the MaxDirectMapped() + // limit before calling. This also guards against integer overflow in the + // calculation here. + PA_DCHECK(raw_size <= MaxDirectMapped()); + return bits::Align(raw_size, SystemPageSize()); + } + + ALWAYS_INLINE size_t GetDirectMapReservedSize(size_t raw_size) { + // Caller must check that the size is not above the MaxDirectMapped() + // limit before calling. This also guards against integer overflow in the + // calculation here. + PA_DCHECK(raw_size <= MaxDirectMapped()); + // Align to allocation granularity. However, when 64-bit GigaCage is used, + // the granularity is super page size. + size_t alignment = PageAllocationGranularity(); +#if defined(PA_HAS_64_BITS_POINTERS) + if (UsesGigaCage()) { + alignment = kSuperPageSize; + } +#endif + return bits::Align(raw_size + GetDirectMapMetadataAndGuardPagesSize(), + alignment); + } + private: // Allocates memory, without initializing extras. // @@ -1060,7 +1112,7 @@ } else if (size > MaxDirectMapped()) { // Too large to allocate => return the size unchanged. } else { - size = Bucket::get_direct_map_size(size); + size = GetDirectMapSlotSize(size); } size = internal::PartitionSizeAdjustSubtract(allow_extras, size); return size;
diff --git a/base/android/java/src/org/chromium/base/compat/ApiHelperForO.java b/base/android/java/src/org/chromium/base/compat/ApiHelperForO.java index 511a870..1806e546 100644 --- a/base/android/java/src/org/chromium/base/compat/ApiHelperForO.java +++ b/base/android/java/src/org/chromium/base/compat/ApiHelperForO.java
@@ -11,14 +11,12 @@ import android.content.pm.PackageManager; import android.content.res.Configuration; import android.os.Build; -import android.view.autofill.AutofillManager; import android.view.Display; import android.view.View; import android.view.Window; import org.chromium.base.StrictModeContext; import org.chromium.base.annotations.VerifiesOnO; -import org.chromium.base.ContextUtils; /** * Utility class to use new APIs that were added in O (API level 26). These need to exist in a @@ -72,13 +70,4 @@ return context.createContextForSplit(name); } } - - /** See {@link AutofillManager@cancel()}. */ - public static void cancelAutofillSession() { - AutofillManager autofillManager = ContextUtils.getApplicationContext().getSystemService( - AutofillManager.class); - if (autofillManager != null) { - autofillManager.cancel(); - } - } }
diff --git a/base/cpu_unittest.cc b/base/cpu_unittest.cc index b4a59cf3..fa245a1 100644 --- a/base/cpu_unittest.cc +++ b/base/cpu_unittest.cc
@@ -20,6 +20,7 @@ ASSERT_TRUE(cpu.has_mmx()); ASSERT_TRUE(cpu.has_sse()); ASSERT_TRUE(cpu.has_sse2()); + ASSERT_TRUE(cpu.has_sse3()); // GCC and clang instruction test. #if defined(COMPILER_GCC) @@ -32,10 +33,8 @@ // Execute an SSE 2 instruction. __asm__ __volatile__("psrldq $0, %%xmm0\n" : : : "xmm0"); - if (cpu.has_sse3()) { - // Execute an SSE 3 instruction. - __asm__ __volatile__("addsubpd %%xmm0, %%xmm0\n" : : : "xmm0"); - } + // Execute an SSE 3 instruction. + __asm__ __volatile__("addsubpd %%xmm0, %%xmm0\n" : : : "xmm0"); if (cpu.has_ssse3()) { // Execute a Supplimental SSE 3 instruction. @@ -80,10 +79,8 @@ // Execute an SSE 2 instruction. __asm psrldq xmm0, 0; - if (cpu.has_sse3()) { - // Execute an SSE 3 instruction. - __asm addsubpd xmm0, xmm0; - } + // Execute an SSE 3 instruction. + __asm addsubpd xmm0, xmm0; if (cpu.has_ssse3()) { // Execute a Supplimental SSE 3 instruction.
diff --git a/base/system/sys_info.h b/base/system/sys_info.h index eee730e..902a63a 100644 --- a/base/system/sys_info.h +++ b/base/system/sys_info.h
@@ -66,6 +66,12 @@ // on failure. static int64_t AmountOfTotalDiskSpace(const FilePath& path); +#if defined(OS_FUCHSIA) + // Sets the total amount of disk space to report under the specified |path|. + // If |bytes| is -ve then any existing entry for |path| is removed. + static void SetAmountOfTotalDiskSpace(const FilePath& path, int64_t bytes); +#endif + // Returns system uptime. static TimeDelta Uptime();
diff --git a/base/system/sys_info_fuchsia.cc b/base/system/sys_info_fuchsia.cc index 1205f63..e4fd0eee 100644 --- a/base/system/sys_info_fuchsia.cc +++ b/base/system/sys_info_fuchsia.cc
@@ -6,13 +6,57 @@ #include <zircon/syscalls.h> +#include "base/containers/flat_map.h" +#include "base/files/file_util.h" #include "base/fuchsia/fuchsia_logging.h" #include "base/logging.h" +#include "base/no_destructor.h" +#include "base/synchronization/lock.h" #include "base/threading/scoped_blocking_call.h" #include "build/build_config.h" namespace base { +namespace { + +struct TotalDiskSpace { + Lock lock; + flat_map<FilePath, int64_t> space_map GUARDED_BY(lock); +}; + +TotalDiskSpace& GetTotalDiskSpace() { + static NoDestructor<TotalDiskSpace> total_disk_space; + return *total_disk_space; +} + +// Returns the total-disk-space set for the volume containing |path|. If +// |volume_path| is non-null then it receives the path to the relevant volume. +// Returns -1, and does not modify |volume_path|, if no match is found. +int64_t GetAmountOfTotalDiskSpaceAndVolumePath(const FilePath& path, + base::FilePath* volume_path) { + CHECK(path.IsAbsolute()); + TotalDiskSpace& total_disk_space = GetTotalDiskSpace(); + + AutoLock l(total_disk_space.lock); + int64_t result = -1; + base::FilePath matched_path; + for (const auto& path_and_size : total_disk_space.space_map) { + if (path_and_size.first == path || path_and_size.first.IsParent(path)) { + // If a deeper path was already matched then ignore this entry. + if (!matched_path.empty() && !matched_path.IsParent(path_and_size.first)) + continue; + matched_path = path_and_size.first; + result = path_and_size.second; + } + } + + if (volume_path) + *volume_path = matched_path; + return result; +} + +} // namespace + // static int64_t SysInfo::AmountOfPhysicalMemoryImpl() { return zx_system_get_physmem(); @@ -42,18 +86,37 @@ // static int64_t SysInfo::AmountOfFreeDiskSpace(const FilePath& path) { - base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, - base::BlockingType::MAY_BLOCK); - NOTIMPLEMENTED_LOG_ONCE(); - return -1; + ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK); + base::FilePath volume_path; + const int64_t total_space = + GetAmountOfTotalDiskSpaceAndVolumePath(path, &volume_path); + if (total_space < 0) + return -1; + + // TODO(crbug.com/1148334): Replace this with an efficient implementation. + const int64_t used_space = ComputeDirectorySize(volume_path); + if (used_space < total_space) + return total_space - used_space; + + return 0; } // static int64_t SysInfo::AmountOfTotalDiskSpace(const FilePath& path) { - base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, - base::BlockingType::MAY_BLOCK); - NOTIMPLEMENTED_LOG_ONCE(); - return -1; + if (path.empty()) + return -1; + return GetAmountOfTotalDiskSpaceAndVolumePath(path, nullptr); +} + +// static +void SysInfo::SetAmountOfTotalDiskSpace(const FilePath& path, int64_t bytes) { + DCHECK(path.IsAbsolute()); + TotalDiskSpace& total_disk_space = GetTotalDiskSpace(); + AutoLock l(total_disk_space.lock); + if (bytes >= 0) + total_disk_space.space_map[path] = bytes; + else + total_disk_space.space_map.erase(path); } // static
diff --git a/base/system/sys_info_unittest.cc b/base/system/sys_info_unittest.cc index 34b5c17a..82f00bd 100644 --- a/base/system/sys_info_unittest.cc +++ b/base/system/sys_info_unittest.cc
@@ -89,34 +89,54 @@ } #endif // defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID) -#if defined(OS_FUCHSIA) -// TODO(crbug.com/851734): Implementation depends on statvfs, which is not -// implemented on Fuchsia -#define MAYBE_AmountOfFreeDiskSpace DISABLED_AmountOfFreeDiskSpace -#else -#define MAYBE_AmountOfFreeDiskSpace AmountOfFreeDiskSpace -#endif -TEST_F(SysInfoTest, MAYBE_AmountOfFreeDiskSpace) { +TEST_F(SysInfoTest, AmountOfFreeDiskSpace) { // We aren't actually testing that it's correct, just that it's sane. FilePath tmp_path; ASSERT_TRUE(GetTempDir(&tmp_path)); +#if defined(OS_FUCHSIA) + // Fuchsia currently requires "total disk space" be set explicitly. + // See crbug.com/1148334. + SysInfo::SetAmountOfTotalDiskSpace(tmp_path, 1024); +#endif EXPECT_GE(SysInfo::AmountOfFreeDiskSpace(tmp_path), 0) << tmp_path.value(); } -#if defined(OS_FUCHSIA) -// TODO(crbug.com/851734): Implementation depends on statvfs, which is not -// implemented on Fuchsia -#define MAYBE_AmountOfTotalDiskSpace DISABLED_AmountOfTotalDiskSpace -#else -#define MAYBE_AmountOfTotalDiskSpace AmountOfTotalDiskSpace -#endif -TEST_F(SysInfoTest, MAYBE_AmountOfTotalDiskSpace) { +TEST_F(SysInfoTest, AmountOfTotalDiskSpace) { // We aren't actually testing that it's correct, just that it's sane. FilePath tmp_path; ASSERT_TRUE(GetTempDir(&tmp_path)); +#if defined(OS_FUCHSIA) + // Fuchsia currently requires "total disk space" be set explicitly. + // See crbug.com/1148334. + SysInfo::SetAmountOfTotalDiskSpace(tmp_path, 1024); +#endif EXPECT_GT(SysInfo::AmountOfTotalDiskSpace(tmp_path), 0) << tmp_path.value(); } +#if defined(OS_FUCHSIA) +// Verify that specifying total disk space for nested directories matches +// the deepest-nested. +TEST_F(SysInfoTest, NestedVolumesAmountOfTotalDiskSpace) { + constexpr int64_t kOuterVolumeQuota = 1024; + constexpr int64_t kInnerVolumeQuota = kOuterVolumeQuota / 2; + + FilePath tmp_path; + ASSERT_TRUE(GetTempDir(&tmp_path)); + SysInfo::SetAmountOfTotalDiskSpace(tmp_path, kOuterVolumeQuota); + const FilePath subdirectory_path = tmp_path.Append("subdirectory"); + SysInfo::SetAmountOfTotalDiskSpace(subdirectory_path, kInnerVolumeQuota); + + EXPECT_EQ(SysInfo::AmountOfTotalDiskSpace(tmp_path), kOuterVolumeQuota); + EXPECT_EQ(SysInfo::AmountOfTotalDiskSpace(subdirectory_path), + kInnerVolumeQuota); + + // Remove the inner directory quota setting and check again. + SysInfo::SetAmountOfTotalDiskSpace(subdirectory_path, -1); + EXPECT_EQ(SysInfo::AmountOfTotalDiskSpace(subdirectory_path), + kOuterVolumeQuota); +} +#endif // defined(OS_FUCHSIA) + #if defined(OS_WIN) || defined(OS_APPLE) || defined(OS_LINUX) || \ defined(OS_CHROMEOS) || defined(OS_FUCHSIA) TEST_F(SysInfoTest, OperatingSystemVersionNumbers) {
diff --git a/build/android/resource_sizes.gni b/build/android/resource_sizes.gni index 1cc8aecb..5bc430a 100644 --- a/build/android/resource_sizes.gni +++ b/build/android/resource_sizes.gni
@@ -81,8 +81,6 @@ # to_resource_sizes_py: Scope containing data to pass to resource_sizes.py, # processed by generate_commit_size_analysis.py. # supersize_input_file: Main input for SuperSize. -# version: Version number, uses to notify build bots that significant changes -# has occurred, so that gs:// should be ignored. template("android_size_bot_config") { _full_target_name = get_label_info(target_name, "label_no_toolchain") _out_json = { @@ -92,7 +90,6 @@ "mapping_files", "to_resource_sizes_py", "supersize_input_file", - "version", ]) } _output_json_path = "$root_build_dir/config/${invoker.name}_size_config.json"
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn index 9d66490..faff9259 100644 --- a/build/config/compiler/BUILD.gn +++ b/build/config/compiler/BUILD.gn
@@ -750,6 +750,7 @@ cflags += [ "-m64", "-march=$x64_arch", + "-msse3", ] ldflags += [ "-m64" ] } else if (current_cpu == "x86") { @@ -757,9 +758,8 @@ ldflags += [ "-m32" ] if (!is_nacl) { cflags += [ - "-msse2", "-mfpmath=sse", - "-mmmx", + "-msse3", ] } } else if (current_cpu == "arm") {
diff --git a/build/config/sanitizers/BUILD.gn b/build/config/sanitizers/BUILD.gn index 68ea4c3..0473a8d 100644 --- a/build/config/sanitizers/BUILD.gn +++ b/build/config/sanitizers/BUILD.gn
@@ -157,12 +157,6 @@ ldflags = [] if (is_asan) { ldflags += [ "-fsanitize=address" ] - if (is_mac) { - # https://crbug.com/708707 - ldflags += [ "-fno-sanitize-address-use-after-scope" ] - } else { - ldflags += [ "-fsanitize-address-use-after-scope" ] - } } if (is_hwasan) { ldflags += [ "-fsanitize=hwaddress" ] @@ -277,12 +271,6 @@ cflags = [] if (is_asan) { cflags += [ "-fsanitize=address" ] - if (!is_mac) { - cflags += [ "-fsanitize-address-use-after-scope" ] - } else { - # https://crbug.com/708707 - cflags += [ "-fno-sanitize-address-use-after-scope" ] - } if (asan_use_blacklist) { if (is_win) { if (!defined(asan_win_blacklist_path)) {
diff --git a/build/config/win/BUILD.gn b/build/config/win/BUILD.gn index 7e1cea5..9ad01d02 100644 --- a/build/config/win/BUILD.gn +++ b/build/config/win/BUILD.gn
@@ -118,6 +118,13 @@ assert(false, "unknown current_cpu " + current_cpu) } + # Chrome currently requires SSE3. Clang supports targeting any Intel + # microarchitecture. MSVC only supports a subset of architectures, and the + # next step after SSE2 will be AVX. + if (current_cpu == "x86" || current_cpu == "x64") { + cflags += [ "-msse3" ] + } + if (exec_script("//build/win/use_ansi_codes.py", [], "trim string") == "True") { cflags += [
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1 index 6cf3511..29b0834 100644 --- a/build/fuchsia/linux.sdk.sha1 +++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@ -0.20201113.3.1 +0.20201114.1.1
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1 index 46f24e4..fbe0516 100644 --- a/build/fuchsia/mac.sdk.sha1 +++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@ -0.20201113.2.2 +0.20201114.2.1
diff --git a/chrome/VERSION b/chrome/VERSION index 5b859d3c..9802319 100644 --- a/chrome/VERSION +++ b/chrome/VERSION
@@ -1,4 +1,4 @@ MAJOR=89 MINOR=0 -BUILD=4325 +BUILD=4327 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn index e6f4935..ef2cc98f9 100644 --- a/chrome/android/BUILD.gn +++ b/chrome/android/BUILD.gn
@@ -3014,7 +3014,6 @@ apk_name = "apks/$_minimal_apks_filename" } supersize_input_file = "apks/$_minimal_apks_filename" - version = "1" } } @@ -3100,7 +3099,6 @@ trichrome_webview = "apks/$_trichrome_webview_basename" } supersize_input_file = "apks/$_ssargs_filename" - version = "1" } }
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni index e254f21c..53a8d19 100644 --- a/chrome/android/chrome_java_resources.gni +++ b/chrome/android/chrome_java_resources.gni
@@ -688,6 +688,7 @@ "java/res/drawable/ic_sync_on_48dp.xml", "java/res/drawable/ic_toolbar_share_offset_24dp.xml", "java/res/drawable/ic_translate.xml", + "java/res/drawable/ic_trending_down_black.xml", "java/res/drawable/ic_tv_options_input_settings_rotated_grey.xml", "java/res/drawable/ic_visibility_black.xml", "java/res/drawable/ic_visibility_off_black.xml",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni index e299da2..17ba60ad 100644 --- a/chrome/android/chrome_java_sources.gni +++ b/chrome/android/chrome_java_sources.gni
@@ -92,6 +92,7 @@ "java/src/org/chromium/chrome/browser/app/video_tutorials/NewTabPageVideoIPHManager.java", "java/src/org/chromium/chrome/browser/app/video_tutorials/VideoPlayerActivity.java", "java/src/org/chromium/chrome/browser/app/video_tutorials/VideoTutorialListActivity.java", + "java/src/org/chromium/chrome/browser/app/video_tutorials/VideoTutorialShareHelper.java", "java/src/org/chromium/chrome/browser/autofill/AutofillExpirationDateFixFlowBridge.java", "java/src/org/chromium/chrome/browser/autofill/AutofillExpirationDateFixFlowPrompt.java", "java/src/org/chromium/chrome/browser/autofill/AutofillLogger.java",
diff --git a/chrome/android/expectations/lint-suppressions.xml b/chrome/android/expectations/lint-suppressions.xml index 3c477dc..fa96c402 100644 --- a/chrome/android/expectations/lint-suppressions.xml +++ b/chrome/android/expectations/lint-suppressions.xml
@@ -323,8 +323,7 @@ <ignore regexp="The resource `R.string.ntp_article_suggestions_section_empty` appears to be unused"/> <!-- 1: TODO(crbug.com/1137985) resource is used in downstream image editor. --> <ignore regexp="The resource `R.string.clear` appears to be unused"/> - <!-- 4: Temporarily suppressed until impelmentation is ready, see https://crbug.com/1144742, https://crbug.com/1148020 --> - <ignore regexp="The resource `R.string.menu_track_prices` appears to be unused"/> + <!-- 3: Temporarily suppressed until impelmentation is ready, see https://crbug.com/1148020 --> <ignore regexp="The resource `R.string.price_drop_spotted_title` appears to be unused"/> <ignore regexp="The resource `R.string.price_drop_spotted_content` appears to be unused"/> <ignore regexp="The resource `R.string.price_drop_spotted_show_me` appears to be unused"/>
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java index dfa55027..ae361f2 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java
@@ -202,4 +202,11 @@ public static boolean isLaunchBugFixEnabled() { return ENABLE_LAUNCH_BUG_FIX.getValue(); } + + /** + * @return Whether the price tracking feature is enabled and available for use. + */ + public static boolean isPriceTrackingEnabled() { + return ENABLE_PRICE_TRACKING.getValue(); + } }
diff --git a/chrome/android/java/res/drawable/ic_trending_down_black.xml b/chrome/android/java/res/drawable/ic_trending_down_black.xml new file mode 100644 index 0000000..c6b0a5c --- /dev/null +++ b/chrome/android/java/res/drawable/ic_trending_down_black.xml
@@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright 2020 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. --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M20,12v2.58L13.41,8l-4,4 -6,-6L2,7.41l7.41,7.42 4,-4L18.58,16H16v2h6v-6z" + android:fillColor="@color/default_icon_color" /> +</vector>
diff --git a/chrome/android/java/res/menu/main_menu.xml b/chrome/android/java/res/menu/main_menu.xml index 502f309..1458baf 100644 --- a/chrome/android/java/res/menu/main_menu.xml +++ b/chrome/android/java/res/menu/main_menu.xml
@@ -139,6 +139,17 @@ <item android:id="@+id/menu_group_tabs" android:title="@string/menu_group_tabs" android:icon="@drawable/ic_widgets" /> + <item android:id="@+id/track_prices_row_menu_id" + android:title="@null"> + <menu> + <item android:id="@+id/track_prices_id" + android:title="@string/menu_track_prices" + android:icon="@drawable/ic_trending_down_black" /> + <item android:id="@+id/track_prices_check_id" + android:title="@null" + android:checkable="true" /> + </menu> + </item> <item android:id="@id/preferences_id" android:title="@string/menu_settings" android:icon="@drawable/settings_cog" /> @@ -174,6 +185,17 @@ <item android:id="@id/menu_group_tabs" android:title="@string/menu_group_tabs" android:icon="@drawable/ic_widgets" /> + <item android:id="@id/track_prices_row_menu_id" + android:title="@null"> + <menu> + <item android:id="@id/track_prices_id" + android:title="@string/menu_track_prices" + android:icon="@drawable/ic_trending_down_black" /> + <item android:id="@id/track_prices_check_id" + android:title="@null" + android:checkable="true" /> + </menu> + </item> <item android:id="@id/preferences_id" android:title="@string/menu_settings" android:icon="@drawable/settings_cog" />
diff --git a/chrome/android/java/res/menu/main_menu_regroup.xml b/chrome/android/java/res/menu/main_menu_regroup.xml index 1e1ae57..840be2e 100644 --- a/chrome/android/java/res/menu/main_menu_regroup.xml +++ b/chrome/android/java/res/menu/main_menu_regroup.xml
@@ -198,6 +198,17 @@ <item android:id="@+id/menu_group_tabs" android:title="@string/menu_group_tabs" android:icon="@drawable/ic_widgets" /> + <item android:id="@+id/track_prices_row_menu_id" + android:title="@null"> + <menu> + <item android:id="@+id/track_prices_id" + android:title="@string/menu_track_prices" + android:icon="@drawable/ic_trending_down_black" /> + <item android:id="@+id/track_prices_check_id" + android:title="@null" + android:checkable="true" /> + </menu> + </item> <item android:id="@id/preferences_id" android:title="@string/menu_settings" android:icon="@drawable/settings_cog" /> @@ -237,6 +248,17 @@ <item android:id="@id/menu_group_tabs" android:title="@string/menu_group_tabs" android:icon="@drawable/ic_widgets" /> + <item android:id="@id/track_prices_row_menu_id" + android:title="@null"> + <menu> + <item android:id="@id/track_prices_id" + android:title="@string/menu_track_prices" + android:icon="@drawable/ic_trending_down_black" /> + <item android:id="@id/track_prices_check_id" + android:title="@null" + android:checkable="true" /> + </menu> + </item> <item android:id="@id/preferences_id" android:title="@string/menu_settings" android:icon="@drawable/settings_cog" />
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java b/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java index 47b35ac8..f416c78 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
@@ -28,6 +28,7 @@ import org.chromium.base.StrictModeContext; import org.chromium.base.metrics.RecordHistogram; import org.chromium.chrome.browser.app.ChromeActivity; +import org.chromium.chrome.browser.app.video_tutorials.VideoTutorialShareHelper; import org.chromium.chrome.browser.browserservices.BrowserServicesIntentDataProvider.CustomTabsUiType; import org.chromium.chrome.browser.browserservices.SessionDataHolder; import org.chromium.chrome.browser.browserservices.ui.splashscreen.trustedwebactivity.TwaSplashController; @@ -159,6 +160,11 @@ return Action.FINISH_ACTIVITY; } + // Check if the URL is a video tutorial and needs to be handled in a video player. + if (VideoTutorialShareHelper.handleVideoTutorialURL(url)) { + return Action.FINISH_ACTIVITY; + } + // Check if a LIVE WebappActivity has to be brought back to the foreground. We can't // check for a dead WebappActivity because we don't have that information without a global // TabManager. If that ever lands, code to bring back any Tab could be consolidated
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java index 1f3f9caa..9ad8d6b 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/appmenu/AppMenuPropertiesDelegateImpl.java
@@ -523,6 +523,9 @@ .getTabsWithNoOtherRelatedTabs() .size() > 1; + boolean isPriceTrackingVisible = TabUiFeatureUtilities.isPriceTrackingEnabled() + && !DeviceClassManager.enableAccessibilityLayout(); + boolean isPriceTrackingEnabled = isPriceTrackingVisible; for (int i = 0; i < menu.size(); ++i) { MenuItem item = menu.getItem(i); @@ -571,6 +574,10 @@ item.setVisible(isMenuGroupTabsVisible); item.setEnabled(isMenuGroupTabsEnabled); } + if (item.getItemId() == R.id.track_prices_row_menu_id) { + item.setVisible(isPriceTrackingVisible); + item.setEnabled(isPriceTrackingEnabled); + } if (item.getItemId() == R.id.close_all_tabs_menu_id) { boolean hasTabs = mTabModelSelector.getTotalTabCount() > 0; item.setVisible(!isIncognito);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/NewTabPageVideoIPHManager.java b/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/NewTabPageVideoIPHManager.java index b20a2f7..e5d315b 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/NewTabPageVideoIPHManager.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/NewTabPageVideoIPHManager.java
@@ -124,7 +124,7 @@ @VisibleForTesting protected void launchVideoPlayer(Tutorial tutorial) { - VideoPlayerActivity.playVideoTutorial(mContext, tutorial); + VideoPlayerActivity.playVideoTutorial(mContext, tutorial.featureType); } @VisibleForTesting
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/VideoPlayerActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/VideoPlayerActivity.java index 17c9854a..a79d4d7 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/VideoPlayerActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/VideoPlayerActivity.java
@@ -81,10 +81,11 @@ } /** Called to launch this activity to play a given video tutorial. */ - static void playVideoTutorial(Context context, Tutorial tutorial) { + public static void playVideoTutorial(Context context, @FeatureType int featureType) { Intent intent = new Intent(); intent.setClass(context, VideoPlayerActivity.class); - intent.putExtra(VideoPlayerActivity.EXTRA_VIDEO_TUTORIAL, tutorial.featureType); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(VideoPlayerActivity.EXTRA_VIDEO_TUTORIAL, featureType); context.startActivity(intent); }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/VideoTutorialListActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/VideoTutorialListActivity.java index b6c3eb2c..dfe2540 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/VideoTutorialListActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/VideoTutorialListActivity.java
@@ -42,6 +42,6 @@ } private void onTutorialSelected(Tutorial tutorial) { - VideoPlayerActivity.playVideoTutorial(this, tutorial); + VideoPlayerActivity.playVideoTutorial(this, tutorial.featureType); } }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/VideoTutorialShareHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/VideoTutorialShareHelper.java new file mode 100644 index 0000000..d3d930c --- /dev/null +++ b/chrome/android/java/src/org/chromium/chrome/browser/app/video_tutorials/VideoTutorialShareHelper.java
@@ -0,0 +1,97 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.app.video_tutorials; + +import android.text.TextUtils; + +import org.chromium.base.ContextUtils; +import org.chromium.chrome.browser.flags.ChromeFeatureList; +import org.chromium.chrome.browser.init.BrowserParts; +import org.chromium.chrome.browser.init.ChromeBrowserInitializer; +import org.chromium.chrome.browser.init.EmptyBrowserParts; +import org.chromium.chrome.browser.preferences.ChromePreferenceKeys; +import org.chromium.chrome.browser.preferences.SharedPreferencesManager; +import org.chromium.chrome.browser.profiles.Profile; +import org.chromium.chrome.browser.video_tutorials.FeatureType; +import org.chromium.chrome.browser.video_tutorials.Tutorial; +import org.chromium.chrome.browser.video_tutorials.VideoTutorialService; +import org.chromium.chrome.browser.video_tutorials.VideoTutorialServiceFactory; +import org.chromium.chrome.browser.video_tutorials.metrics.VideoTutorialMetrics; +import org.chromium.chrome.browser.video_tutorials.metrics.VideoTutorialMetrics.UserAction; + +import java.util.HashSet; +import java.util.Set; + +/** + * Helper class to handle share intents for video tutorials. + */ +public class VideoTutorialShareHelper { + /** + * Handles the URL, if it is a video tutorial share URL, otherwise returns false. + * @param url The URL being checked. + * @return True, if the URL was recognized as a video tutorial URL and handled, false otherwise. + */ + public static boolean handleVideoTutorialURL(String url) { + Set<String> videoTutorialUrls = SharedPreferencesManager.getInstance().readStringSet( + ChromePreferenceKeys.VIDEO_TUTORIALS_SHARE_URL_SET, new HashSet<>()); + if (!videoTutorialUrls.contains(url)) return false; + if (!ChromeFeatureList.isEnabled(ChromeFeatureList.VIDEO_TUTORIALS)) return false; + + launchVideoPlayer(url); + return true; + } + + /** + * Writes the video tutorial share URLs to shared preferences. This is later used to identify + * whether a URL is a video tutorial. + */ + public static void saveUrlsToSharedPrefs() { + if (!ChromeFeatureList.isEnabled(ChromeFeatureList.VIDEO_TUTORIALS)) { + SharedPreferencesManager.getInstance().removeKey( + ChromePreferenceKeys.VIDEO_TUTORIALS_SHARE_URL_SET); + return; + } + + VideoTutorialService videoTutorialService = + VideoTutorialServiceFactory.getForProfile(Profile.getLastUsedRegularProfile()); + videoTutorialService.getTutorials(tutorials -> { + Set<String> shareUrlSet = new HashSet<>(); + for (Tutorial tutorial : tutorials) { + if (TextUtils.isEmpty(tutorial.shareUrl)) continue; + shareUrlSet.add(tutorial.shareUrl); + } + SharedPreferencesManager.getInstance().writeStringSet( + ChromePreferenceKeys.VIDEO_TUTORIALS_SHARE_URL_SET, shareUrlSet); + }); + } + + private static void launchVideoPlayer(String shareUrl) { + assert !TextUtils.isEmpty(shareUrl); + + BrowserParts parts = new EmptyBrowserParts() { + @Override + public void finishNativeInitialization() { + VideoTutorialService videoTutorialService = + VideoTutorialServiceFactory.getForProfile( + Profile.getLastUsedRegularProfile()); + videoTutorialService.getTutorials(tutorials -> { + for (Tutorial tutorial : tutorials) { + if (TextUtils.equals(tutorial.shareUrl, shareUrl)) { + VideoTutorialMetrics.recordUserAction( + tutorial.featureType, UserAction.OPEN_SHARED_VIDEO); + VideoPlayerActivity.playVideoTutorial( + ContextUtils.getApplicationContext(), tutorial.featureType); + return; + } + } + VideoTutorialMetrics.recordUserAction( + FeatureType.INVALID, UserAction.INVALID_SHARE_URL); + }); + } + }; + ChromeBrowserInitializer.getInstance().handlePreNativeStartup(parts); + ChromeBrowserInitializer.getInstance().handlePostNativeStartup(true, parts); + } +}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java index 84119a78..0c9f02cf 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
@@ -746,6 +746,9 @@ } @Override + public void onIncognitoStateChanged() {} + + @Override public void updateLoadingState(boolean updateUrl) { if (updateUrl) onUrlChanged(); updateStatusIcon(); @@ -831,9 +834,6 @@ @Override public void revertChanges() {} - @Override - public void updateMicButtonState() {} - @Nullable @Override public FakeboxDelegate getFakeboxDelegate() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java index ec39211..d694e11 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/init/ProcessInitializationHandler.java
@@ -36,6 +36,7 @@ import org.chromium.chrome.browser.DefaultBrowserInfo; import org.chromium.chrome.browser.DeferredStartupHandler; import org.chromium.chrome.browser.DevToolsServer; +import org.chromium.chrome.browser.app.video_tutorials.VideoTutorialShareHelper; import org.chromium.chrome.browser.banners.AppBannerManager; import org.chromium.chrome.browser.bookmarkswidget.BookmarkWidgetProvider; import org.chromium.chrome.browser.contacts_picker.ChromePickerAdapter; @@ -434,6 +435,8 @@ () -> OfflineContentAvailabilityStatusProvider.getInstance()); deferredStartupHandler.addDeferredTask( () -> EnterpriseInfo.getInstance().logDeviceEnterpriseInfo()); + deferredStartupHandler.addDeferredTask( + () -> VideoTutorialShareHelper.saveUrlsToSharedPrefs()); } private void initChannelsAsync() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/messages/MessageContainerCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/messages/MessageContainerCoordinator.java index bdd3634..f98735d 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/messages/MessageContainerCoordinator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/messages/MessageContainerCoordinator.java
@@ -44,12 +44,12 @@ mContainer.setLayoutParams(params); } - public void showMessageContainer() { + protected void showMessageContainer() { mContainer.setVisibility(View.VISIBLE); updateMargins(); } - public void hideMessageContainer() { + protected void hideMessageContainer() { mContainer.setVisibility(View.GONE); }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBar.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBar.java index 23dab75c..2028dd3 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBar.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBar.java
@@ -76,15 +76,12 @@ */ View getSecurityIconView(); - /** Updates the state of the mic button if there is one. */ - void updateMicButtonState(); /** Returns the {@link VoiceRecognitionHandler} associated with this LocationBar. */ @Nullable default VoiceRecognitionHandler getVoiceRecognitionHandler() { return null; } - /** * Returns a (@link FakeboxDelegate}. *
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java index 901201f..d43a3bf2e 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
@@ -243,11 +243,6 @@ return mLocationBarLayout.getSecurityIconView(); } - @Override - public void updateMicButtonState() { - mLocationBarLayout.updateMicButtonState(); - } - @Nullable @Override public View getViewForUrlBackFocus() { @@ -339,6 +334,11 @@ return isTablet() ? mLocationBarLayout : null; } + @Override + public void onIncognitoStateChanged() { + mLocationBarLayout.updateMicButtonState(); + } + /** * Returns the {@link LocationBarCoordinatorPhone} for this coordinator. *
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarDataProvider.java index 865a79b..5f05b94 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarDataProvider.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/LocationBarDataProvider.java
@@ -31,9 +31,10 @@ */ interface Observer { void onTitleChanged(); + void onUrlChanged(); + void onIncognitoStateChanged(); // TODO(https://crbug.com/1139481): Add methods for other LocationBarDataProvider // data, e.g. NTP and security state. - void onUrlChanged(); } /** Adds an observer of changes to LocationBarDataProvider's data. */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java index ae727dc..73144f4e 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBar.java
@@ -39,7 +39,6 @@ import org.chromium.base.Log; import org.chromium.base.SysUtils; import org.chromium.base.ThreadUtils; -import org.chromium.base.compat.ApiHelperForO; import org.chromium.base.metrics.RecordUserAction; import org.chromium.chrome.browser.WindowDelegate; import org.chromium.ui.KeyboardVisibilityDelegate; @@ -288,14 +287,6 @@ mPendingScroll = false; } fixupTextDirection(); - - if (!focused && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - // https://crbug.com/1103555: On Android Q+, the URL bar can trigger augmented - // Autofill, which disables ordinary Autofill requests for the duration of the - // Autofill session. This is worked around by canceling the session when the user - // focuses another view. - ApiHelperForO.cancelAutofillSession(); - } } /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarApi26.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarApi26.java index 1f1f191c..5f725b1 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarApi26.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/UrlBarApi26.java
@@ -5,6 +5,7 @@ package org.chromium.chrome.browser.omnibox; import android.content.Context; +import android.os.Build; import android.util.AttributeSet; import android.view.ViewStructure; @@ -31,4 +32,16 @@ super.onProvideAutofillStructure(structure, autofillFlags); mRequestingAutofillStructure = false; } + + @Override + public int getAutofillType() { + // https://crbug.com/1103555: Prevent augmented autofill service from taking over the + // session by disabling both standard and augmented autofill on versions of Android + // where both are supported. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return AUTOFILL_TYPE_NONE; + } else { + return super.getAutofillType(); + } + } }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ChromePaymentRequestService.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ChromePaymentRequestService.java index f999e13..2d6bfd99 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ChromePaymentRequestService.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ChromePaymentRequestService.java
@@ -39,7 +39,6 @@ import org.chromium.components.payments.PaymentRequestSpec; import org.chromium.components.payments.PaymentResponseHelperInterface; import org.chromium.components.payments.PaymentUIsObserver; -import org.chromium.components.payments.PaymentValidator; import org.chromium.components.payments.Section; import org.chromium.components.payments.SkipToGPayHelper; import org.chromium.content_public.browser.RenderFrameHost; @@ -82,7 +81,6 @@ private final PaymentUiService mPaymentUiService; private final PaymentOptions mPaymentOptions; - private final boolean mRequestShipping; private boolean mWasRetryCalled; private boolean mHasClosed; @@ -137,7 +135,6 @@ mPaymentOptions = paymentRequestService.getPaymentOptions(); assert mPaymentOptions != null; - mRequestShipping = mPaymentOptions.requestShipping; mPaymentRequestService = paymentRequestService; mPaymentUiService = new PaymentUiService(/*delegate=*/this, @@ -641,25 +638,13 @@ } mPaymentUiService.onPaymentRequestComplete(result, - /*onMinimalUiErroredAndClosed=*/this::close, onCompleteHandled::run); + /*onMinimalUiErroredAndClosed=*/this::close, onCompleteHandled); } // Implement BrowserPaymentRequest: @Override public void retry(PaymentValidationErrors errors) { - if (mPaymentRequestService == null) return; - // mSpec.retry() can be used only when mSpec has not been destroyed. - assert !mSpec.isDestroyed(); - - if (!PaymentValidator.validatePaymentValidationErrors(errors)) { - mJourneyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER); - disconnectFromClientWithDebugMessage(ErrorStrings.INVALID_VALIDATION_ERRORS); - return; - } - mSpec.retry(errors); - mWasRetryCalled = true; - mPaymentUiService.onRetry(errors); }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/minimal/MinimalUICoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/minimal/MinimalUICoordinator.java index 3359a5b..c53d9ec89 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/payments/minimal/MinimalUICoordinator.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/minimal/MinimalUICoordinator.java
@@ -76,7 +76,7 @@ /** * Shows the minimal UI. * - * @param chromeActivity The activity where the UI should be shown. + * @param context The context where the UI should be shown. * @param app The app that contains the details to display and can be invoked * upon user confirmation. * @param formatter Formats the account balance amount according to its currency. @@ -137,7 +137,7 @@ * has closed. * @param observer The observer to notify when the UI has closed. */ - public void showCompleteAndClose(CompleteAndCloseObserver observer) { + private void showCompleteAndClose(CompleteAndCloseObserver observer) { mMediator.showCompleteAndClose(observer); }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentUiService.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentUiService.java index 63447c0..e44ee78 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentUiService.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentUiService.java
@@ -224,7 +224,7 @@ } /** A callback invoked when the Payment Request UI is closed. */ - public void onPaymentRequestUiClosed() { + private void onPaymentRequestUiClosed() { assert mPaymentRequestUI == null; mShouldShowDialog = false; } @@ -1215,7 +1215,7 @@ mJourneyLogger.setNumberOfSuggestionsShown( Section.SHIPPING_ADDRESS, addresses.size(), hasCompleteShippingAddress); - int missingFields = 0; + int missingFields; if (addresses.isEmpty()) { // All fields are missing. missingFields = AutofillAddress.CompletionStatus.INVALID_RECIPIENT
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java index 920b11a..ef9a257 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
@@ -452,9 +452,9 @@ // We load the URL from the tab rather than directly from the ContentView so the tab has // a chance of using a prerenderer page is any. - int loadType = TabImplJni.get().loadUrl(mNativeTabAndroid, TabImpl.this, - params.getUrl(), params.getInitiatorOrigin(), params.getVerbatimHeaders(), - params.getPostData(), params.getTransitionType(), + int loadType = TabImplJni.get().loadUrl(mNativeTabAndroid, params.getUrl(), + params.getInitiatorOrigin(), params.getVerbatimHeaders(), params.getPostData(), + params.getTransitionType(), params.getReferrer() != null ? params.getReferrer().getUrl() : null, // Policy will be ignored for null referrer url, 0 is just a placeholder. // TODO(ppi): Should we pass Referrer jobject and add JNI methods to read it @@ -680,7 +680,7 @@ // destroying the native tab cleanups up any remaining infobars. The infobar container // expects all infobars to be cleaned up before its own destruction. if (mNativeTabAndroid != 0) { - TabImplJni.get().destroy(mNativeTabAndroid, TabImpl.this); + TabImplJni.get().destroy(mNativeTabAndroid); assert mNativeTabAndroid == 0; } } @@ -1029,8 +1029,8 @@ */ void pushNativePageStateToNavigationEntry() { assert mNativeTabAndroid != 0 && getNativePage() != null; - TabImplJni.get().setActiveNavigationEntryTitleForUrl(mNativeTabAndroid, TabImpl.this, - getNativePage().getUrl(), getNativePage().getTitle()); + TabImplJni.get().setActiveNavigationEntryTitleForUrl( + mNativeTabAndroid, getNativePage().getUrl(), getNativePage().getTitle()); } /** @@ -1063,7 +1063,7 @@ */ void loadOriginalImage() { if (mNativeTabAndroid != 0) { - TabImplJni.get().loadOriginalImage(mNativeTabAndroid, TabImpl.this); + TabImplJni.get().loadOriginalImage(mNativeTabAndroid); } } @@ -1198,7 +1198,7 @@ if (bounds != null) { assert mNativeTabAndroid != 0; TabImplJni.get().onPhysicalBackingSizeChanged( - mNativeTabAndroid, TabImpl.this, webContents, bounds.right, bounds.bottom); + mNativeTabAndroid, webContents, bounds.right, bounds.bottom); } webContents.onShow(); initWebContents(webContents); @@ -1287,8 +1287,8 @@ mWebContentsDelegate = createWebContentsDelegate(); assert mNativeTabAndroid != 0; - TabImplJni.get().initWebContents(mNativeTabAndroid, TabImpl.this, mIncognito, - isDetached(this), webContents, mSourceTabId, mWebContentsDelegate, + TabImplJni.get().initWebContents(mNativeTabAndroid, mIncognito, isDetached(this), + webContents, mSourceTabId, mWebContentsDelegate, new TabContextMenuPopulatorFactory( mDelegateFactory.createContextMenuPopulatorFactory(this), this)); @@ -1352,7 +1352,7 @@ WebContents webContents = getWebContents(); if (webContents != null) { - TabImplJni.get().updateDelegates(mNativeTabAndroid, TabImpl.this, mWebContentsDelegate, + TabImplJni.get().updateDelegates(mNativeTabAndroid, mWebContentsDelegate, new TabContextMenuPopulatorFactory( mDelegateFactory.createContextMenuPopulatorFactory(this), this)); webContents.notifyRendererPreferenceUpdate(); @@ -1520,9 +1520,9 @@ if (deleteNativeWebContents) { // Destruction of the native WebContents will call back into Java to destroy the Java // WebContents. - TabImplJni.get().destroyWebContents(mNativeTabAndroid, TabImpl.this); + TabImplJni.get().destroyWebContents(mNativeTabAndroid); } else { - TabImplJni.get().releaseWebContents(mNativeTabAndroid, TabImpl.this); + TabImplJni.get().releaseWebContents(mNativeTabAndroid); // Since the native WebContents is still alive, we need to clear its reference to the // Java WebContents. While doing so, it will also call back into Java to destroy the // Java WebContents. @@ -1534,27 +1534,24 @@ interface Natives { TabImpl fromWebContents(WebContents webContents); void init(TabImpl caller); - void destroy(long nativeTabAndroid, TabImpl caller); - void initWebContents(long nativeTabAndroid, TabImpl caller, boolean incognito, - boolean isBackgroundTab, WebContents webContents, int parentTabId, + void destroy(long nativeTabAndroid); + void initWebContents(long nativeTabAndroid, boolean incognito, boolean isBackgroundTab, + WebContents webContents, int parentTabId, TabWebContentsDelegateAndroidImpl delegate, ContextMenuPopulatorFactory contextMenuPopulatorFactory); - void updateDelegates(long nativeTabAndroid, TabImpl caller, - TabWebContentsDelegateAndroidImpl delegate, + void updateDelegates(long nativeTabAndroid, TabWebContentsDelegateAndroidImpl delegate, ContextMenuPopulatorFactory contextMenuPopulatorFactory); - void destroyWebContents(long nativeTabAndroid, TabImpl caller); - void releaseWebContents(long nativeTabAndroid, TabImpl caller); - void onPhysicalBackingSizeChanged(long nativeTabAndroid, TabImpl caller, - WebContents webContents, int width, int height); - int loadUrl(long nativeTabAndroid, TabImpl caller, String url, Origin initiatorOrigin, - String extraHeaders, ResourceRequestBody postData, int transition, - String referrerUrl, int referrerPolicy, boolean isRendererInitiated, - boolean shoulReplaceCurrentEntry, boolean hasUserGesture, - boolean shouldClearHistoryList, long inputStartTimestamp, + void destroyWebContents(long nativeTabAndroid); + void releaseWebContents(long nativeTabAndroid); + void onPhysicalBackingSizeChanged( + long nativeTabAndroid, WebContents webContents, int width, int height); + int loadUrl(long nativeTabAndroid, String url, Origin initiatorOrigin, String extraHeaders, + ResourceRequestBody postData, int transition, String referrerUrl, + int referrerPolicy, boolean isRendererInitiated, boolean shoulReplaceCurrentEntry, + boolean hasUserGesture, boolean shouldClearHistoryList, long inputStartTimestamp, long intentReceivedTimestamp); - void setActiveNavigationEntryTitleForUrl( - long nativeTabAndroid, TabImpl caller, String url, String title); - void loadOriginalImage(long nativeTabAndroid, TabImpl caller); + void setActiveNavigationEntryTitleForUrl(long nativeTabAndroid, String url, String title); + void loadOriginalImage(long nativeTabAndroid); void setAddApi2TransitionToFutureNavigations(long nativeTabAndroid, boolean shouldAdd); boolean getAddApi2TransitionToFutureNavigations(long nativeTabAndroid); void setHideFutureNavigations(long nativeTabAndroid, boolean hide);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java index 8eda190..b26a0873 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
@@ -106,6 +106,7 @@ /** * Sets the tab that contains the information to be displayed in the toolbar. + * * @param tab The tab associated currently with the toolbar. * @param isIncognito Whether the incognito model is currently selected, which must match the * passed in tab if non-null. @@ -113,7 +114,10 @@ public void setTab(Tab tab, boolean isIncognito) { assert tab == null || tab.isIncognito() == isIncognito; mTab = tab; - mIsIncognito = isIncognito; + if (mIsIncognito != isIncognito) { + mIsIncognito = isIncognito; + notifyIncognitoStateChanged(); + } updateUsingBrandColor(); notifyTitleChanged(); notifyUrlChanged(); @@ -285,6 +289,12 @@ return mIsIncognito; } + private void notifyIncognitoStateChanged() { + for (LocationBarDataProvider.Observer observer : mLocationBarDataObservers) { + observer.onIncognitoStateChanged(); + } + } + /** * @return Whether the location bar is showing in overview mode. If the location bar should not * currently be showing in overview mode, returns false.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java index 9e92c68..8e5d63f 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/ToolbarLayout.java
@@ -525,7 +525,6 @@ */ void onTabOrModelChanged() { mTabOrModelChangeRunnable.run(); - getLocationBar().updateMicButtonState(); } /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/OverviewAppMenuTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/OverviewAppMenuTest.java index 18af3eb..0044bc3 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/OverviewAppMenuTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/app/appmenu/OverviewAppMenuTest.java
@@ -23,7 +23,6 @@ import org.chromium.base.test.util.Feature; import org.chromium.base.test.util.Restriction; import org.chromium.chrome.R; -import org.chromium.chrome.browser.flags.CachedFeatureFlags; import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.flags.ChromeSwitches; import org.chromium.chrome.browser.ui.appmenu.AppMenuTestSupport; @@ -57,9 +56,9 @@ @SmallTest @Feature({"Browser", "Main"}) @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID}) + @Features.DisableFeatures({ChromeFeatureList.START_SURFACE_ANDROID}) public void testAllMenuItemsWithoutStartSurface() throws Exception { TestThreadUtils.runOnUiThreadBlocking(() -> { - CachedFeatureFlags.setForTesting(ChromeFeatureList.START_SURFACE_ANDROID, false); AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false); }); @@ -74,8 +73,10 @@ || itemId == R.id.new_incognito_tab_menu_id || itemId == R.id.close_all_tabs_menu_id || itemId == R.id.close_all_incognito_tabs_menu_id - || itemId == R.id.menu_group_tabs || itemId == R.id.preferences_id); - if (itemId == R.id.close_all_incognito_tabs_menu_id) { + || itemId == R.id.menu_group_tabs || itemId == R.id.track_prices_row_menu_id + || itemId == R.id.preferences_id); + if (itemId == R.id.close_all_incognito_tabs_menu_id + || itemId == R.id.track_prices_row_menu_id) { assertFalse(item.isVisible()); } else { assertTrue(item.isVisible()); @@ -83,17 +84,17 @@ checkedMenuItems++; } } - assertThat(checkedMenuItems, equalTo(6)); + assertThat(checkedMenuItems, equalTo(7)); } @Test @SmallTest @Feature({"Browser", "Main"}) @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID}) + @Features.DisableFeatures({ChromeFeatureList.START_SURFACE_ANDROID}) public void testIncognitoAllMenuItemsWithoutStartSurface() throws Exception { TestThreadUtils.runOnUiThreadBlocking(() -> { mActivityTestRule.getActivity().getTabModelSelector().selectModel(true); - CachedFeatureFlags.setForTesting(ChromeFeatureList.START_SURFACE_ANDROID, false); AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false); }); @@ -108,8 +109,10 @@ || itemId == R.id.new_incognito_tab_menu_id || itemId == R.id.close_all_tabs_menu_id || itemId == R.id.close_all_incognito_tabs_menu_id - || itemId == R.id.menu_group_tabs || itemId == R.id.preferences_id); - if (itemId == R.id.close_all_tabs_menu_id) { + || itemId == R.id.menu_group_tabs || itemId == R.id.track_prices_row_menu_id + || itemId == R.id.preferences_id); + if (itemId == R.id.close_all_tabs_menu_id + || itemId == R.id.track_prices_row_menu_id) { assertFalse(item.isVisible()); } else { assertTrue(item.isVisible()); @@ -117,16 +120,16 @@ checkedMenuItems++; } } - assertThat(checkedMenuItems, equalTo(6)); + assertThat(checkedMenuItems, equalTo(7)); } @Test @SmallTest @Feature({"Browser", "Main"}) - @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID}) + @Features. + EnableFeatures({ChromeFeatureList.START_SURFACE_ANDROID, ChromeFeatureList.TAB_GROUPS_ANDROID}) public void testAllMenuItemsWithStartSurface() throws Exception { TestThreadUtils.runOnUiThreadBlocking(() -> { - CachedFeatureFlags.setForTesting(ChromeFeatureList.START_SURFACE_ANDROID, true); AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false); }); @@ -143,8 +146,10 @@ || itemId == R.id.recent_tabs_menu_id || itemId == R.id.open_history_menu_id || itemId == R.id.downloads_menu_id || itemId == R.id.close_all_tabs_menu_id || itemId == R.id.close_all_incognito_tabs_menu_id - || itemId == R.id.menu_group_tabs || itemId == R.id.preferences_id); - if (itemId == R.id.close_all_incognito_tabs_menu_id) { + || itemId == R.id.menu_group_tabs || itemId == R.id.track_prices_row_menu_id + || itemId == R.id.preferences_id); + if (itemId == R.id.close_all_incognito_tabs_menu_id + || itemId == R.id.track_prices_row_menu_id) { assertFalse(item.isVisible()); } else { assertTrue(item.isVisible()); @@ -152,17 +157,17 @@ checkedMenuItems++; } } - assertThat(checkedMenuItems, equalTo(10)); + assertThat(checkedMenuItems, equalTo(11)); } @Test @SmallTest @Feature({"Browser", "Main"}) - @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID}) + @Features. + EnableFeatures({ChromeFeatureList.START_SURFACE_ANDROID, ChromeFeatureList.TAB_GROUPS_ANDROID}) public void testIncognitoAllMenuItemsWithStartSurface() throws Exception { TestThreadUtils.runOnUiThreadBlocking(() -> { mActivityTestRule.getActivity().getTabModelSelector().selectModel(true); - CachedFeatureFlags.setForTesting(ChromeFeatureList.START_SURFACE_ANDROID, true); AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false); }); @@ -179,8 +184,10 @@ || itemId == R.id.recent_tabs_menu_id || itemId == R.id.open_history_menu_id || itemId == R.id.downloads_menu_id || itemId == R.id.close_all_tabs_menu_id || itemId == R.id.close_all_incognito_tabs_menu_id - || itemId == R.id.menu_group_tabs || itemId == R.id.preferences_id); - if (itemId == R.id.close_all_tabs_menu_id || itemId == R.id.recent_tabs_menu_id) { + || itemId == R.id.menu_group_tabs || itemId == R.id.track_prices_row_menu_id + || itemId == R.id.preferences_id); + if (itemId == R.id.close_all_tabs_menu_id || itemId == R.id.recent_tabs_menu_id + || itemId == R.id.track_prices_row_menu_id) { assertFalse(item.isVisible()); } else { assertTrue(item.isVisible()); @@ -188,16 +195,16 @@ checkedMenuItems++; } } - assertThat(checkedMenuItems, equalTo(10)); + assertThat(checkedMenuItems, equalTo(11)); } @Test @SmallTest @Feature({"Browser", "Main"}) - @Features.DisableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID}) + @Features. + DisableFeatures({ChromeFeatureList.START_SURFACE_ANDROID, ChromeFeatureList.TAB_GROUPS_ANDROID}) public void testGroupTabsIsDisabled() throws Exception { TestThreadUtils.runOnUiThreadBlocking(() -> { - CachedFeatureFlags.setForTesting(ChromeFeatureList.START_SURFACE_ANDROID, false); AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false); }); @@ -217,9 +224,9 @@ @SmallTest @Feature({"Browser", "Main"}) @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID}) + @Features.DisableFeatures({ChromeFeatureList.START_SURFACE_ANDROID}) public void testGroupTabsIsEnabled() throws Exception { TestThreadUtils.runOnUiThreadBlocking(() -> { - CachedFeatureFlags.setForTesting(ChromeFeatureList.START_SURFACE_ANDROID, false); AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false); }); @@ -244,10 +251,10 @@ @Test @SmallTest @Feature({"Browser", "Main"}) + @Features.EnableFeatures({ChromeFeatureList.START_SURFACE_ANDROID}) @Features.DisableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID}) public void testGroupTabsIsDisabledWithStartSurface() throws Exception { TestThreadUtils.runOnUiThreadBlocking(() -> { - CachedFeatureFlags.setForTesting(ChromeFeatureList.START_SURFACE_ANDROID, true); AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false); }); @@ -266,10 +273,10 @@ @Test @SmallTest @Feature({"Browser", "Main"}) - @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID}) + @Features. + EnableFeatures({ChromeFeatureList.START_SURFACE_ANDROID, ChromeFeatureList.TAB_GROUPS_ANDROID}) public void testGroupTabsIsEnabledWithStartSurface() throws Exception { TestThreadUtils.runOnUiThreadBlocking(() -> { - CachedFeatureFlags.setForTesting(ChromeFeatureList.START_SURFACE_ANDROID, true); AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false); }); @@ -290,4 +297,116 @@ } assertThat(checkedMenuItems, equalTo(2)); } + + @Test + @SmallTest + @Feature({"Browser", "Main"}) + @Features.EnableFeatures({ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID + "<Study"}) + @Features.DisableFeatures({ChromeFeatureList.START_SURFACE_ANDROID}) + @CommandLineFlags.Add({"force-fieldtrials=Study/Group", + "force-fieldtrial-params=Study.Group:enable_price_tracking/false"}) + public void + testTrackPriceOnTabsIsDisabled() throws Exception { + TestThreadUtils.runOnUiThreadBlocking(() -> { + AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false); + }); + + int checkedMenuItems = 0; + Menu menu = mActivityTestRule.getMenu(); + for (int i = 0; i < menu.size(); ++i) { + MenuItem item = menu.getItem(i); + if (item.getItemId() == R.id.track_prices_row_menu_id) { + assertFalse(item.isVisible()); + checkedMenuItems++; + } + } + assertThat(checkedMenuItems, equalTo(2)); + } + + @Test + @SmallTest + @Feature({"Browser", "Main"}) + @Features.EnableFeatures({ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID + "<Study"}) + @Features.DisableFeatures({ChromeFeatureList.START_SURFACE_ANDROID}) + @CommandLineFlags.Add({"force-fieldtrials=Study/Group", + "force-fieldtrial-params=Study.Group:enable_price_tracking/true"}) + public void + testTrackPriceOnTabsIsEnabled() throws Exception { + TestThreadUtils.runOnUiThreadBlocking(() -> { + AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false); + }); + + int checkedMenuItems = 0; + Menu menu = mActivityTestRule.getMenu(); + for (int i = 0; i < menu.size(); ++i) { + MenuItem item = menu.getItem(i); + if (item.getItemId() == R.id.track_prices_row_menu_id) { + int itemGroupId = item.getGroupId(); + if (itemGroupId == R.id.OVERVIEW_MODE_MENU) { + assertTrue(item.isVisible()); + } + if (itemGroupId == R.id.START_SURFACE_MODE_MENU) { + assertFalse(item.isVisible()); + } + checkedMenuItems++; + } + } + assertThat(checkedMenuItems, equalTo(2)); + } + + @Test + @SmallTest + @Feature({"Browser", "Main"}) + @Features.EnableFeatures({ChromeFeatureList.START_SURFACE_ANDROID, + ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID + "<Study"}) + @CommandLineFlags.Add({"force-fieldtrials=Study/Group", + "force-fieldtrial-params=Study.Group:enable_price_tracking/false"}) + public void + testTrackPriceOnTabsIsDisabledWithStartSurface() throws Exception { + TestThreadUtils.runOnUiThreadBlocking(() -> { + AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false); + }); + + int checkedMenuItems = 0; + Menu menu = mActivityTestRule.getMenu(); + for (int i = 0; i < menu.size(); ++i) { + MenuItem item = menu.getItem(i); + if (item.getItemId() == R.id.track_prices_row_menu_id) { + assertFalse(item.isVisible()); + checkedMenuItems++; + } + } + assertThat(checkedMenuItems, equalTo(2)); + } + + @Test + @SmallTest + @Feature({"Browser", "Main"}) + @Features.EnableFeatures({ChromeFeatureList.START_SURFACE_ANDROID, + ChromeFeatureList.TAB_GRID_LAYOUT_ANDROID + "<Study"}) + @CommandLineFlags.Add({"force-fieldtrials=Study/Group", + "force-fieldtrial-params=Study.Group:enable_price_tracking/true"}) + public void + testTrackPriceOnTabsIsEnabledWithStartSurface() throws Exception { + TestThreadUtils.runOnUiThreadBlocking(() -> { + AppMenuTestSupport.showAppMenu(mActivityTestRule.getAppMenuCoordinator(), null, false); + }); + + int checkedMenuItems = 0; + Menu menu = mActivityTestRule.getMenu(); + for (int i = 0; i < menu.size(); ++i) { + MenuItem item = menu.getItem(i); + if (item.getItemId() == R.id.track_prices_row_menu_id) { + int itemGroupId = item.getGroupId(); + if (itemGroupId == R.id.OVERVIEW_MODE_MENU) { + assertFalse(item.isVisible()); + } + if (itemGroupId == R.id.START_SURFACE_MODE_MENU) { + assertTrue(item.isVisible()); + } + checkedMenuItems++; + } + } + assertThat(checkedMenuItems, equalTo(2)); + } }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/LocationBarModelTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/LocationBarModelTest.java index ae3ea94..87aaf3b 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/LocationBarModelTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/LocationBarModelTest.java
@@ -4,6 +4,12 @@ package org.chromium.chrome.browser.toolbar; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + import android.support.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; @@ -16,25 +22,34 @@ import org.junit.runner.RunWith; import org.chromium.base.ContextUtils; +import org.chromium.base.test.params.ParameterAnnotations; +import org.chromium.base.test.params.ParameterProvider; +import org.chromium.base.test.params.ParameterSet; +import org.chromium.base.test.params.ParameterizedRunner; import org.chromium.base.test.util.CommandLineFlags; import org.chromium.base.test.util.Feature; import org.chromium.chrome.R; import org.chromium.chrome.browser.ChromeTabbedActivity; import org.chromium.chrome.browser.flags.ChromeSwitches; +import org.chromium.chrome.browser.omnibox.LocationBarDataProvider; import org.chromium.chrome.browser.omnibox.UrlBarData; import org.chromium.chrome.browser.tab.MockTab; import org.chromium.chrome.browser.tab.Tab; +import org.chromium.chrome.browser.tab.TabSelectionType; import org.chromium.chrome.browser.toolbar.top.ToolbarLayout; -import org.chromium.chrome.test.ChromeJUnit4ClassRunner; import org.chromium.chrome.test.ChromeTabbedActivityTestRule; import org.chromium.chrome.test.util.ChromeTabUtils; import org.chromium.components.embedder_support.util.UrlConstants; import org.chromium.content_public.browser.test.util.TestThreadUtils; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + /** * Tests for LocationBarModel. */ -@RunWith(ChromeJUnit4ClassRunner.class) +@RunWith(ParameterizedRunner.class) @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) public class LocationBarModelTest { @Rule @@ -57,9 +72,9 @@ getCurrentTabId(mActivityTestRule.getActivity())); ChromeTabUtils.closeCurrentTab( InstrumentationRegistry.getInstrumentation(), mActivityTestRule.getActivity()); - Assert.assertEquals("Didn't close all tabs.", 0, + assertEquals("Didn't close all tabs.", 0, ChromeTabUtils.getNumOpenTabs(mActivityTestRule.getActivity())); - Assert.assertEquals("LocationBarModel is still trying to show a tab.", Tab.INVALID_TAB_ID, + assertEquals("LocationBarModel is still trying to show a tab.", Tab.INVALID_TAB_ID, getCurrentTabId(mActivityTestRule.getActivity())); } @@ -88,13 +103,107 @@ }); } + /** Provides parameters for different types of transitions between tabs. */ + public static class IncognitoTransitionParamProvider implements ParameterProvider { + @Override + public Iterable<ParameterSet> getParameters() { + List<ParameterSet> result = new ArrayList<>(8); + for (boolean fromIncognito : Arrays.asList(true, false)) { + for (boolean toIncognito : Arrays.asList(true, false)) { + result.add(new ParameterSet() + .value(fromIncognito, toIncognito) + .name(String.format( + "from_%b_to_%b", fromIncognito, toIncognito))); + } + } + return result; + } + } + + @Test + @MediumTest + @ParameterAnnotations.UseMethodParameter(IncognitoTransitionParamProvider.class) + public void testOnIncognitoStateChange_switchTab(boolean fromIncognito, boolean toIncognito) { + // Add a regular tab next to the one created in setup. + mActivityTestRule.loadUrlInNewTab("about:blank", /*incognito=*/false); + // Add two incognito tabs. + mActivityTestRule.loadUrlInNewTab("about:blank", /*incognito=*/true); + mActivityTestRule.loadUrlInNewTab("about:blank", /*incognito=*/true); + + ChromeTabbedActivity activity = mActivityTestRule.getActivity(); + LocationBarModel locationBarModel = + activity.getToolbarManager().getLocationBarModelForTesting(); + LocationBarDataProvider.Observer observer = mock(LocationBarDataProvider.Observer.class); + doAnswer((invocation) -> { + assertEquals(toIncognito, locationBarModel.isIncognito()); + return null; + }) + .when(observer) + .onIncognitoStateChanged(); + + TestThreadUtils.runOnUiThreadBlocking(() -> { + mActivityTestRule.getActivity().getTabModelSelector().selectModel(fromIncognito); + locationBarModel.addObserver(observer); + + // Switch to an existing tab. + mActivityTestRule.getActivity().getTabModelSelector().selectModel(/*incognito=*/ + toIncognito); + mActivityTestRule.getActivity().getTabModelSelector().getCurrentModel().setIndex( + 0, TabSelectionType.FROM_USER); + }); + + assertEquals(toIncognito, locationBarModel.isIncognito()); + if (fromIncognito != toIncognito) { + verify(observer).onIncognitoStateChanged(); + } else { + verify(observer, times(0)).onIncognitoStateChanged(); + } + } + + @Test + @MediumTest + @ParameterAnnotations.UseMethodParameter(IncognitoTransitionParamProvider.class) + public void testOnIncognitoStateChange_newTab(boolean fromIncognito, boolean toIncognito) { + // Add a regular tab next to the one created in setup. + mActivityTestRule.loadUrlInNewTab("about:blank", /*incognito=*/false); + // Add two incognito tabs. + mActivityTestRule.loadUrlInNewTab("about:blank", /*incognito=*/true); + mActivityTestRule.loadUrlInNewTab("about:blank", /*incognito=*/true); + + ChromeTabbedActivity activity = mActivityTestRule.getActivity(); + LocationBarModel locationBarModel = + activity.getToolbarManager().getLocationBarModelForTesting(); + LocationBarDataProvider.Observer observer = mock(LocationBarDataProvider.Observer.class); + doAnswer((invocation) -> { + assertEquals(toIncognito, locationBarModel.isIncognito()); + return null; + }) + .when(observer) + .onIncognitoStateChanged(); + + TestThreadUtils.runOnUiThreadBlocking(() -> { + mActivityTestRule.getActivity().getTabModelSelector().selectModel(fromIncognito); + locationBarModel.addObserver(observer); + }); + + // Switch to a new tab. + mActivityTestRule.loadUrlInNewTab("about:blank", toIncognito); + + assertEquals(toIncognito, locationBarModel.isIncognito()); + if (fromIncognito != toIncognito) { + verify(observer).onIncognitoStateChanged(); + } else { + verify(observer, times(0)).onIncognitoStateChanged(); + } + } + private void assertDisplayAndEditText( ToolbarDataProvider dataProvider, String displayText, String editText) { TestThreadUtils.runOnUiThreadBlocking(() -> { UrlBarData urlBarData = dataProvider.getUrlBarData(); - Assert.assertEquals( + assertEquals( "Display text did not match", displayText, urlBarData.displayText.toString()); - Assert.assertEquals("Editing text did not match", editText, urlBarData.editingText); + assertEquals("Editing text did not match", editText, urlBarData.editingText); }); }
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn index 3535928d..086c720d 100644 --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn
@@ -4666,6 +4666,8 @@ "win/titlebar_config.h", "win/ui_automation_util.cc", "win/ui_automation_util.h", + "win/uninstallation_via_os_settings.cc", + "win/uninstallation_via_os_settings.h", "win/util_win_service.cc", "win/util_win_service.h", ]
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index eeffe355..2d0ef27 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc
@@ -56,7 +56,6 @@ #include "chrome/browser/unexpire_flags.h" #include "chrome/browser/unexpire_flags_gen.h" #include "chrome/browser/video_tutorials/switches.h" -#include "chrome/browser/web_applications/components/external_app_install_features.h" #include "chrome/common/buildflags.h" #include "chrome/common/channel_info.h" #include "chrome/common/chrome_content_client.h" @@ -3752,7 +3751,7 @@ #if defined(OS_CHROMEOS) {"camera-system-web-app", flag_descriptions::kCameraSystemWebAppName, flag_descriptions::kCameraSystemWebAppDescription, kOsCrOS, - FEATURE_VALUE_TYPE(web_app::kCameraSystemWebApp)}, + FEATURE_VALUE_TYPE(chromeos::features::kCameraSystemWebApp)}, {"crostini-gpu-support", flag_descriptions::kCrostiniGpuSupportName, flag_descriptions::kCrostiniGpuSupportDescription, kOsCrOS, FEATURE_VALUE_TYPE(chromeos::features::kCrostiniGpuSupport)},
diff --git a/chrome/browser/android/tab_android.cc b/chrome/browser/android/tab_android.cc index 03cdb2a..6b029714 100644 --- a/chrome/browser/android/tab_android.cc +++ b/chrome/browser/android/tab_android.cc
@@ -240,13 +240,12 @@ observers_.RemoveObserver(observer); } -void TabAndroid::Destroy(JNIEnv* env, const JavaParamRef<jobject>& obj) { +void TabAndroid::Destroy(JNIEnv* env) { delete this; } void TabAndroid::InitWebContents( JNIEnv* env, - const JavaParamRef<jobject>& obj, jboolean incognito, jboolean is_background_tab, const JavaParamRef<jobject>& jweb_contents, @@ -292,7 +291,6 @@ void TabAndroid::UpdateDelegates( JNIEnv* env, - const JavaParamRef<jobject>& obj, const JavaParamRef<jobject>& jweb_contents_delegate, const JavaParamRef<jobject>& jcontext_menu_populator_factory) { ContextMenuHelper::FromWebContents(web_contents()) @@ -314,8 +312,7 @@ } } // namespace -void TabAndroid::DestroyWebContents(JNIEnv* env, - const JavaParamRef<jobject>& obj) { +void TabAndroid::DestroyWebContents(JNIEnv* env) { WillRemoveWebContentsFromTab(web_contents()); // Terminate the renderer process if this is the last tab. @@ -335,8 +332,7 @@ synced_tab_delegate_->ResetWebContents(); } -void TabAndroid::ReleaseWebContents(JNIEnv* env, - const JavaParamRef<jobject>& obj) { +void TabAndroid::ReleaseWebContents(JNIEnv* env) { WillRemoveWebContentsFromTab(web_contents()); // Ownership of |released_contents| is assumed by the code that initiated the @@ -352,7 +348,6 @@ void TabAndroid::OnPhysicalBackingSizeChanged( JNIEnv* env, - const JavaParamRef<jobject>& obj, const JavaParamRef<jobject>& jweb_contents, jint width, jint height) { @@ -364,7 +359,6 @@ TabAndroid::TabLoadStatus TabAndroid::LoadUrl( JNIEnv* env, - const JavaParamRef<jobject>& obj, const JavaParamRef<jstring>& url, const JavaParamRef<jobject>& j_initiator_origin, const JavaParamRef<jstring>& j_extra_headers, @@ -441,7 +435,6 @@ void TabAndroid::SetActiveNavigationEntryTitleForUrl( JNIEnv* env, - const JavaParamRef<jobject>& obj, const JavaParamRef<jstring>& jurl, const JavaParamRef<jstring>& jtitle) { DCHECK(web_contents()); @@ -460,8 +453,7 @@ entry->SetTitle(title); } -void TabAndroid::LoadOriginalImage(JNIEnv* env, - const JavaParamRef<jobject>& obj) { +void TabAndroid::LoadOriginalImage(JNIEnv* env) { content::RenderFrameHost* render_frame_host = web_contents()->GetFocusedFrame(); mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame> renderer;
diff --git a/chrome/browser/android/tab_android.h b/chrome/browser/android/tab_android.h index fa2aeab06..c8f0ba2d 100644 --- a/chrome/browser/android/tab_android.h +++ b/chrome/browser/android/tab_android.h
@@ -128,10 +128,9 @@ // Methods called from Java via JNI ----------------------------------------- - void Destroy(JNIEnv* env, const base::android::JavaParamRef<jobject>& obj); + void Destroy(JNIEnv* env); void InitWebContents( JNIEnv* env, - const base::android::JavaParamRef<jobject>& obj, jboolean incognito, jboolean is_background_tab, const base::android::JavaParamRef<jobject>& jweb_contents, @@ -141,23 +140,18 @@ jcontext_menu_populator_factory); void UpdateDelegates( JNIEnv* env, - const base::android::JavaParamRef<jobject>& obj, const base::android::JavaParamRef<jobject>& jweb_contents_delegate, const base::android::JavaParamRef<jobject>& jcontext_menu_populator_factory); - void DestroyWebContents(JNIEnv* env, - const base::android::JavaParamRef<jobject>& obj); - void ReleaseWebContents(JNIEnv* env, - const base::android::JavaParamRef<jobject>& obj); + void DestroyWebContents(JNIEnv* env); + void ReleaseWebContents(JNIEnv* env); void OnPhysicalBackingSizeChanged( JNIEnv* env, - const base::android::JavaParamRef<jobject>& obj, const base::android::JavaParamRef<jobject>& jweb_contents, jint width, jint height); TabLoadStatus LoadUrl( JNIEnv* env, - const base::android::JavaParamRef<jobject>& obj, const base::android::JavaParamRef<jstring>& url, const base::android::JavaParamRef<jobject>& j_initiator_origin, const base::android::JavaParamRef<jstring>& j_extra_headers, @@ -173,12 +167,10 @@ jlong intent_received_timestamp); void SetActiveNavigationEntryTitleForUrl( JNIEnv* env, - const base::android::JavaParamRef<jobject>& obj, const base::android::JavaParamRef<jstring>& jurl, const base::android::JavaParamRef<jstring>& jtitle); - void LoadOriginalImage(JNIEnv* env, - const base::android::JavaParamRef<jobject>& obj); + void LoadOriginalImage(JNIEnv* env); void SetAddApi2TransitionToFutureNavigations(JNIEnv* env, jboolean should_add); jboolean GetAddApi2TransitionToFutureNavigations(JNIEnv* env) {
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc index 6a5bebf..439d38d 100644 --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc
@@ -3842,28 +3842,6 @@ #endif // defined(OS_WIN) -void ChromeContentBrowserClient::WillStartServiceManager() { -#if defined(OS_WIN) || defined(OS_MAC) || \ - (defined(OS_LINUX) && !defined(OS_CHROMEOS)) - auto* chrome_feature_list_creator = - startup_data_.chrome_feature_list_creator(); - // This has to run very early before ServiceManagerContext is created. - const policy::PolicyMap& policies = - chrome_feature_list_creator->browser_policy_connector() - ->GetPolicyService() - ->GetPolicies(policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME, - std::string())); - const base::Value* audio_sandbox_enabled_policy_value = - policies.GetValue(policy::key::kAudioSandboxEnabled); - if (audio_sandbox_enabled_policy_value) { - bool force_enable_audio_sandbox; - audio_sandbox_enabled_policy_value->GetAsBoolean( - &force_enable_audio_sandbox); - SetForceAudioServiceSandboxed(force_enable_audio_sandbox); - } -#endif -} - void ChromeContentBrowserClient::OpenURL( content::SiteInstance* site_instance, const content::OpenURLParams& params,
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h index e349796d..d08435df 100644 --- a/chrome/browser/chrome_content_browser_client.h +++ b/chrome/browser/chrome_content_browser_client.h
@@ -429,7 +429,6 @@ void BindHostReceiverForRenderer( content::RenderProcessHost* render_process_host, mojo::GenericPendingReceiver receiver) override; - void WillStartServiceManager() override; void OpenURL( content::SiteInstance* site_instance, const content::OpenURLParams& params,
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn index 2996907..ac4f72c 100644 --- a/chrome/browser/chromeos/BUILD.gn +++ b/chrome/browser/chromeos/BUILD.gn
@@ -1978,6 +1978,8 @@ "net/network_diagnostics/gateway_can_be_pinged_routine.h", "net/network_diagnostics/has_secure_wifi_connection_routine.cc", "net/network_diagnostics/has_secure_wifi_connection_routine.h", + "net/network_diagnostics/host_resolver.cc", + "net/network_diagnostics/host_resolver.h", "net/network_diagnostics/http_firewall_routine.cc", "net/network_diagnostics/http_firewall_routine.h", "net/network_diagnostics/http_request_manager.cc", @@ -1998,6 +2000,8 @@ "net/network_diagnostics/signal_strength_routine.h", "net/network_diagnostics/tls_prober.cc", "net/network_diagnostics/tls_prober.h", + "net/network_diagnostics/udp_prober.cc", + "net/network_diagnostics/udp_prober.h", "net/network_health/network_health.cc", "net/network_health/network_health.h", "net/network_health/network_health_localized_strings.cc", @@ -3593,8 +3597,11 @@ "net/network_diagnostics/fake_network_context.h", "net/network_diagnostics/fake_tcp_connected_socket.cc", "net/network_diagnostics/fake_tcp_connected_socket.h", + "net/network_diagnostics/fake_udp_socket.cc", + "net/network_diagnostics/fake_udp_socket.h", "net/network_diagnostics/gateway_can_be_pinged_routine_unittest.cc", "net/network_diagnostics/has_secure_wifi_connection_routine_unittest.cc", + "net/network_diagnostics/host_resolver_unittest.cc", "net/network_diagnostics/http_firewall_routine_unittest.cc", "net/network_diagnostics/http_request_manager_unittest.cc", "net/network_diagnostics/https_firewall_routine_unittest.cc", @@ -3605,6 +3612,7 @@ "net/network_diagnostics/network_diagnostics_util_unittest.cc", "net/network_diagnostics/signal_strength_routine_unittest.cc", "net/network_diagnostics/tls_prober_unittest.cc", + "net/network_diagnostics/udp_prober_unittest.cc", "net/network_health/network_health_unittest.cc", "net/network_portal_detector_impl_unittest.cc", "net/network_pref_state_observer_unittest.cc",
diff --git a/chrome/browser/chromeos/net/network_diagnostics/fake_host_resolver.cc b/chrome/browser/chromeos/net/network_diagnostics/fake_host_resolver.cc index 381891c..8ecd070 100644 --- a/chrome/browser/chromeos/net/network_diagnostics/fake_host_resolver.cc +++ b/chrome/browser/chromeos/net/network_diagnostics/fake_host_resolver.cc
@@ -43,13 +43,13 @@ receiver_.reset(); return; } - mojo::Remote<network::mojom::ResolveHostClient> response_client( - std::move(pending_response_client)); - DnsResult* result = fake_dns_results_.front(); - DCHECK(result); - fake_dns_results_.pop_front(); - response_client->OnComplete(result->result_, result->resolve_error_info_, - result->resolved_addresses_); + response_client_.Bind(std::move(pending_response_client)); + + DCHECK(fake_dns_result_); + response_client_->OnComplete(fake_dns_result_->result_, + fake_dns_result_->resolve_error_info_, + fake_dns_result_->resolved_addresses_); + fake_dns_result_.release(); } void FakeHostResolver::MdnsListen( @@ -60,5 +60,12 @@ NOTIMPLEMENTED(); } +void FakeHostResolver::SetFakeDnsResult( + std::unique_ptr<DnsResult> fake_dns_result) { + DCHECK(!fake_dns_result_); + + fake_dns_result_ = std::move(fake_dns_result); +} + } // namespace network_diagnostics } // namespace chromeos
diff --git a/chrome/browser/chromeos/net/network_diagnostics/fake_host_resolver.h b/chrome/browser/chromeos/net/network_diagnostics/fake_host_resolver.h index c75243c2..7508513 100644 --- a/chrome/browser/chromeos/net/network_diagnostics/fake_host_resolver.h +++ b/chrome/browser/chromeos/net/network_diagnostics/fake_host_resolver.h
@@ -5,7 +5,8 @@ #ifndef CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_FAKE_HOST_RESOLVER_H_ #define CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_FAKE_HOST_RESOLVER_H_ -#include <deque> +#include <memory> +#include <utility> #include "base/optional.h" #include "mojo/public/cpp/bindings/pending_receiver.h" @@ -49,10 +50,8 @@ mojo::PendingRemote<network::mojom::MdnsListenClient> response_client, MdnsListenCallback callback) override; - // Sets the fake dns results. - void set_fake_dns_results(std::deque<DnsResult*> fake_dns_results) { - fake_dns_results_ = std::move(fake_dns_results); - } + // Sets the fake DNS result for single host resolutions. + void SetFakeDnsResult(std::unique_ptr<DnsResult> fake_dns_result); // If set to true, the binding pipe will be disconnected when attempting to // connect. @@ -61,10 +60,12 @@ } private: + // Handles calls to the HostResolver. mojo::Receiver<network::mojom::HostResolver> receiver_; - // Use the list of fake dns results to fake different responses for multiple - // calls to the host_resolver's ResolveHost(). - std::deque<DnsResult*> fake_dns_results_; + // Responds to calls made to |this|. + mojo::Remote<network::mojom::ResolveHostClient> response_client_; + // Use the |fake_dns_result| to fake a single host resolution. + std::unique_ptr<DnsResult> fake_dns_result_ = nullptr; // Used to mimic the scenario where network::mojom::HostResolver receiver // is disconnected. bool disconnect_ = false;
diff --git a/chrome/browser/chromeos/net/network_diagnostics/fake_network_context.cc b/chrome/browser/chromeos/net/network_diagnostics/fake_network_context.cc index d9b6a59..4d37e06 100644 --- a/chrome/browser/chromeos/net/network_diagnostics/fake_network_context.cc +++ b/chrome/browser/chromeos/net/network_diagnostics/fake_network_context.cc
@@ -6,6 +6,8 @@ #include <utility> +#include "base/logging.h" + namespace chromeos { namespace network_diagnostics { @@ -30,8 +32,8 @@ mojo::PendingReceiver<network::mojom::HostResolver> receiver) { DCHECK(!resolver_); resolver_ = std::make_unique<FakeHostResolver>(std::move(receiver)); - resolver_->set_fake_dns_results(std::move(fake_dns_results_)); resolver_->set_disconnect_during_host_resolution(host_resolution_disconnect_); + resolver_->SetFakeDnsResult(std::move(fake_dns_result_)); } void FakeNetworkContext::CreateTCPConnectedSocket( @@ -59,6 +61,24 @@ mojo::ScopedDataPipeProducerHandle()); } +void FakeNetworkContext::CreateUDPSocket( + mojo::PendingReceiver<network::mojom::UDPSocket> receiver, + mojo::PendingRemote<network::mojom::UDPSocketListener> listener) { + if (udp_connection_attempt_disconnect_) { + receiver.reset(); + listener.reset(); + return; + } + + // Bind the receiver if UDP connection is successful. + if (udp_connect_code_ == net::OK) { + DCHECK(fake_udp_socket_); + + fake_udp_socket_->BindReceiver(std::move(receiver)); + fake_udp_socket_->BindRemote(std::move(listener)); + } +} + void FakeNetworkContext::SetTCPConnectCode( base::Optional<net::Error>& tcp_connect_code) { if (tcp_connect_code.has_value()) { @@ -74,5 +94,41 @@ } } +void FakeNetworkContext::SetUdpConnectCode(net::Error udp_connect_code) { + fake_udp_socket_ = std::make_unique<FakeUdpSocket>(); + fake_udp_socket_->set_udp_connect_code(udp_connect_code); +} + +void FakeNetworkContext::SetUdpSendCode(net::Error udp_send_code) { + DCHECK(fake_udp_socket_); + + fake_udp_socket_->set_udp_send_code(udp_send_code); +} + +void FakeNetworkContext::SetDisconnectDuringUdpSendAttempt(bool disconnect) { + DCHECK(fake_udp_socket_); + + fake_udp_socket_->set_disconnect_during_udp_send_attempt(disconnect); +} + +void FakeNetworkContext::SetUdpOnReceivedCode(net::Error udp_on_received_code) { + DCHECK(fake_udp_socket_); + + fake_udp_socket_->set_udp_on_received_code(udp_on_received_code); +} + +void FakeNetworkContext::SetUdpOnReceivedData( + base::span<const uint8_t> udp_on_received_data) { + DCHECK(fake_udp_socket_); + + fake_udp_socket_->set_udp_on_received_data(std::move(udp_on_received_data)); +} + +void FakeNetworkContext::SetDisconnectDuringUdpReceiveAttempt(bool disconnect) { + DCHECK(fake_udp_socket_); + + fake_udp_socket_->set_disconnect_during_udp_receive_attempt(disconnect); +} + } // namespace network_diagnostics } // namespace chromeos
diff --git a/chrome/browser/chromeos/net/network_diagnostics/fake_network_context.h b/chrome/browser/chromeos/net/network_diagnostics/fake_network_context.h index 6a0a9cc..99432c4 100644 --- a/chrome/browser/chromeos/net/network_diagnostics/fake_network_context.h +++ b/chrome/browser/chromeos/net/network_diagnostics/fake_network_context.h
@@ -7,9 +7,13 @@ #include <deque> #include <memory> +#include <utility> +#include "base/containers/span.h" +#include "base/optional.h" #include "chrome/browser/chromeos/net/network_diagnostics/fake_host_resolver.h" #include "chrome/browser/chromeos/net/network_diagnostics/fake_tcp_connected_socket.h" +#include "chrome/browser/chromeos/net/network_diagnostics/fake_udp_socket.h" #include "services/network/test/test_network_context.h" namespace chromeos { @@ -20,6 +24,8 @@ class FakeNetworkContext : public network::TestNetworkContext { public: FakeNetworkContext(); + FakeNetworkContext(const FakeNetworkContext&) = delete; + FakeNetworkContext& operator=(const FakeNetworkContext&) = delete; ~FakeNetworkContext() override; // network::TestNetworkContext: @@ -36,16 +42,40 @@ mojo::PendingRemote<network::mojom::SocketObserver> observer, CreateTCPConnectedSocketCallback callback) override; - // Sets the fake TCP connect code. + void CreateUDPSocket( + mojo::PendingReceiver<network::mojom::UDPSocket> receiver, + mojo::PendingRemote<network::mojom::UDPSocketListener> listener) override; + + // Sets the fake TCP connect code. TODO(khegde): Change this to + // SetTCPConnectCompleteCode. void SetTCPConnectCode(base::Optional<net::Error>& tcp_connect_code); // Sets the fake TLS upgrade code. void SetTLSUpgradeCode(base::Optional<net::Error>& tls_upgrade_code); - // Sets the fake dns results. - void set_fake_dns_results( - std::deque<FakeHostResolver::DnsResult*> fake_dns_results) { - fake_dns_results_ = std::move(fake_dns_results); + // Sets the fake UDP connect code. + void SetUdpConnectCode(net::Error udp_connect_code); + + // Sets the fake UDP send code. + void SetUdpSendCode(net::Error udp_send_code); + + // Sets the state to mimic a fake disconnect during a UDP send attempt. + void SetDisconnectDuringUdpSendAttempt(bool disconnect); + + // Sets the fake UDP on received code. + void SetUdpOnReceivedCode(net::Error udp_on_received_code); + + // Sets the fake UDP on received data. + void SetUdpOnReceivedData(base::span<const uint8_t> udp_on_received_data); + + // Sets the state to mimic a fake disconnect after receiving successful send + // confirmation, but before receiving any data. + void SetDisconnectDuringUdpReceiveAttempt(bool disconnect); + + // Sets the fake DNS result. Used to test a single host resolution. + void set_fake_dns_result( + std::unique_ptr<FakeHostResolver::DnsResult> fake_dns_result) { + fake_dns_result_ = std::move(fake_dns_result); } // If set to true, the binding pipe will be disconnected when attempting to @@ -66,15 +96,28 @@ tls_upgrade_attempt_disconnect_ = disconnect; } + // If set to true, the binding pipe will be disconnected when attempting to + // connect. + void set_disconnect_during_udp_connection_attempt(bool disconnect) { + udp_connection_attempt_disconnect_ = disconnect; + } + private: // Fake host resolver. std::unique_ptr<FakeHostResolver> resolver_; // Fake DNS lookup results. std::deque<FakeHostResolver::DnsResult*> fake_dns_results_; + // Fake DNS lookup result. + std::unique_ptr<FakeHostResolver::DnsResult> fake_dns_result_; // Provides the TCP socket functionality for tests. std::unique_ptr<FakeTCPConnectedSocket> fake_tcp_connected_socket_; + // Provides the UDP socket functionality for tests. + std::unique_ptr<FakeUdpSocket> fake_udp_socket_; // TCP connect code corresponding to the connection attempt. net::Error tcp_connect_code_ = net::OK; + // UDP connect code corresponding to the connection attempt. + net::Error udp_connect_code_ = net::OK; + // Used to mimic the scenario where network::mojom::HostResolver receiver // is disconnected. bool host_resolution_disconnect_ = false; @@ -84,6 +127,9 @@ // Used to mimic the scenario where network::mojom::TLSClientSocket receiver // is disconnected. bool tls_upgrade_attempt_disconnect_ = false; + // Used to mimic the scenario where network::mojom::UDPSocket receiver is + // disconnected while connecting. + bool udp_connection_attempt_disconnect_ = false; }; } // namespace network_diagnostics
diff --git a/chrome/browser/chromeos/net/network_diagnostics/fake_udp_socket.cc b/chrome/browser/chromeos/net/network_diagnostics/fake_udp_socket.cc new file mode 100644 index 0000000..9c59ac7 --- /dev/null +++ b/chrome/browser/chromeos/net/network_diagnostics/fake_udp_socket.cc
@@ -0,0 +1,117 @@ +// Copyright 2020 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/chromeos/net/network_diagnostics/fake_udp_socket.h" + +#include <utility> + +#include "base/optional.h" +#include "net/base/ip_endpoint.h" + +namespace chromeos { +namespace network_diagnostics { + +namespace {} // namespace + +FakeUdpSocket::FakeUdpSocket() = default; + +FakeUdpSocket::~FakeUdpSocket() = default; + +void FakeUdpSocket::Connect(const net::IPEndPoint& remote_addr, + network::mojom::UDPSocketOptionsPtr options, + ConnectCallback callback) { + if (mojo_disconnect_on_connect_) { + receiver_.reset(); + return; + } + std::move(callback).Run(udp_connect_code_, net::IPEndPoint()); +} + +void FakeUdpSocket::Bind(const net::IPEndPoint& local_addr, + network::mojom::UDPSocketOptionsPtr options, + BindCallback callback) { + NOTREACHED(); +} + +void FakeUdpSocket::SetBroadcast(bool broadcast, + SetBroadcastCallback callback) { + NOTREACHED(); +} + +void FakeUdpSocket::SetSendBufferSize(int32_t send_buffer_size, + SetSendBufferSizeCallback callback) { + NOTREACHED(); +} + +void FakeUdpSocket::SetReceiveBufferSize(int32_t receive_buffer_size, + SetSendBufferSizeCallback callback) { + NOTREACHED(); +} + +void FakeUdpSocket::JoinGroup(const net::IPAddress& group_address, + JoinGroupCallback callback) { + NOTREACHED(); +} + +void FakeUdpSocket::LeaveGroup(const net::IPAddress& group_address, + LeaveGroupCallback callback) { + NOTREACHED(); +} + +void FakeUdpSocket::ReceiveMore(uint32_t num_additional_datagrams) { + DCHECK(remote_.is_bound()); + + if (mojo_disconnect_on_receive_) { + remote_.reset(); + return; + } + + remote_->OnReceived(udp_on_received_code_, net::IPEndPoint(), + std::move(udp_on_received_data_)); +} + +void FakeUdpSocket::ReceiveMoreWithBufferSize(uint32_t num_additional_datagrams, + uint32_t buffer_size) { + NOTREACHED(); +} + +void FakeUdpSocket::SendTo( + const net::IPEndPoint& dest_addr, + base::span<const uint8_t> data, + const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, + SendToCallback callback) { + NOTREACHED(); +} + +void FakeUdpSocket::Send( + base::span<const uint8_t> data, + const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, + SendCallback callback) { + if (mojo_disconnect_on_send_) { + receiver_.reset(); + return; + } + std::move(callback).Run(udp_send_code_); +} + +void FakeUdpSocket::Close() { + NOTREACHED(); +} + +void FakeUdpSocket::BindReceiver( + mojo::PendingReceiver<network::mojom::UDPSocket> socket) { + DCHECK(!receiver_.is_bound()); + + receiver_.Bind(std::move(socket)); +} + +void FakeUdpSocket::BindRemote( + mojo::PendingRemote<network::mojom::UDPSocketListener> socket_listener) { + DCHECK(!remote_.is_bound()); + + remote_.Bind(std::move(socket_listener)); +} + +} // namespace network_diagnostics +} // namespace chromeos
diff --git a/chrome/browser/chromeos/net/network_diagnostics/fake_udp_socket.h b/chrome/browser/chromeos/net/network_diagnostics/fake_udp_socket.h new file mode 100644 index 0000000..41be34f --- /dev/null +++ b/chrome/browser/chromeos/net/network_diagnostics/fake_udp_socket.h
@@ -0,0 +1,110 @@ +// Copyright 2020 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_CHROMEOS_NET_NETWORK_DIAGNOSTICS_FAKE_UDP_SOCKET_H_ +#define CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_FAKE_UDP_SOCKET_H_ + +#include "mojo/public/cpp/bindings/pending_receiver.h" +#include "mojo/public/cpp/bindings/pending_remote.h" +#include "mojo/public/cpp/bindings/receiver.h" +#include "mojo/public/cpp/bindings/remote.h" +#include "net/base/net_errors.h" +#include "services/network/public/mojom/udp_socket.mojom.h" + +namespace chromeos { +namespace network_diagnostics { + +// Provides some UDP socket functionality in tests. Most methods, unless +// otherwise noted, are not expected to be called in tests or in a non Chrome OS +// environment. +class FakeUdpSocket : public network::mojom::UDPSocket { + public: + FakeUdpSocket(); + FakeUdpSocket(const FakeUdpSocket&) = delete; + FakeUdpSocket& operator=(const FakeUdpSocket&) = delete; + ~FakeUdpSocket() override; + + // network::mojom::UDPSocket: + // Used in the fake. + void Connect(const net::IPEndPoint& remote_addr, + network::mojom::UDPSocketOptionsPtr options, + ConnectCallback callback) override; + void Bind(const net::IPEndPoint& local_addr, + network::mojom::UDPSocketOptionsPtr options, + BindCallback callback) override; + void SetBroadcast(bool broadcast, SetBroadcastCallback callback) override; + void SetSendBufferSize(int32_t send_buffer_size, + SetSendBufferSizeCallback callback) override; + void SetReceiveBufferSize(int32_t receive_buffer_size, + SetSendBufferSizeCallback callback) override; + void JoinGroup(const net::IPAddress& group_address, + JoinGroupCallback callback) override; + void LeaveGroup(const net::IPAddress& group_address, + LeaveGroupCallback callback) override; + // Used in the fake. + void ReceiveMore(uint32_t num_additional_datagrams) override; + void ReceiveMoreWithBufferSize(uint32_t num_additional_datagrams, + uint32_t buffer_size) override; + void SendTo(const net::IPEndPoint& dest_addr, + base::span<const uint8_t> data, + const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, + SendToCallback callback) override; + // Used in the fake. + void Send(base::span<const uint8_t> data, + const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, + SendCallback callback) override; + void Close() override; + + // Binds the pending receiver to |this|. + void BindReceiver(mojo::PendingReceiver<network::mojom::UDPSocket> socket); + + // Binds the pending remote to a remote held by |this|. + void BindRemote( + mojo::PendingRemote<network::mojom::UDPSocketListener> socket_listener); + + void set_udp_connect_code(net::Error udp_connect_code) { + udp_connect_code_ = udp_connect_code; + } + + void set_udp_send_code(net::Error udp_send_code) { + udp_send_code_ = udp_send_code; + } + + void set_udp_on_received_code(net::Error udp_on_received_code) { + udp_on_received_code_ = udp_on_received_code; + } + + void set_udp_on_received_data( + base::span<const uint8_t> udp_on_received_data) { + udp_on_received_data_ = std::move(udp_on_received_data); + } + + void set_disconnect_during_udp_connection_attempt(bool disconnect) { + mojo_disconnect_on_connect_ = disconnect; + } + + void set_disconnect_during_udp_send_attempt(bool disconnect) { + mojo_disconnect_on_send_ = disconnect; + } + + void set_disconnect_during_udp_receive_attempt(bool disconnect) { + mojo_disconnect_on_receive_ = disconnect; + } + + private: + mojo::Receiver<network::mojom::UDPSocket> receiver_{this}; + mojo::Remote<network::mojom::UDPSocketListener> remote_; + net::Error udp_connect_code_ = net::ERR_FAILED; + net::Error udp_send_code_ = net::ERR_FAILED; + net::Error udp_on_received_code_ = net::ERR_FAILED; + base::span<const uint8_t> udp_on_received_data_ = {}; + bool mojo_disconnect_on_connect_ = false; + bool mojo_disconnect_on_send_ = false; + bool mojo_disconnect_on_receive_ = false; +}; + +} // namespace network_diagnostics +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_FAKE_UDP_SOCKET_H_
diff --git a/chrome/browser/chromeos/net/network_diagnostics/host_resolver.cc b/chrome/browser/chromeos/net/network_diagnostics/host_resolver.cc new file mode 100644 index 0000000..e8bce56 --- /dev/null +++ b/chrome/browser/chromeos/net/network_diagnostics/host_resolver.cc
@@ -0,0 +1,80 @@ +// Copyright 2020 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/chromeos/net/network_diagnostics/host_resolver.h" + +#include <utility> + +#include "base/bind.h" +#include "base/optional.h" +#include "net/base/net_errors.h" +#include "net/base/network_isolation_key.h" +#include "net/dns/public/dns_config_overrides.h" + +namespace chromeos { +namespace network_diagnostics { + +HostResolver::ResolutionResult::ResolutionResult( + int result, + const net::ResolveErrorInfo& resolve_error_info, + const base::Optional<net::AddressList>& resolved_addresses) + : result(result), + resolve_error_info(resolve_error_info), + resolved_addresses(resolved_addresses) {} + +HostResolver::ResolutionResult::~ResolutionResult() = default; + +HostResolver::HostResolver(const GURL& url, + network::mojom::NetworkContext* network_context, + OnResolutionComplete callback) + : HostResolver(net::HostPortPair::FromURL(url), + network_context, + std::move(callback)) {} + +HostResolver::HostResolver(const net::HostPortPair& host_port_pair, + network::mojom::NetworkContext* network_context, + OnResolutionComplete callback) + : callback_(std::move(callback)) { + DCHECK(network_context); + DCHECK(callback_); + + network_context->CreateHostResolver( + net::DnsConfigOverrides(), host_resolver_.BindNewPipeAndPassReceiver()); + // Disconnect handler will be invoked if the network service crashes. + host_resolver_.set_disconnect_handler(base::BindOnce( + &HostResolver::OnMojoConnectionError, base::Unretained(this))); + + network::mojom::ResolveHostParametersPtr parameters = + network::mojom::ResolveHostParameters::New(); + parameters->dns_query_type = net::DnsQueryType::A; + parameters->source = net::HostResolverSource::DNS; + parameters->cache_usage = + network::mojom::ResolveHostParameters::CacheUsage::DISALLOWED; + + host_resolver_->ResolveHost( + host_port_pair, net::NetworkIsolationKey::CreateTransient(), + std::move(parameters), receiver_.BindNewPipeAndPassRemote()); +} + +HostResolver::~HostResolver() = default; + +void HostResolver::OnComplete( + int result, + const net::ResolveErrorInfo& resolve_error_info, + const base::Optional<net::AddressList>& resolved_addresses) { + receiver_.reset(); + host_resolver_.reset(); + + ResolutionResult resolution_result{result, resolve_error_info, + resolved_addresses}; + std::move(callback_).Run(resolution_result); +} + +void HostResolver::OnMojoConnectionError() { + OnComplete(net::ERR_NAME_NOT_RESOLVED, net::ResolveErrorInfo(net::ERR_FAILED), + base::nullopt); +} + +} // namespace network_diagnostics +} // namespace chromeos
diff --git a/chrome/browser/chromeos/net/network_diagnostics/host_resolver.h b/chrome/browser/chromeos/net/network_diagnostics/host_resolver.h new file mode 100644 index 0000000..e10078bf --- /dev/null +++ b/chrome/browser/chromeos/net/network_diagnostics/host_resolver.h
@@ -0,0 +1,73 @@ +// Copyright 2020 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_CHROMEOS_NET_NETWORK_DIAGNOSTICS_HOST_RESOLVER_H_ +#define CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_HOST_RESOLVER_H_ + +#include "base/callback.h" +#include "mojo/public/cpp/bindings/receiver.h" +#include "mojo/public/cpp/bindings/remote.h" +#include "net/base/host_port_pair.h" +#include "net/base/net_errors.h" +#include "services/network/public/cpp/resolve_host_client_base.h" +#include "services/network/public/mojom/network_context.mojom.h" +#include "url/gurl.h" + +namespace chromeos { +namespace network_diagnostics { + +// Performs a DNS host resolution. This is a single-use class. +class HostResolver : public network::ResolveHostClientBase { + public: + struct ResolutionResult { + ResolutionResult( + int result, + const net::ResolveErrorInfo& resolve_error_info, + const base::Optional<net::AddressList>& resolved_addresses); + ~ResolutionResult(); + + int result; + net::ResolveErrorInfo resolve_error_info; + base::Optional<net::AddressList> resolved_addresses; + }; + using OnResolutionComplete = base::OnceCallback<void(ResolutionResult&)>; + + // Performs the DNS resolution of a specified |url|. Note that |callback| + // will not be called until construction is complete. + HostResolver(const GURL& url, + network::mojom::NetworkContext* network_context, + OnResolutionComplete callback); + + // Performs the DNS resolution of a specified |host_port_pair|. Note that + // |callback| will not be called until construction is complete. + HostResolver(const net::HostPortPair& host_port_pair, + network::mojom::NetworkContext* network_context, + OnResolutionComplete callback); + + HostResolver(const HostResolver&) = delete; + HostResolver& operator=(const HostResolver&) = delete; + ~HostResolver() override; + + // network::mojom::ResolveHostClient: + void OnComplete( + int result, + const net::ResolveErrorInfo& resolve_error_info, + const base::Optional<net::AddressList>& resolved_addresses) override; + + private: + // Handles Mojo connection errors during host resolution. + void OnMojoConnectionError(); + + // Callback invoked once resolution is complete. + OnResolutionComplete callback_; + // Receiver endpoint to handle host resolution results. + mojo::Receiver<network::mojom::ResolveHostClient> receiver_{this}; + // Remote endpoint that calls the network service. + mojo::Remote<network::mojom::HostResolver> host_resolver_; +}; + +} // namespace network_diagnostics +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_HOST_RESOLVER_H_
diff --git a/chrome/browser/chromeos/net/network_diagnostics/host_resolver_unittest.cc b/chrome/browser/chromeos/net/network_diagnostics/host_resolver_unittest.cc new file mode 100644 index 0000000..3b874c8 --- /dev/null +++ b/chrome/browser/chromeos/net/network_diagnostics/host_resolver_unittest.cc
@@ -0,0 +1,174 @@ +// Copyright 2020 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/chromeos/net/network_diagnostics/host_resolver.h" + +#include <memory> + +#include "base/bind.h" +#include "base/run_loop.h" +#include "chrome/browser/chromeos/net/network_diagnostics/fake_host_resolver.h" +#include "chrome/browser/chromeos/net/network_diagnostics/fake_network_context.h" +#include "content/public/test/browser_task_environment.h" +#include "net/base/address_list.h" +#include "net/base/host_port_pair.h" +#include "net/base/ip_address.h" +#include "net/base/ip_endpoint.h" +#include "net/dns/public/resolve_error_info.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace chromeos { +namespace network_diagnostics { + +class HostResolverTest : public ::testing::Test { + public: + HostResolverTest() = default; + HostResolverTest(const HostResolverTest&) = delete; + HostResolverTest& operator=(const HostResolverTest&) = delete; + + void InitializeNetworkContext( + std::unique_ptr<FakeHostResolver::DnsResult> fake_dns_result) { + fake_network_context_.set_fake_dns_result(std::move(fake_dns_result)); + } + + FakeNetworkContext* fake_network_context() { return &fake_network_context_; } + + protected: + const net::HostPortPair kFakeHostPortPair = + net::HostPortPair::FromString("fake_stun_server.com:80"); + const GURL kFakeUrl{"https://www.FAKE_HOST_NAME.com:1234/"}; + const net::IPEndPoint kFakeIPAddress{ + net::IPEndPoint(net::IPAddress::IPv4Localhost(), /*port=*/1234)}; + std::unique_ptr<HostResolver> host_resolver_; + + private: + content::BrowserTaskEnvironment task_environment_; + FakeNetworkContext fake_network_context_; +}; + +TEST_F(HostResolverTest, TestSuccessfulResolutionWithUrl) { + auto address_list = net::AddressList(kFakeIPAddress); + auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>( + net::OK, net::ResolveErrorInfo(net::OK), address_list); + InitializeNetworkContext(std::move(fake_dns_result)); + HostResolver::ResolutionResult resolution_result{ + net::ERR_FAILED, net::ResolveErrorInfo(net::OK), base::nullopt}; + base::RunLoop run_loop; + host_resolver_ = std::make_unique<HostResolver>( + kFakeUrl, fake_network_context(), + base::BindOnce( + [](HostResolver::ResolutionResult* resolution_result, + base::OnceClosure quit_closure, + HostResolver::ResolutionResult& res_result) { + resolution_result->result = res_result.result; + resolution_result->resolve_error_info = + res_result.resolve_error_info; + resolution_result->resolved_addresses = + res_result.resolved_addresses; + std::move(quit_closure).Run(); + }, + &resolution_result, run_loop.QuitClosure())); + run_loop.Run(); + + EXPECT_EQ(resolution_result.result, net::OK); + EXPECT_EQ(resolution_result.resolve_error_info, + net::ResolveErrorInfo(net::OK)); + EXPECT_EQ(resolution_result.resolved_addresses.value().size(), 1); + EXPECT_EQ(resolution_result.resolved_addresses.value().front(), + address_list.front()); +} + +TEST_F(HostResolverTest, TestSuccessfulResolutionWithHostPortPair) { + auto address_list = net::AddressList(kFakeIPAddress); + auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>( + net::OK, net::ResolveErrorInfo(net::OK), address_list); + InitializeNetworkContext(std::move(fake_dns_result)); + HostResolver::ResolutionResult resolution_result{ + net::ERR_FAILED, net::ResolveErrorInfo(net::OK), base::nullopt}; + base::RunLoop run_loop; + host_resolver_ = std::make_unique<HostResolver>( + kFakeHostPortPair, fake_network_context(), + base::BindOnce( + [](HostResolver::ResolutionResult* resolution_result, + base::OnceClosure quit_closure, + HostResolver::ResolutionResult& res_result) { + resolution_result->result = res_result.result; + resolution_result->resolve_error_info = + res_result.resolve_error_info; + resolution_result->resolved_addresses = + res_result.resolved_addresses; + std::move(quit_closure).Run(); + }, + &resolution_result, run_loop.QuitClosure())); + run_loop.Run(); + + EXPECT_EQ(resolution_result.result, net::OK); + EXPECT_EQ(resolution_result.resolve_error_info, + net::ResolveErrorInfo(net::OK)); + EXPECT_EQ(resolution_result.resolved_addresses.value().size(), 1); + EXPECT_EQ(resolution_result.resolved_addresses.value().front(), + address_list.front()); +} + +TEST_F(HostResolverTest, TestFailedHostResolution) { + auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>( + net::ERR_NAME_NOT_RESOLVED, + net::ResolveErrorInfo(net::ERR_NAME_NOT_RESOLVED), base::nullopt); + InitializeNetworkContext(std::move(fake_dns_result)); + HostResolver::ResolutionResult resolution_result{ + net::ERR_FAILED, net::ResolveErrorInfo(net::OK), base::nullopt}; + base::RunLoop run_loop; + host_resolver_ = std::make_unique<HostResolver>( + kFakeHostPortPair, fake_network_context(), + base::BindOnce( + [](HostResolver::ResolutionResult* resolution_result, + base::OnceClosure quit_closure, + HostResolver::ResolutionResult& res_result) { + resolution_result->result = res_result.result; + resolution_result->resolve_error_info = + res_result.resolve_error_info; + resolution_result->resolved_addresses = + res_result.resolved_addresses; + std::move(quit_closure).Run(); + }, + &resolution_result, run_loop.QuitClosure())); + run_loop.Run(); + + EXPECT_EQ(resolution_result.result, net::ERR_NAME_NOT_RESOLVED); + EXPECT_EQ(resolution_result.resolve_error_info, + net::ResolveErrorInfo(net::ERR_NAME_NOT_RESOLVED)); + ASSERT_FALSE(resolution_result.resolved_addresses.has_value()); +} + +TEST_F(HostResolverTest, TestMojoDisconnectDuringHostResolution) { + InitializeNetworkContext(/*fake_dns_result=*/{}); + fake_network_context()->set_disconnect_during_host_resolution(true); + HostResolver::ResolutionResult resolution_result{ + net::ERR_FAILED, net::ResolveErrorInfo(net::OK), base::nullopt}; + base::RunLoop run_loop; + host_resolver_ = std::make_unique<HostResolver>( + kFakeHostPortPair, fake_network_context(), + base::BindOnce( + [](HostResolver::ResolutionResult* resolution_result, + base::OnceClosure quit_closure, + HostResolver::ResolutionResult& res_result) { + resolution_result->result = res_result.result; + resolution_result->resolve_error_info = + res_result.resolve_error_info; + resolution_result->resolved_addresses = + res_result.resolved_addresses; + std::move(quit_closure).Run(); + }, + &resolution_result, run_loop.QuitClosure())); + run_loop.Run(); + + EXPECT_EQ(resolution_result.result, net::ERR_NAME_NOT_RESOLVED); + EXPECT_EQ(resolution_result.resolve_error_info, + net::ResolveErrorInfo(net::ERR_FAILED)); + ASSERT_FALSE(resolution_result.resolved_addresses.has_value()); +} + +} // namespace network_diagnostics +} // namespace chromeos
diff --git a/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_util.cc b/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_util.cc index e07d6ee..c138b64 100644 --- a/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_util.cc +++ b/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_util.cc
@@ -149,6 +149,39 @@ return profile; } +const std::array<uint8_t, kStunHeaderSize>& GetStunHeader() { + static std::array<uint8_t, kStunHeaderSize> stun_header = { + 0x00, 0x01, 0x00, 0x00, 0x21, 0x12, 0xa4, 0x42, 0x79, 0x64, + 0x66, 0x36, 0x66, 0x53, 0x42, 0x73, 0x76, 0x77, 0x76, 0x75}; + + return stun_header; +} + +net::NetworkTrafficAnnotationTag GetStunNetworkAnnotationTag() { + return net::DefineNetworkTrafficAnnotation("network_diagnostics_routines", + R"( + semantics { + sender: "NetworkDiagnosticsRoutines" + description: + "Routines send network traffic to hosts in order to " + "validate the internet connection on a device." + trigger: + "A routine attempts a socket connection or makes an http/s " + "request." + data: + "For UDP connections, data is sent along with the origin " + "(scheme-host-port). The primary purpose of the UDP prober is to " + "send a STUN packet header to a STUN server. For TCP connections, " + "only the origin is sent. No user identifier is sent along with the " + "data." + destination: GOOGLE_OWNED_SERVICE + } + policy { + cookies_allowed: NO + } + )"); +} + } // namespace util } // namespace network_diagnostics
diff --git a/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_util.h b/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_util.h index 3d824ed..850c67a 100644 --- a/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_util.h +++ b/chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_util.h
@@ -5,9 +5,12 @@ #ifndef CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_NETWORK_DIAGNOSTICS_UTIL_H_ #define CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_NETWORK_DIAGNOSTICS_UTIL_H_ +#include <array> +#include <cstdint> #include <string> #include <vector> +#include "net/traffic_annotation/network_traffic_annotation.h" #include "url/gurl.h" class Profile; @@ -20,6 +23,9 @@ // Generate 204 path. extern const char kGenerate204Path[]; +// STUN packet header size. +constexpr int kStunHeaderSize = 20; + // Returns the Gstatic host suffix. Network diagnostic routines attach a random // prefix to |kGstaticHostSuffix| to get a complete hostname. const char* GetGstaticHostSuffix(); @@ -78,6 +84,12 @@ // Returns the profile associated with this account. Profile* GetUserProfile(); +// Returns a STUN packet with a header defined in RFC 5389. +const std::array<uint8_t, kStunHeaderSize>& GetStunHeader(); + +// Returns the traffic annotation tag for STUN traffic. +net::NetworkTrafficAnnotationTag GetStunNetworkAnnotationTag(); + } // namespace util } // namespace network_diagnostics
diff --git a/chrome/browser/chromeos/net/network_diagnostics/tls_prober.cc b/chrome/browser/chromeos/net/network_diagnostics/tls_prober.cc index 664aac8b..a8f9938f 100644 --- a/chrome/browser/chromeos/net/network_diagnostics/tls_prober.cc +++ b/chrome/browser/chromeos/net/network_diagnostics/tls_prober.cc
@@ -54,89 +54,6 @@ } // namespace -class TlsProber::HostResolver : public network::ResolveHostClientBase { - public: - HostResolver(network::mojom::NetworkContext* network_context, - TlsProber* tls_prober); - HostResolver(const HostResolver&) = delete; - HostResolver& operator=(const HostResolver&) = delete; - ~HostResolver() override; - - // network::mojom::ResolveHostClient: - void OnComplete( - int result, - const net::ResolveErrorInfo& resolve_error_info, - const base::Optional<net::AddressList>& resolved_addresses) override; - - // Performs the DNS resolution. - void Run(const GURL& url); - - network::mojom::NetworkContext* network_context() const { - return network_context_; - } - - private: - void CreateHostResolver(); - void OnMojoConnectionError(); - - network::mojom::NetworkContext* network_context_ = nullptr; // Unowned - TlsProber* tls_prober_; // Unowned - mojo::Receiver<network::mojom::ResolveHostClient> receiver_{this}; - mojo::Remote<network::mojom::HostResolver> host_resolver_; -}; - -TlsProber::HostResolver::HostResolver( - network::mojom::NetworkContext* network_context, - TlsProber* tls_prober) - : network_context_(network_context), tls_prober_(tls_prober) { - DCHECK(network_context_); - DCHECK(tls_prober_); -} - -TlsProber::HostResolver::~HostResolver() = default; - -void TlsProber::HostResolver::OnComplete( - int result, - const net::ResolveErrorInfo& resolve_error_info, - const base::Optional<net::AddressList>& resolved_addresses) { - receiver_.reset(); - host_resolver_.reset(); - - tls_prober_->OnHostResolutionComplete(result, resolve_error_info, - resolved_addresses); -} - -void TlsProber::HostResolver::Run(const GURL& url) { - CreateHostResolver(); - DCHECK(host_resolver_); - DCHECK(!receiver_.is_bound()); - - network::mojom::ResolveHostParametersPtr parameters = - network::mojom::ResolveHostParameters::New(); - parameters->dns_query_type = net::DnsQueryType::A; - parameters->source = net::HostResolverSource::DNS; - parameters->cache_usage = - network::mojom::ResolveHostParameters::CacheUsage::DISALLOWED; - - host_resolver_->ResolveHost(net::HostPortPair::FromURL(url), - net::NetworkIsolationKey::CreateTransient(), - std::move(parameters), - receiver_.BindNewPipeAndPassRemote()); -} - -void TlsProber::HostResolver::CreateHostResolver() { - network_context()->CreateHostResolver( - net::DnsConfigOverrides(), host_resolver_.BindNewPipeAndPassReceiver()); - // Disconnect handler will be invoked if the network service crashes. - host_resolver_.set_disconnect_handler(base::BindOnce( - &HostResolver::OnMojoConnectionError, base::Unretained(this))); -} - -void TlsProber::HostResolver::OnMojoConnectionError() { - OnComplete(net::ERR_NAME_NOT_RESOLVED, net::ResolveErrorInfo(net::ERR_FAILED), - base::nullopt); -} - TlsProber::TlsProber(NetworkContextGetter network_context_getter, const GURL& url, TlsProbeCompleteCallback callback) @@ -151,9 +68,10 @@ network_context_getter_.Run(); DCHECK(network_context); - host_resolver_ = std::make_unique<HostResolver>(network_context, this); - DCHECK(host_resolver_); - host_resolver_->Run(url); + host_resolver_ = std::make_unique<HostResolver>( + net::HostPortPair::FromURL(url_), network_context, + base::BindOnce(&TlsProber::OnHostResolutionComplete, + weak_factory_.GetWeakPtr())); } TlsProber::TlsProber() = default; @@ -161,15 +79,15 @@ TlsProber::~TlsProber() = default; void TlsProber::OnHostResolutionComplete( - int result, - const net::ResolveErrorInfo& resolve_error_info, - const base::Optional<net::AddressList>& resolved_addresses) { + HostResolver::ResolutionResult& resolution_result) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - bool success = result == net::OK && !resolved_addresses->empty() && - resolved_addresses.has_value(); + host_resolver_.reset(); + bool success = resolution_result.result == net::OK && + !resolution_result.resolved_addresses->empty() && + resolution_result.resolved_addresses.has_value(); if (!success) { - OnDone(result, ProbeExitEnum::kDnsFailure); + OnDone(resolution_result.result, ProbeExitEnum::kDnsFailure); return; } @@ -187,7 +105,8 @@ DCHECK(network_context); network_context->CreateTCPConnectedSocket( - /*local_addr=*/base::nullopt, resolved_addresses.value(), + /*local_addr=*/base::nullopt, + resolution_result.resolved_addresses.value(), /*options=*/nullptr, net::MutableNetworkTrafficAnnotationTag(GetTrafficAnnotationTag()), std::move(pending_receiver), /*observer=*/mojo::NullRemote(),
diff --git a/chrome/browser/chromeos/net/network_diagnostics/tls_prober.h b/chrome/browser/chromeos/net/network_diagnostics/tls_prober.h index 9f433ec..f02f1e8d 100644 --- a/chrome/browser/chromeos/net/network_diagnostics/tls_prober.h +++ b/chrome/browser/chromeos/net/network_diagnostics/tls_prober.h
@@ -11,6 +11,7 @@ #include "base/memory/scoped_refptr.h" #include "base/memory/weak_ptr.h" #include "base/sequenced_task_runner.h" +#include "chrome/browser/chromeos/net/network_diagnostics/host_resolver.h" #include "mojo/public/cpp/bindings/remote.h" #include "net/base/completion_once_callback.h" #include "services/network/public/mojom/network_context.mojom.h" @@ -18,10 +19,6 @@ #include "services/network/public/mojom/tls_socket.mojom.h" #include "url/gurl.h" -namespace net { -class AddressList; -} - namespace chromeos { namespace network_diagnostics { @@ -39,7 +36,6 @@ kMojoDisconnectFailure, kSuccess, }; - class HostResolver; using NetworkContextGetter = base::RepeatingCallback<network::mojom::NetworkContext*()>; using OnConnectCompleteOnUIThreadCallback = base::OnceCallback< @@ -62,9 +58,7 @@ // Processes the results of the DNS resolution done by |host_resolver_|. void OnHostResolutionComplete( - int result, - const net::ResolveErrorInfo& resolve_error_info, - const base::Optional<net::AddressList>& resolved_addresses); + HostResolver::ResolutionResult& resolution_result); protected: // Test-only constructor.
diff --git a/chrome/browser/chromeos/net/network_diagnostics/tls_prober_unittest.cc b/chrome/browser/chromeos/net/network_diagnostics/tls_prober_unittest.cc index 349c45d..16d1905 100644 --- a/chrome/browser/chromeos/net/network_diagnostics/tls_prober_unittest.cc +++ b/chrome/browser/chromeos/net/network_diagnostics/tls_prober_unittest.cc
@@ -4,7 +4,6 @@ #include "chrome/browser/chromeos/net/network_diagnostics/tls_prober.h" -#include <deque> #include <memory> #include <utility> #include <vector> @@ -48,11 +47,11 @@ const TlsProberWithFakeNetworkContextTest&) = delete; void InitializeProberNetworkContext( - std::deque<FakeHostResolver::DnsResult*> fake_dns_result, + std::unique_ptr<FakeHostResolver::DnsResult> fake_dns_result, base::Optional<net::Error> tcp_connect_code, base::Optional<net::Error> tls_upgrade_code) { fake_network_context_ = std::make_unique<FakeNetworkContext>(); - fake_network_context_->set_fake_dns_results(std::move(fake_dns_result)); + fake_network_context_->set_fake_dns_result(std::move(fake_dns_result)); fake_network_context_->SetTCPConnectCode(tcp_connect_code); fake_network_context_->SetTLSUpgradeCode(tls_upgrade_code); } @@ -65,8 +64,7 @@ [](network::mojom::NetworkContext* network_context) { return network_context; }, - static_cast<network::mojom::NetworkContext*>( - fake_network_context_.get())), + fake_network_context_.get()), url, std::move(callback)); } @@ -87,10 +85,9 @@ TEST_F(TlsProberWithFakeNetworkContextTest, SocketConnectedAndUpgradedSuccessfully) { - auto dns_result = std::make_unique<FakeHostResolver::DnsResult>( + auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>( net::OK, net::ResolveErrorInfo(net::OK), net::AddressList(kFakeIPAddress)); - std::deque<FakeHostResolver::DnsResult*> fake_dns_result = {dns_result.get()}; net::Error tcp_connect_code = net::OK; net::Error tls_upgrade_code = net::OK; InitializeProberNetworkContext(std::move(fake_dns_result), tcp_connect_code, @@ -114,10 +111,9 @@ } TEST_F(TlsProberWithFakeNetworkContextTest, FailedDnsLookup) { - auto dns_result = std::make_unique<FakeHostResolver::DnsResult>( + auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>( net::ERR_NAME_NOT_RESOLVED, net::ResolveErrorInfo(net::ERR_NAME_NOT_RESOLVED), net::AddressList()); - std::deque<FakeHostResolver::DnsResult*> fake_dns_result = {dns_result.get()}; // Neither TCP connect nor TLS upgrade should not be called in this scenario. InitializeProberNetworkContext(std::move(fake_dns_result), /*tcp_connect_code=*/base::nullopt, @@ -141,10 +137,9 @@ } TEST_F(TlsProberWithFakeNetworkContextTest, MojoDisconnectDuringDnsLookup) { - // Host resolution will not be successful due to Mojo disconnect. - std::deque<FakeHostResolver::DnsResult*> fake_dns_result = {}; - // Neither TCP connect nor TLS upgrade should not be called in this scenario. - InitializeProberNetworkContext(std::move(fake_dns_result), + // Host resolution will not be successful due to Mojo disconnect. Neither TCP + // connect nor TLS upgrade should not be called in this scenario. + InitializeProberNetworkContext(/*fake_dns_result=*/{}, /*tcp_connect_code=*/base::nullopt, /*tls_upgrade_code=*/base::nullopt); fake_network_context()->set_disconnect_during_host_resolution(true); @@ -167,10 +162,9 @@ } TEST_F(TlsProberWithFakeNetworkContextTest, FailedTcpConnection) { - auto dns_result = std::make_unique<FakeHostResolver::DnsResult>( + auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>( net::OK, net::ResolveErrorInfo(net::OK), net::AddressList(kFakeIPAddress)); - std::deque<FakeHostResolver::DnsResult*> fake_dns_result = {dns_result.get()}; net::Error tcp_connect_code = net::ERR_CONNECTION_FAILED; // TLS upgrade should not be called in this scenario. InitializeProberNetworkContext(std::move(fake_dns_result), tcp_connect_code, @@ -194,10 +188,9 @@ } TEST_F(TlsProberWithFakeNetworkContextTest, FailedTlsUpgrade) { - auto dns_result = std::make_unique<FakeHostResolver::DnsResult>( + auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>( net::OK, net::ResolveErrorInfo(net::OK), net::AddressList(kFakeIPAddress)); - std::deque<FakeHostResolver::DnsResult*> fake_dns_result = {dns_result.get()}; net::Error tcp_connect_code = net::OK; net::Error tls_upgrade_code = net::ERR_SSL_PROTOCOL_ERROR; InitializeProberNetworkContext(std::move(fake_dns_result), tcp_connect_code, @@ -222,10 +215,9 @@ TEST_F(TlsProberWithFakeNetworkContextTest, MojoDisconnectedDuringTcpConnectionAttempt) { - auto dns_result = std::make_unique<FakeHostResolver::DnsResult>( + auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>( net::OK, net::ResolveErrorInfo(net::OK), net::AddressList(kFakeIPAddress)); - std::deque<FakeHostResolver::DnsResult*> fake_dns_result = {dns_result.get()}; // Since the TCP connection is disconnected, no connection codes are needed. InitializeProberNetworkContext(std::move(fake_dns_result), /*tcp_connect_code=*/base::nullopt, @@ -251,10 +243,9 @@ TEST_F(TlsProberWithFakeNetworkContextTest, MojoDisconnectedDuringTlsUpgradeAttempt) { - auto dns_result = std::make_unique<FakeHostResolver::DnsResult>( + auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>( net::OK, net::ResolveErrorInfo(net::OK), net::AddressList(kFakeIPAddress)); - std::deque<FakeHostResolver::DnsResult*> fake_dns_result = {dns_result.get()}; net::Error tcp_connect_code = net::OK; // TLS upgrade attempt will fail due to disconnection. |tls_upgrade_code| // is only populated to correctly initialize the FakeNetworkContext instance.
diff --git a/chrome/browser/chromeos/net/network_diagnostics/udp_prober.cc b/chrome/browser/chromeos/net/network_diagnostics/udp_prober.cc new file mode 100644 index 0000000..09b82fc --- /dev/null +++ b/chrome/browser/chromeos/net/network_diagnostics/udp_prober.cc
@@ -0,0 +1,150 @@ +// Copyright 2020 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/chromeos/net/network_diagnostics/udp_prober.h" + +#include <utility> + +#include "base/logging.h" +#include "chrome/browser/profiles/profile_manager.h" +#include "content/public/browser/browser_task_traits.h" +#include "content/public/browser/browser_thread.h" +#include "mojo/public/cpp/bindings/pending_receiver.h" +#include "mojo/public/cpp/bindings/pending_remote.h" +#include "net/base/address_list.h" +#include "net/base/host_port_pair.h" +#include "net/base/net_errors.h" +#include "net/dns/public/resolve_error_info.h" +#include "net/traffic_annotation/network_traffic_annotation.h" + +namespace chromeos { +namespace network_diagnostics { + +UdpProber::UdpProber(NetworkContextGetter network_context_getter, + net::HostPortPair host_port_pair, + base::span<const uint8_t> data, + net::NetworkTrafficAnnotationTag tag, + UdpProbeCompleteCallback callback) + : network_context_getter_(std::move(network_context_getter)), + host_port_pair_(host_port_pair), + data_(std::move(data)), + tag_(tag), + callback_(std::move(callback)) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + DCHECK(!data_.empty()); + DCHECK(callback_); + DCHECK(!host_port_pair_.IsEmpty()); + + network::mojom::NetworkContext* network_context = + network_context_getter_.Run(); + DCHECK(network_context); + + host_resolver_ = std::make_unique<HostResolver>( + host_port_pair_, network_context, + base::BindOnce(&UdpProber::OnHostResolutionComplete, + weak_factory_.GetWeakPtr())); +} + +UdpProber::~UdpProber() = default; + +void UdpProber::OnHostResolutionComplete( + HostResolver::ResolutionResult& resolution_result) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + bool success = resolution_result.result == net::OK && + !resolution_result.resolved_addresses->empty() && + resolution_result.resolved_addresses.has_value(); + if (!success) { + OnDone(resolution_result.result, ProbeExitEnum::kDnsFailure); + return; + } + + network::mojom::NetworkContext* network_context = + network_context_getter_.Run(); + DCHECK(network_context); + + auto pending_receiver = udp_socket_remote_.BindNewPipeAndPassReceiver(); + udp_socket_remote_.set_disconnect_handler( + base::BindOnce(&UdpProber::OnDisconnect, weak_factory_.GetWeakPtr())); + + auto pending_remote = + udp_socket_listener_receiver_.BindNewPipeAndPassRemote(); + udp_socket_listener_receiver_.set_disconnect_handler( + base::BindOnce(&UdpProber::OnDisconnect, weak_factory_.GetWeakPtr())); + + network_context->CreateUDPSocket(std::move(pending_receiver), + std::move(pending_remote)); + udp_socket_remote_->Connect( + resolution_result.resolved_addresses.value().front(), nullptr, + base::BindOnce(&UdpProber::OnConnectComplete, + weak_factory_.GetWeakPtr())); +} + +void UdpProber::OnConnectComplete( + int result, + const base::Optional<net::IPEndPoint>& local_addr_out) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + if (result != net::OK) { + OnDone(result, ProbeExitEnum::kConnectFailure); + return; + } + udp_socket_remote_->Send( + std::move(data_), net::MutableNetworkTrafficAnnotationTag(tag_), + base::BindOnce(&UdpProber::OnSendComplete, weak_factory_.GetWeakPtr())); +} + +void UdpProber::OnSendComplete(int result) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + if (result != net::OK) { + OnDone(result, ProbeExitEnum::kSendFailure); + return; + } + udp_socket_remote_->ReceiveMore(/*num_additional_datagrams=*/1); +} + +void UdpProber::OnReceived(int32_t result, + const base::Optional<net::IPEndPoint>& src_ip, + base::Optional<base::span<const uint8_t>> data) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + if (result != net::OK) { + OnDone(result, ProbeExitEnum::kNetworkErrorOnReceiveFailure); + return; + } + + // The UdpProber instance is only interested in validating whether + // data can be received from the destination host. + if (!data.has_value() || data.value().empty()) { + // Note that net::ERR_FAILED is reported even if |result| is net::OK + // when no data is received. + OnDone(net::ERR_FAILED, ProbeExitEnum::kNoDataReceivedFailure); + return; + } + OnDone(net::OK, ProbeExitEnum::kSuccess); +} + +void UdpProber::OnDone(int result, ProbeExitEnum probe_exit_enum) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + // Invalidate pending callbacks. + weak_factory_.InvalidateWeakPtrs(); + // Destroy the socket connection. + udp_socket_listener_receiver_.reset(); + udp_socket_remote_.reset(); + // Reset the host resolver. + host_resolver_.reset(); + + std::move(callback_).Run(result, probe_exit_enum); +} + +void UdpProber::OnDisconnect() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + OnDone(net::ERR_FAILED, ProbeExitEnum::kMojoDisconnectFailure); +} + +} // namespace network_diagnostics +} // namespace chromeos
diff --git a/chrome/browser/chromeos/net/network_diagnostics/udp_prober.h b/chrome/browser/chromeos/net/network_diagnostics/udp_prober.h new file mode 100644 index 0000000..9acd39a --- /dev/null +++ b/chrome/browser/chromeos/net/network_diagnostics/udp_prober.h
@@ -0,0 +1,123 @@ +// Copyright 2020 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_CHROMEOS_NET_NETWORK_DIAGNOSTICS_UDP_PROBER_H_ +#define CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_UDP_PROBER_H_ + +#include <cstdint> + +#include "base/callback.h" +#include "base/containers/span.h" +#include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "chrome/browser/chromeos/net/network_diagnostics/host_resolver.h" +#include "mojo/public/cpp/bindings/receiver.h" +#include "mojo/public/cpp/bindings/remote.h" +#include "net/base/address_list.h" +#include "net/base/ip_endpoint.h" +#include "net/dns/public/resolve_error_info.h" +#include "net/traffic_annotation/network_traffic_annotation.h" +#include "services/network/public/mojom/network_context.mojom.h" +#include "services/network/public/mojom/udp_socket.mojom.h" +#include "url/gurl.h" + +namespace chromeos { +namespace network_diagnostics { + +// Uses a UDP socket to send data to a remote destination. After sending data, +// the prober listens for received data. It confirms that data was received but +// does not validate the content, hence no data is parsed. Used by network +// diagnostic routines. +class UdpProber : public network::mojom::UDPSocketListener { + public: + // Lists the ways a prober may end. The callback passed into the prober's + // constructor is invoked while exiting. + enum ProbeExitEnum { + kDnsFailure, + kConnectFailure, + kSendFailure, + kNetworkErrorOnReceiveFailure, + kMojoDisconnectFailure, + kNoDataReceivedFailure, + kSuccess, + }; + + using NetworkContextGetter = + base::RepeatingCallback<network::mojom::NetworkContext*()>; + using ConnectCallback = base::OnceCallback< + void(int result, const base::Optional<net::IPEndPoint>& local_addr_out)>; + using SendCallback = base::OnceCallback<void(int result)>; + using UdpProbeCompleteCallback = + base::OnceCallback<void(int result, ProbeExitEnum probe_exit_enum)>; + + // Establishes a UDP connection and sends |data| to |host_port_pair|. The + // traffic sent by the prober is described by |tag|. Note that the constructor + // will not invoke |callback|, which is passed into UdpProber during + // construction. This ensures the UdpProber instance is constructed before + // |callback| is invoked. The UdpProber must be created on the UI thread and + // will invoke |callback| on the UI thread. |network_context_getter| will be + // invoked on the UI thread. + UdpProber(NetworkContextGetter network_context_getter, + net::HostPortPair host_port_pair, + base::span<const uint8_t> data, + net::NetworkTrafficAnnotationTag tag, + UdpProbeCompleteCallback callback); + UdpProber(const UdpProber&) = delete; + UdpProber& operator=(const UdpProber&) = delete; + ~UdpProber() override; + + private: + // Processes the results of the DNS resolution done by |host_resolver_|. + void OnHostResolutionComplete( + HostResolver::ResolutionResult& resolution_result); + + // On success, the UDP socket is connected to the destination and is ready to + // send data. On failure, the UdpProber exits with a failure. + void OnConnectComplete(int result, + const base::Optional<net::IPEndPoint>& local_addr_out); + + // On success, the UDP socket is ready to receive data. So long as the + // received data is not empty, it is considered valid. The content itself is + // not verified. + void OnSendComplete(int result); + + // network::mojom::UDPSocketListener: + void OnReceived(int32_t result, + const base::Optional<net::IPEndPoint>& src_ip, + base::Optional<base::span<const uint8_t>> data) override; + + // Signals the end of the probe. Manages the clean up and returns a response + // to the caller. + void OnDone(int result, ProbeExitEnum probe_exit_enum); + + // Handles disconnects on the UDPSocket remote and UDPSocketListener receiver. + void OnDisconnect(); + + // Gets the active profile-specific network context. + NetworkContextGetter network_context_getter_; + // Contains the hostname and port. + net::HostPortPair host_port_pair_; + // Data to be sent to the destination. + base::span<const uint8_t> data_; + // Network annotation tag describing the socket traffic. + net::NetworkTrafficAnnotationTag tag_; + // Host resolver used for DNS lookup. + std::unique_ptr<HostResolver> host_resolver_; + // Stores the callback invoked once probe is complete or interrupted. + UdpProbeCompleteCallback callback_; + // Holds the UDPSocket remote. + mojo::Remote<network::mojom::UDPSocket> udp_socket_remote_; + // Listens to the response from hostname specified by |url_|. + mojo::Receiver<network::mojom::UDPSocketListener> + udp_socket_listener_receiver_{this}; + + // Must be the last member so that any callbacks taking a weak pointer to this + // instance are invalidated first during object destruction. + base::WeakPtrFactory<UdpProber> weak_factory_{this}; +}; + +} // namespace network_diagnostics +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_UDP_PROBER_H_
diff --git a/chrome/browser/chromeos/net/network_diagnostics/udp_prober_unittest.cc b/chrome/browser/chromeos/net/network_diagnostics/udp_prober_unittest.cc new file mode 100644 index 0000000..9b236bf --- /dev/null +++ b/chrome/browser/chromeos/net/network_diagnostics/udp_prober_unittest.cc
@@ -0,0 +1,254 @@ +// Copyright 2020 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/chromeos/net/network_diagnostics/udp_prober.h" + +#include <cstdint> +#include <memory> +#include <utility> +#include <vector> + +#include "base/bind.h" +#include "base/containers/span.h" +#include "base/run_loop.h" +#include "base/test/bind.h" +#include "chrome/browser/chromeos/net/network_diagnostics/fake_host_resolver.h" +#include "chrome/browser/chromeos/net/network_diagnostics/fake_network_context.h" +#include "chrome/browser/chromeos/net/network_diagnostics/network_diagnostics_util.h" +#include "content/public/test/browser_task_environment.h" +#include "mojo/public/cpp/bindings/receiver.h" +#include "mojo/public/cpp/bindings/remote.h" +#include "net/base/host_port_pair.h" +#include "net/base/ip_endpoint.h" +#include "net/base/net_errors.h" +#include "net/traffic_annotation/network_traffic_annotation.h" +#include "services/network/test/test_network_context.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace chromeos { +namespace network_diagnostics { + +namespace { + +using ProbeExitEnum = UdpProber::ProbeExitEnum; + +} // namespace + +class UdpProberWithFakeNetworkContextTest : public ::testing::Test { + public: + UdpProberWithFakeNetworkContextTest() = default; + UdpProberWithFakeNetworkContextTest( + const UdpProberWithFakeNetworkContextTest&) = delete; + UdpProberWithFakeNetworkContextTest& operator=( + const UdpProberWithFakeNetworkContextTest&) = delete; + + void InitializeProberNetworkContext( + std::unique_ptr<FakeHostResolver::DnsResult> fake_dns_result, + base::Optional<net::Error> udp_connect_complete_code, + base::Optional<net::Error> udp_send_complete_code, + base::Optional<net::Error> udp_on_received_code, + base::Optional<base::span<const uint8_t>> udp_on_received_data) { + fake_network_context_ = std::make_unique<FakeNetworkContext>(); + fake_network_context_->set_fake_dns_result(std::move(fake_dns_result)); + if (udp_connect_complete_code.has_value()) { + fake_network_context_->SetUdpConnectCode( + udp_connect_complete_code.value()); + } + if (udp_send_complete_code.has_value()) { + fake_network_context_->SetUdpSendCode(udp_send_complete_code.value()); + } + if (udp_on_received_code.has_value()) { + fake_network_context_->SetUdpOnReceivedCode(udp_on_received_code.value()); + } + if (udp_on_received_data.has_value()) { + fake_network_context_->SetUdpOnReceivedData( + std::move(udp_on_received_data.value())); + } + } + + void CreateAndExecuteUdpProber(base::span<const uint8_t> data, + UdpProber::UdpProbeCompleteCallback callback) { + ASSERT_TRUE(fake_network_context_); + udp_prober_ = std::make_unique<UdpProber>( + base::BindRepeating( + [](network::mojom::NetworkContext* network_context) { + return network_context; + }, + fake_network_context_.get()), + kFakeHostPortPair, std::move(data), kStunTag, std::move(callback)); + } + + void RunProberExpectingResult(int expected_result, + ProbeExitEnum expected_exit_enum) { + int probe_result = -1; + ProbeExitEnum probe_exit_enum = ProbeExitEnum::kConnectFailure; + base::RunLoop run_loop; + CreateAndExecuteUdpProber( + kValidStunData, + base::BindLambdaForTesting([&](int result, ProbeExitEnum exit_enum) { + probe_result = result; + probe_exit_enum = exit_enum; + EXPECT_EQ(expected_result, probe_result); + EXPECT_EQ(expected_exit_enum, probe_exit_enum); + run_loop.Quit(); + })); + run_loop.Run(); + } + + FakeNetworkContext* fake_network_context() { + return fake_network_context_.get(); + } + + protected: + const net::HostPortPair kFakeHostPortPair = + net::HostPortPair::FromString("fake_stun_server.com:80"); + const net::IPEndPoint kFakeIPAddress{ + net::IPEndPoint(net::IPAddress::IPv4Localhost(), /*port=*/1234)}; + const base::span<const uint8_t> kValidStunData = util::GetStunHeader(); + const net::NetworkTrafficAnnotationTag kStunTag = + util::GetStunNetworkAnnotationTag(); + + private: + content::BrowserTaskEnvironment task_environment_; + std::unique_ptr<FakeNetworkContext> fake_network_context_; + std::unique_ptr<UdpProber> udp_prober_; +}; + +TEST_F(UdpProberWithFakeNetworkContextTest, SuccessfulEndToEndResponse) { + auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>( + net::OK, net::ResolveErrorInfo(net::OK), + net::AddressList(kFakeIPAddress)); + std::array<uint8_t, 1> udp_on_received_data = {0x00}; + InitializeProberNetworkContext(std::move(fake_dns_result), + /*udp_connect_code=*/net::OK, + /*udp_send_complete_code=*/net::OK, + /*udp_on_received_code=*/net::OK, + udp_on_received_data); + RunProberExpectingResult(net::OK, ProbeExitEnum::kSuccess); +} + +TEST_F(UdpProberWithFakeNetworkContextTest, FailedDnsLookup) { + auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>( + net::ERR_NAME_NOT_RESOLVED, + net::ResolveErrorInfo(net::ERR_NAME_NOT_RESOLVED), net::AddressList()); + // UDP connect and subsequent steps will not happen in this scenario. + InitializeProberNetworkContext(std::move(fake_dns_result), + /*udp_connect_code=*/base::nullopt, + /*udp_send_complete_code=*/base::nullopt, + /*udp_on_received_code=*/base::nullopt, + /*udp_on_received_data=*/base::nullopt); + RunProberExpectingResult(net::ERR_NAME_NOT_RESOLVED, + ProbeExitEnum::kDnsFailure); +} + +TEST_F(UdpProberWithFakeNetworkContextTest, MojoDisconnectDnsLookup) { + // UDP connect and subsequent steps will not happen in this scenario. + InitializeProberNetworkContext(/*fake_dns_result=*/{}, + /*udp_connect_code=*/base::nullopt, + /*udp_send_code=*/base::nullopt, + /*udp_on_received_code=*/base::nullopt, + /*udp_on_received_data=*/base::nullopt); + fake_network_context()->set_disconnect_during_host_resolution(true); + RunProberExpectingResult(net::ERR_NAME_NOT_RESOLVED, + ProbeExitEnum::kDnsFailure); +} + +TEST_F(UdpProberWithFakeNetworkContextTest, FailedUdpConnection) { + auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>( + net::OK, net::ResolveErrorInfo(net::OK), + net::AddressList(kFakeIPAddress)); + InitializeProberNetworkContext( + std::move(fake_dns_result), + /*udp_connect_code=*/net::ERR_CONNECTION_FAILED, + /*udp_send_code=*/base::nullopt, + /*udp_on_received_code=*/base::nullopt, + /*udp_on_received_data=*/base::nullopt); + RunProberExpectingResult(net::ERR_CONNECTION_FAILED, + ProbeExitEnum::kConnectFailure); +} + +TEST_F(UdpProberWithFakeNetworkContextTest, MojoDisconnectDuringUdpConnection) { + auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>( + net::OK, net::ResolveErrorInfo(net::OK), + net::AddressList(kFakeIPAddress)); + InitializeProberNetworkContext(std::move(fake_dns_result), + /*udp_connect_code=*/base::nullopt, + /*udp_send_code=*/base::nullopt, + /*udp_on_received_code=*/base::nullopt, + /*udp_on_received_data=*/base::nullopt); + fake_network_context()->set_disconnect_during_udp_connection_attempt(true); + RunProberExpectingResult(net::ERR_FAILED, + ProbeExitEnum::kMojoDisconnectFailure); +} + +TEST_F(UdpProberWithFakeNetworkContextTest, FailedUdpSend) { + auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>( + net::OK, net::ResolveErrorInfo(net::OK), + net::AddressList(kFakeIPAddress)); + InitializeProberNetworkContext(std::move(fake_dns_result), + /*udp_connect_code=*/net::OK, + /*udp_send_code=*/net::ERR_CONNECTION_FAILED, + /*udp_on_received_code=*/base::nullopt, + /*udp_on_received_data=*/base::nullopt); + RunProberExpectingResult(net::ERR_CONNECTION_FAILED, + ProbeExitEnum::kSendFailure); +} + +TEST_F(UdpProberWithFakeNetworkContextTest, MojoDisconnectDuringUdpSend) { + auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>( + net::OK, net::ResolveErrorInfo(net::OK), + net::AddressList(kFakeIPAddress)); + net::Error udp_connect_code = net::OK; + InitializeProberNetworkContext(std::move(fake_dns_result), udp_connect_code, + /*udp_send_code=*/base::nullopt, + /*udp_on_received_code=*/base::nullopt, + /*udp_on_received_data=*/base::nullopt); + fake_network_context()->SetDisconnectDuringUdpSendAttempt(true); + RunProberExpectingResult(net::ERR_FAILED, + ProbeExitEnum::kMojoDisconnectFailure); +} + +TEST_F(UdpProberWithFakeNetworkContextTest, BadUdpNetworkCodeOnReceive) { + auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>( + net::OK, net::ResolveErrorInfo(net::OK), + net::AddressList(kFakeIPAddress)); + InitializeProberNetworkContext( + std::move(fake_dns_result), + /*udp_connect_code=*/net::OK, + /*udp_send_code=*/net::OK, + /*udp_on_received_code=*/net::ERR_CONNECTION_FAILED, + /*udp_on_received_data=*/base::nullopt); + RunProberExpectingResult(net::ERR_CONNECTION_FAILED, + ProbeExitEnum::kNetworkErrorOnReceiveFailure); +} + +TEST_F(UdpProberWithFakeNetworkContextTest, NoDataReceivedOnReceiveFailure) { + auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>( + net::OK, net::ResolveErrorInfo(net::OK), + net::AddressList(kFakeIPAddress)); + InitializeProberNetworkContext(std::move(fake_dns_result), + /*udp_connect_code=*/net::OK, + /*udp_send_code=*/net::OK, + /*udp_on_received_code*/ net::OK, + /*udp_on_received_data=*/{}); + RunProberExpectingResult(net::ERR_FAILED, + ProbeExitEnum::kNoDataReceivedFailure); +} + +TEST_F(UdpProberWithFakeNetworkContextTest, MojoDisconnectDuringUdpReceive) { + auto fake_dns_result = std::make_unique<FakeHostResolver::DnsResult>( + net::OK, net::ResolveErrorInfo(net::OK), + net::AddressList(kFakeIPAddress)); + InitializeProberNetworkContext(std::move(fake_dns_result), + /*udp_connect_code=*/net::OK, + /*udp_send_code=*/net::OK, + /*udp_on_received_code=*/net::OK, + /*udp_on_received_data=*/{}); + fake_network_context()->SetDisconnectDuringUdpReceiveAttempt(true); + RunProberExpectingResult(net::ERR_FAILED, + ProbeExitEnum::kMojoDisconnectFailure); +} + +} // namespace network_diagnostics +} // namespace chromeos
diff --git a/chrome/browser/chromeos/scanning/scan_service.cc b/chrome/browser/chromeos/scanning/scan_service.cc index d1ef6fb..98bc774 100644 --- a/chrome/browser/chromeos/scanning/scan_service.cc +++ b/chrome/browser/chromeos/scanning/scan_service.cc
@@ -90,7 +90,12 @@ return; } + scan_job_observer_.reset(); scan_job_observer_.Bind(std::move(observer)); + // Unretained is safe here, because `this` owns `scan_job_observer_`, and no + // reply callbacks will be invoked once the mojo::Remote is destroyed. + scan_job_observer_.set_disconnect_handler( + base::BindOnce(&ScanService::CancelScan, base::Unretained(this))); base::Time::Now().LocalExplode(&start_time_); save_failed_ = false; @@ -106,6 +111,11 @@ std::move(callback).Run(true); } +void ScanService::CancelScan() { + lorgnette_scanner_manager_->CancelScan(base::BindOnce( + &ScanService::OnCancelCompleted, weak_ptr_factory_.GetWeakPtr())); +} + void ScanService::BindInterface( mojo::PendingReceiver<mojo_ipc::ScanService> pending_receiver) { receiver_.Bind(std::move(pending_receiver)); @@ -159,7 +169,6 @@ void ScanService::OnProgressPercentReceived(uint32_t progress_percent, uint32_t page_number) { DCHECK_LE(progress_percent, kMaxProgressPercent); - DCHECK(scan_job_observer_.is_connected()); scan_job_observer_->OnPageProgress(page_number, progress_percent); } @@ -171,7 +180,6 @@ // vector. // In case the last reported progress percent was less than 100, send one // final progress event before the page complete event. - DCHECK(scan_job_observer_.is_connected()); scan_job_observer_->OnPageProgress(page_number, kMaxProgressPercent); scan_job_observer_->OnPageComplete( std::vector<uint8_t>(scanned_image.begin(), scanned_image.end())); @@ -208,9 +216,11 @@ } void ScanService::OnScanCompleted(bool success) { - DCHECK(scan_job_observer_.is_connected()); scan_job_observer_->OnScanComplete(success && !save_failed_); - scan_job_observer_.reset(); +} + +void ScanService::OnCancelCompleted(bool success) { + scan_job_observer_->OnCancelComplete(success); } bool ScanService::FilePathSupported(const base::FilePath& file_path) {
diff --git a/chrome/browser/chromeos/scanning/scan_service.h b/chrome/browser/chromeos/scanning/scan_service.h index 781dea3..28e3b2b2 100644 --- a/chrome/browser/chromeos/scanning/scan_service.h +++ b/chrome/browser/chromeos/scanning/scan_service.h
@@ -48,6 +48,7 @@ scanning::mojom::ScanSettingsPtr settings, mojo::PendingRemote<scanning::mojom::ScanJobObserver> observer, StartScanCallback callback) override; + void CancelScan() override; // Binds receiver_ by consuming |pending_receiver|. void BindInterface( @@ -90,6 +91,10 @@ // Processes the final result of calling LorgnetteScannerManager::Scan(). void OnScanCompleted(bool success); + // Processes the final result of calling + // LorgnetteScannerManager::CancelScan(). + void OnCancelCompleted(bool success); + // TODO(jschettler): Replace this with a generic helper function when one is // available. // Determines whether the service supports saving scanned images to
diff --git a/chrome/browser/chromeos/scanning/scan_service_unittest.cc b/chrome/browser/chromeos/scanning/scan_service_unittest.cc index 7aa812f..282b21e 100644 --- a/chrome/browser/chromeos/scanning/scan_service_unittest.cc +++ b/chrome/browser/chromeos/scanning/scan_service_unittest.cc
@@ -88,6 +88,10 @@ void OnScanComplete(bool success) override { scan_success_ = success; } + void OnCancelComplete(bool success) override { + cancel_scan_success_ = success; + } + // Creates a pending remote that can be passed in calls to // ScanService::StartScan(). mojo::PendingRemote<mojo_ipc::ScanJobObserver> GenerateRemote() { @@ -104,10 +108,14 @@ return progress_ == 100 && page_complete_ && scan_success_; } + // Returns true if the cancel scan request completed successfully. + bool cancel_scan_success() const { return cancel_scan_success_; } + private: uint32_t progress_ = 0; bool page_complete_ = false; bool scan_success_ = false; + bool cancel_scan_success_ = false; mojo::Receiver<mojo_ipc::ScanJobObserver> receiver_{this}; }; @@ -152,6 +160,12 @@ return success; } + // Performs a cancel scan request. + void CancelScan() { + scan_service_remote_->CancelScan(); + scan_service_remote_.FlushForTesting(); + } + protected: base::ScopedTempDir temp_dir_; FakeLorgnetteScannerManager fake_lorgnette_scanner_manager_; @@ -295,4 +309,21 @@ } } +// Test that canceling sends an update to the observer OnCancelComplete(). +TEST_F(ScanServiceTest, CancelScanBeforeScanCompletes) { + fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse( + {kFirstTestScannerName}); + fake_lorgnette_scanner_manager_.SetScanResponse("TestData"); + auto scanners = GetScanners(); + ASSERT_EQ(scanners.size(), 1u); + + scan_service_.SetMyFilesPathForTesting(temp_dir_.GetPath()); + mojo_ipc::ScanSettings settings; + settings.scan_to_path = temp_dir_.GetPath(); + + Scan(scanners[0]->id, settings.Clone()); + CancelScan(); + EXPECT_TRUE(fake_scan_job_observer_.cancel_scan_success()); +} + } // namespace chromeos
diff --git a/chrome/browser/chromeos/web_applications/camera_app_integration_browsertest.cc b/chrome/browser/chromeos/web_applications/camera_app_integration_browsertest.cc index 5cf0030..f1590674 100644 --- a/chrome/browser/chromeos/web_applications/camera_app_integration_browsertest.cc +++ b/chrome/browser/chromeos/web_applications/camera_app_integration_browsertest.cc
@@ -7,16 +7,17 @@ #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/views/frame/browser_view.h" -#include "chrome/browser/web_applications/components/external_app_install_features.h" #include "chrome/browser/web_applications/system_web_app_manager_browsertest.h" #include "chrome/test/base/ui_test_utils.h" +#include "chromeos/constants/chromeos_features.h" #include "content/public/test/browser_test.h" #include "content/public/test/test_navigation_observer.h" class CameraAppIntegrationTest : public SystemWebAppIntegrationTest { public: CameraAppIntegrationTest() { - scoped_feature_list_.InitAndEnableFeature(web_app::kCameraSystemWebApp); + scoped_feature_list_.InitWithFeatures( + {chromeos::features::kCameraSystemWebApp}, {}); } private:
diff --git a/chrome/browser/extensions/component_loader.cc b/chrome/browser/extensions/component_loader.cc index cf8c45c1..38bfa86 100644 --- a/chrome/browser/extensions/component_loader.cc +++ b/chrome/browser/extensions/component_loader.cc
@@ -395,8 +395,10 @@ } void ComponentLoader::AddChromeCameraApp() { - // TODO(crbug.com/1135280): Remove all the logic here once CCA is fully - // migrated to SWA. + if (base::FeatureList::IsEnabled(chromeos::features::kCameraSystemWebApp)) { + return; + } + base::FilePath resources_path; if (base::PathService::Get(chrome::DIR_RESOURCES, &resources_path)) { AddComponentFromDir(resources_path.Append(extension_misc::kCameraAppPath),
diff --git a/chrome/browser/media/audio_service_util.cc b/chrome/browser/media/audio_service_util.cc index 693197d..7da7a06 100644 --- a/chrome/browser/media/audio_service_util.cc +++ b/chrome/browser/media/audio_service_util.cc
@@ -4,22 +4,38 @@ #include "chrome/browser/media/audio_service_util.h" +#include <string> + #include "base/feature_list.h" +#include "base/optional.h" +#include "base/values.h" +#include "build/build_config.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/policy/chrome_browser_policy_connector.h" +#include "components/policy/core/common/policy_map.h" +#include "components/policy/core/common/policy_namespace.h" +#include "components/policy/core/common/policy_service.h" +#include "components/policy/policy_constants.h" #include "content/public/common/content_features.h" -static base::Optional<bool> g_force_audio_service_sandbox; - bool IsAudioServiceSandboxEnabled() { - return g_force_audio_service_sandbox.value_or( + base::Optional<bool> force_enable_audio_sandbox; +#if defined(OS_WIN) || defined(OS_MAC) || \ + (defined(OS_LINUX) && !defined(OS_CHROMEOS)) + const policy::PolicyMap& policies = + g_browser_process->browser_policy_connector() + ->GetPolicyService() + ->GetPolicies(policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME, + std::string())); + const base::Value* audio_sandbox_enabled_policy_value = + policies.GetValue(policy::key::kAudioSandboxEnabled); + if (audio_sandbox_enabled_policy_value) { + force_enable_audio_sandbox.emplace(); + audio_sandbox_enabled_policy_value->GetAsBoolean( + &force_enable_audio_sandbox.value()); + } +#endif + return force_enable_audio_sandbox.value_or( base::FeatureList::IsEnabled(features::kAudioServiceSandbox)); } -// If |force_audio_service_sandbox| is: -// * null: use the default audio-service sandbox configuration (varies per -// platform) -// * true: enable the audio-service sandbox, regardless of default settings. -// * false: disable the audio-service sandbox, regardless of default settings. -void SetForceAudioServiceSandboxed( - const base::Optional<bool>& force_audio_service_sandbox) { - g_force_audio_service_sandbox = force_audio_service_sandbox; -}
diff --git a/chrome/browser/media/audio_service_util.h b/chrome/browser/media/audio_service_util.h index 4d2706c5..721d386 100644 --- a/chrome/browser/media/audio_service_util.h +++ b/chrome/browser/media/audio_service_util.h
@@ -5,16 +5,6 @@ #ifndef CHROME_BROWSER_MEDIA_AUDIO_SERVICE_UTIL_H_ #define CHROME_BROWSER_MEDIA_AUDIO_SERVICE_UTIL_H_ -#include "base/optional.h" - bool IsAudioServiceSandboxEnabled(); -// If |force_audio_service_sandbox| is: -// * null: use the default audio-service sandbox configuration (varies per -// platform) -// * true: enable the audio-service sandbox, regardless of default settings. -// * false: disable the audio-service sandbox, regardless of default settings. -void SetForceAudioServiceSandboxed( - const base::Optional<bool>& force_audio_service_sandbox); - #endif // CHROME_BROWSER_MEDIA_AUDIO_SERVICE_UTIL_H_
diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java index 4cc976e..27dc28d 100644 --- a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java +++ b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
@@ -725,6 +725,7 @@ public static final String VERIFIED_DIGITAL_ASSET_LINKS = "verified_digital_asset_links"; + public static final String VIDEO_TUTORIALS_SHARE_URL_SET = "Chrome.VideoTutorials.ShareUrls"; public static final String VR_EXIT_TO_2D_COUNT = "VR_EXIT_TO_2D_COUNT"; public static final String VR_FEEDBACK_OPT_OUT = "VR_FEEDBACK_OPT_OUT"; @@ -835,7 +836,8 @@ SETTINGS_SAFETY_CHECK_LAST_RUN_TIMESTAMP, SETTINGS_SAFETY_CHECK_RUN_COUNTER, SIGNIN_PROMO_IMPRESSIONS_COUNT_NTP, - TWA_DISCLOSURE_SEEN_PACKAGES + TWA_DISCLOSURE_SEEN_PACKAGES, + VIDEO_TUTORIALS_SHARE_URL_SET ); // clang-format on }
diff --git a/chrome/browser/prefetch/no_state_prefetch/prerender_unittest.cc b/chrome/browser/prefetch/no_state_prefetch/prerender_unittest.cc index 96535a6..bdc1ec8 100644 --- a/chrome/browser/prefetch/no_state_prefetch/prerender_unittest.cc +++ b/chrome/browser/prefetch/no_state_prefetch/prerender_unittest.cc
@@ -1503,31 +1503,6 @@ EXPECT_FALSE(prerender_manager()->FindEntry(url)); } -TEST_F(PrerenderTest, LinkManagerRendererDisconnect) { - prerender_manager()->SetTickClockForTesting(tick_clock()); - EXPECT_TRUE(IsEmptyPrerenderLinkManager()); - GURL url("http://www.myexample.com"); - DummyPrerenderContents* prerender_contents = - prerender_manager()->CreateNextPrerenderContents(url, - FINAL_STATUS_TIMED_OUT); - - EXPECT_TRUE(AddSimplePrerender(url)); - EXPECT_TRUE(prerender_contents->prerendering_has_started()); - EXPECT_FALSE(prerender_contents->prerendering_has_been_cancelled()); - ASSERT_EQ(prerender_contents, prerender_manager()->FindEntry(url)); - - // Disconnect all clients. Spin the run loop to give the link manager - // opportunity to detect disconnection. - DisconnectAllPrerenderProcessorClients(); - base::RunLoop().RunUntilIdle(); - - tick_clock()->Advance(prerender_manager()->config().abandon_time_to_live + - TimeDelta::FromSeconds(1)); - - EXPECT_FALSE(prerender_manager()->FindEntry(url)); - EXPECT_TRUE(IsEmptyPrerenderLinkManager()); -} - // Creates two prerenders, one of which should be blocked by the // max_link_concurrency; abandons both of them and waits to make sure both // are cleared from the PrerenderLinkManager.
diff --git a/chrome/browser/printing/print_backend_browsertest.cc b/chrome/browser/printing/print_backend_browsertest.cc index c8a95046..7ec3df9 100644 --- a/chrome/browser/printing/print_backend_browsertest.cc +++ b/chrome/browser/printing/print_backend_browsertest.cc
@@ -131,7 +131,10 @@ EXPECT_FALSE(default_printer_name.has_value()); } -IN_PROC_BROWSER_TEST_F(PrintBackendBrowserTest, GetDefaultPrinterName) { +// TODO(crbug.com/809738): Re-enable after the updates for setting up the +// printer test environment are made to print_backend_service.mojom. +IN_PROC_BROWSER_TEST_F(PrintBackendBrowserTest, + DISABLED_GetDefaultPrinterName) { base::Optional<std::string> default_printer_name; DoInitAndSetupTestData();
diff --git a/chrome/browser/resources/nearby_share/nearby_discovery_page.html b/chrome/browser/resources/nearby_share/nearby_discovery_page.html index 96e0fbf..4e26b1ed 100644 --- a/chrome/browser/resources/nearby_share/nearby_discovery_page.html +++ b/chrome/browser/resources/nearby_share/nearby_discovery_page.html
@@ -18,6 +18,7 @@ #deviceList { display: block; + margin-top: 12px; overflow: auto; padding-inline-end: var(--nearby-page-space-large-inline); padding-inline-start: var(--nearby-page-space-large-inline); @@ -58,10 +59,10 @@ } #process-row { - align-items: center; + align-items: flex-start; display: flex; - flex-grow: 1; justify-content: space-between; + margin: auto 0; margin-block-start: 48px; overflow: hidden; } @@ -73,6 +74,7 @@ } #placeholder { + align-self: center; color: rgb(95, 99, 104); font-size: 13px; line-height: 20px; @@ -103,11 +105,9 @@ aria-label="$i18n{nearbyShareDiscoveryPageSubtitle}"> <template> <nearby-device tabindex$="[[tabIndex]]" share-target="[[item]]" - is-selected="[[isShareTargetSelected_( - item, selectedShareTarget)]]" + is-selected="[[selected]]" role="radio" - aria-checked$="[[isShareTargetSelectedStr_( - item, selectedShareTarget)]]"> + aria-checked$="[[boolToString_(selected)]]"> </nearby-device> </template> </iron-list>
diff --git a/chrome/browser/resources/nearby_share/nearby_discovery_page.js b/chrome/browser/resources/nearby_share/nearby_discovery_page.js index 80984a1f9..937215f 100644 --- a/chrome/browser/resources/nearby_share/nearby_discovery_page.js +++ b/chrome/browser/resources/nearby_share/nearby_discovery_page.js
@@ -271,35 +271,35 @@ /** @private */ onSelectedShareTargetChanged_() { - // The device list gets removed from the DOM if there are no share targets. - if (!this.$.deviceList) { + const deviceList = this.$$('#deviceList'); + if (!deviceList) { + // deviceList is in dom-if and may not be found return; } - // <iron-list> causes |this.$.deviceList.selectedItem| to be null if tapped - // a second time. Manually reselect the last item to preserve selection. - if (!this.$.deviceList.selectedItem && this.lastSelectedShareTarget_) { - this.$.deviceList.selectItem(this.lastSelectedShareTarget_); + // <iron-list> causes |selectedItem| to be null if tapped a second time. + // Manually reselect the last item to preserve selection. + if (!deviceList.selectedItem && this.lastSelectedShareTarget_) { + // Use async to make sure this happens after |selectedItem| gets its + // final value. + this.async(() => { + const deviceList = this.$$('#deviceList'); + if (!deviceList.selectedItem) { + deviceList.selectItem(this.lastSelectedShareTarget_); + } + }); + } else { + this.lastSelectedShareTarget_ = deviceList.selectedItem; } - this.lastSelectedShareTarget_ = this.$.deviceList.selectedItem; }, /** - * @param {!nearbyShare.mojom.ShareTarget} shareTarget - * @return {boolean} - * @private - */ - isShareTargetSelected_(shareTarget) { - return this.selectedShareTarget === shareTarget; - }, - - /** - * @param {!nearbyShare.mojom.ShareTarget} shareTarget + * @param {boolean} bool * @return {string} * @private */ - isShareTargetSelectedStr_(shareTarget) { - return this.isShareTargetSelected_(shareTarget).toString(); + boolToString_(bool) { + return bool.toString(); }, /**
diff --git a/chrome/browser/resources/tab_search/tab_search_search_field.html b/chrome/browser/resources/tab_search/tab_search_search_field.html index 7039004..9db6523 100644 --- a/chrome/browser/resources/tab_search/tab_search_search_field.html +++ b/chrome/browser/resources/tab_search/tab_search_search_field.html
@@ -51,6 +51,10 @@ text-overflow: ellipsis; } + #searchInput::-webkit-search-cancel-button { + display: none; + } + @media (prefers-color-scheme: dark) { #searchIcon { color: var(--google-blue-refresh-300);
diff --git a/chrome/browser/site_isolation/site_per_process_interactive_browsertest.cc b/chrome/browser/site_isolation/site_per_process_interactive_browsertest.cc index 4d51765..5b51984 100644 --- a/chrome/browser/site_isolation/site_per_process_interactive_browsertest.cc +++ b/chrome/browser/site_isolation/site_per_process_interactive_browsertest.cc
@@ -48,7 +48,6 @@ #include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "ui/base/test/ui_controls.h" -#include "ui/base/ui_base_features.h" #include "ui/display/display.h" #include "ui/display/screen.h" #include "ui/gfx/geometry/point.h" @@ -426,11 +425,6 @@ // Verifies against regression of https://crbug.com/702330 IN_PROC_BROWSER_TEST_F(SitePerProcessInteractiveBrowserTest, TabAndMouseFocusNavigation) { -#if defined(USE_OZONE) - // TODO(https://crbug.com/1109696): enable for ozone. - if (features::IsUsingOzonePlatform()) - return; -#endif GURL main_url(embedded_test_server()->GetURL( "a.com", "/cross_site_iframe_factory.html?a(b,c)")); ui_test_utils::NavigateToURL(browser(), main_url);
diff --git a/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc b/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc index c2e2550..d90a52d 100644 --- a/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc +++ b/chrome/browser/sync/test/integration/two_client_web_apps_bmo_sync_test.cc
@@ -625,6 +625,7 @@ expected_os_hook_requests[OsHookType::kShortcuts] = true; expected_os_hook_requests[OsHookType::kRunOnOsLogin] = false; expected_os_hook_requests[OsHookType::kShortcutsMenu] = true; + expected_os_hook_requests[OsHookType::kUninstallationViaOsSettings] = true; expected_os_hook_requests[OsHookType::kFileHandlers] = true; EXPECT_EQ(expected_os_hook_requests, last_options->os_hooks); EXPECT_TRUE(last_options->add_to_desktop);
diff --git a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuTest.java b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuTest.java index 0a3db69..9b65114 100644 --- a/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuTest.java +++ b/chrome/browser/ui/android/appmenu/internal/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuTest.java
@@ -27,6 +27,7 @@ import org.junit.runner.RunWith; import org.mockito.Mockito; +import org.chromium.base.test.util.Batch; import org.chromium.base.test.util.CallbackHelper; import org.chromium.base.test.util.CommandLineFlags; import org.chromium.base.test.util.CriteriaHelper; @@ -53,6 +54,7 @@ */ @RunWith(ChromeJUnit4ClassRunner.class) @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) +@Batch(Batch.PER_CLASS) public class AppMenuTest extends DummyUiActivityTestCase { private AppMenuCoordinatorImpl mAppMenuCoordinator; private AppMenuHandlerImpl mAppMenuHandler; @@ -146,6 +148,8 @@ Assert.assertEquals("Popup should be aligned with right of anchor. Anchor rect: " + viewRect + ", popup rect: " + popupRect, viewRect.right, popupRect.right); + + AppMenuCoordinatorImpl.setHasPermanentMenuKeyForTesting(null); } @Test @@ -162,6 +166,7 @@ Assert.assertNotEquals("Popup should be offset from right of anchor." + "Anchor rect: " + viewRect + ", popup rect: " + popupRect, viewRect.right, popupRect.right); + AppMenuCoordinatorImpl.setHasPermanentMenuKeyForTesting(null); } @Test
diff --git a/chrome/browser/ui/startup/startup_browser_creator.cc b/chrome/browser/ui/startup/startup_browser_creator.cc index d9c4992..27f6fb8 100644 --- a/chrome/browser/ui/startup/startup_browser_creator.cc +++ b/chrome/browser/ui/startup/startup_browser_creator.cc
@@ -58,6 +58,7 @@ #include "chrome/browser/ui/startup/launch_mode_recorder.h" #include "chrome/browser/ui/startup/startup_browser_creator_impl.h" #include "chrome/browser/ui/ui_features.h" +#include "chrome/browser/ui/web_applications/web_app_ui_manager_impl.h" #include "chrome/common/buildflags.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_features.h" @@ -837,6 +838,23 @@ return true; } +#if defined(OS_WIN) + // If --uninstall-app-id is specified, remove the target web app. + if (command_line.HasSwitch(switches::kUninstallAppId)) { + std::string app_id = + command_line.GetSwitchValueASCII(switches::kUninstallAppId); + + web_app::WebAppUiManagerImpl::Get(last_used_profile) + ->UninstallWebAppFromStartupSwitch(app_id); + + // Return true to allow startup to continue and for the main event loop to + // run. The process will shut down if no browser windows are open when the + // uninstall completes thanks to UninstallWebAppFromStartupSwitch's + // ScopedKeepAlive. + return true; + } +#endif // defined(OS_WIN) + if (command_line.HasSwitch(extensions::switches::kLoadApps) && can_use_last_profile) { if (!ProcessLoadApps(command_line, cur_dir, last_used_profile))
diff --git a/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.cc b/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.cc index 63403d3..619feae 100644 --- a/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.cc +++ b/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.cc
@@ -132,11 +132,9 @@ : HistogramCloseAction::kUninstall; UMA_HISTOGRAM_ENUMERATION("Webapp.UninstallDialogAction", action); - bool uninstalled = Uninstall(); + Uninstall(); if (checkbox_->GetChecked()) ClearWebAppSiteData(); - - std::exchange(dialog_, nullptr)->CallCallback(uninstalled); } void WebAppUninstallDialogDelegateView::OnDialogCanceled() { @@ -151,18 +149,28 @@ return image_; } -bool WebAppUninstallDialogDelegateView::Uninstall() { +void WebAppUninstallDialogDelegateView::Uninstall() { auto* provider = web_app::WebAppProvider::Get(profile_); DCHECK(provider); - if (!provider->install_finalizer().CanUserUninstallExternalApp(app_id_)) - return false; + if (!provider->install_finalizer().CanUserUninstallExternalApp(app_id_)) { + std::exchange(dialog_, nullptr)->CallCallback(/*uninstalled=*/false); + return; + } dialog_->UninstallStarted(); - provider->install_finalizer().UninstallExternalAppByUser(app_id_, - base::DoNothing()); - return true; + // Callback to the WebAppUninstallDialogViews because + // WebAppUninstallDialogDelegateView lifetime is controlled by Widget and it + // is terminiated as soon as dialog is closed regardless of web app + // uninstallation. + provider->install_finalizer().UninstallExternalAppByUser( + app_id_, base::BindOnce(&WebAppUninstallDialogViews::CallCallback, + dialog_->GetWeakPtr())); + // We successfully call Web App Uninstall routine, then + // WebAppUninstallDialogDelegateView can be terminated, but can't call the + // callback of the dialog caller. + dialog_ = nullptr; } void WebAppUninstallDialogDelegateView::ClearWebAppSiteData() { @@ -281,6 +289,11 @@ std::move(closed_callback_).Run(uninstalled); } +base::WeakPtr<WebAppUninstallDialogViews> +WebAppUninstallDialogViews::GetWeakPtr() { + return weak_ptr_factory_.GetWeakPtr(); +} + // static std::unique_ptr<web_app::WebAppUninstallDialog> web_app::WebAppUninstallDialog::Create(Profile* profile,
diff --git a/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.h b/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.h index 84c438c..a9b537b 100644 --- a/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.h +++ b/chrome/browser/ui/views/web_apps/web_app_uninstall_dialog_view.h
@@ -53,8 +53,8 @@ // views::DialogDelegateView: gfx::ImageSkia GetWindowIcon() override; - // Uninstalls the web app. Returns true on success. - bool Uninstall(); + // Uninstalls the web app. + void Uninstall(); void ClearWebAppSiteData(); void OnDialogAccepted(); @@ -94,6 +94,7 @@ void UninstallStarted(); void CallCallback(bool uninstalled); + base::WeakPtr<WebAppUninstallDialogViews> GetWeakPtr(); private: // web_app::AppRegistrarObserver:
diff --git a/chrome/browser/ui/web_applications/web_app_dialog_manager.cc b/chrome/browser/ui/web_applications/web_app_dialog_manager.cc index 920ddd09..77a48b58 100644 --- a/chrome/browser/ui/web_applications/web_app_dialog_manager.cc +++ b/chrome/browser/ui/web_applications/web_app_dialog_manager.cc
@@ -73,6 +73,10 @@ base::UmaHistogramBoolean( "WebApp.UninstallDialog.AppsPageUninstallSuccess", uninstalled); break; + case UninstallSource::kOsSettings: + base::UmaHistogramBoolean( + "WebApp.UninstallDialog.OsSettingsUninstallSuccess", uninstalled); + break; } std::move(callback).Run(/*success=*/uninstalled);
diff --git a/chrome/browser/ui/web_applications/web_app_dialog_manager.h b/chrome/browser/ui/web_applications/web_app_dialog_manager.h index 21f566b..16484af0 100644 --- a/chrome/browser/ui/web_applications/web_app_dialog_manager.h +++ b/chrome/browser/ui/web_applications/web_app_dialog_manager.h
@@ -30,6 +30,7 @@ enum class UninstallSource { kAppMenu, kAppsPage, + kOsSettings, }; using Callback = base::OnceCallback<void(bool success)>;
diff --git a/chrome/browser/ui/web_applications/web_app_ui_manager_impl.cc b/chrome/browser/ui/web_applications/web_app_ui_manager_impl.cc index c849cc0..af0d2c3 100644 --- a/chrome/browser/ui/web_applications/web_app_ui_manager_impl.cc +++ b/chrome/browser/ui/web_applications/web_app_ui_manager_impl.cc
@@ -35,6 +35,12 @@ #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h" #endif +#if defined(OS_WIN) +#include "components/keep_alive_registry/keep_alive_types.h" +#include "components/keep_alive_registry/scoped_keep_alive.h" +#include "ui/gfx/native_widget_types.h" +#endif // defined(OS_WIN) + namespace web_app { namespace { @@ -49,6 +55,34 @@ return installed; } +#if defined(OS_WIN) + +// UninstallWebAppWithDialog handles WebApp uninstallation from the +// Windows Settings. +void UninstallWebAppWithDialog( + const AppId& app_id, + Profile* profile, + std::unique_ptr<ScopedKeepAlive> keep_browser_alive) { + auto* provider = WebAppProvider::Get(profile); + if (!provider->registrar().IsLocallyInstalled(app_id)) { + // App does not exist and controller is destroyed. + return; + } + + WebAppUiManagerImpl::Get(profile)->dialog_manager().UninstallWebApp( + app_id, WebAppDialogManager::UninstallSource::kOsSettings, + gfx::kNullNativeWindow, + base::BindOnce( + [](std::unique_ptr<ScopedKeepAlive> keep_browser_alive, + bool /*uninstalled*/) { + // This callback exists to own |keep_browser_alive|, + // until after the uninstallation completes. + }, + std::move(keep_browser_alive))); +} + +#endif // defined(OS_WIN) + } // namespace // static @@ -289,6 +323,18 @@ windows_closed_requests_map_.erase(app_id); } +#if defined(OS_WIN) +void WebAppUiManagerImpl::UninstallWebAppFromStartupSwitch( + const AppId& app_id) { + auto keep_browser_alive = std::make_unique<ScopedKeepAlive>( + KeepAliveOrigin::APP_UNINSTALLATION_FROM_OS_SETTINGS, + KeepAliveRestartOption::DISABLED); + WebAppProvider::Get(profile_)->on_registry_ready().Post( + FROM_HERE, base::BindOnce(&UninstallWebAppWithDialog, app_id, profile_, + std::move(keep_browser_alive))); +} +#endif // defined(OS_WIN) + bool WebAppUiManagerImpl::IsBrowserForInstalledApp(Browser* browser) { if (browser->profile() != profile_) return false;
diff --git a/chrome/browser/ui/web_applications/web_app_ui_manager_impl.h b/chrome/browser/ui/web_applications/web_app_ui_manager_impl.h index 6dc83b9..03be401 100644 --- a/chrome/browser/ui/web_applications/web_app_ui_manager_impl.h +++ b/chrome/browser/ui/web_applications/web_app_ui_manager_impl.h
@@ -12,6 +12,7 @@ #include "base/callback_forward.h" #include "base/memory/weak_ptr.h" #include "base/optional.h" +#include "build/build_config.h" #include "chrome/browser/ui/browser_list_observer.h" #include "chrome/browser/web_applications/components/web_app_ui_manager.h" @@ -65,6 +66,12 @@ void OnBrowserAdded(Browser* browser) override; void OnBrowserRemoved(Browser* browser) override; +#if defined(OS_WIN) + // Attempts to uninstall the given web app id. Meant to be used with OS-level + // uninstallation support/hooks. + void UninstallWebAppFromStartupSwitch(const AppId& app_id); +#endif + private: // Returns true if Browser is for an installed App. bool IsBrowserForInstalledApp(Browser* browser);
diff --git a/chrome/browser/ui/webui/ntp/app_launcher_handler.cc b/chrome/browser/ui/webui/ntp/app_launcher_handler.cc index 763b8fd1..db7bef1 100644 --- a/chrome/browser/ui/webui/ntp/app_launcher_handler.cc +++ b/chrome/browser/ui/webui/ntp/app_launcher_handler.cc
@@ -1359,6 +1359,7 @@ options.os_hooks[web_app::OsHookType::kShortcutsMenu] = true; options.os_hooks[web_app::OsHookType::kFileHandlers] = true; options.os_hooks[web_app::OsHookType::kRunOnOsLogin] = false; + options.os_hooks[web_app::OsHookType::kUninstallationViaOsSettings] = true; web_app_provider_->os_integration_manager().InstallOsHooks( app_id,
diff --git a/chrome/browser/video_tutorials/BUILD.gn b/chrome/browser/video_tutorials/BUILD.gn index 91f5faa..efd8d69 100644 --- a/chrome/browser/video_tutorials/BUILD.gn +++ b/chrome/browser/video_tutorials/BUILD.gn
@@ -68,6 +68,7 @@ "android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoTutorialIPHUtils.java", "android/java/src/org/chromium/chrome/browser/video_tutorials/iph/VideoTutorialTryNowTracker.java", "android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialListCoordinator.java", + "android/java/src/org/chromium/chrome/browser/video_tutorials/metrics/VideoTutorialMetrics.java", "android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerCoordinator.java", ]
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/metrics/VideoTutorialMetrics.java b/chrome/browser/video_tutorials/android/java/src/org/chromium/chrome/browser/video_tutorials/metrics/VideoTutorialMetrics.java similarity index 93% rename from chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/metrics/VideoTutorialMetrics.java rename to chrome/browser/video_tutorials/android/java/src/org/chromium/chrome/browser/video_tutorials/metrics/VideoTutorialMetrics.java index af1087e..d4420de 100644 --- a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/metrics/VideoTutorialMetrics.java +++ b/chrome/browser/video_tutorials/android/java/src/org/chromium/chrome/browser/video_tutorials/metrics/VideoTutorialMetrics.java
@@ -33,7 +33,8 @@ // Please treat this list as append only and keep it in sync with // VideoTutorials.UserAction in enums.xml. @IntDef({UserAction.CHANGE_LANGUAGE, UserAction.WATCH_NEXT_VIDEO, UserAction.TRY_NOW, - UserAction.SHARE, UserAction.CLOSE, UserAction.BACK_PRESS_WHEN_SHOWING_VIDEO_PLAYER}) + UserAction.SHARE, UserAction.CLOSE, UserAction.BACK_PRESS_WHEN_SHOWING_VIDEO_PLAYER, + UserAction.OPEN_SHARED_VIDEO, UserAction.INVALID_SHARE_URL}) @Retention(RetentionPolicy.SOURCE) public @interface UserAction { int CHANGE_LANGUAGE = 0; @@ -42,7 +43,9 @@ int SHARE = 3; int CLOSE = 4; int BACK_PRESS_WHEN_SHOWING_VIDEO_PLAYER = 5; - int NUM_ENTRIES = 6; + int OPEN_SHARED_VIDEO = 6; + int INVALID_SHARE_URL = 7; + int NUM_ENTRIES = 8; } /** Called to record various user actions on the video player. */
diff --git a/chrome/browser/video_tutorials/internal/BUILD.gn b/chrome/browser/video_tutorials/internal/BUILD.gn index 358c5c6..9db20bb9 100644 --- a/chrome/browser/video_tutorials/internal/BUILD.gn +++ b/chrome/browser/video_tutorials/internal/BUILD.gn
@@ -76,7 +76,6 @@ "android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialCardViewBinder.java", "android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialListCoordinatorImpl.java", "android/java/src/org/chromium/chrome/browser/video_tutorials/list/TutorialListMediator.java", - "android/java/src/org/chromium/chrome/browser/video_tutorials/metrics/VideoTutorialMetrics.java", "android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerCoordinatorImpl.java", "android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediator.java", "android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerProperties.java",
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/VideoTutorialUtils.java b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/VideoTutorialUtils.java index a5f4371db..ef347f7 100644 --- a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/VideoTutorialUtils.java +++ b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/VideoTutorialUtils.java
@@ -4,7 +4,12 @@ package org.chromium.chrome.browser.video_tutorials; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; + import org.chromium.base.Callback; +import org.chromium.base.Log; import java.util.List; import java.util.Locale; @@ -13,6 +18,31 @@ * Handles various feature utility functions associated with video tutorials UI. */ public class VideoTutorialUtils { + private static final String TAG = "VideoTutorialShare"; + + /** + * Creates and launches an Intent that allows sharing a video tutorial. + */ + public static void launchShareIntent(Context context, Tutorial tutorial) { + Intent intent = new Intent(); + intent.setType("video/*"); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setAction(Intent.ACTION_SEND); + intent.putExtra(Intent.EXTRA_TEXT, tutorial.shareUrl); + startShareIntent(context, intent); + } + + private static void startShareIntent(Context context, Intent intent) { + try { + context.startActivity(Intent.createChooser( + intent, context.getString(R.string.share_link_chooser_title))); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "Cannot find activity for sharing"); + } catch (Exception e) { + Log.e(TAG, "Cannot start activity for sharing, exception: " + e); + } + } + /** * Converts a duration string in ms to a human-readable form. * @param videoLengthSeconds The video length in seconds.
diff --git a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediator.java b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediator.java index 4cd3970..d33a6cc 100644 --- a/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediator.java +++ b/chrome/browser/video_tutorials/internal/android/java/src/org/chromium/chrome/browser/video_tutorials/player/VideoPlayerMediator.java
@@ -177,6 +177,7 @@ private void share() { VideoTutorialMetrics.recordUserAction(mTutorial.featureType, UserAction.SHARE); + VideoTutorialUtils.launchShareIntent(mContext, mTutorial); } private void close() {
diff --git a/chrome/browser/web_applications/components/BUILD.gn b/chrome/browser/web_applications/components/BUILD.gn index 44480a9c..d739a25 100644 --- a/chrome/browser/web_applications/components/BUILD.gn +++ b/chrome/browser/web_applications/components/BUILD.gn
@@ -67,6 +67,8 @@ "web_app_tab_helper_base.cc", "web_app_tab_helper_base.h", "web_app_ui_manager.h", + "web_app_uninstallation_via_os_settings_registration.cc", + "web_app_uninstallation_via_os_settings_registration.h", "web_app_url_loader.cc", "web_app_url_loader.h", "web_app_utils.cc", @@ -119,6 +121,7 @@ "web_app_shortcut_win.h", "web_app_shortcuts_menu_win.cc", "web_app_shortcuts_menu_win.h", + "web_app_uninstallation_via_os_settings_registration_win.cc", ] }
diff --git a/chrome/browser/web_applications/components/external_app_install_features.cc b/chrome/browser/web_applications/components/external_app_install_features.cc index 63dd9c87..7592179 100644 --- a/chrome/browser/web_applications/components/external_app_install_features.cc +++ b/chrome/browser/web_applications/components/external_app_install_features.cc
@@ -16,9 +16,6 @@ constexpr const base::Feature* kExternalAppInstallFeatures[] = { &kMigrateDefaultChromeAppToWebAppsGSuite, &kMigrateDefaultChromeAppToWebAppsNonGSuite, -#if defined(OS_CHROMEOS) - &kCameraSystemWebApp -#endif // OS_CHROMEOS }; bool g_always_enabled_for_testing = false; @@ -37,12 +34,6 @@ "MigrateDefaultChromeAppToWebAppsNonGSuite", base::FEATURE_DISABLED_BY_DEFAULT}; -#if defined(OS_CHROMEOS) -// Enable or disables running the Camera App as a System Web App. -const base::Feature kCameraSystemWebApp{"CameraSystemWebApp", - base::FEATURE_DISABLED_BY_DEFAULT}; -#endif // OS_CHROMEOS - bool IsExternalAppInstallFeatureEnabled(base::StringPiece feature_name) { if (g_always_enabled_for_testing) return true;
diff --git a/chrome/browser/web_applications/components/external_app_install_features.h b/chrome/browser/web_applications/components/external_app_install_features.h index e8cbb52e..32eed21ca 100644 --- a/chrome/browser/web_applications/components/external_app_install_features.h +++ b/chrome/browser/web_applications/components/external_app_install_features.h
@@ -15,10 +15,6 @@ extern const base::Feature kMigrateDefaultChromeAppToWebAppsNonGSuite; -#if defined(OS_CHROMEOS) -extern const base::Feature kCameraSystemWebApp; -#endif // OS_CHROMEOS - // Returns the base::Feature in |kExternalAppInstallFeatures| that corresponds // to |feature_name|. Used by external app install configs to gate installation // on features listed in |kExternalAppInstallFeatures|.
diff --git a/chrome/browser/web_applications/components/os_integration_manager.cc b/chrome/browser/web_applications/components/os_integration_manager.cc index c2b80eff..7b99430 100644 --- a/chrome/browser/web_applications/components/os_integration_manager.cc +++ b/chrome/browser/web_applications/components/os_integration_manager.cc
@@ -18,6 +18,7 @@ #include "chrome/browser/web_applications/components/web_app_id.h" #include "chrome/browser/web_applications/components/web_app_shortcut.h" #include "chrome/browser/web_applications/components/web_app_ui_manager.h" +#include "chrome/browser/web_applications/components/web_app_uninstallation_via_os_settings_registration.h" #include "chrome/common/chrome_features.h" #include "content/public/browser/browser_thread.h" @@ -182,10 +183,9 @@ return; } - base::RepeatingCallback<void(OsHookType::Type os_hook, bool completed)> - barrier = base::BindRepeating( - &OsHooksBarrierInfo::Run, - base::Owned(new OsHooksBarrierInfo(std::move(callback)))); + BarrierCallback barrier = base::BindRepeating( + &OsHooksBarrierInfo::Run, + base::Owned(new OsHooksBarrierInfo(std::move(callback)))); CallbackFactory callback_factory = CallbackFactory(barrier); if (os_hooks[OsHookType::kShortcutsMenu] && @@ -231,7 +231,6 @@ callback_factory.CreateCallack(OsHookType::kRunOnOsLogin) .Run(/*completed=*/true); } - // TODO(https://crbug.com/1108109) we should return the result of file handler // unregistration and record errors during unregistration. if (os_hooks[OsHookType::kFileHandlers]) @@ -239,6 +238,15 @@ callback_factory.CreateCallack(OsHookType::kFileHandlers) .Run(/*completed=*/true); + + // There is a chance uninstallation point was created with feature flag + // enabled so we need to clean it up regardless of feature flag state. + if (os_hooks[OsHookType::kUninstallationViaOsSettings] && + ShouldRegisterUninstallationViaOsSettingsWithOs()) { + UnegisterUninstallationViaOsSettingsWithOs(app_id, profile_); + } + callback_factory.CreateCallack(OsHookType::kUninstallationViaOsSettings) + .Run(/*completed=*/true); } void OsIntegrationManager::UpdateOsHooks( @@ -489,6 +497,19 @@ OsHookType::kRunOnOsLogin), /*completed=*/false)); } + + if (options.os_hooks[OsHookType::kUninstallationViaOsSettings] && + base::FeatureList::IsEnabled( + features::kEnableWebAppUninstallFromOsSettings) && + ShouldRegisterUninstallationViaOsSettingsWithOs()) { + RegisterUninstallationViaOsSettingsWithOs( + app_id, registrar_->GetAppShortName(app_id), profile_); + callback_factory.CreateCallack(OsHookType::kUninstallationViaOsSettings) + .Run(/*completed=*/true); + } else { + callback_factory.CreateCallack(OsHookType::kUninstallationViaOsSettings) + .Run(/*completed=*/false); + } } void OsIntegrationManager::OnShortcutsDeleted(const AppId& app_id,
diff --git a/chrome/browser/web_applications/components/web_app_constants.h b/chrome/browser/web_applications/components/web_app_constants.h index 26c7cbf0..f5f433bf 100644 --- a/chrome/browser/web_applications/components/web_app_constants.h +++ b/chrome/browser/web_applications/components/web_app_constants.h
@@ -43,6 +43,7 @@ kShortcuts = 0, kRunOnOsLogin, kShortcutsMenu, + kUninstallationViaOsSettings, kFileHandlers, kMaxValue = kFileHandlers, };
diff --git a/chrome/browser/web_applications/components/web_app_uninstallation_via_os_settings_registration.cc b/chrome/browser/web_applications/components/web_app_uninstallation_via_os_settings_registration.cc new file mode 100644 index 0000000..92c3802 --- /dev/null +++ b/chrome/browser/web_applications/components/web_app_uninstallation_via_os_settings_registration.cc
@@ -0,0 +1,37 @@ +// Copyright 2020 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/web_applications/components/web_app_uninstallation_via_os_settings_registration.h" + +#include "base/check.h" +#include "build/build_config.h" + +namespace web_app { + +#if !defined(OS_WIN) + +// This block defines stub implementations of OS specific methods for +// uninstallation command. Currently, only Windows has their own implementation. +bool ShouldRegisterUninstallationViaOsSettingsWithOs() { + return false; +} + +void RegisterUninstallationViaOsSettingsWithOs(const AppId& app_id, + const std::string& app_name, + Profile* profile) { + DCHECK(ShouldRegisterUninstallationViaOsSettingsWithOs()); + // Stub function for OS's which don't register uninstallation command with the + // OS. +} + +void UnegisterUninstallationViaOsSettingsWithOs(const AppId& app_id, + Profile* profile) { + DCHECK(ShouldRegisterUninstallationViaOsSettingsWithOs()); + // Stub function for OS's which don't register uninstallation command with the + // OS. +} + +#endif // !defined (OS_WIN) + +} // namespace web_app
diff --git a/chrome/browser/web_applications/components/web_app_uninstallation_via_os_settings_registration.h b/chrome/browser/web_applications/components/web_app_uninstallation_via_os_settings_registration.h new file mode 100644 index 0000000..ef9b184 --- /dev/null +++ b/chrome/browser/web_applications/components/web_app_uninstallation_via_os_settings_registration.h
@@ -0,0 +1,39 @@ +// Copyright 2020 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_WEB_APPLICATIONS_COMPONENTS_WEB_APP_UNINSTALLATION_VIA_OS_SETTINGS_REGISTRATION_H_ +#define CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_WEB_APP_UNINSTALLATION_VIA_OS_SETTINGS_REGISTRATION_H_ + +#include <string> + +#include "base/callback.h" +#include "base/files/file_path.h" +#include "build/build_config.h" +#include "chrome/browser/web_applications/components/web_app_id.h" +#include "components/services/app_service/public/cpp/file_handler.h" + +class Profile; + +namespace web_app { + +// True if uninstallation via os settings are managed externally by the +// operating system. Windows is the only Os that support this feature now. +bool ShouldRegisterUninstallationViaOsSettingsWithOs(); + +// Do OS-specific registration for the web app. The registration writes +// an entry to the global uninstall location in the Windows registry. +// Once an entry exists in the given Windows registry, it will be +// displayed in the Windows OS settings so that user can uninstall from +// there like any other native apps. +void RegisterUninstallationViaOsSettingsWithOs(const AppId& app_id, + const std::string& app_name, + Profile* profile); + +// Remove an entry from the Windows uninstall registry. +void UnegisterUninstallationViaOsSettingsWithOs(const AppId& app_id, + Profile* profile); + +} // namespace web_app + +#endif // CHROME_BROWSER_WEB_APPLICATIONS_COMPONENTS_WEB_APP_UNINSTALLATION_VIA_OS_SETTINGS_REGISTRATION_H_
diff --git a/chrome/browser/web_applications/components/web_app_uninstallation_via_os_settings_registration_win.cc b/chrome/browser/web_applications/components/web_app_uninstallation_via_os_settings_registration_win.cc new file mode 100644 index 0000000..9191e195 --- /dev/null +++ b/chrome/browser/web_applications/components/web_app_uninstallation_via_os_settings_registration_win.cc
@@ -0,0 +1,147 @@ +// Copyright 2020 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/web_applications/components/web_app_uninstallation_via_os_settings_registration.h" + +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/hash/md5.h" +#include "base/path_service.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/web_applications/components/web_app_helpers.h" +#include "chrome/browser/web_applications/components/web_app_shortcut_win.h" +#include "chrome/browser/win/uninstallation_via_os_settings.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_paths_internal.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/install_static/install_util.h" +#include "content/public/browser/browser_thread.h" + +namespace web_app { + +namespace { + +// UninstallationViaOsSettingsHelper is a axilliary class for calculate the +// uninstallation registry key by |profile_path| and |app_id|. +class UninstallationViaOsSettingsHelper { + public: + UninstallationViaOsSettingsHelper(const base::FilePath& profile_path, + const web_app::AppId& app_id) + : profile_path_(profile_path), app_id_(app_id) {} + + UninstallationViaOsSettingsHelper( + const UninstallationViaOsSettingsHelper& other) = delete; + UninstallationViaOsSettingsHelper& operator=( + const UninstallationViaOsSettingsHelper& other) = delete; + + // Returns an identifier for the web app installed for the + // profile at |profile_path|. The identifier is guaranteed to be unique among + // all web apps installed in all profiles across all browser installations + // for the user. + std::wstring GetUninstallStringKey() const { + // We don't normalize (lower/upper) cases here mainly because people + // don't change shortcut file case. If anyone changes the file name + // or case, then it is the user's responsibility for cleanup the apps. + // (we assume he/she is a power user when could change + // the system created file.). + std::wstring key = + base::StringPrintf(L"%ls_%ls", profile_path_.value().c_str(), + base::ASCIIToWide(app_id_).c_str()); + base::MD5Digest digest; + base::MD5Sum(key.c_str(), key.size() * sizeof(wchar_t), &digest); + return base::ASCIIToUTF16(base::MD5DigestToBase16(digest)); + } + + base::CommandLine GetCommandLine() const { + base::FilePath full_exe_name; + + base::PathService::Get(base::FILE_EXE, &full_exe_name); + base::CommandLine uninstall_commandline(full_exe_name); + + // If the current user data directory isn't default, the uninstall + // string should have it. + base::FilePath default_user_data_dir; + base::FilePath user_data_dir; + if (chrome::GetDefaultUserDataDirectory(&default_user_data_dir) && + base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir) && + default_user_data_dir != user_data_dir) { + uninstall_commandline.AppendSwitchNative(switches::kUserDataDir, + user_data_dir.value()); + } + + base::FilePath profile_name = profile_path_.BaseName(); + uninstall_commandline.AppendSwitchNative(switches::kProfileDirectory, + profile_name.value()); + + uninstall_commandline.AppendSwitchASCII(switches::kUninstallAppId, app_id_); + + // e.g. uninstall_commandline + // "C:\Users\account\AppData\Local\Microsoft\Chromium\ + // Application\chrome.exe" + // --user-data-dir=c:\users\account\appdata\local\chromium\ + // CustomUserData (optional) + // --profile-directory=Default + // --uninstall-app-id=dadckofbdkccdemmkofcgkcbpjbnafgf + return uninstall_commandline; + } + + base::FilePath GetWebAppIconPath(const std::string& app_name) { + base::FilePath web_app_icon_dir = GetOsIntegrationResourcesDirectoryForApp( + profile_path_, app_id_, GURL()); + + return internals::GetIconFilePath(web_app_icon_dir, + base::UTF8ToUTF16(app_name)); + } + + private: + const base::FilePath profile_path_; + const AppId app_id_; +}; + +} // namespace + +bool ShouldRegisterUninstallationViaOsSettingsWithOs() { + return true; +} + +void RegisterUninstallationViaOsSettingsWithOs(const AppId& app_id, + const std::string& app_name, + Profile* profile) { + DCHECK(ShouldRegisterUninstallationViaOsSettingsWithOs()); + + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + UninstallationViaOsSettingsHelper uninstall_os_settings_helper( + profile->GetPath(), app_id); + base::string16 hash_key = + uninstall_os_settings_helper.GetUninstallStringKey(); + + auto uninstall_commandline = uninstall_os_settings_helper.GetCommandLine(); + base::FilePath icon_path = + uninstall_os_settings_helper.GetWebAppIconPath(app_name); + base::string16 product_name = install_static::GetChromeInstallSubDirectory(); + + ::RegisterUninstallationViaOsSettings(hash_key, base::UTF8ToUTF16(app_name), + product_name, uninstall_commandline, + icon_path); +} + +void UnegisterUninstallationViaOsSettingsWithOs(const AppId& app_id, + Profile* profile) { + DCHECK(ShouldRegisterUninstallationViaOsSettingsWithOs()); + + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + UninstallationViaOsSettingsHelper uninstall_os_settings_helper( + profile->GetPath(), app_id); + base::string16 hash_key = + uninstall_os_settings_helper.GetUninstallStringKey(); + ::UnregisterUninstallationViaOsSettings(hash_key); +} + +} // namespace web_app
diff --git a/chrome/browser/web_applications/pending_app_install_task.cc b/chrome/browser/web_applications/pending_app_install_task.cc index 5c010fd..c879884 100644 --- a/chrome/browser/web_applications/pending_app_install_task.cc +++ b/chrome/browser/web_applications/pending_app_install_task.cc
@@ -277,6 +277,7 @@ // TODO(crbug.com/1087219): Determine if |register_file_handlers| should be // configured from somewhere else rather than always true. options.os_hooks[OsHookType::kFileHandlers] = true; + options.os_hooks[OsHookType::kUninstallationViaOsSettings] = true; os_integration_manager_->InstallOsHooks( app_id,
diff --git a/chrome/browser/web_applications/system_web_app_manager.cc b/chrome/browser/web_applications/system_web_app_manager.cc index 56d0eab..2ee6b89 100644 --- a/chrome/browser/web_applications/system_web_app_manager.cc +++ b/chrome/browser/web_applications/system_web_app_manager.cc
@@ -56,7 +56,6 @@ #include "chrome/browser/chromeos/web_applications/scanning_system_web_app_info.h" #include "chrome/browser/chromeos/web_applications/terminal_source.h" #include "chrome/browser/chromeos/web_applications/terminal_system_web_app_info.h" -#include "chrome/browser/web_applications/components/external_app_install_features.h" #include "chromeos/components/camera_app_ui/url_constants.h" #include "chromeos/components/connectivity_diagnostics/url_constants.h" #include "chromeos/components/help_app_ui/url_constants.h" @@ -338,7 +337,8 @@ case SystemAppType::SETTINGS: return true; case SystemAppType::CAMERA: - return base::FeatureList::IsEnabled(web_app::kCameraSystemWebApp); + return base::FeatureList::IsEnabled( + chromeos::features::kCameraSystemWebApp); case SystemAppType::TERMINAL: return true; case SystemAppType::MEDIA:
diff --git a/chrome/browser/web_applications/web_app_install_finalizer.cc b/chrome/browser/web_applications/web_app_install_finalizer.cc index eb75e2a9..3e3126a1 100644 --- a/chrome/browser/web_applications/web_app_install_finalizer.cc +++ b/chrome/browser/web_applications/web_app_install_finalizer.cc
@@ -329,8 +329,16 @@ void WebAppInstallFinalizer::UninstallWebApp(const AppId& app_id, UninstallWebAppCallback callback) { registrar().NotifyWebAppUninstalled(app_id); - os_integration_manager().UninstallAllOsHooks(app_id, base::DoNothing()); + os_integration_manager().UninstallAllOsHooks( + app_id, base::BindOnce(&WebAppInstallFinalizer::OnUninstallOsHooks, + weak_ptr_factory_.GetWeakPtr(), app_id, + std::move(callback))); +} +void WebAppInstallFinalizer::OnUninstallOsHooks( + const AppId& app_id, + UninstallWebAppCallback callback, + OsHooksResults os_hooks_info) { ScopedRegistryUpdate update(registry_controller().AsWebAppSyncBridge()); update->DeleteApp(app_id);
diff --git a/chrome/browser/web_applications/web_app_install_finalizer.h b/chrome/browser/web_applications/web_app_install_finalizer.h index 7064e15..88fd51b 100644 --- a/chrome/browser/web_applications/web_app_install_finalizer.h +++ b/chrome/browser/web_applications/web_app_install_finalizer.h
@@ -11,6 +11,7 @@ #include "base/memory/weak_ptr.h" #include "chrome/browser/web_applications/components/install_finalizer.h" +#include "chrome/browser/web_applications/components/os_integration_manager.h" #include "chrome/browser/web_applications/components/web_app_constants.h" #include "chrome/browser/web_applications/components/web_application_info.h" #include "third_party/skia/include/core/SkBitmap.h" @@ -91,6 +92,9 @@ std::string old_name, const WebApplicationInfo& web_app_info, bool success); + void OnUninstallOsHooks(const AppId& app_id, + UninstallWebAppCallback callback, + OsHooksResults os_hooks_info); WebAppRegistrar& GetWebAppRegistrar() const;
diff --git a/chrome/browser/web_applications/web_app_install_task.cc b/chrome/browser/web_applications/web_app_install_task.cc index 8aef81ea..5ded2a3 100644 --- a/chrome/browser/web_applications/web_app_install_task.cc +++ b/chrome/browser/web_applications/web_app_install_task.cc
@@ -817,6 +817,7 @@ // configured from somewhere else rather than always true. options.os_hooks[OsHookType::kFileHandlers] = true; } + options.os_hooks[OsHookType::kUninstallationViaOsSettings] = true; auto hooks_created_callback = base::BindOnce(&WebAppInstallTask::OnOsHooksCreated, GetWeakPtr(), web_app_info->open_as_window, app_id);
diff --git a/chrome/browser/win/uninstallation_via_os_settings.cc b/chrome/browser/win/uninstallation_via_os_settings.cc new file mode 100644 index 0000000..2dde7cab --- /dev/null +++ b/chrome/browser/win/uninstallation_via_os_settings.cc
@@ -0,0 +1,83 @@ +// Copyright 2020 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/win/uninstallation_via_os_settings.h" + +#include <windows.h> + +#include "base/check_op.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/win/registry.h" +#include "base/win/windows_types.h" + +namespace { + +// Win32 App registry entry for uninstallation. System detects the registry +// and show its App for App or Remove Settings. +constexpr wchar_t kUninstallRegistryKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall"; + +} // namespace + +bool RegisterUninstallationViaOsSettings( + const std::wstring& key, + const std::wstring& display_name, + const std::wstring& publisher, + const base::CommandLine& uninstall_command, + const base::FilePath& icon_path) { + DCHECK(!key.empty()); + DCHECK(!display_name.empty()); + DCHECK(!publisher.empty()); + DCHECK(!uninstall_command.GetProgram().empty()); + + base::win::RegKey uninstall_reg_key; + LONG result = uninstall_reg_key.Open(HKEY_CURRENT_USER, kUninstallRegistryKey, + KEY_CREATE_SUB_KEY); + if (result != ERROR_SUCCESS) + return false; + + base::win::RegKey uninstall_reg_entry_key; + DWORD disposition; + + result = uninstall_reg_entry_key.CreateWithDisposition( + uninstall_reg_key.Handle(), key.c_str(), &disposition, KEY_WRITE); + if (result != ERROR_SUCCESS) + return false; + + if (disposition != REG_CREATED_NEW_KEY) + return false; + + // Add Uninstall values. Windows will show the icon at index 0 + // if no index is specified in this value. + uninstall_reg_entry_key.WriteValue(L"DisplayIcon", icon_path.value().c_str()); + uninstall_reg_entry_key.WriteValue(L"DisplayName", display_name.c_str()); + uninstall_reg_entry_key.WriteValue(L"DisplayVersion", L"1.0"); + uninstall_reg_entry_key.WriteValue(L"ApplicationVersion", L"1.0"); + + static constexpr wchar_t kDateFormat[] = L"yyyyyMMdd"; + wchar_t date_str[base::size(kDateFormat)] = {}; + int len = ::GetDateFormatW(LOCALE_INVARIANT, 0, nullptr, kDateFormat, + date_str, base::size(date_str)); + if (len) + uninstall_reg_entry_key.WriteValue(L"InstallDate", date_str); + + uninstall_reg_entry_key.WriteValue(L"Publisher", publisher.c_str()); + uninstall_reg_entry_key.WriteValue( + L"UninstallString", uninstall_command.GetCommandLineString().c_str()); + uninstall_reg_entry_key.WriteValue(L"NoRepair", 1); + uninstall_reg_entry_key.WriteValue(L"NoModify", 1); + + return true; +} + +void UnregisterUninstallationViaOsSettings(const std::wstring& name) { + base::win::RegKey uninstall_reg_key; + LONG result = uninstall_reg_key.Open(HKEY_CURRENT_USER, kUninstallRegistryKey, + KEY_QUERY_VALUE); + if (result != ERROR_SUCCESS) + return; + + uninstall_reg_key.DeleteKey(name.c_str()); +}
diff --git a/chrome/browser/win/uninstallation_via_os_settings.h b/chrome/browser/win/uninstallation_via_os_settings.h new file mode 100644 index 0000000..4146d6c --- /dev/null +++ b/chrome/browser/win/uninstallation_via_os_settings.h
@@ -0,0 +1,40 @@ +// Copyright 2020 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_WIN_UNINSTALLATION_VIA_OS_SETTINGS_H_ +#define CHROME_BROWSER_WIN_UNINSTALLATION_VIA_OS_SETTINGS_H_ + +#include <string> + +namespace base { + +class CommandLine; +class FilePath; + +} // namespace base + +// Registers an uninstall command for a program |key| with the +// Windows Programs and Features control panel. +// The API doesn't allow same duplicate entry for the |key|. If the caller +// updates the uninstall string, it should delete the previous one first. +// |key|: A command ID that is a representative of the application. +// |display_name|: Display name that will be shown on App or Remove Program. +// |publisher|: URL Origin name of the App where it is installed from. +// |uninstall_command|: A command line that has command string that is run +// when the uninstall command is executed for the target application. +// |icon_path|: An icon that will be shown with name in the uninstall UI +// such as Program and Features. +// The system default icon will be used if |icon_path| is empty. +bool RegisterUninstallationViaOsSettings( + const std::wstring& key, + const std::wstring& display_name, + const std::wstring& publisher, + const base::CommandLine& uninstall_command, + const base::FilePath& icon_path); + +// Removes the uninstall command for the program |key|. |key| should be +// same with what is used for RegisterUninstallationViaOsSettings for the entry. +void UnregisterUninstallationViaOsSettings(const std::wstring& key); + +#endif // CHROME_BROWSER_WIN_UNINSTALLATION_VIA_OS_SETTINGS_H_
diff --git a/chrome/browser/win/uninstallation_via_os_settings_unittest.cc b/chrome/browser/win/uninstallation_via_os_settings_unittest.cc new file mode 100644 index 0000000..91d16a9 --- /dev/null +++ b/chrome/browser/win/uninstallation_via_os_settings_unittest.cc
@@ -0,0 +1,127 @@ +// Copyright 2020 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/win/uninstallation_via_os_settings.h" + +#include <string> + +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/test/test_reg_util_win.h" +#include "base/win/registry.h" +#include "base/win/scoped_com_initializer.h" +#include "chrome/common/chrome_switches.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +constexpr wchar_t kUninstallRegistryKey[] = + L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\"; + +class RegisterUninstallationViaOsSettingsTest : public testing::Test { + protected: + void SetUp() override { + const HKEY kRoot = HKEY_CURRENT_USER; + ASSERT_NO_FATAL_FAILURE(registry_override_manager_.OverrideRegistry(kRoot)); + ASSERT_TRUE( + base::win::RegKey(kRoot, kUninstallRegistryKey, KEY_CREATE_SUB_KEY) + .Valid()); + + ASSERT_EQ(uninstall_key_.Create(kRoot, kUninstallRegistryKey, KEY_WRITE), + ERROR_SUCCESS); + + uninstall_commandline_ = + std::make_unique<base::CommandLine>(base::FilePath(L"chrome.exe")); + uninstall_commandline_->AppendSwitchNative(switches::kProfileDirectory, + L"Default"); + uninstall_commandline_->AppendSwitchNative(switches::kUninstallAppId, + L"fhkpfpfifagoflnfpbdgegnghg"); + } + + base::win::ScopedCOMInitializer com_initializer_; + base::win::RegKey uninstall_reg_key_; + + const std::wstring register_key_ = L"12cde1ab"; + std::unique_ptr<base::CommandLine> uninstall_commandline_; + base::win::RegKey uninstall_key_; + registry_util::RegistryOverrideManager registry_override_manager_; +}; + +} // namespace + +// Test that the uninstall registry key are inserted during shortcut +// creation on the Start Menu only. +TEST_F(RegisterUninstallationViaOsSettingsTest, DuplicateKey) { + ASSERT_TRUE(RegisterUninstallationViaOsSettings( + register_key_, L"Display_Name", L"Chromium", *uninstall_commandline_, + base::FilePath(L"C:\\users\\account\\AppData\\Local\\Chromium\\User " + "Data\\Default\\Icons\\icon.ico"))); + + ASSERT_EQ(uninstall_key_.OpenKey(register_key_.c_str(), KEY_QUERY_VALUE), + ERROR_SUCCESS); + + // It should be failed for duplicate key. + ASSERT_FALSE(RegisterUninstallationViaOsSettings( + register_key_, L"Display_Name", L"Chromium", *uninstall_commandline_, + base::FilePath(L"C:\\users\\account\\AppData\\Local\\Chromium\\User " + "Data\\Default\\Icons\\icon.ico"))); + + UnregisterUninstallationViaOsSettings(register_key_); + + ASSERT_FALSE( + base::win::RegKey( + HKEY_CURRENT_USER, + (std::wstring(kUninstallRegistryKey) + register_key_).c_str(), + KEY_QUERY_VALUE) + .Valid()); +} + +TEST_F(RegisterUninstallationViaOsSettingsTest, RegValues) { + // Registry entry is inserted as it has --app-id argument and + // uninstall_string property set. + std::wstring display_name = L"Display_Name"; + std::wstring publisher = L"Chromium"; + + base::FilePath icon_path( + L"C:\\users\\account\\AppData\\Local\\Chromium\\User " + "Data\\Default\\Icons\\icon.ico"); + + ASSERT_TRUE(RegisterUninstallationViaOsSettings( + register_key_, display_name, publisher, *uninstall_commandline_, + icon_path)); + + ASSERT_EQ(uninstall_key_.OpenKey(register_key_.c_str(), KEY_QUERY_VALUE), + ERROR_SUCCESS); + + std::wstring value; + ASSERT_EQ(uninstall_key_.ReadValue(L"DisplayName", &value), ERROR_SUCCESS); + EXPECT_EQ(value, display_name); + + ASSERT_EQ(uninstall_key_.ReadValue(L"DisplayVersion", &value), ERROR_SUCCESS); + EXPECT_EQ(value, L"1.0"); + + ASSERT_EQ(uninstall_key_.ReadValue(L"ApplicationVersion", &value), + ERROR_SUCCESS); + EXPECT_EQ(value, L"1.0"); + + ASSERT_EQ(uninstall_key_.ReadValue(L"InstallDate", &value), ERROR_SUCCESS); + + ASSERT_EQ(uninstall_key_.ReadValue(L"UninstallString", &value), + ERROR_SUCCESS); + EXPECT_EQ(value, + L"chrome.exe --profile-directory=Default " + "--uninstall-app-id=fhkpfpfifagoflnfpbdgegnghg"); + + DWORD int_value; + ASSERT_EQ(uninstall_key_.ReadValueDW(L"NoRepair", &int_value), ERROR_SUCCESS); + EXPECT_EQ(int_value, 1U); + + ASSERT_EQ(uninstall_key_.ReadValueDW(L"NoModify", &int_value), ERROR_SUCCESS); + EXPECT_EQ(int_value, 1U); + + ASSERT_EQ(uninstall_key_.ReadValue(L"DisplayIcon", &value), ERROR_SUCCESS); + EXPECT_EQ(value, icon_path.value()); + + UnregisterUninstallationViaOsSettings(register_key_); +}
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt index 858e291..31773102 100644 --- a/chrome/build/linux.pgo.txt +++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@ -chrome-linux-master-1605290311-c0a8bccef266e50cf7d2fe9fae22d951549688a8.profdata +chrome-linux-master-1605418474-d40e0a66ac214c017c874665540974720359ae8b.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt index a7bd8471..43b16ea 100644 --- a/chrome/build/win32.pgo.txt +++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@ -chrome-win32-master-1605278951-6acf5f1c81bf8e5b853d44ef9575d8aab067a4b3.profdata +chrome-win32-master-1605301167-f2e539f21f91663031225bb0d3160662a773cdbd.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt index 568cfbc2..aca24a0 100644 --- a/chrome/build/win64.pgo.txt +++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@ -chrome-win64-master-1605268075-daeeb1bc6080c8b4d9f48b3dab7bc22bf5a02dcb.profdata +chrome-win64-master-1605322732-f3f9a6c28d3c2189d459e96b0d723f5210a3cdc8.profdata
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc index eee1a69..39a2838 100644 --- a/chrome/common/chrome_features.cc +++ b/chrome/common/chrome_features.cc
@@ -352,6 +352,10 @@ "EnableIncognitoShortcutOnDesktop", base::FEATURE_DISABLED_BY_DEFAULT}; #endif +// Enable web app uninstallation from Windows settings or control panel. +const base::Feature kEnableWebAppUninstallFromOsSettings{ + "EnableWebAppUninstallFromOsSettings", base::FEATURE_DISABLED_BY_DEFAULT}; + #if defined(OS_MAC) const base::Feature kEnterpriseReportingApiKeychainRecreation{ "EnterpriseReportingApiKeychainRecreation",
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h index 738a659..2ed72720 100644 --- a/chrome/common/chrome_features.h +++ b/chrome/common/chrome_features.h
@@ -234,6 +234,9 @@ extern const base::Feature kEnableIncognitoShortcutOnDesktop; #endif +COMPONENT_EXPORT(CHROME_FEATURES) +extern const base::Feature kEnableWebAppUninstallFromOsSettings; + #if !defined(OS_ANDROID) COMPONENT_EXPORT(CHROME_FEATURES) extern const base::Feature kEnterpriseRealtimeExtensionRequest;
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc index 7a14aa6..4a0793e 100644 --- a/chrome/common/chrome_switches.cc +++ b/chrome/common/chrome_switches.cc
@@ -764,6 +764,9 @@ // Runs un-installation steps that were done by chrome first-run. const char kUninstall[] = "uninstall"; +// Specifies that the WebApp with the specified id should be uninstalled. +const char kUninstallAppId[] = "uninstall-app-id"; + // Specifies the version of the Progressive-Web-App launcher that launched // Chrome, used to determine whether to update all launchers. // NOTE: changing this switch requires adding legacy handling for the previous
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h index f6f11272e..6d6a866e 100644 --- a/chrome/common/chrome_switches.h +++ b/chrome/common/chrome_switches.h
@@ -236,6 +236,7 @@ extern const char kPwaLauncherVersion[]; extern const char kShowIcons[]; extern const char kUninstall[]; +extern const char kUninstallAppId[]; #endif // defined(OS_WIN) #if BUILDFLAG(ENABLE_PRINT_PREVIEW) && !defined(OFFICIAL_BUILD)
diff --git a/chrome/credential_provider/extension/BUILD.gn b/chrome/credential_provider/extension/BUILD.gn index 60bb635..e6aa7bb 100644 --- a/chrome/credential_provider/extension/BUILD.gn +++ b/chrome/credential_provider/extension/BUILD.gn
@@ -72,7 +72,7 @@ ":version", "../eventlog:gcp_eventlog_messages", "../gaiacp:common", - "../gaiacp:policies", + "../gaiacp:tasks", "../gaiacp:util", "//base", "//components/crash/core/app:crash_export_thunks",
diff --git a/chrome/credential_provider/extension/extension_main.cc b/chrome/credential_provider/extension/extension_main.cc index 3858456..af11aeb 100644 --- a/chrome/credential_provider/extension/extension_main.cc +++ b/chrome/credential_provider/extension/extension_main.cc
@@ -14,6 +14,8 @@ #include "chrome/credential_provider/extension/os_service_manager.h" #include "chrome/credential_provider/extension/service.h" #include "chrome/credential_provider/extension/task_manager.h" +#include "chrome/credential_provider/gaiacp/experiments_fetcher.h" +#include "chrome/credential_provider/gaiacp/experiments_manager.h" #include "chrome/credential_provider/gaiacp/gem_device_details_manager.h" #include "chrome/credential_provider/gaiacp/logging.h" #include "chrome/credential_provider/gaiacp/reg_utils.h" @@ -38,6 +40,13 @@ "UploadDeviceDetails", credential_provider::GemDeviceDetailsManager:: UploadDeviceDetailsTaskCreator()); } + + // Task to fetch experiments for all GCPW users. + if (credential_provider::ExperimentsManager::Get()->ExperimentsEnabled()) { + credential_provider::extension::TaskManager::Get()->RegisterTask( + "FetchExperiments", credential_provider::ExperimentsFetcher:: + GetFetchExperimentsTaskCreator()); + } } int APIENTRY wWinMain(HINSTANCE hInstance,
diff --git a/chrome/credential_provider/gaiacp/BUILD.gn b/chrome/credential_provider/gaiacp/BUILD.gn index b883d6b..d4bbb0d 100644 --- a/chrome/credential_provider/gaiacp/BUILD.gn +++ b/chrome/credential_provider/gaiacp/BUILD.gn
@@ -99,14 +99,18 @@ ] } -# This component is used for the cloud policies feature. -component("policies") { - output_name = "gcpw_policies" +# This component is used for the periodically executed tasks. +component("tasks") { + output_name = "gcpw_tasks" sources = [ "device_policies.cc", "device_policies.h", "device_policies_manager.cc", "device_policies_manager.h", + "experiments_fetcher.cc", + "experiments_fetcher.h", + "experiments_manager.cc", + "experiments_manager.h", "gcpw_version.cc", "gcpw_version.h", "user_policies.cc", @@ -114,12 +118,13 @@ "user_policies_manager.cc", "user_policies_manager.h", ] - defines = [ "IS_GCPW_POLICIES_IMPL" ] + defines = [ "IS_GCPW_TASKS_IMPL" ] deps = [ ":common", ":util", "../extension:extension_lib", "//base", + "//chrome/common:version_header", "//chrome/updater/protos:omaha_proto", "//components/policy/proto", "//url", @@ -175,9 +180,9 @@ public_deps = [ ":common" ] deps = [ ":gaia_credential_provider_idl", - ":policies", ":static_resources", ":string_resources", + ":tasks", ":util", "../eventlog:gcp_eventlog_messages", "../extension:common", @@ -274,7 +279,7 @@ deps = [ ":common", ":gaiacp_lib", - ":policies", + ":tasks", ":util", ":version", "//base",
diff --git a/chrome/credential_provider/gaiacp/device_policies.h b/chrome/credential_provider/gaiacp/device_policies.h index 660fb58..da30813 100644 --- a/chrome/credential_provider/gaiacp/device_policies.h +++ b/chrome/credential_provider/gaiacp/device_policies.h
@@ -14,7 +14,7 @@ namespace credential_provider { // Structure to hold the policies for the device. -struct COMPONENT_EXPORT(GCPW_POLICIES) DevicePolicies { +struct COMPONENT_EXPORT(GCPW_TASKS) DevicePolicies { // Controls whether MDM enrollment is enabled/disabled. bool enable_dm_enrollment;
diff --git a/chrome/credential_provider/gaiacp/device_policies_manager.h b/chrome/credential_provider/gaiacp/device_policies_manager.h index cc15dc5..7a6d7d5 100644 --- a/chrome/credential_provider/gaiacp/device_policies_manager.h +++ b/chrome/credential_provider/gaiacp/device_policies_manager.h
@@ -11,7 +11,7 @@ namespace credential_provider { // Manager used to fetch user policies from GCPW backends. -class COMPONENT_EXPORT(GCPW_POLICIES) DevicePoliciesManager { +class COMPONENT_EXPORT(GCPW_TASKS) DevicePoliciesManager { public: // Get the user policies manager instance. static DevicePoliciesManager* Get();
diff --git a/chrome/credential_provider/gaiacp/experiments_fetcher.cc b/chrome/credential_provider/gaiacp/experiments_fetcher.cc new file mode 100644 index 0000000..94def07b --- /dev/null +++ b/chrome/credential_provider/gaiacp/experiments_fetcher.cc
@@ -0,0 +1,236 @@ +// Copyright 2020 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/credential_provider/gaiacp/experiments_fetcher.h" + +#include "base/bind.h" +#include "base/files/file.h" +#include "base/files/file_enumerator.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/path_service.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "chrome/common/chrome_version.h" +#include "chrome/credential_provider/gaiacp/experiments_manager.h" +#include "chrome/credential_provider/gaiacp/gcp_utils.h" +#include "chrome/credential_provider/gaiacp/logging.h" +#include "chrome/credential_provider/gaiacp/reg_utils.h" + +namespace credential_provider { +namespace { + +// HTTP endpoint on the GCPW service to fetch experiments. +const wchar_t kGcpwServiceFetchExperimentsPath[] = L"/v1/experiments"; + +// Default timeout when trying to make requests to the GCPW service. +const base::TimeDelta kDefaultFetchExperimentsRequestTimeout = + base::TimeDelta::FromMilliseconds(5000); + +// Maximum number of retries if a HTTP call to the backend fails. +constexpr unsigned int kMaxNumHttpRetries = 1; + +// HTTP query parameters for fetch experiments RPC. +const char kObfuscatedUserIdKey[] = "obfuscated_gaia_id"; +const char kGcpwVersionKey[] = "gcpw_version"; +const char kDmTokenKey[] = "dm_token"; +const char kDeviceResourceIdKey[] = "device_resource_id"; +const char kFeaturesKey[] = "feature"; + +// Defines a task that is called by the ESA to perform the experiments fetch +// operation. +class ExperimentsFetchTask : public extension::Task { + public: + static std::unique_ptr<extension::Task> Create() { + std::unique_ptr<extension::Task> esa_task(new ExperimentsFetchTask()); + return esa_task; + } + + // ESA calls this to retrieve a configuration for the task execution. Return + // a default config for now. + extension::Config GetConfig() final { return extension::Config(); } + + // ESA calls this to set all the user-device contexts for the execution of the + // task. + HRESULT SetContext(const std::vector<extension::UserDeviceContext>& c) final { + context_ = c; + return S_OK; + } + + // ESA calls execute function to perform the actual task. + HRESULT Execute() final { + HRESULT task_status = S_OK; + for (const auto& c : context_) { + HRESULT hr = ExperimentsFetcher::Get()->FetchAndStoreExperiments(c); + if (FAILED(hr)) { + LOGFN(ERROR) << "Failed fetching experiments for " << c.user_sid + << ". hr=" << putHR(hr); + task_status = hr; + } + } + + return task_status; + } + + private: + std::vector<extension::UserDeviceContext> context_; +}; + +// Builds the request dictionary to fetch experiments from the backend. If +// |dm_token| is empty, it isn't added into request. If user id isn't found for +// the given |sid|, returns an empty dictionary. +std::unique_ptr<base::DictionaryValue> GetExperimentsRequestDict( + const base::string16& sid, + const base::string16& device_resource_id, + const base::string16& dm_token) { + std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue); + + base::string16 user_id; + + HRESULT status = GetIdFromSid(sid.c_str(), &user_id); + if (FAILED(status)) { + LOGFN(ERROR) << "Could not get user id from sid " << sid; + return nullptr; + } + + dict->SetString(kObfuscatedUserIdKey, user_id); + + if (!dm_token.empty()) { + dict->SetString(kDmTokenKey, dm_token); + } + dict->SetString(kDeviceResourceIdKey, device_resource_id); + + dict->SetString(kGcpwVersionKey, TEXT(CHROME_VERSION_STRING)); + + auto keys = std::make_unique<base::ListValue>(); + for (auto& experiment : ExperimentsManager::Get()->GetExperimentsList()) + keys->AppendString(experiment); + + dict->Set(kFeaturesKey, std::move(keys)); + + return dict; +} + +} // namespace + +GURL ExperimentsFetcher::GetExperimentsUrl() { + GURL gcpw_service_url = GetGcpwServiceUrl(); + return gcpw_service_url.Resolve(kGcpwServiceFetchExperimentsPath); +} + +// static +ExperimentsFetcher* ExperimentsFetcher::Get() { + return *GetInstanceStorage(); +} + +// static +ExperimentsFetcher** ExperimentsFetcher::GetInstanceStorage() { + static ExperimentsFetcher instance; + static ExperimentsFetcher* instance_storage = &instance; + return &instance_storage; +} + +// static +extension::TaskCreator ExperimentsFetcher::GetFetchExperimentsTaskCreator() { + return base::BindRepeating(&ExperimentsFetchTask::Create); +} + +ExperimentsFetcher::ExperimentsFetcher() {} + +ExperimentsFetcher::~ExperimentsFetcher() = default; + +HRESULT ExperimentsFetcher::FetchAndStoreExperiments( + const extension::UserDeviceContext& context) { + return FetchAndStoreExperimentsInternal( + context.user_sid, /* access_token= */ "", + GetExperimentsRequestDict(context.user_sid, context.device_resource_id, + context.dm_token)); +} + +HRESULT ExperimentsFetcher::FetchAndStoreExperiments( + const base::string16& sid, + const std::string& access_token) { + HRESULT hr = FetchAndStoreExperimentsInternal( + sid, access_token, + GetExperimentsRequestDict(sid, GetUserDeviceResourceId(sid), + /* dm_token= */ L"")); + return hr; +} + +HRESULT ExperimentsFetcher::FetchAndStoreExperimentsInternal( + const base::string16& sid, + const std::string& access_token, + std::unique_ptr<base::DictionaryValue> request_dict) { + if (!request_dict) { + LOGFN(ERROR) << "Request dictionary is null"; + return E_FAIL; + } + + // Make the fetch experiments HTTP request. + base::Optional<base::Value> request_result; + HRESULT hr = WinHttpUrlFetcher::BuildRequestAndFetchResultFromHttpService( + GetExperimentsUrl(), access_token, {}, *request_dict.get(), + kDefaultFetchExperimentsRequestTimeout, kMaxNumHttpRetries, + &request_result); + + if (FAILED(hr)) { + LOGFN(ERROR) << "BuildRequestAndFetchResultFromHttpService hr=" + << putHR(hr); + return hr; + } + + std::string experiments_data; + if (request_result && request_result->is_dict()) { + if (!base::JSONWriter::Write(*request_result, &experiments_data)) { + LOGFN(ERROR) << "base::JSONWriter::Write failed"; + return E_FAIL; + } + } else { + LOGFN(ERROR) << "Failed to parse experiments response!"; + return E_FAIL; + } + + uint32_t open_flags = base::File::FLAG_CREATE_ALWAYS | + base::File::FLAG_WRITE | + base::File::FLAG_EXCLUSIVE_WRITE; + std::unique_ptr<base::File> experiments_file = GetOpenedFileForUser( + sid, open_flags, kGcpwExperimentsDirectory, kGcpwUserExperimentsFileName); + if (!experiments_file) { + LOGFN(ERROR) << "Failed to open " << kGcpwUserExperimentsFileName + << " file."; + return E_FAIL; + } + + int num_bytes_written = experiments_file->Write(0, experiments_data.c_str(), + experiments_data.size()); + + experiments_file.reset(); + + if (size_t(num_bytes_written) != experiments_data.size()) { + LOGFN(ERROR) << "Failed writing experiments data to file! Only " + << num_bytes_written << " bytes written out of " + << experiments_data.size(); + return E_FAIL; + } + + base::Time fetch_time = base::Time::Now(); + base::string16 fetch_time_millis = base::NumberToString16( + fetch_time.ToDeltaSinceWindowsEpoch().InMilliseconds()); + + if (!ExperimentsManager::Get()->ReloadExperiments(sid)) { + LOGFN(ERROR) << "Error when loading experiments for user with sid " << sid; + } + + // Store the fetch time so we know whether a refresh is needed. + SetUserProperty(sid, kLastUserExperimentsRefreshTimeRegKey, + fetch_time_millis); + + return S_OK; +} + +} // namespace credential_provider
diff --git a/chrome/credential_provider/gaiacp/experiments_fetcher.h b/chrome/credential_provider/gaiacp/experiments_fetcher.h new file mode 100644 index 0000000..0f2776b --- /dev/null +++ b/chrome/credential_provider/gaiacp/experiments_fetcher.h
@@ -0,0 +1,59 @@ +// Copyright 2020 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_CREDENTIAL_PROVIDER_GAIACP_EXPERIMENTS_FETCHER_H_ +#define CHROME_CREDENTIAL_PROVIDER_GAIACP_EXPERIMENTS_FETCHER_H_ + +#include "base/component_export.h" +#include "base/values.h" +#include "base/win/windows_types.h" +#include "chrome/credential_provider/extension/task_manager.h" +#include "url/gurl.h" + +namespace credential_provider { + +// Fetcher used to fetch experiments from GCPW backends. +class COMPONENT_EXPORT(GCPW_TASKS) ExperimentsFetcher { + public: + // Gets the singleton instance. + static ExperimentsFetcher* Get(); + + // Provides the GCPW extension with a TaskCreator which can be used to create + // a task for fetching experiments. + static extension::TaskCreator GetFetchExperimentsTaskCreator(); + + // Fetches the experiments for the user implied with the |access_token| from + // the GCPW backend and saves it in file storage replacing any previously + // fetched versions. + HRESULT FetchAndStoreExperiments(const base::string16& sid, + const std::string& access_token); + + // Fetches the experiments for the user-device |context| provided by the GCPW + // extension service from the GCPW backend and saves it in file storage + // replacing any previously fetched versions. + HRESULT FetchAndStoreExperiments(const extension::UserDeviceContext& context); + + // Returns the experiments fetch endpoint. + GURL GetExperimentsUrl(); + + private: + // Returns the storage used for the instance pointer. + static ExperimentsFetcher** GetInstanceStorage(); + + ExperimentsFetcher(); + virtual ~ExperimentsFetcher(); + + // Fetches experiments with the provided |request_dict|. |access_token| needs + // to be present if the experiments are being fetched in the presence of an + // oauth token. Otherwise |request_dict| should be carrying the obfuscated + // user id as well as DM token. + HRESULT FetchAndStoreExperimentsInternal( + const base::string16& sid, + const std::string& access_token, + std::unique_ptr<base::DictionaryValue> request_dict); +}; + +} // namespace credential_provider + +#endif // CHROME_CREDENTIAL_PROVIDER_GAIACP_EXPERIMENTS_FETCHER_H_
diff --git a/chrome/credential_provider/gaiacp/experiments_manager.cc b/chrome/credential_provider/gaiacp/experiments_manager.cc new file mode 100644 index 0000000..98edc36 --- /dev/null +++ b/chrome/credential_provider/gaiacp/experiments_manager.cc
@@ -0,0 +1,159 @@ +// Copyright 2020 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/credential_provider/gaiacp/experiments_manager.h" + +#include <vector> + +#include "base/files/file.h" +#include "base/json/json_reader.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/credential_provider/gaiacp/gcp_utils.h" +#include "chrome/credential_provider/gaiacp/logging.h" +#include "chrome/credential_provider/gaiacp/reg_utils.h" + +namespace credential_provider { +namespace { + +// Registry key to control whether experiments feature is enabled. +const wchar_t kExperimentsEnabledRegKey[] = L"experiments_enabled"; + +// Name of the keys found in the experiments server response. +const char kResponseExperimentsKeyName[] = "experiments"; +const char kResponseFeatureKeyName[] = "feature"; +const char kResponseValueKeyName[] = "value"; + +} // namespace + +// static +ExperimentsManager* ExperimentsManager::Get() { + return *GetInstanceStorage(); +} + +// static +ExperimentsManager** ExperimentsManager::GetInstanceStorage() { + static ExperimentsManager instance; + static ExperimentsManager* instance_storage = &instance; + return &instance_storage; +} + +ExperimentsManager::ExperimentsManager() { + if (ExperimentsEnabled()) { + RegisterExperiments(); + ReloadAllExperiments(); + } +} + +ExperimentsManager::~ExperimentsManager() = default; + +void ExperimentsManager::RegisterExperiments() { + experiments_to_values_.clear(); + for (ExperimentMetadata em : experiments) { + // Experiment name to default value mapping is created prior to reading + // experiments file. + experiments_to_values_[em.name].first = em.default_value; + } +} + +// Reloads the experiment values from fetched experiments file for the given +// |sid|. Loads the fetched experiments values into |experiments_to_values_|. +bool ExperimentsManager::ReloadExperiments(const base::string16& sid) { + uint32_t open_flags = base::File::FLAG_OPEN | base::File::FLAG_READ; + std::unique_ptr<base::File> experiments_file = GetOpenedFileForUser( + sid, open_flags, kGcpwExperimentsDirectory, kGcpwUserExperimentsFileName); + if (!experiments_file) { + return false; + } + + std::vector<char> buffer(experiments_file->GetLength()); + experiments_file->Read(0, buffer.data(), buffer.size()); + experiments_file.reset(); + + base::Optional<base::Value> experiments_data = + base::JSONReader::Read(base::StringPiece(buffer.data(), buffer.size()), + base::JSON_ALLOW_TRAILING_COMMAS); + if (!experiments_data || !experiments_data->is_dict()) { + LOGFN(ERROR) << "Failed to read experiments data from file!"; + return false; + } + + const base::Value* experiments = + experiments_data->FindListKey(kResponseExperimentsKeyName); + if (!experiments) { + LOGFN(ERROR) << "User experiments not found!"; + return false; + } + + if (experiments->is_list()) { + for (const auto& item : experiments->GetList()) { + auto* f = item.FindStringKey(kResponseFeatureKeyName); + auto* v = item.FindStringKey(kResponseValueKeyName); + if (!f || !v) { + LOGFN(WARNING) << "Either feature or value are not found!"; + } + + experiments_to_values_[*f].second[base::UTF16ToUTF8(sid)] = *v; + } + } + return true; +} + +// TODO(crbug.com/1143829): Reload experiments if they were fetched by ESA. +void ExperimentsManager::ReloadAllExperiments() { + std::map<base::string16, UserTokenHandleInfo> sid_to_gaia_id; + HRESULT hr = GetUserTokenHandles(&sid_to_gaia_id); + + if (FAILED(hr)) { + LOGFN(ERROR) << "Unable to get the list of associated users"; + return; + } + + for (auto& sid : sid_to_gaia_id) { + if (!ReloadExperiments(sid.first)) { + LOGFN(ERROR) << "Error when loading experiments for user with sid " + << sid.first; + } + } +} + +std::string ExperimentsManager::GetExperimentForUser(const std::string& sid, + Experiment experiment) { + std::string experiment_name = experiments[experiment].name; + + // There shouldn't be a case where a registered experiment can't be found, but + // just in case we return an empty string. + if (experiments_to_values_.find(experiment_name) == + experiments_to_values_.end()) + return ""; + + auto& sid_to_experiment_values = + experiments_to_values_[experiment_name].second; + + // If the experiment value doesn't exist for the given sid, return the default + // value. + if (sid_to_experiment_values.find(sid) == sid_to_experiment_values.end()) { + return experiments_to_values_[experiment_name].first; + } + + return sid_to_experiment_values[sid]; +} + +bool ExperimentsManager::GetExperimentForUserAsBool(const std::string& sid, + Experiment experiment) { + return base::ToLowerASCII(GetExperimentForUser(sid, experiment)) == "true"; +} + +bool ExperimentsManager::ExperimentsEnabled() const { + return GetGlobalFlagOrDefault(kExperimentsEnabledRegKey, 0); +} + +std::vector<std::string> ExperimentsManager::GetExperimentsList() const { + std::vector<std::string> experiment_names; + for (auto& experiment : experiments) + experiment_names.push_back(experiment.name); + return experiment_names; +} + +} // namespace credential_provider
diff --git a/chrome/credential_provider/gaiacp/experiments_manager.h b/chrome/credential_provider/gaiacp/experiments_manager.h new file mode 100644 index 0000000..3512844 --- /dev/null +++ b/chrome/credential_provider/gaiacp/experiments_manager.h
@@ -0,0 +1,88 @@ +// Copyright 2020 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_CREDENTIAL_PROVIDER_GAIACP_EXPERIMENTS_MANAGER_H_ +#define CHROME_CREDENTIAL_PROVIDER_GAIACP_EXPERIMENTS_MANAGER_H_ + +#include <map> +#include <vector> + +#include "base/component_export.h" +#include "base/values.h" +#include "base/win/windows_types.h" +#include "chrome/credential_provider/extension/task_manager.h" +#include "url/gurl.h" + +namespace credential_provider { + +// List of experiments which must be kept in sync with the metadata for the +// experiment below. +enum Experiment { TEST_CLIENT_FLAG, TEST_CLIENT_FLAG2 }; + +class COMPONENT_EXPORT(GCPW_TASKS) ExperimentsManager { + public: + // Gets the singleton instance. + static ExperimentsManager* Get(); + + // Called as the first thing when ExperimentsManager is getting constructed. + // Registers all the experiments used in GCPW and ESA with their default + // values. If fetching experiments happen to fail, defaults set by this + // function are used. + void RegisterExperiments(); + + // Reloads the experiments for the given |sid|. + bool ReloadExperiments(const base::string16& sid); + + // Returns the experiment value for the provided |sid| and |experiment|. + std::string GetExperimentForUser(const std::string& sid, + Experiment experiment); + + // Returns the experiment value as a boolean for the provided |sid| and + // |experiment|. + bool GetExperimentForUserAsBool(const std::string& sid, + Experiment experiment); + + // Return true if experiments feature is enabled. + bool ExperimentsEnabled() const; + + // Returns the list of experiments to fetch from the backend. + std::vector<std::string> GetExperimentsList() const; + + private: + // Returns the storage used for the instance pointer. + static ExperimentsManager** GetInstanceStorage(); + + ExperimentsManager(); + virtual ~ExperimentsManager(); + + // Updates the cached values of experiments with the recent fetch. + void ReloadAllExperiments(); + + // sid to experiment value mapping. + typedef std::map<std::string, std::string> PerUserValues; + + // default value and per user experiment value pair. + typedef std::pair<std::string, PerUserValues> ExperimentValue; + + // Map of features to <default value, per user value mapping> + std::map<std::string, ExperimentValue> experiments_to_values_; + + // Struct which contains an individual experiment name and default value. + struct ExperimentMetadata { + Experiment experiment; + std::string name; + std::string default_value; + }; + + // Metadata for list of supported experiments. + std::vector<ExperimentMetadata> experiments = { + // TODO(crbug.com/1147522): Clean up the test experiments when actual + // experiments are introduced. These were added for e2e testing. + {TEST_CLIENT_FLAG, "test_client_flag", "false"}, + {TEST_CLIENT_FLAG2, "test_client_flag2", "false"}}; +}; + +} // namespace credential_provider + +#endif // CHROME_CREDENTIAL_PROVIDER_GAIACP_EXPERIMENTS_MANAGER_H_
diff --git a/chrome/credential_provider/gaiacp/experiments_manager_unittests.cc b/chrome/credential_provider/gaiacp/experiments_manager_unittests.cc new file mode 100644 index 0000000..85d47a8 --- /dev/null +++ b/chrome/credential_provider/gaiacp/experiments_manager_unittests.cc
@@ -0,0 +1,202 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/credential_provider/extension/task.h" +#include "chrome/credential_provider/gaiacp/experiments_fetcher.h" +#include "chrome/credential_provider/gaiacp/experiments_manager.h" +#include "chrome/credential_provider/gaiacp/gcp_utils.h" +#include "chrome/credential_provider/gaiacp/logging.h" +#include "chrome/credential_provider/gaiacp/reg_utils.h" +#include "chrome/credential_provider/test/gcp_fakes.h" +#include "chrome/credential_provider/test/gls_runner_test_base.h" +#include "chrome/credential_provider/test/test_credential.h" +#include "google_apis/gaia/gaia_urls.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace credential_provider { + +namespace testing { + +class ExperimentsManagerTest : public GlsRunnerTestBase { + protected: + void SetUp() override; +}; + +void ExperimentsManagerTest::SetUp() { + GlsRunnerTestBase::SetUp(); + + ASSERT_EQ(S_OK, SetGlobalFlagForTesting(L"experiments_enabled", 1)); + FakesForTesting fakes; + fakes.fake_win_http_url_fetcher_creator = + fake_http_url_fetcher_factory()->GetCreatorCallback(); + WinHttpUrlFetcher::SetCreatorForTesting( + fakes.fake_win_http_url_fetcher_creator); + + // Set token result a valid access token. + fake_http_url_fetcher_factory()->SetFakeResponse( + GURL(GaiaUrls::GetInstance()->oauth2_token_url().spec().c_str()), + FakeWinHttpUrlFetcher::Headers(), "{\"access_token\": \"dummy_token\"}"); +} + +TEST_F(ExperimentsManagerTest, DefaultValues) { + EXPECT_EQ("false", ExperimentsManager::Get()->GetExperimentForUser( + "test_sid", Experiment::TEST_CLIENT_FLAG)); + EXPECT_EQ("false", ExperimentsManager::Get()->GetExperimentForUser( + "test_sid", Experiment::TEST_CLIENT_FLAG2)); + + EXPECT_FALSE(ExperimentsManager::Get()->GetExperimentForUserAsBool( + "test_sid", Experiment::TEST_CLIENT_FLAG)); + EXPECT_FALSE(ExperimentsManager::Get()->GetExperimentForUserAsBool( + "test_sid", Experiment::TEST_CLIENT_FLAG2)); +} + +// Tests different outcomes of fetching experiments through GCPW: +// 0 - Experiments are fetched successfully. +// 1 - Failed fetching experiments. +// 2 - Response in fetching experiments contains malformed payload. +class ExperimentsManagerGcpwE2ETest + : public ExperimentsManagerTest, + public ::testing::WithParamInterface<int> {}; + +TEST_P(ExperimentsManagerGcpwE2ETest, FetchingExperiments) { + int experiment_fetch_status = GetParam(); + + // Create a fake user that has the same gaia id as the test gaia id. + CComBSTR sid; + ASSERT_EQ(S_OK, + fake_os_user_manager()->CreateTestOSUser( + L"foo", L"password", L"Full Name", L"comment", + base::UTF8ToUTF16(kDefaultGaiaId), L"user@company.com", &sid)); + + base::string16 device_resource_id = L"foo_resource_id"; + ASSERT_EQ(S_OK, SetUserProperty(OLE2W(sid), L"device_resource_id", + device_resource_id)); + + // Re-registering effectively clears the experiment values from the previous + // fetches. + ExperimentsManager::Get()->RegisterExperiments(); + + GURL url = ExperimentsFetcher::Get()->GetExperimentsUrl(); + + if (experiment_fetch_status == 0) { + fake_http_url_fetcher_factory()->SetFakeResponse( + url, FakeWinHttpUrlFetcher::Headers(), + "{\"experiments\": [{\"feature\": \"test_client_flag\", \"value\": " + "\"abc\"}, {\"feature\": \"test_client_flag2\", \"value\": \"def\"} ] " + "}"); + } else if (experiment_fetch_status == 1) { + fake_http_url_fetcher_factory()->SetFakeFailedResponse(url, E_FAIL); + } else { + fake_http_url_fetcher_factory()->SetFakeResponse( + url, FakeWinHttpUrlFetcher::Headers(), "{\"bad_experiments\": [ ] }"); + } + + // Create provider and start logon. + Microsoft::WRL::ComPtr<ICredentialProviderCredential> cred; + + ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred)); + + Microsoft::WRL::ComPtr<ITestCredential> test; + ASSERT_EQ(S_OK, cred.As(&test)); + + ASSERT_EQ(S_OK, StartLogonProcessAndWait()); + + // Email should be the same as the default one. + EXPECT_EQ(test->GetFinalEmail(), kDefaultEmail); + + std::string experiment1_value = "false"; + std::string experiment2_value = "false"; + + if (experiment_fetch_status == 0) { + experiment1_value = "abc"; + experiment2_value = "def"; + } + + EXPECT_EQ(experiment1_value, + ExperimentsManager::Get()->GetExperimentForUser( + base::UTF16ToUTF8(OLE2W(sid)), Experiment::TEST_CLIENT_FLAG)); + EXPECT_EQ(experiment2_value, + ExperimentsManager::Get()->GetExperimentForUser( + base::UTF16ToUTF8(OLE2W(sid)), Experiment::TEST_CLIENT_FLAG2)); +} + +INSTANTIATE_TEST_SUITE_P(All, + ExperimentsManagerGcpwE2ETest, + ::testing::Values(0, 1, 2)); + +// Tests different outcomes of fetching experiments through ESA: +// 0 - Experiments are fetched successfully. +// 1 - Failed fetching experiments. +// 2 - Response in fetching experiments contains malformed payload. +class ExperimentsManagerESAE2ETest : public ExperimentsManagerTest, + public ::testing::WithParamInterface<int> { +}; + +TEST_P(ExperimentsManagerESAE2ETest, FetchingExperiments) { + int experiment_fetch_status = GetParam(); + + // Create a fake user that has the same gaia id as the test gaia id. + CComBSTR sid; + ASSERT_EQ(S_OK, + fake_os_user_manager()->CreateTestOSUser( + L"foo", L"password", L"Full Name", L"comment", + base::UTF8ToUTF16(kDefaultGaiaId), L"user@company.com", &sid)); + + ASSERT_EQ(S_OK, GenerateGCPWDmToken((BSTR)sid)); + + base::string16 device_resource_id = L"foo_resource_id"; + ASSERT_EQ(S_OK, SetUserProperty(OLE2W(sid), L"device_resource_id", + device_resource_id)); + + // Re-registering effectively clears the experiment values from the previous + // fetches. + ExperimentsManager::Get()->RegisterExperiments(); + + GURL url = ExperimentsFetcher::Get()->GetExperimentsUrl(); + + if (experiment_fetch_status == 0) { + fake_http_url_fetcher_factory()->SetFakeResponse( + url, FakeWinHttpUrlFetcher::Headers(), + "{\"experiments\": [{\"feature\": \"test_client_flag\", \"value\": " + "\"abc\"}, {\"feature\": \"test_client_flag2\", \"value\": \"def\"} ] " + "}"); + } else if (experiment_fetch_status == 1) { + fake_http_url_fetcher_factory()->SetFakeFailedResponse(url, E_FAIL); + } else { + fake_http_url_fetcher_factory()->SetFakeResponse( + url, FakeWinHttpUrlFetcher::Headers(), "{\"bad_experiments\": [ ] }"); + } + + base::string16 dm_token; + ASSERT_EQ(S_OK, GetGCPWDmToken((BSTR)sid, &dm_token)); + + std::unique_ptr<extension::Task> task( + ExperimentsFetcher::GetFetchExperimentsTaskCreator().Run()); + task->SetContext({{device_resource_id, L"", L"", OLE2W(sid), dm_token}}); + task->Execute(); + + std::string experiment1_value = "false"; + std::string experiment2_value = "false"; + + if (experiment_fetch_status == 0) { + experiment1_value = "abc"; + experiment2_value = "def"; + } + + EXPECT_EQ(experiment1_value, + ExperimentsManager::Get()->GetExperimentForUser( + base::UTF16ToUTF8(OLE2W(sid)), Experiment::TEST_CLIENT_FLAG)); + EXPECT_EQ(experiment2_value, + ExperimentsManager::Get()->GetExperimentForUser( + base::UTF16ToUTF8(OLE2W(sid)), Experiment::TEST_CLIENT_FLAG2)); +} + +INSTANTIATE_TEST_SUITE_P(All, + ExperimentsManagerESAE2ETest, + ::testing::Values(0, 1, 2)); + +} // namespace testing +} // namespace credential_provider
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_base.cc b/chrome/credential_provider/gaiacp/gaia_credential_base.cc index 1e72b9c6..db57f206 100644 --- a/chrome/credential_provider/gaiacp/gaia_credential_base.cc +++ b/chrome/credential_provider/gaiacp/gaia_credential_base.cc
@@ -40,6 +40,8 @@ #include "chrome/credential_provider/gaiacp/auth_utils.h" #include "chrome/credential_provider/gaiacp/device_policies_manager.h" #include "chrome/credential_provider/gaiacp/event_logs_upload_manager.h" +#include "chrome/credential_provider/gaiacp/experiments_fetcher.h" +#include "chrome/credential_provider/gaiacp/experiments_manager.h" #include "chrome/credential_provider/gaiacp/gaia_credential_provider.h" #include "chrome/credential_provider/gaiacp/gaia_credential_provider_i.h" #include "chrome/credential_provider/gaiacp/gaia_resources.h" @@ -810,6 +812,44 @@ return HRESULT_FROM_WIN32(NERR_UserExists); } +// If GCPW user policies or experiments are stale, make sure to fetch them +// before proceeding with the login. +void GetUserConfigsIfStale(const base::string16& sid, + const base::string16& gaia_id, + const base::string16& access_token) { + if (UserPoliciesManager::Get()->CloudPoliciesEnabled() && + UserPoliciesManager::Get()->IsUserPolicyStaleOrMissing(sid)) { + // Save gaia id since it is needed for the cloud policies server request. + HRESULT hr = SetUserProperty(sid, kUserId, gaia_id); + if (FAILED(hr)) { + LOGFN(ERROR) << "SetUserProperty(id) hr=" << putHR(hr); + } + + hr = UserPoliciesManager::Get()->FetchAndStoreCloudUserPolicies( + sid, base::UTF16ToUTF8(access_token)); + if (FAILED(hr)) { + LOGFN(ERROR) << "Failed fetching user policies for user " << sid + << " Error: " << putHR(hr); + } + } + + if (ExperimentsManager::Get()->ExperimentsEnabled() && + GetTimeDeltaSinceLastFetch(sid, kLastUserExperimentsRefreshTimeRegKey) > + kMaxTimeDeltaSinceLastExperimentsFetch) { + HRESULT hr = SetUserProperty(sid, kUserId, gaia_id); + if (FAILED(hr)) { + LOGFN(ERROR) << "SetUserProperty(id) hr=" << putHR(hr); + } + + hr = ExperimentsFetcher::Get()->FetchAndStoreExperiments( + sid, base::UTF16ToUTF8(access_token)); + if (FAILED(hr)) { + LOGFN(ERROR) << "Failed fetching experiments for user " << sid + << " Error: " << putHR(hr); + } + } +} + } // namespace CGaiaCredentialBase::UIProcessInfo::UIProcessInfo() {} @@ -2492,27 +2532,12 @@ : "false")); } - base::string16 sid = OLE2CW(user_sid_); - if (UserPoliciesManager::Get()->CloudPoliciesEnabled() && - UserPoliciesManager::Get()->IsUserPolicyStaleOrMissing(sid)) { - // Save gaia id since it is needed for the cloud policies server request. - base::string16 gaia_id = GetDictString(*authentication_results_, kKeyId); - HRESULT hr = SetUserProperty(sid, kUserId, gaia_id); - if (FAILED(hr)) { - LOGFN(ERROR) << "SetUserProperty(id) hr=" << putHR(hr); - } - - // TODO(crbug.com/976744) Use downscoped token here. - base::string16 access_token = - GetDictString(*authentication_results_, kKeyAccessToken); - hr = UserPoliciesManager::Get()->FetchAndStoreCloudUserPolicies( - sid, base::UTF16ToUTF8(access_token)); - SecurelyClearString(access_token); - if (FAILED(hr)) { - LOGFN(ERROR) << "Failed fetching user policies for user " << sid - << " Error: " << putHR(hr); - } - } + base::string16 gaia_id = GetDictString(*authentication_results_, kKeyId); + // TODO(crbug.com/976744) Use downscoped token here. + base::string16 access_token = + GetDictString(*authentication_results_, kKeyAccessToken); + GetUserConfigsIfStale(OLE2CW(user_sid_), gaia_id, access_token); + SecurelyClearString(access_token); base::string16 local_password = GetDictString(*authentication_results_, kKeyPassword);
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc b/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc index 59dd0c1..beedba1d 100644 --- a/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc +++ b/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
@@ -3596,7 +3596,7 @@ ASSERT_EQ(S_OK, FinishLogonProcess(true, true, 0)); base::TimeDelta time_since_last_fetch = - UserPoliciesManager::Get()->GetTimeDeltaSinceLastPolicyFetch(sid); + GetTimeDeltaSinceLastFetch(sid, kLastUserPolicyRefreshTimeRegKey); if (cloud_policies_enabled && !policy_refreshed_recently) { ASSERT_EQ(1, fake_user_policies_manager.GetNumTimesFetchAndStoreCalled());
diff --git a/chrome/credential_provider/gaiacp/gcp_utils.cc b/chrome/credential_provider/gaiacp/gcp_utils.cc index eb870b5..e90b7ce7 100644 --- a/chrome/credential_provider/gaiacp/gcp_utils.cc +++ b/chrome/credential_provider/gaiacp/gcp_utils.cc
@@ -92,6 +92,12 @@ constexpr int kMaxNumConsecutiveUploadDeviceFailures = 3; const base::TimeDelta kMaxTimeDeltaSinceLastUserPolicyRefresh = base::TimeDelta::FromDays(1); +const base::TimeDelta kMaxTimeDeltaSinceLastExperimentsFetch = + base::TimeDelta::FromDays(1); + +// Path elements for the path where the experiments are stored on disk. +const wchar_t kGcpwExperimentsDirectory[] = L"Experiments"; +const wchar_t kGcpwUserExperimentsFileName[] = L"ExperimentsFetchResponse"; namespace { @@ -109,6 +115,7 @@ #define HANDLE_LANGUAGE(l_, o_) {L## #l_, o_}, DO_LANGUAGES #undef HANDLE_LANGUAGE + }; base::FilePath GetStartupSentinelLocation(const base::string16& version) { @@ -233,6 +240,23 @@ return S_OK; } +// Get the path to the directory under DIR_COMMON_APP_DATA with the given |sid| +// and |file_dir|. +base::FilePath GetDirectoryFilePath(const base::string16& sid, + const base::string16& file_dir) { + base::FilePath path; + if (!base::PathService::Get(base::DIR_COMMON_APP_DATA, &path)) { + HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); + LOGFN(ERROR) << "PathService::Get(DIR_COMMON_APP_DATA) hr=" << putHR(hr); + return base::FilePath(); + } + path = path.Append(GetInstallParentDirectoryName()) + .Append(kCredentialProviderFolder) + .Append(file_dir) + .Append(sid); + return path; +} + } // namespace // GoogleRegistrationDataForTesting ////////////////////////////////////////// @@ -1255,4 +1279,62 @@ return url; } +std::unique_ptr<base::File> GetOpenedFileForUser( + const base::string16& sid, + uint32_t open_flags, + const base::string16& file_dir, + const base::string16& file_name) { + base::FilePath experiments_dir = GetDirectoryFilePath(sid, file_dir); + if (!base::DirectoryExists(experiments_dir)) { + base::File::Error error; + if (!CreateDirectoryAndGetError(experiments_dir, &error)) { + LOGFN(ERROR) << "Experiments data directory could not be created for " + << sid << " Error: " << error; + return nullptr; + } + } + + base::FilePath experiments_file_path = experiments_dir.Append(file_name); + std::unique_ptr<base::File> experiments_file( + new base::File(experiments_file_path, open_flags)); + + if (!experiments_file->IsValid()) { + LOGFN(ERROR) << "Error opening experiments file for user " << sid + << " with flags " << open_flags + << " Error: " << experiments_file->error_details(); + return nullptr; + } + + base::File::Error lock_error = + experiments_file->Lock(base::File::LockMode::kExclusive); + if (lock_error != base::File::FILE_OK) { + LOGFN(ERROR) + << "Failed to obtain exclusive lock on experiments file! Error: " + << lock_error; + return nullptr; + } + + return experiments_file; +} + +base::TimeDelta GetTimeDeltaSinceLastFetch(const base::string16& sid, + const base::string16& flag) { + wchar_t last_fetch_millis[512]; + ULONG last_fetch_size = base::size(last_fetch_millis); + HRESULT hr = GetUserProperty(sid, flag, last_fetch_millis, &last_fetch_size); + + if (FAILED(hr)) { + return base::TimeDelta::Max(); + } + + int64_t last_fetch_millis_int64; + base::StringToInt64(last_fetch_millis, &last_fetch_millis_int64); + + int64_t time_delta_from_last_fetch_ms = + base::Time::Now().ToDeltaSinceWindowsEpoch().InMilliseconds() - + last_fetch_millis_int64; + + return base::TimeDelta::FromMilliseconds(time_delta_from_last_fetch_ms); +} + } // namespace credential_provider
diff --git a/chrome/credential_provider/gaiacp/gcp_utils.h b/chrome/credential_provider/gaiacp/gcp_utils.h index b9cb3a4..2a932412 100644 --- a/chrome/credential_provider/gaiacp/gcp_utils.h +++ b/chrome/credential_provider/gaiacp/gcp_utils.h
@@ -10,6 +10,7 @@ #include <string> #include "base/callback.h" +#include "base/files/file.h" #include "base/files/file_path.h" #include "base/strings/string16.h" #include "base/values.h" @@ -75,6 +76,14 @@ // again. extern const base::TimeDelta kMaxTimeDeltaSinceLastUserPolicyRefresh; +// Maximum allowed time delta after which experiments should be fetched +// again. +extern const base::TimeDelta kMaxTimeDeltaSinceLastExperimentsFetch; + +// Path elements for the path where the experiments are stored on disk. +extern const wchar_t kGcpwExperimentsDirectory[]; +extern const wchar_t kGcpwUserExperimentsFileName[]; + // Because of some strange dependency problems with windows header files, // define STATUS_SUCCESS here instead of including ntstatus.h or SubAuth.h #define STATUS_SUCCESS ((NTSTATUS)0x00000000L) @@ -404,6 +413,20 @@ base::string16 GetDevelopmentUrl(const base::string16& url, const base::string16& dev); +// Returns a handle to a file which is stored under DIR_COMMON_APP_DATA > |sid| +// > |file_dir| > |file_name|. The file is opened with the provided +// |open_flags|. +std::unique_ptr<base::File> GetOpenedFileForUser( + const base::string16& sid, + uint32_t open_flags, + const base::string16& file_dir, + const base::string16& file_name); + +// Returns the time delta since the last fetch for the given |sid|. |flag| +// stores the last fetch time. +base::TimeDelta GetTimeDeltaSinceLastFetch(const base::string16& sid, + const base::string16& flag); + } // namespace credential_provider #endif // CHROME_CREDENTIAL_PROVIDER_GAIACP_GCP_UTILS_H_
diff --git a/chrome/credential_provider/gaiacp/gcpw_version.h b/chrome/credential_provider/gaiacp/gcpw_version.h index dbedfe3f..c960841 100644 --- a/chrome/credential_provider/gaiacp/gcpw_version.h +++ b/chrome/credential_provider/gaiacp/gcpw_version.h
@@ -13,7 +13,7 @@ namespace credential_provider { // A structure to hold the version of GCPW. -class COMPONENT_EXPORT(GCPW_POLICIES) GcpwVersion { +class COMPONENT_EXPORT(GCPW_TASKS) GcpwVersion { public: // Create a default version which is not valid. GcpwVersion();
diff --git a/chrome/credential_provider/gaiacp/reg_utils.cc b/chrome/credential_provider/gaiacp/reg_utils.cc index bf14a8e..9bb0af64 100644 --- a/chrome/credential_provider/gaiacp/reg_utils.cc +++ b/chrome/credential_provider/gaiacp/reg_utils.cc
@@ -65,6 +65,10 @@ constexpr wchar_t kEmailDomainsKey[] = L"ed"; // deprecated. constexpr wchar_t kEmailDomainsKeyNew[] = L"domains_allowed_to_login"; +const wchar_t kLastUserPolicyRefreshTimeRegKey[] = L"last_policy_refresh_time"; +const wchar_t kLastUserExperimentsRefreshTimeRegKey[] = + L"last_experiments_refresh_time"; + namespace { constexpr wchar_t kAccountPicturesRootRegKey[] =
diff --git a/chrome/credential_provider/gaiacp/reg_utils.h b/chrome/credential_provider/gaiacp/reg_utils.h index f9b7cea..f7f2407 100644 --- a/chrome/credential_provider/gaiacp/reg_utils.h +++ b/chrome/credential_provider/gaiacp/reg_utils.h
@@ -83,6 +83,14 @@ extern const wchar_t kEmailDomainsKey[]; // Older deprecated key. extern const wchar_t kEmailDomainsKeyNew[]; +// Registry key where the the last time the policy is refreshed for the user is +// stored. +extern const wchar_t kLastUserPolicyRefreshTimeRegKey[]; + +// Registry key where the the last time the experiments is refreshed for the +// user is stored. +extern const wchar_t kLastUserExperimentsRefreshTimeRegKey[]; + // Gets any HKLM registry key on the system. HRESULT GetMachineRegDWORD(const base::string16& key_name, const base::string16& name,
diff --git a/chrome/credential_provider/gaiacp/user_policies.h b/chrome/credential_provider/gaiacp/user_policies.h index c410ed0..fee2a84 100644 --- a/chrome/credential_provider/gaiacp/user_policies.h +++ b/chrome/credential_provider/gaiacp/user_policies.h
@@ -12,7 +12,7 @@ namespace credential_provider { // Structure to hold the policies for each user. -struct COMPONENT_EXPORT(GCPW_POLICIES) UserPolicies { +struct COMPONENT_EXPORT(GCPW_TASKS) UserPolicies { // Controls whether MDM enrollment is enabled/disabled. bool enable_dm_enrollment;
diff --git a/chrome/credential_provider/gaiacp/user_policies_manager.cc b/chrome/credential_provider/gaiacp/user_policies_manager.cc index c21236b..bb118771 100644 --- a/chrome/credential_provider/gaiacp/user_policies_manager.cc +++ b/chrome/credential_provider/gaiacp/user_policies_manager.cc
@@ -41,13 +41,8 @@ base::TimeDelta::FromMilliseconds(5000); // Path elements for the path where the policies are stored on disk. -constexpr base::FilePath::CharType kGcpwPoliciesDirectory[] = L"Policies"; -constexpr base::FilePath::CharType kGcpwUserPolicyFileName[] = - L"PolicyFetchResponse"; - -// Registry key where the the last time the policy is refreshed for the user is -// stored. -const wchar_t kLastUserPolicyRefreshTimeRegKey[] = L"last_policy_refresh_time"; +constexpr wchar_t kGcpwPoliciesDirectory[] = L"Policies"; +constexpr wchar_t kGcpwUserPolicyFileName[] = L"PolicyFetchResponse"; // Maximum number of retries if a HTTP call to the backend fails. constexpr unsigned int kMaxNumHttpRetries = 1; @@ -62,57 +57,6 @@ // True when cloud policies feature is enabled. bool g_cloud_policies_enabled = false; -// Get the path to the directory where the policies will be stored for the user -// with |sid|. -base::FilePath GetUserPolicyDirectoryFilePath(const base::string16& sid) { - base::FilePath path; - if (!base::PathService::Get(base::DIR_COMMON_APP_DATA, &path)) { - HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); - LOGFN(ERROR) << "PathService::Get(DIR_COMMON_APP_DATA) hr=" << putHR(hr); - return base::FilePath(); - } - path = path.Append(GetInstallParentDirectoryName()) - .Append(kCredentialProviderFolder) - .Append(kGcpwPoliciesDirectory) - .Append(sid); - return path; -} - -std::unique_ptr<base::File> GetOpenedPolicyFileForUser( - const base::string16& sid, - uint32_t open_flags) { - base::FilePath policy_dir = GetUserPolicyDirectoryFilePath(sid); - if (!base::DirectoryExists(policy_dir)) { - base::File::Error error; - if (!CreateDirectoryAndGetError(policy_dir, &error)) { - LOGFN(ERROR) << "Policy data directory could not be created for " << sid - << " Error: " << error; - return nullptr; - } - } - - base::FilePath policy_file_path = policy_dir.Append(kGcpwUserPolicyFileName); - std::unique_ptr<base::File> policy_file( - new base::File(policy_file_path, open_flags)); - - if (!policy_file->IsValid()) { - LOGFN(ERROR) << "Error opening policy file for user " << sid - << " with flags " << open_flags - << " Error: " << policy_file->error_details(); - return nullptr; - } - - base::File::Error lock_error = - policy_file->Lock(base::File::LockMode::kExclusive); - if (lock_error != base::File::FILE_OK) { - LOGFN(ERROR) << "Failed to obtain exclusive lock on policy file! Error: " - << lock_error; - return nullptr; - } - - return policy_file; -} - // Creates the URL used to fetch the policies from the backend based on the // credential present (OAuth vs DM token) for authentication. GURL GetFetchUserPoliciesUrl(const base::string16& sid, @@ -294,8 +238,8 @@ uint32_t open_flags = base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE | base::File::FLAG_EXCLUSIVE_WRITE; - std::unique_ptr<base::File> policy_file = - GetOpenedPolicyFileForUser(sid, open_flags); + std::unique_ptr<base::File> policy_file = GetOpenedFileForUser( + sid, open_flags, kGcpwPoliciesDirectory, kGcpwUserPolicyFileName); if (!policy_file) { return (fetch_status_ = E_FAIL); } @@ -322,35 +266,14 @@ return (fetch_status_ = S_OK); } -base::TimeDelta UserPoliciesManager::GetTimeDeltaSinceLastPolicyFetch( - const base::string16& sid) const { - wchar_t last_fetch_millis[512]; - ULONG last_fetch_size = base::size(last_fetch_millis); - HRESULT hr = GetUserProperty(sid, kLastUserPolicyRefreshTimeRegKey, - last_fetch_millis, &last_fetch_size); - - if (FAILED(hr)) { - // The policy was never fetched before. - return base::TimeDelta::Max(); - } - - int64_t last_fetch_millis_int64; - base::StringToInt64(last_fetch_millis, &last_fetch_millis_int64); - - int64_t time_delta_from_last_fetch_ms = - base::Time::Now().ToDeltaSinceWindowsEpoch().InMilliseconds() - - last_fetch_millis_int64; - - return base::TimeDelta::FromMilliseconds(time_delta_from_last_fetch_ms); -} bool UserPoliciesManager::GetUserPolicies(const base::string16& sid, UserPolicies* user_policies) const { DCHECK(user_policies); uint32_t open_flags = base::File::FLAG_OPEN | base::File::FLAG_READ; - std::unique_ptr<base::File> policy_file = - GetOpenedPolicyFileForUser(sid, open_flags); + std::unique_ptr<base::File> policy_file = GetOpenedFileForUser( + sid, open_flags, kGcpwPoliciesDirectory, kGcpwUserPolicyFileName); if (!policy_file) { return false; } @@ -387,7 +310,7 @@ return true; } - if (GetTimeDeltaSinceLastPolicyFetch(sid) > + if (GetTimeDeltaSinceLastFetch(sid, kLastUserPolicyRefreshTimeRegKey) > kMaxTimeDeltaSinceLastUserPolicyRefresh) { return true; }
diff --git a/chrome/credential_provider/gaiacp/user_policies_manager.h b/chrome/credential_provider/gaiacp/user_policies_manager.h index e8d7ef3..1f5f646e 100644 --- a/chrome/credential_provider/gaiacp/user_policies_manager.h +++ b/chrome/credential_provider/gaiacp/user_policies_manager.h
@@ -17,7 +17,7 @@ namespace credential_provider { // Manager used to fetch user policies from GCPW backends. -class COMPONENT_EXPORT(GCPW_POLICIES) UserPoliciesManager { +class COMPONENT_EXPORT(GCPW_TASKS) UserPoliciesManager { public: // Get the user policies manager instance. static UserPoliciesManager* Get(); @@ -42,11 +42,6 @@ virtual HRESULT FetchAndStoreCloudUserPolicies( const extension::UserDeviceContext& context); - // Return the elapsed time delta since the last time the policies were - // successfully fetched for the user with |sid|. - base::TimeDelta GetTimeDeltaSinceLastPolicyFetch( - const base::string16& sid) const; - // Get the URL of GCPW service for HTTP request for fetching user policies // when the caller has a valid OAuth token for authentication. GURL GetGcpwServiceUserPoliciesUrl(const base::string16& sid);
diff --git a/chrome/credential_provider/test/BUILD.gn b/chrome/credential_provider/test/BUILD.gn index e540efa4..0d6c630 100644 --- a/chrome/credential_provider/test/BUILD.gn +++ b/chrome/credential_provider/test/BUILD.gn
@@ -11,6 +11,7 @@ "../extension/task_manager_unittests.cc", "../gaiacp/associated_user_validator_unittests.cc", "../gaiacp/device_policies_manager_unittests.cc", + "../gaiacp/experiments_manager_unittests.cc", "../gaiacp/gaia_credential_base_unittests.cc", "../gaiacp/gaia_credential_other_user_unittests.cc", "../gaiacp/gaia_credential_provider_unittests.cc", @@ -39,8 +40,8 @@ "../gaiacp:common", "../gaiacp:gaia_credential_provider_idl", "../gaiacp:gaiacp_lib", - "../gaiacp:policies", "../gaiacp:string_resources", + "../gaiacp:tasks", "../gaiacp:util", "../gaiacp:version", "../setup:common",
diff --git a/chrome/installer/setup/setup_util.cc b/chrome/installer/setup/setup_util.cc index 347427d..fbf3bd3 100644 --- a/chrome/installer/setup/setup_util.cc +++ b/chrome/installer/setup/setup_util.cc
@@ -429,7 +429,7 @@ bool IsProcessorSupported() { #if defined(ARCH_CPU_X86_FAMILY) - return base::CPU().has_sse2(); + return base::CPU().has_sse3(); #elif defined(ARCH_CPU_ARM64) return true; #else
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index fdefd67..931b6c59 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn
@@ -3890,6 +3890,7 @@ "../browser/win/jumplist_file_util_unittest.cc", "../browser/win/jumplist_update_util_unittest.cc", "../browser/win/taskbar_icon_finder_unittest.cc", + "../browser/win/uninstallation_via_os_settings_unittest.cc", "../common/chrome_constants_win_unittest.cc", "../common/conflicts/module_watcher_win_unittest.cc", "../common/conflicts/remote_module_watcher_win_unittest.cc",
diff --git a/chrome/test/data/webui/chromeos/scanning/scanning_app_test.js b/chrome/test/data/webui/chromeos/scanning/scanning_app_test.js index e20514e0..e53b5e7 100644 --- a/chrome/test/data/webui/chromeos/scanning/scanning_app_test.js +++ b/chrome/test/data/webui/chromeos/scanning/scanning_app_test.js
@@ -91,6 +91,7 @@ this.resolverMap_.set('getScanners', new PromiseResolver()); this.resolverMap_.set('getScannerCapabilities', new PromiseResolver()); this.resolverMap_.set('startScan', new PromiseResolver()); + this.resolverMap_.set('cancelScan', new PromiseResolver()); } /** @@ -206,6 +207,10 @@ resolve({success: true}); }); } + + cancelScan() { + this.methodCalled('cancelScan'); + } } export function scanningAppTest() { @@ -309,6 +314,8 @@ let resolutionSelect; /** @type {!CrButtonElement} */ let scanButton; + /** @type {!CrButtonElement} */ + let cancelButton; /** @type {!HTMLElement} */ let statusText; /** @type {!HTMLElement} */ @@ -330,6 +337,8 @@ resolutionSelect = scanningApp.$$('#resolutionSelect').$$('select'); scanButton = /** @type {!CrButtonElement} */ (scanningApp.$$('#scanButton')); + cancelButton = + /** @type {!CrButtonElement} */ (scanningApp.$$('#cancelButton')); statusText = /** @type {!HTMLElement} */ (scanningApp.$$('#statusText')); helperText = scanningApp.$$('#scanPreview').$$('#helperText'); @@ -365,6 +374,8 @@ assertFalse(pageSizeSelect.disabled); assertFalse(resolutionSelect.disabled); assertFalse(scanButton.disabled); + assertTrue(isVisible(scanButton)); + assertFalse(isVisible(cancelButton)); assertEquals('', statusText.textContent.trim()); assertTrue(isVisible(helperText)); assertFalse(isVisible(scanProgress)); @@ -387,6 +398,8 @@ assertTrue(pageSizeSelect.disabled); assertTrue(resolutionSelect.disabled); assertTrue(scanButton.disabled); + assertFalse(isVisible(scanButton)); + assertTrue(isVisible(cancelButton)); assertFalse(isVisible(helperText)); assertTrue(isVisible(scanProgress)); assertFalse(isVisible( @@ -445,6 +458,8 @@ assertFalse(pageSizeSelect.disabled); assertFalse(resolutionSelect.disabled); assertFalse(scanButton.disabled); + assertTrue(isVisible(scanButton)); + assertFalse(isVisible(cancelButton)); assertTrue(isVisible(helperText)); assertFalse(isVisible(scanProgress)); assertFalse(isVisible( @@ -452,6 +467,65 @@ }); }); + test('CancelScan', () => { + const expectedScanners = [ + createScanner(firstScannerId, firstScannerName), + createScanner(secondScannerId, secondScannerName) + ]; + + let capabilities = new Map(); + capabilities.set(firstScannerId, firstCapabilities); + capabilities.set(secondScannerId, secondCapabilities); + + /** @type {!CrButtonElement} */ + let scanButton; + /** @type {!CrButtonElement} */ + let cancelButton; + + return initializeScanningApp(expectedScanners, capabilities) + .then(() => { + scanButton = + /** @type {!CrButtonElement} */ (scanningApp.$$('#scanButton')); + cancelButton = + /** @type {!CrButtonElement} */ (scanningApp.$$('#cancelButton')); + return fakeScanService_.whenCalled('getScannerCapabilities'); + }) + .then(() => { + // Before the scan button is clicked, the scan button should be + // visible and enabled, and the cancel button shouldn't be visible. + assertFalse(scanButton.disabled); + assertTrue(isVisible(scanButton)); + assertFalse(isVisible(cancelButton)); + + // Click the Scan button and wait till the scan is started. + scanButton.click(); + return fakeScanService_.whenCalled('startScan'); + }) + .then(() => { + // After the scan button is clicked and the scan has started, the scan + // button should be disabled and not visible, and the cancel button + // should be visible. + assertTrue(scanButton.disabled); + assertFalse(isVisible(scanButton)); + assertTrue(isVisible(cancelButton)); + + // Simulate a progress update and verify the progress bar and text are + // updated correctly. + return fakeScanService_.simulateProgress(1, 17); + }) + .then(() => { + // Click the cancel button to cancel the scan. + cancelButton.click(); + return fakeScanService_.cancelScan(); + }) + .then(() => { + // After canceling is complete, the scan button should be visible and + // enabled, and the cancel button shouldn't be visible. + assertTrue(isVisible(scanButton)); + assertFalse(isVisible(cancelButton)); + }); + }); + test('PanelContainerContent', () => { const scanners = []; const capabilities = new Map();
diff --git a/chrome/updater/constants.h b/chrome/updater/constants.h index 6371f86..e2b81fb 100644 --- a/chrome/updater/constants.h +++ b/chrome/updater/constants.h
@@ -204,6 +204,9 @@ constexpr bool kInstallPolicyDefault = kPolicyEnabled; constexpr bool kUpdatePolicyDefault = kPolicyEnabled; +constexpr int kUninstallPingReasonUninstalled = 0; +constexpr int kUninstallPingReasonUserNotAnOwner = 1; + } // namespace updater #endif // CHROME_UPDATER_CONSTANTS_H_
diff --git a/chrome/updater/control_service_impl.cc b/chrome/updater/control_service_impl.cc index 11aaeba..c87a5e9 100644 --- a/chrome/updater/control_service_impl.cc +++ b/chrome/updater/control_service_impl.cc
@@ -13,6 +13,8 @@ #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/logging.h" +#include "base/task/post_task.h" +#include "base/task/thread_pool.h" #include "base/threading/sequenced_task_runner_handle.h" #include "base/time/time.h" #include "chrome/updater/configurator.h" @@ -20,7 +22,9 @@ #include "chrome/updater/persisted_data.h" #include "chrome/updater/prefs.h" #include "chrome/updater/update_service_impl.h" +#include "chrome/updater/util.h" #include "components/prefs/pref_service.h" +#include "components/update_client/update_client.h" namespace updater { @@ -28,24 +32,49 @@ scoped_refptr<updater::Configurator> config) : config_(config), persisted_data_( - base::MakeRefCounted<PersistedData>(config_->GetPrefService())) {} + base::MakeRefCounted<PersistedData>(config_->GetPrefService())), + update_client_(update_client::UpdateClientFactory(config_)), + number_of_pings_remaining_(0) {} void ControlServiceImpl::Run(base::OnceClosure callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - - UnregisterMissingApps(); - MaybeCheckForUpdates(std::move(callback)); + callback_ = std::move(callback); + UnregisterMissingApps(GetRegisteredApps()); } void ControlServiceImpl::InitializeUpdateService(base::OnceClosure callback) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - VLOG(1) << __func__; std::move(callback).Run(); } -void ControlServiceImpl::MaybeCheckForUpdates(base::OnceClosure callback) { +std::vector<ControlServiceImpl::AppInfo> +ControlServiceImpl::GetRegisteredApps() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + std::vector<AppInfo> apps_to_unregister; + for (const std::string& app_id : persisted_data_->GetAppIds()) { + if (app_id == kUpdaterAppId) + continue; + + const base::FilePath ecp = persisted_data_->GetExistenceCheckerPath(app_id); + if (!ecp.empty()) { + apps_to_unregister.push_back( + AppInfo(app_id, persisted_data_->GetProductVersion(app_id), ecp)); + } + } + return apps_to_unregister; +} + +bool ControlServiceImpl::WaitingOnUninstallPings() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return number_of_pings_remaining_ > 0; +} + +void ControlServiceImpl::MaybeCheckForUpdates() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + scoped_refptr<UpdateServiceImpl> update_service = + base::MakeRefCounted<UpdateServiceImpl>(config_); + const base::Time lastUpdateTime = config_->GetPrefService()->GetTime(kPrefUpdateTime); @@ -57,13 +86,10 @@ VLOG(0) << "Skipping checking for updates: " << timeSinceUpdate.InMinutes(); base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE, - std::move(callback)); + std::move(callback_)); return; } - scoped_refptr<UpdateServiceImpl> update_service = - base::MakeRefCounted<UpdateServiceImpl>(config_); - update_service->UpdateAll( base::DoNothing(), base::BindOnce( @@ -78,22 +104,81 @@ } std::move(closure).Run(); }, - base::BindOnce(std::move(callback)), config_)); + base::BindOnce(std::move(callback_)), config_)); } -void ControlServiceImpl::UnregisterMissingApps() { +void ControlServiceImpl::UnregisterMissingApps(std::vector<AppInfo> apps) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - for (const auto& app_id : persisted_data_->GetAppIds()) { + + base::ThreadPool::PostTaskAndReplyWithResult( + FROM_HERE, {base::MayBlock()}, + base::BindOnce(&ControlServiceImpl::GetAppIDsToRemove, this, apps), + base::BindOnce(&ControlServiceImpl::RemoveAppIDsAndSendUninstallPings, + this)); +} + +void ControlServiceImpl::UnregisterMissingAppsDone() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + MaybeCheckForUpdates(); +} + +std::vector<ControlServiceImpl::PingInfo> ControlServiceImpl::GetAppIDsToRemove( + std::vector<AppInfo> apps) { + std::vector<PingInfo> app_ids_to_remove; + for (const auto& app : apps) { // Skip if app_id is equal to updater app id. - if (app_id == kUpdaterAppId) + if (app.app_id_ == kUpdaterAppId) continue; - const base::FilePath ecp = persisted_data_->GetExistenceCheckerPath(app_id); - if (!ecp.empty() && !base::PathExists(ecp)) { - if (!persisted_data_->RemoveApp(app_id)) - VLOG(0) << "Could not remove registration of app " << app_id; + if (!base::PathExists(app.ecp_)) { + app_ids_to_remove.push_back(PingInfo(app.app_id_, app.app_version_, + kUninstallPingReasonUninstalled)); + } else if (!PathOwnedByUser(app.ecp_)) { + app_ids_to_remove.push_back(PingInfo(app.app_id_, app.app_version_, + kUninstallPingReasonUserNotAnOwner)); } } + + return app_ids_to_remove; +} + +void ControlServiceImpl::RemoveAppIDsAndSendUninstallPings( + std::vector<PingInfo> app_ids_to_remove) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + if (app_ids_to_remove.empty()) { + UnregisterMissingAppsDone(); + return; + } + + for (const auto& app_id_to_remove : app_ids_to_remove) { + const auto app_id = app_id_to_remove.app_id_; + const int ping_reason = app_id_to_remove.ping_reason_; + const base::Version app_version = app_id_to_remove.app_version_; + + if (persisted_data_->RemoveApp(app_id)) { + VLOG(1) << "Uninstall ping for app id: " << app_id + << ". Ping reason: " << ping_reason; + number_of_pings_remaining_++; + update_client_->SendUninstallPing( + app_id, app_version, ping_reason, + base::BindOnce(&ControlServiceImpl::UninstallPingSent, this)); + } else { + VLOG(0) << "Could not remove registration of app " << app_id; + } + } +} + +void ControlServiceImpl::UninstallPingSent(update_client::Error error) { + number_of_pings_remaining_--; + + if (error != update_client::Error::NONE) + VLOG(0) << __func__ << ": Error: " << static_cast<int>(error); + + if (!WaitingOnUninstallPings()) + std::move( + base::BindOnce(&ControlServiceImpl::UnregisterMissingAppsDone, this)) + .Run(); } void ControlServiceImpl::Uninitialize() {
diff --git a/chrome/updater/control_service_impl.h b/chrome/updater/control_service_impl.h index 8dd3fb9..a4d263e 100644 --- a/chrome/updater/control_service_impl.h +++ b/chrome/updater/control_service_impl.h
@@ -5,11 +5,22 @@ #ifndef CHROME_UPDATER_CONTROL_SERVICE_IMPL_H_ #define CHROME_UPDATER_CONTROL_SERVICE_IMPL_H_ +#include <string> +#include <vector> + +#include "base/callback.h" #include "base/callback_forward.h" +#include "base/files/file_path.h" #include "base/memory/scoped_refptr.h" #include "base/sequence_checker.h" +#include "base/version.h" #include "chrome/updater/control_service.h" +namespace update_client { +enum class Error; +class UpdateClient; +} // namespace update_client + namespace updater { class Configurator; @@ -31,16 +42,63 @@ SEQUENCE_CHECKER(sequence_checker_); + struct AppInfo { + AppInfo(const std::string& app_id, + const base::Version& app_version, + const base::FilePath& ecp) + : app_id_(app_id), app_version_(app_version), ecp_(ecp) {} + std::string app_id_; + base::Version app_version_; + base::FilePath ecp_; + }; + + struct PingInfo { + PingInfo(const std::string& app_id, + const base::Version& app_version, + int ping_reason) + : app_id_(app_id), + app_version_(app_version), + ping_reason_(ping_reason) {} + std::string app_id_; + base::Version app_version_; + int ping_reason_; + }; + // Checks for updates of all registered applications if it has been longer // than the last check time by NextCheckDelay() amount defined in the config. - void MaybeCheckForUpdates(base::OnceClosure callback); + void MaybeCheckForUpdates(); + + // Returns a list of apps registered with the updater. + std::vector<AppInfo> GetRegisteredApps(); + + void UnregisterMissingAppsDone(); // Provides a way to remove apps from the persisted data if the app is no // longer installed on the machine. - void UnregisterMissingApps(); + void UnregisterMissingApps(std::vector<AppInfo> apps); + + // After an uninstall ping has been processed, reduces the number of pings + // that we need to wait on before checking for updates. + void UninstallPingSent(update_client::Error error); + + // Returns true if there are uninstall ping tasks which haven't finished. + // Returns false if |number_of_pings_remaining_| is 0. + bool WaitingOnUninstallPings() const; + + // Returns a list of apps that need to be unregistered. + std::vector<ControlServiceImpl::PingInfo> GetAppIDsToRemove( + std::vector<AppInfo> apps); + + // Unregisters the apps in |app_ids_to_remove| and starts an update check + // if necessary. + void RemoveAppIDsAndSendUninstallPings( + std::vector<PingInfo> app_ids_to_remove); scoped_refptr<updater::Configurator> config_; scoped_refptr<updater::PersistedData> persisted_data_; + scoped_refptr<update_client::UpdateClient> update_client_; + base::OnceClosure callback_; + int number_of_pings_remaining_; }; } // namespace updater
diff --git a/chrome/updater/mac/BUILD.gn b/chrome/updater/mac/BUILD.gn index cd4176c..4369361 100644 --- a/chrome/updater/mac/BUILD.gn +++ b/chrome/updater/mac/BUILD.gn
@@ -87,6 +87,7 @@ deps = [ "//base", + "//chrome/updater:base", "//chrome/updater:version_header", ]
diff --git a/chrome/updater/mac/util.mm b/chrome/updater/mac/util.mm index 6e0c5325..e7224a3b 100644 --- a/chrome/updater/mac/util.mm +++ b/chrome/updater/mac/util.mm
@@ -4,13 +4,16 @@ #import "chrome/updater/mac/util.h" +#include <pwd.h> #include <unistd.h> +#include "base/files/file.h" #include "base/files/file_path.h" #include "base/logging.h" #include "base/mac/foundation_util.h" #include "base/version.h" #include "chrome/updater/updater_version.h" +#include "chrome/updater/util.h" namespace updater { namespace { @@ -68,4 +71,41 @@ .Append(FILE_PATH_LITERAL(PRODUCT_FULLNAME_STRING)); } +bool PathOwnedByUser(const base::FilePath& path) { + struct passwd* result = nullptr; + struct passwd user_info = {}; + char pwbuf[2048] = {}; + uid_t user_uid = geteuid(); + + int error = getpwuid_r(user_uid, &user_info, pwbuf, sizeof(pwbuf), &result); + + if (error) { + VLOG(1) << "Failed to get user info."; + return true; + } + + if (result == nullptr) { + VLOG(1) << "No entry for user."; + return true; + } + + base::stat_wrapper_t stat_info = {}; + if (base::File::Lstat(path.value().c_str(), &stat_info) != 0) { + DPLOG(ERROR) << "Failed to get information on path " << path.value(); + return false; + } + + if (S_ISLNK(stat_info.st_mode)) { + DLOG(ERROR) << "Path " << path.value() << " is a symbolic link."; + return false; + } + + if (stat_info.st_uid != user_uid) { + DLOG(ERROR) << "Path " << path.value() << " is owned by the wrong user."; + return false; + } + + return true; +} + } // namespace updater
diff --git a/chrome/updater/persisted_data.cc b/chrome/updater/persisted_data.cc index 5d5e375..1517d49 100644 --- a/chrome/updater/persisted_data.cc +++ b/chrome/updater/persisted_data.cc
@@ -67,7 +67,14 @@ base::FilePath PersistedData::GetExistenceCheckerPath( const std::string& id) const { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - return base::FilePath().AppendASCII(GetString(id, kECP)); +#if defined(OS_WIN) + base::FilePath::StringType ecp; + const std::string str = GetString(id, kECP); + return base::UTF8ToWide(str.c_str(), str.size(), &ecp) ? base::FilePath(ecp) + : base::FilePath(); +#else + return base::FilePath(GetString(id, kECP)); +#endif // OS_WIN } void PersistedData::SetExistenceCheckerPath(const std::string& id,
diff --git a/chrome/updater/test/integration_tests.cc b/chrome/updater/test/integration_tests.cc index 7e920b3..26f75e1 100644 --- a/chrome/updater/test/integration_tests.cc +++ b/chrome/updater/test/integration_tests.cc
@@ -18,6 +18,7 @@ #include "chrome/updater/constants.h" #include "chrome/updater/persisted_data.h" #include "chrome/updater/prefs.h" +#include "chrome/updater/registration_data.h" #include "chrome/updater/test/test_app/constants.h" #include "chrome/updater/test/test_app/test_app_version.h" #include "chrome/updater/updater_version.h" @@ -181,6 +182,75 @@ ExpectActive(); Uninstall(); } + +TEST_F(IntegrationTest, UnregisterUninstalledApp) { + RegisterTestApp(); + ExpectInstalled(); + ExpectActiveVersion(UPDATER_VERSION_STRING); + ExpectActive(); + + { + std::unique_ptr<GlobalPrefs> global_prefs = CreateGlobalPrefs(); + auto persisted_data = + base::MakeRefCounted<PersistedData>(global_prefs->GetPrefService()); + base::FilePath fake_ecp = + persisted_data->GetExistenceCheckerPath(kTestAppId) + .Append(FILE_PATH_LITERAL("NOT_THERE")); + persisted_data->SetExistenceCheckerPath(kTestAppId, fake_ecp); + + PrefsCommitPendingWrites(global_prefs->GetPrefService()); + + EXPECT_EQ(fake_ecp.value(), + persisted_data->GetExistenceCheckerPath(kTestAppId).value()); + } + + RunWake(0); + + { + std::unique_ptr<GlobalPrefs> global_prefs = CreateGlobalPrefs(); + auto persisted_data = + base::MakeRefCounted<PersistedData>(global_prefs->GetPrefService()); + EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("")).value(), + persisted_data->GetExistenceCheckerPath(kTestAppId).value()); + } + + Uninstall(); + Clean(); +} + +TEST_F(IntegrationTest, UnregisterUnownedApp) { + RegisterTestApp(); + ExpectInstalled(); + ExpectActiveVersion(UPDATER_VERSION_STRING); + ExpectActive(); + + { + std::unique_ptr<GlobalPrefs> global_prefs = CreateGlobalPrefs(); + auto persisted_data = + base::MakeRefCounted<PersistedData>(global_prefs->GetPrefService()); + base::FilePath fake_ecp{FILE_PATH_LITERAL("/Library")}; + persisted_data->SetExistenceCheckerPath(kTestAppId, fake_ecp); + + PrefsCommitPendingWrites(global_prefs->GetPrefService()); + + EXPECT_EQ(fake_ecp.value(), + persisted_data->GetExistenceCheckerPath(kTestAppId).value()); + } + + RunWake(0); + + { + std::unique_ptr<GlobalPrefs> global_prefs = CreateGlobalPrefs(); + auto persisted_data = + base::MakeRefCounted<PersistedData>(global_prefs->GetPrefService()); + EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("")).value(), + persisted_data->GetExistenceCheckerPath(kTestAppId).value()); + } + + Uninstall(); + Clean(); +} + #endif // OS_MAC #if defined(OS_WIN)
diff --git a/chrome/updater/util.h b/chrome/updater/util.h index 72564c5..67ce73c 100644 --- a/chrome/updater/util.h +++ b/chrome/updater/util.h
@@ -37,6 +37,9 @@ // %localappdata%\Chromium\ChromiumUpdater\1.2.3.4 for a User install. bool GetVersionedDirectory(base::FilePath* path); +// Returns true if the user running the updater also owns the |path|. +bool PathOwnedByUser(const base::FilePath& path); + // Initializes logging for an executable. void InitLogging(const base::FilePath::StringType& filename);
diff --git a/chrome/updater/win/util.cc b/chrome/updater/win/util.cc index d507832..a95d4805 100644 --- a/chrome/updater/win/util.cc +++ b/chrome/updater/win/util.cc
@@ -387,4 +387,10 @@ return token_handle; } +bool PathOwnedByUser(const base::FilePath& path) { + // TODO(crbug.com/1147094): Implement for Win. + + return true; +} + } // namespace updater
diff --git a/chromecast/media/cma/backend/desktop/mixer_output_stream_desktop.cc b/chromecast/media/cma/backend/desktop/mixer_output_stream_desktop.cc index c0318b00..998ed2d 100644 --- a/chromecast/media/cma/backend/desktop/mixer_output_stream_desktop.cc +++ b/chromecast/media/cma/backend/desktop/mixer_output_stream_desktop.cc
@@ -14,7 +14,7 @@ namespace media { constexpr base::TimeDelta kTargetWritePeriod = - base::TimeDelta::FromMilliseconds(10); + base::TimeDelta::FromMilliseconds(20); bool MixerOutputStreamDesktop::Start(int requested_sample_rate, int channels) { sample_rate_ = requested_sample_rate;
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd index 6e38157..e06a96e7 100644 --- a/chromeos/chromeos_strings.grd +++ b/chromeos/chromeos_strings.grd
@@ -570,6 +570,9 @@ <message name="IDS_SCANNING_APP_DONE_BUTTON_TEXT" desc="The text displayed for the button the user clicks to return to the intial Scanning App page after a scan job is completed."> Done </message> + <message name="IDS_SCANNING_APP_CANCEL_BUTTON_TEXT" desc="The text displayed for the button to cancel an ongoing scan job."> + Cancel + </message> <!-- Diagnostics App --> <!-- TODO(michaelcheco): Update with finalized copies of the strings -->
diff --git a/chromeos/chromeos_strings_grd/IDS_SCANNING_APP_CANCEL_BUTTON_TEXT.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SCANNING_APP_CANCEL_BUTTON_TEXT.png.sha1 new file mode 100644 index 0000000..3e2817c --- /dev/null +++ b/chromeos/chromeos_strings_grd/IDS_SCANNING_APP_CANCEL_BUTTON_TEXT.png.sha1
@@ -0,0 +1 @@ +26cd0a3d5ec5481f7cdd51682119a76dd5402c55 \ No newline at end of file
diff --git a/chromeos/components/phonehub/connection_manager_impl.cc b/chromeos/components/phonehub/connection_manager_impl.cc index 5dd5c82..f157fba 100644 --- a/chromeos/components/phonehub/connection_manager_impl.cc +++ b/chromeos/components/phonehub/connection_manager_impl.cc
@@ -199,7 +199,7 @@ } void ConnectionManagerImpl::OnConnectionTimeout() { - PA_LOG(WARNING) << "AttemptionConnection() has timed out. Closing connection " + PA_LOG(WARNING) << "AttemptConnection() has timed out. Closing connection " << "attempt."; connection_attempt_.reset();
diff --git a/chromeos/components/scanning/mojom/scanning.mojom b/chromeos/components/scanning/mojom/scanning.mojom index eac7d5e..153b591 100644 --- a/chromeos/components/scanning/mojom/scanning.mojom +++ b/chromeos/components/scanning/mojom/scanning.mojom
@@ -105,6 +105,10 @@ // Called when the scan is complete. |success| indicates whether the scan // completed successfully. OnScanComplete(bool success); + + // Called when canceling the current scan job is complete. |success| + // indicates whether the scan job was cancelled successfully. + OnCancelComplete(bool success); }; // Interface used to obtain information about and interact with connected @@ -127,4 +131,8 @@ // |observer|. |success| indicates whether the scan started successfully. StartScan(mojo_base.mojom.UnguessableToken scanner_id, ScanSettings settings, pending_remote<ScanJobObserver> observer) => (bool success); + + // Attempts to cancel the currently running scan job. The success of the + // cancel attempt is reported through the ScanJobObserver. + CancelScan(); };
diff --git a/chromeos/components/scanning/resources/scanning_app.html b/chromeos/components/scanning/resources/scanning_app.html index 0f6b851..e756044 100644 --- a/chromeos/components/scanning/resources/scanning_app.html +++ b/chromeos/components/scanning/resources/scanning_app.html
@@ -143,9 +143,14 @@ </iron-collapse> <div class="scan-button-container"> <cr-button id="scanButton" class="action-button" on-click="onScanClick_" - disabled$="[[areSettingsDisabled_(appState_)]]"> + disabled$="[[areSettingsDisabled_(appState_)]]" + hidden$="[[shouldShowCancelButton_(appState_)]]"> [[i18n('scanButtonText')]] </cr-button> + <cr-button id="cancelButton" on-click="onCancelClick_" + hidden$="[[!shouldShowCancelButton_(appState_)]]"> + [[i18n('cancelButtonText')]] + </cr-button> </div> <p id="statusText">[[statusText_]]</p> </template>
diff --git a/chromeos/components/scanning/resources/scanning_app.js b/chromeos/components/scanning/resources/scanning_app.js index e71f6b2..e930019 100644 --- a/chromeos/components/scanning/resources/scanning_app.js +++ b/chromeos/components/scanning/resources/scanning_app.js
@@ -386,6 +386,28 @@ return this.i18n(fileSavedText); }, + /** @private */ + onCancelClick_() { + assert(this.appState_ === AppState.SCANNING); + + this.scanService_.cancelScan(); + this.setAppState_(AppState.READY); + }, + + /** + * Overrides chromeos.scanning.mojom.ScanJobObserverInterface. + * @param {boolean} success + */ + onCancelComplete(success) {}, + + /** + * @return {boolean} + * @private + */ + shouldShowCancelButton_() { + return this.appState_ === AppState.SCANNING; + }, + /** * Sets the app state if the state transition is allowed. * @param {!AppState} newState
diff --git a/chromeos/components/scanning/scanning_ui.cc b/chromeos/components/scanning/scanning_ui.cc index c10dad1..2d50326 100644 --- a/chromeos/components/scanning/scanning_ui.cc +++ b/chromeos/components/scanning/scanning_ui.cc
@@ -55,6 +55,7 @@ {"a4OptionText", IDS_SCANNING_APP_A4_OPTION_TEXT}, {"appTitle", IDS_SCANNING_APP_TITLE}, {"blackAndWhiteOptionText", IDS_SCANNING_APP_BLACK_AND_WHITE_OPTION_TEXT}, + {"cancelButtonText", IDS_SCANNING_APP_CANCEL_BUTTON_TEXT}, {"colorModeDropdownLabel", IDS_SCANNING_APP_COLOR_MODE_DROPDOWN_LABEL}, {"colorOptionText", IDS_SCANNING_APP_COLOR_OPTION_TEXT}, {"defaultSourceOptionText", IDS_SCANNING_APP_DEFAULT_SOURCE_OPTION_TEXT},
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc index 895cd68..93f7ad6c 100644 --- a/chromeos/constants/chromeos_features.cc +++ b/chromeos/constants/chromeos_features.cc
@@ -145,6 +145,10 @@ const base::Feature kBluetoothPhoneFilter{"BluetoothPhoneFilter", base::FEATURE_ENABLED_BY_DEFAULT}; +// Enable or disables running the Camera App as a System Web App. +const base::Feature kCameraSystemWebApp{"CameraSystemWebApp", + base::FEATURE_DISABLED_BY_DEFAULT}; + // If enabled, will use the CDM in the Chrome OS daemon rather than loading the // CDM using the library CDM interface. const base::Feature kCdmFactoryDaemon{"CdmFactoryDaemon",
diff --git a/chromeos/constants/chromeos_features.h b/chromeos/constants/chromeos_features.h index 5bb46456..4e47fe7 100644 --- a/chromeos/constants/chromeos_features.h +++ b/chromeos/constants/chromeos_features.h
@@ -80,6 +80,8 @@ COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const base::Feature kBluetoothPhoneFilter; COMPONENT_EXPORT(CHROMEOS_CONSTANTS) +extern const base::Feature kCameraSystemWebApp; +COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const base::Feature kCdmFactoryDaemon; COMPONENT_EXPORT(CHROMEOS_CONSTANTS) extern const base::Feature kChildSpecificSignin;
diff --git a/chromeos/services/ime/decoder/decoder_engine.cc b/chromeos/services/ime/decoder/decoder_engine.cc index c81bc8c..c27b1e3 100644 --- a/chromeos/services/ime/decoder/decoder_engine.cc +++ b/chromeos/services/ime/decoder/decoder_engine.cc
@@ -8,7 +8,6 @@ #include "base/files/file_path.h" #include "base/files/file_util.h" #include "chromeos/services/ime/constants.h" -#include "chromeos/services/ime/ime_decoder.h" #include "chromeos/services/ime/public/cpp/buildflags.h" namespace chromeos { @@ -66,12 +65,11 @@ DecoderEngine::~DecoderEngine() {} bool DecoderEngine::TryLoadDecoder() { - if (engine_main_entry_) - return true; - auto* decoder = ImeDecoder::GetInstance(); - if (decoder->GetStatus() == ImeDecoder::Status::kSuccess) { - engine_main_entry_ = decoder->CreateMainEntry(platform_); + if (decoder->GetStatus() == ImeDecoder::Status::kSuccess && + decoder->GetEntryPoints().isReady) { + decoder_entry_points_ = decoder->GetEntryPoints(); + decoder_entry_points_->initOnce(platform_); return true; } return false; @@ -86,7 +84,7 @@ // Activates an IME engine via the shared library. Passing a // |ClientDelegate| for engine instance created by the shared library to // make safe calls on the client. - if (engine_main_entry_->ActivateIme( + if (decoder_entry_points_->activateIme( ime_spec.c_str(), new ClientDelegate(ime_spec, std::move(remote)))) { decoder_channel_receivers_.Add(this, std::move(receiver)); @@ -102,8 +100,10 @@ } bool DecoderEngine::IsImeSupportedByDecoder(const std::string& ime_spec) { - return engine_main_entry_ && - engine_main_entry_->IsImeSupported(ime_spec.c_str()); + if (decoder_entry_points_) { + return decoder_entry_points_->support(ime_spec.c_str()); + } + return false; } void DecoderEngine::ProcessMessage(const std::vector<uint8_t>& message, @@ -112,8 +112,9 @@ std::vector<uint8_t> result; // Handle message via corresponding functions of loaded decoder. - if (engine_main_entry_) - engine_main_entry_->Process(message.data(), message.size()); + if (decoder_entry_points_) { + decoder_entry_points_->process(message.data(), message.size()); + } std::move(callback).Run(result); }
diff --git a/chromeos/services/ime/decoder/decoder_engine.h b/chromeos/services/ime/decoder/decoder_engine.h index 1a85603..9d857a6 100644 --- a/chromeos/services/ime/decoder/decoder_engine.h +++ b/chromeos/services/ime/decoder/decoder_engine.h
@@ -5,7 +5,9 @@ #ifndef CHROMEOS_SERVICES_IME_DECODER_DECODER_ENGINE_H_ #define CHROMEOS_SERVICES_IME_DECODER_DECODER_ENGINE_H_ +#include "base/optional.h" #include "base/scoped_native_library.h" +#include "chromeos/services/ime/ime_decoder.h" #include "chromeos/services/ime/input_engine.h" #include "chromeos/services/ime/public/cpp/shared_lib/interfaces.h" #include "chromeos/services/ime/public/mojom/input_engine.mojom.h" @@ -48,13 +50,10 @@ // Returns whether the decoder shared library supports this ime_spec. bool IsImeSupportedByDecoder(const std::string& ime_spec); - // Shared library handle of the implementation for input logic with decoders. - base::ScopedNativeLibrary library_; - - ImeEngineMainEntry* engine_main_entry_ = nullptr; - ImeCrosPlatform* platform_ = nullptr; + base::Optional<ImeDecoder::EntryPoints> decoder_entry_points_; + mojo::ReceiverSet<mojom::InputChannel> decoder_channel_receivers_; DISALLOW_COPY_AND_ASSIGN(DecoderEngine);
diff --git a/chromeos/services/ime/decoder/system_engine.h b/chromeos/services/ime/decoder/system_engine.h index 29929a2e..f5be8fa2 100644 --- a/chromeos/services/ime/decoder/system_engine.h +++ b/chromeos/services/ime/decoder/system_engine.h
@@ -57,9 +57,6 @@ void OnReply(const std::vector<uint8_t>& message, mojo::Remote<mojom::InputChannel>& remote); - // Shared library handle of the implementation for input logic with decoders. - base::ScopedNativeLibrary library_; - ImeEngineMainEntry* engine_main_entry_ = nullptr; ImeCrosPlatform* platform_ = nullptr;
diff --git a/chromeos/services/ime/ime_decoder.cc b/chromeos/services/ime/ime_decoder.cc index 47f0492..eabc794c 100644 --- a/chromeos/services/ime/ime_decoder.cc +++ b/chromeos/services/ime/ime_decoder.cc
@@ -45,6 +45,13 @@ break; } } + +// Check whether the crucial members of an EntryPoints are loaded. +bool isEntryPointsLoaded(ImeDecoder::EntryPoints entry) { + return (entry.initOnce && entry.support && entry.activateIme && + entry.process && entry.closeDecoder); +} + } // namespace ImeDecoder::ImeDecoder() : status_(Status::kUninitialized) { @@ -65,21 +72,35 @@ return; } + // TODO(b/172527471): Remove it when decoder DSO is uprevved. createMainEntry_ = reinterpret_cast<ImeMainEntryCreateFn>( library.GetFunctionPointer(IME_MAIN_ENTRY_CREATE_FN_NAME)); - if (!createMainEntry_) { + + // TODO(b/172527471): Create a macro to fetch function pointers. + entry_points_.initOnce = reinterpret_cast<ImeDecoderInitOnceFn>( + library.GetFunctionPointer("ImeDecoderInitOnce")); + entry_points_.support = reinterpret_cast<ImeDecoderSupportsFn>( + library.GetFunctionPointer("ImeDecoderSupports")); + entry_points_.activateIme = reinterpret_cast<ImeDecoderActivateImeFn>( + library.GetFunctionPointer("ImeDecoderActivateIme")); + entry_points_.process = reinterpret_cast<ImeDecoderProcessFn>( + library.GetFunctionPointer("ImeDecoderProcess")); + entry_points_.closeDecoder = reinterpret_cast<ImeDecoderCloseFn>( + library.GetFunctionPointer("ImeDecoderClose")); + if (!isEntryPointsLoaded(entry_points_)) { status_ = Status::kFunctionMissing; return; } + entry_points_.isReady = true; + // Optional function pointer. ImeEngineLoggerSetterFn loggerSetter = reinterpret_cast<ImeEngineLoggerSetterFn>( library.GetFunctionPointer("SetImeEngineLogger")); if (loggerSetter) { loggerSetter(ImeLoggerBridge); } else { - // Not a blocking issue yet. - LOG(ERROR) << "Failed to load SetImeEngineLogger function."; + LOG(WARNING) << "Failed to set a Chrome Logger for decoder DSO."; } library_ = std::move(library); @@ -97,10 +118,16 @@ return status_; } +// TODO(b/172527471): Remove it when decoder DSO is uprevved. ImeEngineMainEntry* ImeDecoder::CreateMainEntry(ImeCrosPlatform* platform) { - DCHECK(status_ == Status::kSuccess); + DCHECK(createMainEntry_); return createMainEntry_(platform); } +ImeDecoder::EntryPoints ImeDecoder::GetEntryPoints() { + DCHECK(status_ == Status::kSuccess); + return entry_points_; +} + } // namespace ime } // namespace chromeos
diff --git a/chromeos/services/ime/ime_decoder.h b/chromeos/services/ime/ime_decoder.h index 90ace22..a15f5f9e 100644 --- a/chromeos/services/ime/ime_decoder.h +++ b/chromeos/services/ime/ime_decoder.h
@@ -28,6 +28,19 @@ kFunctionMissing = 4, }; + // This contains the function pointers to the entry points for the loaded + // decoder shared library. + struct EntryPoints { + ImeDecoderInitOnceFn initOnce; + ImeDecoderSupportsFn support; + ImeDecoderActivateImeFn activateIme; + ImeDecoderProcessFn process; + ImeDecoderCloseFn closeDecoder; + + // Whether the EntryPoints is ready to use. + bool isReady; + }; + // Gets the singleton ImeDecoder. static ImeDecoder* GetInstance(); @@ -36,8 +49,12 @@ Status GetStatus() const; // Returns an instance of ImeEngineMainEntry from the IME shared library. + // TODO(b/172527471): Remove it when decoder DSO is uprevved. ImeEngineMainEntry* CreateMainEntry(ImeCrosPlatform* platform); + // Returns entry points of the loaded decoder shared library. + EntryPoints GetEntryPoints(); + private: friend class base::NoDestructor<ImeDecoder>; @@ -49,8 +66,13 @@ // Result of IME decoder DSO initialization. base::Optional<base::ScopedNativeLibrary> library_; + + // Function pointors from decoder DSO. + // TODO(b/172527471): Remove it when decoder DSO is uprevved. ImeMainEntryCreateFn createMainEntry_; + EntryPoints entry_points_; + DISALLOW_COPY_AND_ASSIGN(ImeDecoder); };
diff --git a/chromeos/services/ime/public/cpp/shared_lib/interfaces.h b/chromeos/services/ime/public/cpp/shared_lib/interfaces.h index 4968af8..694a6cb 100644 --- a/chromeos/services/ime/public/cpp/shared_lib/interfaces.h +++ b/chromeos/services/ime/public/cpp/shared_lib/interfaces.h
@@ -209,6 +209,7 @@ // This class is implemented in the shared library and processes messages from // clients of the IME service. The shared library will exposes its create // function to the IME service. +// DEPRECATED: Will be removed soon. class ImeEngineMainEntry { protected: virtual ~ImeEngineMainEntry() = default; @@ -252,6 +253,12 @@ // For use when bridging logs logged in IME shared library to Chrome logging. typedef void (*ImeEngineLoggerSetterFn)(ChromeLoggerFunc); +typedef void (*ImeDecoderInitOnceFn)(ImeCrosPlatform*); +typedef bool (*ImeDecoderSupportsFn)(const char*); +typedef bool (*ImeDecoderActivateImeFn)(const char*, ImeClientDelegate*); +typedef void (*ImeDecoderProcessFn)(const uint8_t*, size_t); +typedef void (*ImeDecoderCloseFn)(); + // Defined name of ImeMainEntryCreateFn exported from shared library. #define IME_MAIN_ENTRY_CREATE_FN_NAME "CreateImeMainEntry"
diff --git a/components/keep_alive_registry/keep_alive_types.cc b/components/keep_alive_registry/keep_alive_types.cc index 86122c05..fbbf528a 100644 --- a/components/keep_alive_registry/keep_alive_types.cc +++ b/components/keep_alive_registry/keep_alive_types.cc
@@ -42,6 +42,8 @@ return out << "APP_LIST_SERVICE_VIEWS"; case KeepAliveOrigin::APP_LIST_SHOWER: return out << "APP_LIST_SHOWER"; + case KeepAliveOrigin::APP_UNINSTALLATION_FROM_OS_SETTINGS: + return out << "APP_UNINSTALLATION_FROM_OS_SETTINGS"; case KeepAliveOrigin::CHROME_APP_DELEGATE: return out << "CHROME_APP_DELEGATE"; case KeepAliveOrigin::CHROME_VIEWS_DELEGATE:
diff --git a/components/keep_alive_registry/keep_alive_types.h b/components/keep_alive_registry/keep_alive_types.h index 4183281..3697ba16 100644 --- a/components/keep_alive_registry/keep_alive_types.h +++ b/components/keep_alive_registry/keep_alive_types.h
@@ -49,6 +49,7 @@ // c/b/ui APP_LIST_SERVICE_VIEWS, APP_LIST_SHOWER, + APP_UNINSTALLATION_FROM_OS_SETTINGS, CHROME_APP_DELEGATE, CHROME_VIEWS_DELEGATE, PANEL,
diff --git a/components/messages/android/java/res/layout/message_banner_view.xml b/components/messages/android/java/res/layout/message_banner_view.xml index 47d6a84..2d25077 100644 --- a/components/messages/android/java/res/layout/message_banner_view.xml +++ b/components/messages/android/java/res/layout/message_banner_view.xml
@@ -9,15 +9,19 @@ <org.chromium.components.messages.MessageBannerView xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" tools:ignore="UnusedResources" android:layout_height="@dimen/message_banner_height" android:layout_width="match_parent" + android:layout_gravity="center_horizontal" android:orientation="horizontal" android:layout_marginTop="@dimen/message_shadow_top_margin" android:layout_marginBottom="@dimen/message_shadow_bottom_margin" android:layout_marginStart="@dimen/message_shadow_lateral_margin" android:layout_marginEnd="@dimen/message_shadow_lateral_margin" android:elevation="@dimen/message_banner_elevation" + app:maxWidthPortrait="@dimen/message_max_width" + app:maxWidthLandscape="@dimen/message_max_width" android:background="@drawable/message_bg_tinted"> <ImageView
diff --git a/components/messages/android/java/res/values/dimens.xml b/components/messages/android/java/res/values/dimens.xml index 42ab861f..9a37fb7d 100644 --- a/components/messages/android/java/res/values/dimens.xml +++ b/components/messages/android/java/res/values/dimens.xml
@@ -18,5 +18,6 @@ <dimen name="message_shadow_bottom_margin">16dp</dimen> <dimen name="message_bubble_inset">8dp</dimen> <dimen name="message_hide_threshold">16dp</dimen> + <dimen name="message_max_width">380dp</dimen> </resources> \ No newline at end of file
diff --git a/components/messages/android/java/src/org/chromium/components/messages/MessageBannerView.java b/components/messages/android/java/src/org/chromium/components/messages/MessageBannerView.java index a749de02..44404ed3 100644 --- a/components/messages/android/java/src/org/chromium/components/messages/MessageBannerView.java +++ b/components/messages/android/java/src/org/chromium/components/messages/MessageBannerView.java
@@ -11,18 +11,18 @@ import android.view.MotionEvent; import android.view.View; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.Nullable; +import org.chromium.components.browser_ui.widget.BoundedLinearLayout; import org.chromium.components.browser_ui.widget.gesture.SwipeGestureListener; import org.chromium.components.browser_ui.widget.gesture.SwipeGestureListener.SwipeHandler; /** * View representing the message banner. */ -public class MessageBannerView extends LinearLayout { +public class MessageBannerView extends BoundedLinearLayout { private ImageView mIconView; private TextView mTitle; private TextView mDescription;
diff --git a/components/no_state_prefetch/browser/BUILD.gn b/components/no_state_prefetch/browser/BUILD.gn index e3702c1..af27de6 100644 --- a/components/no_state_prefetch/browser/BUILD.gn +++ b/components/no_state_prefetch/browser/BUILD.gn
@@ -54,13 +54,15 @@ testonly = true sources = [ "prerender_history_unittest.cc", + "prerender_processor_impl_unittest.cc", "prerender_util_unittest.cc", ] deps = [ - ":browser", "//base", "//base/test:test_support", + "//components/no_state_prefetch/browser", + "//content/test:test_support", "//google_apis:google_apis", "//testing/gtest", "//url:url",
diff --git a/components/no_state_prefetch/browser/DEPS b/components/no_state_prefetch/browser/DEPS index 85f97c0..2857626e 100644 --- a/components/no_state_prefetch/browser/DEPS +++ b/components/no_state_prefetch/browser/DEPS
@@ -5,6 +5,7 @@ "+components/keyed_service/core", "+content/public/browser", "+content/public/common", + "+content/public/test", "+mojo/public", "+net/http", "+services/metrics/public/cpp",
diff --git a/components/no_state_prefetch/browser/prerender_link_manager.cc b/components/no_state_prefetch/browser/prerender_link_manager.cc index 30639e2..c78a146 100644 --- a/components/no_state_prefetch/browser/prerender_link_manager.cc +++ b/components/no_state_prefetch/browser/prerender_link_manager.cc
@@ -125,12 +125,6 @@ std::move(attributes), initiator_origin, std::move(processor_client), manager_->GetCurrentTimeTicks(), prerender_contents); - // Observe disconnect of the client to abandon the running prerender. The raw - // pointer to |this| is safe because |prerender| is owned by |this|. - prerender->remote_processor_client.set_disconnect_handler( - base::BindOnce(&PrerenderLinkManager::OnAbandonPrerender, - base::Unretained(this), prerender->prerender_id)); - // Stash pointer used only for comparison later. const LinkPrerender* prerender_ptr = prerender.get();
diff --git a/components/no_state_prefetch/browser/prerender_link_manager.h b/components/no_state_prefetch/browser/prerender_link_manager.h index 5fe79e12..9f93591 100644 --- a/components/no_state_prefetch/browser/prerender_link_manager.h +++ b/components/no_state_prefetch/browser/prerender_link_manager.h
@@ -40,7 +40,7 @@ // Called when a <link rel=prerender ...> element has been inserted into the // document. Returns the prerender id that is used for canceling or abandoning // prerendering. Returns base::nullopt if the prerender was not started. - base::Optional<int> OnStartPrerender( + virtual base::Optional<int> OnStartPrerender( int launcher_render_process_id, int launcher_render_view_id, blink::mojom::PrerenderAttributesPtr attributes, @@ -50,13 +50,13 @@ // Called when a <link rel=prerender ...> element has been explicitly removed // from a document. - void OnCancelPrerender(int prerender_id); + virtual void OnCancelPrerender(int prerender_id); // Called when a renderer launching <link rel=prerender ...> has navigated // away from the launching page, the launching renderer process has crashed, // or perhaps the renderer process was fast-closed when the last render view // in it was closed. - void OnAbandonPrerender(int prerender_id); + virtual void OnAbandonPrerender(int prerender_id); private: friend class PrerenderBrowserTest;
diff --git a/components/no_state_prefetch/browser/prerender_processor_impl.cc b/components/no_state_prefetch/browser/prerender_processor_impl.cc index 7649241f..ace3ac5 100644 --- a/components/no_state_prefetch/browser/prerender_processor_impl.cc +++ b/components/no_state_prefetch/browser/prerender_processor_impl.cc
@@ -10,7 +10,6 @@ #include "content/public/browser/render_process_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/common/referrer.h" -#include "mojo/public/cpp/bindings/self_owned_receiver.h" namespace prerender { @@ -18,11 +17,16 @@ int render_process_id, int render_frame_id, const url::Origin& initiator_origin, + mojo::PendingReceiver<blink::mojom::PrerenderProcessor> receiver, std::unique_ptr<PrerenderProcessorImplDelegate> delegate) : render_process_id_(render_process_id), render_frame_id_(render_frame_id), initiator_origin_(initiator_origin), - delegate_(std::move(delegate)) {} + delegate_(std::move(delegate)) { + receiver_.Bind(std::move(receiver)); + receiver_.set_disconnect_handler( + base::BindOnce(&PrerenderProcessorImpl::Abandon, base::Unretained(this))); +} PrerenderProcessorImpl::~PrerenderProcessorImpl() = default; @@ -31,11 +35,12 @@ content::RenderFrameHost* frame_host, mojo::PendingReceiver<blink::mojom::PrerenderProcessor> receiver, std::unique_ptr<PrerenderProcessorImplDelegate> delegate) { - mojo::MakeSelfOwnedReceiver( - std::make_unique<PrerenderProcessorImpl>( - frame_host->GetProcess()->GetID(), frame_host->GetRoutingID(), - frame_host->GetLastCommittedOrigin(), std::move(delegate)), - std::move(receiver)); + // PrerenderProcessorImpl is a self-owned object. This deletes itself on the + // mojo disconnect handler. + new PrerenderProcessorImpl(frame_host->GetProcess()->GetID(), + frame_host->GetRoutingID(), + frame_host->GetLastCommittedOrigin(), + std::move(receiver), std::move(delegate)); } void PrerenderProcessorImpl::Start( @@ -79,6 +84,15 @@ link_manager->OnCancelPrerender(*prerender_id_); } +void PrerenderProcessorImpl::Abandon() { + if (prerender_id_) { + auto* link_manager = GetPrerenderLinkManager(); + if (link_manager) + link_manager->OnAbandonPrerender(*prerender_id_); + } + delete this; +} + PrerenderLinkManager* PrerenderProcessorImpl::GetPrerenderLinkManager() { auto* render_frame_host = content::RenderFrameHost::FromID(render_process_id_, render_frame_id_);
diff --git a/components/no_state_prefetch/browser/prerender_processor_impl.h b/components/no_state_prefetch/browser/prerender_processor_impl.h index 09620613..dafe6f8a 100644 --- a/components/no_state_prefetch/browser/prerender_processor_impl.h +++ b/components/no_state_prefetch/browser/prerender_processor_impl.h
@@ -6,6 +6,7 @@ #define COMPONENTS_NO_STATE_PREFETCH_BROWSER_PRERENDER_PROCESSOR_IMPL_H_ #include "components/no_state_prefetch/browser/prerender_processor_impl_delegate.h" +#include "mojo/public/cpp/bindings/receiver.h" #include "third_party/blink/public/mojom/prerender/prerender.mojom.h" #include "url/origin.h" @@ -15,12 +16,17 @@ namespace prerender { +// PrerenderProcessorImpl implements blink::mojom::PrerenderProcessor and works +// as the browser-side entry point of NoStatePrefetch for <link rel=prerender>. +// This is a self-owned object and deletes itself when the mojo connection is +// lost. class PrerenderProcessorImpl : public blink::mojom::PrerenderProcessor { public: PrerenderProcessorImpl( int render_process_id, int render_frame_id, const url::Origin& initiator_origin, + mojo::PendingReceiver<blink::mojom::PrerenderProcessor> receiver, std::unique_ptr<PrerenderProcessorImplDelegate> delegate); ~PrerenderProcessorImpl() override; @@ -36,6 +42,10 @@ void Cancel() override; private: + // Abandons prerendering and deletes `this`. Called from the mojo disconnect + // handler. + void Abandon(); + PrerenderLinkManager* GetPrerenderLinkManager(); const int render_process_id_; @@ -46,6 +56,8 @@ // The ID of PrerenderLinkManager::LinkPrerender. Used for canceling or // abandoning prerendering. base::Optional<int> prerender_id_; + + mojo::Receiver<blink::mojom::PrerenderProcessor> receiver_{this}; }; } // namespace prerender
diff --git a/components/no_state_prefetch/browser/prerender_processor_impl_unittest.cc b/components/no_state_prefetch/browser/prerender_processor_impl_unittest.cc new file mode 100644 index 0000000..2c922864 --- /dev/null +++ b/components/no_state_prefetch/browser/prerender_processor_impl_unittest.cc
@@ -0,0 +1,176 @@ +// Copyright 2020 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 "components/no_state_prefetch/browser/prerender_processor_impl.h" + +#include "base/run_loop.h" +#include "components/no_state_prefetch/browser/prerender_link_manager.h" +#include "components/no_state_prefetch/browser/prerender_processor_impl_delegate.h" +#include "content/public/test/test_browser_context.h" +#include "content/public/test/test_renderer_host.h" +#include "third_party/blink/public/common/features.h" + +namespace prerender { + +class MockPrerenderLinkManager final : public PrerenderLinkManager { + public: + MockPrerenderLinkManager() : PrerenderLinkManager(/*manager=*/nullptr) {} + + base::Optional<int> OnStartPrerender( + int launcher_render_process_id, + int launcher_render_view_id, + blink::mojom::PrerenderAttributesPtr attributes, + const url::Origin& initiator_origin, + mojo::PendingRemote<blink::mojom::PrerenderProcessorClient> + processor_client) override { + DCHECK(!is_start_called_); + is_start_called_ = true; + return prerender_id_; + } + + void OnCancelPrerender(int prerender_id) override { + DCHECK_EQ(prerender_id_, prerender_id); + DCHECK(!is_cancel_called_); + is_cancel_called_ = true; + } + + void OnAbandonPrerender(int prerender_id) override { + DCHECK_EQ(prerender_id_, prerender_id); + DCHECK(!is_abandon_called_); + is_abandon_called_ = true; + } + + bool is_start_called() const { return is_start_called_; } + bool is_cancel_called() const { return is_cancel_called_; } + bool is_abandon_called() const { return is_abandon_called_; } + + private: + const int prerender_id_ = 100; + bool is_start_called_ = false; + bool is_cancel_called_ = false; + bool is_abandon_called_ = false; +}; + +class MockPrerenderProcessorImplDelegate final + : public PrerenderProcessorImplDelegate { + public: + explicit MockPrerenderProcessorImplDelegate( + MockPrerenderLinkManager* link_manager) + : link_manager_(link_manager) {} + + PrerenderLinkManager* GetPrerenderLinkManager( + content::BrowserContext* browser_context) override { + return link_manager_; + } + + private: + MockPrerenderLinkManager* link_manager_; +}; + +class PrerenderProcessorImplTest + : public content::RenderViewHostTestHarness, + public blink::mojom::PrerenderProcessorClient { + public: + // blink::mojom::PrerenderProcessorClient: + void OnPrerenderStart() override {} + void OnPrerenderStopLoading() override {} + void OnPrerenderDomContentLoaded() override {} + void OnPrerenderStop() override {} + + protected: + mojo::Receiver<blink::mojom::PrerenderProcessorClient> receiver_{this}; +}; + +TEST_F(PrerenderProcessorImplTest, StartCancelAbandon) { + auto link_manager = std::make_unique<MockPrerenderLinkManager>(); + + mojo::Remote<blink::mojom::PrerenderProcessor> remote; + PrerenderProcessorImpl::Create( + main_rfh(), remote.BindNewPipeAndPassReceiver(), + std::make_unique<MockPrerenderProcessorImplDelegate>(link_manager.get())); + + auto attributes = blink::mojom::PrerenderAttributes::New(); + attributes->url = GURL("https://example.com/prefetch"); + attributes->referrer = blink::mojom::Referrer::New(); + + // Start() call should be propagated to the link manager. + EXPECT_FALSE(link_manager->is_start_called()); + remote->Start(std::move(attributes), receiver_.BindNewPipeAndPassRemote()); + remote.FlushForTesting(); + EXPECT_TRUE(link_manager->is_start_called()); + + // Cancel() call should be propagated to the link manager. + EXPECT_FALSE(link_manager->is_cancel_called()); + remote->Cancel(); + remote.FlushForTesting(); + EXPECT_TRUE(link_manager->is_cancel_called()); + + // Connection lost should abandon the link manager. + EXPECT_FALSE(link_manager->is_abandon_called()); + remote.reset(); + // FlushForTesting() is no longer available. Instead, use base::RunLoop. + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(link_manager->is_abandon_called()); +} + +TEST_F(PrerenderProcessorImplTest, StartAbandon) { + auto link_manager = std::make_unique<MockPrerenderLinkManager>(); + + mojo::Remote<blink::mojom::PrerenderProcessor> remote; + PrerenderProcessorImpl::Create( + main_rfh(), remote.BindNewPipeAndPassReceiver(), + std::make_unique<MockPrerenderProcessorImplDelegate>(link_manager.get())); + + auto attributes = blink::mojom::PrerenderAttributes::New(); + attributes->url = GURL("https://example.com/prefetch"); + attributes->referrer = blink::mojom::Referrer::New(); + + // Start() call should be propagated to the link manager. + EXPECT_FALSE(link_manager->is_start_called()); + remote->Start(std::move(attributes), receiver_.BindNewPipeAndPassRemote()); + remote.FlushForTesting(); + EXPECT_TRUE(link_manager->is_start_called()); + + // Connection lost should abandon the link manager. + EXPECT_FALSE(link_manager->is_abandon_called()); + remote.reset(); + // FlushForTesting() is no longer available. Instead, use base::RunLoop. + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(link_manager->is_abandon_called()); +} + +TEST_F(PrerenderProcessorImplTest, Cancel) { + auto link_manager = std::make_unique<MockPrerenderLinkManager>(); + + mojo::Remote<blink::mojom::PrerenderProcessor> remote; + PrerenderProcessorImpl::Create( + main_rfh(), remote.BindNewPipeAndPassReceiver(), + std::make_unique<MockPrerenderProcessorImplDelegate>(link_manager.get())); + + // Call Cancel() before Start(). + EXPECT_FALSE(link_manager->is_cancel_called()); + remote->Cancel(); + remote.FlushForTesting(); + // The cancellation should not be propagated to the link manager. + EXPECT_FALSE(link_manager->is_cancel_called()); +} + +TEST_F(PrerenderProcessorImplTest, Abandon) { + auto link_manager = std::make_unique<MockPrerenderLinkManager>(); + + mojo::Remote<blink::mojom::PrerenderProcessor> remote; + PrerenderProcessorImpl::Create( + main_rfh(), remote.BindNewPipeAndPassReceiver(), + std::make_unique<MockPrerenderProcessorImplDelegate>(link_manager.get())); + + // Disconnect before Start(). + EXPECT_FALSE(link_manager->is_abandon_called()); + remote.reset(); + // FlushForTesting() is no longer available. Instead, use base::RunLoop. + base::RunLoop().RunUntilIdle(); + // The disconnection should not be propagated to the link manager. + EXPECT_FALSE(link_manager->is_abandon_called()); +} + +} // namespace prerender
diff --git a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestService.java b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestService.java index 9fb69d2..86a908c 100644 --- a/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestService.java +++ b/components/payments/content/android/java/src/org/chromium/components/payments/PaymentRequestService.java
@@ -369,14 +369,6 @@ } /** - * Sets that the payment details is no longer pending to be updated because the promise that - * was passed into PaymentRequest.show() has been resolved. - */ - public void resetWaitingForUpdatedDetails() { - mIsShowWaitingForUpdatedDetails = false; - } - - /** * Called to open a new PaymentHandler UI on the showing PaymentRequest. * @param url The url of the payment app to be displayed in the UI. * @return The WebContents of the payment handler that's just opened when the opening is @@ -557,7 +549,7 @@ * @param paymentApp The payment app to be invoked. * @param paymentResponseHelper The helper to create and fill the response to send to the * merchant. The helper should have this instance as the delegate {@link - * PaymentResponseHelperInterface.PaymentResponseRequesterDelegate}. + * PaymentResponseHelperInterface.PaymentResponseResultCallback}. */ public void invokePaymentApp( PaymentApp paymentApp, PaymentResponseHelperInterface paymentResponseHelper) { @@ -726,28 +718,12 @@ return mIsUserGestureShow; } - /** - * Records that PaymentRequest.show() was invoked with a user gesture. - * @param userGestureShow Whether it is invoked with a user gesture. - */ - public void setUserGestureShow(boolean userGestureShow) { - mIsUserGestureShow = userGestureShow; - } - /** @return Whether the current payment request service has called show(). */ public boolean isCurrentPaymentRequestShowing() { return mIsCurrentPaymentRequestShowing; } /** - * Records whether the current payment request service has called show(). - * @param isShowing Whether show() has been called. - */ - public void setCurrentPaymentRequestShowing(boolean isShowing) { - mIsCurrentPaymentRequestShowing = isShowing; - } - - /** * @return Whether all payment apps have been queried of canMakePayment() and * hasEnrolledInstrument(). */ @@ -866,11 +842,6 @@ } } - /** @return Whether the created payment apps includes any autofill payment app. */ - public boolean getHasNonAutofillApp() { - return mHasNonAutofillApp; - } - /** * @param methodDataList A list of PaymentMethodData. * @return The validated method data, a mapping of method names to its PaymentMethodData(s); @@ -1085,6 +1056,15 @@ */ /* package */ void retry(PaymentValidationErrors errors) { if (mBrowserPaymentRequest == null) return; + if (!PaymentValidator.validatePaymentValidationErrors(errors)) { + mJourneyLogger.setAborted(AbortReason.INVALID_DATA_FROM_RENDERER); + disconnectFromClientWithDebugMessage( + ErrorStrings.INVALID_VALIDATION_ERRORS, PaymentErrorReason.USER_CANCEL); + return; + } + assert mSpec != null; + assert !mSpec.isDestroyed() : "mSpec should not be used after being destroyed."; + mSpec.retry(errors); mBrowserPaymentRequest.retry(errors); } @@ -1216,11 +1196,6 @@ if (mClient != null) mClient.onPayerDetailChange(detail); } - /** Invokes {@link PaymentRequestClient.onError}. */ - public void onError(int error, String errorMessage) { - if (mClient != null) mClient.onError(error, errorMessage); - } - /** Invokes {@link PaymentRequestClient.warnNoFavicon}. */ public void warnNoFavicon() { if (mClient != null) mClient.warnNoFavicon();
diff --git a/content/browser/renderer_host/compositor_impl_android.cc b/content/browser/renderer_host/compositor_impl_android.cc index 5d60f752..44d35de 100644 --- a/content/browser/renderer_host/compositor_impl_android.cc +++ b/content/browser/renderer_host/compositor_impl_android.cc
@@ -367,6 +367,9 @@ void CompositorImpl::SetSurface(const base::android::JavaRef<jobject>& surface, bool can_be_used_with_surface_control) { + can_be_used_with_surface_control &= + !root_window_->ApplyDisableSurfaceControlWorkaround(); + JNIEnv* env = base::android::AttachCurrentThread(); gpu::GpuSurfaceTracker* tracker = gpu::GpuSurfaceTracker::Get();
diff --git a/content/browser/service_manager/service_manager_context.cc b/content/browser/service_manager/service_manager_context.cc index e361e18..d13cc27 100644 --- a/content/browser/service_manager/service_manager_context.cc +++ b/content/browser/service_manager/service_manager_context.cc
@@ -289,28 +289,6 @@ base::Token{}, base::Token::CreateRandom()), std::move(system_remote), metadata.BindNewPipeAndPassReceiver()); metadata->SetPID(base::GetCurrentProcId()); - - service_manager_->SetInstanceQuitCallback( - base::BindOnce(&OnInstanceQuitOnServiceManagerThread, - std::move(ui_thread_task_runner))); - } - - static void OnInstanceQuitOnServiceManagerThread( - scoped_refptr<base::SequencedTaskRunner> ui_thread_task_runner, - const service_manager::Identity& id) { - ui_thread_task_runner->PostTask(FROM_HERE, - base::BindOnce(&OnInstanceQuit, id)); - } - - static void OnInstanceQuit(const service_manager::Identity& id) { - if (GetContentClient()->browser()->ShouldTerminateOnServiceQuit(id)) { - // Don't LOG(FATAL) because we don't want a browser crash report. - LOG(ERROR) << "Terminating because service '" << id.name() - << "' quit unexpectedly."; - // Skip shutdown to reduce the risk that other code in the browser will - // respond to the service pipe closing. - exit(1); - } } void ShutDownOnServiceManagerThread() { @@ -370,8 +348,6 @@ // GetConnectorForIOThread(). g_io_thread_connector.Get() = system_connection->GetConnector()->Clone(); - GetContentClient()->browser()->WillStartServiceManager(); - in_process_context_->Start( manifests, std::move(system_remote), base::BindRepeating(&ServiceManagerContext::RunServiceInstance,
diff --git a/content/browser/xr/service/xr_runtime_manager_impl.cc b/content/browser/xr/service/xr_runtime_manager_impl.cc index fc6106d..a547590 100644 --- a/content/browser/xr/service/xr_runtime_manager_impl.cc +++ b/content/browser/xr/service/xr_runtime_manager_impl.cc
@@ -229,9 +229,11 @@ #endif #if BUILDFLAG(ENABLE_OPENXR) - auto* openxr = GetRuntime(device::mojom::XRDeviceId::OPENXR_DEVICE_ID); - if (openxr && openxr->SupportsArBlendMode()) - return openxr; + if (base::FeatureList::IsEnabled(features::kOpenXrExtendedFeatureSupport)) { + auto* openxr = GetRuntime(device::mojom::XRDeviceId::OPENXR_DEVICE_ID); + if (openxr && openxr->SupportsArBlendMode()) + return openxr; + } #endif return nullptr;
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc index 1ee7cc5..418501e 100644 --- a/content/public/browser/content_browser_client.cc +++ b/content/public/browser/content_browser_client.cc
@@ -721,11 +721,6 @@ const service_manager::Identity& identity, mojo::PendingReceiver<service_manager::mojom::Service>* receiver) {} -bool ContentBrowserClient::ShouldTerminateOnServiceQuit( - const service_manager::Identity& id) { - return false; -} - base::Optional<service_manager::Manifest> ContentBrowserClient::GetServiceManifestOverlay(base::StringPiece name) { return base::nullopt;
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h index 2806836..482b60a 100644 --- a/content/public/browser/content_browser_client.h +++ b/content/public/browser/content_browser_client.h
@@ -1086,9 +1086,6 @@ RenderProcessHost* render_process_host, mojo::GenericPendingReceiver receiver) {} - // Called just before the Service Manager is initialized. - virtual void WillStartServiceManager() {} - // Handles a service instance request for a new service instance with identity // |identity|. If the client knows how to run the named service, it should // bind |*receiver| accordingly, in the browser process. @@ -1101,11 +1098,6 @@ const service_manager::Identity& identity, mojo::PendingReceiver<service_manager::mojom::Service>* receiver); - // Allows the embedder to terminate the browser if a specific service instance - // quits or crashes. - virtual bool ShouldTerminateOnServiceQuit( - const service_manager::Identity& id); - // Allows the embedder to amend service manifests for existing services. // Specifically, the sets of exposed and required capabilities, interface // filter capabilities (deprecated), packaged services, and preloaded files
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc index 28c332a..112814b 100644 --- a/content/public/common/content_features.cc +++ b/content/public/common/content_features.cc
@@ -438,6 +438,14 @@ const base::Feature kOriginPolicy{"OriginPolicy", base::FEATURE_DISABLED_BY_DEFAULT}; +// Some WebXR features may have been enabled for ARCore, but are not yet ready +// to be plumbed up from the OpenXR backend. This feature provides a mechanism +// to gate such support in a generic way. Note that this feature should not be +// used for features we intend to ship simultaneously on both OpenXR and ArCore. +// For those features, a feature-specific flag should be created if needed. +const base::Feature kOpenXrExtendedFeatureSupport{ + "OpenXrExtendedFeatureSupport", base::FEATURE_DISABLED_BY_DEFAULT}; + // History navigation in response to horizontal overscroll (aka gesture-nav). const base::Feature kOverscrollHistoryNavigation { "OverscrollHistoryNavigation",
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h index 0e92e70..89259a3e 100644 --- a/content/public/common/content_features.h +++ b/content/public/common/content_features.h
@@ -99,6 +99,7 @@ CONTENT_EXPORT extern const base::Feature kNotificationTriggers; CONTENT_EXPORT extern const base::Feature kOriginIsolationHeader; CONTENT_EXPORT extern const base::Feature kOriginPolicy; +CONTENT_EXPORT extern const base::Feature kOpenXrExtendedFeatureSupport; CONTENT_EXPORT extern const base::Feature kOverscrollHistoryNavigation; CONTENT_EXPORT extern const base::Feature kPeriodicBackgroundSync; CONTENT_EXPORT extern const base::Feature kPermissionsPolicyHeader;
diff --git a/content/shell/browser/shell_content_browser_client.cc b/content/shell/browser/shell_content_browser_client.cc index 6512d180..a69f4c853 100644 --- a/content/shell/browser/shell_content_browser_client.cc +++ b/content/shell/browser/shell_content_browser_client.cc
@@ -200,13 +200,6 @@ return false; } -bool ShellContentBrowserClient::ShouldTerminateOnServiceQuit( - const service_manager::Identity& id) { - if (should_terminate_on_service_quit_callback_) - return std::move(should_terminate_on_service_quit_callback_).Run(id); - return false; -} - void ShellContentBrowserClient::AppendExtraCommandLineSwitches( base::CommandLine* command_line, int child_process_id) {
diff --git a/content/shell/browser/shell_content_browser_client.h b/content/shell/browser/shell_content_browser_client.h index ba39b6e..af0417d 100644 --- a/content/shell/browser/shell_content_browser_client.h +++ b/content/shell/browser/shell_content_browser_client.h
@@ -43,8 +43,6 @@ std::unique_ptr<BrowserMainParts> CreateBrowserMainParts( const MainFunctionParams& parameters) override; bool IsHandledURL(const GURL& url) override; - bool ShouldTerminateOnServiceQuit( - const service_manager::Identity& id) override; void AppendExtraCommandLineSwitches(base::CommandLine* command_line, int child_process_id) override; std::string GetAcceptLangs(BrowserContext* context) override; @@ -129,10 +127,6 @@ select_client_certificate_callback_ = std::move(select_client_certificate_callback); } - void set_should_terminate_on_service_quit_callback( - base::OnceCallback<bool(const service_manager::Identity&)> callback) { - should_terminate_on_service_quit_callback_ = std::move(callback); - } void set_login_request_callback( base::OnceCallback<void(bool is_main_frame)> login_request_callback) { login_request_callback_ = std::move(login_request_callback); @@ -183,8 +177,6 @@ static bool allow_any_cors_exempt_header_for_browser_; base::OnceClosure select_client_certificate_callback_; - base::OnceCallback<bool(const service_manager::Identity&)> - should_terminate_on_service_quit_callback_; base::OnceCallback<void(bool is_main_frame)> login_request_callback_; base::RepeatingCallback<void(const network::mojom::URLLoaderFactoryParams*, const url::Origin&,
diff --git a/docs/accessibility/chromevox_on_desktop_linux.md b/docs/accessibility/chromevox_on_desktop_linux.md index 07ef834..9f016cd 100644 --- a/docs/accessibility/chromevox_on_desktop_linux.md +++ b/docs/accessibility/chromevox_on_desktop_linux.md
@@ -85,9 +85,13 @@ Pick the latest version and ``` -gsutil cp gs://chromeos-localmirror/distfiles/espeak-ng-20180801.tar.gz /usr/share/chromeos-assets/speech_synthesis/espeak-ng/ -tar xvf /usr/share/chromeos-assets/speech_synthesis/espeak-ng/espeak-ng-20180801.tar.gz -rm /usr/share/chromeos-assets/speech_synthesis/espeak-ng/espeak-ng-20180801.tar.gz +TMPDIR=$(mktemp -d) +gsutil cp gs://chromeos-localmirror/distfiles/espeak-ng-20180801.tar.gz $TMPDIR +tar -C $TMPDIR -xvf ~/espeak-ng/espeak-ng-20180801.tar.gz +sudo mkdir -p /usr/share/chromeos-assets/speech_synthesis/espeak-ng/ +sudo chown -R $(whoami) /usr/share/chromeos-assets/ +cp -r $TMPDIR/espeak-ng/chrome-extension/* /usr/share/chromeos-assets/speech_synthesis/espeak-ng +rm -rf $TMPDIR ``` **Be sure to check permissions of /usr/share/chromeos-assets, some users report
diff --git a/extensions/browser/api/web_request/web_request_permissions.cc b/extensions/browser/api/web_request/web_request_permissions.cc index eaf6ed2b..54e16ea 100644 --- a/extensions/browser/api/web_request/web_request_permissions.cc +++ b/extensions/browser/api/web_request/web_request_permissions.cc
@@ -270,7 +270,9 @@ if (request.web_request_type != extensions::WebRequestResourceType::MAIN_FRAME && request.web_request_type != - extensions::WebRequestResourceType::SUB_FRAME) { + extensions::WebRequestResourceType::SUB_FRAME && + request.web_request_type != + extensions::WebRequestResourceType::OBJECT) { // TODO(crbug.com/1145496): Remove crash key logging once the DCHECK // failure below is fixed. static auto* web_request_type_key = base::debug::AllocateCrashKeyString( @@ -284,10 +286,7 @@ base::debug::ScopedCrashKeyString scoped_url( url_key, request.url.possibly_invalid_spec()); - DCHECK(request.web_request_type == - extensions::WebRequestResourceType::MAIN_FRAME || - request.web_request_type == - extensions::WebRequestResourceType::SUB_FRAME); + DCHECK(false); } // Hide sub-frame requests to clientsX.google.com.
diff --git a/fuchsia/engine/browser/web_engine_browser_context.cc b/fuchsia/engine/browser/web_engine_browser_context.cc index 03a6e9fa..6d83fa02 100644 --- a/fuchsia/engine/browser/web_engine_browser_context.cc +++ b/fuchsia/engine/browser/web_engine_browser_context.cc
@@ -12,25 +12,45 @@ #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/path_service.h" +#include "base/system/sys_info.h" #include "components/keyed_service/core/simple_key_map.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/resource_context.h" #include "fuchsia/engine/browser/web_engine_net_log_observer.h" #include "fuchsia/engine/browser/web_engine_permission_delegate.h" +#include "fuchsia/engine/switches.h" #include "media/capabilities/in_memory_video_decode_stats_db_impl.h" #include "media/mojo/services/video_decode_perf_history.h" #include "services/network/public/cpp/network_switches.h" -class WebEngineBrowserContext::ResourceContext - : public content::ResourceContext { - public: - ResourceContext() = default; - ~ResourceContext() override = default; +namespace { - private: - DISALLOW_COPY_AND_ASSIGN(ResourceContext); -}; +// Determines whether a data directory is configured, and returns its path. +// Passes the quota, if specified, for SysInfo to report as total disk space. +base::FilePath InitializeDataDirectoryAndQuotaFromCommandLine() { + base::FilePath data_directory_path; + + const base::CommandLine* command_line = + base::CommandLine::ForCurrentProcess(); + if (!base::PathService::Get(base::DIR_APP_DATA, &data_directory_path) || + !base::PathExists(data_directory_path)) { + // Run in incognito mode if /data doesn't exist. + return base::FilePath(); + } + + if (command_line->HasSwitch(switches::kDataQuotaBytes)) { + // Configure SysInfo to use the specified quota as the total-disk-space + // for the |data_dir_path_|. + uint64_t quota_bytes = 0; + CHECK(base::StringToUint64( + command_line->GetSwitchValueASCII(switches::kDataQuotaBytes), + "a_bytes)); + base::SysInfo::SetAmountOfTotalDiskSpace(data_directory_path, quota_bytes); + } + + return data_directory_path; +} std::unique_ptr<WebEngineNetLogObserver> CreateNetLogObserver() { std::unique_ptr<WebEngineNetLogObserver> result; @@ -46,16 +66,25 @@ return result; } +} // namespace + +class WebEngineBrowserContext::ResourceContext + : public content::ResourceContext { + public: + ResourceContext() = default; + ~ResourceContext() override = default; + + private: + DISALLOW_COPY_AND_ASSIGN(ResourceContext); +}; + WebEngineBrowserContext::WebEngineBrowserContext(bool force_incognito) : net_log_observer_(CreateNetLogObserver()), resource_context_(new ResourceContext()) { if (!force_incognito) { - base::PathService::Get(base::DIR_APP_DATA, &data_dir_path_); - if (!base::PathExists(data_dir_path_)) { - // Run in incognito mode if /data doesn't exist. - data_dir_path_.clear(); - } + data_dir_path_ = InitializeDataDirectoryAndQuotaFromCommandLine(); } + simple_factory_key_ = std::make_unique<SimpleFactoryKey>(GetPath(), IsOffTheRecord()); SimpleKeyMap::GetInstance()->Associate(this, simple_factory_key_.get());
diff --git a/fuchsia/engine/switches.cc b/fuchsia/engine/switches.cc index 4cfc7d2..f51e111 100644 --- a/fuchsia/engine/switches.cc +++ b/fuchsia/engine/switches.cc
@@ -19,5 +19,6 @@ const char kEnableCastStreamingReceiver[] = "enable-cast-streaming-receiver"; const char kCdmDataDirectory[] = "cdm-data-directory"; const char kUseLegacyAndroidUserAgent[] = "use-legacy-android-user-agent"; +const char kDataQuotaBytes[] = "data-quota-bytes"; } // namespace switches
diff --git a/fuchsia/engine/switches.h b/fuchsia/engine/switches.h index 9c76afd..a5bd8f87 100644 --- a/fuchsia/engine/switches.h +++ b/fuchsia/engine/switches.h
@@ -56,6 +56,9 @@ // Enables reporting of an Android-like User Agent string. extern const char kUseLegacyAndroidUserAgent[]; +// Soft quota to apply to the Context's persistent data directory, in bytes. +extern const char kDataQuotaBytes[]; + } // namespace switches #endif // FUCHSIA_ENGINE_SWITCHES_H_
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1 index bde94d3..018cdbd 100644 --- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1 +++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@ -2e70359a2f65abe783393044df9035e2e62e9435 \ No newline at end of file +107b86543d4590561ee78fd325cfa43432950e0c \ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1 index f4d3ac1b..70c2881 100644 --- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1 +++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@ -fd1950c178269ca145bd476ef49b835ae13a1210 \ No newline at end of file +2dde7818365e13a68254d3fc5b51ce976ef5241a \ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1 index 89cf27f0..3f36d51 100644 --- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1 +++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@ -bd8783ac9236d5e312417d9d3847874198b89344 \ No newline at end of file +caad13a3e31e7be41532f338e7acf8e79f563024 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1 index 5089c3ff..50886c0cc 100644 --- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1 +++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@ -7318b45a159d95a82c38b739124b9497f262c5ab \ No newline at end of file +f4692362b3eded8cdf8977fa16f7ce3078075726 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1 index e410881..e7c53ad 100644 --- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1 +++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@ -1a1f781a5343368ef49fc390c2c526d4be3edaa3 \ No newline at end of file +73df533b644903b48c71931a8a7f32648e3ddaf2 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1 index 006e048..257c738f2 100644 --- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1 +++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@ -cd187f7e5b1ebb8324b2b50b494a1d698ae7cbd2 \ No newline at end of file +9dc90738ee8b434b235ac81e83089f7c45016e91 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1 index 8291faab..9fe122d 100644 --- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1 +++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@ -27a7ec540cf98de940912f1c238258fa9e85f43b \ No newline at end of file +750a4c2a02668526966a190ebfd61e43e49c0988 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1 index 1ab74e5d..2b857e09 100644 --- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1 +++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@ -5289996c4dc6a18b53a184e30552ac3c3b0d0eb1 \ No newline at end of file +c671f6dd5161862879a3c0fd62837678e76a9623 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1 index a9028d00..b0f69ba 100644 --- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1 +++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.arm64.zip.sha1
@@ -1 +1 @@ -a0a065aa31a2f29119a781c29b4b239d92959c1f \ No newline at end of file +a58824c580aa0666ae2a1e801c59422813200642 \ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1 index 7408a14..4ecf484 100644 --- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1 +++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.x64.zip.sha1
@@ -1 +1 @@ -a42a2ccbde5f3c7fb81d8fcbaad9916c0ca60f7e \ No newline at end of file +2fef576903e43a1855b872ed2a16332542c88c4f \ No newline at end of file
diff --git a/media/gpu/BUILD.gn b/media/gpu/BUILD.gn index a26415e..473d255e 100644 --- a/media/gpu/BUILD.gn +++ b/media/gpu/BUILD.gn
@@ -297,6 +297,7 @@ ] } + public_deps = [] deps = [ ":buildflags", "//base", @@ -307,6 +308,17 @@ "//ui/gfx:memory_buffer", "//ui/gfx/geometry", ] + + if (is_ash && use_vaapi) { + assert(use_libgav1_parser) + sources += [ + "av1_decoder.cc", + "av1_decoder.h", + "av1_picture.cc", + "av1_picture.h", + ] + public_deps += [ "//third_party/libgav1:libgav1" ] + } } source_set("command_buffer_helper") { @@ -524,6 +536,12 @@ if (use_v4l2_codec || use_vaapi) { sources += [ "vp8_decoder_unittest.cc" ] } + + if (is_ash && use_vaapi) { + sources += [ "av1_decoder_unittest.cc" ] + deps += [ "//third_party/ffmpeg" ] + } + if (is_win && enable_library_cdms) { sources += [ "windows/d3d11_copying_texture_wrapper_unittest.cc",
diff --git a/media/gpu/av1_decoder.cc b/media/gpu/av1_decoder.cc new file mode 100644 index 0000000..1dbbe50 --- /dev/null +++ b/media/gpu/av1_decoder.cc
@@ -0,0 +1,450 @@ +// Copyright 2020 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/gpu/av1_decoder.h" + +#include <bitset> + +#include "base/callback_helpers.h" +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "base/stl_util.h" +#include "media/base/limits.h" +#include "media/gpu/av1_picture.h" +#include "third_party/libgav1/src/src/decoder_state.h" +#include "third_party/libgav1/src/src/gav1/status_code.h" +#include "third_party/libgav1/src/src/utils/constants.h" + +namespace media { +namespace { +// (Section 6.4.1): +// +// - "An operating point specifies which spatial and temporal layers should be +// decoded." +// +// - "The order of operating points indicates the preferred order for producing +// an output: a decoder should select the earliest operating point in the list +// that meets its decoding capabilities as expressed by the level associated +// with each operating point." +// +// For simplicity, we always select operating point 0 and will validate that it +// doesn't have scalability information. +constexpr unsigned int kDefaultOperatingPoint = 0; + +// Conversion function from libgav1 profiles to media::VideoCodecProfile. +VideoCodecProfile AV1ProfileToVideoCodecProfile( + libgav1::BitstreamProfile profile) { + switch (profile) { + case libgav1::kProfile0: + return AV1PROFILE_PROFILE_MAIN; + case libgav1::kProfile1: + return AV1PROFILE_PROFILE_HIGH; + case libgav1::kProfile2: + return AV1PROFILE_PROFILE_PRO; + default: + // ObuParser::ParseSequenceHeader() validates the profile. + NOTREACHED() << "Invalid profile: " << base::strict_cast<int>(profile); + return AV1PROFILE_PROFILE_MAIN; + } +} + +// Returns true iff the sequence has spatial or temporal scalability information +// for the selected operating point. +bool SequenceUsesScalability(int operating_point_idc) { + return operating_point_idc != 0; +} + +bool IsYUV420Sequence(const libgav1::ColorConfig& color_config) { + return color_config.subsampling_x == 1 && color_config.subsampling_y == 1 && + !color_config.is_monochrome; +} +} // namespace + +AV1Decoder::AV1Decoder(std::unique_ptr<AV1Accelerator> accelerator, + VideoCodecProfile profile) + : buffer_pool_(std::make_unique<libgav1::BufferPool>( + /*on_frame_buffer_size_changed=*/nullptr, + /*get_frame_buffer=*/nullptr, + /*release_frame_buffer=*/nullptr, + /*callback_private_data=*/nullptr)), + state_(std::make_unique<libgav1::DecoderState>()), + accelerator_(std::move(accelerator)), + profile_(profile) { + ref_frames_.fill(nullptr); +} + +AV1Decoder::~AV1Decoder() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // |buffer_pool_| checks that all the allocated frames are released in its + // dtor. Explicitly destruct |state_| before |buffer_pool_| to release frames + // in |reference_frame| in |state_|. + state_.reset(); +} + +bool AV1Decoder::HasNewSequenceHeader() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(parser_); + const auto& obu_headers = parser_->obu_headers(); + const bool has_sequence_header = + std::find_if(obu_headers.begin(), obu_headers.end(), + [](const auto& obu_header) { + return obu_header.type == libgav1::kObuSequenceHeader; + }) != obu_headers.end(); + if (!has_sequence_header) + return false; + if (!current_sequence_header_) + return true; + return parser_->sequence_header().ParametersChanged( + *current_sequence_header_); +} + +bool AV1Decoder::Flush() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DVLOG(2) << "Decoder flush"; + Reset(); + return true; +} + +void AV1Decoder::Reset() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + ClearCurrentFrame(); + // Resetting current sequence header might not be necessary. But this violates + // AcceleratedVideoDecoder interface spec, "Reset any current state that may + // be cached in the accelerator." + // TODO(hiroh): We may want to change this interface spec so that a caller + // doesn't have to allocate video frames buffers every seek operation. + current_sequence_header_.reset(); + visible_rect_ = gfx::Rect(); + frame_size_ = gfx::Size(); + profile_ = VideoCodecProfile::VIDEO_CODEC_PROFILE_UNKNOWN; + stream_id_ = 0; + stream_ = nullptr; + stream_size_ = 0; + on_error_ = false; + + state_ = std::make_unique<libgav1::DecoderState>(); + ClearReferenceFrames(); + parser_.reset(); + + buffer_pool_ = std::make_unique<libgav1::BufferPool>( + /*on_frame_buffer_size_changed=*/nullptr, + /*get_frame_buffer=*/nullptr, + /*release_frame_buffer=*/nullptr, + /*callback_private_data=*/nullptr); +} + +void AV1Decoder::SetStream(int32_t id, const DecoderBuffer& decoder_buffer) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + stream_id_ = id; + stream_ = decoder_buffer.data(); + stream_size_ = decoder_buffer.data_size(); + ClearCurrentFrame(); + + parser_ = base::WrapUnique(new (std::nothrow) libgav1::ObuParser( + decoder_buffer.data(), decoder_buffer.data_size(), kDefaultOperatingPoint, + buffer_pool_.get(), state_.get())); + if (!parser_) { + on_error_ = true; + return; + } + + if (current_sequence_header_) + parser_->set_sequence_header(*current_sequence_header_); +} + +void AV1Decoder::ClearCurrentFrame() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + current_frame_.reset(); + current_frame_header_.reset(); +} + +AcceleratedVideoDecoder::DecodeResult AV1Decoder::Decode() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (on_error_) + return kDecodeError; + auto result = DecodeInternal(); + on_error_ = result == kDecodeError; + return result; +} + +AcceleratedVideoDecoder::DecodeResult AV1Decoder::DecodeInternal() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (!parser_) { + DLOG(ERROR) << "Decode() is called before SetStream()"; + return kDecodeError; + } + while (parser_->HasData() || current_frame_header_) { + base::ScopedClosureRunner clear_current_frame( + base::BindOnce(&AV1Decoder::ClearCurrentFrame, base::Unretained(this))); + if (!current_frame_header_) { + libgav1::StatusCode status_code = parser_->ParseOneFrame(¤t_frame_); + if (status_code != libgav1::kStatusOk) { + DLOG(WARNING) << "Failed to parse OBU: " + << libgav1::GetErrorString(status_code); + return kDecodeError; + } + if (!current_frame_) { + DLOG(WARNING) << "No frame found. Skipping the current stream"; + continue; + } + + current_frame_header_ = parser_->frame_header(); + // Detects if a new coded video sequence is starting. + // TODO(b/171853869): Replace HasNewSequenceHeader() with whatever + // libgav1::ObuParser provides for more than one sequence headers case. + if (HasNewSequenceHeader()) { + // TODO(b/171853869): Remove this check once libgav1::ObuParser does + // this check. + if (current_frame_header_->frame_type != libgav1::kFrameKey || + !current_frame_header_->show_frame || + current_frame_header_->show_existing_frame || + current_frame_->temporal_id() != 0) { + // Section 7.5. + DVLOG(1) + << "The first frame successive to sequence header OBU must be a " + << "keyframe with show_frame=1, show_existing_frame=0 and " + << "temporal_id=0"; + return kDecodeError; + } + if (SequenceUsesScalability( + parser_->sequence_header() + .operating_point_idc[kDefaultOperatingPoint])) { + DVLOG(3) << "Either temporal or spatial layer decoding is not " + << "supported"; + return kDecodeError; + } + + current_sequence_header_ = parser_->sequence_header(); + // TODO(hiroh): Expose the bit depth to let the AV1Decoder client + // perform these checks. + if (current_sequence_header_->color_config.bitdepth != 8) { + // TODO(hiroh): Handle 10/12 bit depth. + DVLOG(1) << "10/12 bit depth are not supported"; + return kDecodeError; + } + if (!IsYUV420Sequence(current_sequence_header_->color_config)) { + DVLOG(1) << "Only YUV 4:2:0 is supported"; + return kDecodeError; + } + + const gfx::Size new_frame_size( + base::strict_cast<int>(current_sequence_header_->max_frame_width), + base::strict_cast<int>(current_sequence_header_->max_frame_height)); + gfx::Rect new_visible_rect( + base::strict_cast<int>(current_frame_header_->render_width), + base::strict_cast<int>(current_frame_header_->render_height)); + const VideoCodecProfile new_profile = + AV1ProfileToVideoCodecProfile(current_sequence_header_->profile); + DCHECK(!new_frame_size.IsEmpty()); + if (!gfx::Rect(new_frame_size).Contains(new_visible_rect)) { + DVLOG(1) << "Render size exceeds picture size. render size: " + << new_visible_rect.ToString() + << ", picture size: " << new_frame_size.ToString(); + new_visible_rect = gfx::Rect(new_frame_size); + } + + ClearReferenceFrames(); + // Issues kConfigChange only if either the dimensions or profile is + // changed. + if (frame_size_ != new_frame_size || + visible_rect_ != new_visible_rect || profile_ != new_profile) { + frame_size_ = new_frame_size; + visible_rect_ = new_visible_rect; + profile_ = new_profile; + clear_current_frame.ReplaceClosure(base::DoNothing()); + return kConfigChange; + } + } + } + + if (!current_sequence_header_) { + // Decoding is not doable because we haven't received a sequence header. + // This occurs when seeking a video. + DVLOG(3) << "Discarded the current frame because no sequence header has " + << "been found yet"; + continue; + } + + DCHECK(current_frame_header_); + const auto& frame_header = *current_frame_header_; + if (frame_header.show_existing_frame) { + const size_t frame_to_show = + base::checked_cast<size_t>(frame_header.frame_to_show); + DCHECK_LE(0u, frame_to_show); + DCHECK_LT(frame_to_show, ref_frames_.size()); + if (!CheckAndCleanUpReferenceFrames()) { + DLOG(ERROR) << "The states of reference frames are different between" + << "|ref_frames_| and |state_->reference_frame|"; + return kDecodeError; + } + + auto pic = ref_frames_[frame_to_show]; + CHECK(pic); + pic = pic->Duplicate(); + if (!pic) { + DVLOG(1) << "Failed duplication"; + return kDecodeError; + } + + pic->set_bitstream_id(stream_id_); + if (!accelerator_->OutputPicture(*pic)) { + return kDecodeError; + } + + // libgav1::ObuParser sets |current_frame_| to the frame to show while + // |current_frame_header_| is the frame header of the currently parsed + // frame. If |current_frame_| is a keyframe, then refresh_frame_flags must + // be 0xff. Otherwise, refresh_frame_flags must be 0x00 (Section 5.9.2). + DCHECK(current_frame_->frame_type() == libgav1::kFrameKey || + current_frame_header_->refresh_frame_flags == 0x00); + DCHECK(current_frame_->frame_type() != libgav1::kFrameKey || + current_frame_header_->refresh_frame_flags == 0xff); + UpdateReferenceFrames(std::move(pic)); + continue; + } + + if (parser_->tile_buffers().empty()) { + // The last call to ParseOneFrame() didn't actually have any tile groups. + // This could happen in rare cases (for example, if there is a Metadata + // OBU after the TileGroup OBU). Ignore this case. + continue; + } + + const gfx::Size current_frame_size( + base::strict_cast<int>(frame_header.width), + base::strict_cast<int>(frame_header.height)); + if (current_frame_size != frame_size_) { + // TODO(hiroh): This must be handled in decoding spatial layer. + DVLOG(1) << "Resolution change in the middle of video sequence (i.e." + << " between sequence headers) is not supported"; + return kDecodeError; + } + if (current_frame_size.width() != + base::strict_cast<int>(frame_header.upscaled_width)) { + DVLOG(1) << "Super resolution is not supported"; + return kDecodeError; + } + const gfx::Rect current_visible_rect( + base::strict_cast<int>(frame_header.render_width), + base::strict_cast<int>(frame_header.render_height)); + if (current_visible_rect != visible_rect_) { + // TODO(andrescj): Handle the visible rectangle change in the middle of + // video sequence. + DVLOG(1) << "Visible rectangle change in the middle of video sequence" + << "(i.e. between sequence headers) is not supported"; + return kDecodeError; + } + + auto pic = accelerator_->CreateAV1Picture( + frame_header.film_grain_params.apply_grain); + if (!pic) { + clear_current_frame.ReplaceClosure(base::DoNothing()); + return kRanOutOfSurfaces; + } + + pic->set_visible_rect(current_visible_rect); + pic->set_bitstream_id(stream_id_); + pic->frame_header = frame_header; + // TODO(hiroh): Set color space and decrypt config. + if (!DecodeAndOutputPicture(std::move(pic), parser_->tile_buffers())) + return kDecodeError; + } + return kRanOutOfStreamData; +} + +void AV1Decoder::UpdateReferenceFrames(scoped_refptr<AV1Picture> pic) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(state_); + DCHECK(current_frame_header_); + const uint8_t refresh_frame_flags = + current_frame_header_->refresh_frame_flags; + DCHECK(current_frame_->frame_type() != libgav1::kFrameKey || + current_frame_header_->refresh_frame_flags == 0xff); + const std::bitset<libgav1::kNumReferenceFrameTypes> update_reference_frame( + refresh_frame_flags); + for (size_t i = 0; i < libgav1::kNumReferenceFrameTypes; ++i) { + if (update_reference_frame[i]) + ref_frames_[i] = pic; + } + state_->UpdateReferenceFrames(current_frame_, + base::strict_cast<int>(refresh_frame_flags)); +} + +void AV1Decoder::ClearReferenceFrames() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(state_); + ref_frames_.fill(nullptr); + // If AV1Decoder has decided to clear the reference frames, then ObuParser + // must have also decided to do so. + DCHECK_EQ(base::STLCount(state_->reference_frame, nullptr), + static_cast<int>(state_->reference_frame.size())); +} + +bool AV1Decoder::CheckAndCleanUpReferenceFrames() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(state_); + const auto& reference_valid = state_->reference_valid; + for (size_t i = 0; i < libgav1::kNumReferenceFrameTypes; ++i) { + if (reference_valid[i] && !ref_frames_[i]) + return false; + if (!reference_valid[i] && ref_frames_[i]) + ref_frames_[i].reset(); + } + return true; +} + +bool AV1Decoder::DecodeAndOutputPicture( + scoped_refptr<AV1Picture> pic, + const libgav1::Vector<libgav1::TileBuffer>& tile_buffers) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(pic); + DCHECK(current_sequence_header_); + DCHECK(stream_); + DCHECK_GT(stream_size_, 0u); + if (!CheckAndCleanUpReferenceFrames()) { + DLOG(ERROR) << "The states of reference frames are different between" + << "|ref_frames_| and |state_->reference_frame|"; + return false; + } + if (!accelerator_->SubmitDecode(*pic, *current_sequence_header_, ref_frames_, + tile_buffers, + base::make_span(stream_, stream_size_))) { + return false; + } + + if (pic->frame_header.show_frame && !accelerator_->OutputPicture(*pic)) + return false; + UpdateReferenceFrames(std::move(pic)); + return true; +} + +gfx::Size AV1Decoder::GetPicSize() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // TODO(hiroh): It should be safer to align this by 64 or 128 (depending on + // use_128x128_superblock) so that a driver doesn't touch out of the buffer. + return frame_size_; +} + +gfx::Rect AV1Decoder::GetVisibleRect() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return visible_rect_; +} + +VideoCodecProfile AV1Decoder::GetProfile() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return profile_; +} + +size_t AV1Decoder::GetRequiredNumOfPictures() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // TODO(hiroh): Double this value in the case of film grain sequence. + constexpr size_t kPicsInPipeline = limits::kMaxVideoFrames + 1; + return kPicsInPipeline + GetNumReferenceFrames(); +} + +size_t AV1Decoder::GetNumReferenceFrames() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return libgav1::kNumReferenceFrameTypes; +} +} // namespace media
diff --git a/media/gpu/av1_decoder.h b/media/gpu/av1_decoder.h new file mode 100644 index 0000000..a646df39 --- /dev/null +++ b/media/gpu/av1_decoder.h
@@ -0,0 +1,150 @@ +// Copyright 2020 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_GPU_AV1_DECODER_H_ +#define MEDIA_GPU_AV1_DECODER_H_ + +#include <array> +#include <memory> + +#include "base/containers/span.h" +#include "base/macros.h" +#include "base/sequence_checker.h" +#include "media/base/video_codecs.h" +#include "media/base/video_color_space.h" +#include "media/gpu/accelerated_video_decoder.h" +#include "media/gpu/media_gpu_export.h" +#include "third_party/libgav1/src/src/utils/constants.h" + +// For libgav1::RefCountedBufferPtr. +#include "third_party/libgav1/src/src/buffer_pool.h" +// For libgav1::ObuSequenceHeader. base::Optional demands ObuSequenceHeader to +// fulfill std::is_trivially_constructible if it is forward-declared. But +// ObuSequenceHeader doesn't. +#include "third_party/libgav1/src/src/obu_parser.h" + +namespace libgav1 { +struct DecoderState; +struct ObuFrameHeader; +template <typename T> +class Vector; +} // namespace libgav1 + +namespace media { +class AV1Picture; +using AV1ReferenceFrameVector = + std::array<scoped_refptr<AV1Picture>, libgav1::kNumReferenceFrameTypes>; + +// Clients of this class are expected to pass an AV1 OBU stream and are expected +// to provide an implementation of AV1Accelerator for offloading final steps +// of the decoding process. +// +// This class must be created, called and destroyed on a single thread, and +// does nothing internally on any other thread. +class MEDIA_GPU_EXPORT AV1Decoder : public AcceleratedVideoDecoder { + public: + class MEDIA_GPU_EXPORT AV1Accelerator { + public: + AV1Accelerator() = default; + virtual ~AV1Accelerator() = default; + AV1Accelerator(const AV1Accelerator&) = delete; + AV1Accelerator& operator=(const AV1Accelerator&) = delete; + + // Creates an AV1Picture that the AV1Decoder can use to store some of the + // information needed to request accelerated decoding. This picture is later + // passed when calling SubmitDecode() so that the AV1Accelerator can submit + // the decode request to the driver. It may also be stored for use as + // reference to decode other pictures. + // When a picture is no longer needed by the decoder, it will just drop + // its reference to it, and it may do so at any time. + // Note that this may return nullptr if the accelerator is not able to + // provide any new pictures at the given time. The decoder must handle this + // case and treat it as normal, returning kRanOutOfSurfaces from Decode(). + virtual scoped_refptr<AV1Picture> CreateAV1Picture(bool apply_grain) = 0; + + // Submits |pic| to the driver for accelerated decoding. The following + // parameters are also passed: + // - |sequence_header|: the current OBU sequence header. + // - |ref_frames|: the pictures used as reference for decoding |pic|. + // - |tile_buffers|: tile information. + // - |data|: the entire data of the DecoderBuffer set by + // AV1Decoder::SetStream(). + // Note that returning from this method does not mean that the decode + // process is finished, but the caller may drop its references to |pic| + // and |ref_frames| immediately, and |data| does not need to remain valid + // after this method returns. + // Returns true when successful, false otherwise. + virtual bool SubmitDecode( + const AV1Picture& pic, + const libgav1::ObuSequenceHeader& sequence_header, + const AV1ReferenceFrameVector& ref_frames, + const libgav1::Vector<libgav1::TileBuffer>& tile_buffers, + base::span<const uint8_t> data) = 0; + + // Schedules output (display) of |pic|. + // Note that returning from this method does not mean that |pic| has already + // been outputted (displayed), but guarantees that all pictures will be + // outputted in the same order as this method was called for them, and that + // they are decoded before outputting (assuming SubmitDecode() has been + // called for them beforehand). + // Returns true when successful, false otherwise. + virtual bool OutputPicture(const AV1Picture& pic) = 0; + }; + + AV1Decoder(std::unique_ptr<AV1Accelerator> accelerator, + VideoCodecProfile profile); + ~AV1Decoder() override; + AV1Decoder(const AV1Decoder&) = delete; + AV1Decoder& operator=(const AV1Decoder&) = delete; + + // AcceleratedVideoDecoder implementation. + void SetStream(int32_t id, const DecoderBuffer& decoder_buffer) override; + bool Flush() override WARN_UNUSED_RESULT; + void Reset() override; + DecodeResult Decode() override WARN_UNUSED_RESULT; + gfx::Size GetPicSize() const override; + gfx::Rect GetVisibleRect() const override; + VideoCodecProfile GetProfile() const override; + size_t GetRequiredNumOfPictures() const override; + size_t GetNumReferenceFrames() const override; + + private: + // Returns whether the current stream contains a new OBU sequence header. + bool HasNewSequenceHeader() const; + bool DecodeAndOutputPicture( + scoped_refptr<AV1Picture> pic, + const libgav1::Vector<libgav1::TileBuffer>& tile_buffers); + void UpdateReferenceFrames(scoped_refptr<AV1Picture> pic); + void ClearReferenceFrames(); + bool CheckAndCleanUpReferenceFrames(); + void ClearCurrentFrame(); + DecodeResult DecodeInternal(); + + bool on_error_ = false; + + std::unique_ptr<libgav1::BufferPool> buffer_pool_; + std::unique_ptr<libgav1::DecoderState> state_; + std::unique_ptr<libgav1::ObuParser> parser_; + + const std::unique_ptr<AV1Accelerator> accelerator_; + AV1ReferenceFrameVector ref_frames_; + + base::Optional<libgav1::ObuSequenceHeader> current_sequence_header_; + base::Optional<libgav1::ObuFrameHeader> current_frame_header_; + libgav1::RefCountedBufferPtr current_frame_; + + gfx::Rect visible_rect_; + gfx::Size frame_size_; + VideoCodecProfile profile_; + + int32_t stream_id_ = 0; + const uint8_t* stream_ = nullptr; + size_t stream_size_ = 0; + + SEQUENCE_CHECKER(sequence_checker_); +}; + +} // namespace media + +#endif // MEDIA_GPU_AV1_DECODER_H_
diff --git a/media/gpu/av1_decoder_unittest.cc b/media/gpu/av1_decoder_unittest.cc new file mode 100644 index 0000000..1878d8f --- /dev/null +++ b/media/gpu/av1_decoder_unittest.cc
@@ -0,0 +1,392 @@ +// Copyright 2020 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/gpu/av1_decoder.h" + +#include <string.h> + +#include <string> +#include <vector> + +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/numerics/safe_conversions.h" +#include "media/base/decoder_buffer.h" +#include "media/base/test_data_util.h" +#include "media/ffmpeg/ffmpeg_common.h" +#include "media/filters/ffmpeg_demuxer.h" +#include "media/filters/in_memory_url_protocol.h" +#include "media/filters/ivf_parser.h" +#include "media/gpu/av1_picture.h" +#include "media/media_buildflags.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/libgav1/src/src/obu_parser.h" +#include "third_party/libgav1/src/src/utils/constants.h" +#include "third_party/libgav1/src/src/utils/types.h" + +#if !BUILDFLAG(USE_CHROMEOS_MEDIA_ACCELERATION) +#error "This test requires Chrome OS media acceleration" +#endif +#include "media/gpu/chromeos/fourcc.h" + +using ::testing::_; +using ::testing::Return; + +namespace media { +namespace { + +class FakeAV1Picture : public AV1Picture { + public: + FakeAV1Picture() = default; + + protected: + ~FakeAV1Picture() override = default; + + private: + scoped_refptr<AV1Picture> CreateDuplicate() override { + return base::MakeRefCounted<FakeAV1Picture>(); + } +}; + +bool IsYUV420(int8_t subsampling_x, int8_t subsampling_y, bool is_monochrome) { + return subsampling_x == 1 && subsampling_y == 1 && !is_monochrome; +} + +MATCHER_P(SameAV1PictureInstance, av1_picture, "") { + return &arg == av1_picture.get(); +} + +MATCHER_P2(MatchesFrameSizeAndRenderSize, frame_size, render_size, "") { + const auto& frame_header = arg.frame_header; + return base::strict_cast<int>(frame_header.width) == frame_size.width() && + base::strict_cast<int>(frame_header.height) == frame_size.height() && + base::strict_cast<int>(frame_header.render_width) == + render_size.width() && + base::strict_cast<int>(frame_header.render_height) == + render_size.height(); +} + +MATCHER_P4(MatchesFrameHeader, + frame_size, + render_size, + show_existing_frame, + show_frame, + "") { + const auto& frame_header = arg.frame_header; + return base::strict_cast<int>(frame_header.width) == frame_size.width() && + base::strict_cast<int>(frame_header.height) == frame_size.height() && + base::strict_cast<int>(frame_header.render_width) == + render_size.width() && + base::strict_cast<int>(frame_header.render_height) == + render_size.height() && + frame_header.show_existing_frame == show_existing_frame && + frame_header.show_frame == show_frame; +} + +MATCHER_P4(MatchesYUV420SequenceHeader, + profile, + bitdepth, + max_frame_size, + film_grain_params_present, + "") { + return arg.profile == profile && arg.color_config.bitdepth == bitdepth && + base::strict_cast<int>(arg.max_frame_width) == + max_frame_size.width() && + base::strict_cast<int>(arg.max_frame_height) == + max_frame_size.height() && + arg.film_grain_params_present == film_grain_params_present && + IsYUV420(arg.color_config.subsampling_x, + arg.color_config.subsampling_y, + arg.color_config.is_monochrome); +} + +MATCHER(NonEmptyTileBuffers, "") { + return !arg.empty(); +} + +MATCHER_P(MatchesFrameData, decoder_buffer, "") { + return arg.data() == decoder_buffer->data() && + arg.size() == decoder_buffer->data_size(); +} + +class MockAV1Accelerator : public AV1Decoder::AV1Accelerator { + public: + MockAV1Accelerator() = default; + ~MockAV1Accelerator() override = default; + + MOCK_METHOD1(CreateAV1Picture, scoped_refptr<AV1Picture>(bool)); + MOCK_METHOD5(SubmitDecode, + bool(const AV1Picture&, + const libgav1::ObuSequenceHeader&, + const AV1ReferenceFrameVector&, + const libgav1::Vector<libgav1::TileBuffer>&, + base::span<const uint8_t>)); + MOCK_METHOD1(OutputPicture, bool(const AV1Picture&)); +}; + +class AV1DecoderTest : public ::testing::Test { + public: + using DecodeResult = AcceleratedVideoDecoder::DecodeResult; + + AV1DecoderTest() = default; + ~AV1DecoderTest() override = default; + void SetUp() override; + std::vector<DecodeResult> Decode(scoped_refptr<DecoderBuffer> buffer); + scoped_refptr<DecoderBuffer> ReadDecoderBuffer(const std::string& fname); + std::vector<scoped_refptr<DecoderBuffer>> ReadIVF(const std::string& fname); + std::vector<scoped_refptr<DecoderBuffer>> ReadWebm(const std::string& fname); + + protected: + base::FilePath GetTestFilePath(const std::string& fname) { + base::FilePath file_path(base::FilePath(base::FilePath::kCurrentDirectory) + .Append(base::FilePath::StringType(fname))); + if (base::PathExists(file_path)) { + return file_path; + } + return GetTestDataFilePath(fname); + } + + // Owned by |decoder_|. + MockAV1Accelerator* mock_accelerator_; + + std::unique_ptr<AV1Decoder> decoder_; + int32_t bitstream_id_ = 0; +}; + +void AV1DecoderTest::SetUp() { + auto accelerator = std::make_unique<MockAV1Accelerator>(); + mock_accelerator_ = accelerator.get(); + decoder_ = std::make_unique<AV1Decoder>(std::move(accelerator), + VIDEO_CODEC_PROFILE_UNKNOWN); +} + +std::vector<AcceleratedVideoDecoder::DecodeResult> AV1DecoderTest::Decode( + scoped_refptr<DecoderBuffer> buffer) { + decoder_->SetStream(bitstream_id_++, *buffer); + + std::vector<DecodeResult> results; + DecodeResult res; + do { + res = decoder_->Decode(); + results.push_back(res); + } while (res != DecodeResult::kDecodeError && + res != DecodeResult::kRanOutOfStreamData); + return results; +} // namespace + +scoped_refptr<DecoderBuffer> AV1DecoderTest::ReadDecoderBuffer( + const std::string& fname) { + auto input_file = GetTestFilePath(fname); + std::string bitstream; + + EXPECT_TRUE(base::ReadFileToString(input_file, &bitstream)); + auto buffer = DecoderBuffer::CopyFrom( + reinterpret_cast<const uint8_t*>(bitstream.data()), bitstream.size()); + EXPECT_TRUE(!!buffer); + return buffer; +} + +std::vector<scoped_refptr<DecoderBuffer>> AV1DecoderTest::ReadIVF( + const std::string& fname) { + std::string ivf_data; + auto input_file = GetTestFilePath(fname); + EXPECT_TRUE(base::ReadFileToString(input_file, &ivf_data)); + + IvfParser ivf_parser; + IvfFileHeader ivf_header{}; + EXPECT_TRUE( + ivf_parser.Initialize(reinterpret_cast<const uint8_t*>(ivf_data.data()), + ivf_data.size(), &ivf_header)); + EXPECT_EQ(ivf_header.fourcc, ComposeFourcc('A', 'V', '0', '1')); + + std::vector<scoped_refptr<DecoderBuffer>> buffers; + IvfFrameHeader ivf_frame_header{}; + const uint8_t* data; + while (ivf_parser.ParseNextFrame(&ivf_frame_header, &data)) { + buffers.push_back(DecoderBuffer::CopyFrom( + reinterpret_cast<const uint8_t*>(data), ivf_frame_header.frame_size)); + } + return buffers; +} + +std::vector<scoped_refptr<DecoderBuffer>> AV1DecoderTest::ReadWebm( + const std::string& fname) { + std::string webm_data; + auto input_file = GetTestFilePath(fname); + EXPECT_TRUE(base::ReadFileToString(input_file, &webm_data)); + + InMemoryUrlProtocol protocol( + reinterpret_cast<const uint8_t*>(webm_data.data()), webm_data.size(), + false); + FFmpegGlue glue(&protocol); + LOG_ASSERT(glue.OpenContext()); + int stream_index = -1; + for (unsigned int i = 0; i < glue.format_context()->nb_streams; ++i) { + const AVStream* stream = glue.format_context()->streams[i]; + const AVCodecParameters* codec_parameters = stream->codecpar; + const AVMediaType codec_type = codec_parameters->codec_type; + const AVCodecID codec_id = codec_parameters->codec_id; + if (codec_type == AVMEDIA_TYPE_VIDEO && codec_id == AV_CODEC_ID_AV1) { + stream_index = i; + break; + } + } + EXPECT_NE(stream_index, -1) << "No AV1 data found in " << input_file; + + std::vector<scoped_refptr<DecoderBuffer>> buffers; + AVPacket packet{}; + while (av_read_frame(glue.format_context(), &packet) >= 0) { + if (packet.stream_index == stream_index) + buffers.push_back(DecoderBuffer::CopyFrom(packet.data, packet.size)); + av_packet_unref(&packet); + } + return buffers; +} + +TEST_F(AV1DecoderTest, DecodeInvalidOBU) { + std::string kInvalidData = "ThisIsInvalidData"; + auto kInvalidBuffer = DecoderBuffer::CopyFrom( + reinterpret_cast<const uint8_t*>(kInvalidData.data()), + kInvalidData.size()); + std::vector<DecodeResult> results = Decode(kInvalidBuffer); + std::vector<DecodeResult> expected = {DecodeResult::kDecodeError}; + EXPECT_EQ(results, expected); +} + +TEST_F(AV1DecoderTest, DecodeEmptyOBU) { + auto kEmptyBuffer = base::MakeRefCounted<DecoderBuffer>(0); + std::vector<DecodeResult> results = Decode(kEmptyBuffer); + std::vector<DecodeResult> expected = {DecodeResult::kRanOutOfStreamData}; + EXPECT_EQ(results, expected); +} + +TEST_F(AV1DecoderTest, DecodeOneIFrame) { + constexpr gfx::Size kFrameSize(320, 240); + constexpr gfx::Size kRenderSize(320, 240); + constexpr auto kProfile = libgav1::BitstreamProfile::kProfile0; + const std::string kIFrame("av1-I-frame-320x240"); + scoped_refptr<DecoderBuffer> i_frame_buffer = ReadDecoderBuffer(kIFrame); + ASSERT_TRUE(!!i_frame_buffer); + auto av1_picture = base::MakeRefCounted<AV1Picture>(); + ::testing::InSequence s; + EXPECT_CALL(*mock_accelerator_, CreateAV1Picture(/*apply_grain=*/false)) + .WillOnce(Return(av1_picture)); + EXPECT_CALL( + *mock_accelerator_, + SubmitDecode( + MatchesFrameHeader(kFrameSize, kRenderSize, + /*show_existing_frame=*/false, + /*show_frame=*/true), + MatchesYUV420SequenceHeader(kProfile, /*bitdepth=*/8, kFrameSize, + /*film_grain_params_present=*/false), + _, NonEmptyTileBuffers(), MatchesFrameData(i_frame_buffer))) + .WillOnce(Return(true)); + EXPECT_CALL(*mock_accelerator_, + OutputPicture(SameAV1PictureInstance(av1_picture))) + .WillOnce(Return(true)); + std::vector<DecodeResult> results = Decode(i_frame_buffer); + std::vector<DecodeResult> expected = {DecodeResult::kConfigChange, + DecodeResult::kRanOutOfStreamData}; + EXPECT_EQ(results, expected); +} + +TEST_F(AV1DecoderTest, DecodeSimpleStream) { + constexpr gfx::Size kFrameSize(320, 240); + constexpr gfx::Size kRenderSize(320, 240); + constexpr auto kProfile = libgav1::BitstreamProfile::kProfile0; + const std::string kSimpleStream("bear-av1.webm"); + std::vector<scoped_refptr<DecoderBuffer>> buffers = ReadWebm(kSimpleStream); + ASSERT_FALSE(buffers.empty()); + std::vector<DecodeResult> expected = {DecodeResult::kConfigChange}; + std::vector<DecodeResult> results; + for (auto buffer : buffers) { + ::testing::InSequence sequence; + auto av1_picture = base::MakeRefCounted<AV1Picture>(); + EXPECT_CALL(*mock_accelerator_, CreateAV1Picture(/*apply_grain=*/false)) + .WillOnce(Return(av1_picture)); + EXPECT_CALL( + *mock_accelerator_, + SubmitDecode( + MatchesFrameHeader(kFrameSize, kRenderSize, + /*show_existing_frame=*/false, + /*show_frame=*/true), + MatchesYUV420SequenceHeader(kProfile, /*bitdepth=*/8, kFrameSize, + /*film_grain_params_present=*/false), + _, NonEmptyTileBuffers(), MatchesFrameData(buffer))) + .WillOnce(Return(true)); + EXPECT_CALL(*mock_accelerator_, + OutputPicture(SameAV1PictureInstance(av1_picture))) + .WillOnce(Return(true)); + for (DecodeResult r : Decode(buffer)) + results.push_back(r); + expected.push_back(DecodeResult::kRanOutOfStreamData); + testing::Mock::VerifyAndClearExpectations(mock_accelerator_); + } + EXPECT_EQ(results, expected); +} + +TEST_F(AV1DecoderTest, DecodeShowExistingPictureStream) { + constexpr gfx::Size kFrameSize(208, 144); + constexpr gfx::Size kRenderSize(208, 144); + constexpr auto kProfile = libgav1::BitstreamProfile::kProfile0; + constexpr size_t kDecodedFrames = 10; + constexpr size_t kOutputFrames = 10; + const std::string kShowExistingFrameStream("av1-show_existing_frame.ivf"); + std::vector<scoped_refptr<DecoderBuffer>> buffers = + ReadIVF(kShowExistingFrameStream); + ASSERT_FALSE(buffers.empty()); + + // TODO(hiroh): Test what's unique about the show_existing_frame path. + std::vector<DecodeResult> expected = {DecodeResult::kConfigChange}; + std::vector<DecodeResult> results; + EXPECT_CALL(*mock_accelerator_, CreateAV1Picture(/*apply_grain=*/false)) + .Times(kDecodedFrames) + .WillRepeatedly(Return(base::MakeRefCounted<FakeAV1Picture>())); + EXPECT_CALL( + *mock_accelerator_, + SubmitDecode( + MatchesFrameSizeAndRenderSize(kFrameSize, kRenderSize), + MatchesYUV420SequenceHeader(kProfile, /*bitdepth=*/8, kFrameSize, + /*film_grain_params_present=*/false), + _, NonEmptyTileBuffers(), _)) + .Times(kDecodedFrames) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*mock_accelerator_, OutputPicture(_)) + .Times(kOutputFrames) + .WillRepeatedly(Return(true)); + + for (auto buffer : buffers) { + for (DecodeResult r : Decode(buffer)) + results.push_back(r); + expected.push_back(DecodeResult::kRanOutOfStreamData); + } + EXPECT_EQ(results, expected); +} + +TEST_F(AV1DecoderTest, Decode10bitStream) { + const std::string k10bitStream("bear-av1-320x180-10bit.webm"); + std::vector<scoped_refptr<DecoderBuffer>> buffers = ReadWebm(k10bitStream); + ASSERT_FALSE(buffers.empty()); + std::vector<DecodeResult> expected = {DecodeResult::kDecodeError}; + EXPECT_EQ(Decode(buffers[0]), expected); + // Once AV1Decoder gets into an error state, Decode() returns kDecodeError + // until Reset(). + EXPECT_EQ(Decode(buffers[1]), expected); +} + +TEST_F(AV1DecoderTest, DecodeSVCStream) { + const std::string kSVCStream("av1-svc-L2T2.ivf"); + std::vector<scoped_refptr<DecoderBuffer>> buffers = ReadIVF(kSVCStream); + ASSERT_FALSE(buffers.empty()); + std::vector<DecodeResult> expected = {DecodeResult::kDecodeError}; + EXPECT_EQ(Decode(buffers[0]), expected); + // Once AV1Decoder gets into an error state, Decode() returns kDecodeError + // until Reset(). + EXPECT_EQ(Decode(buffers[1]), expected); +} + +// TODO(hiroh): Add more tests, non-YUV420 stream, Reset() flow, mid-stream +// configuration change, and reference frame tracking. +} // namespace +} // namespace media
diff --git a/media/gpu/av1_picture.cc b/media/gpu/av1_picture.cc new file mode 100644 index 0000000..393f1072 --- /dev/null +++ b/media/gpu/av1_picture.cc
@@ -0,0 +1,30 @@ +// Copyright 2020 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/gpu/av1_picture.h" +#include <memory> + +namespace media { +AV1Picture::AV1Picture() = default; +AV1Picture::~AV1Picture() = default; + +scoped_refptr<AV1Picture> AV1Picture::Duplicate() { + scoped_refptr<AV1Picture> dup_pic = CreateDuplicate(); + if (!dup_pic) + return nullptr; + + // Copy members of AV1Picture and CodecPicture. + // A proper bitstream id is set in AV1Decoder. + // Note that decrypt_config_ is not used in here, so skip copying it. + dup_pic->frame_header = frame_header; + dup_pic->set_bitstream_id(bitstream_id()); + dup_pic->set_visible_rect(visible_rect()); + dup_pic->set_colorspace(get_colorspace()); + return dup_pic; +} + +scoped_refptr<AV1Picture> AV1Picture::CreateDuplicate() { + return nullptr; +} +} // namespace media
diff --git a/media/gpu/av1_picture.h b/media/gpu/av1_picture.h new file mode 100644 index 0000000..505c9cd --- /dev/null +++ b/media/gpu/av1_picture.h
@@ -0,0 +1,38 @@ +// Copyright 2020 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_GPU_AV1_PICTURE_H_ +#define MEDIA_GPU_AV1_PICTURE_H_ + +#include <memory> + +#include "media/gpu/codec_picture.h" +#include "media/gpu/media_gpu_export.h" +#include "third_party/libgav1/src/src/utils/types.h" + +namespace media { +// AV1Picture carries the parsed frame header needed for decoding an AV1 frame. +// It also owns the decoded frame itself. +class MEDIA_GPU_EXPORT AV1Picture : public CodecPicture { + public: + AV1Picture(); + AV1Picture(const AV1Picture&) = delete; + AV1Picture& operator=(const AV1Picture&) = delete; + + // Create a duplicate instance and copy the data to it. It is used to support + // the AV1 show_existing_frame feature. Return the scoped_refptr pointing to + // the duplicate instance, or nullptr on failure. + scoped_refptr<AV1Picture> Duplicate(); + + libgav1::ObuFrameHeader frame_header = {}; + + protected: + ~AV1Picture() override; + + private: + // Create a duplicate instance. + virtual scoped_refptr<AV1Picture> CreateDuplicate(); +}; +} // namespace media +#endif // MEDIA_GPU_AV1_PICTURE_H_
diff --git a/media/gpu/mac/vt_video_encode_accelerator_mac.cc b/media/gpu/mac/vt_video_encode_accelerator_mac.cc index dce3bab..40301bf 100644 --- a/media/gpu/mac/vt_video_encode_accelerator_mac.cc +++ b/media/gpu/mac/vt_video_encode_accelerator_mac.cc
@@ -7,6 +7,7 @@ #include <memory> #include "base/logging.h" +#include "base/mac/mac_logging.h" #include "base/memory/shared_memory_mapping.h" #include "base/memory/unsafe_shared_memory_region.h" #include "base/threading/thread_task_runner_handle.h" @@ -142,6 +143,9 @@ DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(client); + // Clients are expected to call Flush() before reinitializing the encoder. + DCHECK_EQ(pending_encodes_, 0); + if (config.input_format != PIXEL_FORMAT_I420 && config.input_format != PIXEL_FORMAT_NV12) { DLOG(ERROR) << "Input format not supported= " @@ -254,6 +258,19 @@ delete this; } +void VTVideoEncodeAccelerator::Flush(FlushCallback flush_callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + + encoder_thread_task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&VTVideoEncodeAccelerator::FlushTask, + base::Unretained(this), std::move(flush_callback))); +} + +bool VTVideoEncodeAccelerator::IsFlushSupported() { + return true; +} + void VTVideoEncodeAccelerator::EncodeTask(scoped_refptr<VideoFrame> frame, bool force_keyframe) { DCHECK(encoder_thread_task_runner_->BelongsToCurrentThread()); @@ -284,12 +301,13 @@ // We can pass the ownership of |request| to the encode callback if // successful. Otherwise let it fall out of scope. OSStatus status = VTCompressionSessionEncodeFrame( - compression_session_, pixel_buffer, timestamp_cm, CMTime{0, 0, 0, 0}, + compression_session_, pixel_buffer, timestamp_cm, kCMTimeInvalid, frame_props, reinterpret_cast<void*>(request.get()), nullptr); if (status != noErr) { DLOG(ERROR) << " VTCompressionSessionEncodeFrame failed: " << status; NotifyError(kPlatformFailureError); } else { + ++pending_encodes_; CHECK(request.release()); } } @@ -360,8 +378,6 @@ // Cancel all encoder thread callbacks. encoder_task_weak_factory_.InvalidateWeakPtrs(); - - // This call blocks until all pending frames are flushed out. DestroyCompressionSession(); } @@ -408,6 +424,9 @@ std::unique_ptr<EncodeOutput> encode_output) { DCHECK(encoder_thread_task_runner_->BelongsToCurrentThread()); + --pending_encodes_; + DCHECK_GE(pending_encodes_, 0); + if (status != noErr) { DLOG(ERROR) << " encode failed: " << status; NotifyError(kPlatformFailureError); @@ -440,6 +459,7 @@ base::BindOnce(&Client::BitstreamBufferReady, client_, buffer_ref->id, BitstreamBufferMetadata( 0, false, encode_output->capture_timestamp))); + MaybeRunFlushCallback(); return; } @@ -466,6 +486,7 @@ &Client::BitstreamBufferReady, client_, buffer_ref->id, BitstreamBufferMetadata(used_buffer_size, keyframe, encode_output->capture_timestamp))); + MaybeRunFlushCallback(); } bool VTVideoEncodeAccelerator::ResetCompressionSession() { @@ -557,4 +578,48 @@ } } +void VTVideoEncodeAccelerator::FlushTask(FlushCallback flush_callback) { + DVLOG(3) << __func__; + DCHECK(encoder_thread_task_runner_->BelongsToCurrentThread()); + DCHECK(flush_callback); + + if (!compression_session_) { + client_task_runner_->PostTask( + FROM_HERE, base::BindOnce(std::move(flush_callback), false)); + return; + } + + // Even though this will block until all frames are returned, the frames will + // be posted to the current task runner, so we can't run the flush callback + // at this time. + OSStatus status = + VTCompressionSessionCompleteFrames(compression_session_, kCMTimeInvalid); + + if (status != noErr) { + OSSTATUS_DLOG(ERROR, status) + << " VTCompressionSessionCompleteFrames failed: " << status; + client_task_runner_->PostTask( + FROM_HERE, + base::BindOnce(std::move(flush_callback), /*success=*/false)); + return; + } + + pending_flush_cb_ = std::move(flush_callback); + MaybeRunFlushCallback(); +} + +void VTVideoEncodeAccelerator::MaybeRunFlushCallback() { + DCHECK(encoder_thread_task_runner_->BelongsToCurrentThread()); + + if (!pending_flush_cb_) + return; + + if (pending_encodes_ || !encoder_output_queue_.empty()) + return; + + client_task_runner_->PostTask( + FROM_HERE, + base::BindOnce(std::move(pending_flush_cb_), /*success=*/true)); +} + } // namespace media
diff --git a/media/gpu/mac/vt_video_encode_accelerator_mac.h b/media/gpu/mac/vt_video_encode_accelerator_mac.h index 5f955e53..70d1f76 100644 --- a/media/gpu/mac/vt_video_encode_accelerator_mac.h +++ b/media/gpu/mac/vt_video_encode_accelerator_mac.h
@@ -38,6 +38,8 @@ void RequestEncodingParametersChange(uint32_t bitrate, uint32_t framerate) override; void Destroy() override; + void Flush(FlushCallback flush_callback) override; + bool IsFlushSupported() override; private: // Holds the associated data of a video frame being processed. @@ -93,6 +95,11 @@ // encoding work). void DestroyCompressionSession(); + // Flushes the encoder. The flush callback won't be run until all pending + // encodes have been completed. + void FlushTask(FlushCallback flush_callback); + void MaybeRunFlushCallback(); + base::ScopedCFTypeRef<VTCompressionSessionRef> compression_session_; gfx::Size input_visible_size_; @@ -131,6 +138,11 @@ base::Thread encoder_thread_; scoped_refptr<base::SingleThreadTaskRunner> encoder_thread_task_runner_; + // Tracking information for ensuring flushes aren't completed until all + // pending encodes have been returned. + int pending_encodes_ = 0; + FlushCallback pending_flush_cb_; + // Declared last to ensure that all weak pointers are invalidated before // other destructors run. base::WeakPtr<VTVideoEncodeAccelerator> encoder_weak_ptr_;
diff --git a/media/gpu/vaapi/BUILD.gn b/media/gpu/vaapi/BUILD.gn index af59943..225173d2 100644 --- a/media/gpu/vaapi/BUILD.gn +++ b/media/gpu/vaapi/BUILD.gn
@@ -109,7 +109,10 @@ ] if (is_ash) { + assert(use_libgav1_parser) sources += [ + "av1_vaapi_video_decoder_delegate.cc", + "av1_vaapi_video_decoder_delegate.h", "vaapi_jpeg_encode_accelerator.cc", "vaapi_jpeg_encode_accelerator.h", "vaapi_mjpeg_decode_accelerator.cc",
diff --git a/media/gpu/vaapi/av1_vaapi_video_decoder_delegate.cc b/media/gpu/vaapi/av1_vaapi_video_decoder_delegate.cc new file mode 100644 index 0000000..d189e19 --- /dev/null +++ b/media/gpu/vaapi/av1_vaapi_video_decoder_delegate.cc
@@ -0,0 +1,797 @@ +// Copyright 2020 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/gpu/vaapi/av1_vaapi_video_decoder_delegate.h" + +#include <string.h> +#include <va/va.h> +#include <algorithm> +#include <vector> + +#include "base/logging.h" +#include "base/memory/scoped_refptr.h" +#include "media/gpu/av1_picture.h" +#include "media/gpu/decode_surface_handler.h" +#include "media/gpu/vaapi/vaapi_common.h" +#include "media/gpu/vaapi/vaapi_wrapper.h" +#include "third_party/libgav1/src/src/obu_parser.h" +#include "third_party/libgav1/src/src/utils/types.h" +#include "third_party/libgav1/src/src/warp_prediction.h" + +namespace media { +namespace { + +#define ARRAY_SIZE(ar) (sizeof(ar) / sizeof(ar[0])) +#define STD_ARRAY_SIZE(ar) (std::tuple_size<decltype(ar)>::value) + +void FillSegmentInfo(VASegmentationStructAV1& va_seg_info, + const libgav1::Segmentation& segmentation) { + auto& va_seg_info_fields = va_seg_info.segment_info_fields.bits; + va_seg_info_fields.enabled = segmentation.enabled; + va_seg_info_fields.update_map = segmentation.update_map; + va_seg_info_fields.temporal_update = segmentation.temporal_update; + va_seg_info_fields.update_data = segmentation.update_data; + + static_assert(libgav1::kMaxSegments == 8 && libgav1::kSegmentFeatureMax == 8, + "Invalid Segment array size"); + static_assert(ARRAY_SIZE(segmentation.feature_data) == 8 && + ARRAY_SIZE(segmentation.feature_data[0]) == 8 && + ARRAY_SIZE(segmentation.feature_enabled) == 8 && + ARRAY_SIZE(segmentation.feature_enabled[0]) == 8, + "Invalid segmentation array size"); + static_assert(ARRAY_SIZE(va_seg_info.feature_data) == 8 && + ARRAY_SIZE(va_seg_info.feature_data[0]) == 8 && + ARRAY_SIZE(va_seg_info.feature_mask) == 8, + "Invalid feature array size"); + for (size_t i = 0; i < libgav1::kMaxSegments; ++i) { + for (size_t j = 0; j < libgav1::kSegmentFeatureMax; ++j) + va_seg_info.feature_data[i][j] = segmentation.feature_data[i][j]; + } + for (size_t i = 0; i < libgav1::kMaxSegments; ++i) { + uint8_t feature_mask = 0; + for (size_t j = 0; j < libgav1::kSegmentFeatureMax; ++j) { + if (segmentation.feature_enabled[i][j]) + feature_mask |= 1 << j; + } + va_seg_info.feature_mask[i] = feature_mask; + } +} + +void FillFilmGrainInfo(VAFilmGrainStructAV1& va_film_grain_info, + const libgav1::FilmGrainParams& film_grain_params) { + if (!film_grain_params.apply_grain) + return; + +#define COPY_FILM_GRAIN_FIELD(a) \ + va_film_grain_info.film_grain_info_fields.bits.a = film_grain_params.a + COPY_FILM_GRAIN_FIELD(apply_grain); + COPY_FILM_GRAIN_FIELD(chroma_scaling_from_luma); + COPY_FILM_GRAIN_FIELD(grain_scale_shift); + COPY_FILM_GRAIN_FIELD(overlap_flag); + COPY_FILM_GRAIN_FIELD(clip_to_restricted_range); +#undef COPY_FILM_GRAIN_FIELD + va_film_grain_info.film_grain_info_fields.bits.ar_coeff_lag = + film_grain_params.auto_regression_coeff_lag; + DCHECK_GE(film_grain_params.chroma_scaling, 8u); + DCHECK_GE(film_grain_params.auto_regression_shift, 6u); + va_film_grain_info.film_grain_info_fields.bits.grain_scaling_minus_8 = + film_grain_params.chroma_scaling - 8; + va_film_grain_info.film_grain_info_fields.bits.ar_coeff_shift_minus_6 = + film_grain_params.auto_regression_shift - 6; + + constexpr size_t kFilmGrainPointYSize = 14; + constexpr size_t kFilmGrainPointUVSize = 10; + static_assert( + ARRAY_SIZE(va_film_grain_info.point_y_value) == kFilmGrainPointYSize && + ARRAY_SIZE(va_film_grain_info.point_y_scaling) == + kFilmGrainPointYSize && + ARRAY_SIZE(va_film_grain_info.point_cb_value) == + kFilmGrainPointUVSize && + ARRAY_SIZE(va_film_grain_info.point_cb_scaling) == + kFilmGrainPointUVSize && + ARRAY_SIZE(va_film_grain_info.point_cr_value) == + kFilmGrainPointUVSize && + ARRAY_SIZE(va_film_grain_info.point_cr_scaling) == + kFilmGrainPointUVSize && + ARRAY_SIZE(film_grain_params.point_y_value) == kFilmGrainPointYSize && + ARRAY_SIZE(film_grain_params.point_y_scaling) == + kFilmGrainPointYSize && + ARRAY_SIZE(film_grain_params.point_u_value) == + kFilmGrainPointUVSize && + ARRAY_SIZE(film_grain_params.point_u_scaling) == + kFilmGrainPointUVSize && + ARRAY_SIZE(film_grain_params.point_v_value) == + kFilmGrainPointUVSize && + ARRAY_SIZE(film_grain_params.point_v_scaling) == + kFilmGrainPointUVSize, + "Invalid array size of film grain values"); + DCHECK_LE(film_grain_params.num_y_points, kFilmGrainPointYSize); + DCHECK_LE(film_grain_params.num_u_points, kFilmGrainPointUVSize); + DCHECK_LE(film_grain_params.num_v_points, kFilmGrainPointUVSize); +#define COPY_FILM_GRAIN_FIELD2(a, b) va_film_grain_info.a = film_grain_params.b +#define COPY_FILM_GRAIN_FIELD3(a) COPY_FILM_GRAIN_FIELD2(a, a) + COPY_FILM_GRAIN_FIELD3(grain_seed); + COPY_FILM_GRAIN_FIELD3(num_y_points); + for (uint8_t i = 0; i < film_grain_params.num_y_points; ++i) { + COPY_FILM_GRAIN_FIELD3(point_y_value[i]); + COPY_FILM_GRAIN_FIELD3(point_y_scaling[i]); + } +#undef COPY_FILM_GRAIN_FIELD3 + COPY_FILM_GRAIN_FIELD2(num_cb_points, num_u_points); + for (uint8_t i = 0; i < film_grain_params.num_u_points; ++i) { + COPY_FILM_GRAIN_FIELD2(point_cb_value[i], point_u_value[i]); + COPY_FILM_GRAIN_FIELD2(point_cb_scaling[i], point_u_scaling[i]); + } + COPY_FILM_GRAIN_FIELD2(num_cr_points, num_v_points); + for (uint8_t i = 0; i < film_grain_params.num_v_points; ++i) { + COPY_FILM_GRAIN_FIELD2(point_cr_value[i], point_v_value[i]); + COPY_FILM_GRAIN_FIELD2(point_cr_scaling[i], point_v_scaling[i]); + } + + constexpr size_t kAutoRegressionCoeffYSize = 24; + constexpr size_t kAutoRegressionCoeffUVSize = 25; + static_assert( + ARRAY_SIZE(va_film_grain_info.ar_coeffs_y) == kAutoRegressionCoeffYSize && + ARRAY_SIZE(va_film_grain_info.ar_coeffs_cb) == + kAutoRegressionCoeffUVSize && + ARRAY_SIZE(va_film_grain_info.ar_coeffs_cr) == + kAutoRegressionCoeffUVSize && + ARRAY_SIZE(film_grain_params.auto_regression_coeff_y) == + kAutoRegressionCoeffYSize && + ARRAY_SIZE(film_grain_params.auto_regression_coeff_u) == + kAutoRegressionCoeffUVSize && + ARRAY_SIZE(film_grain_params.auto_regression_coeff_v) == + kAutoRegressionCoeffUVSize, + "Invalid array size of auto-regressive coefficients"); + const size_t num_pos_y = (film_grain_params.auto_regression_coeff_lag * 2) * + (film_grain_params.auto_regression_coeff_lag + 1); + const size_t num_pos_uv = num_pos_y + (film_grain_params.num_y_points > 0); + if (film_grain_params.num_y_points > 0) { + DCHECK_LE(num_pos_y, kAutoRegressionCoeffYSize); + for (size_t i = 0; i < num_pos_y; ++i) + COPY_FILM_GRAIN_FIELD2(ar_coeffs_y[i], auto_regression_coeff_y[i]); + } + if (film_grain_params.chroma_scaling_from_luma || + film_grain_params.num_u_points > 0 || + film_grain_params.num_v_points > 0) { + DCHECK_LE(num_pos_uv, kAutoRegressionCoeffUVSize); + for (size_t i = 0; i < num_pos_uv; ++i) { + if (film_grain_params.chroma_scaling_from_luma || + film_grain_params.num_u_points > 0) { + COPY_FILM_GRAIN_FIELD2(ar_coeffs_cb[i], auto_regression_coeff_u[i]); + } + if (film_grain_params.chroma_scaling_from_luma || + film_grain_params.num_v_points > 0) { + COPY_FILM_GRAIN_FIELD2(ar_coeffs_cr[i], auto_regression_coeff_v[i]); + } + } + } + if (film_grain_params.num_u_points > 0) { + COPY_FILM_GRAIN_FIELD2(cb_mult, u_multiplier + 128); + COPY_FILM_GRAIN_FIELD2(cb_luma_mult, u_luma_multiplier + 128); + COPY_FILM_GRAIN_FIELD2(cb_offset, u_offset + 256); + } + if (film_grain_params.num_v_points > 0) { + COPY_FILM_GRAIN_FIELD2(cr_mult, v_multiplier + 128); + COPY_FILM_GRAIN_FIELD2(cr_luma_mult, v_luma_multiplier + 128); + COPY_FILM_GRAIN_FIELD2(cr_offset, v_offset + 256); + } +#undef COPY_FILM_GRAIN_FIELD2 +} + +void FillGlobalMotionInfo( + VAWarpedMotionParamsAV1 va_warped_motion[7], + const std::array<libgav1::GlobalMotion, libgav1::kNumReferenceFrameTypes>& + global_motion) { + // global_motion[0] (for kReferenceFrameIntra) is not used. + constexpr size_t kWarpedMotionSize = libgav1::kNumReferenceFrameTypes - 1; + for (size_t i = 0; i < kWarpedMotionSize; ++i) { + // Copy |global_motion| because SetupShear updates the affine variables of + // the |global_motion|. + auto gm = global_motion[i + 1]; + switch (gm.type) { + case libgav1::kGlobalMotionTransformationTypeIdentity: + va_warped_motion[i].wmtype = VAAV1TransformationIdentity; + break; + case libgav1::kGlobalMotionTransformationTypeTranslation: + va_warped_motion[i].wmtype = VAAV1TransformationTranslation; + break; + case libgav1::kGlobalMotionTransformationTypeRotZoom: + va_warped_motion[i].wmtype = VAAV1TransformationRotzoom; + break; + case libgav1::kGlobalMotionTransformationTypeAffine: + va_warped_motion[i].wmtype = VAAV1TransformationAffine; + break; + default: + NOTREACHED() << "Invalid global motion transformation type, " + << va_warped_motion[i].wmtype; + } + static_assert(ARRAY_SIZE(va_warped_motion[i].wmmat) == 8 && + ARRAY_SIZE(gm.params) == 6, + "Invalid size of warp motion parameters"); + for (size_t j = 0; j < 6; ++j) + va_warped_motion[i].wmmat[j] = gm.params[j]; + va_warped_motion[i].wmmat[6] = 0; + va_warped_motion[i].wmmat[7] = 0; + va_warped_motion[i].invalid = !libgav1::SetupShear(&gm); + } +} + +bool FillTileInfo(VADecPictureParameterBufferAV1& va_pic_param, + const libgav1::TileInfo& tile_info) { + // Since gav1 decoder doesn't support decoding with tile lists (i.e. large + // scale tile decoding), libgav1::ObuParser doesn't parse tile list, so that + // we cannot acquire anchor_frames_num, anchor_frames_list, tile_count_minus_1 + // and output_frame_width/height_in_tiles_minus_1, and thus must set them and + // large_scale_tile to 0 or false. This is already done by the memset in + // SubmitDecode(). libgav1::ObuParser returns kStatusUnimplemented on + // ParseOneFrame(), a fallback to av1 software decoder happens in the large + // scale tile decoding. + // TODO(hiroh): Support the large scale tile decoding once libgav1::ObuParser + // supports it. + va_pic_param.tile_cols = base::checked_cast<uint8_t>(tile_info.tile_columns); + va_pic_param.tile_rows = base::checked_cast<uint8_t>(tile_info.tile_rows); + + if (!tile_info.uniform_spacing) { + constexpr int kVaSizeOfTileWidthAndHeightArray = 63; + static_assert( + ARRAY_SIZE(tile_info.tile_column_width_in_superblocks) == 65 && + ARRAY_SIZE(tile_info.tile_row_height_in_superblocks) == 65 && + ARRAY_SIZE(va_pic_param.width_in_sbs_minus_1) == + kVaSizeOfTileWidthAndHeightArray && + ARRAY_SIZE(va_pic_param.height_in_sbs_minus_1) == + kVaSizeOfTileWidthAndHeightArray, + "Invalid sizes of tile column widths and row heights"); + const int tile_columns = + std::min(kVaSizeOfTileWidthAndHeightArray, tile_info.tile_columns); + for (int i = 0; i < tile_columns; i++) { + if (!base::CheckSub<int>(tile_info.tile_column_width_in_superblocks[i], 1) + .AssignIfValid(&va_pic_param.width_in_sbs_minus_1[i])) { + return false; + } + } + const int tile_rows = + std::min(kVaSizeOfTileWidthAndHeightArray, tile_info.tile_rows); + for (int i = 0; i < tile_rows; i++) { + if (!base::CheckSub<int>(tile_info.tile_row_height_in_superblocks[i], 1) + .AssignIfValid(&va_pic_param.height_in_sbs_minus_1[i])) { + return false; + } + } + } + + va_pic_param.context_update_tile_id = + base::checked_cast<uint16_t>(tile_info.context_update_id); + return true; +} + +void FillLoopFilterInfo(VADecPictureParameterBufferAV1& va_pic_param, + const libgav1::LoopFilter& loop_filter) { + static_assert(STD_ARRAY_SIZE(loop_filter.level) == libgav1::kFrameLfCount && + libgav1::kFrameLfCount == 4 && + ARRAY_SIZE(va_pic_param.filter_level) == 2, + "Invalid size of loop filter strength array"); + va_pic_param.filter_level[0] = + base::checked_cast<uint8_t>(loop_filter.level[0]); + va_pic_param.filter_level[1] = + base::checked_cast<uint8_t>(loop_filter.level[1]); + va_pic_param.filter_level_u = + base::checked_cast<uint8_t>(loop_filter.level[2]); + va_pic_param.filter_level_v = + base::checked_cast<uint8_t>(loop_filter.level[3]); + + va_pic_param.loop_filter_info_fields.bits.sharpness_level = + loop_filter.sharpness; + va_pic_param.loop_filter_info_fields.bits.mode_ref_delta_enabled = + loop_filter.delta_enabled; + va_pic_param.loop_filter_info_fields.bits.mode_ref_delta_update = + loop_filter.delta_update; + + static_assert(libgav1::kNumReferenceFrameTypes == 8 && + ARRAY_SIZE(va_pic_param.ref_deltas) == + libgav1::kNumReferenceFrameTypes && + STD_ARRAY_SIZE(loop_filter.ref_deltas) == + libgav1::kNumReferenceFrameTypes, + "Invalid size of ref deltas array"); + static_assert(libgav1::kLoopFilterMaxModeDeltas == 2 && + ARRAY_SIZE(va_pic_param.mode_deltas) == + libgav1::kLoopFilterMaxModeDeltas && + STD_ARRAY_SIZE(loop_filter.mode_deltas) == + libgav1::kLoopFilterMaxModeDeltas, + "Invalid size of mode deltas array"); + for (size_t i = 0; i < libgav1::kNumReferenceFrameTypes; i++) + va_pic_param.ref_deltas[i] = loop_filter.ref_deltas[i]; + for (size_t i = 0; i < libgav1::kLoopFilterMaxModeDeltas; i++) + va_pic_param.mode_deltas[i] = loop_filter.mode_deltas[i]; +} + +void FillQuantizationInfo(VADecPictureParameterBufferAV1& va_pic_param, + const libgav1::QuantizerParameters& quant_param) { + va_pic_param.base_qindex = quant_param.base_index; + static_assert( + libgav1::kPlaneY == 0 && libgav1::kPlaneU == 1 && libgav1::kPlaneV == 2, + "Invalid plane index"); + static_assert(libgav1::kMaxPlanes == 3 && + ARRAY_SIZE(quant_param.delta_dc) == libgav1::kMaxPlanes && + ARRAY_SIZE(quant_param.delta_ac) == libgav1::kMaxPlanes, + "Invalid size of delta dc/ac array"); + va_pic_param.y_dc_delta_q = quant_param.delta_dc[0]; + va_pic_param.u_dc_delta_q = quant_param.delta_dc[1]; + va_pic_param.v_dc_delta_q = quant_param.delta_dc[2]; + // quant_param.delta_ac[0] is useless as it is always 0. + va_pic_param.u_ac_delta_q = quant_param.delta_ac[1]; + va_pic_param.v_ac_delta_q = quant_param.delta_ac[2]; + + va_pic_param.qmatrix_fields.bits.using_qmatrix = quant_param.use_matrix; + if (!quant_param.use_matrix) + return; + static_assert(ARRAY_SIZE(quant_param.matrix_level) == libgav1::kMaxPlanes, + "Invalid size of matrix levels"); + va_pic_param.qmatrix_fields.bits.qm_y = + base::checked_cast<uint16_t>(quant_param.matrix_level[0]); + va_pic_param.qmatrix_fields.bits.qm_u = + base::checked_cast<uint16_t>(quant_param.matrix_level[1]); + va_pic_param.qmatrix_fields.bits.qm_v = + base::checked_cast<uint16_t>(quant_param.matrix_level[2]); +} + +void FillCdefInfo(VADecPictureParameterBufferAV1& va_pic_param, + const libgav1::Cdef& cdef, + uint8_t color_bitdepth) { + // Damping value parsed in libgav1 is from the spec + (bitdepth - 8). + // All the strength values parsed in libgav1 are from the spec and left + // shifted by (bitdepth - 8). + CHECK_GE(color_bitdepth, 8u); + const uint8_t coeff_shift = color_bitdepth - 8u; + va_pic_param.cdef_damping_minus_3 = + base::checked_cast<uint8_t>(cdef.damping - coeff_shift - 3u); + + va_pic_param.cdef_bits = cdef.bits; + static_assert( + libgav1::kMaxCdefStrengths == 8 && + ARRAY_SIZE(cdef.y_primary_strength) == libgav1::kMaxCdefStrengths && + ARRAY_SIZE(cdef.y_secondary_strength) == libgav1::kMaxCdefStrengths && + ARRAY_SIZE(cdef.uv_primary_strength) == libgav1::kMaxCdefStrengths && + ARRAY_SIZE(cdef.uv_secondary_strength) == + libgav1::kMaxCdefStrengths && + ARRAY_SIZE(va_pic_param.cdef_y_strengths) == + libgav1::kMaxCdefStrengths && + ARRAY_SIZE(va_pic_param.cdef_uv_strengths) == + libgav1::kMaxCdefStrengths, + "Invalid size of cdef strengths"); + const size_t num_cdef_strengths = 1 << cdef.bits; + DCHECK_LE(num_cdef_strengths, + static_cast<size_t>(libgav1::kMaxCdefStrengths)); + for (size_t i = 0; i < num_cdef_strengths; ++i) { + const uint8_t prim_strength = cdef.y_primary_strength[i] >> coeff_shift; + uint8_t sec_strength = cdef.y_secondary_strength[i] >> coeff_shift; + DCHECK_LE(sec_strength, 4u); + if (sec_strength == 4) + sec_strength--; + va_pic_param.cdef_y_strengths[i] = + ((prim_strength & 0xf) << 2) | (sec_strength & 0x03); + } + + for (size_t i = 0; i < num_cdef_strengths; ++i) { + const uint8_t prim_strength = cdef.uv_primary_strength[i] >> coeff_shift; + uint8_t sec_strength = cdef.uv_secondary_strength[i] >> coeff_shift; + DCHECK_LE(sec_strength, 4u); + if (sec_strength == 4) + sec_strength--; + va_pic_param.cdef_uv_strengths[i] = + ((prim_strength & 0xf) << 2) | (sec_strength & 0x03); + } +} + +void FillModeControlInfo(VADecPictureParameterBufferAV1& va_pic_param, + const libgav1::ObuFrameHeader& frame_header) { + auto& mode_control = va_pic_param.mode_control_fields.bits; + mode_control.delta_q_present_flag = frame_header.delta_q.present; + mode_control.log2_delta_q_res = frame_header.delta_q.scale; + mode_control.delta_lf_present_flag = frame_header.delta_lf.present; + mode_control.log2_delta_lf_res = frame_header.delta_lf.scale; + mode_control.delta_lf_multi = frame_header.delta_lf.multi; + switch (frame_header.tx_mode) { + case libgav1::TxMode::kTxModeOnly4x4: + case libgav1::TxMode::kTxModeLargest: + case libgav1::TxMode::kTxModeSelect: + mode_control.tx_mode = base::strict_cast<uint32_t>(frame_header.tx_mode); + break; + default: + NOTREACHED() << "Unknown tx mode: " + << base::strict_cast<int>(frame_header.tx_mode); + } + mode_control.reference_select = frame_header.reference_mode_select; + mode_control.reduced_tx_set_used = frame_header.reduced_tx_set; + mode_control.skip_mode_present = frame_header.skip_mode_present; +} + +void FillLoopRestorationInfo(VADecPictureParameterBufferAV1& va_pic_param, + const libgav1::LoopRestoration& loop_restoration) { + auto to_frame_restoration_type = [](libgav1::LoopRestorationType lr_type) { + // Spec. 6.10.15 + switch (lr_type) { + case libgav1::LoopRestorationType::kLoopRestorationTypeNone: + return 0; + case libgav1::LoopRestorationType::kLoopRestorationTypeSwitchable: + return 3; + case libgav1::LoopRestorationType::kLoopRestorationTypeWiener: + return 1; + case libgav1::LoopRestorationType::kLoopRestorationTypeSgrProj: + return 2; + case libgav1::LoopRestorationType::kNumLoopRestorationTypes: + default: + NOTREACHED() << "Invalid restoration type" + << base::strict_cast<int>(lr_type); + return 0; + } + }; + static_assert( + ARRAY_SIZE(loop_restoration.type) == libgav1::kMaxPlanes && + ARRAY_SIZE(loop_restoration.unit_size_log2) == libgav1::kMaxPlanes, + "Invalid size of loop restoration values"); + auto& va_loop_restoration = va_pic_param.loop_restoration_fields.bits; + va_loop_restoration.yframe_restoration_type = + to_frame_restoration_type(loop_restoration.type[0]); + va_loop_restoration.cbframe_restoration_type = + to_frame_restoration_type(loop_restoration.type[1]); + va_loop_restoration.crframe_restoration_type = + to_frame_restoration_type(loop_restoration.type[2]); + + const size_t num_planes = libgav1::kMaxPlanes; + const bool use_loop_restoration = + std::find_if(std::begin(loop_restoration.type), + std::begin(loop_restoration.type) + num_planes, + [](const auto type) { + return type != libgav1::kLoopRestorationTypeNone; + }) != (loop_restoration.type + num_planes); + if (!use_loop_restoration) + return; + DCHECK_GE(loop_restoration.unit_size_log2[0], 6); + DCHECK_GE(loop_restoration.unit_size_log2[0], + loop_restoration.unit_size_log2[1]); + va_loop_restoration.lr_unit_shift = loop_restoration.unit_size_log2[0] - 6; + va_loop_restoration.lr_uv_shift = + loop_restoration.unit_size_log2[0] - loop_restoration.unit_size_log2[1]; +} + +bool FillAV1PictureParameter(const AV1Picture& pic, + const libgav1::ObuSequenceHeader& sequence_header, + const AV1ReferenceFrameVector& ref_frames, + VADecPictureParameterBufferAV1& va_pic_param) { + memset(&va_pic_param, 0, sizeof(VADecPictureParameterBufferAV1)); + DCHECK_LE(base::strict_cast<uint8_t>(sequence_header.profile), 2u) + << "Unknown profile: " << base::strict_cast<int>(sequence_header.profile); + va_pic_param.profile = base::strict_cast<uint8_t>(sequence_header.profile); + + if (sequence_header.enable_order_hint) { + DCHECK_GT(sequence_header.order_hint_bits, 0); + DCHECK_LE(sequence_header.order_hint_bits, 8); + va_pic_param.order_hint_bits_minus_1 = sequence_header.order_hint_bits - 1; + } + + switch (sequence_header.color_config.bitdepth) { + case 8: + va_pic_param.bit_depth_idx = 0; + break; + case 10: + va_pic_param.bit_depth_idx = 1; + break; + case 12: + va_pic_param.bit_depth_idx = 2; + break; + default: + NOTREACHED() << "Unknown bit depth: " + << base::strict_cast<int>( + sequence_header.color_config.bitdepth); + } + switch (sequence_header.color_config.matrix_coefficients) { + case libgav1::kMatrixCoefficientsIdentity: + case libgav1::kMatrixCoefficientsBt709: + case libgav1::kMatrixCoefficientsUnspecified: + case libgav1::kMatrixCoefficientsFcc: + case libgav1::kMatrixCoefficientsBt470BG: + case libgav1::kMatrixCoefficientsBt601: + case libgav1::kMatrixCoefficientsSmpte240: + case libgav1::kMatrixCoefficientsSmpteYcgco: + case libgav1::kMatrixCoefficientsBt2020Ncl: + case libgav1::kMatrixCoefficientsBt2020Cl: + case libgav1::kMatrixCoefficientsSmpte2085: + case libgav1::kMatrixCoefficientsChromatNcl: + case libgav1::kMatrixCoefficientsChromatCl: + case libgav1::kMatrixCoefficientsIctcp: + va_pic_param.matrix_coefficients = base::checked_cast<uint8_t>( + sequence_header.color_config.matrix_coefficients); + break; + default: + DLOG(ERROR) << "Invalid matrix coefficients: " + << static_cast<int>( + sequence_header.color_config.matrix_coefficients); + return false; + } + + DCHECK(!sequence_header.color_config.is_monochrome); +#define COPY_SEQ_FIELD(a) \ + va_pic_param.seq_info_fields.fields.a = sequence_header.a +#define COPY_SEQ_FIELD2(a, b) va_pic_param.seq_info_fields.fields.a = b + COPY_SEQ_FIELD(still_picture); + COPY_SEQ_FIELD(use_128x128_superblock); + COPY_SEQ_FIELD(enable_filter_intra); + COPY_SEQ_FIELD(enable_intra_edge_filter); + COPY_SEQ_FIELD(enable_interintra_compound); + COPY_SEQ_FIELD(enable_masked_compound); + COPY_SEQ_FIELD(enable_dual_filter); + COPY_SEQ_FIELD(enable_order_hint); + COPY_SEQ_FIELD(enable_jnt_comp); + COPY_SEQ_FIELD(enable_cdef); + COPY_SEQ_FIELD2(mono_chrome, sequence_header.color_config.is_monochrome); + COPY_SEQ_FIELD2(subsampling_x, sequence_header.color_config.subsampling_x); + COPY_SEQ_FIELD2(subsampling_y, sequence_header.color_config.subsampling_y); + COPY_SEQ_FIELD(film_grain_params_present); +#undef COPY_SEQ_FIELD + switch (sequence_header.color_config.color_range) { + case libgav1::kColorRangeStudio: + case libgav1::kColorRangeFull: + COPY_SEQ_FIELD2(color_range, + base::strict_cast<uint32_t>( + sequence_header.color_config.color_range)); + break; + default: + NOTREACHED() << "Unknown color range: " + << static_cast<int>( + sequence_header.color_config.color_range); + } +#undef COPY_SEQ_FILED2 + + const libgav1::ObuFrameHeader& frame_header = pic.frame_header; + const auto* vaapi_pic = static_cast<const VaapiAV1Picture*>(&pic); + DCHECK(!!vaapi_pic->display_va_surface() && + !!vaapi_pic->reconstruct_va_surface()); + if (frame_header.film_grain_params.apply_grain) { + DCHECK_NE(vaapi_pic->display_va_surface()->id(), + vaapi_pic->reconstruct_va_surface()->id()) + << "When using film grain synthesis, the display and reconstruct " + "surfaces" + << " should be different."; + va_pic_param.current_frame = vaapi_pic->reconstruct_va_surface()->id(); + va_pic_param.current_display_picture = + vaapi_pic->display_va_surface()->id(); + } else { + DCHECK_EQ(vaapi_pic->display_va_surface()->id(), + vaapi_pic->reconstruct_va_surface()->id()) + << "When not using film grain synthesis, the display and reconstruct" + << " surfaces should be the same."; + va_pic_param.current_frame = vaapi_pic->display_va_surface()->id(); + va_pic_param.current_display_picture = VA_INVALID_SURFACE; + } + + if (!base::CheckSub<int32_t>(frame_header.width, 1) + .AssignIfValid(&va_pic_param.frame_width_minus1) || + !base::CheckSub<int32_t>(frame_header.height, 1) + .AssignIfValid(&va_pic_param.frame_height_minus1)) { + DLOG(ERROR) << "Invalid frame width and height" + << ", width=" << frame_header.width + << ", height=" << frame_header.height; + return false; + } + + static_assert(libgav1::kNumReferenceFrameTypes == 8 && + ARRAY_SIZE(va_pic_param.ref_frame_map) == + libgav1::kNumReferenceFrameTypes, + "Invalid size of reference frames"); + // TODO(hiroh): We should start with 1 and set ref_frame_map[0] to + // VA_INVALID_SURFACE based on the libva documentation. + for (int8_t i = 0; i < libgav1::kNumReferenceFrameTypes; ++i) { + const auto* ref_pic = + static_cast<const VaapiAV1Picture*>(ref_frames[i].get()); + va_pic_param.ref_frame_map[i] = + ref_pic ? ref_pic->reconstruct_va_surface()->id() : VA_INVALID_SURFACE; + } + + static_assert(libgav1::kNumInterReferenceFrameTypes == 7 && + ARRAY_SIZE(frame_header.reference_frame_index) == + libgav1::kNumInterReferenceFrameTypes && + ARRAY_SIZE(va_pic_param.ref_frame_idx) == + libgav1::kNumInterReferenceFrameTypes, + "Invalid size of reference frame indices"); + for (size_t i = 0; i < libgav1::kNumInterReferenceFrameTypes; ++i) { + const int8_t index = frame_header.reference_frame_index[i]; + if (index < 0) + continue; + CHECK_LT(index, libgav1::kNumReferenceFrameTypes); + CHECK_NE(va_pic_param.ref_frame_map[index], VA_INVALID_SURFACE); + va_pic_param.ref_frame_idx[i] = base::checked_cast<uint8_t>(index); + } + + va_pic_param.primary_ref_frame = + base::checked_cast<uint8_t>(frame_header.primary_reference_frame); + va_pic_param.order_hint = frame_header.order_hint; + + FillSegmentInfo(va_pic_param.seg_info, frame_header.segmentation); + FillFilmGrainInfo(va_pic_param.film_grain_info, + frame_header.film_grain_params); + + if (!FillTileInfo(va_pic_param, frame_header.tile_info)) + return false; + + if (frame_header.use_superres) { + DVLOG(2) << "Upscaling (use_superres=1) is not supported"; + return false; + } + auto& va_pic_info_fields = va_pic_param.pic_info_fields.bits; + va_pic_info_fields.uniform_tile_spacing_flag = + frame_header.tile_info.uniform_spacing; +#define COPY_PIC_FIELD(a) va_pic_info_fields.a = frame_header.a + COPY_PIC_FIELD(show_frame); + COPY_PIC_FIELD(showable_frame); + COPY_PIC_FIELD(error_resilient_mode); + COPY_PIC_FIELD(allow_screen_content_tools); + COPY_PIC_FIELD(force_integer_mv); + COPY_PIC_FIELD(allow_intrabc); + COPY_PIC_FIELD(use_superres); + COPY_PIC_FIELD(allow_high_precision_mv); + COPY_PIC_FIELD(is_motion_mode_switchable); + COPY_PIC_FIELD(use_ref_frame_mvs); + COPY_PIC_FIELD(allow_warped_motion); +#undef COPY_PIC_FIELD + switch (frame_header.frame_type) { + case libgav1::FrameType::kFrameKey: + case libgav1::FrameType::kFrameInter: + case libgav1::FrameType::kFrameIntraOnly: + case libgav1::FrameType::kFrameSwitch: + va_pic_info_fields.frame_type = + base::strict_cast<uint32_t>(frame_header.frame_type); + break; + default: + NOTREACHED() << "Unknown frame type: " + << base::strict_cast<int>(frame_header.frame_type); + } + va_pic_info_fields.disable_cdf_update = !frame_header.enable_cdf_update; + va_pic_info_fields.disable_frame_end_update_cdf = + !frame_header.enable_frame_end_update_cdf; + + CHECK_EQ(frame_header.superres_scale_denominator, + libgav1::kSuperResScaleNumerator); + va_pic_param.superres_scale_denominator = + frame_header.superres_scale_denominator; + DCHECK_LE(base::strict_cast<uint8_t>(frame_header.interpolation_filter), 4u) + << "Unknown interpolation filter: " + << base::strict_cast<int>(frame_header.interpolation_filter); + va_pic_param.interp_filter = + base::strict_cast<uint8_t>(frame_header.interpolation_filter); + + FillQuantizationInfo(va_pic_param, frame_header.quantizer); + FillLoopFilterInfo(va_pic_param, frame_header.loop_filter); + FillModeControlInfo(va_pic_param, frame_header); + FillLoopRestorationInfo(va_pic_param, frame_header.loop_restoration); + FillGlobalMotionInfo(va_pic_param.wm, frame_header.global_motion); + FillCdefInfo( + va_pic_param, frame_header.cdef, + base::checked_cast<uint8_t>(sequence_header.color_config.bitdepth)); + return true; +} + +bool FillAV1SliceParameters( + const libgav1::Vector<libgav1::TileBuffer>& tile_buffers, + const size_t tile_columns, + base::span<const uint8_t> data, + std::vector<VASliceParameterBufferAV1>& va_slice_params) { + CHECK_GT(tile_columns, 0u); + const uint16_t num_tiles = base::checked_cast<uint16_t>(tile_buffers.size()); + va_slice_params.resize(num_tiles); + for (uint16_t tile = 0; tile < num_tiles; ++tile) { + VASliceParameterBufferAV1& va_tile_param = va_slice_params[tile]; + memset(&va_tile_param, 0, sizeof(VASliceParameterBufferAV1)); + va_tile_param.slice_data_flag = VA_SLICE_DATA_FLAG_ALL; + va_tile_param.tile_row = tile / base::checked_cast<uint16_t>(tile_columns); + va_tile_param.tile_column = + tile % base::checked_cast<uint16_t>(tile_columns); + if (!base::CheckedNumeric<size_t>(tile_buffers[tile].size) + .AssignIfValid(&va_tile_param.slice_data_size)) { + return false; + } + CHECK(tile_buffers[tile].data >= data.data()); + base::CheckedNumeric<uint32_t> safe_va_slice_data_end( + va_tile_param.slice_data_offset); + safe_va_slice_data_end += va_tile_param.slice_data_size; + size_t va_slice_data_end; + if (!safe_va_slice_data_end.AssignIfValid(&va_slice_data_end) || + va_slice_data_end > data.size()) { + DLOG(ERROR) << "Invalid tile offset and size" + << ", offset=" << va_tile_param.slice_data_size + << ", size=" << va_tile_param.slice_data_offset + << ", entire data size=" << data.size(); + return false; + } + } + return true; +} +} // namespace + +AV1VaapiVideoDecoderDelegate::AV1VaapiVideoDecoderDelegate( + DecodeSurfaceHandler<VASurface>* const vaapi_dec, + scoped_refptr<VaapiWrapper> vaapi_wrapper) + : VaapiVideoDecoderDelegate(vaapi_dec, std::move(vaapi_wrapper)) {} + +AV1VaapiVideoDecoderDelegate::~AV1VaapiVideoDecoderDelegate() = default; + +scoped_refptr<AV1Picture> AV1VaapiVideoDecoderDelegate::CreateAV1Picture( + bool apply_grain) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + const auto display_va_surface = vaapi_dec_->CreateSurface(); + if (!display_va_surface) + return nullptr; + + auto reconstruct_va_surface = display_va_surface; + if (apply_grain) { + // TODO(hiroh): When no surface is available here, this returns nullptr and + // |display_va_surface| is released. Since the surface is back to the pool, + // VaapiVideoDecoder will detect that there are surfaces available and will + // start another decode task which means that CreateSurface() might fail + // again for |reconstruct_va_surface| since only one surface might have gone + // back to the pool (the one for |display_va_surface|). We should avoid this + // loop for the sake of efficiency. + reconstruct_va_surface = vaapi_dec_->CreateSurface(); + if (!reconstruct_va_surface) + return nullptr; + } + + return base::MakeRefCounted<VaapiAV1Picture>( + std::move(display_va_surface), std::move(reconstruct_va_surface)); +} + +bool AV1VaapiVideoDecoderDelegate::OutputPicture(const AV1Picture& pic) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + const auto* vaapi_pic = static_cast<const VaapiAV1Picture*>(&pic); + vaapi_dec_->SurfaceReady(vaapi_pic->display_va_surface(), + vaapi_pic->bitstream_id(), vaapi_pic->visible_rect(), + vaapi_pic->get_colorspace()); + return true; +} + +bool AV1VaapiVideoDecoderDelegate::SubmitDecode( + const AV1Picture& pic, + const libgav1::ObuSequenceHeader& seq_header, + const AV1ReferenceFrameVector& ref_frames, + const libgav1::Vector<libgav1::TileBuffer>& tile_buffers, + base::span<const uint8_t> data) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // libgav1 ensures that tile_columns is >= 0 and <= MAX_TILE_COLS. + DCHECK_LE(0, pic.frame_header.tile_info.tile_columns); + DCHECK_LE(pic.frame_header.tile_info.tile_columns, libgav1::kMaxTileColumns); + const size_t tile_columns = + base::checked_cast<size_t>(pic.frame_header.tile_info.tile_columns); + + VADecPictureParameterBufferAV1 pic_param; + std::vector<VASliceParameterBufferAV1> slice_params; + if (!FillAV1PictureParameter(pic, seq_header, ref_frames, pic_param) || + !FillAV1SliceParameters(tile_buffers, tile_columns, data, slice_params)) { + return false; + } + + // TODO(hiroh): Batch VABuffer submissions like Vp9VaapiVideoDecoderDelegate. + // Submit the picture parameters. + if (!vaapi_wrapper_->SubmitBuffer(VAPictureParameterBufferType, &pic_param)) + return false; + + // Submit the entire buffer and the per-tile information. + // TODO(hiroh): Don't submit the entire coded data to the buffer. Instead, + // only pass the data starting from the tile list OBU to reduce the size of + // the VA buffer. + if (!vaapi_wrapper_->SubmitBuffer(VASliceDataBufferType, data.size(), + data.data())) { + return false; + } + for (const VASliceParameterBufferAV1& tile_param : slice_params) { + if (!vaapi_wrapper_->SubmitBuffer(VASliceParameterBufferType, + &tile_param)) { + return false; + } + } + + const auto* vaapi_pic = static_cast<const VaapiAV1Picture*>(&pic); + return vaapi_wrapper_->ExecuteAndDestroyPendingBuffers( + vaapi_pic->display_va_surface()->id()); +} +} // namespace media
diff --git a/media/gpu/vaapi/av1_vaapi_video_decoder_delegate.h b/media/gpu/vaapi/av1_vaapi_video_decoder_delegate.h new file mode 100644 index 0000000..327c6a5 --- /dev/null +++ b/media/gpu/vaapi/av1_vaapi_video_decoder_delegate.h
@@ -0,0 +1,32 @@ +// Copyright 2020 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_GPU_VAAPI_AV1_VAAPI_VIDEO_DECODER_DELEGATE_H_ +#define MEDIA_GPU_VAAPI_AV1_VAAPI_VIDEO_DECODER_DELEGATE_H_ + +#include "media/gpu/av1_decoder.h" +#include "media/gpu/vaapi/vaapi_video_decoder_delegate.h" + +namespace media { +class AV1VaapiVideoDecoderDelegate : public AV1Decoder::AV1Accelerator, + public VaapiVideoDecoderDelegate { + public: + AV1VaapiVideoDecoderDelegate(DecodeSurfaceHandler<VASurface>* const vaapi_dec, + scoped_refptr<VaapiWrapper> vaapi_wrapper); + ~AV1VaapiVideoDecoderDelegate() override; + AV1VaapiVideoDecoderDelegate(const AV1VaapiVideoDecoderDelegate&) = delete; + AV1VaapiVideoDecoderDelegate& operator=(const AV1VaapiVideoDecoderDelegate&) = + delete; + + // AV1Decoder::AV1Accelerator implementation. + scoped_refptr<AV1Picture> CreateAV1Picture(bool apply_grain) override; + bool SubmitDecode(const AV1Picture& pic, + const libgav1::ObuSequenceHeader& seq_header, + const AV1ReferenceFrameVector& ref_frames, + const libgav1::Vector<libgav1::TileBuffer>& tile_buffers, + base::span<const uint8_t> data) override; + bool OutputPicture(const AV1Picture& pic) override; +}; +} // namespace media +#endif // MEDIA_GPU_VAAPI_AV1_VAAPI_VIDEO_DECODER_DELEGATE_H_
diff --git a/media/gpu/vaapi/vaapi_common.cc b/media/gpu/vaapi/vaapi_common.cc index 415f84a..44e7db0d 100644 --- a/media/gpu/vaapi/vaapi_common.cc +++ b/media/gpu/vaapi/vaapi_common.cc
@@ -48,4 +48,18 @@ return new VaapiVP9Picture(va_surface_); } +#if BUILDFLAG(IS_ASH) +VaapiAV1Picture::VaapiAV1Picture( + scoped_refptr<VASurface> display_va_surface, + scoped_refptr<VASurface> reconstruct_va_surface) + : display_va_surface_(std::move(display_va_surface)), + reconstruct_va_surface_(std::move(reconstruct_va_surface)) {} + +VaapiAV1Picture::~VaapiAV1Picture() = default; + +scoped_refptr<AV1Picture> VaapiAV1Picture::CreateDuplicate() { + return base::MakeRefCounted<VaapiAV1Picture>(display_va_surface_, + reconstruct_va_surface_); +} +#endif // BUILDFLAG(IS_ASH) } // namespace media
diff --git a/media/gpu/vaapi/vaapi_common.h b/media/gpu/vaapi/vaapi_common.h index 1c71ce6..efbd4a0f 100644 --- a/media/gpu/vaapi/vaapi_common.h +++ b/media/gpu/vaapi/vaapi_common.h
@@ -14,6 +14,10 @@ #include "media/gpu/h265_dpb.h" #endif +#if BUILDFLAG(IS_ASH) +#include "media/gpu/av1_picture.h" +#endif // BUILDFLAG(IS_ASH) + namespace media { // These picture classes derive from platform-independent, codec-specific @@ -97,6 +101,38 @@ DISALLOW_COPY_AND_ASSIGN(VaapiVP9Picture); }; +#if BUILDFLAG(IS_ASH) +class VaapiAV1Picture : public AV1Picture { + public: + VaapiAV1Picture(scoped_refptr<VASurface> display_va_surface, + scoped_refptr<VASurface> reconstruct_va_surface); + VaapiAV1Picture(const VaapiAV1Picture&) = delete; + VaapiAV1Picture& operator=(const VaapiAV1Picture&) = delete; + + const scoped_refptr<VASurface>& display_va_surface() const { + return display_va_surface_; + } + const scoped_refptr<VASurface>& reconstruct_va_surface() const { + return reconstruct_va_surface_; + } + + protected: + ~VaapiAV1Picture() override; + + private: + scoped_refptr<AV1Picture> CreateDuplicate() override; + + // |display_va_surface_| refers to the final decoded frame, both when using + // film grain synthesis and when not using film grain. + // |reconstruct_va_surface_| is only useful when using film grain synthesis: + // it's the decoded frame prior to applying the film grain. + // When not using film grain synthesis, |reconstruct_va_surface_| is equal to + // |display_va_surface_|. This is necessary to simplify the reference frame + // code when filling the VA-API structures. + scoped_refptr<VASurface> display_va_surface_; + scoped_refptr<VASurface> reconstruct_va_surface_; +}; +#endif // BUILDFLAG(IS_ASH) } // namespace media #endif // MEDIA_GPU_VAAPI_VAAPI_COMMON_H_
diff --git a/media/gpu/vaapi/vaapi_video_decoder.cc b/media/gpu/vaapi/vaapi_video_decoder.cc index 9672aaf..08fe795 100644 --- a/media/gpu/vaapi/vaapi_video_decoder.cc +++ b/media/gpu/vaapi/vaapi_video_decoder.cc
@@ -32,6 +32,11 @@ #include "media/gpu/vaapi/h265_vaapi_video_decoder_delegate.h" #endif +#if BUILDFLAG(IS_ASH) +#include "media/gpu/av1_decoder.h" +#include "media/gpu/vaapi/av1_vaapi_video_decoder_delegate.h" +#endif // BUILDFLAG(IS_ASH) + namespace media { namespace { @@ -643,16 +648,27 @@ decoder_.reset( new VP9Decoder(std::move(accelerator), profile_, color_space_)); + } #if BUILDFLAG(ENABLE_PLATFORM_HEVC) - } else if (profile_ >= HEVCPROFILE_MIN && profile_ <= HEVCPROFILE_MAX) { + else if (profile_ >= HEVCPROFILE_MIN && profile_ <= HEVCPROFILE_MAX) { auto accelerator = std::make_unique<H265VaapiVideoDecoderDelegate>(this, vaapi_wrapper_); decoder_delegate_ = accelerator.get(); decoder_.reset( new H265Decoder(std::move(accelerator), profile_, color_space_)); + } #endif // BUILDFLAG(ENABLE_PLATFORM_HEVC) - } else { +#if BUILDFLAG(IS_ASH) + else if (profile_ >= AV1PROFILE_MIN && profile_ <= AV1PROFILE_MAX) { + auto accelerator = + std::make_unique<AV1VaapiVideoDecoderDelegate>(this, vaapi_wrapper_); + decoder_delegate_ = accelerator.get(); + + decoder_.reset(new AV1Decoder(std::move(accelerator), profile_)); + } +#endif // BUILDFLAG(IS_ASH) + else { return Status(StatusCode::kDecoderUnsupportedProfile) .WithData("profile", profile_); }
diff --git a/net/http/transport_security_state_static.json b/net/http/transport_security_state_static.json index 858eac98..4a34a21 100644 --- a/net/http/transport_security_state_static.json +++ b/net/http/transport_security_state_static.json
@@ -120015,7 +120015,6 @@ { "name": "ramydent.no", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true }, { "name": "ranking-mensesthe.jp", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true }, { "name": "rdxbioscience.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true }, - { "name": "reach.gov", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true }, { "name": "readingea.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true }, { "name": "redlands.net.au", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true }, { "name": "refurb-tracker.com", "policy": "bulk-1-year", "mode": "force-https", "include_subdomains": true }, @@ -122249,6 +122248,70 @@ { "name": "washingtoncountyar.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, { "name": "worthcountyiowa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, { "name": "zerodeathsmd.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "azjlbc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "cannoncountytn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "carrollcountyiowa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "churchillcountynv.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "cityofelynv.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "cityofherculaneum.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "clearcreekcountyco.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "decaturcountyiowa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "dubuquecountyiowa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "fillmorecountyne.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "floridafx.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "floridahealthcareconnections.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "flyhealthy.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "gajqc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "gcso.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "greenecountyny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "greenecountyohio.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "hancockcountyia.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "haughtonla.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "hoodrivercounty.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "horrycountysc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "kissimmee.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "lafayetteco.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "lenoirnc.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "louisville.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "millscountyiowa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "millwoodwa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "modestoca.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "montgomerycountyia.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "monticelloky.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "mtpleasant-tn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "muscatinecountyiowa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "myvotect.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "newberryfl.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "njleg.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "nvigate.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "ocsan.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "odenvilleal.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "osceolacountyia.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "pcbfl.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "pikecountyohcommissioners.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "placercountyelections.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "plymouthcountyiowa.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "portsmouthsheriffsofficeva.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "priorlakemn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "pwcva.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "quantum.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "radcliffky.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "reach.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "sanpatriciocountytx.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "southbridge-ma.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "stephensoncountyil.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "swa-il.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "tijerasnm.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "usicecenter.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "villageoftikiisland.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "votecitrus.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "wakpamnilake-nsn.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "wawarsingny.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "waynecountyne.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "westlakehills.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "wisdotplans.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "yavapaiaz.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, + { "name": "yellowstonecountymt.gov", "policy": "public-suffix-requested", "mode": "force-https", "include_subdomains": true }, // END OF ETLD-OWNER REQUESTED ENTRIES // To avoid trailing comma changes from showing up in diffs, we place a
diff --git a/printing/BUILD.gn b/printing/BUILD.gn index 3e70dc1d..e71403b 100644 --- a/printing/BUILD.gn +++ b/printing/BUILD.gn
@@ -322,6 +322,7 @@ test("printing_unittests") { sources = [ "backend/mojom/print_backend_mojom_traits_unittest.cc", + "backend/test_print_backend_unittest.cc", "metafile_skia_unittest.cc", "nup_parameters_unittest.cc", "page_number_unittest.cc", @@ -337,6 +338,7 @@ configs += [ "//build/config/compiler:noshadowing" ] deps = [ ":printing", + ":test_support", "//base/test:test_support", "//build:chromeos_buildflags", "//mojo/core/test:run_all_unittests",
diff --git a/printing/backend/print_backend.cc b/printing/backend/print_backend.cc index 7176a773..36661cc 100644 --- a/printing/backend/print_backend.cc +++ b/printing/backend/print_backend.cc
@@ -4,6 +4,9 @@ #include "printing/backend/print_backend.h" +#include <string> + +#include "base/memory/scoped_refptr.h" #include "build/chromeos_buildflags.h" namespace { @@ -17,10 +20,31 @@ PrinterBasicInfo::PrinterBasicInfo() = default; +PrinterBasicInfo::PrinterBasicInfo(const std::string& printer_name, + const std::string& display_name, + const std::string& printer_description, + int printer_status, + bool is_default, + const PrinterBasicInfoOptions& options) + : printer_name(printer_name), + display_name(display_name), + printer_description(printer_description), + printer_status(printer_status), + is_default(is_default), + options(options) {} + PrinterBasicInfo::PrinterBasicInfo(const PrinterBasicInfo& other) = default; PrinterBasicInfo::~PrinterBasicInfo() = default; +bool PrinterBasicInfo::operator==(const PrinterBasicInfo& other) const { + return printer_name == other.printer_name && + display_name == other.display_name && + printer_description == other.printer_description && + printer_status == other.printer_status && + is_default == other.is_default && options == other.options; +} + #if BUILDFLAG(IS_ASH) AdvancedCapabilityValue::AdvancedCapabilityValue() = default;
diff --git a/printing/backend/print_backend.h b/printing/backend/print_backend.h index b8b46ff..4aeb423 100644 --- a/printing/backend/print_backend.h +++ b/printing/backend/print_backend.h
@@ -35,9 +35,17 @@ struct PRINTING_EXPORT PrinterBasicInfo { PrinterBasicInfo(); + PrinterBasicInfo(const std::string& printer_name, + const std::string& display_name, + const std::string& printer_description, + int printer_status, + bool is_default, + const PrinterBasicInfoOptions& options); PrinterBasicInfo(const PrinterBasicInfo& other); ~PrinterBasicInfo(); + bool operator==(const PrinterBasicInfo& other) const; + // The name of the printer as understood by OS. std::string printer_name;
diff --git a/printing/backend/test_print_backend.cc b/printing/backend/test_print_backend.cc index 2eff8c9a..8a0f0a1 100644 --- a/printing/backend/test_print_backend.cc +++ b/printing/backend/test_print_backend.cc
@@ -5,8 +5,11 @@ #include "printing/backend/test_print_backend.h" #include <memory> +#include <string> #include <utility> +#include "base/check.h" +#include "base/logging.h" #include "base/stl_util.h" #include "printing/backend/print_backend.h" @@ -17,11 +20,17 @@ TestPrintBackend::~TestPrintBackend() = default; bool TestPrintBackend::EnumeratePrinters(PrinterList* printer_list) { - // TODO(crbug.com/809738) There was never a call to provide a list of - // printers for the environment, so this would always be empty. - // This needs to be updated as part of a larger cleanup to make - // TestPrintBackend have consistent behavior across its APIs. - return false; + if (printer_map_.empty()) + return false; + + for (const auto& entry : printer_map_) { + const std::unique_ptr<PrinterData>& data = entry.second; + + // Can only return basic info for printers which have registered info. + if (data->info) + printer_list->emplace_back(*data->info); + } + return true; } std::string TestPrintBackend::GetDefaultPrinterName() { @@ -30,53 +39,97 @@ bool TestPrintBackend::GetPrinterBasicInfo(const std::string& printer_name, PrinterBasicInfo* printer_info) { - NOTREACHED() << "Not implemented"; - return false; + auto found = printer_map_.find(printer_name); + if (found == printer_map_.end()) + return false; // Matching entry not found. + + // Basic info might not have been provided. + const std::unique_ptr<PrinterData>& data = found->second; + if (!data->info) + return false; + + *printer_info = *data->info; + return true; } bool TestPrintBackend::GetPrinterSemanticCapsAndDefaults( const std::string& printer_name, - PrinterSemanticCapsAndDefaults* printer_info) { - auto found = valid_printers_.find(printer_name); - if (found == valid_printers_.end()) + PrinterSemanticCapsAndDefaults* printer_caps) { + auto found = printer_map_.find(printer_name); + if (found == printer_map_.end()) return false; - const std::unique_ptr<PrinterSemanticCapsAndDefaults>& caps = found->second; - if (!caps) + // Capabilities might not have been provided. + const std::unique_ptr<PrinterData>& data = found->second; + if (!data->caps) return false; - *printer_info = *(found->second); + *printer_caps = *data->caps; return true; } bool TestPrintBackend::GetPrinterCapsAndDefaults( const std::string& printer_name, - PrinterCapsAndDefaults* printer_info) { + PrinterCapsAndDefaults* printer_caps) { // not implemented return false; } std::string TestPrintBackend::GetPrinterDriverInfo( - const std::string& printr_name) { + const std::string& printer_name) { // not implemented return ""; } bool TestPrintBackend::IsValidPrinter(const std::string& printer_name) { - return base::Contains(valid_printers_, printer_name); + return base::Contains(printer_map_, printer_name); } void TestPrintBackend::SetDefaultPrinterName(const std::string& printer_name) { + if (default_printer_name_ == printer_name) + return; + + auto found = printer_map_.find(printer_name); + if (found == printer_map_.end()) { + DLOG(ERROR) << "Unable to set an unknown printer as the default. Unknown " + << "printer name: " << printer_name; + return; + } + + // Previous default printer is no longer the default. + const std::unique_ptr<PrinterData>& new_default_data = found->second; + if (!default_printer_name_.empty()) + printer_map_[default_printer_name_]->info->is_default = false; + + // Now update new printer as default. default_printer_name_ = printer_name; + if (!default_printer_name_.empty()) + new_default_data->info->is_default = true; } void TestPrintBackend::AddValidPrinter( const std::string& printer_name, std::unique_ptr<PrinterSemanticCapsAndDefaults> caps, std::unique_ptr<PrinterBasicInfo> info) { - // TODO(crbug.com/809738) Utilize the extra `info` parameter to improve - // TestPrintBackend internal consistency. - valid_printers_[printer_name] = std::move(caps); + DCHECK(!printer_name.empty()); + + const bool is_default = info && info->is_default; + printer_map_[printer_name] = + std::make_unique<PrinterData>(std::move(caps), std::move(info)); + + // Ensure that default settings are honored if more than one is attempted to + // be marked as default or if this prior default should no longer be so. + if (is_default) + SetDefaultPrinterName(printer_name); + else if (default_printer_name_ == printer_name) + default_printer_name_.clear(); } +TestPrintBackend::PrinterData::PrinterData( + std::unique_ptr<PrinterSemanticCapsAndDefaults> caps, + std::unique_ptr<PrinterBasicInfo> info) + : caps(std::move(caps)), info(std::move(info)) {} + +TestPrintBackend::PrinterData::~PrinterData() = default; + } // namespace printing
diff --git a/printing/backend/test_print_backend.h b/printing/backend/test_print_backend.h index c80b8b0..17ff074 100644 --- a/printing/backend/test_print_backend.h +++ b/printing/backend/test_print_backend.h
@@ -5,11 +5,10 @@ #ifndef PRINTING_BACKEND_TEST_PRINT_BACKEND_H_ #define PRINTING_BACKEND_TEST_PRINT_BACKEND_H_ -#include <map> #include <memory> #include <string> -#include <vector> +#include "base/containers/flat_map.h" #include "printing/backend/print_backend.h" namespace printing { @@ -33,17 +32,18 @@ std::string GetPrinterDriverInfo(const std::string& printer_name) override; bool IsValidPrinter(const std::string& printer_name) override; - // Set a default printer. The default is the empty string. + // Methods for test setup: + + // Sets a default printer. The default is the empty string. void SetDefaultPrinterName(const std::string& printer_name); - // Add a printer to satisfy IsValidPrinter(), EnumeratePrinters(), + // Adds a printer to satisfy IsValidPrinter(), EnumeratePrinters(), // GetPrinterBasicInfo(), and GetPrinterSemanticCapsAndDefaults(). // While `caps` can be null, it will cause queries for the capabilities to // fail, and thus is likely not of interest for most tests. IsValidPrinter() // will still show true even if `caps` is null, which provides the benefit of // simulating a printer that exists in the system but cannot be queried. - // `info` can be null, which will result in empty information being provided - // for any queries. + // `info` can be null, which will result in queries for basic info to fail. // Calling EnumeratePrinters() will include the identified `printer_name` // even if either parameter is null. void AddValidPrinter(const std::string& printer_name, @@ -54,9 +54,21 @@ ~TestPrintBackend() override; private: + struct PrinterData { + PrinterData(std::unique_ptr<PrinterSemanticCapsAndDefaults> caps, + std::unique_ptr<PrinterBasicInfo> info); + ~PrinterData(); + + std::unique_ptr<PrinterSemanticCapsAndDefaults> caps; + std::unique_ptr<PrinterBasicInfo> info; + }; + std::string default_printer_name_; - std::map<std::string, std::unique_ptr<PrinterSemanticCapsAndDefaults>> - valid_printers_; + // The values in `printer_map_` will not be null. The use of a pointer to + // PrinterData is just a workaround for the deleted copy assignment operator + // for the `caps` field, as that prevents the PrinterData container from + // being copied into the map. + base::flat_map<std::string, std::unique_ptr<PrinterData>> printer_map_; }; } // namespace printing
diff --git a/printing/backend/test_print_backend_unittest.cc b/printing/backend/test_print_backend_unittest.cc new file mode 100644 index 0000000..5628819df --- /dev/null +++ b/printing/backend/test_print_backend_unittest.cc
@@ -0,0 +1,232 @@ +// Copyright 2020 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 "printing/backend/test_print_backend.h" + +#include <stdint.h> + +#include <string> +#include <utility> +#include <vector> + +#include "base/memory/scoped_refptr.h" +#include "base/stl_util.h" +#include "base/test/gtest_util.h" +#include "printing/backend/print_backend.h" +#include "printing/mojom/print.mojom.h" +#include "testing/gmock/include/gmock/gmock-matchers.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/size.h" + +namespace printing { + +namespace { + +constexpr char kDefaultPrinterName[] = "default-test-printer"; +constexpr char kAlternatePrinterName[] = "alternate-test-printer"; +constexpr char kNullDataPrinterName[] = "null-data-test-printer"; +constexpr char kInvalidPrinterName[] = "invalid-test-printer"; + +constexpr int kDefaultPrinterStatus = 0; +constexpr int kAlternatePrinterStatus = 1; + +const PrinterBasicInfo kDefaultPrinterInfo( + /*printer_name=*/kDefaultPrinterName, + /*display_name=*/"default test printer", + /*printer_description=*/"Default printer for testing.", + /*printer_status=*/kDefaultPrinterStatus, + /*is_default=*/true, + /*options=*/PrinterBasicInfoOptions{}); +const PrinterBasicInfo kAlternatePrinterInfo( + /*printer_name=*/kAlternatePrinterName, + /*display_name=*/"alternate test printer", + /*printer_description=*/"Alternate printer for testing.", + /*printer_status=*/kAlternatePrinterStatus, + /*is_default=*/false, + /*options=*/PrinterBasicInfoOptions{}); + +constexpr int32_t kDefaultCopiesMax = 123; +constexpr int32_t kAlternateCopiesMax = 456; + +} // namespace + +class TestPrintBackendTest : public testing::Test { + public: + void SetUp() override { + test_print_backend_ = base::MakeRefCounted<TestPrintBackend>(); + } + + void AddPrinters() { + // Add some printers; only bother to set one capabilities field that will + // be paid attention to in the tests as way of knowing it has provided the + // real capabilities. + auto caps = std::make_unique<PrinterSemanticCapsAndDefaults>(); + caps->copies_max = kDefaultCopiesMax; + test_print_backend_->AddValidPrinter( + kDefaultPrinterName, std::move(caps), + std::make_unique<PrinterBasicInfo>(kDefaultPrinterInfo)); + + caps = std::make_unique<PrinterSemanticCapsAndDefaults>(); + caps->copies_max = kAlternateCopiesMax; + test_print_backend_->AddValidPrinter( + kAlternatePrinterName, std::move(caps), + std::make_unique<PrinterBasicInfo>(kAlternatePrinterInfo)); + + test_print_backend_->AddValidPrinter(kNullDataPrinterName, /*caps=*/nullptr, + /*info=*/nullptr); + } + + // Get the test print backend. + TestPrintBackend* GetPrintBackend() const { + return test_print_backend_.get(); + } + + private: + scoped_refptr<TestPrintBackend> test_print_backend_; +}; + +TEST_F(TestPrintBackendTest, EnumeratePrinters) { + const PrinterList kPrinterList{kAlternatePrinterInfo, kDefaultPrinterInfo}; + PrinterList printer_list; + + // Should return false when there are no printers in the environment. + EXPECT_FALSE(GetPrintBackend()->EnumeratePrinters(&printer_list)); + + AddPrinters(); + + EXPECT_TRUE(GetPrintBackend()->EnumeratePrinters(&printer_list)); + EXPECT_THAT(printer_list, testing::ContainerEq(kPrinterList)); +} + +TEST_F(TestPrintBackendTest, DefaultPrinterName) { + // If no printers added then no default. + EXPECT_TRUE(GetPrintBackend()->GetDefaultPrinterName().empty()); + + // Once printers are available, should be a default. + AddPrinters(); + EXPECT_EQ(GetPrintBackend()->GetDefaultPrinterName(), kDefaultPrinterName); + + // Changing default should be reflected on next query. + GetPrintBackend()->SetDefaultPrinterName(kAlternatePrinterName); + EXPECT_EQ(GetPrintBackend()->GetDefaultPrinterName(), kAlternatePrinterName); + + // Adding a new printer to environment which is marked as default should + // automatically make it the new default. + static constexpr char kNewDefaultPrinterName[] = "new-default-test-printer"; + auto caps = std::make_unique<PrinterSemanticCapsAndDefaults>(); + auto printer_info = std::make_unique<PrinterBasicInfo>(); + printer_info->printer_name = kNewDefaultPrinterName; + printer_info->is_default = true; + GetPrintBackend()->AddValidPrinter(kNewDefaultPrinterName, std::move(caps), + std::move(printer_info)); + EXPECT_EQ(GetPrintBackend()->GetDefaultPrinterName(), kNewDefaultPrinterName); + + // Requesting an invalid printer name to be a default should have no effect. + GetPrintBackend()->SetDefaultPrinterName(kInvalidPrinterName); + EXPECT_EQ(GetPrintBackend()->GetDefaultPrinterName(), kNewDefaultPrinterName); + + // Verify that re-adding a printer that was previously the default with null + // basic info results in no default printer anymore. + GetPrintBackend()->AddValidPrinter(kNewDefaultPrinterName, /*caps=*/nullptr, + /*info=*/nullptr); + EXPECT_TRUE(GetPrintBackend()->GetDefaultPrinterName().empty()); +} + +TEST_F(TestPrintBackendTest, PrinterBasicInfo) { + PrinterBasicInfo printer_info; + + AddPrinters(); + + EXPECT_TRUE(GetPrintBackend()->GetPrinterBasicInfo(kDefaultPrinterName, + &printer_info)); + EXPECT_EQ(printer_info.printer_name, kDefaultPrinterName); + EXPECT_EQ(printer_info.printer_status, kDefaultPrinterStatus); + EXPECT_TRUE(printer_info.is_default); + + EXPECT_TRUE(GetPrintBackend()->GetPrinterBasicInfo(kAlternatePrinterName, + &printer_info)); + EXPECT_EQ(printer_info.printer_name, kAlternatePrinterName); + EXPECT_EQ(printer_info.printer_status, kAlternatePrinterStatus); + EXPECT_FALSE(printer_info.is_default); + + EXPECT_FALSE(GetPrintBackend()->GetPrinterBasicInfo(kInvalidPrinterName, + &printer_info)); + + // Changing default should be reflected on next query. + GetPrintBackend()->SetDefaultPrinterName(kAlternatePrinterName); + EXPECT_TRUE(GetPrintBackend()->GetPrinterBasicInfo(kAlternatePrinterName, + &printer_info)); + EXPECT_TRUE(printer_info.is_default); + EXPECT_TRUE(GetPrintBackend()->GetPrinterBasicInfo(kDefaultPrinterName, + &printer_info)); + EXPECT_FALSE(printer_info.is_default); + + // Printers added with null basic info fail to get data on a query. + EXPECT_FALSE(GetPrintBackend()->GetPrinterBasicInfo(kNullDataPrinterName, + &printer_info)); + + // Verify that (re)adding a printer with null basic info results in a failure + // the next time when trying to get the basic info. + GetPrintBackend()->AddValidPrinter(kAlternatePrinterName, /*caps=*/nullptr, + /*info=*/nullptr); + EXPECT_FALSE(GetPrintBackend()->GetPrinterBasicInfo(kAlternatePrinterName, + &printer_info)); +} + +TEST_F(TestPrintBackendTest, GetPrinterSemanticCapsAndDefaults) { + PrinterSemanticCapsAndDefaults caps; + + // Should fail when there are no printers in the environment. + EXPECT_FALSE(GetPrintBackend()->GetPrinterSemanticCapsAndDefaults( + kDefaultPrinterName, &caps)); + + AddPrinters(); + + EXPECT_TRUE(GetPrintBackend()->GetPrinterSemanticCapsAndDefaults( + kDefaultPrinterName, &caps)); + EXPECT_EQ(caps.copies_max, kDefaultCopiesMax); + + EXPECT_TRUE(GetPrintBackend()->GetPrinterSemanticCapsAndDefaults( + kAlternatePrinterName, &caps)); + EXPECT_EQ(caps.copies_max, kAlternateCopiesMax); + + EXPECT_FALSE(GetPrintBackend()->GetPrinterSemanticCapsAndDefaults( + kInvalidPrinterName, &caps)); + + // Printers added with null capabilities fail to get data on a query. + EXPECT_FALSE(GetPrintBackend()->GetPrinterSemanticCapsAndDefaults( + kNullDataPrinterName, &caps)); + + // Verify that (re)adding a printer with null capabilities results in a + // failure the next time when trying to get capabilities. + GetPrintBackend()->AddValidPrinter(kAlternatePrinterName, /*caps=*/nullptr, + /*info=*/nullptr); + EXPECT_FALSE(GetPrintBackend()->GetPrinterSemanticCapsAndDefaults( + kAlternatePrinterName, &caps)); +} + +TEST_F(TestPrintBackendTest, IsValidPrinter) { + PrinterSemanticCapsAndDefaults caps; + + // Should fail when there are no printers in the environment. + EXPECT_FALSE(GetPrintBackend()->IsValidPrinter(kDefaultPrinterName)); + + AddPrinters(); + + EXPECT_TRUE(GetPrintBackend()->IsValidPrinter(kDefaultPrinterName)); + EXPECT_TRUE(GetPrintBackend()->IsValidPrinter(kAlternatePrinterName)); + EXPECT_FALSE(GetPrintBackend()->IsValidPrinter(kInvalidPrinterName)); + + // Verify that still shows as valid printer even if basic info and + // capabilities were originally null. + EXPECT_TRUE(GetPrintBackend()->IsValidPrinter(kNullDataPrinterName)); + + // Verify that (re)adding a printer with null info and capabilities still + // shows as valid. + GetPrintBackend()->AddValidPrinter(kAlternatePrinterName, /*caps=*/nullptr, + /*info=*/nullptr); + EXPECT_TRUE(GetPrintBackend()->IsValidPrinter(kAlternatePrinterName)); +} + +} // namespace printing
diff --git a/remoting/protocol/connection_unittest.cc b/remoting/protocol/connection_unittest.cc index ff4b858e0..d81147eb 100644 --- a/remoting/protocol/connection_unittest.cc +++ b/remoting/protocol/connection_unittest.cc
@@ -530,8 +530,7 @@ run_loop.Run(); } -// TODO(crbug.com/1143311): Test is flaky. -TEST_P(ConnectionTest, DISABLED_Video) { +TEST_P(ConnectionTest, Video) { Connect(); std::unique_ptr<VideoStream> video_stream =
diff --git a/services/service_manager/service_manager.cc b/services/service_manager/service_manager.cc index af7d55c..4599480 100644 --- a/services/service_manager/service_manager.cc +++ b/services/service_manager/service_manager.cc
@@ -190,11 +190,6 @@ instances_.clear(); } -void ServiceManager::SetInstanceQuitCallback( - base::OnceCallback<void(const Identity&)> callback) { - instance_quit_callback_ = std::move(callback); -} - ServiceInstance* ServiceManager::FindOrCreateMatchingTargetInstance( const ServiceInstance& source_instance, const ServiceFilter& partial_target_filter) { @@ -404,9 +399,6 @@ for (auto& listener : listeners_) { listener->OnServiceStopped(identity); } - - if (!instance_quit_callback_.is_null()) - std::move(instance_quit_callback_).Run(identity); } ServiceInstance* ServiceManager::GetExistingInstance(
diff --git a/services/service_manager/service_manager.h b/services/service_manager/service_manager.h index d4e84ab..4336d6b 100644 --- a/services/service_manager/service_manager.h +++ b/services/service_manager/service_manager.h
@@ -112,12 +112,6 @@ ~ServiceManager() override; - // Provide a callback to be notified whenever an instance is destroyed. - // Typically the creator of the Service Manager will use this to determine - // when some set of services it created are destroyed, so it can shut down. - void SetInstanceQuitCallback( - base::OnceCallback<void(const Identity&)> callback); - // Directly requests that the Service Manager start a new instance for // |service_name| if one is not already running. // @@ -212,7 +206,6 @@ ServiceInstance* service_manager_instance_; mojo::RemoteSet<mojom::ServiceManagerListener> listeners_; - base::OnceCallback<void(const Identity&)> instance_quit_callback_; DISALLOW_COPY_AND_ASSIGN(ServiceManager); };
diff --git a/third_party/abseil-cpp/symbols_x64_dbg.def b/third_party/abseil-cpp/symbols_x64_dbg.def index a6ef4bba2..c932512 100644 --- a/third_party/abseil-cpp/symbols_x64_dbg.def +++ b/third_party/abseil-cpp/symbols_x64_dbg.def
@@ -1336,7 +1336,6 @@ ??RStringReleaser@?M@???$?0V?$basic_string@DU?$char_traits@D@__1@std@@V?$allocator@D@23@@__1@std@@$0A@@Cord@absl@@QEAA@$$QEAV?$basic_string@DU?$char_traits@D@__1@std@@V?$allocator@D@23@@__1@std@@@Z@QEAAXVstring_view@2@@Z ??Sabsl@@YA?AVuint128@0@V10@@Z ??Tabsl@@YA?AVuint128@0@V10@0@Z - ??Uabsl@@YA?AVuint128@0@V10@0@Z ??XDuration@absl@@QEAAAEAV01@N@Z ??XDuration@absl@@QEAAAEAV01@_J@Z ??Xint128@absl@@QEAAAEAV01@V01@@Z
diff --git a/third_party/abseil-cpp/symbols_x86_dbg.def b/third_party/abseil-cpp/symbols_x86_dbg.def index 204aa4a..d56bc53 100644 --- a/third_party/abseil-cpp/symbols_x86_dbg.def +++ b/third_party/abseil-cpp/symbols_x86_dbg.def
@@ -1333,7 +1333,6 @@ ??RByUnixTime@Transition@cctz@time_internal@absl@@QBE_NABU1234@0@Z ??RStringReleaser@?M@???$?0V?$basic_string@DU?$char_traits@D@__1@std@@V?$allocator@D@23@@__1@std@@$0A@@Cord@absl@@QAE@$$QAV?$basic_string@DU?$char_traits@D@__1@std@@V?$allocator@D@23@@__1@std@@@Z@QAEXVstring_view@2@@Z ??Sabsl@@YA?AVuint128@0@V10@@Z - ??Uabsl@@YA?AVuint128@0@V10@0@Z ??XDuration@absl@@QAEAAV01@N@Z ??XDuration@absl@@QAEAAV01@_J@Z ??Xint128@absl@@QAEAAV01@V01@@Z
diff --git a/third_party/blink/renderer/core/css/css_counter_style_rule.cc b/third_party/blink/renderer/core/css/css_counter_style_rule.cc index e6155be..930dcfc 100644 --- a/third_party/blink/renderer/core/css/css_counter_style_rule.cc +++ b/third_party/blink/renderer/core/css/css_counter_style_rule.cc
@@ -4,6 +4,7 @@ #include "third_party/blink/renderer/core/css/css_counter_style_rule.h" #include "third_party/blink/renderer/core/css/style_rule.h" +#include "third_party/blink/renderer/platform/wtf/text/string_builder.h" namespace blink { @@ -15,7 +16,84 @@ CSSCounterStyleRule::~CSSCounterStyleRule() = default; String CSSCounterStyleRule::cssText() const { - return String(); + StringBuilder result; + result.Append("@counter-style "); + result.Append(name()); + result.Append(" {"); + + // Note: The exact serialization isn't well specified. + String system_text = system(); + if (system_text.length()) { + result.Append(" system: "); + result.Append(system_text); + result.Append(";"); + } + + String symbols_text = symbols(); + if (symbols_text.length()) { + result.Append(" symbols: "); + result.Append(symbols_text); + result.Append(";"); + } + + String additive_symbols_text = additiveSymbols(); + if (additive_symbols_text.length()) { + result.Append(" additive-symbols: "); + result.Append(additive_symbols_text); + result.Append(";"); + } + + String negative_text = negative(); + if (negative_text.length()) { + result.Append(" negative: "); + result.Append(negative_text); + result.Append(";"); + } + + String prefix_text = prefix(); + if (prefix_text.length()) { + result.Append(" prefix: "); + result.Append(prefix_text); + result.Append(";"); + } + + String suffix_text = suffix(); + if (suffix_text.length()) { + result.Append(" suffix: "); + result.Append(suffix_text); + result.Append(";"); + } + + String pad_text = pad(); + if (pad_text.length()) { + result.Append(" pad: "); + result.Append(pad_text); + result.Append(";"); + } + + String range_text = range(); + if (range_text.length()) { + result.Append(" range: "); + result.Append(range_text); + result.Append(";"); + } + + String fallback_text = fallback(); + if (fallback_text.length()) { + result.Append(" fallback: "); + result.Append(fallback_text); + result.Append(";"); + } + + String speak_as_text = speakAs(); + if (speak_as_text.length()) { + result.Append(" speak-as: "); + result.Append(speak_as_text); + result.Append(";"); + } + + result.Append(" }"); + return result.ToString(); } void CSSCounterStyleRule::Reattach(StyleRuleBase* rule) {
diff --git a/third_party/blink/renderer/core/css/css_value_keywords.json5 b/third_party/blink/renderer/core/css/css_value_keywords.json5 index dba0e266..afe28fcc 100644 --- a/third_party/blink/renderer/core/css/css_value_keywords.json5 +++ b/third_party/blink/renderer/core/css/css_value_keywords.json5
@@ -1447,5 +1447,24 @@ "more", "less", "forced", + + // @counter-style system + "cyclic", + // fixed, + "symbolic", + // alphabetic, + "numeric", + "additive", + "extends", + + // @counter-style range + // infinite, + + // @counter-style speak-as + // auto + "bullets", + "numbers", + "words", + // spell-out, ], }
diff --git a/third_party/blink/renderer/core/css/parser/at_rule_counter_style_descriptor_parser.cc b/third_party/blink/renderer/core/css/parser/at_rule_counter_style_descriptor_parser.cc index 6d524bea..4dc23632 100644 --- a/third_party/blink/renderer/core/css/parser/at_rule_counter_style_descriptor_parser.cc +++ b/third_party/blink/renderer/core/css/parser/at_rule_counter_style_descriptor_parser.cc
@@ -6,6 +6,7 @@ #include "third_party/blink/renderer/core/css/css_string_value.h" #include "third_party/blink/renderer/core/css/css_value.h" +#include "third_party/blink/renderer/core/css/css_value_pair.h" #include "third_party/blink/renderer/core/css/parser/css_parser_token_range.h" #include "third_party/blink/renderer/core/css/properties/css_parsing_utils.h" @@ -13,48 +14,208 @@ namespace { -CSSValue* ConsumeCounterStyleName(CSSParserTokenRange& range) { - // TODO(crbug.com/687225): Implement. +CSSValue* ConsumeCounterStyleName(CSSParserTokenRange& range, + const CSSParserContext& context) { + // <counter-style-name> is a <custom-ident> that is not an ASCII + // case-insensitive match for "none". + CSSCustomIdentValue* name = + css_parsing_utils::ConsumeCustomIdent(range, context); + if (!name || EqualIgnoringASCIICase(name->Value(), "none")) + return nullptr; + return name; +} + +CSSValue* ConsumeCounterStyleSymbol(CSSParserTokenRange& range, + const CSSParserContext& context) { + // <symbol> = <string> | <image> | <custom-ident> + if (CSSValue* string = css_parsing_utils::ConsumeString(range)) + return string; + if (CSSValue* image = css_parsing_utils::ConsumeImage(range, context)) + return image; + if (CSSCustomIdentValue* custom_ident = + css_parsing_utils::ConsumeCustomIdent(range, context)) { + return custom_ident; + } return nullptr; } -CSSValue* ConsumeCounterStyleSymbol(CSSParserTokenRange& range) { - // TODO(crbug.com/687225): Implement. +CSSValue* ConsumeCounterStyleSystem(CSSParserTokenRange& range, + const CSSParserContext& context) { + // Syntax: cyclic | numeric | alphabetic | symbolic | additive | + // [ fixed <integer>? ] | [ extends <counter-style-name> ] + if (CSSValue* ident = css_parsing_utils::ConsumeIdent< + CSSValueID::kCyclic, CSSValueID::kSymbolic, CSSValueID::kAlphabetic, + CSSValueID::kNumeric, CSSValueID::kAdditive>(range)) + return ident; + + if (CSSValue* ident = + css_parsing_utils::ConsumeIdent<CSSValueID::kFixed>(range)) { + CSSValue* first_symbol_value = + css_parsing_utils::ConsumeInteger(range, context); + if (!first_symbol_value) { + first_symbol_value = CSSNumericLiteralValue::Create( + 1, CSSPrimitiveValue::UnitType::kInteger); + } + return MakeGarbageCollected<CSSValuePair>( + ident, first_symbol_value, CSSValuePair::kKeepIdenticalValues); + } + + if (CSSValue* ident = + css_parsing_utils::ConsumeIdent<CSSValueID::kExtends>(range)) { + CSSValue* extended = ConsumeCounterStyleName(range, context); + if (!extended) + return nullptr; + return MakeGarbageCollected<CSSValuePair>( + ident, extended, CSSValuePair::kKeepIdenticalValues); + } return nullptr; } -CSSValue* ConsumeCounterStyleSystem(CSSParserTokenRange& range) { - // TODO(crbug.com/687225): Implement. +CSSValue* ConsumeCounterStyleNegative(CSSParserTokenRange& range, + const CSSParserContext& context) { + // Syntax: <symbol> <symbol>? + CSSValue* prepend = ConsumeCounterStyleSymbol(range, context); + if (!prepend) + return nullptr; + if (range.AtEnd()) + return prepend; + + CSSValue* append = ConsumeCounterStyleSymbol(range, context); + if (!append || !range.AtEnd()) + return nullptr; + + return MakeGarbageCollected<CSSValuePair>(prepend, append, + CSSValuePair::kKeepIdenticalValues); +} + +CSSValue* ConsumeCounterStyleRangeBound(CSSParserTokenRange& range, + const CSSParserContext& context) { + if (CSSValue* infinite = + css_parsing_utils::ConsumeIdent<CSSValueID::kInfinite>(range)) + return infinite; + if (CSSValue* integer = css_parsing_utils::ConsumeInteger(range, context)) + return integer; return nullptr; } -CSSValue* ConsumeCounterStyleNegative(CSSParserTokenRange& range) { - // TODO(crbug.com/687225): Implement. - return nullptr; +CSSValue* ConsumeCounterStyleRange(CSSParserTokenRange& range, + const CSSParserContext& context) { + // Syntax: [ [ <integer> | infinite ]{2} ]# | auto + if (CSSValue* auto_value = + css_parsing_utils::ConsumeIdent<CSSValueID::kAuto>(range)) + return auto_value; + + CSSValueList* list = CSSValueList::CreateCommaSeparated(); + do { + CSSValue* lower_bound = ConsumeCounterStyleRangeBound(range, context); + if (!lower_bound) + return nullptr; + CSSValue* upper_bound = ConsumeCounterStyleRangeBound(range, context); + if (!upper_bound) + return nullptr; + + // If the lower bound of any range is higher than the upper bound, the + // entire descriptor is invalid and must be ignored. + if (lower_bound->IsPrimitiveValue() && upper_bound->IsPrimitiveValue() && + To<CSSPrimitiveValue>(lower_bound)->GetIntValue() > + To<CSSPrimitiveValue>(upper_bound)->GetIntValue()) + return nullptr; + + list->Append(*MakeGarbageCollected<CSSValuePair>( + lower_bound, upper_bound, CSSValuePair::kKeepIdenticalValues)); + } while (css_parsing_utils::ConsumeCommaIncludingWhitespace(range)); + if (!range.AtEnd() || !list->length()) + return nullptr; + return list; } -CSSValue* ConsumeCounterStyleRange(CSSParserTokenRange& range) { - // TODO(crbug.com/687225): Implement. - return nullptr; +CSSValue* ConsumeCounterStylePad(CSSParserTokenRange& range, + const CSSParserContext& context) { + // Syntax: <integer [0,∞]> && <symbol> + CSSValue* integer = nullptr; + CSSValue* symbol = nullptr; + while (!integer || !symbol) { + if (!integer) { + integer = css_parsing_utils::ConsumeInteger(range, context, 0); + if (integer) + continue; + } + if (!symbol) { + symbol = ConsumeCounterStyleSymbol(range, context); + if (symbol) + continue; + } + return nullptr; + } + if (!range.AtEnd()) + return nullptr; + + return MakeGarbageCollected<CSSValuePair>(integer, symbol, + CSSValuePair::kKeepIdenticalValues); } -CSSValue* ConsumeCounterStylePad(CSSParserTokenRange& range) { - // TODO(crbug.com/687225): Implement. - return nullptr; +CSSValue* ConsumeCounterStyleSymbols(CSSParserTokenRange& range, + const CSSParserContext& context) { + // Syntax: <symbol>+ + CSSValueList* list = CSSValueList::CreateSpaceSeparated(); + while (!range.AtEnd()) { + CSSValue* symbol = ConsumeCounterStyleSymbol(range, context); + if (!symbol) + return nullptr; + list->Append(*symbol); + } + if (!list->length()) + return nullptr; + return list; } -CSSValue* ConsumeCounterStyleSymbols(CSSParserTokenRange& range) { - // TODO(crbug.com/687225): Implement. - return nullptr; +CSSValue* ConsumeCounterStyleAdditiveSymbols(CSSParserTokenRange& range, + const CSSParserContext& context) { + // Syntax: [ <integer [0,∞]> && <symbol> ]# + CSSValueList* list = CSSValueList::CreateCommaSeparated(); + CSSPrimitiveValue* last_integer = nullptr; + do { + CSSPrimitiveValue* integer = nullptr; + CSSValue* symbol = nullptr; + while (!integer || !symbol) { + if (!integer) { + integer = css_parsing_utils::ConsumeInteger(range, context, 0); + if (integer) + continue; + } + if (!symbol) { + symbol = ConsumeCounterStyleSymbol(range, context); + if (symbol) + continue; + } + return nullptr; + } + + if (last_integer) { + // The additive tuples must be specified in order of strictly descending + // weight; otherwise, the declaration is invalid and must be ignored. + if (integer->GetIntValue() >= last_integer->GetIntValue()) + return nullptr; + } + last_integer = integer; + + list->Append(*MakeGarbageCollected<CSSValuePair>( + integer, symbol, CSSValuePair::kKeepIdenticalValues)); + } while (css_parsing_utils::ConsumeCommaIncludingWhitespace(range)); + if (!range.AtEnd() || !list->length()) + return nullptr; + return list; } -CSSValue* ConsumeCounterStyleAdditiveSymbols(CSSParserTokenRange& range) { - // TODO(crbug.com/687225): Implement. - return nullptr; -} - -CSSValue* ConsumeCounterStyleSpeakAs(CSSParserTokenRange& range) { - // TODO(crbug.com/687225): Implement. +CSSValue* ConsumeCounterStyleSpeakAs(CSSParserTokenRange& range, + const CSSParserContext& context) { + // Syntax: auto | bullets | numbers | words | spell-out | <counter-style-name> + if (CSSValue* ident = css_parsing_utils::ConsumeIdent< + CSSValueID::kAuto, CSSValueID::kBullets, CSSValueID::kNumbers, + CSSValueID::kWords, CSSValueID::kSpellOut>(range)) + return ident; + if (CSSValue* name = ConsumeCounterStyleName(range, context)) + return name; return nullptr; } @@ -70,40 +231,40 @@ switch (id) { case AtRuleDescriptorID::System: range.ConsumeWhitespace(); - parsed_value = ConsumeCounterStyleSystem(range); + parsed_value = ConsumeCounterStyleSystem(range, context); break; case AtRuleDescriptorID::Negative: range.ConsumeWhitespace(); - parsed_value = ConsumeCounterStyleNegative(range); + parsed_value = ConsumeCounterStyleNegative(range, context); break; case AtRuleDescriptorID::Prefix: case AtRuleDescriptorID::Suffix: range.ConsumeWhitespace(); - parsed_value = ConsumeCounterStyleSymbol(range); + parsed_value = ConsumeCounterStyleSymbol(range, context); break; case AtRuleDescriptorID::Range: range.ConsumeWhitespace(); - parsed_value = ConsumeCounterStyleRange(range); + parsed_value = ConsumeCounterStyleRange(range, context); break; case AtRuleDescriptorID::Pad: range.ConsumeWhitespace(); - parsed_value = ConsumeCounterStylePad(range); + parsed_value = ConsumeCounterStylePad(range, context); break; case AtRuleDescriptorID::Fallback: range.ConsumeWhitespace(); - parsed_value = ConsumeCounterStyleName(range); + parsed_value = ConsumeCounterStyleName(range, context); break; case AtRuleDescriptorID::Symbols: range.ConsumeWhitespace(); - parsed_value = ConsumeCounterStyleSymbols(range); + parsed_value = ConsumeCounterStyleSymbols(range, context); break; case AtRuleDescriptorID::AdditiveSymbols: range.ConsumeWhitespace(); - parsed_value = ConsumeCounterStyleAdditiveSymbols(range); + parsed_value = ConsumeCounterStyleAdditiveSymbols(range, context); break; case AtRuleDescriptorID::SpeakAs: range.ConsumeWhitespace(); - parsed_value = ConsumeCounterStyleSpeakAs(range); + parsed_value = ConsumeCounterStyleSpeakAs(range, context); break; default: break;
diff --git a/third_party/blink/renderer/core/css/parser/css_parser_impl.cc b/third_party/blink/renderer/core/css/parser/css_parser_impl.cc index 55c4c9e..f738200 100644 --- a/third_party/blink/renderer/core/css/parser/css_parser_impl.cc +++ b/third_party/blink/renderer/core/css/parser/css_parser_impl.cc
@@ -943,9 +943,11 @@ if (!prelude.AtEnd()) return nullptr; - // TODO(crbug.com/687225): If the name is invalid (none, decimal, disc and - // CSS-wide keywords), should the entire rule be invalid, or does it simply - // not define a counter style? + if (name_token.GetType() != kIdentToken || + !css_parsing_utils::IsCustomIdent<CSSValueID::kNone, CSSValueID::kDecimal, + CSSValueID::kDisc>(name_token.Id())) + return nullptr; + AtomicString name(name_token.Value().ToString()); if (observer_) {
diff --git a/third_party/blink/renderer/core/dom/scripted_animation_controller.cc b/third_party/blink/renderer/core/dom/scripted_animation_controller.cc index 3f909b3..36d77748 100644 --- a/third_party/blink/renderer/core/dom/scripted_animation_controller.cc +++ b/third_party/blink/renderer/core/dom/scripted_animation_controller.cc
@@ -69,6 +69,7 @@ visitor->Trace(callback_collection_); visitor->Trace(event_queue_); visitor->Trace(media_query_list_listeners_); + visitor->Trace(media_query_list_listeners_set_); visitor->Trace(per_frame_events_); } @@ -169,7 +170,8 @@ void ScriptedAnimationController::CallMediaQueryListListeners() { MediaQueryListListeners listeners; - listeners.Swap(media_query_list_listeners_); + listeners.swap(media_query_list_listeners_); + media_query_list_listeners_set_.clear(); for (const auto& listener : listeners) { listener->NotifyMediaQueryChanged(); @@ -267,8 +269,13 @@ void ScriptedAnimationController::EnqueueMediaQueryChangeListeners( HeapVector<Member<MediaQueryListListener>>& listeners) { for (const auto& listener : listeners) { - media_query_list_listeners_.insert(listener); + if (!media_query_list_listeners_set_.Contains(listener)) { + media_query_list_listeners_.push_back(listener); + media_query_list_listeners_set_.insert(listener); + } } + DCHECK_EQ(media_query_list_listeners_.size(), + media_query_list_listeners_set_.size()); ScheduleAnimationIfNeeded(); }
diff --git a/third_party/blink/renderer/core/dom/scripted_animation_controller.h b/third_party/blink/renderer/core/dom/scripted_animation_controller.h index 7f55d6b6..6e09d92 100644 --- a/third_party/blink/renderer/core/dom/scripted_animation_controller.h +++ b/third_party/blink/renderer/core/dom/scripted_animation_controller.h
@@ -121,9 +121,11 @@ using PerFrameEventsMap = HeapHashMap<Member<const EventTarget>, HashSet<const StringImpl*>>; PerFrameEventsMap per_frame_events_; - using MediaQueryListListeners = - HeapListHashSet<Member<MediaQueryListListener>>; + using MediaQueryListListeners = HeapVector<Member<MediaQueryListListener>>; MediaQueryListListeners media_query_list_listeners_; + // This is used to quickly lookup if a listener exists in + // media_query_list_listeners_. The contents should be exactly the same. + HeapHashSet<Member<MediaQueryListListener>> media_query_list_listeners_set_; double current_frame_time_ms_ = 0.0; double current_frame_legacy_time_ms_ = 0.0;
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_base.cc b/third_party/blink/renderer/core/frame/web_frame_widget_base.cc index bc6ed57..4c36665 100644 --- a/third_party/blink/renderer/core/frame/web_frame_widget_base.cc +++ b/third_party/blink/renderer/core/frame/web_frame_widget_base.cc
@@ -1311,6 +1311,10 @@ } void WebFrameWidgetBase::SetZoomLevel(double zoom_level) { + // Override the zoom level with the testing one if necessary. + if (zoom_level_for_testing_ != -INFINITY) + zoom_level = zoom_level_for_testing_; + View()->SetZoomLevel(zoom_level); // Part of the UpdateVisualProperties dance we send the zoom level to @@ -3059,6 +3063,10 @@ return {}; } +float WebFrameWidgetBase::GetTestingDeviceScaleFactorOverride() { + return device_scale_factor_for_testing_; +} + void WebFrameWidgetBase::ReleaseMouseLockAndPointerCaptureForTesting() { GetPage()->GetPointerLockController().ExitPointerLock(); MouseCaptureLost(); @@ -3075,6 +3083,49 @@ return CoreHitTestResultAt(point); } +void WebFrameWidgetBase::SetZoomLevelForTesting(double zoom_level) { + DCHECK(ForMainFrame()); + DCHECK_NE(zoom_level, -INFINITY); + zoom_level_for_testing_ = zoom_level; + SetZoomLevel(zoom_level); +} + +void WebFrameWidgetBase::ResetZoomLevelForTesting() { + DCHECK(ForMainFrame()); + zoom_level_for_testing_ = -INFINITY; + SetZoomLevel(0); +} + +void WebFrameWidgetBase::SetDeviceScaleFactorForTesting(float factor) { + DCHECK(ForMainFrame()); + DCHECK_GE(factor, 0.f); + + // Stash the window size before we adjust the scale factor, as subsequent + // calls to convert will use the new scale factor. + gfx::Size size_in_dips = widget_base_->BlinkSpaceToFlooredDIPs(Size()); + device_scale_factor_for_testing_ = factor; + + // Receiving a 0 is used to reset between tests, it removes the override in + // order to listen to the browser for the next test. + if (!factor) + return; + + // We are changing the device scale factor from the renderer, so allocate a + // new viz::LocalSurfaceId to avoid surface invariants violations in tests. + widget_base_->LayerTreeHost()->RequestNewLocalSurfaceId(); + + ScreenInfo info = widget_base_->GetScreenInfo(); + info.device_scale_factor = factor; + gfx::Size size_with_dsf = gfx::ScaleToCeiledSize(size_in_dips, factor); + widget_base_->UpdateCompositorViewportAndScreenInfo(gfx::Rect(size_with_dsf), + info); + if (!AutoResizeMode()) { + // This picks up the new device scale factor as + // `UpdateCompositorViewportAndScreenInfo()` has applied a new value. + Resize(widget_base_->DIPsToCeiledBlinkSpace(size_in_dips)); + } +} + WebPlugin* WebFrameWidgetBase::GetFocusedPluginContainer() { LocalFrame* focused_frame = FocusedLocalFrameInWidget(); if (!focused_frame)
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_base.h b/third_party/blink/renderer/core/frame/web_frame_widget_base.h index a7965977..ccc14a57 100644 --- a/third_party/blink/renderer/core/frame/web_frame_widget_base.h +++ b/third_party/blink/renderer/core/frame/web_frame_widget_base.h
@@ -300,6 +300,9 @@ void ReleaseMouseLockAndPointerCaptureForTesting() override; const viz::FrameSinkId& GetFrameSinkId() override; WebHitTestResult HitTestResultAt(const gfx::PointF&) override; + void SetZoomLevelForTesting(double zoom_level) override; + void ResetZoomLevelForTesting() override; + void SetDeviceScaleFactorForTesting(float factor) override; // Called when a drag-n-drop operation should begin. void StartDragging(const WebDragData&, @@ -404,6 +407,7 @@ void WasHidden() override; void WasShown(bool was_evicted) override; KURL GetURLForDebugTrace() override; + float GetTestingDeviceScaleFactorOverride() override; // mojom::blink::FrameWidget methods. void DragTargetDragEnter(const WebDragData&, @@ -477,7 +481,7 @@ // Called when the FrameView for this Widget's local root is created. virtual void DidCreateLocalRootView() {} - virtual void SetZoomLevel(double zoom_level); + void SetZoomLevel(double zoom_level); // Enable or disable auto-resize. This is part of // UpdateVisualProperties though tests may call to it more directly. @@ -835,6 +839,20 @@ return child_local_root_data_; } + // Web tests override the zoom factor in the renderer with this. We store it + // to keep the override if the browser passes along VisualProperties with the + // real device scale factor. A value of -INFINITY means this is ignored. + // It is always valid to read this variable but it can only be set for main + // frame widgets. + double zoom_level_for_testing_ = -INFINITY; + + // Web tests override the device scale factor in the renderer with this. We + // store it to keep the override if the browser passes along VisualProperties + // with the real device scale factor. A value of 0.f means this is ignored. + // It is always valid to read this variable but it can only be set for main + // frame widgets. + float device_scale_factor_for_testing_ = 0; + friend class WebViewImpl; friend class ReportTimeSwapPromise; };
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc index 7474306b..7d9d3b1 100644 --- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc +++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
@@ -345,20 +345,6 @@ return true; } -void WebFrameWidgetImpl::SetZoomLevelForTesting(double zoom_level) { - // Zoom level is only controlled for testing on the main frame. - NOTREACHED(); -} - -void WebFrameWidgetImpl::ResetZoomLevelForTesting() { - // Zoom level is only controlled for testing on the main frame. - NOTREACHED(); -} - -void WebFrameWidgetImpl::SetDeviceScaleFactorForTesting(float factor) { - NOTREACHED(); -} - void WebFrameWidgetImpl::IntrinsicSizingInfoChanged( mojom::blink::IntrinsicSizingInfoPtr sizing_info) { GetAssociatedFrameWidgetHost()->IntrinsicSizingInfoChanged(
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h index aeaded62..2def196 100644 --- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.h +++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.h
@@ -102,9 +102,6 @@ // WebFrameWidget implementation. bool ScrollFocusedEditableElementIntoView() override; - void SetZoomLevelForTesting(double zoom_level) override; - void ResetZoomLevelForTesting() override; - void SetDeviceScaleFactorForTesting(float factor) override; PaintLayerCompositor* Compositor() const;
diff --git a/third_party/blink/renderer/core/frame/web_view_frame_widget.cc b/third_party/blink/renderer/core/frame/web_view_frame_widget.cc index 9cb5d2f..4fab15d 100644 --- a/third_party/blink/renderer/core/frame/web_view_frame_widget.cc +++ b/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
@@ -115,10 +115,6 @@ web_view_->SetFocus(enable); } -float WebViewFrameWidget::GetDeviceScaleFactorForTesting() { - return device_scale_factor_for_testing_; -} - bool WebViewFrameWidget::ShouldHandleImeEvents() { return HasFocus(); } @@ -373,53 +369,6 @@ return nullptr; } -void WebViewFrameWidget::SetZoomLevelForTesting(double zoom_level) { - DCHECK_NE(zoom_level, -INFINITY); - zoom_level_for_testing_ = zoom_level; - SetZoomLevel(zoom_level); -} - -void WebViewFrameWidget::ResetZoomLevelForTesting() { - zoom_level_for_testing_ = -INFINITY; - SetZoomLevel(0); -} - -void WebViewFrameWidget::SetDeviceScaleFactorForTesting(float factor) { - DCHECK_GE(factor, 0.f); - - // Stash the window size before we adjust the scale factor, as subsequent - // calls to convert will use the new scale factor. - gfx::Size size_in_dips = widget_base_->BlinkSpaceToFlooredDIPs(size_); - device_scale_factor_for_testing_ = factor; - - // Receiving a 0 is used to reset between tests, it removes the override in - // order to listen to the browser for the next test. - if (!factor) - return; - - // We are changing the device scale factor from the renderer, so allocate a - // new viz::LocalSurfaceId to avoid surface invariants violations in tests. - widget_base_->LayerTreeHost()->RequestNewLocalSurfaceId(); - - ScreenInfo info = widget_base_->GetScreenInfo(); - info.device_scale_factor = factor; - gfx::Size size_with_dsf = gfx::ScaleToCeiledSize(size_in_dips, factor); - widget_base_->UpdateCompositorViewportAndScreenInfo(gfx::Rect(size_with_dsf), - info); - if (!AutoResizeMode()) { - // This picks up the new device scale factor as - // UpdateCompositorViewportAndScreenInfo has applied a new value. - Resize(widget_base_->DIPsToCeiledBlinkSpace(size_in_dips)); - } -} - -void WebViewFrameWidget::SetZoomLevel(double zoom_level) { - // Override the zoom level with the testing one if necessary - if (zoom_level_for_testing_ != -INFINITY) - zoom_level = zoom_level_for_testing_; - WebFrameWidgetBase::SetZoomLevel(zoom_level); -} - void WebViewFrameWidget::SetAutoResizeMode(bool auto_resize, const gfx::Size& min_window_size, const gfx::Size& max_window_size,
diff --git a/third_party/blink/renderer/core/frame/web_view_frame_widget.h b/third_party/blink/renderer/core/frame/web_view_frame_widget.h index b1d0cf4..74ecdb6 100644 --- a/third_party/blink/renderer/core/frame/web_view_frame_widget.h +++ b/third_party/blink/renderer/core/frame/web_view_frame_widget.h
@@ -68,19 +68,13 @@ DocumentUpdateReason reason) override; void MouseCaptureLost() override; - // blink::mojom::FrameWidget - // WebFrameWidget overrides: bool ScrollFocusedEditableElementIntoView() override; - void SetZoomLevelForTesting(double zoom_level) override; - void ResetZoomLevelForTesting() override; - void SetDeviceScaleFactorForTesting(float factor) override; // WebFrameWidgetBase overrides: bool ForSubframe() const override { return false; } bool ForTopLevelFrame() const override { return !is_for_nested_main_frame_; } void ZoomToFindInPageRect(const WebRect& rect_in_root_frame) override; - void SetZoomLevel(double zoom_level) override; void SetAutoResizeMode(bool auto_resize, const gfx::Size& min_size_before_dsf, const gfx::Size& max_size_before_dsf, @@ -101,7 +95,6 @@ void ApplyViewportChanges(const cc::ApplyViewportChangesArgs& args) override; void RecordManipulationTypeCounts(cc::ManipulationInfo info) override; void FocusChanged(bool enabled) override; - float GetDeviceScaleFactorForTesting() override; void RunPaintBenchmark(int repeat_count, cc::PaintBenchmarkResult& result) override; @@ -126,16 +119,6 @@ scoped_refptr<WebViewImpl> web_view_; - // Web tests override the zoom factor in the renderer with this. We store it - // to keep the override if the browser passes along VisualProperties with the - // real device scale factor. A value of -INFINITY means this is ignored. - double zoom_level_for_testing_ = -INFINITY; - - // Web tests override the device scale factor in the renderer with this. We - // store it to keep the override if the browser passes along VisualProperties - // with the real device scale factor. A value of 0.f means this is ignored. - float device_scale_factor_for_testing_ = 0; - // This bit is used to tell if this is a nested widget (an "inner web // contents") like a <webview> or <portal> widget. If false, the widget is the // top level widget.
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc b/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc index b002dbfa..324de371 100644 --- a/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc +++ b/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc
@@ -463,13 +463,17 @@ properties.unclipped_absolute_bounding_box = EnclosingIntRect(geometry_map_->AbsoluteRect( - layer->BoundingBoxForCompositingOverlapTest())); + layer->LocalBoundingBoxForCompositingOverlapTest())); bool affected_by_scroll = root_layer_->GetScrollableArea() && layer->IsAffectedByScrollOf(root_layer_); - // At ths point, |unclipped_absolute_bounding_box| is in viewport space. + // At this point, |unclipped_absolute_bounding_box| is in viewport space. // To convert to absolute space, add scroll offset for non-fixed layers. + // Content that is not affected by scroll, e.g. fixed-pos content and + // children of that content, stays in viewport space so we can expand its + // bounds during overlap testing without having a dependency on the scroll + // offset at the time these properties are calculated. if (affected_by_scroll) { properties.unclipped_absolute_bounding_box.Move( RoundedIntSize(root_layer_->GetScrollableArea()->GetScrollOffset()));
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_requirements_updater.cc b/third_party/blink/renderer/core/paint/compositing/compositing_requirements_updater.cc index cb0486ae..17482ee 100644 --- a/third_party/blink/renderer/core/paint/compositing/compositing_requirements_updater.cc +++ b/third_party/blink/renderer/core/paint/compositing/compositing_requirements_updater.cc
@@ -352,23 +352,8 @@ unclipped_descendants.push_back(layer); } - IntRect abs_bounds = use_clipped_bounding_rect - ? layer->ClippedAbsoluteBoundingBox() - : layer->UnclippedAbsoluteBoundingBox(); - - if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { - PaintLayer* root_layer = layout_view_.Layer(); - // |abs_bounds| does not include root scroller offset. For the purposes - // of overlap, this only matters for fixed-position objects, and their - // relative position to other elements. Therefore, it's still correct to, - // instead of adding scroll to all non-fixed elements, add a reverse scroll - // to ones that are fixed. - if (root_layer->GetScrollableArea() && - !layer->IsAffectedByScrollOf(root_layer)) { - abs_bounds.Move( - RoundedIntSize(root_layer->GetScrollableArea()->GetScrollOffset())); - } - } + IntRect abs_bounds = layer->ExpandedBoundingBoxForCompositingOverlapTest( + use_clipped_bounding_rect); absolute_descendant_bounding_box = abs_bounds; if (layer_can_be_composited && current_recursion_data.testing_overlap_ &&
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc index f171e56d..a9231c68 100644 --- a/third_party/blink/renderer/core/paint/paint_layer.cc +++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -409,6 +409,30 @@ return current_layer == ancestor; } +bool PaintLayer::IsTopMostNotAffectedByScrollOf( + const PaintLayer* ancestor) const { + // Returns true if |this| is the top-most fixed-pos layer between |this| + // (inclusive) and |ancestor. + + // Should only call this method for layers that we already know are not + // affected by the scroll offset of the ancestor (implying this element or + // an ancestor must be fixed). + DCHECK(!IsAffectedByScrollOf(ancestor)); + + // Only fixed-pos elements can be top-most. + if (!GetLayoutObject().IsFixedPositioned()) + return false; + + PaintLayer* curr = Parent(); + while (curr && curr != ancestor) { + if (curr->GetLayoutObject().IsFixedPositioned()) + return false; + curr = curr->Parent(); + } + + return true; +} + void PaintLayer::UpdateTransformationMatrix() { if (TransformationMatrix* transform = Transform()) { LayoutBox* box = GetLayoutBox(); @@ -1079,7 +1103,7 @@ const IntRect PaintLayer::ClippedAbsoluteBoundingBox() const { if (RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { - PhysicalRect mapping_rect = BoundingBoxForCompositingOverlapTest(); + PhysicalRect mapping_rect = LocalBoundingBoxForCompositingOverlapTest(); GetLayoutObject().MapToVisualRectInAncestorSpace( GetLayoutObject().View(), mapping_rect, kUseGeometryMapper); return EnclosingIntRect(mapping_rect); @@ -1091,7 +1115,7 @@ const IntRect PaintLayer::UnclippedAbsoluteBoundingBox() const { if (RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { return EnclosingIntRect(GetLayoutObject().LocalToAbsoluteRect( - BoundingBoxForCompositingOverlapTest(), + LocalBoundingBoxForCompositingOverlapTest(), kUseGeometryMapperMode | kIgnoreScrollOffset)); } else { return GetAncestorDependentCompositingInputs() @@ -2503,7 +2527,7 @@ return; PhysicalRect result = LocalBoundingBox(); ExpandRectForStackingChildren( - *this, result, PaintLayer::kIncludeTransformsAndCompositedChildLayers); + *this, result, kIncludeTransforms | kIncludeCompositedChildLayers); FloatRect reference_box = FloatRect(result); float zoom = GetLayoutObject().StyleRef().EffectiveZoom(); @@ -2645,7 +2669,15 @@ return result; } -PhysicalRect PaintLayer::BoundingBoxForCompositingOverlapTest() const { +PhysicalRect PaintLayer::LocalBoundingBoxForCompositingOverlapTest() const { + // Returns the bounding box, in the local coordinate system for this layer, + // for the content that this paint layer is responsible for compositing. This + // doesn't include the content painted by self-painting descendants such as + // composited absolute positioned children. But the bounds is suitable for + // calculations such as squashing sparsity. To get the bounds that includes + // the visible extent of this layer and its children for overlap testing, use + // ExpandedBoundingBoxForCompositingOverlapTest. + // Apply NeverIncludeTransformForAncestorLayer, because the geometry map in // CompositingInputsUpdater will take care of applying the transform of |this| // (== the ancestorLayer argument to boundingBoxForCompositing). @@ -2664,37 +2696,85 @@ style.BackdropFilter().MapRect(FloatRect(bounding_box))); } - if (FixedToViewport() && !bounding_box.IsEmpty()) { - DCHECK_EQ(style.GetPosition(), EPosition::kFixed); - // Note that we only expand the bounding box for overlap testing when the - // fixed's containing block is the viewport. This keeps us from expanding - // the bounds when the fixed is a child of an ancestor with transform, - // filters, etc. and the fixed is no longer scroll position dependent. - - // Expand the bounding box by the amount that scrolling the - // viewport can expand the area that this fixed-pos element could - // cover. Compute how much we could still scroll in each direction. - // |max_scroll_delta| is the amount we could still scroll in - // increasing offset direction. |min_scroll_delta| is the amount we - // can still scroll in a decreasing scroll offset direction. - PaintLayerScrollableArea* scrollable_area = - GetLayoutObject().View()->GetScrollableArea(); - ScrollOffset current_scroll_offset = scrollable_area->GetScrollOffset(); - ScrollOffset max_scroll_delta = - scrollable_area->MaximumScrollOffset() - current_scroll_offset; - ScrollOffset min_scroll_delta = - current_scroll_offset - scrollable_area->MinimumScrollOffset(); - bounding_box.Expand( - LayoutRectOutsets(min_scroll_delta.Height(), max_scroll_delta.Width(), - max_scroll_delta.Height(), min_scroll_delta.Width())); - } return bounding_box; } +IntRect PaintLayer::ExpandedBoundingBoxForCompositingOverlapTest( + bool use_clipped_bounding_rect) const { + // Returns the bounding box for this layer and self-painted composited + // children which are otherwise not included in + // LocalBoundingBoxForCompositingOverlapTest. Use the bounds from this layer + // for overlap testing that cares about the bounds of this layer and all its + // children. + IntRect abs_bounds = use_clipped_bounding_rect + ? ClippedAbsoluteBoundingBox() + : UnclippedAbsoluteBoundingBox(); + PaintLayer* root_layer = GetLayoutObject().View()->Layer(); + // |abs_bounds| does not include root scroller offset, as in it's in absolute + // coordinates, for everything but fixed-pos objects (and their children) + // which are in viewport coords. Adjusting these to all be in absolute coords + // happens here. This adjustment is delayed until this point in time as doing + // it during compositing inputs update would embed the scroll offset at the + // time the compositing inputs was ran when converting from viewport to + // absolute, making the resulting rects unusable for any other scroll offset. + if (root_layer->GetScrollableArea() && !abs_bounds.IsEmpty() && + !IsAffectedByScrollOf(root_layer)) { + PaintLayerScrollableArea* scrollable_area = root_layer->GetScrollableArea(); + ScrollOffset current_scroll_offset = scrollable_area->GetScrollOffset(); + // Move the bounds to absolute coords + if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) + abs_bounds.Move(RoundedIntSize(current_scroll_offset)); + + if (IsTopMostNotAffectedByScrollOf(root_layer)) { + // For overlap testing, expand the rect used for fixed-pos content in + // two ways. First, include any children such that overlap testing + // against the top-most fixed-pos layer is guaranteed to detect any + // overlap where a self-painting composited child of the fixed-pos layer + // exceeds the fixed-pos layer's bounds. Second, expand the rect to + // include the area it could cover if the view were to be scrolled to + // its minimum and maximum extents. This allows skipping overlap testing + // on scroll offset changes. Note that bounds expansion does not happen + // for fixed under a non-view container (under xform or filter for + // example) as those fixed still are affected by the view's scroll offset. + // This is checked for with the IsAffectedByScrollOf call earlier. + + // Expand the rect to include children that are not already included in + // |layer|'s bounds. + PhysicalRect children_bounds; + if (!GetLayoutObject().ChildPaintBlockedByDisplayLock()) { + PaintLayerPaintOrderIterator iterator(*this, kAllChildren); + while (PaintLayer* child_layer = iterator.Next()) { + // Note that we intentionally include children irrespective of if they + // are composited or not. + children_bounds.Unite(child_layer->BoundingBoxForCompositingInternal( + *this, this, + kIncludeClips | kIncludeTransforms | + kIncludeCompositedChildLayers)); + } + if (!children_bounds.IsEmpty()) { + GetLayoutObject().MapToVisualRectInAncestorSpace( + GetLayoutObject().View(), children_bounds, kUseGeometryMapper); + abs_bounds.Unite(EnclosingIntRect(children_bounds)); + } + } + + // Expand bounds to include min/max scroll extents + ScrollOffset max_scroll_delta = + scrollable_area->MaximumScrollOffset() - current_scroll_offset; + ScrollOffset min_scroll_delta = + current_scroll_offset - scrollable_area->MinimumScrollOffset(); + abs_bounds.Expand( + IntRectOutsets(min_scroll_delta.Height(), max_scroll_delta.Width(), + max_scroll_delta.Height(), min_scroll_delta.Width())); + } + } + return abs_bounds; +} + void PaintLayer::ExpandRectForStackingChildren( const PaintLayer& composited_layer, PhysicalRect& result, - PaintLayer::CalculateBoundsOptions options) const { + unsigned options) const { // If we're locked, th en the subtree does not contribute painted output. // Furthermore, we might not have up-to-date sizing and position information // in the subtree, so skip recursing into the subtree. @@ -2708,31 +2788,32 @@ // for this Layer. For example, the bounds of squashed Layers // will be included in the computation of the appropriate squashing // GraphicsLayer. - if (options != PaintLayer::CalculateBoundsOptions:: - kIncludeTransformsAndCompositedChildLayers && - child_layer->GetCompositingState() != kNotComposited) - continue; - result.Unite(child_layer->BoundingBoxForCompositingInternal( - composited_layer, this, options)); + if ((options & kIncludeCompositedChildLayers) || + child_layer->GetCompositingState() == kNotComposited) { + result.Unite(child_layer->BoundingBoxForCompositingInternal( + composited_layer, this, options)); + } } } PhysicalRect PaintLayer::BoundingBoxForCompositing() const { return BoundingBoxForCompositingInternal( - *this, nullptr, kIncludeClipsAndMaybeIncludeTransformForAncestorLayer); + *this, nullptr, kIncludeClips | kMaybeIncludeTransformForAncestorLayer); } bool PaintLayer::ShouldApplyTransformToBoundingBox( const PaintLayer& composited_layer, - CalculateBoundsOptions options) const { + unsigned options) const { + DCHECK(!(options & kIncludeTransforms) || + !(options & kMaybeIncludeTransformForAncestorLayer)); if (!Transform()) return false; - if (options == kIncludeTransformsAndCompositedChildLayers) + if (options & kIncludeTransforms) return true; if (PaintsWithTransform(kGlobalPaintNormalPhase)) { if (this != &composited_layer) return true; - if (options == kIncludeClipsAndMaybeIncludeTransformForAncestorLayer) + if (options & kMaybeIncludeTransformForAncestorLayer) return true; } return false; @@ -2741,7 +2822,7 @@ PhysicalRect PaintLayer::BoundingBoxForCompositingInternal( const PaintLayer& composited_layer, const PaintLayer* stacking_parent, - CalculateBoundsOptions options) const { + unsigned options) const { DCHECK_GE(GetLayoutObject().GetDocument().Lifecycle().GetState(), DocumentLifecycle::kInPrePaint); if (!IsSelfPaintingLayer()) @@ -2771,14 +2852,11 @@ return PhysicalRect(); PhysicalRect result; - if (options == kIncludeClipsAndMaybeIncludeTransformForAncestorLayer) { + if (options & kIncludeClips) { // If there is a clip applied by an ancestor to this PaintLayer but below or // equal to |ancestorLayer|, apply that clip. This optimizes the size // of the composited layer to exclude clipped-out regions of descendants. - result = Clipper((GetLayoutObject().GetDocument().Lifecycle().GetState() == - DocumentLifecycle::kInCompositingAssignmentsUpdate) - ? GeometryMapperOption::kUseGeometryMapper - : GeometryMapperOption::kUseGeometryMapper) + result = Clipper(GeometryMapperOption::kUseGeometryMapper) .LocalClipRect(composited_layer); result.Intersect(LocalBoundingBox());
diff --git a/third_party/blink/renderer/core/paint/paint_layer.h b/third_party/blink/renderer/core/paint/paint_layer.h index 2ef4b36..a415f1d8 100644 --- a/third_party/blink/renderer/core/paint/paint_layer.h +++ b/third_party/blink/renderer/core/paint/paint_layer.h
@@ -461,12 +461,17 @@ const PhysicalRect& damage_rect, const PhysicalOffset& offset_from_root) const; - // MaybeIncludeTransformForAncestorLayer means that a transform on - // |ancestorLayer| may be applied to the bounding box, in particular if - // paintsWithTransform() is true. enum CalculateBoundsOptions { - kIncludeClipsAndMaybeIncludeTransformForAncestorLayer, - kIncludeTransformsAndCompositedChildLayers, + // Include clips between this layer and its ancestor layer (inclusive). + kIncludeClips = 0x1, + // Include transforms, irrespective of if they are applied via composition + // or painting. + kIncludeTransforms = 0x2, + // Include child layers (recursive) whether composited or not. + kIncludeCompositedChildLayers = 0x4, + // Include transform for the ancestor layer (|composited_layer| in the + // initial call) if |PaintsWithTransform| + kMaybeIncludeTransformForAncestorLayer = 0x8 }; // Bounding box relative to some ancestor layer. Pass offsetFromRoot if known. @@ -475,10 +480,11 @@ PhysicalRect PhysicalBoundingBox(const PaintLayer* ancestor_layer) const; PhysicalRect FragmentsBoundingBox(const PaintLayer* ancestor_layer) const; - PhysicalRect BoundingBoxForCompositingOverlapTest() const; + PhysicalRect LocalBoundingBoxForCompositingOverlapTest() const; + IntRect ExpandedBoundingBoxForCompositingOverlapTest( + bool use_clipped_bounding_rect) const; PhysicalRect BoundingBoxForCompositing() const; - // Static position is set in parent's coordinate space. LayoutUnit StaticInlinePosition() const { return static_inline_position_; } LayoutUnit StaticBlockPosition() const { return static_block_position_; } @@ -804,7 +810,14 @@ const PaintLayer* nearest_contained_layout_layer = nullptr; // These two boxes do not include any applicable scroll offset of the - // root PaintLayer. + // root PaintLayer. Note that 'absolute' here is potentially misleading as + // the actual coordinate system depends on if this layer is affected by the + // viewport's scroll offset or not. For content that is not affected by the + // viewport scroll offsets, this ends up being a rect in viewport coords. + // For content that is affected by the viewport's scroll offset this + // coordinate system is in absolute coords. + // Note: This stores LocalBoundingBoxForCompositingOverlapTest and not the + // expanded bounds (ExpandedBoundingBoxForCompositingOverlapTest). IntRect clipped_absolute_bounding_box; IntRect unclipped_absolute_bounding_box; @@ -1274,18 +1287,20 @@ needs_paint_phase_float_ |= layer.needs_paint_phase_float_; } + bool IsTopMostNotAffectedByScrollOf(const PaintLayer* ancestor) const; + void ExpandRectForStackingChildren(const PaintLayer& composited_layer, PhysicalRect& result, - PaintLayer::CalculateBoundsOptions) const; + unsigned options) const; // The return value is in the space of |stackingParent|, if non-null, or // |this| otherwise. PhysicalRect BoundingBoxForCompositingInternal( const PaintLayer& composited_layer, const PaintLayer* stacking_parent, - CalculateBoundsOptions) const; + unsigned options) const; bool ShouldApplyTransformToBoundingBox(const PaintLayer& composited_layer, - CalculateBoundsOptions) const; + unsigned options) const; AncestorDependentCompositingInputs& EnsureAncestorDependentCompositingInputs() const {
diff --git a/third_party/blink/renderer/core/paint/paint_layer_test.cc b/third_party/blink/renderer/core/paint/paint_layer_test.cc index 4b8d451..49abbec 100644 --- a/third_party/blink/renderer/core/paint/paint_layer_test.cc +++ b/third_party/blink/renderer/core/paint/paint_layer_test.cc
@@ -2594,211 +2594,6 @@ EXPECT_NE(nullptr, paint_layer); } -TEST_P(PaintLayerTest, FixedUsesExpandedBoundingBoxForOverlap) { - SetBodyInnerHTML(R"HTML( - <style> - * { - margin: 0; - } - body { - height: 610px; - width: 820px; - } - #fixed { - height: 10px; - left: 50px; - position: fixed; - top: 50px; - width: 10px; - } - </style> - <div id=fixed></div> - )HTML"); - - PaintLayer* fixed = GetPaintLayerByElementId("fixed"); - EXPECT_EQ(fixed->BoundingBoxForCompositingOverlapTest(), - PhysicalRect(0, 0, 30, 20)); - - // Modify the viewport scroll offset and ensure that the bounding box is still - // adjusted by the new amount the viewport can scroll in any direction. - GetDocument().View()->LayoutViewport()->SetScrollOffset( - ScrollOffset(10, 10), mojom::blink::ScrollType::kProgrammatic); - UpdateAllLifecyclePhasesForTest(); - - EXPECT_EQ(fixed->BoundingBoxForCompositingOverlapTest(), - PhysicalRect(-10, -10, 30, 20)); -} - -TEST_P(PaintLayerTest, FixedInScrollerUsesExpandedBoundingBoxForOverlap) { - SetBodyInnerHTML(R"HTML( - <style> - * { - margin: 0; - } - body { - height: 610px; - width: 820px; - } - #scroller { - height: 100px; - left: 100px; - overflow: scroll; - position: absolute; - top: 100px; - width: 100px; - } - #spacer { - height: 500px; - } - #fixed { - height: 10px; - left: 50px; - position: fixed; - top: 50px; - width: 10px; - } - </style> - <div id=scroller> - <div id=fixed></div> - <div id=spacer></div> - </div> - )HTML"); - - PaintLayer* fixed = GetPaintLayerByElementId("fixed"); - EXPECT_EQ(fixed->BoundingBoxForCompositingOverlapTest(), - PhysicalRect(0, 0, 30, 20)); - - // Modify the inner scroll offset and ensure that the bounding box is still - // the same. - PaintLayerScrollableArea* scrollable_area = - GetPaintLayerByElementId("scroller")->GetScrollableArea(); - scrollable_area->ScrollToAbsolutePosition(FloatPoint(10, 10)); - UpdateAllLifecyclePhasesForTest(); - - EXPECT_EQ(fixed->BoundingBoxForCompositingOverlapTest(), - PhysicalRect(0, 0, 30, 20)); - - // Modify the viewport scroll offset and ensure that the bounding box is still - // adjusted by the newamount the viewport can scroll in any direction. - GetDocument().View()->LayoutViewport()->SetScrollOffset( - ScrollOffset(10, 10), mojom::blink::ScrollType::kProgrammatic); - UpdateAllLifecyclePhasesForTest(); - - EXPECT_EQ(fixed->BoundingBoxForCompositingOverlapTest(), - PhysicalRect(-10, -10, 30, 20)); -} - -TEST_P(PaintLayerTest, FixedUnderTransformDoesNotExpandBoundingBoxForOverlap) { - SetBodyInnerHTML(R"HTML( - <style> - .anim { - animation: pulse 5s infinite; - } - @keyframes pulse { - 0% { opacity: 0.1; } - 100% { opacity: 0.9; } - } - .xform { - height: 100px; - left: 100px; - position: absolute; - top: 100px; - transform: rotate(20deg); - width: 100px; - } - .fixed { - height: 50px; - left: 25px; - position: fixed; - top: 25px; - width: 50px; - } - .spacer { - height: 2000px; - } - </style> - <div id=fixed-cb class=xform> - <div id=fixed class='fixed anim'></div> - </div> - <div class=spacer></div> - )HTML"); - - // The animation is to cause the fixed to be composited. However, even with - // fixed composited, it shouldn't have expanded bounds because its containing - // block isn't the viewport. - PaintLayer* fixed = GetPaintLayerByElementId("fixed"); - EXPECT_EQ(fixed->BoundingBoxForCompositingOverlapTest(), - PhysicalRect(0, 0, 50, 50)); -} - -TEST_P(PaintLayerTest, NestedFixedUsesExpandedBoundingBoxForOverlap) { - SetBodyInnerHTML(R"HTML( - <style> - * { - margin: 0; - } - body { - height: 610px; - width: 820px; - } - #iframe1 { - height: 100px; - left: 50px; - position: fixed; - top: 50px; - width: 100px; - } - </style> - <iframe id=iframe1></iframe> - )HTML"); - SetChildFrameHTML(R"HTML( - <style> - * { - margin: 0; - } - body { - height: 500px; - width: 500px; - } - #fixed { - height: 10px; - left: 50px; - position: fixed; - top: 50px; - width: 10px; - } - </style> - <div id=fixed></div> - )HTML"); - UpdateAllLifecyclePhasesForTest(); - - PaintLayer* fixed = - To<LayoutBoxModelObject>( - ChildDocument().getElementById("fixed")->GetLayoutObject()) - ->Layer(); - EXPECT_EQ(fixed->BoundingBoxForCompositingOverlapTest(), - PhysicalRect(0, 0, 410, 410)); - - // Modify the top-most viewport's scroll offset and ensure that the bounding - // box is still the same. This shows that we're not considering the wrong - // viewport's scroll offset when computing the bounding box. - GetDocument().View()->LayoutViewport()->SetScrollOffset( - ScrollOffset(10, 10), mojom::blink::ScrollType::kProgrammatic); - UpdateAllLifecyclePhasesForTest(); - - EXPECT_EQ(fixed->BoundingBoxForCompositingOverlapTest(), - PhysicalRect(0, 0, 410, 410)); - - // Now modify the iframe's scroll offset. This one should affect the fixed's - // bounding box. - ChildDocument().View()->LayoutViewport()->SetScrollOffset( - ScrollOffset(10, 10), mojom::blink::ScrollType::kProgrammatic); - UpdateAllLifecyclePhasesForTest(); - - EXPECT_EQ(fixed->BoundingBoxForCompositingOverlapTest(), - PhysicalRect(-10, -10, 410, 410)); -} - TEST_P(PaintLayerTest, DirectCompositingReasonsCrossingFrameBoundaries) { if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) return; @@ -2877,7 +2672,6 @@ } TEST_P(PaintLayerTest, GlobalRootScrollerHitTest) { - USE_NON_OVERLAY_SCROLLBARS(); SetBodyInnerHTML(R"HTML( <style> :root { @@ -2885,7 +2679,6 @@ background:blue; transform: rotate(30deg); transform-style: preserve-3d; - overflow-x: hidden; } #perspective { perspective:100px; @@ -2898,7 +2691,6 @@ <div id="perspective"> <div id="threedee"></div> </div> - <div style="height:1000px"></div> )HTML"); GetDocument().GetPage()->SetPageScaleFactor(2); UpdateAllLifecyclePhasesForTest(); @@ -2910,13 +2702,12 @@ EXPECT_EQ(result.InnerNode(), GetDocument().documentElement()); EXPECT_EQ(result.GetScrollbar(), nullptr); - const HitTestLocation location_scrollbar(IntPoint(790, 300)); - HitTestResult result_scrollbar; - GetLayoutView().HitTestNoLifecycleUpdate(location_scrollbar, - result_scrollbar); - EXPECT_EQ(result_scrollbar.InnerNode(), GetDocument().documentElement()); - EXPECT_NE(result_scrollbar.GetScrollbar(), nullptr); - EXPECT_EQ(result_scrollbar.LocalPoint(), location_scrollbar.Point()); + if (GetDocument().GetPage()->GetScrollbarTheme().AllowsHitTest()) { + const HitTestLocation location_scrollbar(IntPoint(790, 300)); + HitTestResult result_scrollbar; + EXPECT_EQ(result_scrollbar.InnerNode(), &GetDocument()); + EXPECT_NE(result_scrollbar.GetScrollbar(), nullptr); + } } TEST_P(PaintLayerTest, HasNonEmptyChildLayoutObjectsZeroSizeOverflowVisible) { @@ -2932,4 +2723,800 @@ EXPECT_TRUE(layer->HasNonEmptyChildLayoutObjects()); } +enum { kCompositingOptimizations = 1 << 0 }; + +// TODO(chrishtr): Remove this test configuration and keep the appropriate +// variants of the tests when CompositingOptimizations ships or is removed. +class PaintLayerOverlapTestConfigurations + : public testing::WithParamInterface<unsigned>, + private ScopedCompositingOptimizationsForTest { + public: + PaintLayerOverlapTestConfigurations() + : ScopedCompositingOptimizationsForTest(GetParam() & + kCompositingOptimizations) {} + ~PaintLayerOverlapTestConfigurations() override { + // Must destruct all objects before toggling back feature flags. + WebHeap::CollectAllGarbageForTesting(); + } +}; + +class PaintLayerOverlapTest : public PaintLayerOverlapTestConfigurations, + public RenderingTest { + public: + PaintLayerOverlapTest() + : RenderingTest(MakeGarbageCollected<SingleChildLocalFrameClient>()) {} + + void SetUp() override { + EnableCompositing(); + RenderingTest::SetUp(); + } +}; + +INSTANTIATE_TEST_SUITE_P(All, + PaintLayerOverlapTest, + ::testing::Values(0, kCompositingOptimizations)); + +TEST_P(PaintLayerOverlapTest, FixedUsesExpandedBoundingBoxForOverlap) { + SetBodyInnerHTML(R"HTML( + <style> + * { + margin: 0; + } + body { + height: 610px; + width: 820px; + } + #fixed { + height: 10px; + left: 50px; + position: fixed; + top: 50px; + width: 10px; + } + </style> + <div id=fixed></div> + )HTML"); + + PaintLayer* fixed = GetPaintLayerByElementId("fixed"); + EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(50, 50, 30, 20)); + EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 10, 10)); + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10)); + + // Modify the viewport scroll offset and ensure that the bounding box is still + // adjusted by the new amount the viewport can scroll in any direction. + GetDocument().View()->LayoutViewport()->SetScrollOffset( + ScrollOffset(10, 10), mojom::blink::ScrollType::kProgrammatic); + UpdateAllLifecyclePhasesForTest(); + + EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(50, 50, 30, 20)); + EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 10, 10)); + if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10)); + } else { + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(60, 60, 10, 10)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(60, 60, 10, 10)); + } +} + +TEST_P(PaintLayerOverlapTest, + FixedInScrollerUsesExpandedBoundingBoxForOverlap) { + SetBodyInnerHTML(R"HTML( + <style> + * { + margin: 0; + } + body { + height: 610px; + width: 820px; + } + #scroller { + height: 100px; + left: 100px; + overflow: scroll; + position: absolute; + top: 100px; + width: 100px; + } + #spacer { + height: 500px; + } + #fixed { + height: 10px; + left: 50px; + position: fixed; + top: 50px; + width: 10px; + } + </style> + <div id=scroller> + <div id=fixed></div> + <div id=spacer></div> + </div> + )HTML"); + + PaintLayer* fixed = GetPaintLayerByElementId("fixed"); + EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(50, 50, 30, 20)); + EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 10, 10)); + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10)); + + // Modify the inner scroll offset and ensure that the bounding box is still + // the same. + PaintLayerScrollableArea* scrollable_area = + GetPaintLayerByElementId("scroller")->GetScrollableArea(); + scrollable_area->ScrollToAbsolutePosition(FloatPoint(10, 10)); + UpdateAllLifecyclePhasesForTest(); + + EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(50, 50, 30, 20)); + EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 10, 10)); + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10)); + + // Modify the viewport scroll offset and ensure that the bounding box is still + // adjusted by the newamount the viewport can scroll in any direction. + GetDocument().View()->LayoutViewport()->SetScrollOffset( + ScrollOffset(10, 10), mojom::blink::ScrollType::kProgrammatic); + UpdateAllLifecyclePhasesForTest(); + + EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(50, 50, 30, 20)); + EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 10, 10)); + if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10)); + } else { + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(60, 60, 10, 10)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(60, 60, 10, 10)); + } +} + +TEST_P(PaintLayerOverlapTest, + FixedUnderTransformDoesNotExpandBoundingBoxForOverlap) { + SetBodyInnerHTML(R"HTML( + <style> + .anim { + animation: pulse 5s infinite; + } + @keyframes pulse { + 0% { opacity: 0.1; } + 100% { opacity: 0.9; } + } + .xform { + height: 100px; + left: 100px; + position: absolute; + top: 100px; + transform: rotate(20deg); + width: 100px; + } + .fixed { + height: 50px; + left: 25px; + position: fixed; + top: 25px; + width: 50px; + } + .spacer { + height: 2000px; + } + </style> + <div id=fixed-cb class=xform> + <div id=fixed class='fixed anim'></div> + </div> + <div class=spacer></div> + )HTML"); + + // The animation is to cause the fixed to be composited. However, even with + // fixed composited, it shouldn't have expanded bounds because its containing + // block isn't the viewport. + PaintLayer* fixed = GetPaintLayerByElementId("fixed"); + EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(117, 117, 66, 66)); + EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 50, 50)); + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(117, 117, 66, 66)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(117, 117, 66, 66)); +} + +TEST_P(PaintLayerOverlapTest, NestedFixedUsesExpandedBoundingBoxForOverlap) { + SetBodyInnerHTML(R"HTML( + <style> + * { + margin: 0; + } + body { + height: 610px; + width: 820px; + } + #iframe1 { + height: 100px; + left: 50px; + position: fixed; + top: 50px; + width: 100px; + } + </style> + <iframe id=iframe1></iframe> + )HTML"); + SetChildFrameHTML(R"HTML( + <style> + * { + margin: 0; + } + body { + height: 500px; + width: 500px; + } + #fixed { + height: 10px; + left: 50px; + position: fixed; + top: 50px; + width: 10px; + } + </style> + <div id=fixed></div> + )HTML"); + UpdateAllLifecyclePhasesForTest(); + + PaintLayer* fixed = + To<LayoutBoxModelObject>( + ChildDocument().getElementById("fixed")->GetLayoutObject()) + ->Layer(); + EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(50, 50, 410, 410)); + EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 10, 10)); + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10)); + + // Modify the top-most viewport's scroll offset and ensure that the bounding + // box is still the same. This shows that we're not considering the wrong + // viewport's scroll offset when computing the bounding box. + GetDocument().View()->LayoutViewport()->SetScrollOffset( + ScrollOffset(10, 10), mojom::blink::ScrollType::kProgrammatic); + UpdateAllLifecyclePhasesForTest(); + + EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(50, 50, 410, 410)); + EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 10, 10)); + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10)); + + // Now modify the iframe's scroll offset. This one should affect the fixed's + // bounding box. + ChildDocument().View()->LayoutViewport()->SetScrollOffset( + ScrollOffset(10, 10), mojom::blink::ScrollType::kProgrammatic); + UpdateAllLifecyclePhasesForTest(); + + EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(50, 50, 410, 410)); + EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 10, 10)); + if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(50, 50, 10, 10)); + } else { + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(60, 60, 10, 10)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(60, 60, 10, 10)); + } +} + +TEST_P(PaintLayerOverlapTest, FixedWithExpandedBoundsForChild) { + SetBodyInnerHTML(R"HTML( + <style> + * { + margin: 0; + } + body { + height: 1000px; + } + #fixed { + height: 50px; + left: 25px; + position: fixed; + top: 25px; + width: 50px; + } + #abs { + height: 25px; + left: 50px; + position: absolute; + top: 200px; + width: 25px; + will-change: transform; + } + </style> + <div id=fixed> + <div id=abs></div> + </div> + )HTML"); + UpdateAllLifecyclePhasesForTest(); + + // The fixed-pos layer should use bounds that have been expanded to include + // the absolutely positioned child. Without this expansion, overlap testing + // can miss overlap from that child leading to incorrect composition order. + PaintLayer* fixed = GetPaintLayerByElementId("fixed"); + EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(25, 25, 75, 625)); + EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 50, 50)); + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50)); + + // Verify that the abs bounds is not expanded even though it is a child of a + // fixed-pos layer. Expanding the abs bounds would mean that it could + // unnecessarily detect overlap with siblings that it doesn't ever actually + // overlap with. + PaintLayer* abs = GetPaintLayerByElementId("abs"); + EXPECT_EQ(abs->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(75, 225, 25, 25)); + EXPECT_EQ(abs->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 25, 25)); + EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25)); + EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25)); + + // Modify the scroll offset and ensure that the bounding box is still the + // same. Note that if we get different expanded bounding boxes for overlap + // testing with different scroll offsets then it implies that scroll offset is + // a part of that calculation and we may get incorrect results as scroll + // offsets changes and partial updates happen. + GetDocument().View()->LayoutViewport()->SetScrollOffset( + ScrollOffset(0, 400), mojom::blink::ScrollType::kProgrammatic); + UpdateAllLifecyclePhasesForTest(); + + EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(25, 25, 75, 625)); + EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 50, 50)); + if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50)); + } else { + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50)); + } + + EXPECT_EQ(abs->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(75, 625, 25, 25)); + EXPECT_EQ(abs->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 25, 25)); + if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { + EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25)); + EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25)); + } else { + EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 625, 25, 25)); + EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 625, 25, 25)); + } +} + +TEST_P(PaintLayerOverlapTest, FixedWithClippedExpandedBoundsForChild) { + SetBodyInnerHTML(R"HTML( + <style> + * { + margin: 0; + } + body { + height: 1000px; + } + #fixed { + height: 50px; + left: 25px; + position: fixed; + top: 25px; + width: 50px; + overflow: hidden; + } + #abs { + height: 25px; + left: 50px; + position: absolute; + top: 200px; + width: 25px; + will-change: transform; + } + </style> + <div id=fixed> + <div id=abs></div> + </div> + )HTML"); + UpdateAllLifecyclePhasesForTest(); + + // The fixed-pos layer should use bounds that have been expanded to include + // the absolutely positioned child. However, the fixed-pos ancestor also has + // clipping which will limit the expansion. + PaintLayer* fixed = GetPaintLayerByElementId("fixed"); + EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(25, 25, 50, 450)); + EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 50, 50)); + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50)); + + // Verify that the abs bounds is not expanded even though it is a child of a + // fixed-pos layer. Expanding the abs bounds would mean that it could + // unnecessarily detect overlap with siblings that it doesn't ever actually + // overlap with. Note that the clipped bounds is an empty rect because of the + // clipping from the ancestor. + PaintLayer* abs = GetPaintLayerByElementId("abs"); + EXPECT_EQ(abs->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(75, 225, 25, 25)); + EXPECT_EQ(abs->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 25, 25)); + EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25)); + EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(0, 0, 0, 0)); + + // Modify the scroll offset and ensure that the bounding box is still the + // same. Note that if we get different expanded bounding boxes for overlap + // testing with different scroll offsets then it implies that scroll offset is + // a part of that calculation and we may get incorrect results as scroll + // offsets changes and partial updates happen. + GetDocument().View()->LayoutViewport()->SetScrollOffset( + ScrollOffset(0, 400), mojom::blink::ScrollType::kProgrammatic); + UpdateAllLifecyclePhasesForTest(); + + EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(25, 25, 50, 450)); + EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 50, 50)); + if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50)); + } else { + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50)); + } + + EXPECT_EQ(abs->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(75, 625, 25, 25)); + EXPECT_EQ(abs->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 25, 25)); + if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { + EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25)); + EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(0, 0, 0, 0)); + } else { + EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 625, 25, 25)); + EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(0, 0, 0, 0)); + } +} + +TEST_P(PaintLayerOverlapTest, FixedWithExpandedBoundsForGrandChild) { + SetBodyInnerHTML(R"HTML( + <style> + * { + margin: 0; + } + body { + height: 1000px; + } + #fixed { + height: 50px; + left: 25px; + position: fixed; + top: 25px; + width: 50px; + } + #abs { + height: 25px; + left: 50px; + position: absolute; + top: 200px; + width: 25px; + } + #abs2 { + height: 25px; + left: 50px; + position: absolute; + top: 100px; + width: 25px; + } + </style> + <div id=fixed> + <div id=abs> + <div id=abs2></div> + </div> + </div> + )HTML"); + UpdateAllLifecyclePhasesForTest(); + + // The fixed-pos layer should use bounds that have been expanded to include + // the absolutely positioned grandchild. + PaintLayer* fixed = GetPaintLayerByElementId("fixed"); + EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(25, 25, 125, 725)); + EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 50, 50)); + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50)); + + // Verify that the abs bounds is not expanded even though it is a child of a + // fixed-pos layer. Additionally, it shouldn't include its child as only + // fixed-pos expands to include descendants. + PaintLayer* abs = GetPaintLayerByElementId("abs"); + EXPECT_EQ(abs->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(75, 225, 25, 25)); + EXPECT_EQ(abs->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 25, 25)); + EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25)); + EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25)); + + PaintLayer* abs2 = GetPaintLayerByElementId("abs2"); + EXPECT_EQ(abs2->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(125, 325, 25, 25)); + EXPECT_EQ(abs2->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 25, 25)); + EXPECT_EQ(abs2->UnclippedAbsoluteBoundingBox(), IntRect(125, 325, 25, 25)); + EXPECT_EQ(abs2->ClippedAbsoluteBoundingBox(), IntRect(125, 325, 25, 25)); + + // Modify the scroll offset and ensure that the bounding box is still the + // same. Note that if we get different expanded bounding boxes for overlap + // testing with different scroll offsets then it implies that scroll offset is + // a part of that calculation and we may get incorrect results as scroll + // offsets changes and partial updates happen. + GetDocument().View()->LayoutViewport()->SetScrollOffset( + ScrollOffset(0, 400), mojom::blink::ScrollType::kProgrammatic); + UpdateAllLifecyclePhasesForTest(); + + EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(25, 25, 125, 725)); + EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 50, 50)); + if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50)); + } else { + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50)); + } + + EXPECT_EQ(abs->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(75, 625, 25, 25)); + EXPECT_EQ(abs->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 25, 25)); + if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { + EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25)); + EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25)); + } else { + EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 625, 25, 25)); + EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 625, 25, 25)); + } + + EXPECT_EQ(abs2->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(125, 725, 25, 25)); + EXPECT_EQ(abs2->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 25, 25)); + if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { + EXPECT_EQ(abs2->UnclippedAbsoluteBoundingBox(), IntRect(125, 325, 25, 25)); + EXPECT_EQ(abs2->ClippedAbsoluteBoundingBox(), IntRect(125, 325, 25, 25)); + } else { + EXPECT_EQ(abs2->UnclippedAbsoluteBoundingBox(), IntRect(125, 725, 25, 25)); + EXPECT_EQ(abs2->ClippedAbsoluteBoundingBox(), IntRect(125, 725, 25, 25)); + } + + // Add will-change to the middle child to ensure the bounds are still the + // same. This helps confirm that the computation of the bounds is agnostic to + // if descendants are composited or not. + GetDocument().getElementById("abs")->setAttribute(html_names::kStyleAttr, + "will-change: transform"); + UpdateAllLifecyclePhasesForTest(); + + EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(25, 25, 125, 725)); + EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 50, 50)); + if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50)); + } else { + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50)); + } + + EXPECT_EQ(abs->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(75, 625, 25, 25)); + EXPECT_EQ(abs->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 25, 25)); + if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { + EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25)); + EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25)); + } else { + EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 625, 25, 25)); + EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 625, 25, 25)); + } + + EXPECT_EQ(abs2->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(125, 725, 25, 25)); + EXPECT_EQ(abs2->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 25, 25)); + if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { + EXPECT_EQ(abs2->UnclippedAbsoluteBoundingBox(), IntRect(125, 325, 25, 25)); + EXPECT_EQ(abs2->ClippedAbsoluteBoundingBox(), IntRect(125, 325, 25, 25)); + } else { + EXPECT_EQ(abs2->UnclippedAbsoluteBoundingBox(), IntRect(125, 725, 25, 25)); + EXPECT_EQ(abs2->ClippedAbsoluteBoundingBox(), IntRect(125, 725, 25, 25)); + } + + // Add will-change to the grandchild and ensure the bounds are still the same. + GetDocument().getElementById("abs2")->setAttribute(html_names::kStyleAttr, + "will-change: transform"); + UpdateAllLifecyclePhasesForTest(); + + EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(25, 25, 125, 725)); + EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 50, 50)); + if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50)); + } else { + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50)); + } + + EXPECT_EQ(abs->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(75, 625, 25, 25)); + EXPECT_EQ(abs->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 25, 25)); + if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { + EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25)); + EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25)); + } else { + EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 625, 25, 25)); + EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 625, 25, 25)); + } + + EXPECT_EQ(abs2->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(125, 725, 25, 25)); + EXPECT_EQ(abs2->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 25, 25)); + if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { + EXPECT_EQ(abs2->UnclippedAbsoluteBoundingBox(), IntRect(125, 325, 25, 25)); + EXPECT_EQ(abs2->ClippedAbsoluteBoundingBox(), IntRect(125, 325, 25, 25)); + } else { + EXPECT_EQ(abs2->UnclippedAbsoluteBoundingBox(), IntRect(125, 725, 25, 25)); + EXPECT_EQ(abs2->ClippedAbsoluteBoundingBox(), IntRect(125, 725, 25, 25)); + } + + // Remove will-change from the middle child and ensure the bounds are still + // the same. + GetDocument().getElementById("abs")->setAttribute(html_names::kStyleAttr, ""); + UpdateAllLifecyclePhasesForTest(); + + EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(25, 25, 125, 725)); + EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 50, 50)); + if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50)); + } else { + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50)); + } + + EXPECT_EQ(abs->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(75, 625, 25, 25)); + EXPECT_EQ(abs->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 25, 25)); + if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { + EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25)); + EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 225, 25, 25)); + } else { + EXPECT_EQ(abs->UnclippedAbsoluteBoundingBox(), IntRect(75, 625, 25, 25)); + EXPECT_EQ(abs->ClippedAbsoluteBoundingBox(), IntRect(75, 625, 25, 25)); + } + + EXPECT_EQ(abs2->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(125, 725, 25, 25)); + EXPECT_EQ(abs2->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 25, 25)); + if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { + EXPECT_EQ(abs2->UnclippedAbsoluteBoundingBox(), IntRect(125, 325, 25, 25)); + EXPECT_EQ(abs2->ClippedAbsoluteBoundingBox(), IntRect(125, 325, 25, 25)); + } else { + EXPECT_EQ(abs2->UnclippedAbsoluteBoundingBox(), IntRect(125, 725, 25, 25)); + EXPECT_EQ(abs2->ClippedAbsoluteBoundingBox(), IntRect(125, 725, 25, 25)); + } +} + +TEST_P(PaintLayerOverlapTest, FixedWithExpandedBoundsForFixedChild) { + SetBodyInnerHTML(R"HTML( + <style> + * { + margin: 0; + } + body { + height: 1000px; + } + #fixed { + height: 50px; + left: 25px; + position: fixed; + top: 25px; + width: 50px; + } + #nestedFixed { + height: 25px; + left: 50px; + position: fixed; + top: 100px; + width: 25px; + } + </style> + <div id=fixed> + <div id=nestedFixed></div> + </div> + )HTML"); + UpdateAllLifecyclePhasesForTest(); + + // The fixed-pos layer should use bounds that have been expanded to include + // the absolutely positioned child. Without this expansion, overlap testing + // can miss overlap from that child leading to incorrect composition order. + PaintLayer* fixed = GetPaintLayerByElementId("fixed"); + EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(25, 25, 50, 500)); + EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 50, 50)); + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50)); + + // Note that the nested fixed should not expand its bounds as it doesn't move + // relative to its siblings, fixed-pos or not. + PaintLayer* nestedFixed = GetPaintLayerByElementId("nestedFixed"); + EXPECT_EQ(nestedFixed->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(50, 100, 25, 25)); + EXPECT_EQ(nestedFixed->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 25, 25)); + EXPECT_EQ(nestedFixed->UnclippedAbsoluteBoundingBox(), + IntRect(50, 100, 25, 25)); + EXPECT_EQ(nestedFixed->ClippedAbsoluteBoundingBox(), + IntRect(50, 100, 25, 25)); + + // Modify the scroll offset and ensure that the bounding box is still the + // same. + GetDocument().View()->LayoutViewport()->SetScrollOffset( + ScrollOffset(0, 400), mojom::blink::ScrollType::kProgrammatic); + UpdateAllLifecyclePhasesForTest(); + + EXPECT_EQ(fixed->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(25, 25, 50, 500)); + EXPECT_EQ(fixed->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 50, 50)); + if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 25, 50, 50)); + } else { + EXPECT_EQ(fixed->UnclippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50)); + EXPECT_EQ(fixed->ClippedAbsoluteBoundingBox(), IntRect(25, 425, 50, 50)); + } + + EXPECT_EQ(nestedFixed->ExpandedBoundingBoxForCompositingOverlapTest(false), + IntRect(50, 500, 25, 25)); + EXPECT_EQ(nestedFixed->LocalBoundingBoxForCompositingOverlapTest(), + PhysicalRect(0, 0, 25, 25)); + if (!RuntimeEnabledFeatures::CompositingOptimizationsEnabled()) { + EXPECT_EQ(nestedFixed->UnclippedAbsoluteBoundingBox(), + IntRect(50, 100, 25, 25)); + EXPECT_EQ(nestedFixed->ClippedAbsoluteBoundingBox(), + IntRect(50, 100, 25, 25)); + } else { + EXPECT_EQ(nestedFixed->UnclippedAbsoluteBoundingBox(), + IntRect(50, 500, 25, 25)); + EXPECT_EQ(nestedFixed->ClippedAbsoluteBoundingBox(), + IntRect(50, 500, 25, 25)); + } +} + } // namespace blink
diff --git a/third_party/blink/renderer/modules/wake_lock/BUILD.gn b/third_party/blink/renderer/modules/wake_lock/BUILD.gn index 5a45d1f..6fc9c322 100644 --- a/third_party/blink/renderer/modules/wake_lock/BUILD.gn +++ b/third_party/blink/renderer/modules/wake_lock/BUILD.gn
@@ -6,8 +6,6 @@ blink_modules_sources("wake_lock") { sources = [ - "navigator_wake_lock.cc", - "navigator_wake_lock.h", "wake_lock.cc", "wake_lock.h", "wake_lock_manager.cc", @@ -16,8 +14,6 @@ "wake_lock_sentinel.h", "wake_lock_type.cc", "wake_lock_type.h", - "worker_navigator_wake_lock.cc", - "worker_navigator_wake_lock.h", ] deps = [ "//mojo/public/cpp/bindings",
diff --git a/third_party/blink/renderer/modules/wake_lock/navigator_wake_lock.cc b/third_party/blink/renderer/modules/wake_lock/navigator_wake_lock.cc deleted file mode 100644 index 35c8053..0000000 --- a/third_party/blink/renderer/modules/wake_lock/navigator_wake_lock.cc +++ /dev/null
@@ -1,48 +0,0 @@ -// Copyright 2019 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 "third_party/blink/renderer/modules/wake_lock/navigator_wake_lock.h" - -#include "third_party/blink/renderer/core/frame/local_dom_window.h" -#include "third_party/blink/renderer/core/frame/navigator.h" -#include "third_party/blink/renderer/modules/wake_lock/wake_lock.h" - -namespace blink { - -NavigatorWakeLock::NavigatorWakeLock(Navigator& navigator) - : Supplement<Navigator>(navigator) {} - -WakeLock* NavigatorWakeLock::GetWakeLock() { - if (!wake_lock_) { - if (auto* window = GetSupplementable()->DomWindow()) - wake_lock_ = MakeGarbageCollected<WakeLock>(*window); - } - return wake_lock_; -} - -// static -const char NavigatorWakeLock::kSupplementName[] = "NavigatorWakeLock"; - -// static -NavigatorWakeLock& NavigatorWakeLock::From(Navigator& navigator) { - NavigatorWakeLock* supplement = - Supplement<Navigator>::From<NavigatorWakeLock>(navigator); - if (!supplement) { - supplement = MakeGarbageCollected<NavigatorWakeLock>(navigator); - ProvideTo(navigator, supplement); - } - return *supplement; -} - -// static -WakeLock* NavigatorWakeLock::wakeLock(Navigator& navigator) { - return NavigatorWakeLock::From(navigator).GetWakeLock(); -} - -void NavigatorWakeLock::Trace(Visitor* visitor) const { - visitor->Trace(wake_lock_); - Supplement<Navigator>::Trace(visitor); -} - -} // namespace blink
diff --git a/third_party/blink/renderer/modules/wake_lock/navigator_wake_lock.h b/third_party/blink/renderer/modules/wake_lock/navigator_wake_lock.h deleted file mode 100644 index 6a5df68..0000000 --- a/third_party/blink/renderer/modules/wake_lock/navigator_wake_lock.h +++ /dev/null
@@ -1,37 +0,0 @@ -// Copyright 2019 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 THIRD_PARTY_BLINK_RENDERER_MODULES_WAKE_LOCK_NAVIGATOR_WAKE_LOCK_H_ -#define THIRD_PARTY_BLINK_RENDERER_MODULES_WAKE_LOCK_NAVIGATOR_WAKE_LOCK_H_ - -#include "third_party/blink/renderer/platform/heap/handle.h" -#include "third_party/blink/renderer/platform/supplementable.h" - -namespace blink { - -class Navigator; -class WakeLock; - -class NavigatorWakeLock final : public GarbageCollected<NavigatorWakeLock>, - public Supplement<Navigator> { - public: - static const char kSupplementName[]; - - static NavigatorWakeLock& From(Navigator&); - - static WakeLock* wakeLock(Navigator&); - - explicit NavigatorWakeLock(Navigator&); - - void Trace(Visitor*) const override; - - private: - WakeLock* GetWakeLock(); - - Member<WakeLock> wake_lock_; -}; - -} // namespace blink - -#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_WAKE_LOCK_NAVIGATOR_WAKE_LOCK_H_
diff --git a/third_party/blink/renderer/modules/wake_lock/navigator_wake_lock.idl b/third_party/blink/renderer/modules/wake_lock/navigator_wake_lock.idl index 48ecc6d..55d3b0f 100644 --- a/third_party/blink/renderer/modules/wake_lock/navigator_wake_lock.idl +++ b/third_party/blink/renderer/modules/wake_lock/navigator_wake_lock.idl
@@ -5,7 +5,7 @@ // https://w3c.github.io/screen-wake-lock/#extensions-to-the-navigator-interface [ - ImplementedAs=NavigatorWakeLock, + ImplementedAs=WakeLock, RuntimeEnabled=WakeLock, SecureContext ] partial interface Navigator {
diff --git a/third_party/blink/renderer/modules/wake_lock/wake_lock.cc b/third_party/blink/renderer/modules/wake_lock/wake_lock.cc index 8b3983f1..942e41d 100644 --- a/third_party/blink/renderer/modules/wake_lock/wake_lock.cc +++ b/third_party/blink/renderer/modules/wake_lock/wake_lock.cc
@@ -9,6 +9,7 @@ #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/dom_exception.h" #include "third_party/blink/renderer/core/execution_context/execution_context.h" +#include "third_party/blink/renderer/core/execution_context/navigator_base.h" #include "third_party/blink/renderer/core/frame/local_dom_window.h" #include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/frame/web_feature.h" @@ -26,23 +27,39 @@ using mojom::blink::PermissionService; using mojom::blink::PermissionStatus; -WakeLock::WakeLock(LocalDOMWindow& window) - : ExecutionContextLifecycleObserver(&window), - PageVisibilityObserver(window.GetFrame()->GetPage()), - permission_service_(&window), - managers_{ - MakeGarbageCollected<WakeLockManager>(&window, WakeLockType::kScreen), - MakeGarbageCollected<WakeLockManager>(&window, - WakeLockType::kSystem)} {} +// static +const char WakeLock::kSupplementName[] = "WakeLock"; -WakeLock::WakeLock(DedicatedWorkerGlobalScope& worker_scope) - : ExecutionContextLifecycleObserver(&worker_scope), - PageVisibilityObserver(nullptr), - permission_service_(&worker_scope), - managers_{MakeGarbageCollected<WakeLockManager>(&worker_scope, - WakeLockType::kScreen), - MakeGarbageCollected<WakeLockManager>(&worker_scope, - WakeLockType::kSystem)} {} +// static +WakeLock* WakeLock::wakeLock(NavigatorBase& navigator) { + ExecutionContext* context = navigator.GetExecutionContext(); + if (!context || + (!context->IsWindow() && !context->IsDedicatedWorkerGlobalScope())) { + // TODO(https://crbug.com/839117): Remove this check once the Exposed + // attribute is fixed to only expose this property in dedicated workers. + return nullptr; + } + + WakeLock* supplement = Supplement<NavigatorBase>::From<WakeLock>(navigator); + if (!supplement && navigator.GetExecutionContext()) { + supplement = MakeGarbageCollected<WakeLock>(navigator); + ProvideTo(navigator, supplement); + } + return supplement; +} + +WakeLock::WakeLock(NavigatorBase& navigator) + : Supplement<NavigatorBase>(navigator), + ExecutionContextLifecycleObserver(navigator.GetExecutionContext()), + PageVisibilityObserver(navigator.DomWindow() + ? navigator.DomWindow()->GetFrame()->GetPage() + : nullptr), + permission_service_(navigator.GetExecutionContext()), + managers_{ + MakeGarbageCollected<WakeLockManager>(navigator.GetExecutionContext(), + WakeLockType::kScreen), + MakeGarbageCollected<WakeLockManager>(navigator.GetExecutionContext(), + WakeLockType::kSystem)} {} ScriptPromise WakeLock::request(ScriptState* script_state, const String& type, @@ -278,6 +295,7 @@ for (const WakeLockManager* manager : managers_) visitor->Trace(manager); visitor->Trace(permission_service_); + Supplement<NavigatorBase>::Trace(visitor); PageVisibilityObserver::Trace(visitor); ExecutionContextLifecycleObserver::Trace(visitor); ScriptWrappable::Trace(visitor);
diff --git a/third_party/blink/renderer/modules/wake_lock/wake_lock.h b/third_party/blink/renderer/modules/wake_lock/wake_lock.h index 0a8d27e..f405b0b 100644 --- a/third_party/blink/renderer/modules/wake_lock/wake_lock.h +++ b/third_party/blink/renderer/modules/wake_lock/wake_lock.h
@@ -18,6 +18,7 @@ #include "third_party/blink/renderer/platform/heap/handle.h" #include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h" #include "third_party/blink/renderer/platform/mojo/heap_mojo_wrapper_mode.h" +#include "third_party/blink/renderer/platform/supplementable.h" namespace WTF { @@ -28,18 +29,23 @@ namespace blink { class ExceptionState; -class LocalDOMWindow; +class NavigatorBase; class ScriptState; class WakeLockManager; class MODULES_EXPORT WakeLock final : public ScriptWrappable, + public Supplement<NavigatorBase>, public ExecutionContextLifecycleObserver, public PageVisibilityObserver { DEFINE_WRAPPERTYPEINFO(); public: - explicit WakeLock(LocalDOMWindow&); - explicit WakeLock(DedicatedWorkerGlobalScope&); + static const char kSupplementName[]; + + // Getter for navigator.wakelock + static WakeLock* wakeLock(NavigatorBase&); + + explicit WakeLock(NavigatorBase&); ScriptPromise request(ScriptState*, const WTF::String& type,
diff --git a/third_party/blink/renderer/modules/wake_lock/wake_lock_sentinel_test.cc b/third_party/blink/renderer/modules/wake_lock/wake_lock_sentinel_test.cc index daf4cc2..6275adc 100644 --- a/third_party/blink/renderer/modules/wake_lock/wake_lock_sentinel_test.cc +++ b/third_party/blink/renderer/modules/wake_lock/wake_lock_sentinel_test.cc
@@ -12,6 +12,7 @@ #include "third_party/blink/renderer/core/dom/events/native_event_listener.h" #include "third_party/blink/renderer/core/frame/local_dom_window.h" #include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/navigator.h" #include "third_party/blink/renderer/modules/event_target_modules_names.h" #include "third_party/blink/renderer/modules/wake_lock/wake_lock.h" #include "third_party/blink/renderer/modules/wake_lock/wake_lock_manager.h" @@ -114,7 +115,7 @@ MakeGarbageCollected<ScriptPromiseResolver>(context.GetScriptState()); ScriptPromise screen_promise = screen_resolver->Promise(); - auto* wake_lock = MakeGarbageCollected<WakeLock>(*context.DomWindow()); + auto* wake_lock = WakeLock::wakeLock(*context.DomWindow()->navigator()); wake_lock->DoRequest(WakeLockType::kScreen, screen_resolver); WakeLockManager* manager =
diff --git a/third_party/blink/renderer/modules/wake_lock/wake_lock_test.cc b/third_party/blink/renderer/modules/wake_lock/wake_lock_test.cc index 29706f0..ee35774c 100644 --- a/third_party/blink/renderer/modules/wake_lock/wake_lock_test.cc +++ b/third_party/blink/renderer/modules/wake_lock/wake_lock_test.cc
@@ -10,6 +10,7 @@ #include "third_party/blink/renderer/core/dom/dom_exception.h" #include "third_party/blink/renderer/core/frame/local_dom_window.h" #include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/frame/navigator.h" #include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/modules/wake_lock/wake_lock_test_utils.h" #include "third_party/blink/renderer/platform/heap/handle.h" @@ -28,7 +29,7 @@ MakeGarbageCollected<ScriptPromiseResolver>(context.GetScriptState()); ScriptPromise screen_promise = screen_resolver->Promise(); - auto* wake_lock = MakeGarbageCollected<WakeLock>(*context.DomWindow()); + auto* wake_lock = WakeLock::wakeLock(*context.DomWindow()->navigator()); wake_lock->DoRequest(WakeLockType::kScreen, screen_resolver); MockWakeLock& screen_lock = @@ -55,7 +56,7 @@ MakeGarbageCollected<ScriptPromiseResolver>(context.GetScriptState()); ScriptPromise system_promise = system_resolver->Promise(); - auto* wake_lock = MakeGarbageCollected<WakeLock>(*context.DomWindow()); + auto* wake_lock = WakeLock::wakeLock(*context.DomWindow()->navigator()); wake_lock->DoRequest(WakeLockType::kSystem, system_resolver); MockWakeLock& system_lock = @@ -102,7 +103,7 @@ MakeGarbageCollected<ScriptPromiseResolver>(context.GetScriptState()); system_resolver1->Promise(); - auto* wake_lock = MakeGarbageCollected<WakeLock>(*context.DomWindow()); + auto* wake_lock = WakeLock::wakeLock(*context.DomWindow()->navigator()); wake_lock->DoRequest(WakeLockType::kScreen, screen_resolver1); wake_lock->DoRequest(WakeLockType::kScreen, screen_resolver2); screen_lock.WaitForRequest(); @@ -143,7 +144,7 @@ MakeGarbageCollected<ScriptPromiseResolver>(context.GetScriptState()); ScriptPromise system_promise = system_resolver->Promise(); - auto* wake_lock = MakeGarbageCollected<WakeLock>(*context.DomWindow()); + auto* wake_lock = WakeLock::wakeLock(*context.DomWindow()->navigator()); wake_lock->DoRequest(WakeLockType::kScreen, screen_resolver); screen_lock.WaitForRequest(); wake_lock->DoRequest(WakeLockType::kSystem, system_resolver); @@ -194,7 +195,7 @@ MakeGarbageCollected<ScriptPromiseResolver>(context.GetScriptState()); ScriptPromise system_promise = system_resolver->Promise(); - auto* wake_lock = MakeGarbageCollected<WakeLock>(*context.DomWindow()); + auto* wake_lock = WakeLock::wakeLock(*context.DomWindow()->navigator()); wake_lock->DoRequest(WakeLockType::kScreen, screen_resolver); wake_lock->DoRequest(WakeLockType::kSystem, system_resolver); context.Frame()->GetPage()->SetVisibilityState(
diff --git a/third_party/blink/renderer/modules/wake_lock/worker_navigator_wake_lock.cc b/third_party/blink/renderer/modules/wake_lock/worker_navigator_wake_lock.cc deleted file mode 100644 index ecd93b8..0000000 --- a/third_party/blink/renderer/modules/wake_lock/worker_navigator_wake_lock.cc +++ /dev/null
@@ -1,57 +0,0 @@ -// Copyright 2019 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 "third_party/blink/renderer/modules/wake_lock/worker_navigator_wake_lock.h" - -#include "third_party/blink/renderer/core/workers/worker_navigator.h" -#include "third_party/blink/renderer/modules/wake_lock/wake_lock.h" - -namespace blink { - -WorkerNavigatorWakeLock::WorkerNavigatorWakeLock(WorkerNavigator& navigator) - : Supplement<WorkerNavigator>(navigator) {} - -// static -const char WorkerNavigatorWakeLock::kSupplementName[] = - "WorkerNavigatorWakeLock"; - -// static -WorkerNavigatorWakeLock& WorkerNavigatorWakeLock::From( - WorkerNavigator& navigator) { - WorkerNavigatorWakeLock* supplement = - Supplement<WorkerNavigator>::From<WorkerNavigatorWakeLock>(navigator); - if (!supplement) { - supplement = MakeGarbageCollected<WorkerNavigatorWakeLock>(navigator); - ProvideTo(navigator, supplement); - } - return *supplement; -} - -// static -WakeLock* WorkerNavigatorWakeLock::wakeLock(ScriptState* script_state, - WorkerNavigator& navigator) { - return WorkerNavigatorWakeLock::From(navigator).GetWakeLock(script_state); -} - -WakeLock* WorkerNavigatorWakeLock::GetWakeLock(ScriptState* script_state) { - if (!wake_lock_) { - auto* execution_context = ExecutionContext::From(script_state); - DCHECK(execution_context); - - // TODO(https://crbug.com/839117): Remove this check once the Exposed - // attribute is fixed to only expose this property in dedicated workers. - if (execution_context->IsDedicatedWorkerGlobalScope()) { - wake_lock_ = MakeGarbageCollected<WakeLock>( - *To<DedicatedWorkerGlobalScope>(execution_context)); - } - } - return wake_lock_; -} - -void WorkerNavigatorWakeLock::Trace(Visitor* visitor) const { - visitor->Trace(wake_lock_); - Supplement<WorkerNavigator>::Trace(visitor); -} - -} // namespace blink
diff --git a/third_party/blink/renderer/modules/wake_lock/worker_navigator_wake_lock.h b/third_party/blink/renderer/modules/wake_lock/worker_navigator_wake_lock.h deleted file mode 100644 index 031d054..0000000 --- a/third_party/blink/renderer/modules/wake_lock/worker_navigator_wake_lock.h +++ /dev/null
@@ -1,39 +0,0 @@ -// Copyright 2019 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 THIRD_PARTY_BLINK_RENDERER_MODULES_WAKE_LOCK_WORKER_NAVIGATOR_WAKE_LOCK_H_ -#define THIRD_PARTY_BLINK_RENDERER_MODULES_WAKE_LOCK_WORKER_NAVIGATOR_WAKE_LOCK_H_ - -#include "third_party/blink/renderer/platform/heap/handle.h" -#include "third_party/blink/renderer/platform/supplementable.h" - -namespace blink { - -class ScriptState; -class WakeLock; -class WorkerNavigator; - -class WorkerNavigatorWakeLock final - : public GarbageCollected<WorkerNavigatorWakeLock>, - public Supplement<WorkerNavigator> { - public: - static const char kSupplementName[]; - - static WorkerNavigatorWakeLock& From(WorkerNavigator&); - - static WakeLock* wakeLock(ScriptState*, WorkerNavigator&); - - explicit WorkerNavigatorWakeLock(WorkerNavigator&); - - void Trace(Visitor*) const override; - - private: - WakeLock* GetWakeLock(ScriptState*); - - Member<WakeLock> wake_lock_; -}; - -} // namespace blink - -#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_WAKE_LOCK_WORKER_NAVIGATOR_WAKE_LOCK_H_
diff --git a/third_party/blink/renderer/modules/wake_lock/worker_navigator_wake_lock.idl b/third_party/blink/renderer/modules/wake_lock/worker_navigator_wake_lock.idl index 764e881..9b0dcac 100644 --- a/third_party/blink/renderer/modules/wake_lock/worker_navigator_wake_lock.idl +++ b/third_party/blink/renderer/modules/wake_lock/worker_navigator_wake_lock.idl
@@ -7,9 +7,9 @@ // until System Wake Lock API was split from the Screen Wake Lock API. [ - ImplementedAs=WorkerNavigatorWakeLock, + ImplementedAs=WakeLock, RuntimeEnabled=SystemWakeLock, SecureContext ] partial interface WorkerNavigator { - [CallWith=ScriptState, SameObject] readonly attribute WakeLock wakeLock; + [SameObject] readonly attribute WakeLock wakeLock; };
diff --git a/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.cc b/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.cc index 22dac65..2ec11fa 100644 --- a/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.cc +++ b/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.cc
@@ -29,7 +29,6 @@ #include "third_party/blink/renderer/platform/runtime_enabled_features.h" #include "third_party/libavif/src/include/avif/avif.h" #include "third_party/libyuv/include/libyuv.h" -#include "third_party/skia/include/core/SkData.h" #include "ui/gfx/color_space.h" #include "ui/gfx/color_transform.h" #include "ui/gfx/half_float.h" @@ -41,6 +40,11 @@ namespace { +// The maximum AVIF file size we are willing to decode. This helps libavif +// detect invalid sizes and offsets in an AVIF file before the file size is +// known. +constexpr uint64_t kMaxAvifFileSize = 0x10000000; // 256 MB + // Builds a gfx::ColorSpace from the ITU-T H.273 (CICP) color description in the // image. This color space is used to create the gfx::ColorTransform for the // YUV-to-RGB conversion. If the image does not have an ICC profile, this color @@ -271,11 +275,11 @@ } void AVIFImageDecoder::OnSetData(SegmentReader* data) { - // avifDecoder requires all the data be available before reading and cannot - // read incrementally as data comes in. See - // https://github.com/AOMediaCodec/libavif/issues/11. - if (IsAllDataReceived() && !MaybeCreateDemuxer()) - SetFailed(); + have_parsed_current_data_ = false; + const bool all_data_received = IsAllDataReceived(); + avif_io_data_.reader = data_.get(); + avif_io_data_.all_data_received = all_data_received; + avif_io_.sizeHint = all_data_received ? data_->size() : kMaxAvifFileSize; } cc::YUVSubsampling AVIFImageDecoder::GetYUVSubsampling() const { @@ -355,7 +359,7 @@ // libavif cannot decode to an external buffer. So we need to copy from // libavif's internal buffer to |image_planes_|. // TODO(crbug.com/1099825): Enhance libavif to decode to an external buffer. - if (!DecodeImage(0)) { + if (DecodeImage(0) != AVIF_RESULT_OK) { SetFailed(); return; } @@ -449,6 +453,18 @@ return decoded_frame_count_ > 1 ? kAnimationLoopInfinite : kAnimationNone; } +bool AVIFImageDecoder::FrameIsReceivedAtIndex(size_t index) const { + if (!IsDecodedSizeAvailable()) + return false; + if (decoded_frame_count_ == 1) + return ImageDecoder::FrameIsReceivedAtIndex(index); + // frame_buffer_cache_.size() is equal to the return value of + // DecodeFrameCount(). Since DecodeFrameCount() returns the number of frames + // whose encoded data have been received, we can return true if |index| is + // valid for frame_buffer_cache_. See crbug.com/1148577. + return index < frame_buffer_cache_.size(); +} + base::TimeDelta AVIFImageDecoder::FrameDurationAtIndex(size_t index) const { return index < frame_buffer_cache_.size() ? frame_buffer_cache_[index].Duration() @@ -496,15 +512,31 @@ return color_transform_.get(); } +void AVIFImageDecoder::ParseMetadata() { + if (!UpdateDemuxer()) + SetFailed(); +} + void AVIFImageDecoder::DecodeSize() { - // Because avifDecoder cannot read incrementally as data comes in, we cannot - // decode the size until all data is received. When all data is received, - // OnSetData() decodes the size right away. So DecodeSize() doesn't need to do - // anything. + ParseMetadata(); } size_t AVIFImageDecoder::DecodeFrameCount() { - return Failed() ? frame_buffer_cache_.size() : decoded_frame_count_; + if (!Failed()) + ParseMetadata(); + if (!IsDecodedSizeAvailable()) + return frame_buffer_cache_.size(); + if (decoded_frame_count_ == 1 || IsAllDataReceived()) + return decoded_frame_count_; + // For a multi-frame image, Chrome expects DecodeFrameCount() to return the + // number of frames whose encoded data have been received. + size_t index; + for (index = frame_buffer_cache_.size(); index < decoded_frame_count_; + ++index) { + if (avifDecoderNthImageReady(decoder_.get(), index) != AVIF_RESULT_OK) + break; + } + return index; } void AVIFImageDecoder::InitializeNewFrame(size_t index) { @@ -522,16 +554,15 @@ } void AVIFImageDecoder::Decode(size_t index) { - // TODO(dalecurtis): For fragmented AVIF image sequence files we probably want - // to allow partial decoding. Depends on if we see frequent use of multi-track - // images where there's lots to ignore. - if (Failed() || !IsAllDataReceived()) + if (Failed()) return; UpdateAggressivePurging(index); - if (!DecodeImage(index)) { - SetFailed(); + auto ret = DecodeImage(index); + if (ret != AVIF_RESULT_OK) { + if (ret != AVIF_RESULT_WAITING_ON_IO) + SetFailed(); return; } @@ -587,46 +618,102 @@ return true; } -bool AVIFImageDecoder::MaybeCreateDemuxer() { - if (decoder_) +// static +avifResult AVIFImageDecoder::ReadFromSegmentReader(avifIO* io, + uint32_t read_flags, + uint64_t offset, + size_t size, + avifROData* out) { + if (read_flags != 0) { + // Unsupported read_flags + return AVIF_RESULT_IO_ERROR; + } + + AvifIOData* io_data = static_cast<AvifIOData*>(io->data); + + // Sanitize/clamp incoming request + if (offset > io_data->reader->size()) { + // The offset is past the end of the buffer or available data. + return io_data->all_data_received ? AVIF_RESULT_IO_ERROR + : AVIF_RESULT_WAITING_ON_IO; + } + + // It is more convenient to work with a variable of the size_t type. Since + // offset <= io_data->reader->size() <= SIZE_MAX, this cast is safe. + size_t position = static_cast<size_t>(offset); + const size_t available_size = io_data->reader->size() - position; + if (size > available_size) { + if (!io_data->all_data_received) + return AVIF_RESULT_WAITING_ON_IO; + size = available_size; + } + + out->size = size; + const char* data; + size_t data_size = io_data->reader->GetSomeData(data, position); + if (data_size >= size) { + out->data = reinterpret_cast<const uint8_t*>(data); + return AVIF_RESULT_OK; + } + + io_data->buffer.clear(); + io_data->buffer.reserve(size); + while (size != 0) { + data_size = io_data->reader->GetSomeData(data, position); + size_t copy_size = std::min(data_size, size); + io_data->buffer.insert(io_data->buffer.end(), data, data + copy_size); + position += copy_size; + size -= copy_size; + } + + out->data = io_data->buffer.data(); + return AVIF_RESULT_OK; +} + +bool AVIFImageDecoder::UpdateDemuxer() { + DCHECK(!Failed()); + if (IsDecodedSizeAvailable()) return true; - decoder_ = std::unique_ptr<avifDecoder, void (*)(avifDecoder*)>( - avifDecoderCreate(), avifDecoderDestroy); - if (!decoder_) - return false; + if (have_parsed_current_data_) + return true; + have_parsed_current_data_ = true; - // TODO(dalecurtis): This may create a second copy of the media data in - // memory, which is not great. libavif should provide a read() based API: - // https://github.com/AOMediaCodec/libavif/issues/11 - image_data_ = data_->GetAsSkData(); - if (!image_data_) - return false; + if (!decoder_) { + decoder_ = std::unique_ptr<avifDecoder, void (*)(avifDecoder*)>( + avifDecoderCreate(), avifDecoderDestroy); + if (!decoder_) + return false; - // TODO(wtc): Currently libavif always prioritizes the animation, but that's - // not correct. It should instead select animation or still image based on the - // preferred and major brands listed in the file. - if (animation_option_ != AnimationOption::kUnspecified && - avifDecoderSetSource( - decoder_.get(), animation_option_ == AnimationOption::kPreferAnimation - ? AVIF_DECODER_SOURCE_TRACKS - : AVIF_DECODER_SOURCE_PRIMARY_ITEM) != - AVIF_RESULT_OK) { - return false; + // TODO(wtc): Currently libavif always prioritizes the animation, but that's + // not correct. It should instead select animation or still image based on + // the preferred and major brands listed in the file. + if (animation_option_ != AnimationOption::kUnspecified && + avifDecoderSetSource( + decoder_.get(), + animation_option_ == AnimationOption::kPreferAnimation + ? AVIF_DECODER_SOURCE_TRACKS + : AVIF_DECODER_SOURCE_PRIMARY_ITEM) != AVIF_RESULT_OK) { + return false; + } + + // Chrome doesn't use XMP and Exif metadata. Ignoring XMP and Exif will + // ensure avifDecoderParse() isn't waiting for some tiny Exif payload hiding + // at the end of a file. + decoder_->ignoreXMP = AVIF_TRUE; + decoder_->ignoreExif = AVIF_TRUE; + + avif_io_.destroy = nullptr; + avif_io_.read = ReadFromSegmentReader; + avif_io_.write = nullptr; + avif_io_.persistent = AVIF_FALSE; + avif_io_.data = &avif_io_data_; + avifDecoderSetIO(decoder_.get(), &avif_io_); } - // Chrome doesn't use XMP and Exif metadata. Ignoring XMP and Exif will ensure - // avifDecoderParse() isn't waiting for some tiny Exif payload hiding at the - // end of a file. - decoder_->ignoreXMP = AVIF_TRUE; - decoder_->ignoreExif = AVIF_TRUE; - auto ret = avifDecoderSetIOMemory(decoder_.get(), image_data_->bytes(), - image_data_->size()); - if (ret != AVIF_RESULT_OK) { - DVLOG(1) << "avifDecoderSetIOMemory failed: " << avifResultToString(ret); - return false; - } - ret = avifDecoderParse(decoder_.get()); + auto ret = avifDecoderParse(decoder_.get()); + if (ret == AVIF_RESULT_WAITING_ON_IO) + return true; if (ret != AVIF_RESULT_OK) { DVLOG(1) << "avifDecoderParse failed: " << avifResultToString(ret); return false; @@ -724,12 +811,12 @@ return SetSize(container->width, container->height); } -bool AVIFImageDecoder::DecodeImage(size_t index) { +avifResult AVIFImageDecoder::DecodeImage(size_t index) { const auto ret = avifDecoderNthImage(decoder_.get(), index); // |index| should be less than what DecodeFrameCount() returns, so we should // not get the AVIF_RESULT_NO_IMAGES_REMAINING error. DCHECK_NE(ret, AVIF_RESULT_NO_IMAGES_REMAINING); - return ret == AVIF_RESULT_OK; + return ret; } void AVIFImageDecoder::UpdateColorTransform(const gfx::ColorSpace& frame_cs,
diff --git a/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.h b/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.h index b3de5b6..098f8ba 100644 --- a/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.h +++ b/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder.h
@@ -8,14 +8,11 @@ #include <memory> #include "third_party/blink/renderer/platform/image-decoders/image_decoder.h" -#include "third_party/skia/include/core/SkBitmap.h" -#include "third_party/skia/include/core/SkData.h" +#include "third_party/libavif/src/include/avif/avif.h" +#include "third_party/skia/include/core/SkImageInfo.h" #include "ui/gfx/color_space.h" #include "ui/gfx/color_transform.h" -struct avifDecoder; -struct avifImage; - namespace blink { class FastSharedBufferReader; @@ -42,6 +39,7 @@ uint8_t GetYUVBitDepth() const override; void DecodeToYUV() override; int RepetitionCount() const override; + bool FrameIsReceivedAtIndex(size_t) const override; base::TimeDelta FrameDurationAtIndex(size_t) const override; bool ImageHasBothStillAndAnimatedSubImages() const override; @@ -52,6 +50,14 @@ gfx::ColorTransform* GetColorTransformForTesting(); private: + struct AvifIOData { + blink::SegmentReader* reader = nullptr; + std::vector<uint8_t> buffer; + bool all_data_received = false; + }; + + void ParseMetadata(); + // ImageDecoder: void DecodeSize() override; size_t DecodeFrameCount() override; @@ -59,12 +65,19 @@ void Decode(size_t) override; bool CanReusePreviousFrameBuffer(size_t) const override; - // Creates |decoder_| and decodes the size and frame count. - bool MaybeCreateDemuxer(); + // Implements avifIOReadFunc, the |read| function in the avifIO struct. + static avifResult ReadFromSegmentReader(avifIO* io, + uint32_t read_flags, + uint64_t offset, + size_t size, + avifROData* out); + + // Creates |decoder_| if not yet created and decodes the size and frame count. + bool UpdateDemuxer(); // Decodes the frame at index |index|. The decoded frame is available in - // decoder_->image. Returns whether decoding completed successfully. - bool DecodeImage(size_t index); + // decoder_->image. + avifResult DecodeImage(size_t index); // Updates or creates |color_transform_| for YUV-to-RGB conversion. void UpdateColorTransform(const gfx::ColorSpace& frame_cs, int bit_depth); @@ -77,24 +90,24 @@ // desired. void ColorCorrectImage(ImageFrame* buffer); + bool have_parsed_current_data_ = false; // The bit depth from the container. uint8_t bit_depth_ = 0; bool decode_to_half_float_ = false; - // The YUV format from the container. Stores an avifPixelFormat enum value. - // Declared as uint8_t because we can't forward-declare an enum type in C++. - uint8_t avif_yuv_format_ = 0; // AVIF_PIXEL_FORMAT_NONE uint8_t chroma_shift_x_ = 0; uint8_t chroma_shift_y_ = 0; + // The YUV format from the container. + avifPixelFormat avif_yuv_format_ = AVIF_PIXEL_FORMAT_NONE; size_t decoded_frame_count_ = 0; SkYUVColorSpace yuv_color_space_ = SkYUVColorSpace::kIdentity_SkYUVColorSpace; std::unique_ptr<avifDecoder, void (*)(avifDecoder*)> decoder_{nullptr, nullptr}; + avifIO avif_io_ = {}; + AvifIOData avif_io_data_; std::unique_ptr<gfx::ColorTransform> color_transform_; const AnimationOption animation_option_; - - sk_sp<SkData> image_data_; }; } // namespace blink
diff --git a/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder_test.cc b/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder_test.cc index 94b7158..53dcac74 100644 --- a/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder_test.cc +++ b/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder_test.cc
@@ -480,10 +480,12 @@ decoder->SetData(data.get(), true); if (error_phase == ErrorPhase::kParse) { + EXPECT_FALSE(decoder->IsSizeAvailable()); EXPECT_TRUE(decoder->Failed()); EXPECT_EQ(0u, decoder->FrameCount()); EXPECT_FALSE(decoder->DecodeFrameBufferAtIndex(0)); } else { + EXPECT_TRUE(decoder->IsSizeAvailable()); EXPECT_FALSE(decoder->Failed()); EXPECT_GT(decoder->FrameCount(), 0u); ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0);
diff --git a/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.cc b/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.cc index 38d5739..34e7f16 100644 --- a/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.cc +++ b/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.cc
@@ -186,7 +186,7 @@ frame_background_has_alpha_(false), demux_(nullptr), demux_state_(WEBP_DEMUX_PARSING_HEADER), - have_already_parsed_this_data_(false), + have_parsed_current_data_(false), repetition_count_(kAnimationLoopOnce), decoded_height_(0) { blend_function_ = (alpha_option == kAlphaPremultiplied) @@ -259,7 +259,7 @@ } void WEBPImageDecoder::OnSetData(SegmentReader* data) { - have_already_parsed_this_data_ = false; + have_parsed_current_data_ = false; // TODO(crbug.com/943519): Modify this approach for incremental YUV (when // we don't require IsAllDataReceived() to be true before decoding). if (IsAllDataReceived()) { @@ -302,10 +302,9 @@ if (data_->size() < kWebpHeaderSize) return IsAllDataReceived() ? SetFailed() : false; - if (have_already_parsed_this_data_) + if (have_parsed_current_data_) return true; - - have_already_parsed_this_data_ = true; + have_parsed_current_data_ = true; if (consolidated_data_ && consolidated_data_->size() >= data_->size()) { // Less data provided than last time. |consolidated_data_| is guaranteed
diff --git a/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.h b/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.h index e529ec3..68fdcb0 100644 --- a/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.h +++ b/third_party/blink/renderer/platform/image-decoders/webp/webp_image_decoder.h
@@ -123,7 +123,7 @@ WebPDemuxer* demux_; WebPDemuxState demux_state_; - bool have_already_parsed_this_data_; + bool have_parsed_current_data_; int repetition_count_; int decoded_height_;
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 index 3698073..08a204af 100644 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -445,6 +445,7 @@ { // https://drafts.csswg.org/css-counter-styles-3 name: "CSSAtRuleCounterStyle", + status: "test", }, { name: "CSSCalcAsInt",
diff --git a/third_party/blink/renderer/platform/widget/widget_base.cc b/third_party/blink/renderer/platform/widget/widget_base.cc index 9cd6972..1fe8eb1 100644 --- a/third_party/blink/renderer/platform/widget/widget_base.cc +++ b/third_party/blink/renderer/platform/widget/widget_base.cc
@@ -353,7 +353,7 @@ blink::VisualProperties visual_properties = visual_properties_from_browser; // Web tests can override the device scale factor in the renderer. - if (auto scale_factor = client_->GetDeviceScaleFactorForTesting()) { + if (auto scale_factor = client_->GetTestingDeviceScaleFactorOverride()) { visual_properties.screen_info.device_scale_factor = scale_factor; visual_properties.compositor_viewport_pixel_rect = gfx::Rect(gfx::ScaleToCeiledSize(
diff --git a/third_party/blink/renderer/platform/widget/widget_base_client.h b/third_party/blink/renderer/platform/widget/widget_base_client.h index 1012eb159..61ea127 100644 --- a/third_party/blink/renderer/platform/widget/widget_base_client.h +++ b/third_party/blink/renderer/platform/widget/widget_base_client.h
@@ -182,7 +182,7 @@ } // Return the overridden device scale factor for testing. - virtual float GetDeviceScaleFactorForTesting() { return 0.f; } + virtual float GetTestingDeviceScaleFactorOverride() { return 0.f; } // Test-specific methods below this point. virtual void ScheduleAnimationForWebTests() {}
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json index d674ade..ca3a1d05 100644 --- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json +++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -1023,6 +1023,13 @@ {} ] ], + "fixed-pos-children.html": [ + "773439d6f3e9bd29d0e56fe2f06f9b446804e8e1", + [ + null, + {} + ] + ], "multicol-on-token-elements.html": [ "9fc00eb6911b625a7443a3e1ec65ada7daf25105", [ @@ -9914,17 +9921,6 @@ {} ] ] - }, - "order-of-events": { - "focus-events": { - "focus-manual.html": [ - "391daf7028ce7fdff4f78b542caa2dd7d917a0d2", - [ - null, - {} - ] - ] - } } }, "vibration": { @@ -66685,6 +66681,19 @@ {} ] ], + "position-absolute-scrollbar-freeze.html": [ + "ac510f11e9160b1ed646047ed5aa44d2ebaa8a6f", + [ + null, + [ + [ + "/css/css-flexbox/position-absolute-scrollbar-freeze-ref.html", + "==" + ] + ], + {} + ] + ], "position-fixed-001.html": [ "a3de19d6d9df95a505c547e48dea3de82af44de5", [ @@ -91908,7 +91917,7 @@ ] ], "selection-textarea-011.html": [ - "f21e5e190ff1ed587def22a4ed87de6193dfd762", + "4a925bae3389b97bcc03d42c8e6f3fb4190efe5d", [ null, [ @@ -183478,6 +183487,10 @@ "5e1ccdbc8f04119d90c0d7241918bbcd32c5d750", [] ], + "position-absolute-scrollbar-freeze-ref.html": [ + "d0bf617951f3a9b5e3ae8e4b77e6ff3762b3cde1", + [] + ], "reference": { "align-baseline-ref.html": [ "d5b88e8c7dae73b57c7f993260d7cce363eeebaa", @@ -196190,7 +196203,7 @@ [] ], "selection-textarea-011-ref.html": [ - "825a4247a202dac2bc3dde257ddff81f2e8eded8", + "5c78176d606c8e5619b068f03611f8e0894ab138", [] ] }, @@ -207854,10 +207867,6 @@ ] }, "cssom": { - "CSSCounterStyleRule-expected.txt": [ - "525f5da12305d001d26d00cfff1e3c8f6ebcf7d7", - [] - ], "CSSGroupingRule-insertRule-expected.txt": [ "44e8aaa155eef7b2f14b1d86cee6ec9de789162a", [] @@ -241359,7 +241368,7 @@ [] ], "import-module-scripts.https-expected.txt": [ - "6bac8b097c273a972583a8d4c453f22915975bb7", + "21e4c43a967459857dd8c8e8e05b14ba2a68c6fc", [] ], "import-scripts-redirect.https-expected.txt": [ @@ -242103,6 +242112,10 @@ "19c7a4b8e561b3d95f10496e92a2c7264d693815", [] ], + "import-scripts-cross-origin-worker.sub.js": [ + "b432854db8bc7f8292c43434d8c24a3ffe97dec0", + [] + ], "import-scripts-diff-resource-map-worker.js": [ "0fdcb0fcf80ad7f0dffa284c5b77178bdad95ef6", [] @@ -251009,7 +251022,7 @@ [] ], "requirements.txt": [ - "766b9bd914391f558de2e28999cbfcc3f16db8ca", + "7c134b89b9583f9f0584450f20e9665cb80e6445", [] ], "requirements_android_webview.txt": [ @@ -254128,6 +254141,10 @@ "1972187d21fe1d385f03843b2fe222d2ef3183f7", [] ], + "meta-utf8-after-1024-bytes.html": [ + "b5916148b558e5077338bf77575d3677bdffa764", + [] + ], "subframe.html": [ "2019485529f64c778081b542646d36b55e4c4a39", [] @@ -257859,6 +257876,10 @@ "cb762eff806849df46dc758ef7b98b63f27f54c9", [] ], + "export-on-load-script.py": [ + "6cca411e8a85a7fa0d78ced6b32011bda890b7c3", + [] + ], "export-on-static-import-script.js": [ "3f7174eef0beb68a3f36d66d3ed7a73c9074955c", [] @@ -257884,7 +257905,7 @@ [] ], "import-test-cases.js": [ - "c5830e97eebc88b12a5ed1f600e810df443204f8", + "8ad89e8fda11a3923e29128f4835928f9a8c2cb6", [] ], "nested-dynamic-import-worker.js": [ @@ -257900,7 +257921,7 @@ [] ], "new-worker-window.html": [ - "a7bd2eedef6ab6d639f28e063ccd50aefbbb0528", + "32a89fae0ecd59ba7c84870c86f4251d057c5339", [] ], "post-message-on-load-worker.js": [ @@ -257915,6 +257936,10 @@ "699af684a2366fc0aaa8c1e64abf3240489cf7be", [] ], + "redirect.py": [ + "396bd4c4b2dfbb6091779b6f4375245c1a4604d8", + [] + ], "static-import-and-then-dynamic-import-worker.js": [ "2d857a2e90a11af9f52e5d59d15ee30d225deeaf", [] @@ -257927,6 +257952,10 @@ "16f70e9daf4909605746c6614a509afb3cf71ed1", [] ], + "static-import-redirect-worker.js": [ + "8434c1a34e2837c9fde3709ec3d5bed468deeb7e", + [] + ], "static-import-remote-origin-credentials-checker-worker.sub.js": [ "c543cb49616062dc85fbbe7d885c17ada9646728", [] @@ -257936,7 +257965,7 @@ [] ], "static-import-remote-origin-script-worker.sub.js": [ - "0221a2b8ba775c672d724a9f5946cdc9915db362", + "6432dd5d80d0f1c5c05512658dd240f09fa8ae8e", [] ], "static-import-same-origin-credentials-checker-worker.js": [ @@ -335086,7 +335115,7 @@ ] ], "script-resource-with-json-parser-breaker.tentative.sub.html": [ - "d46944727f8ee9fc144caec437a5505be75e3d4b", + "1a8095ec658197833cf07f1f08407ac9970f33c4", [ null, {} @@ -405895,6 +405924,13 @@ {} ] ], + "import-scripts-cross-origin.https.html": [ + "773708a9fbce32a6b17f22d88b9fb6ebc3553710", + [ + null, + {} + ] + ], "import-scripts-mime-types.https.html": [ "1679831d0f2b98aee22898d820373e80079fbe00", [ @@ -406914,7 +406950,7 @@ ] ], "getinnerhtml.tentative.html": [ - "64fd78ba8ed0ca4d465c5a3ef8cb496350c69f7c", + "bcf5471b5e9e8748c49fbc899788203ead6fc19a", [ null, {} @@ -406949,7 +406985,7 @@ ] ], "setinnerhtml.tentative.html": [ - "f99863c7c1fabfbaee11d5cc90cc81d3e4acebff", + "076fdac2b0dda4fadfb1584a8b7650f736e7a908", [ null, {} @@ -417534,6 +417570,15 @@ "testdriver": true } ] + ], + "focus.html": [ + "7942674a70b7941cd1c23cee1251c43524362d57", + [ + null, + { + "testdriver": true + } + ] ] }, "mouse-events": { @@ -438027,6 +438072,13 @@ {} ] ], + "dedicated-worker-import-data-url-cross-origin.html": [ + "37390947b642ebf1ff80434d6e870a0118d3b0be", + [ + null, + {} + ] + ], "dedicated-worker-import-data-url.any.js": [ "0d8510da0c24ad378d1215157a70b4fd615a3d94", [ @@ -454253,7 +454305,7 @@ }, "navigate_to": { "navigate.py": [ - "289e4321011f3ab245ddc359a787adf1d6eddcab", + "b577ecdf96da77174bd4b360fb9a5e385584ff75", [ null, {}
diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/reference/selection-textarea-011-ref.html b/third_party/blink/web_tests/external/wpt/css/css-pseudo/reference/selection-textarea-011-ref.html index 825a424..5c78176 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-pseudo/reference/selection-textarea-011-ref.html +++ b/third_party/blink/web_tests/external/wpt/css/css-pseudo/reference/selection-textarea-011-ref.html
@@ -18,6 +18,7 @@ padding: 0px; resize: none; width: 8ch; + outline: none; } </style>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-pseudo/selection-textarea-011.html b/third_party/blink/web_tests/external/wpt/css/css-pseudo/selection-textarea-011.html index f21e5e1..4a925ba 100644 --- a/third_party/blink/web_tests/external/wpt/css/css-pseudo/selection-textarea-011.html +++ b/third_party/blink/web_tests/external/wpt/css/css-pseudo/selection-textarea-011.html
@@ -22,6 +22,7 @@ padding: 0px; resize: none; width: 8ch; + outline: none; } textarea::selection
diff --git a/third_party/blink/web_tests/external/wpt/css/cssom/CSSCounterStyleRule-expected.txt b/third_party/blink/web_tests/external/wpt/css/cssom/CSSCounterStyleRule-expected.txt deleted file mode 100644 index 525f5da..0000000 --- a/third_party/blink/web_tests/external/wpt/css/cssom/CSSCounterStyleRule-expected.txt +++ /dev/null
@@ -1,4 +0,0 @@ -This is a testharness.js-based test. -FAIL CSSCounterStyleRule.cssText doesn't serialize with newlines Cannot read property 'cssText' of undefined -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/external/wpt/fetch/corb/script-resource-with-json-parser-breaker.tentative.sub.html b/third_party/blink/web_tests/external/wpt/fetch/corb/script-resource-with-json-parser-breaker.tentative.sub.html index d469447..1a8095ec 100644 --- a/third_party/blink/web_tests/external/wpt/fetch/corb/script-resource-with-json-parser-breaker.tentative.sub.html +++ b/third_party/blink/web_tests/external/wpt/fetch/corb/script-resource-with-json-parser-breaker.tentative.sub.html
@@ -1,4 +1,5 @@ <!DOCTYPE html> +<meta charset="utf-8"> <!-- Test verifies CORB will block responses beginning with a JSON parser breaker regardless of their MIME type (excluding text/css - see below). @@ -18,7 +19,6 @@ style-css-with-json-parser-breaker.sub.html) can parse as valid stylesheet even in presence of a JSON parser breaker. --> -<meta charset="utf-8"> <script src="/resources/testharness.js"></script> <script src="/resources/testharnessreport.js"></script> <div id=log></div>
diff --git a/third_party/blink/web_tests/external/wpt/mathml/crashtests/fixed-pos-children.html b/third_party/blink/web_tests/external/wpt/mathml/crashtests/fixed-pos-children.html new file mode 100644 index 0000000..773439d --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/mathml/crashtests/fixed-pos-children.html
@@ -0,0 +1,25 @@ +<style> + * { + border-bottom: hsla(115.49088416259553deg 12% 15% / 18%) groove thin; + -webkit-text-stroke: InactiveCaptionText thin; + scale: -121 + } + + * * { + padding-inline: 69%; + } +</style> +<script> + document.addEventListener('DOMContentLoaded', () => { + const style = document.createElement('style') + document.head.appendChild(style) + style.sheet.insertRule('* {-webkit-mask-image:url(', 0) + }) +</script> +<math> + <mmultiscripts> + <mglyph> + <maligngroup style='position:fixed'></maligngroup> + </mglyph> + </mmultiscripts> +</math> \ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/import-module-scripts.https-expected.txt b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/import-module-scripts.https-expected.txt index 6bac8b09..21e4c43 100644 --- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/import-module-scripts.https-expected.txt +++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/import-module-scripts.https-expected.txt
@@ -1,5 +1,7 @@ This is a testharness.js-based test. PASS Static import. +PASS Static import (cross-origin). +FAIL Static import (redirect). promise_test: Unhandled rejection with value: object "AbortError: Failed to register a ServiceWorker for scope ('https://web-platform.test:8444/workers/modules/resources/static-import-redirect-worker.js') with script ('https://web-platform.test:8444/workers/modules/resources/static-import-redirect-worker.js'): ServiceWorker cannot be started" PASS Nested static import. FAIL Static import and then dynamic import. assert_array_equals: value is "Failed to do dynamic import: TypeError: Module scripts are not supported on ServiceWorkerGlobalScope yet (see https://crbug.com/824647).", expected array FAIL Dynamic import. assert_array_equals: value is "Failed to do dynamic import: TypeError: Module scripts are not supported on ServiceWorkerGlobalScope yet (see https://crbug.com/824647).", expected array
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/import-scripts-cross-origin.https.html b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/import-scripts-cross-origin.https.html new file mode 100644 index 0000000..773708a --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/import-scripts-cross-origin.https.html
@@ -0,0 +1,18 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Tests for importScripts: cross-origin</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/test-helpers.sub.js"></script> +<body> +<script> +promise_test(async t => { + const scope = 'resources/import-scripts-cross-origin'; + await service_worker_unregister(t, scope); + let reg = await navigator.serviceWorker.register( + 'resources/import-scripts-cross-origin-worker.sub.js', { scope: scope }); + t.add_cleanup(_ => reg.unregister()); + assert_not_equals(reg.installing, null, 'worker is installing'); + }, 'importScripts() supports cross-origin requests'); +</script> +</body>
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/import-scripts-cross-origin-worker.sub.js b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/import-scripts-cross-origin-worker.sub.js new file mode 100644 index 0000000..b432854 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/resources/import-scripts-cross-origin-worker.sub.js
@@ -0,0 +1 @@ +importScripts('https://{{domains[www1]}}:{{ports[https][0]}}/service-workers/service-worker/resources/import-scripts-version.py');
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/requirements.txt b/third_party/blink/web_tests/external/wpt/tools/wptrunner/requirements.txt index 766b9bd..7c134b8 100644 --- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/requirements.txt +++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/requirements.txt
@@ -5,6 +5,6 @@ # Pillow 7 requires Python 3 pillow==6.2.2; python_version <= '2.7' # pyup: <7.0 pillow==8.0.1; python_version >= '3.0' -urllib3[secure]==1.26.1 +urllib3[secure]==1.26.2 requests==2.24.0 six==1.15.0
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/navigate_to/navigate.py b/third_party/blink/web_tests/external/wpt/webdriver/tests/navigate_to/navigate.py index 289e432..b577ecd 100644 --- a/third_party/blink/web_tests/external/wpt/webdriver/tests/navigate_to/navigate.py +++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/navigate_to/navigate.py
@@ -1,3 +1,7 @@ +import time + +import pytest + from tests.support import platform_name from webdriver.transport import Response @@ -52,3 +56,23 @@ if session.url.endswith('/'): url += '/' assert session.url == url + + +@pytest.mark.capabilities({"pageLoadStrategy": "eager"}) +def test_utf8_meta_tag_after_1024_bytes(session, url): + page = url("/webdriver/tests/support/html/meta-utf8-after-1024-bytes.html") + + # Loading the page will cause a real parse commencing, and a renavigation + # to the same URL getting triggered subsequently. Test that the navigate + # command waits long enough. + response = navigate_to(session, page) + assert_success(response) + + # If the command returns too early the property will be reset due to the + # subsequent page load. + session.execute_script("window.foo = 'bar'") + + # Use delay to allow a possible missing subsequent navigation to start + time.sleep(1) + + assert session.execute_script("return window.foo") == "bar"
diff --git a/third_party/blink/web_tests/external/wpt/webdriver/tests/support/html/meta-utf8-after-1024-bytes.html b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/html/meta-utf8-after-1024-bytes.html new file mode 100644 index 0000000..b591614 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/webdriver/tests/support/html/meta-utf8-after-1024-bytes.html
@@ -0,0 +1,17 @@ +<!DOCTYPE html> +<!-- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla eu iaculis +lectus. Quisque ullamcorper est at nunc consectetur suscipit. Aliquam imperdiet +mauris in nulla ornare, id eleifend turpis placerat. Vestibulum lorem libero, +sollicitudin in orci suscipit, dictum vestibulum nulla. Ut ac est tincidunt, +cursus leo vel, pellentesque orci. Sed mattis metus augue, ac tincidunt nunc +lobortis in. Proin eu ipsum auctor lorem sagittis malesuada. Vivamus maximus, +eros fringilla vulputate tincidunt, tellus tellus viverra augue, sed iaculis +ipsum lacus quis tellus. Morbi et enim at ante molestie imperdiet et et nulla. +Aliquam consequat rhoncus magna, vitae sodales urna maximus eget. Mauris eu +laoreet turpis, eget condimentum lectus. Maecenas vel lorem vel nulla efficitur +euismod. Sed lobortis enim ac odio bibendum, id vehicula nibh tempus. Phasellus +sodales, ipsum feugiat aliquam vehicula, diam leo cursus est, nec varius nunc +felis vitae est. Curabitur ac purus nisl. Mauris condimentum, magna quis +consectetur biam. --> +<meta charset="utf-8"> +<div id="body"></div>
diff --git a/third_party/blink/web_tests/external/wpt/workers/modules/dedicated-worker-import-data-url-cross-origin.html b/third_party/blink/web_tests/external/wpt/workers/modules/dedicated-worker-import-data-url-cross-origin.html new file mode 100644 index 0000000..37390947b --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/workers/modules/dedicated-worker-import-data-url-cross-origin.html
@@ -0,0 +1,43 @@ +<!DOCTYPE html> +<title>DedicatedWorker: ES modules for data URL workers</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + +const import_from_data_url_worker_test = (importType, isDataURL, expectation) => { + promise_test(async () => { + const importURL = new URL(`resources/${importType}-import-` + + `${isDataURL ? 'data-url' : 'script'}-block-cross-origin.js`, + location.href) + '?pipe=header(Access-Control-Allow-Origin, *)'; + const dataURL = `data:text/javascript,import "${importURL}";`; + const worker = new Worker(dataURL, { type: 'module' }); + worker.postMessage('Send message for tests from main script.'); + const msgEvent = + await new Promise(resolve => worker.onmessage = resolve); + assert_array_equals(msgEvent.data, + expectation === 'blocked' ? ['ERROR'] + : ['export-block-cross-origin.js']); + }, `${importType} import ${isDataURL ? 'data url' : 'script'} from data: ` + + `URL should be ${expectation}.`); +} + +// Static import should obey the outside settings. +// SecurityOrigin of the outside settings is decided by Window. +import_from_data_url_worker_test('static', true, 'allowed'); +import_from_data_url_worker_test('static', false, 'allowed'); + + +// Dynamic import should obey the inside settings. +// SecurityOrigin of the inside settings is a unique opaque origin. +// +// Data url script is cross-origin to the inside settings' SecurityOrigin, but +// dynamic importing it is allowed. +// https://fetch.spec.whatwg.org/#concept-main-fetch +// Step 5: request’s current URL’s scheme is "data" [spec text] +import_from_data_url_worker_test('dynamic', true, 'allowed'); + +// Non-data url script is cross-origin to the inside settings' SecurityOrigin. +// It should be blocked. +import_from_data_url_worker_test('dynamic', false, 'blocked'); + +</script>
diff --git a/third_party/blink/web_tests/external/wpt/workers/modules/resources/export-on-load-script.py b/third_party/blink/web_tests/external/wpt/workers/modules/resources/export-on-load-script.py new file mode 100644 index 0000000..6cca411e --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/workers/modules/resources/export-on-load-script.py
@@ -0,0 +1,11 @@ +def main(request, response): + # This script serves both preflight and main GET request for cross-origin + # static imports from module service workers. + # According to https://w3c.github.io/ServiceWorker/#update-algorithm, + # `Service-Worker: script` request header is added, which triggers CORS + # preflight. + response_headers = [(b"Content-Type", b"text/javascript"), + (b"Access-Control-Allow-Origin", b"*"), + (b"Access-Control-Allow-Headers", b"Service-Worker")] + return (200, response_headers, + b"export const importedModules = ['export-on-load-script.js'];")
diff --git a/third_party/blink/web_tests/external/wpt/workers/modules/resources/import-test-cases.js b/third_party/blink/web_tests/external/wpt/workers/modules/resources/import-test-cases.js index c5830e9..8ad89e8f 100644 --- a/third_party/blink/web_tests/external/wpt/workers/modules/resources/import-test-cases.js +++ b/third_party/blink/web_tests/external/wpt/workers/modules/resources/import-test-cases.js
@@ -5,6 +5,16 @@ description: 'Static import.' }, { + scriptURL: '/workers/modules/resources/static-import-remote-origin-script-worker.sub.js', + expectation: ['export-on-load-script.js'], + description: 'Static import (cross-origin).' + }, + { + scriptURL: '/workers/modules/resources/static-import-redirect-worker.js', + expectation: ['export-on-load-script.js'], + description: 'Static import (redirect).' + }, + { scriptURL: '/workers/modules/resources/nested-static-import-worker.js', expectation: [ 'export-on-static-import-script.js',
diff --git a/third_party/blink/web_tests/external/wpt/workers/modules/resources/new-worker-window.html b/third_party/blink/web_tests/external/wpt/workers/modules/resources/new-worker-window.html index a7bd2ee..32a89fa 100644 --- a/third_party/blink/web_tests/external/wpt/workers/modules/resources/new-worker-window.html +++ b/third_party/blink/web_tests/external/wpt/workers/modules/resources/new-worker-window.html
@@ -8,6 +8,7 @@ // Creates a new dedicated worker for a given script url. window.onmessage = e => { worker = new Worker(e.data, { type: 'module' }); + worker.postMessage('start'); worker.onmessage = msg => window.opener.postMessage(msg.data, '*'); worker.onerror = err => { window.opener.postMessage(['ERROR'], '*');
diff --git a/third_party/blink/web_tests/external/wpt/workers/modules/resources/redirect.py b/third_party/blink/web_tests/external/wpt/workers/modules/resources/redirect.py new file mode 100644 index 0000000..396bd4c --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/workers/modules/resources/redirect.py
@@ -0,0 +1,8 @@ +def main(request, response): + """Simple handler that causes redirection. + This is placed here to stay within the same directory during redirects, + to avoid issues like https://crbug.com/1136775. + """ + response.status = 302 + location = request.GET.first(b"location") + response.headers.set(b"Location", location)
diff --git a/third_party/blink/web_tests/external/wpt/workers/modules/resources/static-import-redirect-worker.js b/third_party/blink/web_tests/external/wpt/workers/modules/resources/static-import-redirect-worker.js new file mode 100644 index 0000000..8434c1a3 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/workers/modules/resources/static-import-redirect-worker.js
@@ -0,0 +1,21 @@ +// This script is meant to be imported by a module worker. It receives a +// message from the worker and responds with the list of imported modules. +import * as module from './redirect.py?location=/workers/modules/resources/export-on-load-script.js'; +if ('DedicatedWorkerGlobalScope' in self && + self instanceof DedicatedWorkerGlobalScope) { + self.onmessage = e => { + e.target.postMessage(module.importedModules); + }; +} else if ( + 'SharedWorkerGlobalScope' in self && + self instanceof SharedWorkerGlobalScope) { + self.onconnect = e => { + e.ports[0].postMessage(module.importedModules); + }; +} else if ( + 'ServiceWorkerGlobalScope' in self && + self instanceof ServiceWorkerGlobalScope) { + self.onmessage = e => { + e.source.postMessage(module.importedModules); + }; +}
diff --git a/third_party/blink/web_tests/external/wpt/workers/modules/resources/static-import-remote-origin-script-worker.sub.js b/third_party/blink/web_tests/external/wpt/workers/modules/resources/static-import-remote-origin-script-worker.sub.js index 0221a2b8b..6432dd5 100644 --- a/third_party/blink/web_tests/external/wpt/workers/modules/resources/static-import-remote-origin-script-worker.sub.js +++ b/third_party/blink/web_tests/external/wpt/workers/modules/resources/static-import-remote-origin-script-worker.sub.js
@@ -1,12 +1,20 @@ // Import a remote origin script. -import * as module from 'https://{{domains[www1]}}:{{ports[https][0]}}/workers/modules/resources/export-on-load-script.js'; +import * as module from 'https://{{domains[www1]}}:{{ports[https][0]}}/workers/modules/resources/export-on-load-script.py'; if ('DedicatedWorkerGlobalScope' in self && self instanceof DedicatedWorkerGlobalScope) { - postMessage(module.importedModules); + self.onmessage = e => { + e.target.postMessage(module.importedModules); + }; } else if ( 'SharedWorkerGlobalScope' in self && self instanceof SharedWorkerGlobalScope) { - onconnect = e => { + self.onconnect = e => { e.ports[0].postMessage(module.importedModules); }; +} else if ( + 'ServiceWorkerGlobalScope' in self && + self instanceof ServiceWorkerGlobalScope) { + self.onmessage = e => { + e.source.postMessage(module.importedModules); + }; }
diff --git a/third_party/blink/web_tests/virtual/link-disabled-old-behavior/external/wpt/css/cssom/CSSCounterStyleRule-expected.txt b/third_party/blink/web_tests/virtual/link-disabled-old-behavior/external/wpt/css/cssom/CSSCounterStyleRule-expected.txt deleted file mode 100644 index 525f5da..0000000 --- a/third_party/blink/web_tests/virtual/link-disabled-old-behavior/external/wpt/css/cssom/CSSCounterStyleRule-expected.txt +++ /dev/null
@@ -1,4 +0,0 @@ -This is a testharness.js-based test. -FAIL CSSCounterStyleRule.cssText doesn't serialize with newlines Cannot read property 'cssText' of undefined -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/webexposed/css-properties-as-js-properties-expected.txt b/third_party/blink/web_tests/webexposed/css-properties-as-js-properties-expected.txt index a55d90f..c117a7f 100644 --- a/third_party/blink/web_tests/webexposed/css-properties-as-js-properties-expected.txt +++ b/third_party/blink/web_tests/webexposed/css-properties-as-js-properties-expected.txt
@@ -1,6 +1,7 @@ This test (crudely) documents Blink's web-exposed CSS properties. All changes to this list should go through Blink's feature review process: http://www.chromium.org/blink#new-features +additiveSymbols advanceOverride advanceProportionalOverride alignContent @@ -143,6 +144,7 @@ dominantBaseline emptyCells end +fallback fill fillOpacity fillRule @@ -257,6 +259,7 @@ minWidth minZoom mixBlendMode +negative objectFit objectPosition offset @@ -287,6 +290,7 @@ overscrollBehaviorInline overscrollBehaviorX overscrollBehaviorY +pad padding paddingBlock paddingBlockEnd @@ -312,8 +316,10 @@ placeSelf pointerEvents position +prefix quotes r +range removeProperty resize right @@ -359,6 +365,7 @@ size source speak +speakAs src start stopColor @@ -371,7 +378,10 @@ strokeMiterlimit strokeOpacity strokeWidth +suffix +symbols syntax +system tabSize tableLayout textAlign
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt index 2ceb23b..7e3ad7f 100644 --- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt +++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -601,6 +601,31 @@ attribute @@toStringTag getter conditionText method constructor +interface CSSCounterStyleRule : CSSRule + attribute @@toStringTag + getter additiveSymbols + getter fallback + getter name + getter negative + getter pad + getter prefix + getter range + getter speakAs + getter suffix + getter symbols + getter system + method constructor + setter additiveSymbols + setter fallback + setter name + setter negative + setter pad + setter prefix + setter range + setter speakAs + setter suffix + setter symbols + setter system interface CSSFontFaceRule : CSSRule attribute @@toStringTag getter style @@ -748,6 +773,7 @@ interface CSSRule attribute @@toStringTag attribute CHARSET_RULE + attribute COUNTER_STYLE_RULE attribute FONT_FACE_RULE attribute IMPORT_RULE attribute KEYFRAMES_RULE
diff --git a/third_party/leveldatabase/env_chromium.cc b/third_party/leveldatabase/env_chromium.cc index 2273630..1e1fe9cb 100644 --- a/third_party/leveldatabase/env_chromium.cc +++ b/third_party/leveldatabase/env_chromium.cc
@@ -10,7 +10,6 @@ #include "base/bind.h" #include "base/check_op.h" -#include "base/feature_list.h" #include "base/files/file_util.h" #include "base/format_macros.h" #include "base/macros.h" @@ -50,9 +49,6 @@ using leveldb::Slice; using leveldb::Status; -const base::Feature kLevelDBFileHandleEviction{ - "LevelDBFileHandleEviction", base::FEATURE_ENABLED_BY_DEFAULT}; - namespace leveldb_env { namespace { @@ -707,8 +703,7 @@ DCHECK(filesystem_); size_t max_open_files = base::GetMaxFds(); - if (base::FeatureList::IsEnabled(kLevelDBFileHandleEviction) && - max_open_files < kFileLimitToDisableEviction) { + if (max_open_files < kFileLimitToDisableEviction) { file_cache_.reset( leveldb::NewLRUCache(GetLevelDBFileLimit(max_open_files))); }
diff --git a/tools/binary_size/generate_commit_size_analysis.py b/tools/binary_size/generate_commit_size_analysis.py index 25dacde0..9090c17b 100755 --- a/tools/binary_size/generate_commit_size_analysis.py +++ b/tools/binary_size/generate_commit_size_analysis.py
@@ -91,8 +91,6 @@ # * supersize_input_file: Main input for SuperSize, and can be {.apk, # .minimal.apks, .ssargs}. If .ssargs, then the file is copied to the # staging dir. - # * version: (Unused by this script) Used by build bots to determine whether - # significant binary package restructure has occurred. # --size-config-json will replace {--apk-name, --mapping-name}. parser.add_argument('--size-config-json',
diff --git a/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java b/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java index 2dbaa08c..55065c4 100644 --- a/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java +++ b/ui/android/java/src/org/chromium/ui/base/WindowAndroid.java
@@ -978,6 +978,11 @@ window.setAttributes(params); } + @CalledByNative + private boolean applyDisableSurfaceControlWorkaround() { + return mDisplayAndroid.applyDisableSurfaceControlWorkaround(); + } + @SuppressLint("NewApi") // mSupportedRefreshRateModes should only be set if Display.Mode is available. @TargetApi(Build.VERSION_CODES.M)
diff --git a/ui/android/java/src/org/chromium/ui/display/DisplayAndroid.java b/ui/android/java/src/org/chromium/ui/display/DisplayAndroid.java index fbed317..2c11f783 100644 --- a/ui/android/java/src/org/chromium/ui/display/DisplayAndroid.java +++ b/ui/android/java/src/org/chromium/ui/display/DisplayAndroid.java
@@ -20,7 +20,7 @@ * anywhere, as long as the corresponding WindowAndroids are destroyed. The observers are * held weakly so to not lead to leaks. */ -public class DisplayAndroid { +public abstract class DisplayAndroid { /** * DisplayAndroidObserver interface for changes to this Display. */ @@ -254,6 +254,8 @@ update(null, null, null, null, null, null, isDisplayServerWideColorGamut, null, null, null); } + public abstract boolean applyDisableSurfaceControlWorkaround(); + /** * Update the display to the provided parameters. Null values leave the parameter unchanged. */
diff --git a/ui/android/java/src/org/chromium/ui/display/PhysicalDisplayAndroid.java b/ui/android/java/src/org/chromium/ui/display/PhysicalDisplayAndroid.java index 6f6f72c..4387946 100644 --- a/ui/android/java/src/org/chromium/ui/display/PhysicalDisplayAndroid.java +++ b/ui/android/java/src/org/chromium/ui/display/PhysicalDisplayAndroid.java
@@ -23,12 +23,16 @@ */ /* package */ class PhysicalDisplayAndroid extends DisplayAndroid { private static final String TAG = "DisplayAndroid"; + private static final String SAMSUNG_DEX_DISPLAY = "Desktop"; // When this object exists, a positive value means that the forced DIP scale is set and // the zero means it is not. The non existing object (i.e. null reference) means that // the existence and value of the forced DIP scale has not yet been determined. private static Float sForcedDIPScale; + // This is a workaround for crbug.com/1042581. + private final boolean mDisableSurfaceControlWorkaround; + private static boolean hasForcedDIPScale() { if (sForcedDIPScale == null) { String forcedScaleAsString = CommandLine.getInstance().getSwitchValue( @@ -123,6 +127,7 @@ /* package */ PhysicalDisplayAndroid(Display display) { super(display.getDisplayId()); + mDisableSurfaceControlWorkaround = display.getName().equals(SAMSUNG_DEX_DISPLAY); } @SuppressWarnings("deprecation") @@ -164,4 +169,9 @@ bitsPerComponent(pixelFormatId), display.getRotation(), isWideColorGamut, null, display.getRefreshRate(), currentMode, supportedModes); } + + @Override + public boolean applyDisableSurfaceControlWorkaround() { + return mDisableSurfaceControlWorkaround; + } }
diff --git a/ui/android/java/src/org/chromium/ui/display/VirtualDisplayAndroid.java b/ui/android/java/src/org/chromium/ui/display/VirtualDisplayAndroid.java index 5719ba4..46cb4fb 100644 --- a/ui/android/java/src/org/chromium/ui/display/VirtualDisplayAndroid.java +++ b/ui/android/java/src/org/chromium/ui/display/VirtualDisplayAndroid.java
@@ -57,4 +57,9 @@ public void destroy() { getManager().removeVirtualDisplay(this); } + + @Override + public boolean applyDisableSurfaceControlWorkaround() { + return false; + } }
diff --git a/ui/android/window_android.cc b/ui/android/window_android.cc index e37dbeb..b1aa28f 100644 --- a/ui/android/window_android.cc +++ b/ui/android/window_android.cc
@@ -243,6 +243,12 @@ Java_WindowAndroid_setPreferredRefreshRate(env, GetJavaObject(), 60.f); } +bool WindowAndroid::ApplyDisableSurfaceControlWorkaround() { + JNIEnv* env = AttachCurrentThread(); + return Java_WindowAndroid_applyDisableSurfaceControlWorkaround( + env, GetJavaObject()); +} + bool WindowAndroid::HasPermission(const std::string& permission) { JNIEnv* env = AttachCurrentThread(); return Java_WindowAndroid_hasPermission(
diff --git a/ui/android/window_android.h b/ui/android/window_android.h index dd82231..d6d0ac73 100644 --- a/ui/android/window_android.h +++ b/ui/android/window_android.h
@@ -106,6 +106,8 @@ void SetForce60HzRefreshRate(); + bool ApplyDisableSurfaceControlWorkaround(); + class TestHooks { public: virtual ~TestHooks() = default;
diff --git a/ui/gfx/android/android_surface_control_compat.cc b/ui/gfx/android/android_surface_control_compat.cc index 64ca780c..c22ee52 100644 --- a/ui/gfx/android/android_surface_control_compat.cc +++ b/ui/gfx/android/android_surface_control_compat.cc
@@ -305,14 +305,7 @@ // static bool SurfaceControl::IsSupported() { - const auto* build_info = base::android::BuildInfo::GetInstance(); - - // Disabled on Samsung devices due to a platform bug fixed in R. - int min_sdk_version = base::android::SDK_VERSION_Q; - if (base::EqualsCaseInsensitiveASCII(build_info->manufacturer(), "samsung")) - min_sdk_version = base::android::SDK_VERSION_R; - - if (build_info->sdk_int() < min_sdk_version) + if (!base::android::BuildInfo::GetInstance()->is_at_least_q()) return false; CHECK(SurfaceControlMethods::Get().supported);
diff --git a/ui/ozone/platform/scenic/scenic_overlay_view.cc b/ui/ozone/platform/scenic/scenic_overlay_view.cc index e865482..a970990 100644 --- a/ui/ozone/platform/scenic/scenic_overlay_view.cc +++ b/ui/ozone/platform/scenic/scenic_overlay_view.cc
@@ -8,6 +8,7 @@ #include <lib/ui/scenic/cpp/view_token_pair.h> #include "base/fuchsia/fuchsia_logging.h" +#include "ui/ozone/platform/scenic/scenic_surface_factory.h" namespace ui { @@ -28,8 +29,10 @@ } // namespace ScenicOverlayView::ScenicOverlayView( - scenic::SessionPtrAndListenerRequest session_and_listener_request) + scenic::SessionPtrAndListenerRequest session_and_listener_request, + ScenicSurfaceFactory* scenic_surface_factory) : scenic_session_(std::move(session_and_listener_request)), + scenic_surface_factory_(scenic_surface_factory), view_(&scenic_session_, CreateViewToken(&view_holder_token_), kSessionDebugName) { @@ -40,14 +43,20 @@ } ScenicOverlayView::~ScenicOverlayView() { - if (surface_) - surface_->RemoveOverlayView(buffer_collection_id_); + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + ScenicSurface* surface = scenic_surface_factory_->GetSurface(widget_); + if (surface) { + surface->AssertBelongsToCurrentThread(); + surface->RemoveOverlayView(buffer_collection_id_); + } } void ScenicOverlayView::Initialize( fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> collection_token) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + uint32_t image_pipe_id = scenic_session_.AllocResourceId(); scenic_session_.Enqueue( scenic::NewCreateImagePipe2Cmd(image_pipe_id, image_pipe_.NewRequest())); @@ -78,6 +87,8 @@ bool ScenicOverlayView::AddImages(uint32_t buffer_count, const gfx::Size& size) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + fuchsia::sysmem::ImageFormat_2 image_format = {}; image_format.coded_width = size.width(); image_format.coded_height = size.height(); @@ -91,6 +102,8 @@ bool ScenicOverlayView::PresentImage(uint32_t buffer_index, std::vector<zx::event> acquire_fences, std::vector<zx::event> release_fences) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + image_pipe_->PresentImage(buffer_index + 1, zx_clock_get_monotonic(), std::move(acquire_fences), std::move(release_fences), [](auto) {}); @@ -98,6 +111,8 @@ } void ScenicOverlayView::SetBlendMode(bool enable_blend) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + if (enable_blend_ == enable_blend) return; @@ -117,10 +132,11 @@ } bool ScenicOverlayView::AttachToScenicSurface( - ScenicSurface* surface, gfx::AcceleratedWidget widget, gfx::SysmemBufferCollectionId id) { - if (surface_ && surface_ == surface) + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + if (widget_ != gfx::kNullAcceleratedWidget && widget_ == widget) return true; if (!view_holder_token_.value.is_valid()) { @@ -128,11 +144,13 @@ return false; } - surface_ = surface; buffer_collection_id_ = id; widget_ = widget; - return surface_->PresentOverlayView(buffer_collection_id_, - std::move(view_holder_token_)); + + ScenicSurface* surface = scenic_surface_factory_->GetSurface(widget_); + DCHECK(surface); + return surface->PresentOverlayView(buffer_collection_id_, + std::move(view_holder_token_)); } } // namespace ui
diff --git a/ui/ozone/platform/scenic/scenic_overlay_view.h b/ui/ozone/platform/scenic/scenic_overlay_view.h index 00220656..c2c25b4 100644 --- a/ui/ozone/platform/scenic/scenic_overlay_view.h +++ b/ui/ozone/platform/scenic/scenic_overlay_view.h
@@ -24,7 +24,8 @@ class ScenicOverlayView { public: ScenicOverlayView( - scenic::SessionPtrAndListenerRequest session_and_listener_request); + scenic::SessionPtrAndListenerRequest session_and_listener_request, + ScenicSurfaceFactory* scenic_surface_factory); ~ScenicOverlayView(); ScenicOverlayView(const ScenicOverlayView&) = delete; ScenicOverlayView& operator=(const ScenicOverlayView&) = delete; @@ -53,21 +54,20 @@ bool CanAttachToAcceleratedWidget(gfx::AcceleratedWidget widget); // Return true if |view_holder_token_| is attached to the scene graph of - // |surface|. - bool AttachToScenicSurface(ScenicSurface* surface, - gfx::AcceleratedWidget widget, + // surface corresponding to |widget|. + bool AttachToScenicSurface(gfx::AcceleratedWidget widget, gfx::SysmemBufferCollectionId id); private: scenic::Session scenic_session_; + ScenicSurfaceFactory* const scenic_surface_factory_; fuchsia::ui::views::ViewHolderToken view_holder_token_; scenic::View view_; fuchsia::images::ImagePipe2Ptr image_pipe_; std::unique_ptr<scenic::Material> image_material_; bool enable_blend_ = false; - ScenicSurface* surface_ = nullptr; - gfx::AcceleratedWidget widget_; + gfx::AcceleratedWidget widget_ = gfx::kNullAcceleratedWidget; gfx::SysmemBufferCollectionId buffer_collection_id_; THREAD_CHECKER(thread_checker_);
diff --git a/ui/ozone/platform/scenic/scenic_surface_factory.cc b/ui/ozone/platform/scenic/scenic_surface_factory.cc index ba0b5e7a2..d1e03bb 100644 --- a/ui/ozone/platform/scenic/scenic_surface_factory.cc +++ b/ui/ozone/platform/scenic/scenic_surface_factory.cc
@@ -271,7 +271,9 @@ ScenicSurface* ScenicSurfaceFactory::GetSurface(gfx::AcceleratedWidget widget) { base::AutoLock lock(surface_lock_); auto it = surface_map_.find(widget); - DCHECK(it != surface_map_.end()); + if (it == surface_map_.end()) + return nullptr; + ScenicSurface* surface = it->second; surface->AssertBelongsToCurrentThread(); return surface;
diff --git a/ui/ozone/platform/scenic/sysmem_buffer_collection.cc b/ui/ozone/platform/scenic/sysmem_buffer_collection.cc index 629045ab..58b0ab35 100644 --- a/ui/ozone/platform/scenic/sysmem_buffer_collection.cc +++ b/ui/ozone/platform/scenic/sysmem_buffer_collection.cc
@@ -140,7 +140,8 @@ is_protected_ = force_protected; if (register_with_image_pipe) { - scenic_overlay_view_.emplace(scenic_surface_factory->CreateScenicSession()); + scenic_overlay_view_.emplace(scenic_surface_factory->CreateScenicSession(), + scenic_surface_factory); surface_factory_ = scenic_surface_factory; }
diff --git a/ui/ozone/platform/scenic/sysmem_native_pixmap.cc b/ui/ozone/platform/scenic/sysmem_native_pixmap.cc index 6059a29..10d6c0f 100644 --- a/ui/ozone/platform/scenic/sysmem_native_pixmap.cc +++ b/ui/ozone/platform/scenic/sysmem_native_pixmap.cc
@@ -106,8 +106,7 @@ DCHECK(collection_->scenic_overlay_view()); ScenicOverlayView* overlay_view = collection_->scenic_overlay_view(); const auto& buffer_collection_id = handle_.buffer_collection_id.value(); - if (!overlay_view->AttachToScenicSurface(surface, widget, - buffer_collection_id)) { + if (!overlay_view->AttachToScenicSurface(widget, buffer_collection_id)) { DLOG(ERROR) << "Failed to attach to surface."; return false; }
diff --git a/ui/ozone/public/ozone_platform.cc b/ui/ozone/public/ozone_platform.cc index 898a9c5..29fd78f 100644 --- a/ui/ozone/public/ozone_platform.cc +++ b/ui/ozone/public/ozone_platform.cc
@@ -87,7 +87,7 @@ } // static -const char* OzonePlatform::GetPlatformName() { +std::string OzonePlatform::GetPlatformNameForTest() { return GetOzonePlatformName(); }
diff --git a/ui/ozone/public/ozone_platform.h b/ui/ozone/public/ozone_platform.h index 739ffae0..7c99a4e 100644 --- a/ui/ozone/public/ozone_platform.h +++ b/ui/ozone/public/ozone_platform.h
@@ -6,6 +6,7 @@ #define UI_OZONE_PUBLIC_OZONE_PLATFORM_H_ #include <memory> +#include <string> #include <vector> #include "base/callback.h" @@ -168,12 +169,8 @@ static OzonePlatform* GetInstance(); // Returns the current ozone platform name. - // TODO(crbug.com/1002674): This is temporary and meant to make it possible - // for higher level components to take run-time actions depending on the - // current ozone platform selected. Which implies in layering violations, - // which are tolerated during the X11 migration to Ozone and must be fixed - // once it is done. - static const char* GetPlatformName(); + // Some tests may skip based on the platform name. + static std::string GetPlatformNameForTest(); // Factory getters to override in subclasses. The returned objects will be // injected into the appropriate layer at startup. Subclasses should not
diff --git a/ui/views/bubble/bubble_dialog_delegate_view.cc b/ui/views/bubble/bubble_dialog_delegate_view.cc index 7c4fb6a5..50b1313a 100644 --- a/ui/views/bubble/bubble_dialog_delegate_view.cc +++ b/ui/views/bubble/bubble_dialog_delegate_view.cc
@@ -409,17 +409,6 @@ return client_view_; } -bool BubbleDialogDelegateView::AcceleratorPressed( - const ui::Accelerator& accelerator) { - if (accelerator.key_code() == ui::VKEY_DOWN || - accelerator.key_code() == ui::VKEY_UP) { - // Move the focus up or down. - GetFocusManager()->AdvanceFocus(accelerator.key_code() != ui::VKEY_DOWN); - return true; - } - return View::AcceleratorPressed(accelerator); -} - Widget* BubbleDialogDelegateView::GetWidget() { return View::GetWidget(); }
diff --git a/ui/views/bubble/bubble_dialog_delegate_view.h b/ui/views/bubble/bubble_dialog_delegate_view.h index b588fdb..c88970286 100644 --- a/ui/views/bubble/bubble_dialog_delegate_view.h +++ b/ui/views/bubble/bubble_dialog_delegate_view.h
@@ -12,7 +12,6 @@ #include "base/scoped_observer.h" #include "build/build_config.h" #include "ui/accessibility/ax_enums.mojom-forward.h" -#include "ui/base/accelerators/accelerator.h" #include "ui/base/class_property.h" #include "ui/views/bubble/bubble_border.h" #include "ui/views/bubble/bubble_frame_view.h" @@ -31,10 +30,6 @@ class Rect; } -namespace ui { -class Accelerator; -} // namespace ui - namespace ui_devtools { class PageAgentViews; } @@ -411,7 +406,6 @@ Widget* GetWidget() override; const Widget* GetWidget() const override; void AddedToWidget() override; - bool AcceleratorPressed(const ui::Accelerator& accelerator) override; protected: // Disallow overrides of GetMinimumSize and GetMaximumSize(). These would only
diff --git a/ui/views/controls/menu/menu_controller_unittest.cc b/ui/views/controls/menu/menu_controller_unittest.cc index c58a5701..1869e85 100644 --- a/ui/views/controls/menu/menu_controller_unittest.cc +++ b/ui/views/controls/menu/menu_controller_unittest.cc
@@ -892,7 +892,7 @@ TEST_F(MenuControllerTest, TouchIdsReleasedCorrectly) { // Run this test only for X11 (either Ozone or non-Ozone). if (features::IsUsingOzonePlatform() && - std::strcmp(ui::OzonePlatform::GetPlatformName(), "x11") != 0) { + ui::OzonePlatform::GetPlatformNameForTest() != "x11") { GTEST_SKIP(); }
diff --git a/ui/views/views_test_suite.h b/ui/views/views_test_suite.h index c9ab3d6..c9b048b 100644 --- a/ui/views/views_test_suite.h +++ b/ui/views/views_test_suite.h
@@ -17,6 +17,11 @@ } #endif +#if defined(USE_OZONE) +#include "ui/base/ui_base_features.h" +#include "ui/ozone/public/ozone_platform.h" +#endif + namespace views { class ViewsTestSuite : public base::TestSuite { @@ -49,6 +54,17 @@ DISALLOW_COPY_AND_ASSIGN(ViewsTestSuite); }; +#if defined(USE_OZONE) +// Skips the X11-specific test on Ozone if the current platform is not X11. +#define SKIP_TEST_IF_NOT_OZONE_X11() \ + if (features::IsUsingOzonePlatform() && \ + ui::OzonePlatform::GetPlatformNameForTest() != "x11") { \ + GTEST_SKIP() << "This test is X11-only"; \ + } +#else +#define SKIP_TEST_IF_NOT_OZONE_X11() +#endif + } // namespace views #endif // UI_VIEWS_VIEWS_TEST_SUITE_H_
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_linux_interactive_uitest.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_linux_interactive_uitest.cc index ee8f4254..26f68abe 100644 --- a/ui/views/widget/desktop_aura/desktop_window_tree_host_linux_interactive_uitest.cc +++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_linux_interactive_uitest.cc
@@ -510,9 +510,6 @@ // Chrome even if it not possible to deactivate the window wrt to the x server. // This behavior is required by several interactive_ui_tests. TEST_F(DesktopWindowTreeHostLinuxTest, Deactivate) { - // TODO(1109112): enable this test. - if (features::IsUsingOzonePlatform()) - GTEST_SKIP(); std::unique_ptr<Widget> widget(CreateWidget(gfx::Rect(100, 100, 100, 100))); ActivationWaiter waiter(static_cast<x11::Window>( @@ -538,9 +535,6 @@ // Chrome synchronously switches the window that mouse events are forwarded to // when capture is changed. TEST_F(DesktopWindowTreeHostLinuxTest, CaptureEventForwarding) { - // TODO(1109112): enable this test. - if (features::IsUsingOzonePlatform()) - GTEST_SKIP(); std::unique_ptr<Widget> widget1(CreateWidget(gfx::Rect(100, 100, 100, 100))); aura::Window* window1 = widget1->GetNativeWindow(); DesktopWindowTreeHostLinux* host1 = @@ -615,9 +609,6 @@ } TEST_F(DesktopWindowTreeHostLinuxTest, InputMethodFocus) { - // TODO(1109112): enable this test. - if (features::IsUsingOzonePlatform()) - GTEST_SKIP(); std::unique_ptr<Widget> widget(CreateWidget(gfx::Rect(100, 100, 100, 100))); // Waiter should be created as early as possible so that PropertyNotify has
diff --git a/ui/views/widget/desktop_aura/x11_topmost_window_finder_interactive_uitest.cc b/ui/views/widget/desktop_aura/x11_topmost_window_finder_interactive_uitest.cc index 12f95a3..93ecbdf 100644 --- a/ui/views/widget/desktop_aura/x11_topmost_window_finder_interactive_uitest.cc +++ b/ui/views/widget/desktop_aura/x11_topmost_window_finder_interactive_uitest.cc
@@ -114,8 +114,7 @@ #if defined(USE_OZONE) // Run tests only for X11 (ozone or not Ozone). if (features::IsUsingOzonePlatform() && - std::strcmp(ui::OzonePlatform::GetInstance()->GetPlatformName(), - "x11") != 0) { + ui::OzonePlatform::GetPlatformNameForTest() != "x11") { // SetUp still is required to be run. Otherwise, ViewsTestBase CHECKs in // the dtor. DesktopWidgetTestInteractive::SetUp();
diff --git a/ui/views/widget/widget_unittest.cc b/ui/views/widget/widget_unittest.cc index e0dbbb6..69f0fbe 100644 --- a/ui/views/widget/widget_unittest.cc +++ b/ui/views/widget/widget_unittest.cc
@@ -34,6 +34,7 @@ #include "ui/views/test/test_widget_observer.h" #include "ui/views/test/widget_test.h" #include "ui/views/view_test_api.h" +#include "ui/views/views_test_suite.h" #include "ui/views/widget/native_widget_delegate.h" #include "ui/views/widget/native_widget_private.h" #include "ui/views/widget/root_view.h" @@ -1219,10 +1220,8 @@ if (base::mac::IsOS10_10()) return; // Fails when swarmed. http://crbug.com/660582 #endif -#if defined(USE_X11) - if (features::IsUsingOzonePlatform()) - return; // TODO(https://crbug.com/1109112): Will be enabled later. -#endif + + SKIP_TEST_IF_NOT_OZONE_X11(); WidgetAutoclosePtr widget; widget.reset(CreateTopLevelNativeWidget());
diff --git a/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html b/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html index 9bf7aa5..2e14f41d 100644 --- a/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html +++ b/ui/webui/resources/cr_elements/cr_dialog/cr_dialog.html
@@ -19,6 +19,8 @@ box-shadow: 0 0 16px rgba(0, 0, 0, 0.12), 0 16px 16px rgba(0, 0, 0, 0.24); color: inherit; + max-height: initial; + max-width: initial; overflow-y: hidden; padding: 0; position: absolute;