diff --git a/BUILD.gn b/BUILD.gn index 97ce0d2..6b0f6c18 100644 --- a/BUILD.gn +++ b/BUILD.gn
@@ -751,7 +751,7 @@ if (is_win || is_linux) { deps += [ "//mash:all", - "//media/mojo/services:media_mojo_shell_unittests", + "//media/mojo/services:media_service_unittests", "//mojo", "//services/navigation", "//services/preferences:tests",
diff --git a/DEPS b/DEPS index c1da9cc6..51f9df8 100644 --- a/DEPS +++ b/DEPS
@@ -40,7 +40,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling Skia # and whatever else without interference from each other. - 'skia_revision': '287f6512f34d456b593ea030197925dfc5b15c65', + 'skia_revision': '45565b676c86d6b4955b8643236880b016772e95', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. @@ -96,7 +96,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling catapult # and whatever else without interference from each other. - 'catapult_revision': '355ca2541b19167eea565d3cad751c3f8c26db51', + 'catapult_revision': '287f4bd6afc9b828782d5910b9a8b05c7b698ab9', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libFuzzer # and whatever else without interference from each other. @@ -188,7 +188,7 @@ Var('chromium_git') + '/external/bidichecker/lib.git' + '@' + '97f2aa645b74c28c57eca56992235c79850fa9e0', 'src/third_party/webgl/src': - Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '3e235f7e8495092ac4ad11eec8f226c7accc9a33', + Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'f554de25329a3d63754a32ae023854b8c1ad094c', 'src/third_party/webdriver/pylib': Var('chromium_git') + '/external/selenium/py.git' + '@' + '5fd78261a75fe08d27ca4835fb6c5ce4b42275bd',
diff --git a/ash/common/system/tray/tray_details_view.cc b/ash/common/system/tray/tray_details_view.cc index 5ee4570..d48c0e36 100644 --- a/ash/common/system/tray/tray_details_view.cc +++ b/ash/common/system/tray/tray_details_view.cc
@@ -230,7 +230,7 @@ kSeparatorColor); paint.setLooper(gfx::CreateShadowDrawLooperCorrectBlur(shadow)); paint.setAntiAlias(true); - canvas->ClipRect(shadowed_area, SkRegion::kDifference_Op); + canvas->ClipRect(shadowed_area, kDifference_SkClipOp); canvas->DrawRect(shadowed_area, paint); }
diff --git a/ash/display/display_manager_unittest.cc b/ash/display/display_manager_unittest.cc index 7616d881..31de493e 100644 --- a/ash/display/display_manager_unittest.cc +++ b/ash/display/display_manager_unittest.cc
@@ -628,6 +628,81 @@ EXPECT_EQ(display2_id, changed()[0].id()); } +#if defined(OS_CHROMEOS) +TEST_P(DisplayManagerTest, TouchCalibrationTest) { + if (!SupportsMultipleDisplays()) + return; + + UpdateDisplay("0+0-500x500,0+501-1024x600"); + reset(); + + ASSERT_EQ(2u, display_manager()->GetNumDisplays()); + const display::ManagedDisplayInfo display_info1 = GetDisplayInfoAt(0); + const display::ManagedDisplayInfo display_info2 = GetDisplayInfoAt(1); + + EXPECT_FALSE(display_info2.has_touch_calibration_data()); + + display::TouchCalibrationData::CalibrationPointPairQuad point_pair_quad = { + {std::make_pair(gfx::Point(50, 50), gfx::Point(43, 51)), + std::make_pair(gfx::Point(950, 50), gfx::Point(975, 45)), + std::make_pair(gfx::Point(50, 550), gfx::Point(48, 534)), + std::make_pair(gfx::Point(950, 550), gfx::Point(967, 574))}}; + gfx::Size bounds_at_calibration(display_info2.size_in_pixel()); + const display::TouchCalibrationData touch_data(point_pair_quad, + bounds_at_calibration); + + // Set the touch calibration data for the secondary display. + display_manager()->SetTouchCalibrationData( + display_info2.id(), point_pair_quad, bounds_at_calibration); + + display::ManagedDisplayInfo updated_display_info2 = GetDisplayInfoAt(1); + EXPECT_TRUE(updated_display_info2.has_touch_calibration_data()); + EXPECT_EQ(touch_data, updated_display_info2.GetTouchCalibrationData()); + + // Clearing touch calibration data from the secondary display. + display_manager()->ClearTouchCalibrationData(display_info2.id()); + updated_display_info2 = GetDisplayInfoAt(1); + EXPECT_FALSE(updated_display_info2.has_touch_calibration_data()); + + // Make sure that SetTouchCalibrationData() is idempotent. + display::TouchCalibrationData::CalibrationPointPairQuad point_pair_quad_2 = + point_pair_quad; + point_pair_quad_2[1] = + std::make_pair(gfx::Point(950, 50), gfx::Point(975, 53)); + display::TouchCalibrationData touch_data_2(point_pair_quad_2, + bounds_at_calibration); + display_manager()->SetTouchCalibrationData( + display_info2.id(), point_pair_quad_2, bounds_at_calibration); + + updated_display_info2 = GetDisplayInfoAt(1); + EXPECT_TRUE(updated_display_info2.has_touch_calibration_data()); + EXPECT_EQ(touch_data_2, updated_display_info2.GetTouchCalibrationData()); + + display_manager()->SetTouchCalibrationData( + display_info2.id(), point_pair_quad, bounds_at_calibration); + EXPECT_TRUE(updated_display_info2.has_touch_calibration_data()); + EXPECT_EQ(touch_data_2, updated_display_info2.GetTouchCalibrationData()); + + // Recreate a new 2nd display. It won't apply the touhc calibration data + // because the new display has a different ID. + UpdateDisplay("0+0-500x500"); + UpdateDisplay("0+0-500x500,0+501-400x400"); + EXPECT_FALSE(GetDisplayInfoAt(1).has_touch_calibration_data()); + + // Recreate the displays with the same ID. It should apply the touch + // calibration associated data. + UpdateDisplay("0+0-500x500"); + std::vector<display::ManagedDisplayInfo> display_info_list; + display_info_list.push_back(display_info1); + display_info_list.push_back(display_info2); + display_manager()->OnNativeDisplaysChanged(display_info_list); + updated_display_info2 = GetDisplayInfoAt(1); + + EXPECT_FALSE(updated_display_info2.has_touch_calibration_data()); + EXPECT_EQ(touch_data, updated_display_info2.GetTouchCalibrationData()); +} +#endif // defined(OS_CHROMEOS) + #if !defined(OS_WIN) // Disabled on windows because of http://crbug.com/650326. TEST_P(DisplayManagerTest, TestDeviceScaleOnlyChange) { @@ -1471,7 +1546,7 @@ const gfx::Insets dummy_overscan_insets; display_manager()->RegisterDisplayProperty( display_id, display::Display::ROTATE_0, 1.0f, &dummy_overscan_insets, - gfx::Size(), 1.0f, ui::ColorCalibrationProfile()); + gfx::Size(), 1.0f, ui::ColorCalibrationProfile(), nullptr); // Setup the display modes with UI-scale. display::ManagedDisplayInfo native_display_info = @@ -2429,9 +2504,9 @@ TEST_P(DisplayManagerTest, CheckInitializationOfRotationProperty) { int64_t id = display_manager()->GetDisplayAt(0).id(); - display_manager()->RegisterDisplayProperty(id, display::Display::ROTATE_90, - 1.0f, nullptr, gfx::Size(), 1.0f, - ui::COLOR_PROFILE_STANDARD); + display_manager()->RegisterDisplayProperty( + id, display::Display::ROTATE_90, 1.0f, nullptr, gfx::Size(), 1.0f, + ui::COLOR_PROFILE_STANDARD, nullptr); const display::ManagedDisplayInfo& info = display_manager()->GetDisplayInfo(id);
diff --git a/ash/system/chromeos/power/tablet_power_button_controller.cc b/ash/system/chromeos/power/tablet_power_button_controller.cc index 66841d16..f66247e 100644 --- a/ash/system/chromeos/power/tablet_power_button_controller.cc +++ b/ash/system/chromeos/power/tablet_power_button_controller.cc
@@ -68,6 +68,7 @@ LockStateController* controller) : tick_clock_(new base::DefaultTickClock()), last_resume_time_(base::TimeTicks()), + force_off_on_button_up_(true), controller_(controller), weak_ptr_factory_(this) { chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver( @@ -92,36 +93,28 @@ void TabletPowerButtonController::OnPowerButtonEvent( bool down, const base::TimeTicks& timestamp) { - // When the system resumes in response to the power button being pressed, - // Chrome receives powerd's SuspendDone signal and notification that the - // backlight has been turned back on before seeing the power button events - // that woke the system. Ignore events just after resuming to ensure that we - // don't turn the screen off in response to the events. - // - // TODO(warx): pressing power button should also StartShutdownTimer() in this - // case. Reorganize the code to support that. - if (timestamp - last_resume_time_ <= - base::TimeDelta::FromMilliseconds(kIgnorePowerButtonAfterResumeMs)) { - // If backlights are forced off, stop forcing off because resuming system - // doesn't handle this. - if (down && backlights_forced_off_) - SetDisplayForcedOff(false); - return; - } - if (down) { + force_off_on_button_up_ = true; + // When the system resumes in response to the power button being pressed, + // Chrome receives powerd's SuspendDone signal and notification that the + // backlight has been turned back on before seeing the power button events + // that woke the system. Avoid forcing off display just after resuming to + // ensure that we don't turn the display off in response to the events. + if (timestamp - last_resume_time_ <= + base::TimeDelta::FromMilliseconds(kIgnorePowerButtonAfterResumeMs)) { + force_off_on_button_up_ = false; + } screen_off_when_power_button_down_ = brightness_level_is_zero_; SetDisplayForcedOff(false); StartShutdownTimer(); } else { if (shutdown_timer_.IsRunning()) { shutdown_timer_.Stop(); - if (!screen_off_when_power_button_down_) { + if (!screen_off_when_power_button_down_ && force_off_on_button_up_) { SetDisplayForcedOff(true); LockScreenIfRequired(); } } - screen_off_when_power_button_down_ = false; // When power button is released, cancel shutdown animation whenever it is // still cancellable.
diff --git a/ash/system/chromeos/power/tablet_power_button_controller.h b/ash/system/chromeos/power/tablet_power_button_controller.h index f59749d..59c606d 100644 --- a/ash/system/chromeos/power/tablet_power_button_controller.h +++ b/ash/system/chromeos/power/tablet_power_button_controller.h
@@ -111,6 +111,9 @@ // updated in SuspendDone(). base::TimeTicks last_resume_time_; + // True if power button released should force off display. + bool force_off_on_button_up_; + // Started when the tablet power button is pressed and stopped when it's // released. Runs OnShutdownTimeout() to start shutdown. base::OneShotTimer shutdown_timer_;
diff --git a/ash/system/chromeos/power/tablet_power_button_controller_unittest.cc b/ash/system/chromeos/power/tablet_power_button_controller_unittest.cc index 4acd482..8e087229 100644 --- a/ash/system/chromeos/power/tablet_power_button_controller_unittest.cc +++ b/ash/system/chromeos/power/tablet_power_button_controller_unittest.cc
@@ -228,19 +228,23 @@ power_manager_client_->SendSuspendDone(); power_manager_client_->SendBrightnessChanged(kNonZeroBrightness, false); - // Send the power button event after a short delay and check that it is - // ignored. + // Send the power button event after a short delay and check that backlights + // are not forced off. tick_clock_->Advance(base::TimeDelta::FromMilliseconds(500)); power_manager_client_->SendPowerButtonEvent(true, tick_clock_->NowTicks()); + EXPECT_TRUE(test_api_->ShutdownTimerIsRunning()); power_manager_client_->SendPowerButtonEvent(false, tick_clock_->NowTicks()); + EXPECT_FALSE(test_api_->ShutdownTimerIsRunning()); EXPECT_FALSE(GetBacklightsForcedOff()); - // Send the power button event after a longer delay and check that it is not - // ignored. + // Send the power button event after a longer delay and check that backlights + // are forced off. tick_clock_->Advance(base::TimeDelta::FromMilliseconds(1600)); power_manager_client_->SendPowerButtonEvent(true, tick_clock_->NowTicks()); + EXPECT_TRUE(test_api_->ShutdownTimerIsRunning()); power_manager_client_->SendPowerButtonEvent(false, tick_clock_->NowTicks()); power_manager_client_->SendBrightnessChanged(0, false); + EXPECT_FALSE(test_api_->ShutdownTimerIsRunning()); EXPECT_TRUE(GetBacklightsForcedOff()); } @@ -258,20 +262,24 @@ // brightness. power_manager_client_->SendSuspendDone(); - // Send the power button event after a short delay and check that it is - // ignored. But if backlights are forced off, stop forcing off. + // Send the power button event after a short delay and check that backlights + // are not forced off. tick_clock_->Advance(base::TimeDelta::FromMilliseconds(500)); power_manager_client_->SendPowerButtonEvent(true, tick_clock_->NowTicks()); power_manager_client_->SendBrightnessChanged(kNonZeroBrightness, false); + EXPECT_TRUE(test_api_->ShutdownTimerIsRunning()); power_manager_client_->SendPowerButtonEvent(false, tick_clock_->NowTicks()); + EXPECT_FALSE(test_api_->ShutdownTimerIsRunning()); EXPECT_FALSE(GetBacklightsForcedOff()); - // Send the power button event after a longer delay and check that it is not - // ignored. + // Send the power button event after a longer delay and check that backlights + // are forced off. tick_clock_->Advance(base::TimeDelta::FromMilliseconds(1600)); power_manager_client_->SendPowerButtonEvent(true, tick_clock_->NowTicks()); + EXPECT_TRUE(test_api_->ShutdownTimerIsRunning()); power_manager_client_->SendPowerButtonEvent(false, tick_clock_->NowTicks()); power_manager_client_->SendBrightnessChanged(0, false); + EXPECT_FALSE(test_api_->ShutdownTimerIsRunning()); EXPECT_TRUE(GetBacklightsForcedOff()); }
diff --git a/cc/blink/web_display_item_list_impl.cc b/cc/blink/web_display_item_list_impl.cc index b8cca0cd5..b94d1464 100644 --- a/cc/blink/web_display_item_list_impl.cc +++ b/cc/blink/web_display_item_list_impl.cc
@@ -70,10 +70,9 @@ display_item_list_->CreateAndAppendPairedEndItem<cc::EndClipDisplayItem>(); } -void WebDisplayItemListImpl::appendClipPathItem( - const SkPath& clip_path, - SkRegion::Op clip_op, - bool antialias) { +void WebDisplayItemListImpl::appendClipPathItem(const SkPath& clip_path, + SkClipOp clip_op, + bool antialias) { display_item_list_->CreateAndAppendPairedBeginItem<cc::ClipPathDisplayItem>( clip_path, clip_op, antialias); }
diff --git a/cc/blink/web_display_item_list_impl.h b/cc/blink/web_display_item_list_impl.h index a72d2bc8..9ef08b0e9 100644 --- a/cc/blink/web_display_item_list_impl.h +++ b/cc/blink/web_display_item_list_impl.h
@@ -12,7 +12,7 @@ #include "third_party/WebKit/public/platform/WebDisplayItemList.h" #include "third_party/WebKit/public/platform/WebVector.h" #include "third_party/skia/include/core/SkBlendMode.h" -#include "third_party/skia/include/core/SkRegion.h" +#include "third_party/skia/include/core/SkClipOp.h" #include "ui/gfx/geometry/point_f.h" class SkColorFilter; @@ -48,7 +48,7 @@ const blink::WebVector<SkRRect>& rounded_clip_rects) override; void appendEndClipItem() override; void appendClipPathItem(const SkPath& clip_path, - SkRegion::Op clip_op, + SkClipOp clip_op, bool antialias) override; void appendEndClipPathItem() override; void appendFloatClipItem(const blink::WebFloatRect& clip_rect) override;
diff --git a/cc/ipc/BUILD.gn b/cc/ipc/BUILD.gn index 01c83e3c..fab163da 100644 --- a/cc/ipc/BUILD.gn +++ b/cc/ipc/BUILD.gn
@@ -54,6 +54,7 @@ "selection.mojom", "shared_quad_state.mojom", "surface_id.mojom", + "surface_reference.mojom", "surface_sequence.mojom", "transferable_resource.mojom", ] @@ -111,6 +112,7 @@ "selection_struct_traits.h", "shared_quad_state_struct_traits.h", "surface_id_struct_traits.h", + "surface_reference_struct_traits.h", "surface_sequence_struct_traits.h", "transferable_resource_struct_traits.cc", "transferable_resource_struct_traits.h",
diff --git a/cc/ipc/local_frame_id_struct_traits.h b/cc/ipc/local_frame_id_struct_traits.h index 514bd4c..4dd01d6 100644 --- a/cc/ipc/local_frame_id_struct_traits.h +++ b/cc/ipc/local_frame_id_struct_traits.h
@@ -24,12 +24,8 @@ static bool Read(cc::mojom::LocalFrameIdDataView data, cc::LocalFrameId* out) { - base::UnguessableToken nonce; - if (!data.ReadNonce(&nonce)) - return false; - - *out = cc::LocalFrameId(data.local_id(), nonce); - return true; + out->local_id_ = data.local_id(); + return data.ReadNonce(&out->nonce_); } };
diff --git a/cc/ipc/struct_traits_unittest.cc b/cc/ipc/struct_traits_unittest.cc index a952b11..b7fa588 100644 --- a/cc/ipc/struct_traits_unittest.cc +++ b/cc/ipc/struct_traits_unittest.cc
@@ -95,6 +95,12 @@ callback.Run(s); } + void EchoSurfaceReference( + const SurfaceReference& s, + const EchoSurfaceReferenceCallback& callback) override { + callback.Run(s); + } + void EchoSurfaceSequence( const SurfaceSequence& s, const EchoSurfaceSequenceCallback& callback) override { @@ -779,6 +785,22 @@ EXPECT_EQ(local_frame_id, output.local_frame_id()); } +TEST_F(StructTraitsTest, SurfaceReference) { + const SurfaceId parent_id( + FrameSinkId(2016, 1234), + LocalFrameId(0xfbadbeef, base::UnguessableToken::Deserialize(123, 456))); + const SurfaceId child_id( + FrameSinkId(1111, 9999), + LocalFrameId(0xabcdabcd, base::UnguessableToken::Deserialize(333, 333))); + const SurfaceReference input(parent_id, child_id); + + mojom::TraitsTestServicePtr proxy = GetTraitsTestProxy(); + SurfaceReference output; + proxy->EchoSurfaceReference(input, &output); + EXPECT_EQ(parent_id, output.parent_id()); + EXPECT_EQ(child_id, output.child_id()); +} + TEST_F(StructTraitsTest, SurfaceSequence) { const FrameSinkId frame_sink_id(2016, 1234); const uint32_t sequence = 0xfbadbeef;
diff --git a/cc/ipc/surface_id_struct_traits.h b/cc/ipc/surface_id_struct_traits.h index 6f66c9f..6a9bee3 100644 --- a/cc/ipc/surface_id_struct_traits.h +++ b/cc/ipc/surface_id_struct_traits.h
@@ -9,6 +9,7 @@ #include "cc/ipc/local_frame_id_struct_traits.h" #include "cc/ipc/surface_id.mojom-shared.h" #include "cc/surfaces/frame_sink_id.h" +#include "cc/surfaces/local_frame_id.h" #include "cc/surfaces/surface_id.h" namespace mojo { @@ -24,16 +25,8 @@ } static bool Read(cc::mojom::SurfaceIdDataView data, cc::SurfaceId* out) { - cc::FrameSinkId frame_sink_id; - if (!data.ReadFrameSinkId(&frame_sink_id)) - return false; - - cc::LocalFrameId local_frame_id; - if (!data.ReadLocalFrameId(&local_frame_id)) - return false; - - *out = cc::SurfaceId(frame_sink_id, local_frame_id); - return true; + return data.ReadFrameSinkId(&out->frame_sink_id_) && + data.ReadLocalFrameId(&out->local_frame_id_); } };
diff --git a/cc/ipc/surface_reference.mojom b/cc/ipc/surface_reference.mojom new file mode 100644 index 0000000..ecdd7ed --- /dev/null +++ b/cc/ipc/surface_reference.mojom
@@ -0,0 +1,12 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module cc.mojom; + +import "cc/ipc/surface_id.mojom"; + +struct SurfaceReference { + SurfaceId parent_id; + SurfaceId child_id; +};
diff --git a/cc/ipc/surface_reference.typemap b/cc/ipc/surface_reference.typemap new file mode 100644 index 0000000..cb35490 --- /dev/null +++ b/cc/ipc/surface_reference.typemap
@@ -0,0 +1,11 @@ +# Copyright 2016 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. + +mojom = "//cc/ipc/surface_reference.mojom" +public_headers = [ "//cc/surfaces/surface_reference.h" ] +traits_headers = [ "//cc/ipc/surface_reference_struct_traits.h" ] +deps = [ + "//cc/ipc:struct_traits", +] +type_mappings = [ "cc.mojom.SurfaceReference=cc::SurfaceReference" ]
diff --git a/cc/ipc/surface_reference_struct_traits.h b/cc/ipc/surface_reference_struct_traits.h new file mode 100644 index 0000000..209f1b3 --- /dev/null +++ b/cc/ipc/surface_reference_struct_traits.h
@@ -0,0 +1,34 @@ +// Copyright 2016 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 CC_IPC_SURFACE_REFERENCE_STRUCT_TRAITS_H_ +#define CC_IPC_SURFACE_REFERENCE_STRUCT_TRAITS_H_ + +#include "cc/ipc/surface_id_struct_traits.h" +#include "cc/ipc/surface_reference.mojom-shared.h" +#include "cc/surfaces/surface_id.h" +#include "cc/surfaces/surface_reference.h" + +namespace mojo { + +template <> +struct StructTraits<cc::mojom::SurfaceReferenceDataView, cc::SurfaceReference> { + static const cc::SurfaceId& parent_id(const cc::SurfaceReference& ref) { + return ref.parent_id(); + } + + static const cc::SurfaceId& child_id(const cc::SurfaceReference& ref) { + return ref.child_id(); + } + + static bool Read(cc::mojom::SurfaceReferenceDataView data, + cc::SurfaceReference* out) { + return data.ReadParentId(&out->parent_id_) && + data.ReadChildId(&out->child_id_); + } +}; + +} // namespace mojo + +#endif // CC_IPC_SURFACE_REFERENCE_STRUCT_TRAITS_H_
diff --git a/cc/ipc/traits_test_service.mojom b/cc/ipc/traits_test_service.mojom index b94ce2f..7948f121 100644 --- a/cc/ipc/traits_test_service.mojom +++ b/cc/ipc/traits_test_service.mojom
@@ -16,6 +16,7 @@ import "cc/ipc/selection.mojom"; import "cc/ipc/shared_quad_state.mojom"; import "cc/ipc/surface_id.mojom"; +import "cc/ipc/surface_reference.mojom"; import "cc/ipc/surface_sequence.mojom"; import "cc/ipc/transferable_resource.mojom"; @@ -57,6 +58,9 @@ EchoSurfaceId(SurfaceId s) => (SurfaceId pass); [Sync] + EchoSurfaceReference(SurfaceReference r) => (SurfaceReference pass); + + [Sync] EchoSurfaceSequence(SurfaceSequence s) => (SurfaceSequence pass); [Sync]
diff --git a/cc/ipc/typemaps.gni b/cc/ipc/typemaps.gni index 7e3e36e..d64c203 100644 --- a/cc/ipc/typemaps.gni +++ b/cc/ipc/typemaps.gni
@@ -16,6 +16,7 @@ "//cc/ipc/selection.typemap", "//cc/ipc/shared_quad_state.typemap", "//cc/ipc/surface_id.typemap", + "//cc/ipc/surface_reference.typemap", "//cc/ipc/surface_sequence.typemap", "//cc/ipc/transferable_resource.typemap", ]
diff --git a/cc/output/software_renderer.cc b/cc/output/software_renderer.cc index 72ce0df..c18e4de 100644 --- a/cc/output/software_renderer.cc +++ b/cc/output/software_renderer.cc
@@ -145,7 +145,7 @@ // Skia applies the current matrix to clip rects so we reset it temporary. SkMatrix current_matrix = current_canvas_->getTotalMatrix(); current_canvas_->resetMatrix(); - current_canvas_->clipRect(gfx::RectToSkRect(rect), SkRegion::kReplace_Op); + current_canvas_->clipRect(gfx::RectToSkRect(rect), kReplace_SkClipOp); current_canvas_->setMatrix(current_matrix); } @@ -257,8 +257,7 @@ QuadFToSkPoints(local_draw_region, clip_points); draw_region_clip_path.addPoly(clip_points, 4, true); - current_canvas_->clipPath(draw_region_clip_path, SkRegion::kIntersect_Op, - false); + current_canvas_->clipPath(draw_region_clip_path); } switch (quad->material) {
diff --git a/cc/playback/clip_display_item.cc b/cc/playback/clip_display_item.cc index 906706a..c35eb22e 100644 --- a/cc/playback/clip_display_item.cc +++ b/cc/playback/clip_display_item.cc
@@ -65,15 +65,12 @@ void ClipDisplayItem::Raster(SkCanvas* canvas, SkPicture::AbortCallback* callback) const { canvas->save(); - canvas->clipRect(gfx::RectToSkRect(clip_rect_), SkRegion::kIntersect_Op, - antialias_); + canvas->clipRect(gfx::RectToSkRect(clip_rect_), antialias_); for (size_t i = 0; i < rounded_clip_rects_.size(); ++i) { if (rounded_clip_rects_[i].isRect()) { - canvas->clipRect(rounded_clip_rects_[i].rect(), SkRegion::kIntersect_Op, - antialias_); + canvas->clipRect(rounded_clip_rects_[i].rect(), antialias_); } else { - canvas->clipRRect(rounded_clip_rects_[i], SkRegion::kIntersect_Op, - antialias_); + canvas->clipRRect(rounded_clip_rects_[i], antialias_); } } }
diff --git a/cc/playback/clip_path_display_item.cc b/cc/playback/clip_path_display_item.cc index 64f3f957..e0542b220 100644 --- a/cc/playback/clip_path_display_item.cc +++ b/cc/playback/clip_path_display_item.cc
@@ -16,7 +16,7 @@ namespace cc { ClipPathDisplayItem::ClipPathDisplayItem(const SkPath& clip_path, - SkRegion::Op clip_op, + SkClipOp clip_op, bool antialias) { SetNew(clip_path, clip_op, antialias); } @@ -25,7 +25,7 @@ DCHECK_EQ(proto::DisplayItem::Type_ClipPath, proto.type()); const proto::ClipPathDisplayItem& details = proto.clip_path_item(); - SkRegion::Op clip_op = SkRegionOpFromProto(details.clip_op()); + SkClipOp clip_op = SkClipOpFromProto(details.clip_op()); bool antialias = details.antialias(); SkPath clip_path; @@ -42,7 +42,7 @@ } void ClipPathDisplayItem::SetNew(const SkPath& clip_path, - SkRegion::Op clip_op, + SkClipOp clip_op, bool antialias) { clip_path_ = clip_path; clip_op_ = clip_op; @@ -53,7 +53,7 @@ proto->set_type(proto::DisplayItem::Type_ClipPath); proto::ClipPathDisplayItem* details = proto->mutable_clip_path_item(); - details->set_clip_op(SkRegionOpToProto(clip_op_)); + details->set_clip_op(SkClipOpToProto(clip_op_)); details->set_antialias(antialias_); // Just use skia's serialization method for the SkPath for now.
diff --git a/cc/playback/clip_path_display_item.h b/cc/playback/clip_path_display_item.h index ea8ab17..e1eccd82 100644 --- a/cc/playback/clip_path_display_item.h +++ b/cc/playback/clip_path_display_item.h
@@ -12,8 +12,8 @@ #include "base/memory/ptr_util.h" #include "cc/base/cc_export.h" #include "cc/playback/display_item.h" +#include "third_party/skia/include/core/SkClipOp.h" #include "third_party/skia/include/core/SkPath.h" -#include "third_party/skia/include/core/SkRegion.h" class SkCanvas; @@ -21,7 +21,7 @@ class CC_EXPORT ClipPathDisplayItem : public DisplayItem { public: - ClipPathDisplayItem(const SkPath& path, SkRegion::Op clip_op, bool antialias); + ClipPathDisplayItem(const SkPath& path, SkClipOp clip_op, bool antialias); explicit ClipPathDisplayItem(const proto::DisplayItem& proto); ~ClipPathDisplayItem() override; @@ -35,10 +35,10 @@ int ApproximateOpCount() const { return 1; } private: - void SetNew(const SkPath& path, SkRegion::Op clip_op, bool antialias); + void SetNew(const SkPath& path, SkClipOp clip_op, bool antialias); SkPath clip_path_; - SkRegion::Op clip_op_; + SkClipOp clip_op_; bool antialias_; };
diff --git a/cc/playback/display_item_list_unittest.cc b/cc/playback/display_item_list_unittest.cc index 6f84642a7..329c33a 100644 --- a/cc/playback/display_item_list_unittest.cc +++ b/cc/playback/display_item_list_unittest.cc
@@ -201,7 +201,7 @@ SkPath path; path.addCircle(5.f, 5.f, 2.f, SkPath::Direction::kCW_Direction); list->CreateAndAppendPairedBeginItem<ClipPathDisplayItem>( - path, SkRegion::Op::kReplace_Op, false); + path, kReplace_SkClipOp, false); // Build the second DrawingDisplayItem. AppendSecondSerializationTestPicture(list, layer_size);
diff --git a/cc/playback/raster_source.cc b/cc/playback/raster_source.cc index 9e5e946..4ad28c3c 100644 --- a/cc/playback/raster_source.cc +++ b/cc/playback/raster_source.cc
@@ -172,7 +172,7 @@ // Use clipRegion to bypass CTM because the rects are device rects. SkRegion interest_region; interest_region.setRect(interest_rect); - canvas->clipRegion(interest_region, SkRegion::kDifference_Op); + canvas->clipRegion(interest_region, kDifference_SkClipOp); canvas->clear(DebugColors::MissingResizeInvalidations()); canvas->restore(); }
diff --git a/cc/proto/BUILD.gn b/cc/proto/BUILD.gn index d03e00a..23472fd8 100644 --- a/cc/proto/BUILD.gn +++ b/cc/proto/BUILD.gn
@@ -48,6 +48,7 @@ "scroll_offset.proto", "size.proto", "sizef.proto", + "skclipop.proto", "skregion.proto", "skrrect.proto", "skxfermode.proto",
diff --git a/cc/proto/display_item.proto b/cc/proto/display_item.proto index 2b7db4b3..d558203 100644 --- a/cc/proto/display_item.proto +++ b/cc/proto/display_item.proto
@@ -4,7 +4,7 @@ syntax = "proto2"; -import "skregion.proto"; +import "skclipop.proto"; import "skrrect.proto"; import "skxfermode.proto"; import "rect.proto"; @@ -61,7 +61,7 @@ } message ClipPathDisplayItem { - optional cc.proto.SkRegion.Op clip_op = 1; + optional cc.proto.SkClipOp.Op clip_op = 1; optional bool antialias = 2; optional bytes clip_path = 3; /* SkPath */ }
diff --git a/cc/proto/skclipop.proto b/cc/proto/skclipop.proto new file mode 100644 index 0000000..fe2da302 --- /dev/null +++ b/cc/proto/skclipop.proto
@@ -0,0 +1,20 @@ +// Copyright 2016 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. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package cc.proto; + +message SkClipOp { + enum Op { + DIFFERENCE_ = 0; // On Windows, winuser.h defines DIFFERENCE. + INTERSECT = 1; + UNION = 2; + XOR = 3; + REVERSE_DIFFERENCE = 4; + REPLACE = 5; + } +}
diff --git a/cc/proto/skia_conversions.cc b/cc/proto/skia_conversions.cc index 21ce808..fcf28b4 100644 --- a/cc/proto/skia_conversions.cc +++ b/cc/proto/skia_conversions.cc
@@ -6,6 +6,7 @@ #include "base/logging.h" #include "cc/proto/gfx_conversions.h" +#include "cc/proto/skclipop.pb.h" #include "cc/proto/skrrect.pb.h" #include "third_party/skia/include/core/SkRRect.h" #include "ui/gfx/skia_util.h" @@ -25,40 +26,40 @@ } // namespace -SkRegion::Op SkRegionOpFromProto(proto::SkRegion::Op op) { +SkClipOp SkClipOpFromProto(proto::SkClipOp::Op op) { switch (op) { - case proto::SkRegion::DIFFERENCE_: - return SkRegion::Op::kDifference_Op; - case proto::SkRegion::INTERSECT: - return SkRegion::Op::kIntersect_Op; - case proto::SkRegion::UNION: - return SkRegion::Op::kUnion_Op; - case proto::SkRegion::XOR: - return SkRegion::Op::kXOR_Op; - case proto::SkRegion::REVERSE_DIFFERENCE: - return SkRegion::Op::kReverseDifference_Op; - case proto::SkRegion::REPLACE: - return SkRegion::Op::kReplace_Op; + case proto::SkClipOp::DIFFERENCE_: + return kDifference_SkClipOp; + case proto::SkClipOp::INTERSECT: + return kIntersect_SkClipOp; + case proto::SkClipOp::UNION: + return kUnion_SkClipOp; + case proto::SkClipOp::XOR: + return kXOR_SkClipOp; + case proto::SkClipOp::REVERSE_DIFFERENCE: + return kReverseDifference_SkClipOp; + case proto::SkClipOp::REPLACE: + return kReplace_SkClipOp; } - return SkRegion::Op::kDifference_Op; + return kDifference_SkClipOp; } -proto::SkRegion::Op SkRegionOpToProto(SkRegion::Op op) { +proto::SkClipOp::Op SkClipOpToProto(SkClipOp op) { switch (op) { - case SkRegion::Op::kDifference_Op: - return proto::SkRegion::DIFFERENCE_; - case SkRegion::Op::kIntersect_Op: - return proto::SkRegion::INTERSECT; - case SkRegion::Op::kUnion_Op: - return proto::SkRegion::UNION; - case SkRegion::Op::kXOR_Op: - return proto::SkRegion::XOR; - case SkRegion::Op::kReverseDifference_Op: - return proto::SkRegion::REVERSE_DIFFERENCE; - case SkRegion::Op::kReplace_Op: - return proto::SkRegion::REPLACE; + case kDifference_SkClipOp: + return proto::SkClipOp::DIFFERENCE_; + case kIntersect_SkClipOp: + return proto::SkClipOp::INTERSECT; + case kUnion_SkClipOp: + return proto::SkClipOp::UNION; + case kXOR_SkClipOp: + return proto::SkClipOp::XOR; + case kReverseDifference_SkClipOp: + return proto::SkClipOp::REVERSE_DIFFERENCE; + case kReplace_SkClipOp: + return proto::SkClipOp::REPLACE; } - return proto::SkRegion::DIFFERENCE_; + return proto::SkClipOp::DIFFERENCE_; } SkBlendMode SkXfermodeModeFromProto(proto::SkXfermode::Mode mode) {
diff --git a/cc/proto/skia_conversions.h b/cc/proto/skia_conversions.h index 402fa4e..e92e1aeb 100644 --- a/cc/proto/skia_conversions.h +++ b/cc/proto/skia_conversions.h
@@ -6,12 +6,11 @@ #define CC_PROTO_SKIA_CONVERSIONS_H_ #include "cc/base/cc_export.h" -#include "cc/proto/skregion.pb.h" +#include "cc/proto/skclipop.pb.h" #include "cc/proto/skxfermode.pb.h" #include "third_party/skia/include/core/SkBlendMode.h" -#include "third_party/skia/include/core/SkRegion.h" +#include "third_party/skia/include/core/SkClipOp.h" -class SkRegion; class SkRRect; namespace cc { @@ -22,8 +21,8 @@ // TODO(dtrainor): Move these to a class and make them static // (crbug.com/548432). -CC_EXPORT SkRegion::Op SkRegionOpFromProto(proto::SkRegion::Op op); -CC_EXPORT proto::SkRegion::Op SkRegionOpToProto(SkRegion::Op op); +CC_EXPORT SkClipOp SkClipOpFromProto(proto::SkClipOp::Op op); +CC_EXPORT proto::SkClipOp::Op SkClipOpToProto(SkClipOp op); CC_EXPORT SkBlendMode SkXfermodeModeFromProto(proto::SkXfermode::Mode mode); CC_EXPORT proto::SkXfermode::Mode SkXfermodeModeToProto(SkBlendMode mode);
diff --git a/cc/proto/skia_conversions_unittest.cc b/cc/proto/skia_conversions_unittest.cc index fac61a4..7e3b6d0 100644 --- a/cc/proto/skia_conversions_unittest.cc +++ b/cc/proto/skia_conversions_unittest.cc
@@ -11,16 +11,22 @@ #include "cc/proto/skxfermode.pb.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/skia/include/core/SkBlendMode.h" +#include "third_party/skia/include/core/SkClipOp.h" #include "third_party/skia/include/core/SkRRect.h" -#include "third_party/skia/include/core/SkRegion.h" namespace cc { namespace { -TEST(SkiaProtoConversionsTest, SerializeDeserializeSkRegionOp) { - for (size_t i = 0; i < SkRegion::Op::kLastOp; i++) { - SkRegion::Op op = static_cast<SkRegion::Op>(i); - EXPECT_EQ(op, SkRegionOpFromProto(SkRegionOpToProto(op))); +TEST(SkiaProtoConversionsTest, SerializeDeserializeSkClipOp) { + // explicitly list the clipops, as this list will be reduced overtime + // as skia constricts the valid ops for clipping + // https://bugs.chromium.org/p/skia/issues/detail?id=3191 + const SkClipOp ops[] = { + kDifference_SkClipOp, kIntersect_SkClipOp, kUnion_SkClipOp, + kXOR_SkClipOp, kReverseDifference_SkClipOp, kReplace_SkClipOp, + }; + for (SkClipOp op : ops) { + EXPECT_EQ(op, SkClipOpFromProto(SkClipOpToProto(op))); } }
diff --git a/cc/surfaces/BUILD.gn b/cc/surfaces/BUILD.gn index fb3140a..284f1a1 100644 --- a/cc/surfaces/BUILD.gn +++ b/cc/surfaces/BUILD.gn
@@ -12,6 +12,8 @@ "local_frame_id.h", "surface_id.cc", "surface_id.h", + "surface_reference.cc", + "surface_reference.h", "surface_sequence.h", "surface_sequence_generator.cc", "surface_sequence_generator.h", @@ -19,6 +21,7 @@ deps = [ "//base", + "//mojo/public/cpp/bindings:struct_traits", ] }
diff --git a/cc/surfaces/DEPS b/cc/surfaces/DEPS new file mode 100644 index 0000000..7502d902 --- /dev/null +++ b/cc/surfaces/DEPS
@@ -0,0 +1,3 @@ +include_rules = [ + "+mojo/public/cpp/bindings/struct_traits.h", +]
diff --git a/cc/surfaces/local_frame_id.h b/cc/surfaces/local_frame_id.h index ea0ec038..7773b964 100644 --- a/cc/surfaces/local_frame_id.h +++ b/cc/surfaces/local_frame_id.h
@@ -13,8 +13,12 @@ #include "base/hash.h" #include "base/unguessable_token.h" +#include "mojo/public/cpp/bindings/struct_traits.h" namespace cc { +namespace mojom { +class LocalFrameIdDataView; +} class LocalFrameId { public: @@ -53,6 +57,8 @@ std::string ToString() const; private: + friend struct mojo::StructTraits<mojom::LocalFrameIdDataView, LocalFrameId>; + uint32_t local_id_; base::UnguessableToken nonce_; };
diff --git a/cc/surfaces/surface_id.h b/cc/surfaces/surface_id.h index 6df5009..fe21851 100644 --- a/cc/surfaces/surface_id.h +++ b/cc/surfaces/surface_id.h
@@ -14,8 +14,12 @@ #include "base/hash.h" #include "cc/surfaces/frame_sink_id.h" #include "cc/surfaces/local_frame_id.h" +#include "mojo/public/cpp/bindings/struct_traits.h" namespace cc { +namespace mojom { +class SurfaceIdDataView; +} class SurfaceId { public: @@ -62,6 +66,8 @@ } private: + friend struct mojo::StructTraits<mojom::SurfaceIdDataView, SurfaceId>; + // See SurfaceIdAllocator::GenerateId. FrameSinkId frame_sink_id_; LocalFrameId local_frame_id_;
diff --git a/cc/surfaces/surface_reference.cc b/cc/surfaces/surface_reference.cc new file mode 100644 index 0000000..5570fbfd --- /dev/null +++ b/cc/surfaces/surface_reference.cc
@@ -0,0 +1,27 @@ +// Copyright 2016 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 "cc/surfaces/surface_reference.h" + +#include "base/strings/stringprintf.h" + +namespace cc { + +SurfaceReference::SurfaceReference() = default; + +SurfaceReference::SurfaceReference(const SurfaceId& parent_id, + const SurfaceId& child_id) + : parent_id_(parent_id), child_id_(child_id) {} + +SurfaceReference::SurfaceReference(const SurfaceReference& other) = default; + +SurfaceReference::~SurfaceReference() = default; + +std::string SurfaceReference::ToString() const { + return base::StringPrintf("parent=%s, child=%s", + parent_id_.ToString().c_str(), + child_id_.ToString().c_str()); +} + +} // namespace cc
diff --git a/cc/surfaces/surface_reference.h b/cc/surfaces/surface_reference.h new file mode 100644 index 0000000..ac3bb77 --- /dev/null +++ b/cc/surfaces/surface_reference.h
@@ -0,0 +1,65 @@ +// Copyright 2016 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 CC_SURFACES_SURFACE_REFERENCE_H_ +#define CC_SURFACES_SURFACE_REFERENCE_H_ + +#include <string> + +#include "base/hash.h" +#include "cc/surfaces/surface_id.h" +#include "mojo/public/cpp/bindings/struct_traits.h" + +namespace cc { +namespace mojom { +class SurfaceReferenceDataView; +} + +// Hold a reference from an embedding (parent) to embedded (child) surface. +class SurfaceReference { + public: + SurfaceReference(); + SurfaceReference(const SurfaceId& parent_id, const SurfaceId& child_id); + SurfaceReference(const SurfaceReference& other); + + ~SurfaceReference(); + + const SurfaceId& parent_id() const { return parent_id_; } + const SurfaceId& child_id() const { return child_id_; } + + size_t hash() const { + return base::HashInts(static_cast<uint64_t>(parent_id_.hash()), + static_cast<uint64_t>(child_id_.hash())); + } + + bool operator==(const SurfaceReference& other) const { + return parent_id_ == other.parent_id_ && child_id_ == other.child_id_; + } + + bool operator!=(const SurfaceReference& other) const { + return !(*this == other); + } + + bool operator<(const SurfaceReference& other) const { + return std::tie(parent_id_, child_id_) < + std::tie(other.parent_id_, other.child_id_); + } + + std::string ToString() const; + + private: + friend struct mojo::StructTraits<mojom::SurfaceReferenceDataView, + SurfaceReference>; + + SurfaceId parent_id_; + SurfaceId child_id_; +}; + +struct SurfaceReferenceHash { + size_t operator()(const SurfaceReference& ref) const { return ref.hash(); } +}; + +} // namespace cc + +#endif // CC_SURFACES_SURFACE_REFERENCE_H_
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/UrlConstants.java b/chrome/android/java/src/org/chromium/chrome/browser/UrlConstants.java index 0923b5a..b4ed7c9 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/UrlConstants.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/UrlConstants.java
@@ -10,6 +10,7 @@ public class UrlConstants { public static final String CHROME_SCHEME = "chrome://"; public static final String CHROME_NATIVE_SCHEME = "chrome-native://"; + public static final String CONTENT_SCHEME = "content://"; public static final String CUSTOM_TAB_SCHEME = "customtab"; public static final String DOCUMENT_SCHEME = "document"; public static final String FILE_SCHEME = "file://";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenuPropertiesDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenuPropertiesDelegate.java index 59d4743b..529b7d6 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenuPropertiesDelegate.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenuPropertiesDelegate.java
@@ -92,6 +92,7 @@ boolean isChromeScheme = url.startsWith(UrlConstants.CHROME_SCHEME) || url.startsWith(UrlConstants.CHROME_NATIVE_SCHEME); boolean isFileScheme = url.startsWith(UrlConstants.FILE_SCHEME); + boolean isContentScheme = url.startsWith(UrlConstants.CONTENT_SCHEME); boolean shouldShowIconRow = !mActivity.isTablet() || mActivity.getWindow().getDecorView().getWidth() < DeviceFormFactor.getMinimumTabletWidthPx(mActivity); @@ -156,15 +157,18 @@ !currentTab.isNativePage() && currentTab.getWebContents() != null); // Hide 'Add to homescreen' for the following: - // 1.) chrome:// pages - Android doesn't know how to direct those URLs. - // 2.) incognito pages - To avoid problems where users create shortcuts in incognito - // mode and then open the webapp in regular mode. - // 3.) file:// - After API 24, file: URIs are not supported in VIEW intents and thus - // can not be added to the homescreen. - // 4.) If creating shortcuts it not supported by the current home screen. + // * chrome:// pages - Android doesn't know how to direct those URLs. + // * incognito pages - To avoid problems where users create shortcuts in incognito + // mode and then open the webapp in regular mode. + // * file:// - After API 24, file: URIs are not supported in VIEW intents and thus + // can not be added to the homescreen. + // * content:// - Accessing external content URIs requires the calling app to grant + // access to the resource via FLAG_GRANT_READ_URI_PERMISSION, and that + // is not persisted when adding to the homescreen. + // * If creating shortcuts it not supported by the current home screen. MenuItem homescreenItem = menu.findItem(R.id.add_to_homescreen_id); boolean homescreenItemVisible = ShortcutHelper.isAddToHomeIntentSupported(mActivity) - && !isChromeScheme && !isFileScheme && !isIncognito; + && !isChromeScheme && !isFileScheme && !isContentScheme && !isIncognito; if (homescreenItemVisible) { homescreenItem.setTitle(AppBannerManager.getHomescreenLanguageOption()); }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/Stack.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/Stack.java index 8634b1a..3ffe7ed 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/Stack.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/phone/stack/Stack.java
@@ -1171,8 +1171,10 @@ float maxScreen = tab0ScreenAfter; for (int i = pinch0TabIndex; i <= pinch1TabIndex; i++) { float screenBefore = approxScreen(mStackTabs[i], oldScrollTarget); - float t = (screenBefore - tab0ScreenBefore) - / (tab1ScreenBefore - tab0ScreenBefore); + float t = (tab1ScreenBefore == tab0ScreenBefore) + ? 1 + : ((screenBefore - tab0ScreenBefore) + / (tab1ScreenBefore - tab0ScreenBefore)); float screenAfter = (1 - t) * tab0ScreenAfter + t * tab1ScreenAfter; screenAfter = Math.max(minScreen, screenAfter); screenAfter = Math.min(maxScreen, screenAfter);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java index 891fccd..54e7779 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/document/ChromeLauncherActivity.java
@@ -55,6 +55,7 @@ import org.chromium.chrome.browser.util.UrlUtilities; import org.chromium.chrome.browser.webapps.ActivityAssigner; import org.chromium.chrome.browser.webapps.WebappLauncherActivity; +import org.chromium.ui.widget.Toast; import java.lang.ref.WeakReference; import java.net.URI; @@ -455,7 +456,9 @@ newIntent.addFlags(Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS); } Uri uri = newIntent.getData(); + boolean isContentScheme = false; if (uri != null && "content".equals(uri.getScheme())) { + isContentScheme = true; newIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); } if (mIsInLegacyMultiInstanceMode) { @@ -470,6 +473,14 @@ StrictMode.allowThreadDiskWrites(); try { startActivity(newIntent); + } catch (SecurityException ex) { + if (isContentScheme) { + Toast.makeText( + this, R.string.external_app_restricted_access_error, + Toast.LENGTH_LONG).show(); + } else { + throw ex; + } } finally { StrictMode.setThreadPolicy(oldPolicy); }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/gsa/ContextReporter.java b/chrome/android/java/src/org/chromium/chrome/browser/gsa/ContextReporter.java index c40337f..394e0f4c 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/gsa/ContextReporter.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/gsa/ContextReporter.java
@@ -212,7 +212,7 @@ * Records an appropriate status via UMA given the current sync status. */ public static void reportSyncStatus(@Nullable ProfileSyncService syncService) { - if (syncService == null || !syncService.isBackendInitialized()) { + if (syncService == null || !syncService.isEngineInitialized()) { reportStatus(STATUS_SYNC_NOT_INITIALIZED); } else if (!syncService.getActiveDataTypes().contains(ModelType.TYPED_URLS)) { reportStatus(STATUS_SYNC_NOT_SYNCING_URLS);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/precache/PrecacheLauncher.java b/chrome/android/java/src/org/chromium/chrome/browser/precache/PrecacheLauncher.java index d9c8f252..4c2703f 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/precache/PrecacheLauncher.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/precache/PrecacheLauncher.java
@@ -146,7 +146,7 @@ if (mListener == null && sync != null) { mListener = new ProfileSyncService.SyncStateChangedListener() { public void syncStateChanged() { - if (sync.isBackendInitialized()) { + if (sync.isEngineInitialized()) { mSyncInitialized = true; updateEnabledSync(context); } @@ -156,7 +156,7 @@ } if (mListener != null) { - // Call the listener once, in case the sync backend is already initialized. + // Call the listener once, in case the sync engine is already initialized. mListener.syncStateChanged(); } Log.v(TAG, "updateEnabled complete");
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/MainPreferences.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/MainPreferences.java index fe60ed16..e79b434 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/MainPreferences.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/MainPreferences.java
@@ -123,9 +123,8 @@ ProfileSyncService syncService = ProfileSyncService.get(); if (AndroidSyncSettings.isSyncEnabled(getActivity().getApplicationContext()) - && syncService.isBackendInitialized() - && !syncService.isUsingSecondaryPassphrase() - && ChromeFeatureList.isEnabled(VIEW_PASSWORDS)) { + && syncService.isEngineInitialized() && !syncService.isUsingSecondaryPassphrase() + && ChromeFeatureList.isEnabled(VIEW_PASSWORDS)) { passwordsPref.setKey(PREF_MANAGE_ACCOUNT_LINK); passwordsPref.setTitle(R.string.redirect_to_passwords_text); passwordsPref.setSummary(R.string.redirect_to_passwords_link);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/GmsCoreSyncListener.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/GmsCoreSyncListener.java index 4d3642ac..5c02bea 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/sync/GmsCoreSyncListener.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/GmsCoreSyncListener.java
@@ -26,7 +26,7 @@ @VisibleForTesting public void syncStateChanged() { ProfileSyncService syncService = ProfileSyncService.get(); - boolean passphraseSet = syncService.isBackendInitialized() + boolean passphraseSet = syncService.isEngineInitialized() && syncService.isUsingSecondaryPassphrase() && syncService.isCryptographerReady(); if (passphraseSet && !mGmsCoreInformed) { byte[] key = syncService.getCustomPassphraseKey();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/ProfileSyncService.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/ProfileSyncService.java index 19619979..7c1890c 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/sync/ProfileSyncService.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/ProfileSyncService.java
@@ -4,14 +4,15 @@ package org.chromium.chrome.browser.sync; +import org.json.JSONArray; +import org.json.JSONException; + import org.chromium.base.ThreadUtils; import org.chromium.base.VisibleForTesting; import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.SuppressFBWarnings; import org.chromium.components.sync.ModelType; import org.chromium.components.sync.PassphraseType; -import org.json.JSONArray; -import org.json.JSONException; import java.util.HashSet; import java.util.List; @@ -159,15 +160,14 @@ } /** - * Returns the actual passphrase type being used for encryption. - * The sync backend must be running (isBackendInitialized() returns true) before - * calling this function. + * Returns the actual passphrase type being used for encryption. The sync engine must be + * running (isEngineInitialized() returns true) before calling this function. * <p/> * This method should only be used if you want to know the raw value. For checking whether * we should ask the user for a passphrase, use isPassphraseRequiredForDecryption(). */ public PassphraseType getPassphraseType() { - assert isBackendInitialized(); + assert isEngineInitialized(); int passphraseType = nativeGetPassphraseType(mNativeProfileSyncServiceAndroid); return PassphraseType.fromInternalValue(passphraseType); } @@ -176,7 +176,7 @@ * Returns true if the current explicit passphrase time is defined. */ public boolean hasExplicitPassphraseTime() { - assert isBackendInitialized(); + assert isEngineInitialized(); return nativeHasExplicitPassphraseTime(mNativeProfileSyncServiceAndroid); } @@ -184,22 +184,22 @@ * Returns the current explicit passphrase time in milliseconds since epoch. */ public long getExplicitPassphraseTime() { - assert isBackendInitialized(); + assert isEngineInitialized(); return nativeGetExplicitPassphraseTime(mNativeProfileSyncServiceAndroid); } public String getSyncEnterGooglePassphraseBodyWithDateText() { - assert isBackendInitialized(); + assert isEngineInitialized(); return nativeGetSyncEnterGooglePassphraseBodyWithDateText(mNativeProfileSyncServiceAndroid); } public String getSyncEnterCustomPassphraseBodyWithDateText() { - assert isBackendInitialized(); + assert isEngineInitialized(); return nativeGetSyncEnterCustomPassphraseBodyWithDateText(mNativeProfileSyncServiceAndroid); } public String getCurrentSignedInAccountText() { - assert isBackendInitialized(); + assert isEngineInitialized(); return nativeGetCurrentSignedInAccountText(mNativeProfileSyncServiceAndroid); } @@ -208,13 +208,13 @@ } /** - * Checks if sync is currently set to use a custom passphrase. The sync backend must be running - * (isBackendInitialized() returns true) before calling this function. + * Checks if sync is currently set to use a custom passphrase. The sync engine must be running + * (isEngineInitialized() returns true) before calling this function. * * @return true if sync is using a custom passphrase. */ public boolean isUsingSecondaryPassphrase() { - assert isBackendInitialized(); + assert isEngineInitialized(); return nativeIsUsingSecondaryPassphrase(mNativeProfileSyncServiceAndroid); } @@ -230,17 +230,17 @@ * @return true if we need a passphrase. */ public boolean isPassphraseRequiredForDecryption() { - assert isBackendInitialized(); + assert isEngineInitialized(); return nativeIsPassphraseRequiredForDecryption(mNativeProfileSyncServiceAndroid); } /** - * Checks if the sync backend is running. + * Checks if the sync engine is running. * * @return true if sync is initialized/running. */ - public boolean isBackendInitialized() { - return nativeIsBackendInitialized(mNativeProfileSyncServiceAndroid); + public boolean isEngineInitialized() { + return nativeIsEngineInitialized(mNativeProfileSyncServiceAndroid); } /** @@ -250,7 +250,7 @@ * be encrypted. */ public boolean isEncryptEverythingAllowed() { - assert isBackendInitialized(); + assert isEngineInitialized(); return nativeIsEncryptEverythingAllowed(mNativeProfileSyncServiceAndroid); } @@ -260,7 +260,7 @@ * @return true if all data types are encrypted, false if only passwords are encrypted. */ public boolean isEncryptEverythingEnabled() { - assert isBackendInitialized(); + assert isEngineInitialized(); return nativeIsEncryptEverythingEnabled(mNativeProfileSyncServiceAndroid); } @@ -269,22 +269,22 @@ * completed and setPreferredDataTypes() is invoked. */ public void enableEncryptEverything() { - assert isBackendInitialized(); + assert isEngineInitialized(); nativeEnableEncryptEverything(mNativeProfileSyncServiceAndroid); } public void setEncryptionPassphrase(String passphrase) { - assert isBackendInitialized(); + assert isEngineInitialized(); nativeSetEncryptionPassphrase(mNativeProfileSyncServiceAndroid, passphrase); } public boolean isCryptographerReady() { - assert isBackendInitialized(); + assert isEngineInitialized(); return nativeIsCryptographerReady(mNativeProfileSyncServiceAndroid); } public boolean setDecryptionPassphrase(String passphrase) { - assert isBackendInitialized(); + assert isEngineInitialized(); return nativeSetDecryptionPassphrase(mNativeProfileSyncServiceAndroid, passphrase); } @@ -547,7 +547,7 @@ private native String nativeQuerySyncStatusSummary(long nativeProfileSyncServiceAndroid); private native int nativeGetAuthError(long nativeProfileSyncServiceAndroid); private native int nativeGetProtocolErrorClientAction(long nativeProfileSyncServiceAndroid); - private native boolean nativeIsBackendInitialized(long nativeProfileSyncServiceAndroid); + private native boolean nativeIsEngineInitialized(long nativeProfileSyncServiceAndroid); private native boolean nativeIsEncryptEverythingAllowed(long nativeProfileSyncServiceAndroid); private native boolean nativeIsEncryptEverythingEnabled(long nativeProfileSyncServiceAndroid); private native void nativeEnableEncryptEverything(long nativeProfileSyncServiceAndroid);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncController.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncController.java index 357724e..cd70dd3 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncController.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncController.java
@@ -212,7 +212,7 @@ * @return Whether sync is enabled to sync urls or open tabs with a non custom passphrase. */ public boolean isSyncingUrlsWithKeystorePassphrase() { - return mProfileSyncService.isBackendInitialized() + return mProfileSyncService.isEngineInitialized() && mProfileSyncService.getPreferredDataTypes().contains(ModelType.TYPED_URLS) && mProfileSyncService.getPassphraseType().equals( PassphraseType.KEYSTORE_PASSPHRASE);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncNotificationController.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncNotificationController.java index a5761b7c..1f37729e 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncNotificationController.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/SyncNotificationController.java
@@ -68,7 +68,7 @@ if (shouldSyncAuthErrorBeShown()) { message = mProfileSyncService.getAuthError().getMessage(); intent = createSettingsIntent(); - } else if (mProfileSyncService.isBackendInitialized() + } else if (mProfileSyncService.isEngineInitialized() && mProfileSyncService.isPassphraseRequiredForDecryption()) { if (mProfileSyncService.isPassphrasePrompted()) { return;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/PassphraseActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/PassphraseActivity.java index c85d41a7..fab506f3 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/PassphraseActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/PassphraseActivity.java
@@ -64,7 +64,7 @@ } if (!isShowingDialog(FRAGMENT_PASSPHRASE)) { - if (ProfileSyncService.get().isBackendInitialized()) { + if (ProfileSyncService.get().isEngineInitialized()) { displayPassphraseDialog(); } else { addSyncStateChangedListener(); @@ -88,7 +88,7 @@ mSyncStateChangedListener = new ProfileSyncService.SyncStateChangedListener() { @Override public void syncStateChanged() { - if (ProfileSyncService.get().isBackendInitialized()) { + if (ProfileSyncService.get().isEngineInitialized()) { removeSyncStateChangedListener(); displayPassphraseDialog(); } @@ -109,7 +109,7 @@ } private void displayPassphraseDialog() { - assert ProfileSyncService.get().isBackendInitialized(); + assert ProfileSyncService.get().isEngineInitialized(); FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.addToBackStack(null); PassphraseDialogFragment.newInstance(null).show(ft, FRAGMENT_PASSPHRASE);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/SyncCustomizationFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/SyncCustomizationFragment.java index eefa711..70e06f5 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/SyncCustomizationFragment.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/SyncCustomizationFragment.java
@@ -112,7 +112,7 @@ public static final String ARGUMENT_ACCOUNT = "account"; private ChromeSwitchPreference mSyncSwitchPreference; - private boolean mIsBackendInitialized; + private boolean mIsEngineInitialized; private boolean mIsPassphraseRequired; @VisibleForTesting @@ -152,9 +152,9 @@ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mProfileSyncService = ProfileSyncService.get(); assert mProfileSyncService != null; - mIsBackendInitialized = mProfileSyncService.isBackendInitialized(); + mIsEngineInitialized = mProfileSyncService.isEngineInitialized(); mIsPassphraseRequired = - mIsBackendInitialized && mProfileSyncService.isPassphraseRequiredForDecryption(); + mIsEngineInitialized && mProfileSyncService.isPassphraseRequiredForDecryption(); getActivity().setTitle(R.string.sign_in_sync); @@ -281,9 +281,9 @@ // account preference displays the correct signed in account. mSyncedAccountPreference.update(); - mIsBackendInitialized = mProfileSyncService.isBackendInitialized(); + mIsEngineInitialized = mProfileSyncService.isEngineInitialized(); mIsPassphraseRequired = - mIsBackendInitialized && mProfileSyncService.isPassphraseRequiredForDecryption(); + mIsEngineInitialized && mProfileSyncService.isPassphraseRequiredForDecryption(); // This prevents sync from actually syncing until the dialog is closed. mProfileSyncService.setSetupInProgress(true); mProfileSyncService.addSyncStateChangedListener(this); @@ -359,17 +359,17 @@ /** * Update the encryption state. * - * If sync's backend is initialized, the button is enabled and the dialog will present the + * If sync's engine is initialized, the button is enabled and the dialog will present the * valid encryption options for the user. Otherwise, any encryption dialogs will be closed - * and the button will be disabled because the backend is needed in order to know and + * and the button will be disabled because the engine is needed in order to know and * modify the encryption state. */ private void updateEncryptionState() { boolean isSyncEnabled = mSyncSwitchPreference.isChecked(); - boolean isBackendInitialized = mProfileSyncService.isBackendInitialized(); - mSyncEncryption.setEnabled(isSyncEnabled && isBackendInitialized); + boolean isEngineInitialized = mProfileSyncService.isEngineInitialized(); + mSyncEncryption.setEnabled(isSyncEnabled && isEngineInitialized); mSyncEncryption.setSummary(null); - if (!isBackendInitialized) { + if (!isEngineInitialized) { // If sync is not initialized, encryption state is unavailable and can't be changed. // Leave the button disabled and the summary empty. Additionally, close the dialogs in // case they were open when a stop and clear comes. @@ -454,7 +454,7 @@ } private void configureEncryption(String passphrase) { - if (mProfileSyncService.isBackendInitialized()) { + if (mProfileSyncService.isEngineInitialized()) { mProfileSyncService.enableEncryptEverything(); mProfileSyncService.setEncryptionPassphrase(passphrase); // Configure the current set of data types - this tells the sync engine to @@ -486,8 +486,8 @@ */ @Override public boolean onPassphraseEntered(String passphrase) { - if (!mProfileSyncService.isBackendInitialized()) { - // If the backend was shut down since the dialog was opened, do nothing. + if (!mProfileSyncService.isEngineInitialized()) { + // If the engine was shut down since the dialog was opened, do nothing. return false; } return handleDecryption(passphrase); @@ -505,8 +505,8 @@ */ @Override public void onPassphraseCreated(String passphrase) { - if (!mProfileSyncService.isBackendInitialized()) { - // If the backend was shut down since the dialog was opened, do nothing. + if (!mProfileSyncService.isEngineInitialized()) { + // If the engine was shut down since the dialog was opened, do nothing. return; } configureEncryption(passphrase); @@ -517,8 +517,8 @@ */ @Override public void onPassphraseTypeSelected(PassphraseType type) { - if (!mProfileSyncService.isBackendInitialized()) { - // If the backend was shut down since the dialog was opened, do nothing. + if (!mProfileSyncService.isEngineInitialized()) { + // If the engine was shut down since the dialog was opened, do nothing. return; } @@ -542,7 +542,7 @@ // roughly the same time. See http://b/5983282 return false; } - if (preference == mSyncEncryption && mProfileSyncService.isBackendInitialized()) { + if (preference == mSyncEncryption && mProfileSyncService.isEngineInitialized()) { if (mProfileSyncService.isPassphraseRequiredForDecryption()) { displayPassphraseDialog(); } else { @@ -587,12 +587,12 @@ * * If sync is on, load the prefs from native. Otherwise, all data types are disabled and * checked. Note that the Password data type will be shown as disabled and unchecked between - * sync being turned on and the backend initialization completing. + * sync being turned on and the engine initialization completing. */ private void updateDataTypeState() { boolean isSyncEnabled = mSyncSwitchPreference.isChecked(); boolean syncEverything = mSyncEverything.isChecked(); - boolean passwordSyncConfigurable = mProfileSyncService.isBackendInitialized() + boolean passwordSyncConfigurable = mProfileSyncService.isEngineInitialized() && mProfileSyncService.isCryptographerReady(); Set<Integer> syncTypes = mProfileSyncService.getPreferredDataTypes(); boolean syncAutofill = syncTypes.contains(ModelType.AUTOFILL); @@ -749,18 +749,18 @@ * Listen to sync state changes. * * If the user has just turned on sync, this listener is needed in order to enable - * the encryption settings once the backend has initialized. + * the encryption settings once the engine has initialized. */ @Override public void syncStateChanged() { - boolean wasSyncInitialized = mIsBackendInitialized; + boolean wasSyncInitialized = mIsEngineInitialized; boolean wasPassphraseRequired = mIsPassphraseRequired; - mIsBackendInitialized = mProfileSyncService.isBackendInitialized(); + mIsEngineInitialized = mProfileSyncService.isEngineInitialized(); mIsPassphraseRequired = - mIsBackendInitialized && mProfileSyncService.isPassphraseRequiredForDecryption(); - if (mIsBackendInitialized != wasSyncInitialized + mIsEngineInitialized && mProfileSyncService.isPassphraseRequiredForDecryption(); + if (mIsEngineInitialized != wasSyncInitialized || mIsPassphraseRequired != wasPassphraseRequired) { - // Update all because Password syncability is also affected by the backend. + // Update all because Password syncability is also affected by the engine. updateSyncStateFromSwitch(); } else { updateSyncErrorCard();
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd index 3a20e40f..d4421ba6 100644 --- a/chrome/android/java/strings/android_chrome_strings.grd +++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -1879,6 +1879,9 @@ <message name="IDS_EXTERNAL_APP_LEAVE_INCOGNITO_WARNING_TITLE" desc="Title for incognito external intent. [CHAR-LIMIT=24]"> Warning </message> + <message name="IDS_EXTERNAL_APP_RESTRICTED_ACCESS_ERROR" desc="A message shown to the user if Chrome receives a file view request to something Chrome does not have access to view."> + Chrome does not have access to the requested resource. + </message> <!-- Messages for remote media playback (casting) --> <message name="IDS_CAST_CASTING_VIDEO" desc="AtHome text to tell user which screen casting is happening. [CHAR LIMIT=40]">
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/PersonalDataManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/PersonalDataManagerTest.java index 7483487..b5b603e8 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/PersonalDataManagerTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/autofill/PersonalDataManagerTest.java
@@ -151,7 +151,8 @@ assertEquals("Visa", storedCard.getName()); assertEquals("10", storedCard.getMonth()); assertEquals("4012888888881881", storedCard.getNumber()); - assertEquals("Visa\u00a0\u22ef1881", storedCard.getObfuscatedNumber()); + assertEquals("Visa\u0020\u0020\u2022\u2006\u2022\u2006\u2022\u2006\u2022\u20061881", + storedCard.getObfuscatedNumber()); assertNotNull(mHelper.getCreditCard(cardTwoGUID)); }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppAndCardsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppAndCardsTest.java index b103a7a..94d6b6b0 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppAndCardsTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPaymentAppAndCardsTest.java
@@ -100,9 +100,13 @@ if (instrumentPresence == HAVE_INSTRUMENTS) { assertEquals("Test Pay", getPaymentInstrumentLabel(i++)); } - // \u00A0\u22EF is a non-breaking space followed by a midline ellipsis. - assertEquals("Visa\u00A0\u22EF1111\nJon Doe", getPaymentInstrumentLabel(i++)); - assertEquals("MasterCard\u00A0\u22EF5454\nJon Doe\nBilling address required", + // \u0020\...\u2006 is four dots ellipsis. + assertEquals( + "Visa\u0020\u0020\u2022\u2006\u2022\u2006\u2022\u2006\u2022\u20061111\nJon Doe", + getPaymentInstrumentLabel(i++)); + assertEquals( + "MasterCard\u0020\u0020\u2022\u2006\u2022\u2006\u2022\u2006\u2022\u20065454\n" + + "Jon Doe\nBilling address required", getPaymentInstrumentLabel(i++)); // Check the output of the selected instrument.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/precache/PrecacheLauncherTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/precache/PrecacheLauncherTest.java index e46233c8..3f414e2a 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/precache/PrecacheLauncherTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/precache/PrecacheLauncherTest.java
@@ -50,19 +50,19 @@ } private static class StubProfileSyncService extends ProfileSyncService { - private boolean mSyncInitialized = false; + private boolean mEngineInitialized = false; public StubProfileSyncService() { super(); } @Override - public boolean isBackendInitialized() { - return mSyncInitialized; + public boolean isEngineInitialized() { + return mEngineInitialized; } - public void setSyncInitialized(boolean syncInitialized) { - mSyncInitialized = syncInitialized; + public void setEngineInitialized(boolean engineInitialized) { + mEngineInitialized = engineInitialized; syncStateChanged(); } } @@ -85,7 +85,7 @@ ContextUtils.initApplicationContext(getTargetContext().getApplicationContext()); // This is a PrecacheLauncher with a stubbed out nativeShouldRun so we can change that on - // the fly without needing to set up a sync backend. + // the fly without needing to set up a sync engine. mLauncher = new PrecacheLauncherUnderTest(); mPrecacheTaskScheduler = new MockPrecacheTaskScheduler(); @@ -104,7 +104,7 @@ ThreadUtils.runOnUiThreadBlocking(new Runnable() { @Override public void run() { - // The StubProfileSyncService stubs out isBackendInitialized so we can change that + // The StubProfileSyncService stubs out isEngineInitialized so we can change that // on the fly. mSync = new StubProfileSyncService(); ProfileSyncService.overrideForTests(mSync); @@ -131,7 +131,7 @@ FailureReason.NATIVE_SHOULD_RUN_IS_FALSE), failureReasons()); - setSyncInitialized(true); + setEngineInitialized(true); assertEquals(false, isPrecachingEnabled()); assertEquals(EnumSet.of(FailureReason.NATIVE_SHOULD_RUN_IS_FALSE), failureReasons()); } @@ -149,7 +149,7 @@ failureReasons()); mLauncher.setShouldRun(true); - setSyncInitialized(true); + setEngineInitialized(true); assertEquals(true, isPrecachingEnabled()); assertEquals(EnumSet.noneOf(FailureReason.class), failureReasons()); } @@ -157,7 +157,7 @@ @SmallTest @Feature({"Precache"}) public void testUpdateEnabled_Disabled_ThenEnabled() { - setSyncInitialized(true); + setEngineInitialized(true); mLauncher.updateEnabled(getTargetContext()); waitUntilUiThreadIdle(); @@ -173,7 +173,7 @@ @Feature({"Precache"}) public void testUpdateEnabled_Enabled_ThenDisabled() { mLauncher.setShouldRun(true); - setSyncInitialized(true); + setEngineInitialized(true); mLauncher.updateEnabled(getTargetContext()); waitUntilUiThreadIdle(); @@ -225,12 +225,12 @@ }); } - /** Pretend the sync backend is initialized or not. */ - private void setSyncInitialized(final boolean syncInitialized) { + /** Pretend the sync engine is initialized or not. */ + private void setEngineInitialized(final boolean syncInitialized) { ThreadUtils.runOnUiThreadBlocking(new Runnable() { @Override public void run() { - mSync.setSyncInitialized(syncInitialized); + mSync.setEngineInitialized(syncInitialized); } }); }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/PasswordViewingTypeTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/PasswordViewingTypeTest.java index ffa2c43..6d3108d 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/PasswordViewingTypeTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/preferences/PasswordViewingTypeTest.java
@@ -89,7 +89,7 @@ } @Override - public boolean isBackendInitialized() { + public boolean isEngineInitialized() { return true; } } @@ -133,7 +133,7 @@ ThreadUtils.runOnUiThreadBlocking(new Runnable() { @Override public void run() { - assertTrue(ProfileSyncService.get().isBackendInitialized()); + assertTrue(ProfileSyncService.get().isEngineInitialized()); assertFalse(ProfileSyncService.get().isUsingSecondaryPassphrase()); } });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/FakeProfileSyncService.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/FakeProfileSyncService.java index 622097c1..f6177b6 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/FakeProfileSyncService.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/FakeProfileSyncService.java
@@ -10,20 +10,19 @@ * Only what has been needed for tests so far has been faked. */ public class FakeProfileSyncService extends ProfileSyncService { - - private boolean mBackendInitialized; + private boolean mEngineInitialized; public FakeProfileSyncService() { super(); } @Override - public boolean isBackendInitialized() { - return mBackendInitialized; + public boolean isEngineInitialized() { + return mEngineInitialized; } - public void setSyncInitialized(boolean syncInitialized) { - mBackendInitialized = syncInitialized; + public void setEngineInitialized(boolean engineInitialized) { + mEngineInitialized = engineInitialized; } @Override
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ui/PassphraseActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ui/PassphraseActivityTest.java index 73ad036..8e1847d 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ui/PassphraseActivityTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/sync/ui/PassphraseActivityTest.java
@@ -74,7 +74,7 @@ getInstrumentation().callActivityOnSaveInstanceState(activity, bundle); // Fake sync's backend finishing its initialization. FakeProfileSyncService pss = (FakeProfileSyncService) ProfileSyncService.get(); - pss.setSyncInitialized(true); + pss.setEngineInitialized(true); pss.syncStateChanged(); } });
diff --git a/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/FirstRunTest.java b/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/FirstRunTest.java index 3a40d64b..fcfcd9d5 100644 --- a/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/FirstRunTest.java +++ b/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/FirstRunTest.java
@@ -72,7 +72,7 @@ // User should be signed in and the sync backend should initialize, but sync should not // become fully active until the settings page is closed. assertEquals(testAccount, SigninTestUtil.getCurrentAccount()); - SyncTestUtil.waitForBackendInitialized(); + SyncTestUtil.waitForEngineInitialized(); assertFalse(SyncTestUtil.isSyncActive()); // Close the settings fragment.
diff --git a/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/SyncCustomizationFragmentTest.java b/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/SyncCustomizationFragmentTest.java index 138ca32..44f5a9b9 100644 --- a/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/SyncCustomizationFragmentTest.java +++ b/chrome/android/sync_shell/javatests/src/org/chromium/chrome/browser/sync/SyncCustomizationFragmentTest.java
@@ -139,7 +139,7 @@ */ @SmallTest @Feature({"Sync"}) - public void testOpeningSettingsDoesntStartBackend() throws Exception { + public void testOpeningSettingsDoesntStartEngine() throws Exception { setUpTestAccountAndSignIn(); stopSync(); startSyncCustomizationFragment(); @@ -147,7 +147,7 @@ @Override public void run() { assertFalse(mProfileSyncService.isSyncRequested()); - assertFalse(mProfileSyncService.isBackendInitialized()); + assertFalse(mProfileSyncService.isEngineInitialized()); } }); } @@ -160,7 +160,7 @@ SyncCustomizationFragment fragment = startSyncCustomizationFragment(); assertDefaultSyncOffState(fragment); togglePreference(getSyncSwitch(fragment)); - SyncTestUtil.waitForBackendInitialized(); + SyncTestUtil.waitForEngineInitialized(); assertDefaultSyncOnState(fragment); }
diff --git a/chrome/app/mash/chrome_mash_content_browser_manifest_overlay.json b/chrome/app/mash/chrome_mash_content_browser_manifest_overlay.json index d4ebfec..cc3774c 100644 --- a/chrome/app/mash/chrome_mash_content_browser_manifest_overlay.json +++ b/chrome/app/mash/chrome_mash_content_browser_manifest_overlay.json
@@ -12,7 +12,7 @@ "ash": [ "app_list::mojom::AppListPresenter" ], - "ime:ime_driver": [ "ui::mojom::IMEDriver" ] + "ime:ime_driver": [] }, "requires": { "accessibility_autoclick": [ "ash:autoclick" ],
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn index 5ddcfae..05594e9 100644 --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn
@@ -2821,8 +2821,6 @@ "obsolete_system/obsolete_system_win.cc", "pdf/pdf_extension_util.cc", "pdf/pdf_extension_util.h", - "power/process_power_collector.cc", - "power/process_power_collector.h", "power_usage_monitor/power_usage_monitor.cc", "power_usage_monitor/power_usage_monitor.h", "process_singleton_modal_dialog_lock.cc",
diff --git a/chrome/browser/android/offline_pages/prerendering_loader.cc b/chrome/browser/android/offline_pages/prerendering_loader.cc index c2b85f6..fb1c4bc 100644 --- a/chrome/browser/android/offline_pages/prerendering_loader.cc +++ b/chrome/browser/android/offline_pages/prerendering_loader.cc
@@ -34,22 +34,43 @@ Offliner::RequestStatus ClassifyFinalStatus( prerender::FinalStatus final_status) { switch (final_status) { - - // Identify aborted/canceled operations + // Identify aborted/canceled operations. case prerender::FINAL_STATUS_CANCELLED: // TODO(dougarnett): Reconsider if/when get better granularity (642768) case prerender::FINAL_STATUS_UNSUPPORTED_SCHEME: return Offliner::PRERENDERING_CANCELED; - // Identify non-retryable failues. + // Identify non-retryable failures. These are a hard type failures + // associated with the page and so are expected to occur again if retried. case prerender::FINAL_STATUS_SAFE_BROWSING: case prerender::FINAL_STATUS_CREATING_AUDIO_STREAM: case prerender::FINAL_STATUS_JAVASCRIPT_ALERT: + case prerender::FINAL_STATUS_CREATE_NEW_WINDOW: + case prerender::FINAL_STATUS_INVALID_HTTP_METHOD: + case prerender::FINAL_STATUS_OPEN_URL: return Offliner::RequestStatus::PRERENDERING_FAILED_NO_RETRY; + // Identify failures that indicate we should stop further processing + // for now. These may be current resource issues or app closing. + + case prerender::FINAL_STATUS_MEMORY_LIMIT_EXCEEDED: + case prerender::FINAL_STATUS_RATE_LIMIT_EXCEEDED: + case prerender::FINAL_STATUS_RENDERER_CRASHED: + case prerender::FINAL_STATUS_TOO_MANY_PROCESSES: + case prerender::FINAL_STATUS_TIMED_OUT: + case prerender::FINAL_STATUS_APP_TERMINATING: + case prerender::FINAL_STATUS_PROFILE_DESTROYED: + return Offliner::RequestStatus::PRERENDERING_FAILED_NO_NEXT; + // Otherwise, assume retryable failure. + + case prerender::FINAL_STATUS_NEW_NAVIGATION_ENTRY: + case prerender::FINAL_STATUS_CACHE_OR_HISTORY_CLEARED: + case prerender::FINAL_STATUS_SSL_ERROR: + case prerender::FINAL_STATUS_SSL_CLIENT_CERTIFICATE_REQUESTED: + case prerender::FINAL_STATUS_WINDOW_PRINT: default: return Offliner::RequestStatus::PRERENDERING_FAILED; }
diff --git a/chrome/browser/android/offline_pages/prerendering_loader_unittest.cc b/chrome/browser/android/offline_pages/prerendering_loader_unittest.cc index ab5c694..e51baa5 100644 --- a/chrome/browser/android/offline_pages/prerendering_loader_unittest.cc +++ b/chrome/browser/android/offline_pages/prerendering_loader_unittest.cc
@@ -243,7 +243,7 @@ TEST_F(PrerenderingLoaderTest, LoadPageLoadFailedNoContent) { test_adapter()->Configure(nullptr /* web_contents */, - prerender::FINAL_STATUS_MEMORY_LIMIT_EXCEEDED); + prerender::FINAL_STATUS_CACHE_OR_HISTORY_CLEARED); GURL gurl("http://testit.sea"); EXPECT_TRUE(loader()->IsIdle()); EXPECT_TRUE(loader()->LoadPage( @@ -290,6 +290,31 @@ PumpLoop(); } +TEST_F(PrerenderingLoaderTest, LoadPageLoadFailedNoNext) { + test_adapter()->Configure(nullptr /* web_contents */, + prerender::FINAL_STATUS_MEMORY_LIMIT_EXCEEDED); + GURL gurl("http://testit.sea"); + EXPECT_TRUE(loader()->IsIdle()); + EXPECT_TRUE(loader()->LoadPage( + gurl, + base::Bind(&PrerenderingLoaderTest::OnLoadDone, base::Unretained(this)))); + EXPECT_FALSE(loader()->IsIdle()); + EXPECT_FALSE(loader()->IsLoaded()); + + test_adapter()->GetObserver()->OnPrerenderDomContentLoaded(); + PumpLoop(); + EXPECT_TRUE(loader()->IsIdle()); + EXPECT_TRUE(callback_called()); + // We did not provide any WebContents for the callback so expect did not load. + // FinalStatus is non-next failure. + EXPECT_EQ(Offliner::RequestStatus::PRERENDERING_FAILED_NO_NEXT, + callback_load_status()); + + // Stopped event causes no harm. + test_adapter()->GetObserver()->OnPrerenderStop(); + PumpLoop(); +} + TEST_F(PrerenderingLoaderTest, LoadPageLoadCanceled) { test_adapter()->Configure(nullptr /* web_contents */, prerender::FINAL_STATUS_CANCELLED);
diff --git a/chrome/browser/browsing_data/browsing_data_remover.cc b/chrome/browser/browsing_data/browsing_data_remover.cc index aa079bed..3ed79a4 100644 --- a/chrome/browser/browsing_data/browsing_data_remover.cc +++ b/chrome/browser/browsing_data/browsing_data_remover.cc
@@ -65,8 +65,6 @@ #include "components/ntp_snippets/content_suggestions_service.h" #include "components/omnibox/browser/omnibox_pref_names.h" #include "components/password_manager/core/browser/password_store.h" -#include "components/power/origin_power_map.h" -#include "components/power/origin_power_map_factory.h" #include "components/prefs/pref_service.h" #include "components/previews/core/previews_ui_service.h" #include "components/search_engines/template_url_service.h" @@ -557,13 +555,6 @@ } #endif - // The power consumption history by origin contains details of websites - // that were visited. - power::OriginPowerMap* origin_power_map = - power::OriginPowerMapFactory::GetForBrowserContext(profile_); - if (origin_power_map) - origin_power_map->ClearOriginMap(nullable_filter); - // Need to clear the host cache and accumulated speculative data, as it also // reveals some history. We have no mechanism to track when these items were // created, so we'll not honor the time range.
diff --git a/chrome/browser/browsing_data/browsing_data_remover_factory.cc b/chrome/browser/browsing_data/browsing_data_remover_factory.cc index ccf45fc2..1cb6b634 100644 --- a/chrome/browser/browsing_data/browsing_data_remover_factory.cc +++ b/chrome/browser/browsing_data/browsing_data_remover_factory.cc
@@ -19,7 +19,6 @@ #include "chrome/browser/sessions/tab_restore_service_factory.h" #include "chrome/browser/web_data_service_factory.h" #include "components/keyed_service/content/browser_context_dependency_manager.h" -#include "components/power/origin_power_map_factory.h" #include "content/public/browser/browser_context.h" #include "extensions/features/features.h" @@ -59,7 +58,6 @@ DependsOn(HistoryServiceFactory::GetInstance()); DependsOn(HostContentSettingsMapFactory::GetInstance()); DependsOn(PasswordStoreFactory::GetInstance()); - DependsOn(power::OriginPowerMapFactory::GetInstance()); DependsOn(prerender::PrerenderManagerFactory::GetInstance()); DependsOn(TabRestoreServiceFactory::GetInstance()); DependsOn(TemplateURLServiceFactory::GetInstance());
diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_browser_main.cc index 1bcc099..ed2ddd5 100644 --- a/chrome/browser/chrome_browser_main.cc +++ b/chrome/browser/chrome_browser_main.cc
@@ -72,7 +72,6 @@ #include "chrome/browser/net/crl_set_fetcher.h" #include "chrome/browser/performance_monitor/performance_monitor.h" #include "chrome/browser/plugins/plugin_prefs.h" -#include "chrome/browser/power/process_power_collector.h" #include "chrome/browser/prefs/chrome_command_line_pref_store.h" #include "chrome/browser/prefs/chrome_pref_service_factory.h" #include "chrome/browser/prefs/incognito_mode_prefs.h" @@ -1914,13 +1913,13 @@ run_message_loop_ = started; browser_creator_.reset(); -#if !defined(OS_LINUX) || defined(OS_CHROMEOS) // http://crbug.com/426393 +#if !defined(OS_LINUX) || defined(OS_CHROMEOS) + // Collects power-related UMA stats for Windows, Mac, and ChromeOS. + // Linux is not supported (crbug.com/426393). power_usage_monitor_.reset(new PowerUsageMonitor()); power_usage_monitor_->Start(); #endif // !defined(OS_LINUX) || defined(OS_CHROMEOS) - process_power_collector_.reset(new ProcessPowerCollector); - process_power_collector_->Initialize(); #endif // !defined(OS_ANDROID) PostBrowserStart(); @@ -2011,10 +2010,6 @@ // Disarm the startup hang detector time bomb if it is still Arm'ed. startup_watcher_->Disarm(); - // Remove observers attached to D-Bus clients before DbusThreadManager is - // shut down. - process_power_collector_.reset(); - web_usb_detector_.reset(); for (size_t i = 0; i < chrome_extra_parts_.size(); ++i)
diff --git a/chrome/browser/chrome_browser_main.h b/chrome/browser/chrome_browser_main.h index 221b612af..4bb3f19 100644 --- a/chrome/browser/chrome_browser_main.h +++ b/chrome/browser/chrome_browser_main.h
@@ -27,7 +27,6 @@ class ChromeBrowserMainExtraParts; class FieldTrialSynchronizer; class PrefService; -class ProcessPowerCollector; class Profile; class StartupBrowserCreator; class StartupTimeBomb; @@ -147,9 +146,6 @@ ChromeBrowserFieldTrials browser_field_trials_; #if !defined(OS_ANDROID) && !defined(OS_IOS) - // A monitor for attributing power consumption to origins. - std::unique_ptr<ProcessPowerCollector> process_power_collector_; - std::unique_ptr<WebUsbDetector> web_usb_detector_; #endif
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn index aaf9540..d1250b6 100644 --- a/chrome/browser/chromeos/BUILD.gn +++ b/chrome/browser/chromeos/BUILD.gn
@@ -1651,6 +1651,7 @@ "//components/policy:generated", "//components/resources", "//components/sync", + "//components/variations:test_support", "//crypto:platform", "//dbus", "//mojo/public/cpp/system:system",
diff --git a/chrome/browser/chromeos/display/display_preferences.cc b/chrome/browser/chromeos/display/display_preferences.cc index 563f394..8956cea 100644 --- a/chrome/browser/chromeos/display/display_preferences.cc +++ b/chrome/browser/chromeos/display/display_preferences.cc
@@ -39,6 +39,10 @@ const char kInsetsBottomKey[] = "insets_bottom"; const char kInsetsRightKey[] = "insets_right"; +const char kTouchCalibrationWidth[] = "touch_calibration_width"; +const char kTouchCalibrationHeight[] = "touch_calibration_height"; +const char kTouchCalibrationPointPairs[] = "touch_calibration_point_pairs"; + // This kind of boilerplates should be done by base::JSONValueConverter but it // doesn't support classes like gfx::Insets for now. // TODO(mukai): fix base::JSONValueConverter and use it here. @@ -66,6 +70,87 @@ value->SetInteger(kInsetsRightKey, insets.right()); } +// Unmarshalls the string containing CalibrationPointPairQuad and populates +// |point_pair_quad| with the unmarshalled data. +bool ParseTouchCalibrationStringValue( + const std::string& str, + display::TouchCalibrationData::CalibrationPointPairQuad* point_pair_quad) { + DCHECK(point_pair_quad); + int x = 0, y = 0; + std::vector<std::string> parts = base::SplitString( + str, " ", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + size_t total = point_pair_quad->size(); + gfx::Point display_point, touch_point; + for (std::size_t row = 0; row < total; row++) { + if (!base::StringToInt(parts[row * total], &x) || + !base::StringToInt(parts[row * total + 1], &y)) { + return false; + } + display_point.SetPoint(x, y); + + if (!base::StringToInt(parts[row * total + 2], &x) || + !base::StringToInt(parts[row * total + 3], &y)) { + return false; + } + touch_point.SetPoint(x, y); + + (*point_pair_quad)[row] = std::make_pair(display_point, touch_point); + } + return true; +} + +// Retrieves touch calibration associated data from the dictionary and stores +// it in an instance of TouchCalibrationData struct. +bool ValueToTouchData(const base::DictionaryValue& value, + display::TouchCalibrationData* touch_calibration_data) { + display::TouchCalibrationData::CalibrationPointPairQuad* point_pair_quad = + &(touch_calibration_data->point_pairs); + + std::string str; + if (!value.GetString(kTouchCalibrationPointPairs, &str)) + return false; + + if (!ParseTouchCalibrationStringValue(str, point_pair_quad)) + return false; + + int width, height; + if (!value.GetInteger(kTouchCalibrationWidth, &width) || + !value.GetInteger(kTouchCalibrationHeight, &height)) { + return false; + } + touch_calibration_data->bounds = gfx::Size(width, height); + return true; +} + +// Stores the touch calibration data into the dictionary. +void TouchDataToValue( + const display::TouchCalibrationData& touch_calibration_data, + base::DictionaryValue* value) { + DCHECK(value); + std::string str; + for (std::size_t row = 0; row < touch_calibration_data.point_pairs.size(); + row++) { + str += + base::IntToString(touch_calibration_data.point_pairs[row].first.x()) + + " "; + str += + base::IntToString(touch_calibration_data.point_pairs[row].first.y()) + + " "; + str += + base::IntToString(touch_calibration_data.point_pairs[row].second.x()) + + " "; + str += + base::IntToString(touch_calibration_data.point_pairs[row].second.y()); + if (row != touch_calibration_data.point_pairs.size() - 1) + str += " "; + } + value->SetString(kTouchCalibrationPointPairs, str); + value->SetInteger(kTouchCalibrationWidth, + touch_calibration_data.bounds.width()); + value->SetInteger(kTouchCalibrationHeight, + touch_calibration_data.bounds.height()); +} + std::string ColorProfileToString(ui::ColorCalibrationProfile profile) { switch (profile) { case ui::COLOR_PROFILE_STANDARD: @@ -182,17 +267,18 @@ if (ValueToInsets(*dict_value, &insets)) insets_to_set = &insets; + display::TouchCalibrationData calibration_data; + display::TouchCalibrationData* calibration_data_to_set = nullptr; + if (ValueToTouchData(*dict_value, &calibration_data)) + calibration_data_to_set = &calibration_data; + ui::ColorCalibrationProfile color_profile = ui::COLOR_PROFILE_STANDARD; std::string color_profile_name; if (dict_value->GetString("color_profile_name", &color_profile_name)) color_profile = StringToColorProfile(color_profile_name); - GetDisplayManager()->RegisterDisplayProperty(id, - rotation, - ui_scale, - insets_to_set, - resolution_in_pixels, - device_scale_factor, - color_profile); + GetDisplayManager()->RegisterDisplayProperty( + id, rotation, ui_scale, insets_to_set, resolution_in_pixels, + device_scale_factor, color_profile, calibration_data_to_set); } } @@ -283,6 +369,8 @@ property_value->SetString( "color_profile_name", ColorProfileToString(info.color_profile())); } + if (info.has_touch_calibration_data()) + TouchDataToValue(info.GetTouchCalibrationData(), property_value.get()); pref_data->Set(base::Int64ToString(id), property_value.release()); } } @@ -401,4 +489,10 @@ StoreDisplayPowerState(power_state); } +bool ParseTouchCalibrationStringForTest( + const std::string& str, + display::TouchCalibrationData::CalibrationPointPairQuad* point_pair_quad) { + return ParseTouchCalibrationStringValue(str, point_pair_quad); +} + } // namespace chromeos
diff --git a/chrome/browser/chromeos/display/display_preferences.h b/chrome/browser/chromeos/display/display_preferences.h index 56d3d7f..cf5e706 100644 --- a/chrome/browser/chromeos/display/display_preferences.h +++ b/chrome/browser/chromeos/display/display_preferences.h
@@ -6,12 +6,17 @@ #define CHROME_BROWSER_CHROMEOS_DISPLAY_DISPLAY_PREFERENCES_H_ #include <stdint.h> +#include <array> #include "third_party/cros_system_api/dbus/service_constants.h" #include "ui/display/display_layout.h" class PrefRegistrySimple; +namespace gfx { +class Point; +} + namespace chromeos { // Registers the prefs associated with display settings and stored @@ -38,6 +43,13 @@ // Stores the given |power_state| for tests. void StoreDisplayPowerStateForTest(DisplayPowerState power_state); +// Parses the marshalled string data stored in local preferences for calibration +// points and populates |point_pair_quad| using the unmarshalled data. +// See TouchCalibrationData in Managed display info. +bool ParseTouchCalibrationStringForTest( + const std::string& str, + std::array<std::pair<gfx::Point, gfx::Point>, 4>* point_pair_quad); + } // namespace chromeos #endif // CHROME_BROWSER_CHROMEOS_DISPLAY_DISPLAY_PREFERENCES_H_
diff --git a/chrome/browser/chromeos/display/display_preferences_unittest.cc b/chrome/browser/chromeos/display/display_preferences_unittest.cc index deb7814..65f7a90 100644 --- a/chrome/browser/chromeos/display/display_preferences_unittest.cc +++ b/chrome/browser/chromeos/display/display_preferences_unittest.cc
@@ -51,6 +51,9 @@ const char kOffsetKey[] = "offset"; const char kPlacementDisplayIdKey[] = "placement.display_id"; const char kPlacementParentDisplayIdKey[] = "placement.parent_display_id"; +const char kTouchCalibrationWidth[] = "touch_calibration_width"; +const char kTouchCalibrationHeight[] = "touch_calibration_height"; +const char kTouchCalibrationPointPairs[] = "touch_calibration_point_pairs"; // The mean acceleration due to gravity on Earth in m/s^2. const float kMeanGravity = -9.80665f; @@ -307,6 +310,15 @@ EXPECT_FALSE(display::test::DisplayManagerTestApi(display_manager()) .SetDisplayUIScale(id2, 1.25f)); + // Set touch calibration data for display |id2|. + display::TouchCalibrationData::CalibrationPointPairQuad point_pair_quad = { + {std::make_pair(gfx::Point(10, 10), gfx::Point(11, 12)), + std::make_pair(gfx::Point(190, 10), gfx::Point(195, 8)), + std::make_pair(gfx::Point(10, 90), gfx::Point(12, 94)), + std::make_pair(gfx::Point(190, 90), gfx::Point(189, 88))}}; + gfx::Size touch_size(200, 150); + display_manager()->SetTouchCalibrationData(id2, point_pair_quad, touch_size); + const base::DictionaryValue* displays = local_state()->GetDictionary(prefs::kSecondaryDisplays); const base::DictionaryValue* layout_value = nullptr; @@ -354,6 +366,11 @@ EXPECT_EQ(12, bottom); EXPECT_EQ(13, right); + std::string touch_str; + EXPECT_FALSE(property->GetString(kTouchCalibrationPointPairs, &touch_str)); + EXPECT_FALSE(property->GetInteger(kTouchCalibrationWidth, &width)); + EXPECT_FALSE(property->GetInteger(kTouchCalibrationHeight, &height)); + std::string color_profile; EXPECT_TRUE(property->GetString("color_profile_name", &color_profile)); EXPECT_EQ("dynamic", color_profile); @@ -369,6 +386,25 @@ EXPECT_FALSE(property->GetInteger("insets_bottom", &bottom)); EXPECT_FALSE(property->GetInteger("insets_right", &right)); + display::TouchCalibrationData::CalibrationPointPairQuad stored_pair_quad; + + EXPECT_TRUE(property->GetString(kTouchCalibrationPointPairs, &touch_str)); + EXPECT_TRUE(ParseTouchCalibrationStringForTest(touch_str, &stored_pair_quad)); + + for (std::size_t row = 0; row < point_pair_quad.size(); row++) { + EXPECT_EQ(point_pair_quad[row].first.x(), stored_pair_quad[row].first.x()); + EXPECT_EQ(point_pair_quad[row].first.y(), stored_pair_quad[row].first.y()); + EXPECT_EQ(point_pair_quad[row].second.x(), + stored_pair_quad[row].second.x()); + EXPECT_EQ(point_pair_quad[row].second.y(), + stored_pair_quad[row].second.y()); + } + width = height = 0; + EXPECT_TRUE(property->GetInteger(kTouchCalibrationWidth, &width)); + EXPECT_TRUE(property->GetInteger(kTouchCalibrationHeight, &height)); + EXPECT_EQ(width, touch_size.width()); + EXPECT_EQ(height, touch_size.height()); + // |id2| doesn't have the color_profile because it doesn't have 'dynamic' in // its available list. EXPECT_FALSE(property->GetString("color_profile_name", &color_profile)); @@ -473,7 +509,7 @@ // Set new display's selected resolution. display_manager()->RegisterDisplayProperty( id2 + 1, display::Display::ROTATE_0, 1.0f, nullptr, gfx::Size(500, 400), - 1.0f, ui::COLOR_PROFILE_STANDARD); + 1.0f, ui::COLOR_PROFILE_STANDARD, nullptr); UpdateDisplay("200x200*2, 600x500#600x500|500x400"); @@ -499,7 +535,7 @@ // Set yet another new display's selected resolution. display_manager()->RegisterDisplayProperty( id2 + 1, display::Display::ROTATE_0, 1.0f, nullptr, gfx::Size(500, 400), - 1.0f, ui::COLOR_PROFILE_STANDARD); + 1.0f, ui::COLOR_PROFILE_STANDARD, nullptr); // Disconnect 2nd display first to generate new id for external display. UpdateDisplay("200x200*2"); UpdateDisplay("200x200*2, 500x400#600x500|500x400%60.0f");
diff --git a/chrome/browser/chromeos/hats/hats_finch_helper_unittest.cc b/chrome/browser/chromeos/hats/hats_finch_helper_unittest.cc index c342a01..26f3d38 100644 --- a/chrome/browser/chromeos/hats/hats_finch_helper_unittest.cc +++ b/chrome/browser/chromeos/hats/hats_finch_helper_unittest.cc
@@ -5,6 +5,8 @@ #include "chrome/browser/chromeos/hats/hats_finch_helper.h" #include <map> +#include <set> +#include <string> #include "base/strings/string_number_conversions.h" #include "base/test/scoped_feature_list.h" @@ -13,6 +15,7 @@ #include "chrome/test/base/testing_profile.h" #include "components/prefs/pref_service.h" #include "components/variations/variations_associated_data.h" +#include "components/variations/variations_params_manager.h" #include "content/public/test/test_browser_thread_bundle.h" #include "testing/gtest/include/gtest/gtest.h" @@ -20,8 +23,7 @@ namespace { -const char kGroupTesting[] = "Testing"; -const std::string kTrialName(features::kHappinessTrackingSystem.name); +const std::string kFeatureAndTrialName(features::kHappinessTrackingSystem.name); } // namespace @@ -29,18 +31,12 @@ public: using ParamMap = std::map<std::string, std::string>; - HatsFinchHelperTest() : params_manager_(kTrialName, {{}}) {} - - void SetUp() override { - scoped_feature_list_.InitAndEnableFeature( - features::kHappinessTrackingSystem); - } + HatsFinchHelperTest() {} void SetFinchSeedParams(ParamMap params) { - // Clear all the things. - variations::testing::ClearAllVariationParams(); - variations::testing::ClearAllVariationIDs(); - variations::AssociateVariationParams(kTrialName, kGroupTesting, params); + params_manager_.SetVariationParamsWithFeatureAssociations( + kFeatureAndTrialName /* trial name */, params, + std::set<std::string>{kFeatureAndTrialName} /*features to switch on */); } ParamMap CreateParamMap(std::string prob, @@ -66,7 +62,6 @@ private: variations::testing::VariationParamsManager params_manager_; - base::test::ScopedFeatureList scoped_feature_list_; DISALLOW_COPY_AND_ASSIGN(HatsFinchHelperTest); };
diff --git a/chrome/browser/component_updater/sw_reporter_installer_win_unittest.cc b/chrome/browser/component_updater/sw_reporter_installer_win_unittest.cc index d8fbb38f..fdbbf14 100644 --- a/chrome/browser/component_updater/sw_reporter_installer_win_unittest.cc +++ b/chrome/browser/component_updater/sw_reporter_installer_win_unittest.cc
@@ -23,7 +23,7 @@ #include "base/values.h" #include "base/version.h" #include "chrome/browser/safe_browsing/srt_fetcher_win.h" -#include "components/variations/variations_associated_data.h" +#include "components/variations/variations_params_manager.h" #include "content/public/test/test_browser_thread_bundle.h" #include "testing/gtest/include/gtest/gtest.h" @@ -137,25 +137,19 @@ void CreateFeatureWithParams( const std::map<std::string, std::string>& params) { - constexpr char kFeatureName[] = "ExperimentalSwReporterEngine"; + constexpr char kFeatureAndTrialName[] = "ExperimentalSwReporterEngine"; std::map<std::string, std::string> params_with_group = params; params_with_group["experiment_group_for_reporting"] = kExperimentGroupName; // Assign the given variation params to the experiment group until // |variations_| goes out of scope when the test exits. This will also - // create a FieldTrial for this group. + // create a FieldTrial for this group and associate the params with the + // feature. variations_ = std::make_unique<variations::testing::VariationParamsManager>( - kFeatureName, params_with_group); - - // Create a feature list containing only the field trial for this group, - // and enable it for the length of the test. - base::FieldTrial* trial = base::FieldTrialList::Find(kFeatureName); - ASSERT_TRUE(trial); - auto feature_list = std::make_unique<base::FeatureList>(); - feature_list->RegisterFieldTrialOverride( - kFeatureName, base::FeatureList::OVERRIDE_ENABLE_FEATURE, trial); - scoped_feature_list_.InitWithFeatureList(std::move(feature_list)); + kFeatureAndTrialName, // trial_name + params_with_group, + std::set<std::string>{kFeatureAndTrialName}); // associated_features } void ExpectAttributesWithTag(const SwReporterInstallerTraits& traits,
diff --git a/chrome/browser/extensions/api/web_navigation/web_navigation_api.cc b/chrome/browser/extensions/api/web_navigation/web_navigation_api.cc index 6d45ec58..07ef3ab 100644 --- a/chrome/browser/extensions/api/web_navigation/web_navigation_api.cc +++ b/chrome/browser/extensions/api/web_navigation/web_navigation_api.cc
@@ -271,11 +271,34 @@ return; } - helpers::DispatchOnBeforeNavigate(navigation_handle); + pending_on_before_navigate_event_ = + helpers::CreateOnBeforeNavigateEvent(navigation_handle); + + // Only dispatch the onBeforeNavigate event if the associated WebContents + // is already added to the tab strip. Otherwise the event should be delayed + // and sent after the addition, to preserve the ordering of events. + // + // TODO(nasko|devlin): This check is necessary because chrome::Navigate() + // begins the navigation before the sending the TAB_ADDED notification, and it + // is used an indication of that. It would be best if instead it was known + // when the tab was created and immediately sent the created event instead of + // waiting for the later TAB_ADDED notification, but this appears to work for + // now. + if (ExtensionTabUtil::GetTabById(ExtensionTabUtil::GetTabId(web_contents()), + web_contents()->GetBrowserContext(), false, + nullptr, nullptr, nullptr, nullptr)) { + DispatchCachedOnBeforeNavigate(); + } } void WebNavigationTabObserver::DidFinishNavigation( content::NavigationHandle* navigation_handle) { + // If there has been a DidStartNavigation call before the tab was ready to + // dispatch events, ensure that it is sent before processing the + // DidFinishNavigation. + // Note: This is exercised by WebNavigationApiTest.TargetBlankIncognito. + DispatchCachedOnBeforeNavigate(); + if (navigation_handle->HasCommitted() && !navigation_handle->IsErrorPage()) { HandleCommit(navigation_handle); return; @@ -391,6 +414,17 @@ registrar_.RemoveAll(); } +void WebNavigationTabObserver::DispatchCachedOnBeforeNavigate() { + if (!pending_on_before_navigate_event_) + return; + + // EventRouter can be null in unit tests. + EventRouter* event_router = + EventRouter::Get(web_contents()->GetBrowserContext()); + if (event_router) + event_router->BroadcastEvent(std::move(pending_on_before_navigate_event_)); +} + void WebNavigationTabObserver::HandleCommit( content::NavigationHandle* navigation_handle) { bool is_reference_fragment_navigation = IsReferenceFragmentNavigation(
diff --git a/chrome/browser/extensions/api/web_navigation/web_navigation_api.h b/chrome/browser/extensions/api/web_navigation/web_navigation_api.h index 47a29154..c663e68 100644 --- a/chrome/browser/extensions/api/web_navigation/web_navigation_api.h +++ b/chrome/browser/extensions/api/web_navigation/web_navigation_api.h
@@ -71,6 +71,9 @@ ui::PageTransition transition) override; void WebContentsDestroyed() override; + // This method dispatches the already created onBeforeNavigate event. + void DispatchCachedOnBeforeNavigate(); + private: explicit WebNavigationTabObserver(content::WebContents* web_contents); friend class content::WebContentsUserData<WebNavigationTabObserver>; @@ -95,6 +98,11 @@ // Tracks the state of the frames we are sending events for. FrameNavigationState navigation_state_; + // The latest onBeforeNavigate event this frame has generated. It is stored + // as it might not be sent immediately, but delayed until the tab is added to + // the tab strip and is ready to dispatch events. + std::unique_ptr<Event> pending_on_before_navigate_event_; + // Used for tracking registrations to redirect notifications. content::NotificationRegistrar registrar_;
diff --git a/chrome/browser/extensions/api/web_navigation/web_navigation_api_helpers.cc b/chrome/browser/extensions/api/web_navigation/web_navigation_api_helpers.cc index 3f1e5a4..2cdcaa6f1 100644 --- a/chrome/browser/extensions/api/web_navigation/web_navigation_api_helpers.cc +++ b/chrome/browser/extensions/api/web_navigation/web_navigation_api_helpers.cc
@@ -13,6 +13,7 @@ #include "base/strings/string_number_conversions.h" #include "base/time/time.h" #include "base/values.h" +#include "chrome/browser/extensions/api/web_navigation/web_navigation_api.h" #include "chrome/browser/extensions/api/web_navigation/web_navigation_api_constants.h" #include "chrome/browser/extensions/extension_tab_util.h" #include "chrome/browser/profiles/profile.h" @@ -61,8 +62,9 @@ } // namespace -// Constructs and dispatches an onBeforeNavigate event. -void DispatchOnBeforeNavigate(content::NavigationHandle* navigation_handle) { +// Constructs an onBeforeNavigate event. +std::unique_ptr<Event> CreateOnBeforeNavigateEvent( + content::NavigationHandle* navigation_handle) { GURL url(navigation_handle->GetURL()); if (navigation_handle->IsSrcdoc()) url = GURL(content::kAboutSrcDocURL); @@ -81,8 +83,15 @@ new Event(events::WEB_NAVIGATION_ON_BEFORE_NAVIGATE, web_navigation::OnBeforeNavigate::kEventName, web_navigation::OnBeforeNavigate::Create(details))); - DispatchEvent(navigation_handle->GetWebContents()->GetBrowserContext(), - std::move(event), url); + + EventFilteringInfo info; + info.SetURL(navigation_handle->GetURL()); + + event->restrict_to_browser_context = + navigation_handle->GetWebContents()->GetBrowserContext(); + event->filter_info = info; + + return event; } // Constructs and dispatches an onCommitted or onReferenceFragmentUpdated @@ -203,6 +212,12 @@ web_navigation::OnCreatedNavigationTarget::kEventName, web_navigation::OnCreatedNavigationTarget::Create(details))); DispatchEvent(browser_context, std::move(event), target_url); + + // If the target WebContents already received the onBeforeNavigate event, + // send it immediately after the onCreatedNavigationTarget above. + WebNavigationTabObserver* target_observer = + WebNavigationTabObserver::Get(target_web_contents); + target_observer->DispatchCachedOnBeforeNavigate(); } // Constructs and dispatches an onErrorOccurred event.
diff --git a/chrome/browser/extensions/api/web_navigation/web_navigation_api_helpers.h b/chrome/browser/extensions/api/web_navigation/web_navigation_api_helpers.h index db2832c1..5a447c92 100644 --- a/chrome/browser/extensions/api/web_navigation/web_navigation_api_helpers.h +++ b/chrome/browser/extensions/api/web_navigation/web_navigation_api_helpers.h
@@ -5,6 +5,7 @@ #ifndef CHROME_BROWSER_EXTENSIONS_API_WEB_NAVIGATION_WEB_NAVIGATION_API_HELPERS_H_ #define CHROME_BROWSER_EXTENSIONS_API_WEB_NAVIGATION_WEB_NAVIGATION_API_HELPERS_H_ +#include <memory> #include <string> #include "extensions/browser/extension_event_histogram_value.h" @@ -21,6 +22,8 @@ namespace extensions { +struct Event; + namespace web_navigation_api_helpers { // Returns the frame ID as it will be passed to the extension: @@ -29,7 +32,8 @@ int GetFrameId(content::RenderFrameHost* frame_host); // Create and dispatch the various events of the webNavigation API. -void DispatchOnBeforeNavigate(content::NavigationHandle* navigation_handle); +std::unique_ptr<Event> CreateOnBeforeNavigateEvent( + content::NavigationHandle* navigation_handle); void DispatchOnCommitted(events::HistogramValue histogram_value, const std::string& event_name,
diff --git a/chrome/browser/extensions/extension_startup_browsertest.cc b/chrome/browser/extensions/extension_startup_browsertest.cc index 18d84224..e2aa865 100644 --- a/chrome/browser/extensions/extension_startup_browsertest.cc +++ b/chrome/browser/extensions/extension_startup_browsertest.cc
@@ -307,6 +307,54 @@ TestInjection(true, true); } +// TODO(catmullings): Remove test in future chrome release, perhaps M59. +class DeprecatedLoadComponentExtensionSwitchBrowserTest + : public ExtensionBrowserTest { + public: + DeprecatedLoadComponentExtensionSwitchBrowserTest() {} + + void SetUpCommandLine(base::CommandLine* command_line) override; + + ExtensionRegistry* GetExtensionRegistry() { + return ExtensionRegistry::Get(browser()->profile()); + } +}; + +void DeprecatedLoadComponentExtensionSwitchBrowserTest::SetUpCommandLine( + base::CommandLine* command_line) { + ExtensionBrowserTest::SetUpCommandLine(command_line); + base::FilePath fp1(test_data_dir_.AppendASCII("app_dot_com_app/")); + base::FilePath fp2(test_data_dir_.AppendASCII("app/")); + + command_line->AppendSwitchASCII( + "load-component-extension", + fp1.AsUTF8Unsafe() + "," + fp2.AsUTF8Unsafe()); +} + +// Tests that the --load-component-extension flag is not supported. +IN_PROC_BROWSER_TEST_F(DeprecatedLoadComponentExtensionSwitchBrowserTest, + DefunctLoadComponentExtensionFlag) { + EXPECT_TRUE(extension_service()->extensions_enabled()); + + // Checks that the extensions loaded with the --load-component-extension flag + // are not installed. + bool is_app_dot_com_extension_installed = false; + bool is_app_test_extension_installed = false; + for (const scoped_refptr<const extensions::Extension>& extension : + GetExtensionRegistry()->enabled_extensions()) { + if (extension->name() == "App Dot Com: The App") { + is_app_dot_com_extension_installed = true; + } else if (extension->name() == "App Test") { + is_app_test_extension_installed = true; + } else { + EXPECT_TRUE( + extensions::Manifest::IsComponentLocation(extension->location())); + } + } + EXPECT_FALSE(is_app_dot_com_extension_installed); + EXPECT_FALSE(is_app_test_extension_installed); +} + class DisableExtensionsExceptBrowserTest : public ExtensionBrowserTest { public: DisableExtensionsExceptBrowserTest() {}
diff --git a/chrome/browser/extensions/extension_system_impl.cc b/chrome/browser/extensions/extension_system_impl.cc index 6070a2dc..d862662f 100644 --- a/chrome/browser/extensions/extension_system_impl.cc +++ b/chrome/browser/extensions/extension_system_impl.cc
@@ -254,21 +254,6 @@ extension_service_->component_loader()->AddDefaultComponentExtensions( skip_session_extensions); #endif - if (command_line->HasSwitch(switches::kLoadComponentExtension)) { - base::CommandLine::StringType path_list = - command_line->GetSwitchValueNative(switches::kLoadComponentExtension); - base::StringTokenizerT<base::CommandLine::StringType, - base::CommandLine::StringType::const_iterator> - t(path_list, FILE_PATH_LITERAL(",")); - while (t.GetNext()) { - // Load the component extension manifest synchronously. - // Blocking the UI thread is acceptable here since - // this flag designated for developers. - base::ThreadRestrictions::ScopedAllowIO allow_io; - extension_service_->component_loader()->AddOrReplace( - base::FilePath(t.token())); - } - } app_sorting_.reset(new ChromeAppSorting(profile_));
diff --git a/chrome/browser/power/process_power_collector.cc b/chrome/browser/power/process_power_collector.cc deleted file mode 100644 index 9c8fc3e..0000000 --- a/chrome/browser/power/process_power_collector.cc +++ /dev/null
@@ -1,208 +0,0 @@ -// Copyright 2014 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/power/process_power_collector.h" - -#include <utility> - -#include "base/memory/ptr_util.h" -#include "base/process/process_handle.h" -#include "base/process/process_metrics.h" -#include "base/stl_util.h" -#include "build/build_config.h" -#include "chrome/browser/browser_process.h" -#include "chrome/browser/profiles/profile.h" -#include "chrome/browser/profiles/profile_manager.h" -#include "chrome/browser/ui/tab_contents/tab_contents_iterator.h" -#include "components/power/origin_power_map.h" -#include "components/power/origin_power_map_factory.h" -#include "content/public/browser/browser_context.h" -#include "content/public/browser/render_process_host.h" -#include "content/public/browser/web_contents.h" -#include "extensions/browser/app_window/app_window.h" -#include "extensions/browser/app_window/app_window_registry.h" -#include "url/gurl.h" - -#if defined(OS_CHROMEOS) -#include "chromeos/dbus/dbus_thread_manager.h" -#include "chromeos/dbus/power_manager/power_supply_properties.pb.h" -#endif - -namespace { -const int kSecondsPerSample = 30; -} - -ProcessPowerCollector::PerProcessData::PerProcessData( - std::unique_ptr<base::ProcessMetrics> metrics, - const GURL& origin, - Profile* profile) - : metrics_(std::move(metrics)), - profile_(profile), - last_origin_(origin), - last_cpu_percent_(0), - seen_this_cycle_(true) {} - -ProcessPowerCollector::PerProcessData::PerProcessData() - : profile_(nullptr), last_cpu_percent_(0.0), seen_this_cycle_(false) {} - -ProcessPowerCollector::PerProcessData::~PerProcessData() { -} - -ProcessPowerCollector::ProcessPowerCollector() - : scale_factor_(1.0) { -#if defined(OS_CHROMEOS) - chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver( - this); -#endif -} - -ProcessPowerCollector::~ProcessPowerCollector() { -#if defined(OS_CHROMEOS) - chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver( - this); -#endif -} - -#if defined(OS_CHROMEOS) -void ProcessPowerCollector::PowerChanged( - const power_manager::PowerSupplyProperties& prop) { - if (prop.battery_state() == - power_manager::PowerSupplyProperties::DISCHARGING) { - if (!timer_.IsRunning()) - StartTimer(); - scale_factor_ = prop.battery_discharge_rate(); - } else { - timer_.Stop(); - } -} -#endif - -void ProcessPowerCollector::Initialize() { - StartTimer(); -} - -double ProcessPowerCollector::UpdatePowerConsumptionForTesting() { - return UpdatePowerConsumption(); -} - -void ProcessPowerCollector::StartTimer() { - DCHECK(!timer_.IsRunning()); - timer_.Start(FROM_HERE, - base::TimeDelta::FromSeconds(kSecondsPerSample), - this, - &ProcessPowerCollector::HandleUpdateTimeout); -} - -// Work around VS 2015 code-gen bug, seen in Update 2 and Update 3. -// crbug.com/640588 -#if defined(COMPILER_MSVC) -#pragma optimize("", off) -#endif -double ProcessPowerCollector::UpdatePowerConsumption() { - double total_cpu_percent = SynchronizeProcesses(); - - // Invalidate the process for the next cycle. - for (auto& metrics : metrics_map_) - metrics.second->set_seen_this_cycle(false); - - RecordCpuUsageByOrigin(total_cpu_percent); - return total_cpu_percent; -} - -void ProcessPowerCollector::HandleUpdateTimeout() { - UpdatePowerConsumption(); -} -#if defined(COMPILER_MSVC) -#pragma optimize("", on) -#endif - -double ProcessPowerCollector::SynchronizeProcesses() { - // Update all tabs. - for (TabContentsIterator it; !it.done(); it.Next()) { - content::RenderProcessHost* render_process = it->GetRenderProcessHost(); - // Skip incognito web contents. - if (render_process->GetBrowserContext()->IsOffTheRecord()) - continue; - UpdateProcessInMap(render_process, it->GetLastCommittedURL().GetOrigin()); - } - - // Iterate over all profiles to find all app windows to attribute all apps. - ProfileManager* pm = g_browser_process->profile_manager(); - std::vector<Profile*> open_profiles = pm->GetLoadedProfiles(); - for (Profile* profile : open_profiles) { - const extensions::AppWindowRegistry::AppWindowList& app_windows = - extensions::AppWindowRegistry::Get(profile)->app_windows(); - for (auto* window : app_windows) { - content::WebContents* web_contents = window->web_contents(); - UpdateProcessInMap(web_contents->GetRenderProcessHost(), - web_contents->GetLastCommittedURL().GetOrigin()); - } - } - - // Remove invalid processes and sum up the cpu cycle. - double total_cpu_percent = 0.0; - ProcessMetricsMap::iterator it = metrics_map_.begin(); - while (it != metrics_map_.end()) { - if (!it->second->seen_this_cycle()) { - metrics_map_.erase(it++); - continue; - } - - total_cpu_percent += it->second->last_cpu_percent(); - ++it; - } - - return total_cpu_percent; -} - -void ProcessPowerCollector::RecordCpuUsageByOrigin(double total_cpu_percent) { - DCHECK_GE(total_cpu_percent, 0); - if (total_cpu_percent == 0) - return; - - for (auto& metrics : metrics_map_) { - double last_process_power_usage = metrics.second->last_cpu_percent(); - last_process_power_usage *= scale_factor_ / total_cpu_percent; - - const GURL& origin = metrics.second->last_origin(); - power::OriginPowerMap* origin_power_map = - power::OriginPowerMapFactory::GetForBrowserContext( - metrics.second->profile()); - // |origin_power_map| can be NULL, if the profile is a guest profile in - // Chrome OS. - if (origin_power_map) - origin_power_map->AddPowerForOrigin(origin, last_process_power_usage); - } - - // Iterate over all profiles to let them know we've finished updating. - ProfileManager* pm = g_browser_process->profile_manager(); - std::vector<Profile*> open_profiles = pm->GetLoadedProfiles(); - for (Profile* profile : open_profiles) { - power::OriginPowerMap* origin_power_map = - power::OriginPowerMapFactory::GetForBrowserContext(profile); - if (origin_power_map) - origin_power_map->OnAllOriginsUpdated(); - } -} - -void ProcessPowerCollector::UpdateProcessInMap( - const content::RenderProcessHost* rph, - const GURL& origin) { - base::ProcessHandle handle = rph->GetHandle(); - if (!base::ContainsKey(metrics_map_, handle)) { - metrics_map_[handle] = base::MakeUnique<PerProcessData>( -#if defined(OS_MACOSX) - base::ProcessMetrics::CreateProcessMetrics(handle, nullptr), -#else - base::ProcessMetrics::CreateProcessMetrics(handle), -#endif - origin, Profile::FromBrowserContext(rph->GetBrowserContext())); - } - - PerProcessData* process_data = metrics_map_[handle].get(); - process_data->set_last_cpu_percent(std::max(0.0, - cpu_usage_callback_.is_null() ? process_data->metrics()->GetCPUUsage() - : cpu_usage_callback_.Run(handle))); - process_data->set_seen_this_cycle(true); -}
diff --git a/chrome/browser/power/process_power_collector.h b/chrome/browser/power/process_power_collector.h deleted file mode 100644 index c1a5a75..0000000 --- a/chrome/browser/power/process_power_collector.h +++ /dev/null
@@ -1,152 +0,0 @@ -// Copyright 2014 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_POWER_PROCESS_POWER_COLLECTOR_H_ -#define CHROME_BROWSER_POWER_PROCESS_POWER_COLLECTOR_H_ - -#include <map> -#include <memory> - -#include "base/macros.h" -#include "base/process/process_handle.h" -#include "base/process/process_metrics.h" -#include "base/timer/timer.h" -#include "build/build_config.h" -#include "components/power/origin_power_map_factory.h" -#include "url/gurl.h" - -#if defined(OS_CHROMEOS) -#include "chromeos/dbus/power_manager_client.h" -#endif - -class Profile; - -namespace content { -class RenderProcessHost; -} - -#if defined(OS_CHROMEOS) -namespace power_manager { -class PowerSupplyProperties; -} -#endif - -// Manages regular updates of the profile power consumption. -class ProcessPowerCollector -#if defined(OS_CHROMEOS) - : public chromeos::PowerManagerClient::Observer -#endif - { - public: - class PerProcessData { - public: - PerProcessData(std::unique_ptr<base::ProcessMetrics> metrics, - const GURL& origin, - Profile* profile); - PerProcessData(); - ~PerProcessData(); - - base::ProcessMetrics* metrics() const { return metrics_.get(); } - Profile* profile() const { return profile_; } - const GURL& last_origin() const { return last_origin_; } - int last_cpu_percent() const { return last_cpu_percent_; } - bool seen_this_cycle() const { return seen_this_cycle_; } - void set_last_cpu_percent(double new_cpu) { last_cpu_percent_ = new_cpu; } - void set_seen_this_cycle(bool seen) { seen_this_cycle_ = seen; } - - private: - // |metrics_| holds the ProcessMetrics information for the given process. - std::unique_ptr<base::ProcessMetrics> metrics_; - - // |profile| is the profile that is visiting the |last_origin_|. - // It is not owned by PerProcessData. - Profile* profile_; - - // |last_origin_| is the last origin visited by the process. - GURL last_origin_; - - // |last_cpu_percent_| is the proportion of the CPU used since the last - // query. - double last_cpu_percent_; - - // |seen_this_cycle| represents if the process still exists in this cycle. - // If it doesn't, we erase the PerProcessData. - bool seen_this_cycle_; - - DISALLOW_COPY_AND_ASSIGN(PerProcessData); - }; - - // A map from all process handles to a metric. - using ProcessMetricsMap = - std::map<base::ProcessHandle, std::unique_ptr<PerProcessData>>; - - // A callback used to define mock CPU usage for testing. - using CpuUsageCallback = base::Callback<double(base::ProcessHandle)>; - - // On Chrome OS, can only be initialized after the DBusThreadManager has been - // initialized. - ProcessPowerCollector(); -#if defined(OS_CHROMEOS) - // On Chrome OS, can only be destroyed before DBusThreadManager is. - ~ProcessPowerCollector() override; -#else - virtual ~ProcessPowerCollector(); -#endif - - void set_cpu_usage_callback_for_testing(const CpuUsageCallback& callback) { - cpu_usage_callback_ = callback; - } - - ProcessMetricsMap* metrics_map_for_testing() { return &metrics_map_; } - -#if defined(OS_CHROMEOS) - // PowerManagerClient::Observer implementation: - void PowerChanged(const power_manager::PowerSupplyProperties& prop) override; -#endif - - // Begin periodically updating the power consumption numbers by profile. - void Initialize(); - - // Calls UpdatePowerConsumption() and returns the total CPU percent. - double UpdatePowerConsumptionForTesting(); - - private: - // Starts the timer for updating the power consumption. - void StartTimer(); - - // Calls SynchronizerProcesses() and RecordCpuUsageByOrigin() to update the - // |metrics_map_| and attribute power consumption. Invoked by |timer_| and as - // a helper method for UpdatePowerConsumptionForTesting(). - double UpdatePowerConsumption(); - - // Calls UpdatePowerConsumption(). Invoked by |timer_|. - void HandleUpdateTimeout(); - - // Synchronizes the currently active processes to the |metrics_map_| and - // returns the total amount of cpu usage in the cycle. - double SynchronizeProcesses(); - - // Attributes the power usage to the profiles and origins using the - // information from SynchronizeProcesses() given a total amount - // of CPU used in this cycle, |total_cpu_percent|. - void RecordCpuUsageByOrigin(double total_cpu_percent); - - // Adds the information from a given RenderProcessHost to the |metrics_map_| - // for a given origin. Called by SynchronizeProcesses(). - void UpdateProcessInMap(const content::RenderProcessHost* render_process, - const GURL& origin); - - ProcessMetricsMap metrics_map_; - base::RepeatingTimer timer_; - - // Callback to use to get CPU usage if set. - CpuUsageCallback cpu_usage_callback_; - - // The factor to scale the CPU usage by. - double scale_factor_; - - DISALLOW_COPY_AND_ASSIGN(ProcessPowerCollector); -}; - -#endif // CHROME_BROWSER_POWER_PROCESS_POWER_COLLECTOR_H_
diff --git a/chrome/browser/power/process_power_collector_unittest.cc b/chrome/browser/power/process_power_collector_unittest.cc deleted file mode 100644 index dffe11ac..0000000 --- a/chrome/browser/power/process_power_collector_unittest.cc +++ /dev/null
@@ -1,291 +0,0 @@ -// Copyright 2014 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/power/process_power_collector.h" - -#include "build/build_config.h" -#include "chrome/browser/profiles/profile_manager.h" -#include "chrome/browser/ui/apps/chrome_app_delegate.h" -#include "chrome/browser/ui/browser_commands.h" -#include "chrome/browser/ui/tabs/tab_strip_model.h" -#include "chrome/test/base/browser_with_test_window_test.h" -#include "chrome/test/base/testing_browser_process.h" -#include "chrome/test/base/testing_profile_manager.h" -#include "components/power/origin_power_map.h" -#include "components/power/origin_power_map_factory.h" -#include "content/public/browser/site_instance.h" -#include "content/public/test/mock_render_process_host.h" -#include "extensions/browser/app_window/app_window_client.h" -#include "extensions/browser/app_window/app_window_registry.h" -#include "extensions/browser/app_window/native_app_window.h" -#include "extensions/browser/app_window/test_app_window_contents.h" -#include "extensions/common/extension.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "url/gurl.h" - -#if defined(OS_CHROMEOS) -#include "chromeos/dbus/power_manager/power_supply_properties.pb.h" -#endif - -using power::OriginPowerMap; -using power::OriginPowerMapFactory; - -class BrowserProcessPowerTest : public BrowserWithTestWindowTest { - public: - BrowserProcessPowerTest() {} - ~BrowserProcessPowerTest() override {} - - void SetUp() override { - BrowserWithTestWindowTest::SetUp(); - collector.reset(new ProcessPowerCollector); - -#if defined(OS_CHROMEOS) - power_manager::PowerSupplyProperties prop; - prop.set_external_power(power_manager::PowerSupplyProperties::AC); - prop.set_battery_state(power_manager::PowerSupplyProperties::DISCHARGING); - prop.set_battery_percent(20.00); - prop.set_battery_discharge_rate(1); - collector->PowerChanged(prop); -#endif - - profile_manager_.reset( - new TestingProfileManager(TestingBrowserProcess::GetGlobal())); - ASSERT_TRUE(profile_manager_->SetUp()); - } - - void TearDown() override { - collector.reset(); - BrowserWithTestWindowTest::TearDown(); - } - - // Mocks out CPU usage for all processes as |value| percent. - double ReturnCpuAsConstant(double value, base::ProcessHandle handle) { - return value; - } - - protected: - content::MockRenderProcessHost* GetProcess(Browser* browser) { - return static_cast<content::MockRenderProcessHost*>( - browser->tab_strip_model() - ->GetActiveWebContents() - ->GetRenderViewHost() - ->GetProcess()); - } - - std::unique_ptr<base::ProcessHandle> MakeProcessHandle(int process_id) { - std::unique_ptr<base::ProcessHandle> proc_handle(new base::ProcessHandle( -#if defined(OS_WIN) - reinterpret_cast<HANDLE>(process_id)) -#else - process_id) -#endif - ); - return proc_handle; - } - - std::unique_ptr<ProcessPowerCollector> collector; - std::unique_ptr<TestingProfileManager> profile_manager_; -}; - -TEST_F(BrowserProcessPowerTest, NoSite) { - collector->UpdatePowerConsumptionForTesting(); - EXPECT_EQ(0u, collector->metrics_map_for_testing()->size()); -} - -TEST_F(BrowserProcessPowerTest, OneSite) { - GURL url("http://www.google.com"); - AddTab(browser(), url); - collector->UpdatePowerConsumptionForTesting(); - ProcessPowerCollector::ProcessMetricsMap* metrics_map = - collector->metrics_map_for_testing(); - EXPECT_EQ(1u, metrics_map->size()); - - // Create fake process numbers. - GetProcess(browser())->SetProcessHandle(MakeProcessHandle(1)); - - OriginPowerMap* origin_power_map = - OriginPowerMapFactory::GetForBrowserContext(profile()); - EXPECT_EQ(0, origin_power_map->GetPowerForOrigin(url)); - - collector->set_cpu_usage_callback_for_testing( - base::Bind(&BrowserProcessPowerTest::ReturnCpuAsConstant, - base::Unretained(this), - 5)); - EXPECT_DOUBLE_EQ(5, collector->UpdatePowerConsumptionForTesting()); - EXPECT_EQ(100, origin_power_map->GetPowerForOrigin(url)); -} - -TEST_F(BrowserProcessPowerTest, MultipleSites) { - Browser::CreateParams native_params(profile()); - GURL url1("http://www.google.com"); - GURL url2("http://www.example.com"); - GURL url3("https://www.google.com"); - std::unique_ptr<Browser> browser2( - chrome::CreateBrowserWithTestWindowForParams(&native_params)); - std::unique_ptr<Browser> browser3( - chrome::CreateBrowserWithTestWindowForParams(&native_params)); - AddTab(browser(), url1); - AddTab(browser2.get(), url2); - AddTab(browser3.get(), url3); - - // Create fake process numbers. - GetProcess(browser())->SetProcessHandle(MakeProcessHandle(1)); - GetProcess(browser2.get())->SetProcessHandle(MakeProcessHandle(2)); - GetProcess(browser3.get())->SetProcessHandle(MakeProcessHandle(3)); - - collector->UpdatePowerConsumptionForTesting(); - ProcessPowerCollector::ProcessMetricsMap* metrics_map = - collector->metrics_map_for_testing(); - EXPECT_EQ(3u, metrics_map->size()); - - // Since all handlers are uninitialized, this should be 0. - EXPECT_DOUBLE_EQ(0, collector->UpdatePowerConsumptionForTesting()); - OriginPowerMap* origin_power_map = - OriginPowerMapFactory::GetForBrowserContext(profile()); - EXPECT_EQ(0, origin_power_map->GetPowerForOrigin(url1)); - EXPECT_EQ(0, origin_power_map->GetPowerForOrigin(url2)); - EXPECT_EQ(0, origin_power_map->GetPowerForOrigin(url3)); - - collector->set_cpu_usage_callback_for_testing( - base::Bind(&BrowserProcessPowerTest::ReturnCpuAsConstant, - base::Unretained(this), - 5)); - EXPECT_DOUBLE_EQ(15, collector->UpdatePowerConsumptionForTesting()); - EXPECT_EQ(33, origin_power_map->GetPowerForOrigin(url1)); - EXPECT_EQ(33, origin_power_map->GetPowerForOrigin(url2)); - EXPECT_EQ(33, origin_power_map->GetPowerForOrigin(url3)); - - // Close some tabs and verify that they are removed from the metrics map. - chrome::CloseTab(browser2.get()); - chrome::CloseTab(browser3.get()); - - collector->UpdatePowerConsumptionForTesting(); - EXPECT_EQ(1u, metrics_map->size()); -} - -TEST_F(BrowserProcessPowerTest, IncognitoDoesntRecordPowerUsage) { - Browser::CreateParams native_params(profile()->GetOffTheRecordProfile()); - std::unique_ptr<Browser> incognito_browser( - chrome::CreateBrowserWithTestWindowForParams(&native_params)); - GURL url("http://www.google.com"); - AddTab(browser(), url); - - GURL hidden_url("http://foo.com"); - AddTab(incognito_browser.get(), hidden_url); - - // Create fake process numbers. - GetProcess(browser())->SetProcessHandle(MakeProcessHandle(1)); - GetProcess(incognito_browser.get())->SetProcessHandle(MakeProcessHandle(2)); - - EXPECT_DOUBLE_EQ(0, collector->UpdatePowerConsumptionForTesting()); - ProcessPowerCollector::ProcessMetricsMap* metrics_map = - collector->metrics_map_for_testing(); - EXPECT_EQ(1u, metrics_map->size()); - - OriginPowerMap* origin_power_map = - OriginPowerMapFactory::GetForBrowserContext(profile()); - EXPECT_EQ(0, origin_power_map->GetPowerForOrigin(url)); - - collector->set_cpu_usage_callback_for_testing( - base::Bind(&BrowserProcessPowerTest::ReturnCpuAsConstant, - base::Unretained(this), - 5)); - EXPECT_DOUBLE_EQ(5, collector->UpdatePowerConsumptionForTesting()); - - // Verify that the incognito data was not stored. - EXPECT_EQ(100, origin_power_map->GetPowerForOrigin(url)); - EXPECT_EQ(0, origin_power_map->GetPowerForOrigin(hidden_url)); - - chrome::CloseTab(incognito_browser.get()); -} - -TEST_F(BrowserProcessPowerTest, MultipleProfilesRecordSeparately) { - std::unique_ptr<Profile> other_profile(CreateProfile()); - Browser::CreateParams native_params(other_profile.get()); - std::unique_ptr<Browser> other_user( - chrome::CreateBrowserWithTestWindowForParams(&native_params)); - - GURL url("http://www.google.com"); - AddTab(browser(), url); - - GURL hidden_url("http://foo.com"); - AddTab(other_user.get(), hidden_url); - - // Create fake process numbers. - GetProcess(browser())->SetProcessHandle(MakeProcessHandle(1)); - GetProcess(other_user.get())->SetProcessHandle(MakeProcessHandle(2)); - - EXPECT_DOUBLE_EQ(0, collector->UpdatePowerConsumptionForTesting()); - EXPECT_EQ(2u, collector->metrics_map_for_testing()->size()); - - collector->set_cpu_usage_callback_for_testing( - base::Bind(&BrowserProcessPowerTest::ReturnCpuAsConstant, - base::Unretained(this), - 5)); - EXPECT_DOUBLE_EQ(10, collector->UpdatePowerConsumptionForTesting()); - - // profile() should have an entry for |url| but not |hidden_url|. - OriginPowerMap* origin_power_map_first = - OriginPowerMapFactory::GetForBrowserContext(profile()); - EXPECT_EQ(100, origin_power_map_first->GetPowerForOrigin(url)); - EXPECT_EQ(0, origin_power_map_first->GetPowerForOrigin(hidden_url)); - - // |other_profile| should have an entry for |hidden_url| but not |url|. - OriginPowerMap* origin_power_map_second = - OriginPowerMapFactory::GetForBrowserContext(other_profile.get()); - EXPECT_EQ(0, origin_power_map_second->GetPowerForOrigin(url)); - EXPECT_EQ(100, origin_power_map_second->GetPowerForOrigin(hidden_url)); - - // Clean up - chrome::CloseTab(other_user.get()); -} - -TEST_F(BrowserProcessPowerTest, AppsRecordPowerUsage) { -// Install an app (an extension*). -#if defined(OS_WIN) - base::FilePath extension_path(FILE_PATH_LITERAL("c:\\foo")); -#elif defined(OS_POSIX) - base::FilePath extension_path(FILE_PATH_LITERAL("/foo")); -#endif - base::DictionaryValue manifest; - manifest.SetString("name", "Fake Name"); - manifest.SetString("version", "1"); - std::string error; - char kTestAppId[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - scoped_refptr<extensions::Extension> extension( - extensions::Extension::Create(extension_path, - extensions::Manifest::INTERNAL, - manifest, - extensions::Extension::NO_FLAGS, - kTestAppId, - &error)); - EXPECT_TRUE(extension.get()) << error; - - Profile* current_profile = - profile_manager_->CreateTestingProfile("Test user"); - GURL url("chrome-extension://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - extensions::AppWindow* window = new extensions::AppWindow( - current_profile, new ChromeAppDelegate(false), extension.get()); - content::WebContents* web_contents( - content::WebContents::Create(content::WebContents::CreateParams( - current_profile, - content::SiteInstance::CreateForURL(current_profile, url)))); - window->SetAppWindowContentsForTesting( - std::unique_ptr<extensions::AppWindowContents>( - new extensions::TestAppWindowContents(web_contents))); - extensions::AppWindowRegistry* app_registry = - extensions::AppWindowRegistry::Get(current_profile); - app_registry->AddAppWindow(window); - - collector->set_cpu_usage_callback_for_testing( - base::Bind(&BrowserProcessPowerTest::ReturnCpuAsConstant, - base::Unretained(this), - 5)); - collector->UpdatePowerConsumptionForTesting(); - EXPECT_EQ(1u, collector->metrics_map_for_testing()->size()); - - window->OnNativeClose(); - collector->UpdatePowerConsumptionForTesting(); - EXPECT_EQ(0u, collector->metrics_map_for_testing()->size()); -}
diff --git a/chrome/browser/supervised_user/supervised_user_service.cc b/chrome/browser/supervised_user/supervised_user_service.cc index 1f25785..aed7f3d 100644 --- a/chrome/browser/supervised_user/supervised_user_service.cc +++ b/chrome/browser/supervised_user/supervised_user_service.cc
@@ -723,14 +723,14 @@ } void SupervisedUserService::FinishSetupSyncWhenReady() { - // If we're already waiting for the Sync backend, there's nothing to do here. + // If we're already waiting for the sync engine, there's nothing to do here. if (waiting_for_sync_initialization_) return; - // Continue in FinishSetupSync() once the Sync backend has been initialized. + // Continue in FinishSetupSync() once the sync engine has been initialized. browser_sync::ProfileSyncService* service = ProfileSyncServiceFactory::GetForProfile(profile_); - if (service->IsBackendInitialized()) { + if (service->IsEngineInitialized()) { FinishSetupSync(); } else { service->AddObserver(this); @@ -741,7 +741,7 @@ void SupervisedUserService::FinishSetupSync() { browser_sync::ProfileSyncService* service = ProfileSyncServiceFactory::GetForProfile(profile_); - DCHECK(service->IsBackendInitialized()); + DCHECK(service->IsEngineInitialized()); // Sync nothing (except types which are set via GetPreferredDataTypes). bool sync_everything = false; @@ -1263,7 +1263,7 @@ void SupervisedUserService::OnStateChanged() { browser_sync::ProfileSyncService* service = ProfileSyncServiceFactory::GetForProfile(profile_); - if (waiting_for_sync_initialization_ && service->IsBackendInitialized()) { + if (waiting_for_sync_initialization_ && service->IsEngineInitialized()) { waiting_for_sync_initialization_ = false; service->RemoveObserver(this); FinishSetupSync();
diff --git a/chrome/browser/sync/profile_sync_service_android.cc b/chrome/browser/sync/profile_sync_service_android.cc index a4d8521..66e1d79 100644 --- a/chrome/browser/sync/profile_sync_service_android.cc +++ b/chrome/browser/sync/profile_sync_service_android.cc
@@ -172,11 +172,11 @@ return sync_service_->IsSyncActive(); } -jboolean ProfileSyncServiceAndroid::IsBackendInitialized( +jboolean ProfileSyncServiceAndroid::IsEngineInitialized( JNIEnv* env, const JavaParamRef<jobject>&) { DCHECK_CURRENTLY_ON(BrowserThread::UI); - return sync_service_->IsBackendInitialized(); + return sync_service_->IsEngineInitialized(); } void ProfileSyncServiceAndroid::SetSetupInProgress(
diff --git a/chrome/browser/sync/profile_sync_service_android.h b/chrome/browser/sync/profile_sync_service_android.h index 068a5b8b..332ebc2 100644 --- a/chrome/browser/sync/profile_sync_service_android.h +++ b/chrome/browser/sync/profile_sync_service_android.h
@@ -59,9 +59,8 @@ const base::android::JavaParamRef<jobject>& obj); jboolean IsSyncActive(JNIEnv* env, const base::android::JavaParamRef<jobject>& obj); - jboolean IsBackendInitialized( - JNIEnv* env, - const base::android::JavaParamRef<jobject>& obj); + jboolean IsEngineInitialized(JNIEnv* env, + const base::android::JavaParamRef<jobject>& obj); void SetSetupInProgress(JNIEnv* env, const base::android::JavaParamRef<jobject>& obj, jboolean in_progress);
diff --git a/chrome/browser/sync/sync_startup_tracker.cc b/chrome/browser/sync/sync_startup_tracker.cc index d65454b4..720ac40 100644 --- a/chrome/browser/sync/sync_startup_tracker.cc +++ b/chrome/browser/sync/sync_startup_tracker.cc
@@ -61,8 +61,8 @@ return SYNC_STARTUP_ERROR; } - // If the sync backend has started up, notify the callback. - if (service->IsBackendInitialized()) + // If the sync engine has started up, notify the callback. + if (service->IsEngineInitialized()) return SYNC_STARTUP_COMPLETE; // If the sync service has some kind of error, report to the user. @@ -76,7 +76,7 @@ return SYNC_STARTUP_ERROR; } - // No error detected yet, but the sync backend hasn't started up yet, so + // No error detected yet, but the sync engine hasn't started up yet, so // we're in the pending state. return SYNC_STARTUP_PENDING; }
diff --git a/chrome/browser/sync/sync_startup_tracker_unittest.cc b/chrome/browser/sync/sync_startup_tracker_unittest.cc index 069634a..6faa4c9 100644 --- a/chrome/browser/sync/sync_startup_tracker_unittest.cc +++ b/chrome/browser/sync/sync_startup_tracker_unittest.cc
@@ -55,7 +55,7 @@ void SetupNonInitializedPSS() { EXPECT_CALL(*mock_pss_, GetAuthError()) .WillRepeatedly(ReturnRef(no_error_)); - EXPECT_CALL(*mock_pss_, IsBackendInitialized()) + EXPECT_CALL(*mock_pss_, IsEngineInitialized()) .WillRepeatedly(Return(false)); EXPECT_CALL(*mock_pss_, HasUnrecoverableError()) .WillRepeatedly(Return(false)); @@ -70,7 +70,7 @@ }; TEST_F(SyncStartupTrackerTest, SyncAlreadyInitialized) { - EXPECT_CALL(*mock_pss_, IsBackendInitialized()).WillRepeatedly(Return(true)); + EXPECT_CALL(*mock_pss_, IsEngineInitialized()).WillRepeatedly(Return(true)); EXPECT_CALL(*mock_pss_, CanSyncStart()).WillRepeatedly(Return(true)); EXPECT_CALL(observer_, SyncStartupCompleted()); SyncStartupTracker tracker(profile_.get(), &observer_); @@ -79,7 +79,7 @@ TEST_F(SyncStartupTrackerTest, SyncNotSignedIn) { // Make sure that we get a SyncStartupFailed() callback if sync is not logged // in. - EXPECT_CALL(*mock_pss_, IsBackendInitialized()).WillRepeatedly(Return(false)); + EXPECT_CALL(*mock_pss_, IsEngineInitialized()).WillRepeatedly(Return(false)); EXPECT_CALL(*mock_pss_, CanSyncStart()).WillRepeatedly(Return(false)); EXPECT_CALL(observer_, SyncStartupFailed()); SyncStartupTracker tracker(profile_.get(), &observer_); @@ -88,7 +88,7 @@ TEST_F(SyncStartupTrackerTest, SyncAuthError) { // Make sure that we get a SyncStartupFailed() callback if sync gets an auth // error. - EXPECT_CALL(*mock_pss_, IsBackendInitialized()).WillRepeatedly(Return(false)); + EXPECT_CALL(*mock_pss_, IsEngineInitialized()).WillRepeatedly(Return(false)); EXPECT_CALL(*mock_pss_, CanSyncStart()).WillRepeatedly(Return(true)); GoogleServiceAuthError error( GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); @@ -105,7 +105,7 @@ SyncStartupTracker tracker(profile_.get(), &observer_); Mock::VerifyAndClearExpectations(&observer_); // Now, mark the PSS as initialized. - EXPECT_CALL(*mock_pss_, IsBackendInitialized()).WillRepeatedly(Return(true)); + EXPECT_CALL(*mock_pss_, IsEngineInitialized()).WillRepeatedly(Return(true)); EXPECT_CALL(observer_, SyncStartupCompleted()); tracker.OnStateChanged(); } @@ -120,7 +120,7 @@ Mock::VerifyAndClearExpectations(mock_pss_); // Now, mark the PSS as having an auth error. - EXPECT_CALL(*mock_pss_, IsBackendInitialized()).WillRepeatedly(Return(false)); + EXPECT_CALL(*mock_pss_, IsEngineInitialized()).WillRepeatedly(Return(false)); EXPECT_CALL(*mock_pss_, CanSyncStart()).WillRepeatedly(Return(true)); GoogleServiceAuthError error( GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); @@ -139,7 +139,7 @@ Mock::VerifyAndClearExpectations(mock_pss_); // Now, mark the PSS as having an unrecoverable error. - EXPECT_CALL(*mock_pss_, IsBackendInitialized()).WillRepeatedly(Return(false)); + EXPECT_CALL(*mock_pss_, IsEngineInitialized()).WillRepeatedly(Return(false)); EXPECT_CALL(*mock_pss_, CanSyncStart()).WillRepeatedly(Return(true)); GoogleServiceAuthError error( GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
diff --git a/chrome/browser/sync/test/integration/profile_sync_service_harness.cc b/chrome/browser/sync/test/integration/profile_sync_service_harness.cc index 6a4b4ce..4e552de4 100644 --- a/chrome/browser/sync/test/integration/profile_sync_service_harness.cc +++ b/chrome/browser/sync/test/integration/profile_sync_service_harness.cc
@@ -49,25 +49,25 @@ GoogleServiceAuthError::REQUEST_CANCELED; } -class BackendInitializeChecker : public SingleClientStatusChangeChecker { +class EngineInitializeChecker : public SingleClientStatusChangeChecker { public: - explicit BackendInitializeChecker(ProfileSyncService* service) + explicit EngineInitializeChecker(ProfileSyncService* service) : SingleClientStatusChangeChecker(service) {} bool IsExitConditionSatisfied() override { - if (service()->IsBackendInitialized()) + if (service()->IsEngineInitialized()) return true; - // Backend initialization is blocked by an auth error. + // Engine initialization is blocked by an auth error. if (HasAuthError(service())) return true; - // Backend initialization is blocked by a failure to fetch Oauth2 tokens. + // Engine initialization is blocked by a failure to fetch Oauth2 tokens. if (service()->IsRetryingAccessTokenFetchForTest()) return true; - // Still waiting on backend initialization. + // Still waiting on engine initialization. return false; } - std::string GetDebugMessage() const override { return "Backend Initialize"; } + std::string GetDebugMessage() const override { return "Engine Initialize"; } }; class SyncSetupChecker : public SingleClientStatusChangeChecker { @@ -179,7 +179,7 @@ // Now that auth is completed, request that sync actually start. service()->RequestStart(); - if (!AwaitBackendInitialization()) { + if (!AwaitEngineInitialization()) { return false; } @@ -246,14 +246,14 @@ return QuiesceStatusChangeChecker(services).Wait(); } -bool ProfileSyncServiceHarness::AwaitBackendInitialization() { - if (!BackendInitializeChecker(service()).Wait()) { - LOG(ERROR) << "BackendInitializeChecker timed out."; +bool ProfileSyncServiceHarness::AwaitEngineInitialization() { + if (!EngineInitializeChecker(service()).Wait()) { + LOG(ERROR) << "EngineInitializeChecker timed out."; return false; } - if (!service()->IsBackendInitialized()) { - LOG(ERROR) << "Service backend not initialized."; + if (!service()->IsEngineInitialized()) { + LOG(ERROR) << "Service engine not initialized."; return false; }
diff --git a/chrome/browser/sync/test/integration/profile_sync_service_harness.h b/chrome/browser/sync/test/integration/profile_sync_service_harness.h index 20de77d..b4b82eb2 100644 --- a/chrome/browser/sync/test/integration/profile_sync_service_harness.h +++ b/chrome/browser/sync/test/integration/profile_sync_service_harness.h
@@ -82,11 +82,11 @@ static bool AwaitQuiescence( const std::vector<ProfileSyncServiceHarness*>& clients); - // Blocks the caller until the sync backend is initialized or some end state - // (e.g., auth error) is reached. Returns true if and only if the backend - // initialized successfully. See ProfileSyncService's IsBackendInitialized() - // method for the definition of backend initialization. - bool AwaitBackendInitialization(); + // Blocks the caller until the sync engine is initialized or some end state + // (e.g., auth error) is reached. Returns true if and only if the engine + // initialized successfully. See ProfileSyncService's IsEngineInitialized() + // method for the definition of engine initialization. + bool AwaitEngineInitialization(); // Blocks the caller until sync setup is complete. Returns true if and only // if sync setup completed successfully. See syncer::SyncService's
diff --git a/chrome/browser/sync/test/integration/single_client_supervised_user_settings_sync_test.cc b/chrome/browser/sync/test/integration/single_client_supervised_user_settings_sync_test.cc index 01ebf63..fe45dce 100644 --- a/chrome/browser/sync/test/integration/single_client_supervised_user_settings_sync_test.cc +++ b/chrome/browser/sync/test/integration/single_client_supervised_user_settings_sync_test.cc
@@ -51,7 +51,7 @@ GoogleServiceAuthError(GoogleServiceAuthError::NONE), std::string("token value doesn't matter in tests")); - ASSERT_TRUE(GetClient(0)->AwaitBackendInitialization()); + ASSERT_TRUE(GetClient(0)->AwaitEngineInitialization()); ASSERT_TRUE(GetClient(0)->AwaitSyncSetupCompletion()); // TODO(pvalenzuela): Add additional tests and some verification of sync-
diff --git a/chrome/browser/sync/test/integration/sync_errors_test.cc b/chrome/browser/sync/test/integration/sync_errors_test.cc index 76a7723..cce9867 100644 --- a/chrome/browser/sync/test/integration/sync_errors_test.cc +++ b/chrome/browser/sync/test/integration/sync_errors_test.cc
@@ -38,14 +38,14 @@ std::string GetDebugMessage() const override { return "Sync Disabled"; } }; -class SyncBackendStoppedChecker : public SingleClientStatusChangeChecker { +class SyncEngineStoppedChecker : public SingleClientStatusChangeChecker { public: - explicit SyncBackendStoppedChecker(ProfileSyncService* service) + explicit SyncEngineStoppedChecker(ProfileSyncService* service) : SingleClientStatusChangeChecker(service) {} // StatusChangeChecker implementation. bool IsExitConditionSatisfied() override { - return !service()->IsBackendInitialized(); + return !service()->IsEngineInitialized(); } std::string GetDebugMessage() const override { return "Sync stopped"; } }; @@ -200,7 +200,7 @@ ASSERT_EQ(status.sync_protocol_error.error_description, description); } -// Tests that on receiving CLIENT_DATA_OBSOLETE sync backend gets restarted and +// Tests that on receiving CLIENT_DATA_OBSOLETE sync engine gets restarted and // initialized with different cache_guld. IN_PROC_BROWSER_TEST_F(SyncErrorTest, ClientDataObsoleteTest) { ASSERT_TRUE(SetupSync()) << "SetupSync() failed."; @@ -224,12 +224,12 @@ const BookmarkNode* node2 = AddFolder(0, 0, "title2"); SetTitle(0, node2, "new_title2"); - ASSERT_TRUE(SyncBackendStoppedChecker(GetSyncService(0)).Wait()); + ASSERT_TRUE(SyncEngineStoppedChecker(GetSyncService(0)).Wait()); // Make server return SUCCESS so that sync can initialize. EXPECT_TRUE(GetFakeServer()->TriggerError(sync_pb::SyncEnums::SUCCESS)); - ASSERT_TRUE(GetClient(0)->AwaitBackendInitialization()); + ASSERT_TRUE(GetClient(0)->AwaitEngineInitialization()); // Ensure cache_guid changed. GetSyncService(0)->QueryDetailedSyncStatus(&status);
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn index 49f5809..f32a7b05 100644 --- a/chrome/browser/ui/BUILD.gn +++ b/chrome/browser/ui/BUILD.gn
@@ -519,7 +519,6 @@ "//components/password_manager/sync/browser", "//components/pdf/browser", "//components/policy/core/browser", - "//components/power", "//components/pref_registry", "//components/proximity_auth", "//components/proxy_config",
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc index 4112d59d..b137776 100644 --- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc +++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc
@@ -503,7 +503,7 @@ gfx::ScopedCanvas scoped_canvas(canvas); gfx::Rect tabstrip_bounds(GetBoundsForTabStrip(browser_view()->tabstrip())); tabstrip_bounds.set_x(GetMirroredXForRect(tabstrip_bounds)); - canvas->ClipRect(tabstrip_bounds, SkRegion::kDifference_Op); + canvas->ClipRect(tabstrip_bounds, kDifference_SkClipOp); const gfx::Rect separator_rect(toolbar_bounds.x(), tabstrip_bounds.bottom(), toolbar_bounds.width(), 0); BrowserView::Paint1pxHorizontalLine(canvas, GetToolbarTopSeparatorColor(),
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mus.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mus.cc index fdc0a87..58cf38f8 100644 --- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mus.cc +++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mus.cc
@@ -492,7 +492,7 @@ gfx::ScopedCanvas scoped_canvas(canvas); gfx::Rect tabstrip_bounds(GetBoundsForTabStrip(browser_view()->tabstrip())); tabstrip_bounds.set_x(GetMirroredXForRect(tabstrip_bounds)); - canvas->ClipRect(tabstrip_bounds, SkRegion::kDifference_Op); + canvas->ClipRect(tabstrip_bounds, kDifference_SkClipOp); const gfx::Rect separator_rect(toolbar_bounds.x(), tabstrip_bounds.bottom(), toolbar_bounds.width(), 0); BrowserView::Paint1pxHorizontalLine(canvas, GetToolbarTopSeparatorColor(),
diff --git a/chrome/browser/ui/views/frame/glass_browser_frame_view.cc b/chrome/browser/ui/views/frame/glass_browser_frame_view.cc index e8e14d3..0ceb12d4 100644 --- a/chrome/browser/ui/views/frame/glass_browser_frame_view.cc +++ b/chrome/browser/ui/views/frame/glass_browser_frame_view.cc
@@ -654,7 +654,7 @@ gfx::Rect tabstrip_bounds(GetBoundsForTabStrip(browser_view()->tabstrip())); tabstrip_bounds.set_x(GetMirroredXForRect(tabstrip_bounds)); canvas->sk_canvas()->clipRect(gfx::RectToSkRect(tabstrip_bounds), - SkRegion::kDifference_Op); + kDifference_SkClipOp); separator_rect.set_y(tabstrip_bounds.bottom()); BrowserView::Paint1pxHorizontalLine(canvas, GetToolbarTopSeparatorColor(), separator_rect, true);
diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc b/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc index 2cf1328..411913b 100644 --- a/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc +++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view.cc
@@ -582,7 +582,7 @@ gfx::Rect tabstrip_bounds(GetBoundsForTabStrip(browser_view()->tabstrip())); tabstrip_bounds.set_x(GetMirroredXForRect(tabstrip_bounds)); canvas->sk_canvas()->clipRect(gfx::RectToSkRect(tabstrip_bounds), - SkRegion::kDifference_Op); + kDifference_SkClipOp); separator_rect.set_y(tabstrip_bounds.bottom()); BrowserView::Paint1pxHorizontalLine(canvas, GetToolbarTopSeparatorColor(), separator_rect, true);
diff --git a/chrome/browser/ui/views/frame/windows_10_caption_button.cc b/chrome/browser/ui/views/frame/windows_10_caption_button.cc index 5061017..e5580b3c 100644 --- a/chrome/browser/ui/views/frame/windows_10_caption_button.cc +++ b/chrome/browser/ui/views/frame/windows_10_caption_button.cc
@@ -176,7 +176,7 @@ DrawRect(canvas, symbol_rect, paint); // Top right ("behind") square. - canvas->ClipRect(symbol_rect, SkRegion::kDifference_Op); + canvas->ClipRect(symbol_rect, kDifference_SkClipOp); symbol_rect.Offset(separation, -separation); DrawRect(canvas, symbol_rect, paint); return;
diff --git a/chrome/browser/ui/views/tabs/tab.cc b/chrome/browser/ui/views/tabs/tab.cc index 4e894358..1f39de8 100644 --- a/chrome/browser/ui/views/tabs/tab.cc +++ b/chrome/browser/ui/views/tabs/tab.cc
@@ -1271,7 +1271,7 @@ use_fill_and_stroke_images ? canvas : nullptr); if (use_fill_and_stroke_images) { canvas->DrawImageInt(it->fill_image, 0, 0); - canvas->sk_canvas()->clipPath(clip, SkRegion::kDifference_Op, true); + canvas->sk_canvas()->clipPath(clip, kDifference_SkClipOp, true); } canvas->DrawImageInt(it->stroke_image, 0, 0); }
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc index 69f4cda0..0fb15a9 100644 --- a/chrome/browser/ui/views/tabs/tab_strip.cc +++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -398,7 +398,7 @@ canvas->ClipPath(stroke, true); Op(stroke, fill, kDifference_SkPathOp, &stroke); if (!pressed) - canvas->sk_canvas()->clipPath(fill, SkRegion::kDifference_Op, true); + canvas->sk_canvas()->clipPath(fill, kDifference_SkClipOp, true); // Now draw the stroke and shadow; the stroke will always be visible, while // the shadow will be affected by the clip we set above. SkPaint paint; @@ -1468,7 +1468,7 @@ if (active_tab) { canvas->sk_canvas()->clipRect( gfx::RectToSkRect(active_tab->GetMirroredBounds()), - SkRegion::kDifference_Op); + kDifference_SkClipOp); } BrowserView::Paint1pxHorizontalLine(canvas, GetToolbarTopSeparatorColor(), GetLocalBounds(), true);
diff --git a/chrome/browser/ui/webui/options/sync_setup_handler.cc b/chrome/browser/ui/webui/options/sync_setup_handler.cc index dd7f0274..8d995e3 100644 --- a/chrome/browser/ui/webui/options/sync_setup_handler.cc +++ b/chrome/browser/ui/webui/options/sync_setup_handler.cc
@@ -431,11 +431,11 @@ base::DictionaryValue args; const int kTimeoutSec = 30; - DCHECK(!backend_start_timer_); - backend_start_timer_.reset(new base::OneShotTimer()); - backend_start_timer_->Start(FROM_HERE, - base::TimeDelta::FromSeconds(kTimeoutSec), - this, &SyncSetupHandler::DisplayTimeout); + DCHECK(!engine_start_timer_); + engine_start_timer_.reset(new base::OneShotTimer()); + engine_start_timer_->Start(FROM_HERE, + base::TimeDelta::FromSeconds(kTimeoutSec), this, + &SyncSetupHandler::DisplayTimeout); web_ui()->CallJavascriptFunctionUnsafe("SyncSetupOverlay.showSyncSetupPage", page, args); @@ -445,7 +445,7 @@ // http://crbug.com/128692 void SyncSetupHandler::DisplayTimeout() { // Stop a timer to handle timeout in waiting for checking network connection. - backend_start_timer_.reset(); + engine_start_timer_.reset(); // Do not listen to sync startup events. sync_startup_tracker_.reset(); @@ -462,7 +462,7 @@ void SyncSetupHandler::SyncStartupFailed() { // Stop a timer to handle timeout in waiting for checking network connection. - backend_start_timer_.reset(); + engine_start_timer_.reset(); // Just close the sync overlay (the idea is that the base settings page will // display the current error.) @@ -471,10 +471,10 @@ void SyncSetupHandler::SyncStartupCompleted() { ProfileSyncService* service = GetSyncService(); - DCHECK(service->IsBackendInitialized()); + DCHECK(service->IsEngineInitialized()); // Stop a timer to handle timeout in waiting for checking network connection. - backend_start_timer_.reset(); + engine_start_timer_.reset(); DisplayConfigureSync(false); } @@ -515,7 +515,7 @@ // If the sync engine has shutdown for some reason, just close the sync // dialog. - if (!service || !service->IsBackendInitialized()) { + if (!service || !service->IsEngineInitialized()) { CloseUI(); return; } @@ -528,7 +528,7 @@ // Note: Data encryption will not occur until configuration is complete // (when the PSS receives its CONFIGURE_DONE notification from the sync - // backend), so the user still has a chance to cancel out of the operation + // engine), so the user still has a chance to cancel out of the operation // if (for example) some kind of passphrase error is encountered. if (configuration.encrypt_all) service->EnableEncryptEverything(); @@ -681,7 +681,7 @@ void SyncSetupHandler::CloseSyncSetup() { // Stop a timer to handle timeout in waiting for checking network connection. - backend_start_timer_.reset(); + engine_start_timer_.reset(); // Clear the sync startup tracker, since the setup wizard is being closed. sync_startup_tracker_.reset(); @@ -698,7 +698,7 @@ ProfileSyncService::CANCEL_DURING_CONFIGURE); // If the user clicked "Cancel" while setting up sync, disable sync - // because we don't want the sync backend to remain in the + // because we don't want the sync engine to remain in the // first-setup-incomplete state. // Note: In order to disable sync across restarts on Chrome OS, // we must call RequestStop(CLEAR_DATA), which suppresses sync startup @@ -821,10 +821,10 @@ GetProfile())->IsAuthenticated()); ProfileSyncService* service = GetSyncService(); DCHECK(service); - if (!service->IsBackendInitialized()) { + if (!service->IsEngineInitialized()) { service->RequestStart(); - // See if it's even possible to bring up the sync backend - if not + // See if it's even possible to bring up the sync engine - if not // (unrecoverable error?), don't bother displaying a spinner that will be // immediately closed because this leads to some ugly infinite UI loop (see // http://crbug.com/244769). @@ -843,8 +843,8 @@ // longer need a SyncStartupTracker. sync_startup_tracker_.reset(); configuring_sync_ = true; - DCHECK(service->IsBackendInitialized()) - << "Cannot configure sync until the sync backend is initialized"; + DCHECK(service->IsEngineInitialized()) + << "Cannot configure sync until the sync engine is initialized"; // Setup args for the sync configure screen: // syncAllDataTypes: true if the user wants to sync everything
diff --git a/chrome/browser/ui/webui/options/sync_setup_handler.h b/chrome/browser/ui/webui/options/sync_setup_handler.h index 5d3ad93..993293f3 100644 --- a/chrome/browser/ui/webui/options/sync_setup_handler.h +++ b/chrome/browser/ui/webui/options/sync_setup_handler.h
@@ -55,7 +55,7 @@ void OpenSyncSetup(bool creating_supervised_user); // Shows advanced configuration dialog without going through sign in dialog. - // Kicks the sync backend if necessary with showing spinner dialog until it + // Kicks the sync engine if necessary with showing spinner dialog until it // gets ready. void OpenConfigureSync(); @@ -65,7 +65,7 @@ protected: friend class SyncSetupHandlerTest; FRIEND_TEST_ALL_PREFIXES(SyncSetupHandlerTest, - DisplayConfigureWithBackendDisabledAndCancel); + DisplayConfigureWithEngineDisabledAndCancel); FRIEND_TEST_ALL_PREFIXES(SyncSetupHandlerTest, HandleSetupUIWhenSyncDisabled); FRIEND_TEST_ALL_PREFIXES(SyncSetupHandlerTest, SelectCustomEncryption); FRIEND_TEST_ALL_PREFIXES(SyncSetupHandlerTest, ShowSyncSetupWhenNotSignedIn); @@ -135,7 +135,7 @@ // is running in the background. void DisplaySpinner(); - // Displays an error dialog which shows timeout of starting the sync backend. + // Displays an error dialog which shows timeout of starting the sync engine. void DisplayTimeout(); // Closes the associated sync settings page. @@ -154,7 +154,7 @@ // requires a passphrase and one hasn't been provided or it was invalid. void DisplayConfigureSync(bool passphrase_failed); - // Helper object used to wait for the sync backend to startup. + // Helper object used to wait for the sync engine to startup. std::unique_ptr<SyncStartupTracker> sync_startup_tracker_; // Prevents Sync from running until configuration is complete. @@ -165,9 +165,9 @@ // histograms in the case that the user cancels out. bool configuring_sync_; - // The OneShotTimer object used to timeout of starting the sync backend + // The OneShotTimer object used to timeout of starting the sync engine // service. - std::unique_ptr<base::OneShotTimer> backend_start_timer_; + std::unique_ptr<base::OneShotTimer> engine_start_timer_; DISALLOW_COPY_AND_ASSIGN(SyncSetupHandler); };
diff --git a/chrome/browser/ui/webui/options/sync_setup_handler_unittest.cc b/chrome/browser/ui/webui/options/sync_setup_handler_unittest.cc index 0d0d3c3..b0f22c9 100644 --- a/chrome/browser/ui/webui/options/sync_setup_handler_unittest.cc +++ b/chrome/browser/ui/webui/options/sync_setup_handler_unittest.cc
@@ -234,10 +234,9 @@ void SetupInitializedProfileSyncService() { // An initialized ProfileSyncService will have already completed sync setup - // and will have an initialized sync backend. + // and will have an initialized sync engine. ASSERT_TRUE(mock_signin_->IsInitialized()); - EXPECT_CALL(*mock_pss_, IsBackendInitialized()) - .WillRepeatedly(Return(true)); + EXPECT_CALL(*mock_pss_, IsEngineInitialized()).WillRepeatedly(Return(true)); } void ExpectConfig() { @@ -358,15 +357,15 @@ // Verifies that the handler correctly handles a cancellation when // it is displaying the spinner to the user. -TEST_F(SyncSetupHandlerTest, DisplayConfigureWithBackendDisabledAndCancel) { +TEST_F(SyncSetupHandlerTest, DisplayConfigureWithEngineDisabledAndCancel) { EXPECT_CALL(*mock_pss_, CanSyncStart()).WillRepeatedly(Return(true)); EXPECT_CALL(*mock_pss_, IsFirstSetupComplete()).WillRepeatedly(Return(false)); error_ = GoogleServiceAuthError::AuthErrorNone(); - EXPECT_CALL(*mock_pss_, IsBackendInitialized()).WillRepeatedly(Return(false)); + EXPECT_CALL(*mock_pss_, IsEngineInitialized()).WillRepeatedly(Return(false)); - // We're simulating a user setting up sync, which would cause the backend to + // We're simulating a user setting up sync, which would cause the engine to // kick off initialization, but not download user data types. The sync - // backend will try to download control data types (e.g encryption info), but + // engine will try to download control data types (e.g encryption info), but // that won't finish for this test as we're simulating cancelling while the // spinner is showing. handler_->HandleShowSetupUI(NULL); @@ -381,12 +380,12 @@ // Verifies that the handler correctly transitions from showing the spinner // to showing a configuration page when sync setup completes successfully. TEST_F(SyncSetupHandlerTest, - DisplayConfigureWithBackendDisabledAndSyncStartupCompleted) { + DisplayConfigureWithEngineDisabledAndSyncStartupCompleted) { EXPECT_CALL(*mock_pss_, CanSyncStart()).WillRepeatedly(Return(true)); EXPECT_CALL(*mock_pss_, IsFirstSetupComplete()).WillRepeatedly(Return(false)); error_ = GoogleServiceAuthError::AuthErrorNone(); - // Sync backend is stopped initially, and will start up. - EXPECT_CALL(*mock_pss_, IsBackendInitialized()).WillRepeatedly(Return(false)); + // Sync engine is stopped initially, and will start up. + EXPECT_CALL(*mock_pss_, IsEngineInitialized()).WillRepeatedly(Return(false)); SetDefaultExpectationsForConfigPage(); handler_->OpenSyncSetup(false /* creating_supervised_user */); @@ -403,7 +402,7 @@ Mock::VerifyAndClearExpectations(mock_pss_); // Now, act as if the ProfileSyncService has started up. SetDefaultExpectationsForConfigPage(); - EXPECT_CALL(*mock_pss_, IsBackendInitialized()).WillRepeatedly(Return(true)); + EXPECT_CALL(*mock_pss_, IsEngineInitialized()).WillRepeatedly(Return(true)); error_ = GoogleServiceAuthError::AuthErrorNone(); EXPECT_CALL(*mock_pss_, GetAuthError()).WillRepeatedly(ReturnRef(error_)); NotifySyncListeners(); @@ -423,17 +422,17 @@ CheckBool(dictionary, "usePassphrase", false); } -// Verifies the case where the user cancels after the sync backend has +// Verifies the case where the user cancels after the sync engine has // initialized (meaning it already transitioned from the spinner to a proper // configuration page, tested by -// DisplayConfigureWithBackendDisabledAndSigninSuccess), but before the user -// before the user has continued on. +// DisplayConfigureWithEngineDisabledAndSyncStartupCompleted), but before the +// user has continued on. TEST_F(SyncSetupHandlerTest, - DisplayConfigureWithBackendDisabledAndCancelAfterSigninSuccess) { + DisplayConfigureWithEngineDisabledAndCancelAfterSigninSuccess) { EXPECT_CALL(*mock_pss_, CanSyncStart()).WillRepeatedly(Return(true)); EXPECT_CALL(*mock_pss_, IsFirstSetupComplete()).WillRepeatedly(Return(false)); error_ = GoogleServiceAuthError::AuthErrorNone(); - EXPECT_CALL(*mock_pss_, IsBackendInitialized()) + EXPECT_CALL(*mock_pss_, IsEngineInitialized()) .WillOnce(Return(false)) .WillRepeatedly(Return(true)); SetDefaultExpectationsForConfigPage(); @@ -452,11 +451,11 @@ } TEST_F(SyncSetupHandlerTest, - DisplayConfigureWithBackendDisabledAndSigninFailed) { + DisplayConfigureWithEngineDisabledAndSigninFailed) { EXPECT_CALL(*mock_pss_, CanSyncStart()).WillRepeatedly(Return(true)); EXPECT_CALL(*mock_pss_, IsFirstSetupComplete()).WillRepeatedly(Return(false)); error_ = GoogleServiceAuthError::AuthErrorNone(); - EXPECT_CALL(*mock_pss_, IsBackendInitialized()).WillRepeatedly(Return(false)); + EXPECT_CALL(*mock_pss_, IsEngineInitialized()).WillRepeatedly(Return(false)); handler_->OpenSyncSetup(false /* creating_supervised_user */); const content::TestWebUI::CallData& data = *web_ui_.call_data()[0]; @@ -736,11 +735,11 @@ .WillRepeatedly(Return(false)); EXPECT_CALL(*mock_pss_, IsUsingSecondaryPassphrase()) .WillRepeatedly(Return(false)); - EXPECT_CALL(*mock_pss_, IsBackendInitialized()).WillRepeatedly(Return(false)); + EXPECT_CALL(*mock_pss_, IsEngineInitialized()).WillRepeatedly(Return(false)); #if defined(OS_CHROMEOS) // On ChromeOS, auth errors are ignored - instead we just try to start the - // sync backend (which will fail due to the auth error). This should only + // sync engine (which will fail due to the auth error). This should only // happen if the user manually navigates to chrome://settings/syncSetup - // clicking on the button in the UI will sign the user out rather than // displaying a spinner. Should be no visible UI on ChromeOS in this case. @@ -749,7 +748,7 @@ #else // On ChromeOS, this should display the spinner while we try to startup the - // sync backend, and on desktop this displays the login dialog. + // sync engine, and on desktop this displays the login dialog. handler_->OpenSyncSetup(false /* creating_supervised_user */); // Sync setup is closed when re-auth is in progress.
diff --git a/chrome/browser/ui/webui/settings/people_handler.cc b/chrome/browser/ui/webui/settings/people_handler.cc index 317f7d43..642b7c8 100644 --- a/chrome/browser/ui/webui/settings/people_handler.cc +++ b/chrome/browser/ui/webui/settings/people_handler.cc
@@ -315,11 +315,11 @@ configuring_sync_ = true; const int kTimeoutSec = 30; - DCHECK(!backend_start_timer_); - backend_start_timer_.reset(new base::OneShotTimer()); - backend_start_timer_->Start(FROM_HERE, - base::TimeDelta::FromSeconds(kTimeoutSec), this, - &PeopleHandler::DisplayTimeout); + DCHECK(!engine_start_timer_); + engine_start_timer_.reset(new base::OneShotTimer()); + engine_start_timer_->Start(FROM_HERE, + base::TimeDelta::FromSeconds(kTimeoutSec), this, + &PeopleHandler::DisplayTimeout); CallJavascriptFunction("cr.webUIListenerCallback", base::StringValue("page-status-changed"), @@ -330,7 +330,7 @@ // http://crbug.com/128692 void PeopleHandler::DisplayTimeout() { // Stop a timer to handle timeout in waiting for checking network connection. - backend_start_timer_.reset(); + engine_start_timer_.reset(); // Do not listen to sync startup events. sync_startup_tracker_.reset(); @@ -347,7 +347,7 @@ void PeopleHandler::SyncStartupFailed() { // Stop a timer to handle timeout in waiting for checking network connection. - backend_start_timer_.reset(); + engine_start_timer_.reset(); // Just close the sync overlay (the idea is that the base settings page will // display the current error.) @@ -356,10 +356,10 @@ void PeopleHandler::SyncStartupCompleted() { ProfileSyncService* service = GetSyncService(); - DCHECK(service->IsBackendInitialized()); + DCHECK(service->IsEngineInitialized()); // Stop a timer to handle timeout in waiting for checking network connection. - backend_start_timer_.reset(); + engine_start_timer_.reset(); sync_startup_tracker_.reset(); @@ -389,7 +389,7 @@ // If the sync engine has shutdown for some reason, just close the sync // dialog. - if (!service || !service->IsBackendInitialized()) { + if (!service || !service->IsEngineInitialized()) { CloseSyncSetup(); ResolveJavascriptCallback(*callback_id, base::StringValue(kDonePageStatus)); return; @@ -420,7 +420,7 @@ // If the sync engine has shutdown for some reason, just close the sync // dialog. - if (!service || !service->IsBackendInitialized()) { + if (!service || !service->IsEngineInitialized()) { CloseSyncSetup(); ResolveJavascriptCallback(*callback_id, base::StringValue(kDonePageStatus)); return; @@ -434,7 +434,7 @@ // Note: Data encryption will not occur until configuration is complete // (when the PSS receives its CONFIGURE_DONE notification from the sync - // backend), so the user still has a chance to cancel out of the operation + // engine), so the user still has a chance to cancel out of the operation // if (for example) some kind of passphrase error is encountered. if (configuration.encrypt_all) service->EnableEncryptEverything(); @@ -568,7 +568,7 @@ void PeopleHandler::CloseSyncSetup() { // Stop a timer to handle timeout in waiting for checking network connection. - backend_start_timer_.reset(); + engine_start_timer_.reset(); // Clear the sync startup tracker, since the setup wizard is being closed. sync_startup_tracker_.reset(); @@ -590,7 +590,7 @@ ProfileSyncService::CANCEL_DURING_CONFIGURE); // If the user clicked "Cancel" while setting up sync, disable sync - // because we don't want the sync backend to remain in the + // because we don't want the sync engine to remain in the // first-setup-incomplete state. // Note: In order to disable sync across restarts on Chrome OS, // we must call RequestStop(CLEAR_DATA), which suppresses sync startup @@ -779,10 +779,10 @@ ProfileSyncService* service = GetSyncService(); DCHECK(service); - if (!service->IsBackendInitialized()) { + if (!service->IsEngineInitialized()) { service->RequestStart(); - // See if it's even possible to bring up the sync backend - if not + // See if it's even possible to bring up the sync engine - if not // (unrecoverable error?), don't bother displaying a spinner that will be // immediately closed because this leads to some ugly infinite UI loop (see // http://crbug.com/244769). @@ -797,8 +797,8 @@ } configuring_sync_ = true; - DCHECK(service->IsBackendInitialized()) - << "Cannot configure sync until the sync backend is initialized"; + DCHECK(service->IsEngineInitialized()) + << "Cannot configure sync until the sync engine is initialized"; // Setup args for the sync configure screen: // syncAllDataTypes: true if the user wants to sync everything
diff --git a/chrome/browser/ui/webui/settings/people_handler.h b/chrome/browser/ui/webui/settings/people_handler.h index bf19c5c..da243e0 100644 --- a/chrome/browser/ui/webui/settings/people_handler.h +++ b/chrome/browser/ui/webui/settings/people_handler.h
@@ -71,10 +71,10 @@ private: friend class PeopleHandlerTest; FRIEND_TEST_ALL_PREFIXES(PeopleHandlerTest, - DisplayConfigureWithBackendDisabledAndCancel); + DisplayConfigureWithEngineDisabledAndCancel); FRIEND_TEST_ALL_PREFIXES( PeopleHandlerTest, - DisplayConfigureWithBackendDisabledAndSyncStartupCompleted); + DisplayConfigureWithEngineDisabledAndSyncStartupCompleted); FRIEND_TEST_ALL_PREFIXES(PeopleHandlerTest, HandleSetupUIWhenSyncDisabled); FRIEND_TEST_ALL_PREFIXES(PeopleHandlerTest, SelectCustomEncryption); FRIEND_TEST_ALL_PREFIXES(PeopleHandlerTest, ShowSyncSetupWhenNotSignedIn); @@ -160,7 +160,7 @@ // is running in the background. void DisplaySpinner(); - // Displays an error dialog which shows timeout of starting the sync backend. + // Displays an error dialog which shows timeout of starting the sync engine. void DisplayTimeout(); // Closes the associated sync settings page. @@ -178,7 +178,7 @@ // Weak pointer. Profile* profile_; - // Helper object used to wait for the sync backend to startup. + // Helper object used to wait for the sync engine to startup. std::unique_ptr<SyncStartupTracker> sync_startup_tracker_; // Prevents Sync from running until configuration is complete. @@ -189,9 +189,9 @@ // histograms in the case that the user cancels out. bool configuring_sync_; - // The OneShotTimer object used to timeout of starting the sync backend + // The OneShotTimer object used to timeout of starting the sync engine // service. - std::unique_ptr<base::OneShotTimer> backend_start_timer_; + std::unique_ptr<base::OneShotTimer> engine_start_timer_; // Used to listen for pref changes to allow or disallow signin. PrefChangeRegistrar profile_pref_registrar_;
diff --git a/chrome/browser/ui/webui/settings/people_handler_unittest.cc b/chrome/browser/ui/webui/settings/people_handler_unittest.cc index c0087b2..273f55bf 100644 --- a/chrome/browser/ui/webui/settings/people_handler_unittest.cc +++ b/chrome/browser/ui/webui/settings/people_handler_unittest.cc
@@ -230,10 +230,9 @@ void SetupInitializedProfileSyncService() { // An initialized ProfileSyncService will have already completed sync setup - // and will have an initialized sync backend. + // and will have an initialized sync engine. ASSERT_TRUE(mock_signin_->IsInitialized()); - EXPECT_CALL(*mock_pss_, IsBackendInitialized()) - .WillRepeatedly(Return(true)); + EXPECT_CALL(*mock_pss_, IsEngineInitialized()).WillRepeatedly(Return(true)); } void ExpectPageStatusResponse(const std::string& expected_status) { @@ -366,15 +365,15 @@ // Verifies that the handler correctly handles a cancellation when // it is displaying the spinner to the user. -TEST_F(PeopleHandlerTest, DisplayConfigureWithBackendDisabledAndCancel) { +TEST_F(PeopleHandlerTest, DisplayConfigureWithEngineDisabledAndCancel) { EXPECT_CALL(*mock_pss_, CanSyncStart()).WillRepeatedly(Return(true)); EXPECT_CALL(*mock_pss_, IsFirstSetupComplete()).WillRepeatedly(Return(false)); error_ = GoogleServiceAuthError::AuthErrorNone(); - EXPECT_CALL(*mock_pss_, IsBackendInitialized()).WillRepeatedly(Return(false)); + EXPECT_CALL(*mock_pss_, IsEngineInitialized()).WillRepeatedly(Return(false)); - // We're simulating a user setting up sync, which would cause the backend to + // We're simulating a user setting up sync, which would cause the engine to // kick off initialization, but not download user data types. The sync - // backend will try to download control data types (e.g encryption info), but + // engine will try to download control data types (e.g encryption info), but // that won't finish for this test as we're simulating cancelling while the // spinner is showing. handler_->HandleShowSetupUI(NULL); @@ -389,12 +388,12 @@ // Verifies that the handler correctly transitions from showing the spinner // to showing a configuration page when sync setup completes successfully. TEST_F(PeopleHandlerTest, - DisplayConfigureWithBackendDisabledAndSyncStartupCompleted) { + DisplayConfigureWithEngineDisabledAndSyncStartupCompleted) { EXPECT_CALL(*mock_pss_, CanSyncStart()).WillRepeatedly(Return(true)); EXPECT_CALL(*mock_pss_, IsFirstSetupComplete()).WillRepeatedly(Return(false)); error_ = GoogleServiceAuthError::AuthErrorNone(); - // Sync backend is stopped initially, and will start up. - EXPECT_CALL(*mock_pss_, IsBackendInitialized()).WillRepeatedly(Return(false)); + // Sync engine is stopped initially, and will start up. + EXPECT_CALL(*mock_pss_, IsEngineInitialized()).WillRepeatedly(Return(false)); SetDefaultExpectationsForConfigPage(); handler_->OpenSyncSetup(false /* creating_supervised_user */); @@ -405,7 +404,7 @@ Mock::VerifyAndClearExpectations(mock_pss_); // Now, act as if the ProfileSyncService has started up. SetDefaultExpectationsForConfigPage(); - EXPECT_CALL(*mock_pss_, IsBackendInitialized()).WillRepeatedly(Return(true)); + EXPECT_CALL(*mock_pss_, IsEngineInitialized()).WillRepeatedly(Return(true)); error_ = GoogleServiceAuthError::AuthErrorNone(); EXPECT_CALL(*mock_pss_, GetAuthError()).WillRepeatedly(ReturnRef(error_)); handler_->SyncStartupCompleted(); @@ -419,17 +418,17 @@ CheckBool(dictionary, "passphraseRequired", false); } -// Verifies the case where the user cancels after the sync backend has +// Verifies the case where the user cancels after the sync engine has // initialized (meaning it already transitioned from the spinner to a proper // configuration page, tested by -// DisplayConfigureWithBackendDisabledAndSigninSuccess), but before the user -// before the user has continued on. +// DisplayConfigureWithEngineDisabledAndSyncStartupCompleted), but before the +// user has continued on. TEST_F(PeopleHandlerTest, - DisplayConfigureWithBackendDisabledAndCancelAfterSigninSuccess) { + DisplayConfigureWithEngineDisabledAndCancelAfterSigninSuccess) { EXPECT_CALL(*mock_pss_, CanSyncStart()).WillRepeatedly(Return(true)); EXPECT_CALL(*mock_pss_, IsFirstSetupComplete()).WillRepeatedly(Return(false)); error_ = GoogleServiceAuthError::AuthErrorNone(); - EXPECT_CALL(*mock_pss_, IsBackendInitialized()) + EXPECT_CALL(*mock_pss_, IsEngineInitialized()) .WillOnce(Return(false)) .WillRepeatedly(Return(true)); SetDefaultExpectationsForConfigPage(); @@ -447,12 +446,11 @@ profile_)->current_login_ui()); } -TEST_F(PeopleHandlerTest, - DisplayConfigureWithBackendDisabledAndSigninFailed) { +TEST_F(PeopleHandlerTest, DisplayConfigureWithEngineDisabledAndSigninFailed) { EXPECT_CALL(*mock_pss_, CanSyncStart()).WillRepeatedly(Return(true)); EXPECT_CALL(*mock_pss_, IsFirstSetupComplete()).WillRepeatedly(Return(false)); error_ = GoogleServiceAuthError::AuthErrorNone(); - EXPECT_CALL(*mock_pss_, IsBackendInitialized()).WillRepeatedly(Return(false)); + EXPECT_CALL(*mock_pss_, IsEngineInitialized()).WillRepeatedly(Return(false)); handler_->OpenSyncSetup(false /* creating_supervised_user */); ExpectPageStatusChanged(PeopleHandler::kSpinnerPageStatus); @@ -721,11 +719,11 @@ .WillRepeatedly(Return(false)); EXPECT_CALL(*mock_pss_, IsUsingSecondaryPassphrase()) .WillRepeatedly(Return(false)); - EXPECT_CALL(*mock_pss_, IsBackendInitialized()).WillRepeatedly(Return(false)); + EXPECT_CALL(*mock_pss_, IsEngineInitialized()).WillRepeatedly(Return(false)); #if defined(OS_CHROMEOS) // On ChromeOS, auth errors are ignored - instead we just try to start the - // sync backend (which will fail due to the auth error). This should only + // sync engine (which will fail due to the auth error). This should only // happen if the user manually navigates to chrome://settings/syncSetup - // clicking on the button in the UI will sign the user out rather than // displaying a spinner. Should be no visible UI on ChromeOS in this case. @@ -734,7 +732,7 @@ #else // On ChromeOS, this should display the spinner while we try to startup the - // sync backend, and on desktop this displays the login dialog. + // sync engine, and on desktop this displays the login dialog. handler_->OpenSyncSetup(false /* creating_supervised_user */); // Sync setup is closed when re-auth is in progress.
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc index dfd072d..f3ef99f5 100644 --- a/chrome/common/chrome_switches.cc +++ b/chrome/common/chrome_switches.cc
@@ -580,9 +580,6 @@ // See http://crbug.com/31395. const char kKioskModePrinting[] = "kiosk-printing"; -// Comma-separated list of directories with component extensions to load. -const char kLoadComponentExtension[] = "load-component-extension"; - // Loads an extension from the specified directory. const char kLoadExtension[] = "load-extension";
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h index 41d5fa6..69bec54 100644 --- a/chrome/common/chrome_switches.h +++ b/chrome/common/chrome_switches.h
@@ -171,7 +171,6 @@ extern const char kKeepAliveForTest[]; extern const char kKioskMode[]; extern const char kKioskModePrinting[]; -extern const char kLoadComponentExtension[]; extern const char kLoadExtension[]; extern const char kLoadMediaRouterComponentExtension[]; extern const char kMakeDefaultBrowser[];
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index af7bdf0e..36bf0ae 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn
@@ -3459,6 +3459,7 @@ "//components/sync:test_support_driver", "//components/sync:test_support_model", "//components/sync_sessions:test_support", + "//components/variations:test_support", "//content/public/app:both", "//content/test:test_support", "//crypto:platform", @@ -3592,7 +3593,6 @@ "../browser/net/firefox_proxy_settings_unittest.cc", "../browser/permissions/permission_request_manager_unittest.cc", "../browser/platform_util_unittest.cc", - "../browser/power/process_power_collector_unittest.cc", "../browser/power_usage_monitor/power_usage_monitor_unittest.cc", "../browser/process_singleton_posix_unittest.cc", "../browser/profile_resetter/profile_resetter_unittest.cc",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/sync/SyncTestUtil.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/sync/SyncTestUtil.java index 75fba1c..9660c2f7 100644 --- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/sync/SyncTestUtil.java +++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/sync/SyncTestUtil.java
@@ -11,15 +11,16 @@ import junit.framework.Assert; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + import org.chromium.base.ThreadUtils; import org.chromium.chrome.browser.invalidation.InvalidationServiceFactory; import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.sync.ProfileSyncService; import org.chromium.content.browser.test.util.Criteria; import org.chromium.content.browser.test.util.CriteriaHelper; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; import java.util.ArrayList; import java.util.Iterator; @@ -76,14 +77,14 @@ } /** - * Waits for sync's backend to be initialized. + * Waits for sync's engine to be initialized. */ - public static void waitForBackendInitialized() throws InterruptedException { + public static void waitForEngineInitialized() throws InterruptedException { CriteriaHelper.pollUiThread( - new Criteria("Timed out waiting for sync's backend to initialize.") { + new Criteria("Timed out waiting for sync's engine to initialize.") { @Override public boolean isSatisfied() { - return ProfileSyncService.get().isBackendInitialized(); + return ProfileSyncService.get().isEngineInitialized(); } }, TIMEOUT_MS, INTERVAL_MS);
diff --git a/chrome/test/media_router/BUILD.gn b/chrome/test/media_router/BUILD.gn index 15df18e..e86de86 100644 --- a/chrome/test/media_router/BUILD.gn +++ b/chrome/test/media_router/BUILD.gn
@@ -2,6 +2,24 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +group("media_router_perf_tests") { + testonly = true + deps = [ + ":test_extension_resource_files", + ] + data_deps = [ + "//chrome:chrome", + ] + data = [ + "$root_out_dir/mr_extension/release/", + "internal/", + "telemetry/", + "../../../third_party/catapult/", + "../../../tools/perf/", + "../../../tools/variations/fieldtrial_util.py", + ] +} + group("media_router_tests") { testonly = true data_deps = [
diff --git a/components/BUILD.gn b/components/BUILD.gn index d518aea..82a929a 100644 --- a/components/BUILD.gn +++ b/components/BUILD.gn
@@ -193,7 +193,6 @@ "//components/offline_pages/content/background_loader:unit_tests", "//components/packed_ct_ev_whitelist:unit_tests", "//components/password_manager/content/browser:unit_tests", - "//components/power:unit_tests", "//components/precache/content:unit_tests", "//components/safe_browsing_db:unit_tests", "//components/safe_json:unit_tests",
diff --git a/components/arc/intent_helper/page_transition_util.h b/components/arc/intent_helper/page_transition_util.h index d2834af..20b66fa1 100644 --- a/components/arc/intent_helper/page_transition_util.h +++ b/components/arc/intent_helper/page_transition_util.h
@@ -15,7 +15,6 @@ bool allow_client_redirect); // Removes |mask| bits from |page_transition|. -// TODO(djacobo): Move this to ui/base/page_transition_types.cc. ui::PageTransition MaskOutPageTransition(ui::PageTransition page_transition, ui::PageTransition mask);
diff --git a/components/autofill/core/browser/autofill_experiments.cc b/components/autofill/core/browser/autofill_experiments.cc index e066d854f..6ecc864b 100644 --- a/components/autofill/core/browser/autofill_experiments.cc +++ b/components/autofill/core/browser/autofill_experiments.cc
@@ -108,9 +108,9 @@ // Users who have enabled a passphrase have chosen to not make their sync // information accessible to Google. Since upload makes credit card data // available to other Google systems, disable it for passphrase users. - // We can't determine the passphrase state until the sync backend is + // We can't determine the passphrase state until the sync engine is // initialized so disable upload if sync is not yet available. - if (!sync_service->IsBackendInitialized() || + if (!sync_service->IsEngineInitialized() || sync_service->IsUsingSecondaryPassphrase()) { return false; }
diff --git a/components/autofill/core/browser/autofill_manager_unittest.cc b/components/autofill/core/browser/autofill_manager_unittest.cc index 5000161..eacfe7b1 100644 --- a/components/autofill/core/browser/autofill_manager_unittest.cc +++ b/components/autofill/core/browser/autofill_manager_unittest.cc
@@ -72,6 +72,13 @@ const int kDefaultPageID = 137; +const std::string kUTF8MidlineEllipsis = + " " + "\xE2\x80\xA2\xE2\x80\x86" + "\xE2\x80\xA2\xE2\x80\x86" + "\xE2\x80\xA2\xE2\x80\x86" + "\xE2\x80\xA2\xE2\x80\x86"; + class MockAutofillClient : public TestAutofillClient { public: MockAutofillClient() {} @@ -1439,14 +1446,11 @@ // Test that we sent the right values to the external delegate. external_delegate_->CheckSuggestions( - kDefaultPageID, Suggestion("Visa\xC2\xA0\xE2\x8B\xAF" - "3456", + kDefaultPageID, Suggestion("Visa" + kUTF8MidlineEllipsis + "3456", "04/99", kVisaCard, autofill_manager_->GetPackedCreditCardID(4)), - Suggestion("MasterCard\xC2\xA0\xE2\x8B\xAF" - "8765", - "10/98", kMasterCard, - autofill_manager_->GetPackedCreditCardID(5))); + Suggestion("MasterCard" + kUTF8MidlineEllipsis + "8765", "10/98", + kMasterCard, autofill_manager_->GetPackedCreditCardID(5))); } // Test that we return all credit card profile suggestions when the triggering @@ -1464,14 +1468,11 @@ // Test that we sent the right values to the external delegate. external_delegate_->CheckSuggestions( - kDefaultPageID, Suggestion("Visa\xC2\xA0\xE2\x8B\xAF" - "3456", + kDefaultPageID, Suggestion("Visa" + kUTF8MidlineEllipsis + "3456", "04/99", kVisaCard, autofill_manager_->GetPackedCreditCardID(4)), - Suggestion("MasterCard\xC2\xA0\xE2\x8B\xAF" - "8765", - "10/98", kMasterCard, - autofill_manager_->GetPackedCreditCardID(5))); + Suggestion("MasterCard" + kUTF8MidlineEllipsis + "8765", "10/98", + kMasterCard, autofill_manager_->GetPackedCreditCardID(5))); } // Test that we return all credit card profile suggestions when the triggering @@ -1489,14 +1490,11 @@ // Test that we sent the right values to the external delegate. external_delegate_->CheckSuggestions( - kDefaultPageID, Suggestion("Visa\xC2\xA0\xE2\x8B\xAF" - "3456", + kDefaultPageID, Suggestion("Visa" + kUTF8MidlineEllipsis + "3456", "04/99", kVisaCard, autofill_manager_->GetPackedCreditCardID(4)), - Suggestion("MasterCard\xC2\xA0\xE2\x8B\xAF" - "8765", - "10/98", kMasterCard, - autofill_manager_->GetPackedCreditCardID(5))); + Suggestion("MasterCard" + kUTF8MidlineEllipsis + "8765", "10/98", + kMasterCard, autofill_manager_->GetPackedCreditCardID(5))); } // Test that we return all credit card profile suggestions when the triggering @@ -1523,8 +1521,7 @@ // Test that we sent the right value to the external delegate. external_delegate_->CheckSuggestions( - kDefaultPageID, Suggestion("MasterCard\xC2\xA0\xE2\x8B\xAF" - "3123", + kDefaultPageID, Suggestion("MasterCard" + kUTF8MidlineEllipsis + "3123", "08/17", kMasterCard, autofill_manager_->GetPackedCreditCardID(7))); } @@ -1544,8 +1541,7 @@ // Test that we sent the right values to the external delegate. external_delegate_->CheckSuggestions( - kDefaultPageID, Suggestion("Visa\xC2\xA0\xE2\x8B\xAF" - "3456", + kDefaultPageID, Suggestion("Visa" + kUTF8MidlineEllipsis + "3456", "04/99", kVisaCard, autofill_manager_->GetPackedCreditCardID(4))); } @@ -1563,15 +1559,13 @@ GetAutofillSuggestions(form, field); #if defined(OS_ANDROID) - static const char* kVisaSuggestion = - "Visa\xC2\xA0\xE2\x8B\xAF" - "3456"; - static const char* kMcSuggestion = - "MasterCard\xC2\xA0\xE2\x8B\xAF" - "8765"; + static const std::string kVisaSuggestion = + "Visa" + kUTF8MidlineEllipsis + "3456"; + static const std::string kMcSuggestion = + "MasterCard" + kUTF8MidlineEllipsis + "8765"; #else - static const char* kVisaSuggestion = "*3456"; - static const char* kMcSuggestion = "*8765"; + static const std::string kVisaSuggestion = "*3456"; + static const std::string kMcSuggestion = "*8765"; #endif // Test that we sent the right values to the external delegate. @@ -1662,14 +1656,11 @@ // Test that we sent the right values to the external delegate. external_delegate_->CheckSuggestions( - kDefaultPageID, Suggestion("Visa\xC2\xA0\xE2\x8B\xAF" - "3456", + kDefaultPageID, Suggestion("Visa" + kUTF8MidlineEllipsis + "3456", "04/99", kVisaCard, autofill_manager_->GetPackedCreditCardID(4)), - Suggestion("MasterCard\xC2\xA0\xE2\x8B\xAF" - "8765", - "10/98", kMasterCard, - autofill_manager_->GetPackedCreditCardID(5))); + Suggestion("MasterCard" + kUTF8MidlineEllipsis + "8765", "10/98", + kMasterCard, autofill_manager_->GetPackedCreditCardID(5))); } // Test that we will eventually return the credit card signin promo when there @@ -1753,14 +1744,11 @@ // Test that we sent the right values to the external delegate. external_delegate_->CheckSuggestions( - kDefaultPageID, Suggestion("Visa\xC2\xA0\xE2\x8B\xAF" - "3456", + kDefaultPageID, Suggestion("Visa" + kUTF8MidlineEllipsis + "3456", "04/99", kVisaCard, autofill_manager_->GetPackedCreditCardID(4)), - Suggestion("MasterCard\xC2\xA0\xE2\x8B\xAF" - "8765", - "10/98", kMasterCard, - autofill_manager_->GetPackedCreditCardID(5))); + Suggestion("MasterCard" + kUTF8MidlineEllipsis + "8765", "10/98", + kMasterCard, autofill_manager_->GetPackedCreditCardID(5))); } // Test that we return credit card suggestions for secure pages that have a @@ -1780,14 +1768,11 @@ // Test that we sent the right values to the external delegate. external_delegate_->CheckSuggestions( - kDefaultPageID, Suggestion("Visa\xC2\xA0\xE2\x8B\xAF" - "3456", + kDefaultPageID, Suggestion("Visa" + kUTF8MidlineEllipsis + "3456", "04/99", kVisaCard, autofill_manager_->GetPackedCreditCardID(4)), - Suggestion("MasterCard\xC2\xA0\xE2\x8B\xAF" - "8765", - "10/98", kMasterCard, - autofill_manager_->GetPackedCreditCardID(5))); + Suggestion("MasterCard" + kUTF8MidlineEllipsis + "8765", "10/98", + kMasterCard, autofill_manager_->GetPackedCreditCardID(5))); } // Test that we return all credit card suggestions in the case that two cards @@ -1814,18 +1799,13 @@ // Test that we sent the right values to the external delegate. external_delegate_->CheckSuggestions( - kDefaultPageID, Suggestion("Visa\xC2\xA0\xE2\x8B\xAF" - "3456", + kDefaultPageID, Suggestion("Visa" + kUTF8MidlineEllipsis + "3456", "04/99", kVisaCard, autofill_manager_->GetPackedCreditCardID(4)), - Suggestion("MasterCard\xC2\xA0\xE2\x8B\xAF" - "8765", - "10/98", kMasterCard, - autofill_manager_->GetPackedCreditCardID(5)), - Suggestion("MasterCard\xC2\xA0\xE2\x8B\xAF" - "3456", - "05/99", kMasterCard, - autofill_manager_->GetPackedCreditCardID(7))); + Suggestion("MasterCard" + kUTF8MidlineEllipsis + "8765", "10/98", + kMasterCard, autofill_manager_->GetPackedCreditCardID(5)), + Suggestion("MasterCard" + kUTF8MidlineEllipsis + "3456", "05/99", + kMasterCard, autofill_manager_->GetPackedCreditCardID(7))); } // Test that we return profile and credit card suggestions for combined forms. @@ -1851,14 +1831,11 @@ // Test that we sent the credit card suggestions to the external delegate. external_delegate_->CheckSuggestions( - kPageID2, Suggestion("Visa\xC2\xA0\xE2\x8B\xAF" - "3456", + kPageID2, Suggestion("Visa" + kUTF8MidlineEllipsis + "3456", "04/99", kVisaCard, autofill_manager_->GetPackedCreditCardID(4)), - Suggestion("MasterCard\xC2\xA0\xE2\x8B\xAF" - "8765", - "10/98", kMasterCard, - autofill_manager_->GetPackedCreditCardID(5))); + Suggestion("MasterCard" + kUTF8MidlineEllipsis + "8765", "10/98", + kMasterCard, autofill_manager_->GetPackedCreditCardID(5))); } // Test that for non-https forms with both address and credit card fields, we @@ -4401,8 +4378,7 @@ GetAutofillSuggestions(form, number_field); external_delegate_->CheckSuggestions( - kDefaultPageID, Suggestion("Visa\xC2\xA0\xE2\x8B\xAF" - "3456", + kDefaultPageID, Suggestion("Visa" + kUTF8MidlineEllipsis + "3456", "04/99", kVisaCard, autofill_manager_->GetPackedCreditCardID(4))); } @@ -5186,11 +5162,10 @@ GetAutofillSuggestions(form, field); #if defined(OS_ANDROID) - static const char* kVisaSuggestion = - "Visa\xC2\xA0\xE2\x8B\xAF" - "3456"; + static const std::string kVisaSuggestion = + "Visa" + kUTF8MidlineEllipsis + "3456"; #else - static const char* kVisaSuggestion = "*3456"; + static const std::string kVisaSuggestion = "*3456"; #endif external_delegate_->CheckSuggestions(
diff --git a/components/autofill/core/browser/credit_card.cc b/components/autofill/core/browser/credit_card.cc index 376ce1b..2bc1ca4 100644 --- a/components/autofill/core/browser/credit_card.cc +++ b/components/autofill/core/browser/credit_card.cc
@@ -39,12 +39,15 @@ namespace autofill { -const base::char16 kMidlineEllipsis[] = { 0x22ef, 0 }; +const base::char16 kMidlineEllipsis[] = { 0x0020, 0x0020, + 0x2022, 0x2006, + 0x2022, 0x2006, + 0x2022, 0x2006, + 0x2022, 0x2006, 0 }; namespace { const base::char16 kCreditCardObfuscationSymbol = '*'; -const base::char16 kNonBreakingSpace[] = { 0x00a0, 0 }; bool ConvertYear(const base::string16& year, int* num) { // If the |year| is empty, clear the stored value. @@ -499,8 +502,7 @@ return type; // TODO(estade): i18n? - return type + base::string16(kNonBreakingSpace) + - base::string16(kMidlineEllipsis) + digits; + return type + base::string16(kMidlineEllipsis) + digits; } base::string16 CreditCard::AbbreviatedExpirationDateForDisplay() const {
diff --git a/components/autofill/core/browser/credit_card_unittest.cc b/components/autofill/core/browser/credit_card_unittest.cc index d0a535e..a0cb0c63 100644 --- a/components/autofill/core/browser/credit_card_unittest.cc +++ b/components/autofill/core/browser/credit_card_unittest.cc
@@ -59,6 +59,13 @@ "3056 9309 0259 04aa", /* non-digit characters */ }; +const std::string kUTF8MidlineEllipsis = + " " + "\xE2\x80\xA2\xE2\x80\x86" + "\xE2\x80\xA2\xE2\x80\x86" + "\xE2\x80\xA2\xE2\x80\x86" + "\xE2\x80\xA2\xE2\x80\x86"; + } // namespace // Tests credit card summary string generation. This test simulates a variety @@ -93,14 +100,10 @@ test::SetCreditCardInfo( &credit_card2, "John Dillinger", "5105 1051 0510 5100", "", "2010"); base::string16 summary2 = credit_card2.Label(); - EXPECT_EQ(UTF8ToUTF16( - "MasterCard\xC2\xA0\xE2\x8B\xAF" - "5100"), + EXPECT_EQ(UTF8ToUTF16("MasterCard" + kUTF8MidlineEllipsis + "5100"), summary2); base::string16 obfuscated2 = credit_card2.TypeAndLastFourDigits(); - EXPECT_EQ(UTF8ToUTF16( - "MasterCard\xC2\xA0\xE2\x8B\xAF" - "5100"), + EXPECT_EQ(UTF8ToUTF16("MasterCard" + kUTF8MidlineEllipsis + "5100"), obfuscated2); // Case 3: No year. @@ -108,14 +111,10 @@ test::SetCreditCardInfo( &credit_card3, "John Dillinger", "5105 1051 0510 5100", "01", ""); base::string16 summary3 = credit_card3.Label(); - EXPECT_EQ(UTF8ToUTF16( - "MasterCard\xC2\xA0\xE2\x8B\xAF" - "5100"), + EXPECT_EQ(UTF8ToUTF16("MasterCard" + kUTF8MidlineEllipsis + "5100"), summary3); base::string16 obfuscated3 = credit_card3.TypeAndLastFourDigits(); - EXPECT_EQ(UTF8ToUTF16( - "MasterCard\xC2\xA0\xE2\x8B\xAF" - "5100"), + EXPECT_EQ(UTF8ToUTF16("MasterCard" + kUTF8MidlineEllipsis + "5100"), obfuscated3); // Case 4: Have everything. @@ -123,14 +122,10 @@ test::SetCreditCardInfo( &credit_card4, "John Dillinger", "5105 1051 0510 5100", "01", "2010"); base::string16 summary4 = credit_card4.Label(); - EXPECT_EQ(UTF8ToUTF16( - "MasterCard\xC2\xA0\xE2\x8B\xAF" - "5100, 01/2010"), + EXPECT_EQ(UTF8ToUTF16("MasterCard" + kUTF8MidlineEllipsis + "5100, 01/2010"), summary4); base::string16 obfuscated4 = credit_card4.TypeAndLastFourDigits(); - EXPECT_EQ(UTF8ToUTF16( - "MasterCard\xC2\xA0\xE2\x8B\xAF" - "5100"), + EXPECT_EQ(UTF8ToUTF16("MasterCard" + kUTF8MidlineEllipsis + "5100"), obfuscated4); // Case 5: Very long credit card @@ -140,14 +135,10 @@ "John Dillinger", "0123456789 0123456789 0123456789 5105 1051 0510 5100", "01", "2010"); base::string16 summary5 = credit_card5.Label(); - EXPECT_EQ(UTF8ToUTF16( - "Card\xC2\xA0\xE2\x8B\xAF" - "5100, 01/2010"), + EXPECT_EQ(UTF8ToUTF16("Card" + kUTF8MidlineEllipsis + "5100, 01/2010"), summary5); base::string16 obfuscated5 = credit_card5.TypeAndLastFourDigits(); - EXPECT_EQ(UTF8ToUTF16( - "Card\xC2\xA0\xE2\x8B\xAF" - "5100"), + EXPECT_EQ(UTF8ToUTF16("Card" + kUTF8MidlineEllipsis + "5100"), obfuscated5); }
diff --git a/components/autofill/core/browser/personal_data_manager_unittest.cc b/components/autofill/core/browser/personal_data_manager_unittest.cc index 49bee42b..9678b18d 100644 --- a/components/autofill/core/browser/personal_data_manager_unittest.cc +++ b/components/autofill/core/browser/personal_data_manager_unittest.cc
@@ -61,6 +61,13 @@ enum UserMode { USER_MODE_NORMAL, USER_MODE_INCOGNITO }; +const std::string kUTF8MidlineEllipsis = + " " + "\xE2\x80\xA2\xE2\x80\x86" + "\xE2\x80\xA2\xE2\x80\x86" + "\xE2\x80\xA2\xE2\x80\x86" + "\xE2\x80\xA2\xE2\x80\x86"; + ACTION(QuitMainMessageLoop) { base::MessageLoop::current()->QuitWhenIdle(); } @@ -3697,8 +3704,7 @@ AutofillType(CREDIT_CARD_NUMBER), /* field_contents= */ base::string16()); ASSERT_EQ(1U, suggestions.size()); - EXPECT_EQ(UTF8ToUTF16("Amex\xC2\xA0\xE2\x8B\xAF" - "8555"), + EXPECT_EQ(UTF8ToUTF16("Amex" + kUTF8MidlineEllipsis + "8555"), suggestions[0].value); EXPECT_EQ(ASCIIToUTF16("04/99"), suggestions[0].label); } @@ -3761,17 +3767,13 @@ suggestions = personal_data_->GetCreditCardSuggestions( AutofillType(CREDIT_CARD_NUMBER), /* field_contents= */ base::string16()); ASSERT_EQ(4U, suggestions.size()); - EXPECT_EQ(UTF8ToUTF16("Visa\xC2\xA0\xE2\x8B\xAF" - "9012"), + EXPECT_EQ(UTF8ToUTF16("Visa" + kUTF8MidlineEllipsis + "9012"), suggestions[0].value); - EXPECT_EQ(UTF8ToUTF16("Amex\xC2\xA0\xE2\x8B\xAF" - "8555"), + EXPECT_EQ(UTF8ToUTF16("Amex" + kUTF8MidlineEllipsis + "8555"), suggestions[1].value); - EXPECT_EQ(UTF8ToUTF16("MasterCard\xC2\xA0\xE2\x8B\xAF" - "2109"), + EXPECT_EQ(UTF8ToUTF16("MasterCard" + kUTF8MidlineEllipsis + "2109"), suggestions[2].value); - EXPECT_EQ(UTF8ToUTF16("Visa\xC2\xA0\xE2\x8B\xAF" - "2109"), + EXPECT_EQ(UTF8ToUTF16("Visa" + kUTF8MidlineEllipsis + "2109"), suggestions[3].value); }
diff --git a/components/browser_sync/profile_sync_service.cc b/components/browser_sync/profile_sync_service.cc index aea8af1..67e30c88 100644 --- a/components/browser_sync/profile_sync_service.cc +++ b/components/browser_sync/profile_sync_service.cc
@@ -203,7 +203,7 @@ channel_(init_params.channel), blocking_pool_(init_params.blocking_pool), is_first_time_sync_configure_(false), - backend_initialized_(false), + engine_initialized_(false), sync_disabled_by_admin_(false), is_auth_in_progress_(false), signin_(std::move(init_params.signin_wrapper)), @@ -247,7 +247,7 @@ gaia_cookie_manager_service_->RemoveObserver(this); sync_prefs_.RemoveSyncPrefObserver(this); // Shutdown() should have been called before destruction. - CHECK(!backend_initialized_); + CHECK(!engine_initialized_); } bool ProfileSyncService::CanSyncStart() const { @@ -263,8 +263,8 @@ // against the controller impl changing to post tasks. startup_controller_ = base::MakeUnique<syncer::StartupController>( &sync_prefs_, - base::Bind(&ProfileSyncService::CanBackendStart, base::Unretained(this)), - base::Bind(&ProfileSyncService::StartUpSlowBackendComponents, + base::Bind(&ProfileSyncService::CanEngineStart, base::Unretained(this)), + base::Bind(&ProfileSyncService::StartUpSlowEngineComponents, weak_factory_.GetWeakPtr())); std::unique_ptr<sync_sessions::LocalSessionEventRouter> router( sync_client_->GetSyncSessionsClient()->GetLocalSessionEventRouter()); @@ -391,8 +391,8 @@ return; } - if (backend_) - backend_->StartSyncingWithServer(); + if (engine_) + engine_->StartSyncingWithServer(); } void ProfileSyncService::RegisterAuthNotifications() { @@ -499,8 +499,8 @@ return !IsFirstSetupComplete(); } -void ProfileSyncService::InitializeBackend(bool delete_stale_data) { - if (!backend_) { +void ProfileSyncService::InitializeEngine(bool delete_stale_data) { + if (!engine_) { NOTREACHED(); return; } @@ -547,7 +547,7 @@ base::Unretained(network_resources_.get()), url_request_context_, network_time_update_callback_); - backend_->Initialize( + engine_->Initialize( this, sync_thread_->task_runner(), GetJsEventHandler(), sync_service_url_, local_device_->GetSyncUserAgent(), credentials, delete_stale_data, enable_local_sync_backend, local_sync_backend_folder, @@ -618,7 +618,7 @@ NotifyObservers(); } - if (backend_.get()) { + if (engine_) { DVLOG(1) << "A data type requested sync startup, but it looks like " "something else beat it to the punch."; return; @@ -627,17 +627,17 @@ startup_controller_->OnDataTypeRequestsSyncStartup(type); } -void ProfileSyncService::StartUpSlowBackendComponents() { +void ProfileSyncService::StartUpSlowEngineComponents() { invalidation::InvalidationService* invalidator = sync_client_->GetInvalidationService(); - backend_.reset(sync_client_->GetSyncApiComponentFactory()->CreateSyncEngine( + engine_.reset(sync_client_->GetSyncApiComponentFactory()->CreateSyncEngine( debug_identifier_, invalidator, sync_prefs_.AsWeakPtr(), directory_path_)); - // Initialize the backend. Every time we start up a new SyncEngine, we'll want + // Initialize the engine. Every time we start up a new SyncEngine, we'll want // to start from a fresh SyncDB, so delete any old one that might be there. - InitializeBackend(ShouldDeleteSyncFolder()); + InitializeEngine(ShouldDeleteSyncFolder()); UpdateFirstSyncTimePref(); @@ -659,8 +659,8 @@ sync_prefs_.SetSyncAuthError(false); } - if (HasSyncingBackend()) - backend_->UpdateCredentials(GetCredentials()); + if (HasSyncingEngine()) + engine_->UpdateCredentials(GetCredentials()); else startup_controller_->TryStart(); } @@ -732,12 +732,11 @@ void ProfileSyncService::OnRefreshTokensLoaded() { DCHECK(thread_checker_.CalledOnValidThread()); - // This notification gets fired when OAuth2TokenService loads the tokens - // from storage. - // Initialize the backend if sync is enabled. If the sync token was - // not loaded, GetCredentials() will generate invalid credentials to - // cause the backend to generate an auth error (crbug.com/121755). - if (HasSyncingBackend()) { + // This notification gets fired when OAuth2TokenService loads the tokens from + // storage. Initialize the engine if sync is enabled. If the sync token was + // not loaded, GetCredentials() will generate invalid credentials to cause the + // engine to generate an auth error (crbug.com/121755). + if (HasSyncingEngine()) { RequestAccessToken(); } else { startup_controller_->TryStart(); @@ -760,9 +759,9 @@ } void ProfileSyncService::ShutdownImpl(syncer::ShutdownReason reason) { - if (!backend_) { + if (!engine_) { if (reason == syncer::ShutdownReason::DISABLE_SYNC && sync_thread_) { - // If the backend is already shut down when a DISABLE_SYNC happens, + // If the engine is already shut down when a DISABLE_SYNC happens, // the data directory needs to be cleaned up here. sync_thread_->task_runner()->PostTask( FROM_HERE, base::Bind(&DeleteSyncDataFolder, directory_path_)); @@ -775,16 +774,16 @@ RemoveClientFromServer(); } - // First, we spin down the backend to stop change processing as soon as + // First, we spin down the engine to stop change processing as soon as // possible. base::Time shutdown_start_time = base::Time::Now(); - backend_->StopSyncingForShutdown(); + engine_->StopSyncingForShutdown(); - // Stop all data type controllers, if needed. Note that until Stop - // completes, it is possible in theory to have a ChangeProcessor apply a - // change from a native model. In that case, it will get applied to the sync - // database (which doesn't get destroyed until we destroy the backend below) - // as an unsynced change. That will be persisted, and committed on restart. + // Stop all data type controllers, if needed. Note that until Stop completes, + // it is possible in theory to have a ChangeProcessor apply a change from a + // native model. In that case, it will get applied to the sync database (which + // doesn't get destroyed until we destroy the engine below) as an unsynced + // change. That will be persisted, and committed on restart. if (data_type_manager_) { if (data_type_manager_->state() != DataTypeManager::STOPPED) { // When aborting as part of shutdown, we should expect an aborted sync @@ -795,13 +794,13 @@ data_type_manager_.reset(); } - // Shutdown the migrator before the backend to ensure it doesn't pull a null + // Shutdown the migrator before the engine to ensure it doesn't pull a null // snapshot. migrator_.reset(); sync_js_controller_.AttachJsBackend(WeakHandle<syncer::JsBackend>()); - backend_->Shutdown(reason); - backend_.reset(); + engine_->Shutdown(reason); + engine_.reset(); base::TimeDelta shutdown_time = base::Time::Now() - shutdown_start_time; UMA_HISTOGRAM_TIMES("Sync.Shutdown.BackendDestroyedTime", shutdown_time); @@ -819,7 +818,7 @@ // Clear various flags. expect_sync_configuration_aborted_ = false; is_auth_in_progress_ = false; - backend_initialized_ = false; + engine_initialized_ = false; cached_passphrase_.clear(); encryption_pending_ = false; encrypt_everything_ = false; @@ -843,8 +842,8 @@ case KEEP_DATA: // TODO(maxbogue): Investigate whether this logic can/should be moved // into ShutdownImpl or the sync engine itself. - if (HasSyncingBackend()) { - backend_->UnregisterInvalidationIds(); + if (HasSyncingEngine()) { + engine_->UnregisterInvalidationIds(); } ShutdownImpl(syncer::STOP_SYNC); break; @@ -866,7 +865,7 @@ void ProfileSyncService::SetFirstSetupComplete() { DCHECK(thread_checker_.CalledOnValidThread()); sync_prefs_.SetFirstSetupComplete(); - if (IsBackendInitialized()) { + if (IsEngineInitialized()) { ReconfigureDatatypeManager(); } } @@ -941,12 +940,12 @@ void ProfileSyncService::ReenableDatatype(syncer::ModelType type) { DCHECK(thread_checker_.CalledOnValidThread()); - if (!backend_initialized_ || !data_type_manager_) + if (!engine_initialized_ || !data_type_manager_) return; data_type_manager_->ReenableType(type); } -void ProfileSyncService::UpdateBackendInitUMA(bool success) { +void ProfileSyncService::UpdateEngineInitUMA(bool success) { is_first_time_sync_configure_ = !IsFirstSetupComplete(); if (is_first_time_sync_configure_) { @@ -955,9 +954,9 @@ UMA_HISTOGRAM_BOOLEAN("Sync.BackendInitializeRestoreSuccess", success); } - base::Time on_backend_initialized_time = base::Time::Now(); + base::Time on_engine_initialized_time = base::Time::Now(); base::TimeDelta delta = - on_backend_initialized_time - startup_controller_->start_engine_time(); + on_engine_initialized_time - startup_controller_->start_engine_time(); if (is_first_time_sync_configure_) { UMA_HISTOGRAM_LONG_TIMES("Sync.BackendInitializeFirstTime", delta); } else { @@ -965,21 +964,21 @@ } } -void ProfileSyncService::PostBackendInitialization() { +void ProfileSyncService::PostEngineInitialization() { if (protocol_event_observers_.might_have_observers()) { - backend_->RequestBufferedProtocolEventsAndEnableForwarding(); + engine_->RequestBufferedProtocolEventsAndEnableForwarding(); } if (type_debug_info_observers_.might_have_observers()) { - backend_->EnableDirectoryTypeDebugInfoForwarding(); + engine_->EnableDirectoryTypeDebugInfoForwarding(); } // If we have a cached passphrase use it to decrypt/encrypt data now that the - // backend is initialized. We want to call this before notifying observers in + // engine is initialized. We want to call this before notifying observers in // case this operation affects the "passphrase required" status. ConsumeCachedPassphraseIfPossible(); - // The very first time the backend initializes is effectively the first time + // The very first time the engine initializes is effectively the first time // we can say we successfully "synced". LastSyncedTime will only be null in // this case, because the pref wasn't restored on StartUp. if (sync_prefs_.GetLastSyncedTime().is_null()) { @@ -1007,14 +1006,14 @@ NotifyObservers(); } -void ProfileSyncService::OnBackendInitialized( +void ProfileSyncService::OnEngineInitialized( const syncer::WeakHandle<syncer::JsBackend>& js_backend, const syncer::WeakHandle<syncer::DataTypeDebugInfoListener>& debug_info_listener, const std::string& cache_guid, bool success) { DCHECK(thread_checker_.CalledOnValidThread()); - UpdateBackendInitUMA(success); + UpdateEngineInitUMA(success); if (!success) { // Something went unexpectedly wrong. Play it safe: stop syncing at once @@ -1030,11 +1029,11 @@ // we get here, we will have already tried and failed to delete the // directory. It would be no big deal if we tried to delete it again. OnInternalUnrecoverableError(FROM_HERE, "BackendInitialize failure", false, - ERROR_REASON_BACKEND_INIT_FAILURE); + ERROR_REASON_ENGINE_INIT_FAILURE); return; } - backend_initialized_ = true; + engine_initialized_ = true; sync_js_controller_.AttachJsBackend(js_backend); debug_info_listener_ = debug_info_listener; @@ -1048,7 +1047,7 @@ local_device_->Initialize(cache_guid, signin_scoped_device_id, blocking_pool_); - PostBackendInitialization(); + PostEngineInitialization(); } void ProfileSyncService::OnSyncCycleCompleted() { @@ -1139,7 +1138,7 @@ // further needs to be done at this point. } else if (request_access_token_backoff_.failure_count() == 0) { // First time request without delay. Currently invalid token is used - // to initialize sync backend and we'll always end up here. We don't + // to initialize sync engine and we'll always end up here. We don't // want to delay initialization. request_access_token_backoff_.InformOfRequest(false); RequestAccessToken(); @@ -1173,13 +1172,13 @@ syncer::PassphraseRequiredReason reason, const sync_pb::EncryptedData& pending_keys) { DCHECK(thread_checker_.CalledOnValidThread()); - DCHECK(backend_.get()); - DCHECK(backend_->IsNigoriEnabled()); + DCHECK(engine_); + DCHECK(engine_->IsNigoriEnabled()); // TODO(lipalani) : add this check to other locations as well. if (HasUnrecoverableError()) { // When unrecoverable error is detected we post a task to shutdown the - // backend. The task might not have executed yet. + // engine. The task might not have executed yet. return; } @@ -1251,8 +1250,8 @@ void ProfileSyncService::OnMigrationNeededForTypes(syncer::ModelTypeSet types) { DCHECK(thread_checker_.CalledOnValidThread()); - DCHECK(backend_initialized_); - DCHECK(data_type_manager_.get()); + DCHECK(engine_initialized_); + DCHECK(data_type_manager_); // Migrator must be valid, because we don't sync until it is created and this // callback originates from a sync cycle. @@ -1339,7 +1338,8 @@ } void ProfileSyncService::ClearAndRestartSyncForPassphraseEncryption() { - backend_->ClearServerData( + DCHECK(thread_checker_.CalledOnValidThread()); + engine_->ClearServerData( base::Bind(&ProfileSyncService::OnClearServerDataDone, sync_enabled_weak_factory_.GetWeakPtr())); } @@ -1365,7 +1365,7 @@ data_type_status_table_ = result.data_type_status_table; // We should have cleared our cached passphrase before we get here (in - // OnBackendInitialized()). + // OnEngineInitialized()). DCHECK(cached_passphrase_.empty()); if (!sync_configure_start_time_.is_null()) { @@ -1430,7 +1430,7 @@ // This must be done before we start syncing with the server to avoid // sending unencrypted data up on a first time sync. if (encryption_pending_) - backend_->EnableEncryptEverything(); + engine_->EnableEncryptEverything(); NotifyObservers(); if (migrator_.get() && migrator_->state() != BackendMigrator::IDLE) { @@ -1461,11 +1461,11 @@ DCHECK(thread_checker_.CalledOnValidThread()); if (HasUnrecoverableError()) { return UNRECOVERABLE_ERROR; - } else if (!backend_) { + } else if (!engine_) { return NOT_ENABLED; - } else if (backend_.get() && !IsFirstSetupComplete()) { + } else if (engine_ && !IsFirstSetupComplete()) { return SETUP_INCOMPLETE; - } else if (backend_ && IsFirstSetupComplete() && data_type_manager_ && + } else if (engine_ && IsFirstSetupComplete() && data_type_manager_ && data_type_manager_->state() == DataTypeManager::STOPPED) { return DATATYPES_NOT_INITIALIZED; } else if (IsSyncActive()) { @@ -1499,7 +1499,7 @@ } } -std::string ProfileSyncService::GetBackendInitializationStateString() const { +std::string ProfileSyncService::GetEngineInitializationStateString() const { DCHECK(thread_checker_.CalledOnValidThread()); return startup_controller_->GetEngineInitializationStateString(); } @@ -1511,8 +1511,8 @@ bool ProfileSyncService::QueryDetailedSyncStatus(SyncEngine::Status* result) { DCHECK(thread_checker_.CalledOnValidThread()); - if (backend_.get() && backend_initialized_) { - *result = backend_->GetDetailedStatus(); + if (engine_ && engine_initialized_) { + *result = engine_->GetDetailedStatus(); return true; } else { SyncEngine::Status status; @@ -1559,14 +1559,14 @@ bool ProfileSyncService::IsSyncActive() const { DCHECK(thread_checker_.CalledOnValidThread()); - return backend_initialized_ && data_type_manager_ && + return engine_initialized_ && data_type_manager_ && data_type_manager_->state() != DataTypeManager::STOPPED; } void ProfileSyncService::TriggerRefresh(const syncer::ModelTypeSet& types) { DCHECK(thread_checker_.CalledOnValidThread()); - if (backend_initialized_) - backend_->TriggerRefresh(types); + if (engine_initialized_) + engine_->TriggerRefresh(types); } bool ProfileSyncService::IsSignedIn() const { @@ -1574,15 +1574,15 @@ return !signin_->GetAccountIdToUse().empty(); } -bool ProfileSyncService::CanBackendStart() const { +bool ProfileSyncService::CanEngineStart() const { return CanSyncStart() && oauth2_token_service_ && oauth2_token_service_->RefreshTokenIsAvailable( signin_->GetAccountIdToUse()); } -bool ProfileSyncService::IsBackendInitialized() const { +bool ProfileSyncService::IsEngineInitialized() const { DCHECK(thread_checker_.CalledOnValidThread()); - return backend_initialized_; + return engine_initialized_; } bool ProfileSyncService::ConfigurationDone() const { @@ -1707,7 +1707,7 @@ DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(syncer::UserSelectableTypes().HasAll(chosen_types)); - if (!backend_.get() && !HasUnrecoverableError()) { + if (!engine_ && !HasUnrecoverableError()) { NOTREACHED(); return; } @@ -1797,18 +1797,18 @@ syncer::PassphraseType ProfileSyncService::GetPassphraseType() const { DCHECK(thread_checker_.CalledOnValidThread()); - return backend_->GetPassphraseType(); + return engine_->GetPassphraseType(); } base::Time ProfileSyncService::GetExplicitPassphraseTime() const { DCHECK(thread_checker_.CalledOnValidThread()); - return backend_->GetExplicitPassphraseTime(); + return engine_->GetExplicitPassphraseTime(); } bool ProfileSyncService::IsCryptographerReady( const syncer::BaseTransaction* trans) const { DCHECK(thread_checker_.CalledOnValidThread()); - return backend_.get() && backend_->IsCryptographerReady(trans); + return engine_ && engine_->IsCryptographerReady(trans); } void ProfileSyncService::SetPlatformSyncAllowedProvider( @@ -1835,7 +1835,7 @@ restart = true; data_type_manager_.reset( sync_client_->GetSyncApiComponentFactory()->CreateDataTypeManager( - debug_info_listener_, &data_type_controllers_, this, backend_.get(), + debug_info_listener_, &data_type_controllers_, this, engine_.get(), this)); // We create the migrator at the same time. @@ -1865,8 +1865,8 @@ syncer::UserShare* ProfileSyncService::GetUserShare() const { DCHECK(thread_checker_.CalledOnValidThread()); - if (backend_.get() && backend_initialized_) { - return backend_->GetUserShare(); + if (engine_ && engine_initialized_) { + return engine_->GetUserShare(); } NOTREACHED(); return nullptr; @@ -1874,15 +1874,15 @@ syncer::SyncCycleSnapshot ProfileSyncService::GetLastCycleSnapshot() const { DCHECK(thread_checker_.CalledOnValidThread()); - if (backend_) - return backend_->GetLastCycleSnapshot(); + if (engine_) + return engine_->GetLastCycleSnapshot(); return syncer::SyncCycleSnapshot(); } bool ProfileSyncService::HasUnsyncedItems() const { DCHECK(thread_checker_.CalledOnValidThread()); - if (HasSyncingBackend() && backend_initialized_) { - return backend_->HasUnsyncedItems(); + if (HasSyncingEngine() && engine_initialized_) { + return engine_->HasUnsyncedItems(); } NOTREACHED(); return false; @@ -1896,8 +1896,8 @@ void ProfileSyncService::GetModelSafeRoutingInfo( syncer::ModelSafeRoutingInfo* out) const { DCHECK(thread_checker_.CalledOnValidThread()); - if (backend_.get() && backend_initialized_) { - backend_->GetModelSafeRoutingInfo(out); + if (engine_ && engine_initialized_) { + engine_->GetModelSafeRoutingInfo(out); } else { NOTREACHED(); } @@ -1907,7 +1907,7 @@ DCHECK(thread_checker_.CalledOnValidThread()); auto result = base::MakeUnique<base::ListValue>(); - if (!backend_.get() || !backend_initialized_) { + if (!engine_ || !engine_initialized_) { return std::move(result); } @@ -1916,7 +1916,7 @@ ModelTypeSet active_types; ModelTypeSet passive_types; ModelSafeRoutingInfo routing_info; - backend_->GetModelSafeRoutingInfo(&routing_info); + engine_->GetModelSafeRoutingInfo(&routing_info); for (ModelSafeRoutingInfo::const_iterator it = routing_info.begin(); it != routing_info.end(); ++it) { if (it->second == syncer::GROUP_PASSIVE) { @@ -1926,7 +1926,7 @@ } } - SyncEngine::Status detailed_status = backend_->GetDetailedStatus(); + SyncEngine::Status detailed_status = engine_->GetDetailedStatus(); ModelTypeSet& throttled_types(detailed_status.throttled_types); ModelTypeSet& backed_off_types(detailed_status.backed_off_types); ModelTypeSet registered = GetRegisteredDataTypes(); @@ -2010,13 +2010,13 @@ } void ProfileSyncService::ConsumeCachedPassphraseIfPossible() { - // If no cached passphrase, or sync backend hasn't started up yet, just exit. - // If the backend isn't running yet, OnBackendInitialized() will call this - // method again after the backend starts up. - if (cached_passphrase_.empty() || !IsBackendInitialized()) + // If no cached passphrase, or sync engine hasn't started up yet, just exit. + // If the engine isn't running yet, OnEngineInitialized() will call this + // method again after the engine starts up. + if (cached_passphrase_.empty() || !IsEngineInitialized()) return; - // Backend is up and running, so we can consume the cached passphrase. + // Engine is up and running, so we can consume the cached passphrase. std::string passphrase = cached_passphrase_; cached_passphrase_.clear(); @@ -2063,8 +2063,8 @@ void ProfileSyncService::SetEncryptionPassphrase(const std::string& passphrase, PassphraseType type) { DCHECK(thread_checker_.CalledOnValidThread()); - // This should only be called when the backend has been initialized. - DCHECK(IsBackendInitialized()); + // This should only be called when the engine has been initialized. + DCHECK(IsEngineInitialized()); DCHECK(!(type == IMPLICIT && IsUsingSecondaryPassphrase())) << "Data is already encrypted using an explicit passphrase"; DCHECK(!(type == EXPLICIT && @@ -2082,7 +2082,7 @@ passphrase_required_reason_ = syncer::REASON_PASSPHRASE_NOT_REQUIRED; NotifyObservers(); } - backend_->SetEncryptionPassphrase(passphrase, type == EXPLICIT); + engine_->SetEncryptionPassphrase(passphrase, type == EXPLICIT); } bool ProfileSyncService::SetDecryptionPassphrase( @@ -2090,7 +2090,7 @@ DCHECK(thread_checker_.CalledOnValidThread()); if (IsPassphraseRequired()) { DVLOG(1) << "Setting passphrase for decryption."; - bool result = backend_->SetDecryptionPassphrase(passphrase); + bool result = engine_->SetDecryptionPassphrase(passphrase); UMA_HISTOGRAM_BOOLEAN("Sync.PassphraseDecryptionSucceeded", result); return result; } else { @@ -2107,7 +2107,7 @@ void ProfileSyncService::SetEncryptEverythingAllowed(bool allowed) { DCHECK(thread_checker_.CalledOnValidThread()); - DCHECK(allowed || !IsBackendInitialized() || !IsEncryptEverythingEnabled()); + DCHECK(allowed || !IsEngineInitialized() || !IsEncryptEverythingEnabled()); encrypt_everything_allowed_ = allowed; } @@ -2115,10 +2115,10 @@ DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(IsEncryptEverythingAllowed()); - // Tests override IsBackendInitialized() to always return true, so we - // must check that instead of |backend_initialized_|. + // Tests override IsEngineInitialized() to always return true, so we + // must check that instead of |engine_initialized_|. // TODO(akalin): Fix the above. :/ - DCHECK(IsBackendInitialized()); + DCHECK(IsEngineInitialized()); // TODO(atwilson): Persist the encryption_pending_ flag to address the various // problems around cancelling encryption in the background (crbug.com/119649). if (!encrypt_everything_) @@ -2135,7 +2135,7 @@ bool ProfileSyncService::IsEncryptEverythingEnabled() const { DCHECK(thread_checker_.CalledOnValidThread()); - DCHECK(backend_initialized_); + DCHECK(engine_initialized_); return encrypt_everything_ || encryption_pending_; } @@ -2163,15 +2163,15 @@ DCHECK(thread_checker_.CalledOnValidThread()); if (IsSyncRequested() && !password.empty()) { cached_passphrase_ = password; - // Try to consume the passphrase we just cached. If the sync backend + // Try to consume the passphrase we just cached. If the sync engine // is not running yet, the passphrase will remain cached until the - // backend starts up. + // engine starts up. ConsumeCachedPassphraseIfPossible(); } #if defined(OS_CHROMEOS) RefreshSpareBootstrapToken(password); #endif - if (!IsBackendInitialized() || GetAuthError().state() != AuthError::NONE) { + if (!IsEngineInitialized() || GetAuthError().state() != AuthError::NONE) { // Track the fact that we're still waiting for auth to complete. is_auth_in_progress_ = true; } @@ -2191,7 +2191,7 @@ const std::vector<gaia::ListedAccount>& signed_out_accounts, const GoogleServiceAuthError& error) { DCHECK(thread_checker_.CalledOnValidThread()); - if (!IsBackendInitialized()) + if (!IsEngineInitialized()) return; bool cookie_jar_mismatch = true; @@ -2208,7 +2208,7 @@ DVLOG(1) << "Cookie jar mismatch: " << cookie_jar_mismatch; DVLOG(1) << "Cookie jar empty: " << cookie_jar_empty; - backend_->OnCookieJarChanged(cookie_jar_mismatch, cookie_jar_empty); + engine_->OnCookieJarChanged(cookie_jar_mismatch, cookie_jar_empty); } void ProfileSyncService::AddObserver(syncer::SyncServiceObserver* observer) { @@ -2225,8 +2225,8 @@ ProtocolEventObserver* observer) { DCHECK(thread_checker_.CalledOnValidThread()); protocol_event_observers_.AddObserver(observer); - if (HasSyncingBackend()) { - backend_->RequestBufferedProtocolEventsAndEnableForwarding(); + if (HasSyncingEngine()) { + engine_->RequestBufferedProtocolEventsAndEnableForwarding(); } } @@ -2234,9 +2234,8 @@ ProtocolEventObserver* observer) { DCHECK(thread_checker_.CalledOnValidThread()); protocol_event_observers_.RemoveObserver(observer); - if (HasSyncingBackend() && - !protocol_event_observers_.might_have_observers()) { - backend_->DisableProtocolEventForwarding(); + if (HasSyncingEngine() && !protocol_event_observers_.might_have_observers()) { + engine_->DisableProtocolEventForwarding(); } } @@ -2245,8 +2244,8 @@ DCHECK(thread_checker_.CalledOnValidThread()); type_debug_info_observers_.AddObserver(type_debug_info_observer); if (type_debug_info_observers_.might_have_observers() && - backend_initialized_) { - backend_->EnableDirectoryTypeDebugInfoForwarding(); + engine_initialized_) { + engine_->EnableDirectoryTypeDebugInfoForwarding(); } } @@ -2255,8 +2254,8 @@ DCHECK(thread_checker_.CalledOnValidThread()); type_debug_info_observers_.RemoveObserver(type_debug_info_observer); if (!type_debug_info_observers_.might_have_observers() && - backend_initialized_) { - backend_->DisableDirectoryTypeDebugInfoForwarding(); + engine_initialized_) { + engine_->DisableDirectoryTypeDebugInfoForwarding(); } } @@ -2349,8 +2348,8 @@ scoped_refptr<GetAllNodesRequestHelper> helper = new GetAllNodesRequestHelper(all_types, callback); - if (!backend_initialized_) { - // If there's no backend available to fulfill the request, handle it here. + if (!engine_initialized_) { + // If there's no engine available to fulfill the request, handle it here. for (ModelTypeSet::Iterator it = all_types.First(); it.Good(); it.Inc()) { helper->OnReceivedNodesForType(it.Get(), base::MakeUnique<base::ListValue>()); @@ -2442,8 +2441,8 @@ DCHECK(thread_checker_.CalledOnValidThread()); // If we haven't initialized yet, don't configure the DTM as it could cause // association to start before a Directory has even been created. - if (backend_initialized_) { - DCHECK(backend_.get()); + if (engine_initialized_) { + DCHECK(engine_); ConfigureDataTypeManager(); } else if (HasUnrecoverableError()) { // There is nothing more to configure. So inform the listeners, @@ -2452,7 +2451,7 @@ DVLOG(1) << "ConfigureDataTypeManager not invoked because of an " << "Unrecoverable error."; } else { - DVLOG(0) << "ConfigureDataTypeManager not invoked because backend is not " + DVLOG(0) << "ConfigureDataTypeManager not invoked because engine is not " << "initialized"; } } @@ -2532,8 +2531,8 @@ network_resources_ = std::move(network_resources); } -bool ProfileSyncService::HasSyncingBackend() const { - return backend_ != nullptr; +bool ProfileSyncService::HasSyncingEngine() const { + return engine_ != nullptr; } void ProfileSyncService::UpdateFirstSyncTimePref() { @@ -2547,10 +2546,10 @@ void ProfileSyncService::FlushDirectory() const { DCHECK(thread_checker_.CalledOnValidThread()); - // backend_initialized_ implies backend_ isn't null and the manager exists. + // engine_initialized_ implies engine_ isn't null and the manager exists. // If sync is not initialized yet, we fail silently. - if (backend_initialized_) - backend_->FlushDirectory(); + if (engine_initialized_) + engine_->FlushDirectory(); } base::FilePath ProfileSyncService::GetDirectoryPathForTest() const { @@ -2569,12 +2568,12 @@ void ProfileSyncService::RefreshTypesForTest(syncer::ModelTypeSet types) { DCHECK(thread_checker_.CalledOnValidThread()); - if (backend_initialized_) - backend_->RefreshTypesForTest(types); + if (engine_initialized_) + engine_->RefreshTypesForTest(types); } void ProfileSyncService::RemoveClientFromServer() const { - if (!backend_initialized_) + if (!engine_initialized_) return; const std::string cache_guid = local_device_->GetLocalSyncCacheGUID(); std::string birthday; @@ -2642,7 +2641,7 @@ DCHECK(startup_controller_->IsSetupInProgress()); startup_controller_->SetSetupInProgress(false); - if (IsBackendInitialized()) + if (IsEngineInitialized()) ReconfigureDatatypeManager(); NotifyObservers(); }
diff --git a/components/browser_sync/profile_sync_service.h b/components/browser_sync/profile_sync_service.h index f974d5d2..ca82b84 100644 --- a/components/browser_sync/profile_sync_service.h +++ b/components/browser_sync/profile_sync_service.h
@@ -93,11 +93,10 @@ namespace browser_sync { // ProfileSyncService is the layer between browser subsystems like bookmarks, -// and the sync backend. Each subsystem is logically thought of as being -// a sync datatype. -// -// Individual datatypes can, at any point, be in a variety of stages of being -// "enabled". Here are some specific terms for concepts used in this class: +// and the sync engine. Each subsystem is logically thought of as being a sync +// datatype. Individual datatypes can, at any point, be in a variety of stages +// of being "enabled". Here are some specific terms for concepts used in this +// class: // // 'Registered' (feature suppression for a datatype) // @@ -127,7 +126,7 @@ // synchronized: the syncer has been instructed to querying the server // for this datatype, first-time merges have finished, and there is an // actively installed ChangeProcessor that listens for changes to this -// datatype, propagating such changes into and out of the sync backend +// datatype, propagating such changes into and out of the sync engine // as necessary. // // When a datatype is in the process of becoming active, it may be @@ -282,7 +281,7 @@ bool ConfigurationDone() const override; const GoogleServiceAuthError& GetAuthError() const override; bool HasUnrecoverableError() const override; - bool IsBackendInitialized() const override; + bool IsEngineInitialized() const override; sync_sessions::OpenTabsUIDelegate* GetOpenTabsUIDelegate() override; bool IsPassphraseRequiredForDecryption() const override; base::Time GetExplicitPassphraseTime() const override; @@ -307,7 +306,7 @@ std::string QuerySyncStatusSummaryString() override; bool QueryDetailedSyncStatus(syncer::SyncStatus* result) override; base::string16 GetLastSyncedTimeString() const override; - std::string GetBackendInitializationStateString() const override; + std::string GetEngineInitializationStateString() const override; syncer::SyncCycleSnapshot GetLastCycleSnapshot() const override; std::unique_ptr<base::Value> GetTypeStatusMap() override; const GURL& sync_service_url() const override; @@ -359,7 +358,7 @@ void OnSessionRestoreComplete(); // SyncEngineHost implementation. - void OnBackendInitialized( + void OnEngineInitialized( const syncer::WeakHandle<syncer::JsBackend>& js_backend, const syncer::WeakHandle<syncer::DataTypeDebugInfoListener>& debug_info_listener, @@ -416,10 +415,10 @@ SyncStatusSummary QuerySyncStatusSummary(); // Reconfigures the data type manager with the latest enabled types. - // Note: Does not initialize the backend if it is not already initialized. + // Note: Does not initialize the engine if it is not already initialized. // This function needs to be called only after sync has been initialized // (i.e.,only for reconfigurations). The reason we don't initialize the - // backend is because if we had encountered an unrecoverable error we don't + // engine is because if we had encountered an unrecoverable error we don't // want to startup once more. // This function is called by |SetSetupInProgress|. virtual void ReconfigureDatatypeManager(); @@ -456,7 +455,7 @@ const std::string& message) override; // The functions below (until ActivateDataType()) should only be - // called if IsBackendInitialized() is true. + // called if IsEngineInitialized() is true. // TODO(akalin): These two functions are used only by // ProfileSyncServiceHarness. Figure out a different way to expose @@ -484,7 +483,7 @@ // Changes which data types we're going to be syncing to |preferred_types|. // If it is running, the DataTypeManager will be instructed to reconfigure - // the sync backend so that exactly these datatypes are actively synced. See + // the sync engine so that exactly these datatypes are actively synced. See // class comment for more on what it means for a datatype to be Preferred. virtual void ChangePreferredDataTypes(syncer::ModelTypeSet preferred_types); @@ -592,7 +591,7 @@ enum UnrecoverableErrorReason { ERROR_REASON_UNSET, ERROR_REASON_SYNCER, - ERROR_REASON_BACKEND_INIT_FAILURE, + ERROR_REASON_ENGINE_INIT_FAILURE, ERROR_REASON_CONFIGURATION_RETRY, ERROR_REASON_CONFIGURATION_FAILURE, ERROR_REASON_ACTIONABLE_ERROR, @@ -627,9 +626,9 @@ // Helper to install and configure a data type manager. void ConfigureDataTypeManager(); - // Shuts down the backend sync components. + // Shuts down the engine sync components. // |reason| dictates if syncing is being disabled or not, and whether - // to claim ownership of sync thread from backend. + // to claim ownership of sync thread from engine. void ShutdownImpl(syncer::ShutdownReason reason); // Return SyncCredentials from the OAuth2TokenService. @@ -655,13 +654,13 @@ // Update the last auth error and notify observers of error state. void UpdateAuthErrorState(const GoogleServiceAuthError& error); - // Puts the backend's sync scheduler into NORMAL mode. + // Puts the engine's sync scheduler into NORMAL mode. // Called when configuration is complete. void StartSyncingWithServer(); // During initial signin, ProfileSyncService caches the user's signin // passphrase so it can be used to encrypt/decrypt data after sync starts up. - // This routine is invoked once the backend has started up to use the + // This routine is invoked once the engine has started up to use the // cached passphrase and clear it out when it is done. void ConsumeCachedPassphraseIfPossible(); @@ -671,12 +670,12 @@ // token. void RequestAccessToken(); - // Return true if backend should start from a fresh sync DB. + // Return true if engine should start from a fresh sync DB. bool ShouldDeleteSyncFolder(); // If |delete_sync_data_folder| is true, then this method will delete all // previous "Sync Data" folders. (useful if the folder is partial/corrupt). - void InitializeBackend(bool delete_sync_data_folder); + void InitializeEngine(bool delete_sync_data_folder); // Sets the last synced time to the current time. void UpdateLastSyncedTime(); @@ -689,8 +688,8 @@ void ClearUnrecoverableError(); - // Starts up the backend sync components. - void StartUpSlowBackendComponents(); + // Starts up the engine sync components. + virtual void StartUpSlowEngineComponents(); // Collects preferred sync data types from |preference_providers_|. syncer::ModelTypeSet GetDataTypesFromPreferenceProviders() const; @@ -714,24 +713,24 @@ bool delete_sync_database, UnrecoverableErrorReason reason); - // Update UMA for syncing backend. - void UpdateBackendInitUMA(bool success); + // Update UMA for syncing engine. + void UpdateEngineInitUMA(bool success); - // Various setup following backend initialization, mostly for syncing backend. - void PostBackendInitialization(); + // Various setup following engine initialization, mostly for syncing engine. + void PostEngineInitialization(); // Whether sync has been authenticated with an account ID. bool IsSignedIn() const; - // The backend can only start if sync can start and has an auth token. This is - // different fron CanSyncStart because it represents whether the backend can + // The engine can only start if sync can start and has an auth token. This is + // different fron CanSyncStart because it represents whether the engine can // be started at this moment, whereas CanSyncStart represents whether sync can // conceptually start without further user action (acquiring a token is an // automatic process). - bool CanBackendStart() const; + bool CanEngineStart() const; - // True if a syncing backend exists. - bool HasSyncingBackend() const; + // True if a syncing engine exists. + bool HasSyncingEngine() const; // Update first sync time stored in preferences void UpdateFirstSyncTimePref(); @@ -756,7 +755,7 @@ // Calls data type manager to start catch up configure. void BeginConfigureCatchUpBeforeClear(); - // Calls sync backend to send ClearServerDataMessage to server. + // Calls sync engine to send ClearServerDataMessage to server. void ClearAndRestartSyncForPassphraseEncryption(); // Restarts sync clearing directory in the process. @@ -773,9 +772,9 @@ // user. GoogleServiceAuthError last_auth_error_; - // Our asynchronous backend to communicate with sync components living on + // Our asynchronous engine to communicate with sync components living on // other threads. - std::unique_ptr<syncer::SyncEngine> backend_; + std::unique_ptr<syncer::SyncEngine> engine_; // Was the last SYNC_PASSPHRASE_REQUIRED notification sent because it // was required for encryption, decryption with a cached passphrase, or @@ -799,7 +798,7 @@ // OnConfigureDone is called. base::Time sync_configure_start_time_; - // Callback to update the network time; used for initializing the backend. + // Callback to update the network time; used for initializing the engine. syncer::NetworkTimeUpdateCallback network_time_update_callback_; // The path to the base directory under which sync should store its @@ -819,7 +818,7 @@ base::SequencedWorkerPool* blocking_pool_; // Indicates if this is the first time sync is being configured. This value - // is equal to !IsFirstSetupComplete() at the time of OnBackendInitialized(). + // is equal to !IsFirstSetupComplete() at the time of OnEngineInitialized(). bool is_first_time_sync_configure_; // Number of UIs currently configuring the Sync service. When this number @@ -830,15 +829,15 @@ syncer::DataTypeController::TypeMap data_type_controllers_; // Whether the SyncEngine has been initialized. - bool backend_initialized_; + bool engine_initialized_; // Set when sync receives DISABLED_BY_ADMIN error from server. Prevents - // ProfileSyncService from starting backend till browser restarted or user + // ProfileSyncService from starting engine till browser restarted or user // signed out. bool sync_disabled_by_admin_; // Set to true if a signin has completed but we're still waiting for the - // backend to refresh its credentials. + // engine to refresh its credentials. bool is_auth_in_progress_; // Encapsulates user signin - used to set/get the user's authenticated @@ -867,7 +866,7 @@ bool expect_sync_configuration_aborted_; // Sometimes we need to temporarily hold on to a passphrase because we don't - // yet have a backend to send it to. This happens during initialization as + // yet have a engine to send it to. This happens during initialization as // we don't StartUp until we have a valid token, which happens after valid // credentials were provided. std::string cached_passphrase_; @@ -964,7 +963,7 @@ std::unique_ptr<base::MemoryPressureListener> memory_pressure_listener_; // Nigori state after user switching to custom passphrase, saved until - // transition steps complete. It will be injected into new backend after sync + // transition steps complete. It will be injected into new engine after sync // restart. std::unique_ptr<syncer::SyncEncryptionHandler::NigoriState> saved_nigori_state_;
diff --git a/components/browser_sync/profile_sync_service_mock.h b/components/browser_sync/profile_sync_service_mock.h index 2c735276..5e8c5ca 100644 --- a/components/browser_sync/profile_sync_service_mock.h +++ b/components/browser_sync/profile_sync_service_mock.h
@@ -34,7 +34,7 @@ virtual ~ProfileSyncServiceMock(); MOCK_METHOD4( - OnBackendInitialized, + OnEngineInitialized, void(const syncer::WeakHandle<syncer::JsBackend>&, const syncer::WeakHandle<syncer::DataTypeDebugInfoListener>&, const std::string&, @@ -73,7 +73,7 @@ MOCK_CONST_METHOD0(GetLastSyncedTimeString, base::string16()); MOCK_CONST_METHOD0(HasUnrecoverableError, bool()); MOCK_CONST_METHOD0(IsSyncActive, bool()); - MOCK_CONST_METHOD0(IsBackendInitialized, bool()); + MOCK_CONST_METHOD0(IsEngineInitialized, bool()); MOCK_CONST_METHOD0(IsSyncRequested, bool()); MOCK_CONST_METHOD0(waiting_for_auth, bool()); MOCK_METHOD1(OnActionableError, void(const syncer::SyncProtocolError&));
diff --git a/components/browser_sync/profile_sync_service_startup_unittest.cc b/components/browser_sync/profile_sync_service_startup_unittest.cc index 61192bd..1ff7cdfa 100644 --- a/components/browser_sync/profile_sync_service_startup_unittest.cc +++ b/components/browser_sync/profile_sync_service_startup_unittest.cc
@@ -408,7 +408,7 @@ EXPECT_CALL(observer_, OnStateChanged()).Times(AnyNumber()); IssueTestTokens(account_id); sync_service_->Initialize(); - EXPECT_TRUE(sync_service_->IsBackendInitialized()); + EXPECT_TRUE(sync_service_->IsEngineInitialized()); EXPECT_TRUE(sync_service_->IsSyncActive()); // The service should stop when switching to managed mode. @@ -417,7 +417,7 @@ .WillOnce(Return(DataTypeManager::CONFIGURED)); EXPECT_CALL(*data_type_manager, Stop()).Times(1); pref_service()->SetBoolean(syncer::prefs::kSyncManaged, true); - EXPECT_FALSE(sync_service_->IsBackendInitialized()); + EXPECT_FALSE(sync_service_->IsEngineInitialized()); // Note that PSS no longer references |data_type_manager| after stopping. // When switching back to unmanaged, the state should change but sync should @@ -427,7 +427,7 @@ EXPECT_CALL(*component_factory_, CreateDataTypeManager(_, _, _, _, _)) .Times(0); pref_service()->ClearPref(syncer::prefs::kSyncManaged); - EXPECT_FALSE(sync_service_->IsBackendInitialized()); + EXPECT_FALSE(sync_service_->IsEngineInitialized()); EXPECT_FALSE(sync_service_->IsSyncActive()); }
diff --git a/components/browser_sync/test_profile_sync_service.cc b/components/browser_sync/test_profile_sync_service.cc index c7222ae5..a4bc8d08 100644 --- a/components/browser_sync/test_profile_sync_service.cc +++ b/components/browser_sync/test_profile_sync_service.cc
@@ -30,7 +30,7 @@ } syncer::UserShare* TestProfileSyncService::GetUserShare() const { - return backend_->GetUserShare(); + return engine_->GetUserShare(); } } // namespace browser_sync
diff --git a/components/ntp_snippets/BUILD.gn b/components/ntp_snippets/BUILD.gn index 84c9c8a..448fc8d66 100644 --- a/components/ntp_snippets/BUILD.gn +++ b/components/ntp_snippets/BUILD.gn
@@ -151,7 +151,7 @@ "//components/strings", "//components/sync:test_support_driver", "//components/sync_sessions", - "//components/variations", + "//components/variations:test_support", "//net:test_support", "//testing/gtest", "//third_party/icu/",
diff --git a/components/ntp_snippets/remote/ntp_snippets_fetcher_unittest.cc b/components/ntp_snippets/remote/ntp_snippets_fetcher_unittest.cc index e5533de8bd..c02ffa4 100644 --- a/components/ntp_snippets/remote/ntp_snippets_fetcher_unittest.cc +++ b/components/ntp_snippets/remote/ntp_snippets_fetcher_unittest.cc
@@ -25,7 +25,7 @@ #include "components/signin/core/browser/fake_signin_manager.h" #include "components/signin/core/browser/test_signin_client.h" #include "components/variations/entropy_provider.h" -#include "components/variations/variations_associated_data.h" +#include "components/variations/variations_params_manager.h" #include "net/url_request/test_url_fetcher_factory.h" #include "net/url_request/url_request_test_util.h" #include "testing/gmock/include/gmock/gmock.h"
diff --git a/components/ntp_snippets/remote/ntp_snippets_status_service.h b/components/ntp_snippets/remote/ntp_snippets_status_service.h new file mode 100644 index 0000000..27380f495 --- /dev/null +++ b/components/ntp_snippets/remote/ntp_snippets_status_service.h
@@ -0,0 +1,82 @@ +// Copyright 2016 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 COMPONENTS_NTP_SNIPPETS_REMOTE_NTP_SNIPPETS_STATUS_SERVICE_H_ +#define COMPONENTS_NTP_SNIPPETS_REMOTE_NTP_SNIPPETS_STATUS_SERVICE_H_ + +#include "base/callback.h" +#include "base/gtest_prod_util.h" +#include "base/scoped_observer.h" +#include "components/prefs/pref_change_registrar.h" + +class PrefRegistrySimple; +class PrefService; +class SigninManagerBase; + +namespace ntp_snippets { + +enum class SnippetsStatus : int { + // Snippets are enabled and the user is signed in. + ENABLED_AND_SIGNED_IN, + // Snippets are enabled and the user is signed out (sign in is not required). + ENABLED_AND_SIGNED_OUT, + // Snippets have been disabled as part of the service configuration. + EXPLICITLY_DISABLED, + // The user is not signed in, and the service requires it to be enabled. + SIGNED_OUT_AND_DISABLED, +}; + +// Aggregates data from preferences and signin to notify the snippet service of +// relevant changes in their states. +class NTPSnippetsStatusService { + public: + using SnippetsStatusChangeCallback = + base::Callback<void(SnippetsStatus /*old_status*/, + SnippetsStatus /*new_status*/)>; + + NTPSnippetsStatusService(SigninManagerBase* signin_manager, + PrefService* pref_service); + + virtual ~NTPSnippetsStatusService(); + + static void RegisterProfilePrefs(PrefRegistrySimple* registry); + + // Starts listening for changes from the dependencies. |callback| will be + // called when a significant change in state is detected. + void Init(const SnippetsStatusChangeCallback& callback); + + // To be called when the signin state changed. Will compute the new + // state considering the initialisation configuration and the preferences, + // and notify via the registered callback if appropriate. + void OnSignInStateChanged(); + + private: + FRIEND_TEST_ALL_PREFIXES(NTPSnippetsStatusServiceTest, + SigninStateCompatibility); + FRIEND_TEST_ALL_PREFIXES(NTPSnippetsStatusServiceTest, DisabledViaPref); + + // Callback for the PrefChangeRegistrar. + void OnSnippetsEnabledChanged(); + + void OnStateChanged(SnippetsStatus new_snippets_status); + + bool IsSignedIn() const; + + SnippetsStatus GetSnippetsStatusFromDeps() const; + + SnippetsStatus snippets_status_; + SnippetsStatusChangeCallback snippets_status_change_callback_; + + bool require_signin_; + SigninManagerBase* signin_manager_; + PrefService* pref_service_; + + PrefChangeRegistrar pref_change_registrar_; + + DISALLOW_COPY_AND_ASSIGN(NTPSnippetsStatusService); +}; + +} // namespace ntp_snippets + +#endif // COMPONENTS_NTP_SNIPPETS_REMOTE_NTP_SNIPPETS_STATUS_SERVICE_H_
diff --git a/components/ntp_snippets/remote/remote_suggestions_provider_unittest.cc b/components/ntp_snippets/remote/remote_suggestions_provider_unittest.cc index 2d77622..b593ee4 100644 --- a/components/ntp_snippets/remote/remote_suggestions_provider_unittest.cc +++ b/components/ntp_snippets/remote/remote_suggestions_provider_unittest.cc
@@ -38,7 +38,7 @@ #include "components/prefs/testing_pref_service.h" #include "components/signin/core/browser/fake_profile_oauth2_token_service.h" #include "components/signin/core/browser/fake_signin_manager.h" -#include "components/variations/variations_associated_data.h" +#include "components/variations/variations_params_manager.h" #include "net/url_request/test_url_fetcher_factory.h" #include "net/url_request/url_request_test_util.h" #include "testing/gmock/include/gmock/gmock.h"
diff --git a/components/offline_pages/background/offliner.h b/components/offline_pages/background/offliner.h index cafd0686..7238de9 100644 --- a/components/offline_pages/background/offliner.h +++ b/components/offline_pages/background/offliner.h
@@ -44,6 +44,9 @@ PRERENDERING_NOT_STARTED = 9, // Prerendering failed with hard error so should not retry the request. PRERENDERING_FAILED_NO_RETRY = 10, + // Prerendering failed with some error that suggests we should not continue + // processing another request at this time. + PRERENDERING_FAILED_NO_NEXT = 11, // NOTE: insert new values above this line and update histogram enum too. STATUS_COUNT };
diff --git a/components/offline_pages/background/request_coordinator.cc b/components/offline_pages/background/request_coordinator.cc index 52983866..beb881b 100644 --- a/components/offline_pages/background/request_coordinator.cc +++ b/components/offline_pages/background/request_coordinator.cc
@@ -888,11 +888,12 @@ case Offliner::RequestStatus::SAVE_FAILED: case Offliner::RequestStatus::REQUEST_COORDINATOR_CANCELED: case Offliner::RequestStatus::REQUEST_COORDINATOR_TIMED_OUT: + case Offliner::RequestStatus::PRERENDERING_FAILED: case Offliner::RequestStatus::PRERENDERING_FAILED_NO_RETRY: return true; case Offliner::RequestStatus::FOREGROUND_CANCELED: case Offliner::RequestStatus::PRERENDERING_CANCELED: - case Offliner::RequestStatus::PRERENDERING_FAILED: + case Offliner::RequestStatus::PRERENDERING_FAILED_NO_NEXT: // No further processing in this service window. return false; default:
diff --git a/components/offline_pages/background/request_coordinator_event_logger.cc b/components/offline_pages/background/request_coordinator_event_logger.cc index 1585265..a638138 100644 --- a/components/offline_pages/background/request_coordinator_event_logger.cc +++ b/components/offline_pages/background/request_coordinator_event_logger.cc
@@ -33,6 +33,8 @@ return "PRERENDERING_NOT_STARTED"; case Offliner::PRERENDERING_FAILED_NO_RETRY: return "PRERENDERING_FAILED_NO_RETRY"; + case Offliner::PRERENDERING_FAILED_NO_NEXT: + return "PRERENDERING_FAILED_NO_NEXT"; default: NOTREACHED(); return std::to_string(static_cast<int>(request_status));
diff --git a/components/offline_pages/background/request_coordinator_unittest.cc b/components/offline_pages/background/request_coordinator_unittest.cc index c352863..5fec208 100644 --- a/components/offline_pages/background/request_coordinator_unittest.cc +++ b/components/offline_pages/background/request_coordinator_unittest.cc
@@ -625,13 +625,12 @@ Offliner::RequestStatus::PRERENDERING_FAILED); PumpLoop(); - // For retriable failure, processing should stop and scheduler callback - // called (so that request can be retried first next processing window). - EXPECT_TRUE(immediate_schedule_callback_called()); + // For retriable failure, processing should continue to 2nd request so + // no scheduler callback yet. + EXPECT_FALSE(immediate_schedule_callback_called()); - // TODO(dougarnett): Consider injecting mock RequestPicker for this test - // and verifying that there is no attempt to pick another request following - // this failure code. + // Busy processing 2nd request. + EXPECT_TRUE(is_busy()); coordinator()->queue()->GetRequests( base::Bind(&RequestCoordinatorTest::GetRequestsDone, @@ -642,7 +641,8 @@ // (max number of attempts exceeded). EXPECT_EQ(1UL, last_requests().size()); // Check that the observer got the notification that we failed (and the - // subsequent notification that the request was removed). + // subsequent notification that the request was removed) since we exceeded + // retry count. EXPECT_TRUE(observer().completed_called()); EXPECT_EQ(RequestCoordinator::BackgroundSavePageResult::RETRY_COUNT_EXCEEDED, observer().last_status()); @@ -669,9 +669,8 @@ // no scheduler callback yet. EXPECT_FALSE(immediate_schedule_callback_called()); - // TODO(dougarnett): Consider injecting mock RequestPicker for this test - // and verifying that there is as attempt to pick another request following - // this non-retryable failure code. + // Busy processing 2nd request. + EXPECT_TRUE(is_busy()); coordinator()->queue()->GetRequests(base::Bind( &RequestCoordinatorTest::GetRequestsDone, base::Unretained(this))); @@ -686,6 +685,38 @@ observer().last_status()); } +TEST_F(RequestCoordinatorTest, OfflinerDoneRequestFailedNoNextFailure) { + // Add a request to the queue, wait for callbacks to finish. + offline_pages::SavePageRequest request(kRequestId1, kUrl1, kClientId1, + base::Time::Now(), kUserRequested); + SetupForOfflinerDoneCallbackTest(&request); + EnableOfflinerCallback(false); + + // Add second request to the queue to check handling when first fails. + AddRequest2(); + PumpLoop(); + + // Call the OfflinerDoneCallback to simulate the request failed, wait + // for callbacks. + SendOfflinerDoneCallback( + request, Offliner::RequestStatus::PRERENDERING_FAILED_NO_NEXT); + PumpLoop(); + + // For no next failure, processing should not continue to 2nd request so + // expect scheduler callback. + EXPECT_TRUE(immediate_schedule_callback_called()); + + // Not busy for NO_NEXT failure. + EXPECT_FALSE(is_busy()); + + coordinator()->queue()->GetRequests(base::Bind( + &RequestCoordinatorTest::GetRequestsDone, base::Unretained(this))); + PumpLoop(); + + // Both requests still in queue. + EXPECT_EQ(2UL, last_requests().size()); +} + TEST_F(RequestCoordinatorTest, OfflinerDoneForegroundCancel) { // Add a request to the queue, wait for callbacks to finish. offline_pages::SavePageRequest request(
diff --git a/components/power/BUILD.gn b/components/power/BUILD.gn deleted file mode 100644 index e8c347e..0000000 --- a/components/power/BUILD.gn +++ /dev/null
@@ -1,32 +0,0 @@ -# Copyright 2014 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. - -static_library("power") { - sources = [ - "origin_power_map.cc", - "origin_power_map.h", - "origin_power_map_factory.cc", - "origin_power_map_factory.h", - ] - - deps = [ - "//base", - "//components/keyed_service/content", - "//components/keyed_service/core", - "//content/public/common", - "//url", - ] -} - -source_set("unit_tests") { - testonly = true - sources = [ - "origin_power_map_unittest.cc", - ] - deps = [ - ":power", - "//base", - "//testing/gtest", - ] -}
diff --git a/components/power/DEPS b/components/power/DEPS deleted file mode 100644 index df2301d5..0000000 --- a/components/power/DEPS +++ /dev/null
@@ -1,4 +0,0 @@ -include_rules = [ - "+components/keyed_service", - "+content/public/common", -]
diff --git a/components/power/OWNERS b/components/power/OWNERS deleted file mode 100644 index 76a50a4..0000000 --- a/components/power/OWNERS +++ /dev/null
@@ -1,3 +0,0 @@ -derat@chromium.org -dhnishi@chromium.org -sivachandra@chromium.org
diff --git a/components/power/origin_power_map.cc b/components/power/origin_power_map.cc deleted file mode 100644 index 10502e9d..0000000 --- a/components/power/origin_power_map.cc +++ /dev/null
@@ -1,87 +0,0 @@ -// Copyright 2014 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/power/origin_power_map.h" - -#include <algorithm> - -#include "base/logging.h" -#include "content/public/common/url_constants.h" -#include "url/gurl.h" - -namespace power { - -OriginPowerMap::OriginPowerMap() : total_consumed_(0.0) { -} - -OriginPowerMap::~OriginPowerMap() { -} - -int OriginPowerMap::GetPowerForOrigin(const GURL& url) { - if (!total_consumed_) - return 0; - - OriginMap::const_iterator it = origin_map_.find(url.GetOrigin()); - return it == origin_map_.end() ? 0 : - static_cast<int>(it->second * 100 / total_consumed_ + 0.5); -} - -void OriginPowerMap::AddPowerForOrigin(const GURL& url, double power) { - DCHECK_GE(power, 0); - GURL origin = url.GetOrigin(); - if (!origin.is_valid() || origin.SchemeIs(content::kChromeUIScheme)) - return; - - origin_map_[origin] += power; - total_consumed_ += power; -} - -OriginPowerMap::PercentOriginMap OriginPowerMap::GetPercentOriginMap() { - OriginPowerMap::PercentOriginMap percent_map; - - if (!total_consumed_) - return percent_map; - - for (OriginMap::iterator it = origin_map_.begin(); it != origin_map_.end(); - ++it) { - percent_map[it->first] = - static_cast<int>(it->second * 100 / total_consumed_ + 0.5); - } - return percent_map; -} - -std::unique_ptr<OriginPowerMap::Subscription> -OriginPowerMap::AddPowerConsumptionUpdatedCallback( - const base::Closure& callback) { - return callback_list_.Add(callback); -} - -void OriginPowerMap::OnAllOriginsUpdated() { - callback_list_.Notify(); -} - -void OriginPowerMap::ClearOriginMap( - const base::Callback<bool(const GURL&)> url_filter) { - if (url_filter.is_null()) { - origin_map_.clear(); - } else { - for (auto it = origin_map_.begin(); it != origin_map_.end();) { - auto next_it = std::next(it); - - if (url_filter.Run(it->first)) { - total_consumed_ -= it->second; - origin_map_.erase(it); - } - - it = next_it; - } - } - - // Handle the empty case separately to avoid reporting nonzero power usage - // for zero origins in case of double rounding errors. - if (origin_map_.empty()) - total_consumed_ = 0; -} - -} // namespace power
diff --git a/components/power/origin_power_map.h b/components/power/origin_power_map.h deleted file mode 100644 index 362c023..0000000 --- a/components/power/origin_power_map.h +++ /dev/null
@@ -1,69 +0,0 @@ -// Copyright 2014 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 COMPONENTS_POWER_ORIGIN_POWER_MAP_H_ -#define COMPONENTS_POWER_ORIGIN_POWER_MAP_H_ - -#include <map> -#include <memory> - -#include "base/callback_list.h" -#include "base/macros.h" -#include "components/keyed_service/core/keyed_service.h" -#include "url/gurl.h" - -namespace power { - -// Tracks app and website origins and how much power they are consuming while -// running. -class OriginPowerMap : public KeyedService { - public: - typedef std::map<GURL, int> PercentOriginMap; - typedef base::CallbackList<void(void)>::Subscription Subscription; - - OriginPowerMap(); - ~OriginPowerMap() override; - - // Returns the integer percentage usage of the total power consumed by a - // given URL's origin. - int GetPowerForOrigin(const GURL& url); - - // Adds a certain amount of power consumption to a given URL's origin. - // |power| is a platform-specific heuristic estimating power consumption. - void AddPowerForOrigin(const GURL& url, double power); - - // Returns a map of all origins to the integer percentage usage of power - // consumed. - PercentOriginMap GetPercentOriginMap(); - - // Adds a callback for the completion of a round of updates to |origin_map_|. - std::unique_ptr<Subscription> AddPowerConsumptionUpdatedCallback( - const base::Closure& callback); - - // Notifies observers to let them know that the origin power map has finished - // updating for all origins this cycle. - void OnAllOriginsUpdated(); - - // Clears URLs out of the map. If |url_filter| is not null, only clears those - // URLs that are matched by it. - void ClearOriginMap(const base::Callback<bool(const GURL&)> url_filter); - - private: - // OriginMap maps a URL to the amount of power consumed by the URL using the - // same units as |total_consumed_|. - typedef std::map<GURL, double> OriginMap; - OriginMap origin_map_; - - // Total amount of power consumed using units determined by - // the power heuristics available to the platform. - double total_consumed_; - - base::CallbackList<void(void)> callback_list_; - - DISALLOW_COPY_AND_ASSIGN(OriginPowerMap); -}; - -} // namespace power - -#endif // COMPONENTS_POWER_ORIGIN_POWER_MAP_H_
diff --git a/components/power/origin_power_map_factory.cc b/components/power/origin_power_map_factory.cc deleted file mode 100644 index f1df4ff..0000000 --- a/components/power/origin_power_map_factory.cc +++ /dev/null
@@ -1,38 +0,0 @@ -// Copyright 2014 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/power/origin_power_map_factory.h" - -#include "base/memory/singleton.h" -#include "components/keyed_service/content/browser_context_dependency_manager.h" -#include "components/power/origin_power_map.h" - -namespace power { -// static -OriginPowerMap* OriginPowerMapFactory::GetForBrowserContext( - content::BrowserContext* context) { - return static_cast<OriginPowerMap*>( - GetInstance()->GetServiceForBrowserContext(context, true)); -} - -// static -OriginPowerMapFactory* OriginPowerMapFactory::GetInstance() { - return base::Singleton<OriginPowerMapFactory>::get(); -} - -OriginPowerMapFactory::OriginPowerMapFactory() - : BrowserContextKeyedServiceFactory( - "OriginPowerMap", - BrowserContextDependencyManager::GetInstance()) { -} - -OriginPowerMapFactory::~OriginPowerMapFactory() { -} - -KeyedService* OriginPowerMapFactory::BuildServiceInstanceFor( - content::BrowserContext* context) const { - return new OriginPowerMap(); -} - -} // namespace power
diff --git a/components/power/origin_power_map_factory.h b/components/power/origin_power_map_factory.h deleted file mode 100644 index a21ee6b4..0000000 --- a/components/power/origin_power_map_factory.h +++ /dev/null
@@ -1,36 +0,0 @@ -// Copyright 2014 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 COMPONENTS_POWER_ORIGIN_POWER_MAP_FACTORY_H_ -#define COMPONENTS_POWER_ORIGIN_POWER_MAP_FACTORY_H_ - -#include "base/macros.h" -#include "base/memory/singleton.h" -#include "components/keyed_service/content/browser_context_keyed_service_factory.h" - -namespace power { - -class OriginPowerMap; - -class OriginPowerMapFactory : public BrowserContextKeyedServiceFactory { - public: - static OriginPowerMap* GetForBrowserContext(content::BrowserContext* context); - static OriginPowerMapFactory* GetInstance(); - - private: - friend struct base::DefaultSingletonTraits<OriginPowerMapFactory>; - - OriginPowerMapFactory(); - ~OriginPowerMapFactory() override; - - // BrowserContextKeyedServiceFactory: - KeyedService* BuildServiceInstanceFor( - content::BrowserContext* context) const override; - - DISALLOW_COPY_AND_ASSIGN(OriginPowerMapFactory); -}; - -} // namespace power - -#endif // COMPONENTS_POWER_ORIGIN_POWER_MAP_FACTORY_H_
diff --git a/components/power/origin_power_map_unittest.cc b/components/power/origin_power_map_unittest.cc deleted file mode 100644 index 061f9bdd..0000000 --- a/components/power/origin_power_map_unittest.cc +++ /dev/null
@@ -1,133 +0,0 @@ -// Copyright 2014 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/power/origin_power_map.h" - -#include <stddef.h> - -#include "base/bind.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace { - -bool HostFilter(const std::string& host, const GURL& url) { - return url.host() == host; -} - -} // namespace - -namespace power { - -TEST(OriginPowerMapTest, StartEmpty) { - OriginPowerMap origin_power_map; - EXPECT_EQ(size_t(0), origin_power_map.GetPercentOriginMap().size()); -} - -TEST(OriginPowerMapTest, AddOneOriginNotInMap) { - OriginPowerMap origin_power_map; - GURL url("http://www.google.com"); - EXPECT_EQ(0, origin_power_map.GetPowerForOrigin(url)); - origin_power_map.AddPowerForOrigin(url, 10); - EXPECT_EQ(size_t(1), origin_power_map.GetPercentOriginMap().size()); - EXPECT_EQ(100, origin_power_map.GetPowerForOrigin(url)); -} - -TEST(OriginPowerMapTest, AddMultiplesOrigins) { - OriginPowerMap origin_power_map; - GURL url1("http://www.google.com"); - EXPECT_EQ(0, origin_power_map.GetPowerForOrigin(url1)); - origin_power_map.AddPowerForOrigin(url1, 10); - EXPECT_EQ(size_t(1), origin_power_map.GetPercentOriginMap().size()); - EXPECT_EQ(100, origin_power_map.GetPowerForOrigin(url1)); - - GURL url2("http://www.example.com"); - origin_power_map.AddPowerForOrigin(url2, 30); - EXPECT_EQ(25, origin_power_map.GetPowerForOrigin(url1)); - EXPECT_EQ(75, origin_power_map.GetPowerForOrigin(url2)); - origin_power_map.AddPowerForOrigin(url2, 10); - EXPECT_EQ(20, origin_power_map.GetPowerForOrigin(url1)); - EXPECT_EQ(80, origin_power_map.GetPowerForOrigin(url2)); - - GURL url3("https://www.google.com"); - origin_power_map.AddPowerForOrigin(url3, 50); - EXPECT_EQ(10, origin_power_map.GetPowerForOrigin(url1)); - EXPECT_EQ(40, origin_power_map.GetPowerForOrigin(url2)); - EXPECT_EQ(50, origin_power_map.GetPowerForOrigin(url3)); -} - -TEST(OriginPowerMapTest, PercentOriginMap) { - OriginPowerMap origin_power_map; - GURL url1("http://www.google.com"); - GURL url2("http://www.example.com"); - origin_power_map.AddPowerForOrigin(url1, 10); - origin_power_map.AddPowerForOrigin(url2, 40); - OriginPowerMap::PercentOriginMap origin_map = - origin_power_map.GetPercentOriginMap(); - EXPECT_EQ(20, origin_map.find(url1)->second); - EXPECT_EQ(80, origin_map.find(url2)->second); -} - -TEST(OriginPowerMapTest, EmptyPercentOriginMapWhenZeroConsumed) { - OriginPowerMap origin_power_map; - EXPECT_EQ(size_t(0), origin_power_map.GetPercentOriginMap().size()); - GURL url("http://www.google.com"); - origin_power_map.AddPowerForOrigin(url, 0); - EXPECT_EQ(size_t(0), origin_power_map.GetPercentOriginMap().size()); -} - -TEST(OriginPowerMapTest, ClearOriginMap) { - GURL url1("https://www.google.com"); - GURL url2("https://www.chrome.com"); - GURL url3("https://www.example.com"); - GURL url4("http://www.chrome.com"); - GURL url5("http://www.example.com"); - - // Add all 5 URLs to the map. - OriginPowerMap origin_power_map; - origin_power_map.AddPowerForOrigin(url1, 50); - origin_power_map.AddPowerForOrigin(url2, 20); - origin_power_map.AddPowerForOrigin(url3, 15); - origin_power_map.AddPowerForOrigin(url4, 5); - origin_power_map.AddPowerForOrigin(url5, 10); - EXPECT_DOUBLE_EQ(50, origin_power_map.GetPowerForOrigin(url1)); - EXPECT_DOUBLE_EQ(20, origin_power_map.GetPowerForOrigin(url2)); - EXPECT_DOUBLE_EQ(15, origin_power_map.GetPowerForOrigin(url3)); - EXPECT_DOUBLE_EQ(5, origin_power_map.GetPowerForOrigin(url4)); - EXPECT_DOUBLE_EQ(10, origin_power_map.GetPowerForOrigin(url5)); - - // Delete |url1|. - origin_power_map.ClearOriginMap(base::Bind( - static_cast<bool (*)(const GURL&, const GURL&)>(operator==), url1)); - EXPECT_DOUBLE_EQ(0, origin_power_map.GetPowerForOrigin(url1)); - EXPECT_DOUBLE_EQ(40, origin_power_map.GetPowerForOrigin(url2)); - EXPECT_DOUBLE_EQ(30, origin_power_map.GetPowerForOrigin(url3)); - EXPECT_DOUBLE_EQ(10, origin_power_map.GetPowerForOrigin(url4)); - EXPECT_DOUBLE_EQ(20, origin_power_map.GetPowerForOrigin(url5)); - - // Delete every URL with the host "www.chrome.com", i.e. |url2| and |url4|. - origin_power_map.ClearOriginMap(base::Bind(&HostFilter, "www.chrome.com")); - EXPECT_DOUBLE_EQ(0, origin_power_map.GetPowerForOrigin(url1)); - EXPECT_DOUBLE_EQ(0, origin_power_map.GetPowerForOrigin(url2)); - EXPECT_DOUBLE_EQ(60, origin_power_map.GetPowerForOrigin(url3)); - EXPECT_DOUBLE_EQ(0, origin_power_map.GetPowerForOrigin(url4)); - EXPECT_DOUBLE_EQ(40, origin_power_map.GetPowerForOrigin(url5)); - - // Delete every URL with the host "www.example.org". There should be none. - origin_power_map.ClearOriginMap(base::Bind(&HostFilter, "www.example.org")); - EXPECT_DOUBLE_EQ(0, origin_power_map.GetPowerForOrigin(url1)); - EXPECT_DOUBLE_EQ(0, origin_power_map.GetPowerForOrigin(url2)); - EXPECT_DOUBLE_EQ(60, origin_power_map.GetPowerForOrigin(url3)); - EXPECT_DOUBLE_EQ(0, origin_power_map.GetPowerForOrigin(url4)); - EXPECT_DOUBLE_EQ(40, origin_power_map.GetPowerForOrigin(url5)); - - // Null callback means complete deletion. - origin_power_map.ClearOriginMap(base::Callback<bool(const GURL&)>()); - EXPECT_DOUBLE_EQ(0, origin_power_map.GetPowerForOrigin(url1)); - EXPECT_DOUBLE_EQ(0, origin_power_map.GetPowerForOrigin(url2)); - EXPECT_DOUBLE_EQ(0, origin_power_map.GetPowerForOrigin(url3)); - EXPECT_DOUBLE_EQ(0, origin_power_map.GetPowerForOrigin(url4)); - EXPECT_DOUBLE_EQ(0, origin_power_map.GetPowerForOrigin(url5)); -} - -} // namespace power
diff --git a/components/precache/content/precache_manager.cc b/components/precache/content/precache_manager.cc index f4333c12..327fa2b6 100644 --- a/components/precache/content/precache_manager.cc +++ b/components/precache/content/precache_manager.cc
@@ -99,7 +99,7 @@ } PrecacheManager::AllowedType PrecacheManager::PrecachingAllowed() const { - if (!(sync_service_ && sync_service_->IsBackendInitialized())) + if (!(sync_service_ && sync_service_->IsEngineInitialized())) return AllowedType::PENDING; // SyncService delegates to SyncPrefs, which must be called on the UI thread. @@ -182,7 +182,7 @@ } } else { if (PrecachingAllowed() != AllowedType::PENDING) { - // We are not waiting on the sync backend to be initialized. The user + // We are not waiting on the sync engine to be initialized. The user // either is not in the field trial, or does not have sync enabled. // Pretend that precaching started, so that the PrecacheServiceLauncher // doesn't try to start it again.
diff --git a/components/sync/driver/about_sync_util.cc b/components/sync/driver/about_sync_util.cc index ab8082c..3a88b59 100644 --- a/components/sync/driver/about_sync_util.cc +++ b/components/sync/driver/about_sync_util.cc
@@ -389,7 +389,7 @@ last_synced.SetValue(service->GetLastSyncedTimeString()); is_setup_complete.SetValue(service->IsFirstSetupComplete()); backend_initialization.SetValue( - service->GetBackendInitializationStateString()); + service->GetEngineInitializationStateString()); if (is_status_valid) { is_syncing.SetValue(full_status.syncing); retry_time.SetValue(GetTimeStr(full_status.retry_time,
diff --git a/components/sync/driver/fake_sync_service.cc b/components/sync/driver/fake_sync_service.cc index 5cede64..fcc3d69c 100644 --- a/components/sync/driver/fake_sync_service.cc +++ b/components/sync/driver/fake_sync_service.cc
@@ -92,7 +92,7 @@ return false; } -bool FakeSyncService::IsBackendInitialized() const { +bool FakeSyncService::IsEngineInitialized() const { return false; } @@ -166,7 +166,7 @@ return base::string16(); } -std::string FakeSyncService::GetBackendInitializationStateString() const { +std::string FakeSyncService::GetEngineInitializationStateString() const { return std::string(); }
diff --git a/components/sync/driver/fake_sync_service.h b/components/sync/driver/fake_sync_service.h index 2c4a59f..fa031a83 100644 --- a/components/sync/driver/fake_sync_service.h +++ b/components/sync/driver/fake_sync_service.h
@@ -49,7 +49,7 @@ bool ConfigurationDone() const override; const GoogleServiceAuthError& GetAuthError() const override; bool HasUnrecoverableError() const override; - bool IsBackendInitialized() const override; + bool IsEngineInitialized() const override; sync_sessions::OpenTabsUIDelegate* GetOpenTabsUIDelegate() override; bool IsPassphraseRequiredForDecryption() const override; base::Time GetExplicitPassphraseTime() const override; @@ -69,7 +69,7 @@ std::string QuerySyncStatusSummaryString() override; bool QueryDetailedSyncStatus(SyncStatus* result) override; base::string16 GetLastSyncedTimeString() const override; - std::string GetBackendInitializationStateString() const override; + std::string GetEngineInitializationStateString() const override; SyncCycleSnapshot GetLastCycleSnapshot() const override; std::unique_ptr<base::Value> GetTypeStatusMap() override; const GURL& sync_service_url() const override;
diff --git a/components/sync/driver/glue/sync_backend_host_core.h b/components/sync/driver/glue/sync_backend_host_core.h index bdec45a..641fed6 100644 --- a/components/sync/driver/glue/sync_backend_host_core.h +++ b/components/sync/driver/glue/sync_backend_host_core.h
@@ -291,7 +291,7 @@ std::unique_ptr<SyncManager> sync_manager_; // Temporary holder of sync manager's initialization results. Set by - // OnInitializeComplete, and consumed when we pass it via OnBackendInitialized + // OnInitializeComplete, and consumed when we pass it via OnEngineInitialized // in the final state of HandleInitializationSuccessOnFrontendLoop. WeakHandle<JsBackend> js_backend_; WeakHandle<DataTypeDebugInfoListener> debug_info_listener_;
diff --git a/components/sync/driver/glue/sync_backend_host_impl.cc b/components/sync/driver/glue/sync_backend_host_impl.cc index b276ae2..77be61b 100644 --- a/components/sync/driver/glue/sync_backend_host_impl.cc +++ b/components/sync/driver/glue/sync_backend_host_impl.cc
@@ -562,15 +562,14 @@ // experimental types to enable. This should be done before we inform // the host to ensure they're visible in the customize screen. AddExperimentalTypes(); - host_->OnBackendInitialized(js_backend, debug_info_listener, cache_guid, - true); + host_->OnEngineInitialized(js_backend, debug_info_listener, cache_guid, true); } void SyncBackendHostImpl::HandleInitializationFailureOnFrontendLoop() { DCHECK(thread_checker_.CalledOnValidThread()); - host_->OnBackendInitialized(WeakHandle<JsBackend>(), - WeakHandle<DataTypeDebugInfoListener>(), "", - false); + host_->OnEngineInitialized(WeakHandle<JsBackend>(), + WeakHandle<DataTypeDebugInfoListener>(), "", + false); } void SyncBackendHostImpl::HandleSyncCycleCompletedOnFrontendLoop(
diff --git a/components/sync/driver/glue/sync_backend_host_impl_unittest.cc b/components/sync/driver/glue/sync_backend_host_impl_unittest.cc index 9572893..f615bb0 100644 --- a/components/sync/driver/glue/sync_backend_host_impl_unittest.cc +++ b/components/sync/driver/glue/sync_backend_host_impl_unittest.cc
@@ -75,7 +75,7 @@ public: virtual ~MockSyncEngineHost() {} - MOCK_METHOD4(OnBackendInitialized, + MOCK_METHOD4(OnEngineInitialized, void(const WeakHandle<JsBackend>&, const WeakHandle<DataTypeDebugInfoListener>&, const std::string&, @@ -205,7 +205,7 @@ // Synchronously initializes the backend. void InitializeBackend(bool expect_success) { - EXPECT_CALL(mock_host_, OnBackendInitialized(_, _, _, expect_success)) + EXPECT_CALL(mock_host_, OnEngineInitialized(_, _, _, expect_success)) .WillOnce(InvokeWithoutArgs(QuitMessageLoop)); SyncEngine::HttpPostProviderFactoryGetter http_post_provider_factory_getter =
diff --git a/components/sync/driver/sync_service.h b/components/sync/driver/sync_service.h index 51e40722..a07f5d5e 100644 --- a/components/sync/driver/sync_service.h +++ b/components/sync/driver/sync_service.h
@@ -64,7 +64,7 @@ }; // Passed as an argument to RequestStop to control whether or not the sync - // backend should clear its data directory when it shuts down. See + // engine should clear its data directory when it shuts down. See // RequestStop for more information. enum SyncStopDataFate { KEEP_DATA, @@ -75,7 +75,7 @@ struct SyncTokenStatus { SyncTokenStatus(); - // Sync server connection status reported by sync backend. + // Sync server connection status reported by sync engine. base::Time connection_status_update_time; ConnectionStatus connection_status; @@ -147,7 +147,7 @@ virtual bool CanSyncStart() const = 0; // Stops sync at the user's request. |data_fate| controls whether the sync - // backend should clear its data directory when it shuts down. Generally + // engine should clear its data directory when it shuts down. Generally // KEEP_DATA is used when the user just stops sync, and CLEAR_DATA is used // when they sign out of the profile entirely. virtual void RequestStop(SyncStopDataFate data_fate) = 0; @@ -201,7 +201,7 @@ virtual bool HasUnrecoverableError() const = 0; // Returns true if the SyncEngine has told us it's ready to accept changes. - virtual bool IsBackendInitialized() const = 0; + virtual bool IsEngineInitialized() const = 0; // Return the active OpenTabsUIDelegate. If open/proxy tabs is not enabled or // not currently syncing, returns nullptr. @@ -217,7 +217,7 @@ virtual base::Time GetExplicitPassphraseTime() const = 0; // Returns true if a secondary (explicit) passphrase is being used. It is not - // legal to call this method before the backend is initialized. + // legal to call this method before the engine is initialized. virtual bool IsUsingSecondaryPassphrase() const = 0; // Turns on encryption for all data. Callers must call OnUserChoseDatatypes() @@ -273,16 +273,16 @@ // Get a description of the sync status for displaying in the user interface. virtual std::string QuerySyncStatusSummaryString() = 0; - // Initializes a struct of status indicators with data from the backend. - // Returns false if the backend was not available for querying; in that case + // Initializes a struct of status indicators with data from the engine. + // Returns false if the engine was not available for querying; in that case // the struct will be filled with default data. virtual bool QueryDetailedSyncStatus(SyncStatus* result) = 0; // Returns a user-friendly string form of last synced time (in minutes). virtual base::string16 GetLastSyncedTimeString() const = 0; - // Returns a human readable string describing backend initialization state. - virtual std::string GetBackendInitializationStateString() const = 0; + // Returns a human readable string describing engine initialization state. + virtual std::string GetEngineInitializationStateString() const = 0; virtual SyncCycleSnapshot GetLastCycleSnapshot() const = 0;
diff --git a/components/sync/engine/fake_sync_engine.cc b/components/sync/engine/fake_sync_engine.cc index a6ab2bb..969ea891 100644 --- a/components/sync/engine/fake_sync_engine.cc +++ b/components/sync/engine/fake_sync_engine.cc
@@ -29,9 +29,9 @@ const base::Closure& report_unrecoverable_error_function, const HttpPostProviderFactoryGetter& http_post_provider_factory_getter, std::unique_ptr<SyncEncryptionHandler::NigoriState> saved_nigori_state) { - host->OnBackendInitialized(WeakHandle<JsBackend>(), - WeakHandle<DataTypeDebugInfoListener>(), - kTestCacheGuid, !fail_initial_download_); + host->OnEngineInitialized(WeakHandle<JsBackend>(), + WeakHandle<DataTypeDebugInfoListener>(), + kTestCacheGuid, !fail_initial_download_); } void FakeSyncEngine::TriggerRefresh(const ModelTypeSet& types) {}
diff --git a/components/sync/engine/sync_engine_host.h b/components/sync/engine/sync_engine_host.h index 55b670b..c04289b 100644 --- a/components/sync/engine/sync_engine_host.h +++ b/components/sync/engine/sync_engine_host.h
@@ -34,20 +34,19 @@ SyncEngineHost(); virtual ~SyncEngineHost(); - // The backend has completed initialization and it is now ready to - // accept and process changes. If success is false, initialization - // wasn't able to be completed and should be retried. + // The engine has completed initialization and it is now ready to accept and + // process changes. If success is false, initialization wasn't able to be + // completed and should be retried. // - // |js_backend| is what about:sync interacts with; it's different - // from the 'Backend' in 'OnBackendInitialized' (unfortunately). It - // is initialized only if |success| is true. - virtual void OnBackendInitialized( + // |js_backend| is what about:sync interacts with. It is initialized only if + // |success| is true. + virtual void OnEngineInitialized( const WeakHandle<JsBackend>& js_backend, const WeakHandle<DataTypeDebugInfoListener>& debug_info_listener, const std::string& cache_guid, bool success) = 0; - // The backend queried the server recently and received some updates. + // The engine queried the server recently and received some updates. virtual void OnSyncCycleCompleted() = 0; // Informs the host of some network event. These notifications are disabled by @@ -60,7 +59,7 @@ // Called when we receive an updated commit counter for a directory type. // // Disabled by default. Enable by calling - // EnableDirectoryTypeDebugInfoForwarding() on the backend. + // EnableDirectoryTypeDebugInfoForwarding() on the engine. virtual void OnDirectoryTypeCommitCounterUpdated( ModelType type, const CommitCounters& counters) = 0; @@ -68,7 +67,7 @@ // Called when we receive an updated update counter for a directory type. // // Disabled by default. Enable by calling - // EnableDirectoryTypeDebugInfoForwarding() on the backend. + // EnableDirectoryTypeDebugInfoForwarding() on the engine. virtual void OnDirectoryTypeUpdateCounterUpdated( ModelType type, const UpdateCounters& counters) = 0; @@ -76,7 +75,7 @@ // Called when we receive an updated status counter for a datatype. // // Disabled by default. Enable by calling - // EnableDirectoryTypeDebugInfoForwarding() on the backend. + // EnableDirectoryTypeDebugInfoForwarding() on the engine. virtual void OnDatatypeStatusCounterUpdated( ModelType type, const StatusCounters& counters) = 0; @@ -131,7 +130,7 @@ // // |nigori_state| contains the new (post custom passphrase) encryption keys // and can be used to restore SyncEncryptionHandler's state across sync - // backend instances. See also SyncEncryptionHandlerImpl::RestoreNigori. + // engine instances. See also SyncEncryptionHandlerImpl::RestoreNigori. virtual void OnLocalSetPassphraseEncryption( const SyncEncryptionHandler::NigoriState& nigori_state) = 0; };
diff --git a/components/variations/BUILD.gn b/components/variations/BUILD.gn index 925210b3..bc7008a 100644 --- a/components/variations/BUILD.gn +++ b/components/variations/BUILD.gn
@@ -93,6 +93,22 @@ } } +static_library("test_support") { + testonly = true + sources = [ + "variations_params_manager.cc", + "variations_params_manager.h", + ] + + public_deps = [ + ":variations", + ] + + deps = [ + "//base/test:test_support", + ] +} + source_set("unit_tests") { testonly = true sources = [
diff --git a/components/variations/variations_associated_data.cc b/components/variations/variations_associated_data.cc index ec12245..465aed5 100644 --- a/components/variations/variations_associated_data.cc +++ b/components/variations/variations_associated_data.cc
@@ -11,6 +11,7 @@ #include "base/feature_list.h" #include "base/macros.h" #include "base/memory/singleton.h" +#include "base/metrics/field_trial.h" #include "base/metrics/field_trial_param_associator.h" #include "base/strings/string_split.h" #include "components/variations/variations_http_header_provider.h" @@ -19,8 +20,6 @@ namespace { -const char kGroupTesting[] = "Testing"; - // The internal singleton accessor for the map, used to keep it thread-safe. class GroupMapAccessor { public: @@ -195,26 +194,6 @@ // They simply wrap existing functions in this file. namespace testing { -VariationParamsManager::VariationParamsManager( - const std::string& trial_name, - const std::map<std::string, std::string>& params) { - SetVariationParams(trial_name, params); -} - -VariationParamsManager::~VariationParamsManager() { - ClearAllVariationIDs(); - ClearAllVariationParams(); - field_trial_list_.reset(); -} - -void VariationParamsManager::SetVariationParams( - const std::string& trial_name, - const std::map<std::string, std::string>& params) { - field_trial_list_.reset(new base::FieldTrialList(nullptr)); - variations::AssociateVariationParams(trial_name, kGroupTesting, params); - base::FieldTrialList::CreateFieldTrial(trial_name, kGroupTesting); -} - void ClearAllVariationIDs() { GroupMapAccessor::GetInstance()->ClearAllMapsForTesting(); }
diff --git a/components/variations/variations_associated_data.h b/components/variations/variations_associated_data.h index 046c2b6..0734e8b 100644 --- a/components/variations/variations_associated_data.h +++ b/components/variations/variations_associated_data.h
@@ -10,7 +10,6 @@ #include <string> #include <vector> -#include "base/metrics/field_trial.h" #include "components/variations/active_field_trials.h" // This file provides various helpers that extend the functionality around @@ -45,7 +44,7 @@ namespace base { struct Feature; -} +} // namespace base namespace variations { @@ -163,35 +162,14 @@ // Expose some functions for testing. namespace testing { -// Use this class as a member in your test class to set variation params for -// your tests. You can directly set the parameters in the constructor (if they -// are used by other members upon construction). You can change them later -// arbitrarily many times using the SetVariationParams function. Internally, it -// creates a FieldTrialList as a member. It works well for multiple tests of a -// given test class, as it clears the parameters when this class is destructed. -// Note that it clears all parameters (not just those registered here). -class VariationParamsManager { - public: - VariationParamsManager(const std::string& trial_name, - const std::map<std::string, std::string>& params); - ~VariationParamsManager(); - - // Associates |params| with the given |trial_name|. It creates a new group, - // used only for testing. Between two calls of this function, - // ClearAllVariationParams() has to be called. - void SetVariationParams(const std::string& trial_name, - const std::map<std::string, std::string>& params); - - private: - std::unique_ptr<base::FieldTrialList> field_trial_list_; - - DISALLOW_COPY_AND_ASSIGN(VariationParamsManager); -}; - -// Clears all of the mapped associations. +// Clears all of the mapped associations. Deprecated, try to use +// VariationParamsManager instead as it does a lot of work for you +// automatically. void ClearAllVariationIDs(); -// Clears all of the associated params. +// Clears all of the associated params. Deprecated, try to use +// VariationParamsManager instead as it does a lot of work for you +// automatically. void ClearAllVariationParams(); } // namespace testing
diff --git a/components/variations/variations_params_manager.cc b/components/variations/variations_params_manager.cc new file mode 100644 index 0000000..e4b92cc --- /dev/null +++ b/components/variations/variations_params_manager.cc
@@ -0,0 +1,92 @@ +// Copyright 2016 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/variations/variations_params_manager.h" + +#include <utility> + +#include "base/feature_list.h" +#include "base/metrics/field_trial.h" +#include "base/test/scoped_feature_list.h" +#include "components/variations/variations_associated_data.h" + +namespace variations { +namespace testing { + +namespace { + +// The fixed testing group created in the provided trail when setting up params. +const char kGroupTesting[] = "Testing"; + +base::FieldTrial* CreateFieldTrialWithParams( + const std::string& trial_name, + const std::map<std::string, std::string>& param_values) { + variations::AssociateVariationParams(trial_name, kGroupTesting, param_values); + return base::FieldTrialList::CreateFieldTrial(trial_name, kGroupTesting); +} + +} // namespace + +VariationParamsManager::VariationParamsManager() + : field_trial_list_(new base::FieldTrialList(nullptr)), + scoped_feature_list_(new base::test::ScopedFeatureList()) {} + +VariationParamsManager::VariationParamsManager( + const std::string& trial_name, + const std::map<std::string, std::string>& param_values) + : VariationParamsManager() { + SetVariationParams(trial_name, param_values); +} + +VariationParamsManager::VariationParamsManager( + const std::string& trial_name, + const std::map<std::string, std::string>& param_values, + const std::set<std::string>& associated_features) + : VariationParamsManager() { + SetVariationParamsWithFeatureAssociations(trial_name, param_values, + associated_features); +} + +VariationParamsManager::~VariationParamsManager() { + ClearAllVariationIDs(); + ClearAllVariationParams(); +} + +void VariationParamsManager::SetVariationParams( + const std::string& trial_name, + const std::map<std::string, std::string>& param_values) { + CreateFieldTrialWithParams(trial_name, param_values); +} + +void VariationParamsManager::SetVariationParamsWithFeatureAssociations( + const std::string& trial_name, + const std::map<std::string, std::string>& param_values, + const std::set<std::string>& associated_features) { + base::FieldTrial* field_trial = + CreateFieldTrialWithParams(trial_name, param_values); + + std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList); + for (const std::string& feature_name : associated_features) { + feature_list->RegisterFieldTrialOverride( + feature_name, + base::FeatureList::OverrideState::OVERRIDE_ENABLE_FEATURE, + field_trial); + } + + scoped_feature_list_->InitWithFeatureList(std::move(feature_list)); +} + +void VariationParamsManager::ClearAllVariationIDs() { + variations::testing::ClearAllVariationIDs(); +} + +void VariationParamsManager::ClearAllVariationParams() { + variations::testing::ClearAllVariationParams(); + // When the scoped feature list is destroyed, it puts back the original + // feature list that was there when InitWithFeatureList() was called. + scoped_feature_list_.reset(new base::test::ScopedFeatureList()); +} + +} // namespace testing +} // namespace variations
diff --git a/components/variations/variations_params_manager.h b/components/variations/variations_params_manager.h new file mode 100644 index 0000000..6a65e18 --- /dev/null +++ b/components/variations/variations_params_manager.h
@@ -0,0 +1,83 @@ +// Copyright 2016 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 COMPONENTS_VARIATIONS_VARIATIONS_PARAMS_MANAGER_H_ +#define COMPONENTS_VARIATIONS_VARIATIONS_PARAMS_MANAGER_H_ + +#include <map> +#include <memory> +#include <set> +#include <string> + +#include "base/macros.h" +#include "base/metrics/field_trial.h" + +namespace base { +class FieldTrialList; + +namespace test { +class ScopedFeatureList; +} // namespace test + +} // namespace base + +namespace variations { +namespace testing { + +// Use this class as a member in your test class to set variation params for +// your tests. You can directly set the parameters in the constructor (if they +// are used by other members upon construction). You can change them later +// arbitrarily many times using the SetVariationParams function. Internally, it +// creates a FieldTrialList as a member. It works well for multiple tests of a +// given test class, as it clears the parameters when this class is destructed. +// Note that it clears all parameters (not just those registered here). +class VariationParamsManager { + public: + // Does not associate any parameters. + VariationParamsManager(); + // Calls directly SetVariationParams with the provided arguments. + VariationParamsManager( + const std::string& trial_name, + const std::map<std::string, std::string>& param_values); + // Calls directly SetVariationParamsWithFeatures with the provided arguments. + VariationParamsManager( + const std::string& trial_name, + const std::map<std::string, std::string>& param_values, + const std::set<std::string>& associated_features); + ~VariationParamsManager(); + + // Associates |param_values| with the given |trial_name|. |param_values| maps + // parameter names to their values. The function creates a new trial group, + // used only for testing. Between two calls of this function, + // ClearAllVariationParams() has to be called. + void SetVariationParams( + const std::string& trial_name, + const std::map<std::string, std::string>& param_values); + + // Like SetVariationParams(). |associated_features| lists names of features + // to be associated to the newly created trial group. As a result, all + // parameters from |param_values| can be accessed via any of the feature from + // |associated_features|. + void SetVariationParamsWithFeatureAssociations( + const std::string& trial_name, + const std::map<std::string, std::string>& param_values, + const std::set<std::string>& associated_features); + + // Clears all of the mapped associations. + void ClearAllVariationIDs(); + + // Clears all of the associated params. + void ClearAllVariationParams(); + + private: + std::unique_ptr<base::FieldTrialList> field_trial_list_; + std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_; + + DISALLOW_COPY_AND_ASSIGN(VariationParamsManager); +}; + +} // namespace testing +} // namespace variations + +#endif // COMPONENTS_VARIATIONS_VARIATIONS_PARAMS_MANAGER_H_
diff --git a/content/browser/blob_storage/blob_flattener_unittest.cc b/content/browser/blob_storage/blob_flattener_unittest.cc index b67066a..3e70dffc 100644 --- a/content/browser/blob_storage/blob_flattener_unittest.cc +++ b/content/browser/blob_storage/blob_flattener_unittest.cc
@@ -6,11 +6,9 @@ #include <memory> -#include "base/bind.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" -#include "base/memory/ptr_util.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/test/test_simple_task_runner.h" @@ -19,7 +17,6 @@ #include "storage/browser/blob/blob_data_handle.h" #include "storage/browser/blob/blob_data_item.h" #include "storage/browser/blob/blob_entry.h" -#include "storage/browser/blob/blob_memory_controller.h" #include "storage/browser/blob/blob_storage_registry.h" #include "storage/browser/blob/shareable_blob_data_item.h" #include "storage/common/data_element.h" @@ -28,26 +25,9 @@ namespace storage { namespace { using base::TestSimpleTaskRunner; -using FileCreationInfo = BlobMemoryController::FileCreationInfo; const char kType[] = "type"; const char kDisposition[] = ""; -const size_t kTestBlobStorageIPCThresholdBytes = 20; -const size_t kTestBlobStorageMaxSharedMemoryBytes = 50; - -const size_t kTestBlobStorageMaxBlobMemorySize = 400; -const uint64_t kTestBlobStorageMaxDiskSpace = 4000; -const uint64_t kTestBlobStorageMinFileSizeBytes = 10; -const uint64_t kTestBlobStorageMaxFileSizeBytes = 100; - -void SaveBlobStatusAndFiles(BlobStatus* status_ptr, - std::vector<FileCreationInfo>* files_ptr, - BlobStatus status, - std::vector<FileCreationInfo> files) { - EXPECT_FALSE(BlobStatusIsError(status)); - *status_ptr = status; - std::move(files.begin(), files.end(), std::back_inserter(*files_ptr)); -} } // namespace @@ -59,15 +39,11 @@ : fake_file_path_(base::FilePath(FILE_PATH_LITERAL("kFakePath"))) {} ~BlobFlattenerTest() override {} - void SetUp() override { - ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); - context_ = base::MakeUnique<BlobStorageContext>(); - } + void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); } void TearDown() override { base::RunLoop().RunUntilIdle(); file_runner_->RunPendingTasks(); - base::RunLoop().RunUntilIdle(); ASSERT_TRUE(temp_dir_.Delete()); } @@ -90,21 +66,14 @@ return scoped_refptr<BlobDataItem>(new BlobDataItem(std::move(element))); }; - scoped_refptr<BlobDataItem> CreateFutureFileItem(size_t offset, size_t size) { - std::unique_ptr<DataElement> element(new DataElement()); - element->SetToFilePathRange(BlobDataBuilder::GetFutureFileItemPath(0), - offset, size, base::Time()); - return scoped_refptr<BlobDataItem>(new BlobDataItem(std::move(element))); - }; - std::unique_ptr<BlobDataHandle> SetupBasicBlob(const std::string& id) { BlobDataBuilder builder(id); builder.AppendData("1", 1); builder.set_content_type("text/plain"); - return context_->AddFinishedBlob(builder); + return context_.AddFinishedBlob(builder); } - BlobStorageRegistry* registry() { return context_->mutable_registry(); } + BlobStorageRegistry* registry() { return context_.mutable_registry(); } const ShareableBlobDataItem& GetItemInBlob(const std::string& uuid, size_t index) { @@ -113,23 +82,12 @@ return *entry->items()[index]; } - void SetTestMemoryLimits() { - BlobStorageLimits limits; - limits.max_ipc_memory_size = kTestBlobStorageIPCThresholdBytes; - limits.max_shared_memory_size = kTestBlobStorageMaxSharedMemoryBytes; - limits.max_blob_in_memory_space = kTestBlobStorageMaxBlobMemorySize; - limits.max_blob_disk_space = kTestBlobStorageMaxDiskSpace; - limits.min_page_file_size = kTestBlobStorageMinFileSizeBytes; - limits.max_file_size = kTestBlobStorageMaxFileSizeBytes; - context_->mutable_memory_controller()->set_limits_for_testing(limits); - } - base::FilePath fake_file_path_; base::ScopedTempDir temp_dir_; scoped_refptr<TestSimpleTaskRunner> file_runner_ = new TestSimpleTaskRunner(); base::MessageLoop fake_io_message_loop; - std::unique_ptr<BlobStorageContext> context_; + BlobStorageContext context_; }; TEST_F(BlobFlattenerTest, NoBlobItems) { @@ -145,7 +103,7 @@ EXPECT_EQ(0u, flattener.dependent_blobs.size()); EXPECT_EQ(0u, flattener.copies.size()); EXPECT_EQ(12u, flattener.total_size); - EXPECT_EQ(2u, flattener.transport_quota_needed); + EXPECT_EQ(2u, flattener.memory_quota_needed); ASSERT_EQ(2u, output.items().size()); EXPECT_EQ(*CreateDataItem("hi", 2u), *output.items()[0]->item()); @@ -189,7 +147,6 @@ const std::string kBlobUUID = "kId"; const std::string kDataBlob = "kId2"; const std::string kFileBlob = "kId3"; - const std::string kPendingFileBlob = "kId4"; // We have the following: // * data, @@ -198,35 +155,19 @@ // * full data blob, // * pending data, - context_ = - base::MakeUnique<BlobStorageContext>(temp_dir_.GetPath(), file_runner_); - SetTestMemoryLimits(); - std::unique_ptr<BlobDataHandle> data_blob; { BlobDataBuilder builder(kDataBlob); builder.AppendData("12345", 5); builder.set_content_type("text/plain"); - data_blob = context_->AddFinishedBlob(builder); + data_blob = context_.AddFinishedBlob(builder); } std::unique_ptr<BlobDataHandle> file_blob; { BlobDataBuilder builder(kFileBlob); builder.AppendFile(fake_file_path_, 1u, 10u, base::Time::Max()); - file_blob = context_->AddFinishedBlob(builder); - } - - BlobStatus file_status = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; - std::vector<FileCreationInfo> file_handles; - std::unique_ptr<BlobDataHandle> future_file_blob; - { - BlobDataBuilder builder(kPendingFileBlob); - builder.AppendFutureFile(0u, 2u, 0); - builder.AppendFutureFile(2u, 5u, 0); - future_file_blob = context_->BuildBlob( - builder, - base::Bind(&SaveBlobStatusAndFiles, &file_status, &file_handles)); + file_blob = context_.AddFinishedBlob(builder); } BlobDataBuilder builder(kBlobUUID); @@ -236,47 +177,28 @@ builder.AppendBlob(kDataBlob); builder.AppendBlob(kFileBlob, 1u, 3u); builder.AppendFutureData(12u); - builder.AppendBlob(kPendingFileBlob, 1u, 3u); BlobEntry output(kType, kDisposition); BlobFlattener flattener(builder, &output, registry()); EXPECT_EQ(BlobStatus::PENDING_QUOTA, flattener.status); - EXPECT_EQ(3u, flattener.dependent_blobs.size()); - EXPECT_EQ(32u, flattener.total_size); - EXPECT_EQ(14u, flattener.transport_quota_needed); - EXPECT_EQ(2u, flattener.copy_quota_needed); + EXPECT_EQ(2u, flattener.dependent_blobs.size()); + EXPECT_EQ(29u, flattener.total_size); + EXPECT_EQ(16u, flattener.memory_quota_needed); - ASSERT_EQ(8u, output.items().size()); + ASSERT_EQ(6u, output.items().size()); EXPECT_EQ(*CreateDataItem("hi", 2u), *output.items()[0]->item()); EXPECT_EQ(*CreateDataDescriptionItem(2u), *output.items()[1]->item()); EXPECT_EQ(*CreateFileItem(3u, 5u), *output.items()[2]->item()); EXPECT_EQ(GetItemInBlob(kDataBlob, 0), *output.items()[3]); EXPECT_EQ(*CreateFileItem(2u, 3u), *output.items()[4]->item()); EXPECT_EQ(*CreateDataDescriptionItem(12u), *output.items()[5]->item()); - EXPECT_EQ(*CreateFutureFileItem(1u, 1u), *output.items()[6]->item()); - EXPECT_EQ(*CreateFutureFileItem(2u, 2u), *output.items()[7]->item()); - // We're copying items at index 1, 6, and 7. - ASSERT_EQ(3u, flattener.copies.size()); + // We're copying item at index 1 + ASSERT_EQ(1u, flattener.copies.size()); EXPECT_EQ(*flattener.copies[0].dest_item, *output.items()[1]); EXPECT_EQ(GetItemInBlob(kDataBlob, 0), *flattener.copies[0].source_item); EXPECT_EQ(1u, flattener.copies[0].source_item_offset); - EXPECT_EQ(*flattener.copies[1].dest_item, *output.items()[6]); - EXPECT_EQ(GetItemInBlob(kPendingFileBlob, 0), - *flattener.copies[1].source_item); - EXPECT_EQ(1u, flattener.copies[1].source_item_offset); - EXPECT_EQ(*flattener.copies[2].dest_item, *output.items()[7]); - EXPECT_EQ(GetItemInBlob(kPendingFileBlob, 1), - *flattener.copies[2].source_item); - EXPECT_EQ(0u, flattener.copies[2].source_item_offset); - - // Clean up temp files. - EXPECT_TRUE(file_runner_->HasPendingTask()); - file_runner_->RunPendingTasks(); - base::RunLoop().RunUntilIdle(); - EXPECT_EQ(BlobStatus::PENDING_TRANSPORT, file_status); - EXPECT_FALSE(file_handles.empty()); } } // namespace storage
diff --git a/content/browser/blob_storage/blob_memory_controller_unittest.cc b/content/browser/blob_storage/blob_memory_controller_unittest.cc index ae0e796c..290a40be 100644 --- a/content/browser/blob_storage/blob_memory_controller_unittest.cc +++ b/content/browser/blob_storage/blob_memory_controller_unittest.cc
@@ -74,7 +74,7 @@ controller->set_limits_for_testing(limits); } - void SaveFileCreationInfo(std::vector<FileCreationInfo> info, bool success) { + void SaveFileCreationInfo(bool success, std::vector<FileCreationInfo> info) { file_quota_result_ = success; if (success) { files_created_.swap(info);
diff --git a/content/browser/blob_storage/blob_slice_unittest.cc b/content/browser/blob_storage/blob_slice_unittest.cc index 97d2bd0..e7266fd3 100644 --- a/content/browser/blob_storage/blob_slice_unittest.cc +++ b/content/browser/blob_storage/blob_slice_unittest.cc
@@ -48,16 +48,6 @@ ShareableBlobDataItem::POPULATED_WITHOUT_QUOTA)); }; - scoped_refptr<ShareableBlobDataItem> CreateTempFileItem(size_t offset, - size_t size) { - std::unique_ptr<DataElement> element(new DataElement()); - element->SetToFilePathRange(BlobDataBuilder::GetFutureFileItemPath(0), - offset, size, base::Time()); - return scoped_refptr<ShareableBlobDataItem>( - new ShareableBlobDataItem(new BlobDataItem(std::move(element)), - ShareableBlobDataItem::QUOTA_NEEDED)); - }; - void ExpectFirstSlice(const BlobSlice& slice, scoped_refptr<ShareableBlobDataItem> source_item, size_t first_item_slice_offset, @@ -66,10 +56,8 @@ EXPECT_EQ(first_item_slice_offset, slice.first_item_slice_offset); ASSERT_LE(1u, slice.dest_items.size()); - - scoped_refptr<ShareableBlobDataItem> item = slice.dest_items[0]; - EXPECT_EQ(ShareableBlobDataItem::QUOTA_NEEDED, item->state()); - const DataElement& dest_element = item->item()->data_element(); + const DataElement& dest_element = + slice.dest_items[0]->item()->data_element(); EXPECT_EQ(DataElement::TYPE_BYTES_DESCRIPTION, dest_element.type()); EXPECT_EQ(static_cast<uint64_t>(size), dest_element.length()); @@ -83,9 +71,8 @@ EXPECT_TRUE(slice.last_source_item); ASSERT_LE(2u, slice.dest_items.size()); - scoped_refptr<ShareableBlobDataItem> item = slice.dest_items.back(); - EXPECT_EQ(ShareableBlobDataItem::QUOTA_NEEDED, item->state()); - const DataElement& dest_element = item->item()->data_element(); + const DataElement& dest_element = + slice.dest_items.back()->item()->data_element(); EXPECT_EQ(DataElement::TYPE_BYTES_DESCRIPTION, dest_element.type()); EXPECT_EQ(static_cast<uint64_t>(size), dest_element.length()); @@ -95,6 +82,7 @@ }; TEST_F(BlobSliceTest, FullItem) { + const std::string kBlobUUID = "kId"; const size_t kSize = 5u; BlobEntry data(kType, kDisposition); @@ -112,6 +100,7 @@ } TEST_F(BlobSliceTest, SliceSingleItem) { + const std::string kBlobUUID = "kId"; const size_t kSize = 5u; BlobEntry data(kType, kDisposition); @@ -126,6 +115,7 @@ } TEST_F(BlobSliceTest, SliceSingleLastItem) { + const std::string kBlobUUID = "kId"; const size_t kSize1 = 5u; const size_t kSize2 = 10u; @@ -142,6 +132,7 @@ } TEST_F(BlobSliceTest, SliceAcrossTwoItems) { + const std::string kBlobUUID = "kId"; const size_t kSize1 = 5u; const size_t kSize2 = 10u; @@ -159,6 +150,7 @@ } TEST_F(BlobSliceTest, SliceFileAndLastItem) { + const std::string kBlobUUID = "kId"; const size_t kSize1 = 5u; const size_t kSize2 = 10u; @@ -178,6 +170,7 @@ } TEST_F(BlobSliceTest, SliceAcrossLargeItem) { + const std::string kBlobUUID = "kId"; const size_t kSize1 = 5u; const size_t kSize2 = 10u; const size_t kSize3 = 10u; @@ -199,23 +192,4 @@ EXPECT_EQ(*item2, *slice.dest_items[1]); } -TEST_F(BlobSliceTest, SliceTempFileItem) { - BlobEntry data(kType, kDisposition); - scoped_refptr<ShareableBlobDataItem> item1 = CreateTempFileItem(1u, 10u); - data.AppendSharedBlobItem(item1); - BlobSlice slice(data, 2, 5); - EXPECT_EQ(0u, slice.copying_memory_size.ValueOrDie()); - EXPECT_TRUE(slice.first_source_item); - EXPECT_EQ(2u, slice.first_item_slice_offset); - ASSERT_LE(1u, slice.dest_items.size()); - scoped_refptr<ShareableBlobDataItem> item = slice.dest_items[0]; - EXPECT_EQ(ShareableBlobDataItem::POPULATED_WITHOUT_QUOTA, item->state()); - - const DataElement& dest_element = item->item()->data_element(); - EXPECT_EQ(DataElement::TYPE_FILE, dest_element.type()); - EXPECT_EQ(static_cast<uint64_t>(5), dest_element.length()); - EXPECT_EQ(*item1, *slice.first_source_item); - ASSERT_EQ(1u, slice.dest_items.size()); -} - } // namespace storage
diff --git a/content/browser/blob_storage/blob_storage_browsertest.cc b/content/browser/blob_storage/blob_storage_browsertest.cc deleted file mode 100644 index fdcf7da..0000000 --- a/content/browser/blob_storage/blob_storage_browsertest.cc +++ /dev/null
@@ -1,103 +0,0 @@ -// Copyright 2016 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/run_loop.h" -#include "content/browser/blob_storage/chrome_blob_storage_context.h" -#include "content/public/browser/browser_context.h" -#include "content/public/browser/browser_thread.h" -#include "content/public/browser/web_contents.h" -#include "content/public/test/browser_test_utils.h" -#include "content/public/test/content_browser_test.h" -#include "content/public/test/content_browser_test_utils.h" -#include "content/shell/browser/shell.h" -#include "storage/browser/blob/blob_memory_controller.h" -#include "storage/browser/blob/blob_storage_context.h" -#include "storage/common/blob_storage/blob_storage_constants.h" - -namespace content { -namespace { -const size_t kTestBlobStorageIPCThresholdBytes = 5; -const size_t kTestBlobStorageMaxSharedMemoryBytes = 10; - -const size_t kTestBlobStorageMaxBlobMemorySize = 200; -const uint64_t kTestBlobStorageMaxDiskSpace = 3000; -const uint64_t kTestBlobStorageMinFileSizeBytes = 20; -const uint64_t kTestBlobStorageMaxFileSizeBytes = 50; -} // namespace - -// This browser test is aimed towards exercising the blob storage transportation -// strategies and paging memory to disk. -class BlobStorageBrowserTest : public ContentBrowserTest { - public: - BlobStorageBrowserTest() { - limits_.max_ipc_memory_size = kTestBlobStorageIPCThresholdBytes; - limits_.max_shared_memory_size = kTestBlobStorageMaxSharedMemoryBytes; - limits_.max_blob_in_memory_space = kTestBlobStorageMaxBlobMemorySize; - limits_.max_blob_disk_space = kTestBlobStorageMaxDiskSpace; - limits_.min_page_file_size = kTestBlobStorageMinFileSizeBytes; - limits_.max_file_size = kTestBlobStorageMaxFileSizeBytes; - } - - void SetBlobLimits() { - GetMemoryController()->set_limits_for_testing(limits_); - } - - storage::BlobMemoryController* GetMemoryController() { - content::ChromeBlobStorageContext* blob_context = - ChromeBlobStorageContext::GetFor( - shell()->web_contents()->GetBrowserContext()); - return blob_context->context()->mutable_memory_controller(); - } - - void SimpleTest(const GURL& test_url, bool incognito = false) { - // The test page will perform tests on blob storage, then navigate to either - // a #pass or #fail ref. - Shell* the_browser = incognito ? CreateOffTheRecordBrowser() : shell(); - - VLOG(0) << "Navigating to URL and blocking. " << test_url.spec(); - NavigateToURLBlockUntilNavigationsComplete(the_browser, test_url, 2); - VLOG(0) << "Navigation done."; - std::string result = - the_browser->web_contents()->GetLastCommittedURL().ref(); - if (result != "pass") { - std::string js_result; - ASSERT_TRUE(ExecuteScriptAndExtractString( - the_browser, "window.domAutomationController.send(getLog())", - &js_result)); - FAIL() << "Failed: " << js_result; - } - } - - protected: - storage::BlobStorageLimits limits_; - - private: - DISALLOW_COPY_AND_ASSIGN(BlobStorageBrowserTest); -}; - -IN_PROC_BROWSER_TEST_F(BlobStorageBrowserTest, BlobCombinations) { - SetBlobLimits(); - SimpleTest(GetTestUrl("blob_storage", "blob_creation_and_slicing.html")); - storage::BlobMemoryController* memory_controller = GetMemoryController(); - // Our exact usages depend on IPC message ordering & garbage collection. - // Since this is basically random, we just check bounds. - EXPECT_LT(0u, memory_controller->memory_usage()); - EXPECT_LT(0ul, memory_controller->disk_usage()); - EXPECT_GT(memory_controller->disk_usage(), - static_cast<uint64_t>(memory_controller->memory_usage())); - EXPECT_GT(limits_.max_blob_in_memory_space, - memory_controller->memory_usage()); - EXPECT_GT(limits_.max_blob_disk_space, memory_controller->disk_usage()); - shell()->Close(); - - // Make sure we run all file / io tasks. - base::RunLoop().RunUntilIdle(); - BrowserThread::GetBlockingPool()->FlushForTesting(); - base::RunLoop().RunUntilIdle(); - - EXPECT_EQ(0u, memory_controller->memory_usage()); - EXPECT_EQ(0ul, memory_controller->disk_usage()); -} - -} // namespace content
diff --git a/content/browser/blob_storage/blob_storage_context_unittest.cc b/content/browser/blob_storage/blob_storage_context_unittest.cc index 5ac28ca9..166509e7 100644 --- a/content/browser/blob_storage/blob_storage_context_unittest.cc +++ b/content/browser/blob_storage/blob_storage_context_unittest.cc
@@ -99,18 +99,12 @@ std::vector<FileCreationInfo>* files_ptr, BlobStatus status, std::vector<FileCreationInfo> files) { - EXPECT_FALSE(BlobStatusIsError(status)); *status_ptr = status; for (FileCreationInfo& info : files) { files_ptr->push_back(std::move(info)); } } -void IncrementNumber(size_t* number, BlobStatus status) { - EXPECT_EQ(BlobStatus::DONE, status); - *number = *number + 1; -} - } // namespace class BlobStorageContextTest : public testing::Test { @@ -118,16 +112,7 @@ BlobStorageContextTest() {} ~BlobStorageContextTest() override {} - void SetUp() override { - ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); - context_ = base::MakeUnique<BlobStorageContext>(); - } - - void TearDown() override { - base::RunLoop().RunUntilIdle(); - file_runner_->RunPendingTasks(); - ASSERT_TRUE(temp_dir_.Delete()); - } + void SetUp() override { context_ = base::MakeUnique<BlobStorageContext>(); } std::unique_ptr<BlobDataHandle> SetupBasicBlob(const std::string& id) { BlobDataBuilder builder(id); @@ -156,8 +141,6 @@ } std::vector<FileCreationInfo> files_; - base::ScopedTempDir temp_dir_; - scoped_refptr<TestSimpleTaskRunner> file_runner_ = new TestSimpleTaskRunner(); base::MessageLoop fake_io_message_loop_; std::unique_ptr<BlobStorageContext> context_; @@ -528,60 +511,6 @@ base::RunLoop().RunUntilIdle(); } -TEST_F(BlobStorageContextTest, BuildFutureFileOnlyBlob) { - const std::string kId1("id1"); - context_ = - base::MakeUnique<BlobStorageContext>(temp_dir_.GetPath(), file_runner_); - SetTestMemoryLimits(); - - BlobDataBuilder builder(kId1); - builder.set_content_type("text/plain"); - builder.AppendFutureFile(0, 10, 0); - - BlobStatus status = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; - std::unique_ptr<BlobDataHandle> handle = context_->BuildBlob( - builder, base::Bind(&SaveBlobStatusAndFiles, &status, &files_)); - - size_t blobs_finished = 0; - EXPECT_EQ(BlobStatus::PENDING_QUOTA, handle->GetBlobStatus()); - EXPECT_EQ(BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS, status); - handle->RunOnConstructionComplete( - base::Bind(&IncrementNumber, &blobs_finished)); - EXPECT_EQ(0u, blobs_finished); - - EXPECT_TRUE(file_runner_->HasPendingTask()); - file_runner_->RunPendingTasks(); - EXPECT_EQ(0u, blobs_finished); - EXPECT_EQ(BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS, status); - EXPECT_EQ(BlobStatus::PENDING_QUOTA, handle->GetBlobStatus()); - base::RunLoop().RunUntilIdle(); - - EXPECT_EQ(BlobStatus::PENDING_TRANSPORT, status); - EXPECT_EQ(BlobStatus::PENDING_TRANSPORT, handle->GetBlobStatus()); - EXPECT_EQ(0u, blobs_finished); - - ASSERT_EQ(1u, files_.size()); - - builder.PopulateFutureFile(0, files_[0].file_reference, base::Time::Max()); - context_->NotifyTransportComplete(kId1); - - EXPECT_EQ(BlobStatus::DONE, handle->GetBlobStatus()); - EXPECT_EQ(0u, blobs_finished); - base::RunLoop().RunUntilIdle(); - EXPECT_EQ(1u, blobs_finished); - - builder.Clear(); - handle.reset(); - files_.clear(); - base::RunLoop().RunUntilIdle(); - // We should have file cleanup tasks. - EXPECT_TRUE(file_runner_->HasPendingTask()); - file_runner_->RunPendingTasks(); - base::RunLoop().RunUntilIdle(); - EXPECT_EQ(0lu, context_->memory_controller().memory_usage()); - EXPECT_EQ(0lu, context_->memory_controller().disk_usage()); -} - TEST_F(BlobStorageContextTest, CompoundBlobs) { const std::string kId1("id1"); const std::string kId2("id2"); @@ -726,171 +655,6 @@ EXPECT_FALSE(context_->registry().HasEntry(kReferencingId)); } -namespace { -constexpr size_t kTotalRawBlobs = 200; -constexpr size_t kTotalSlicedBlobs = 100; -constexpr char kTestDiskCacheData[] = "Test Blob Data"; +// TODO(michaeln): tests for the depcrecated url stuff -// Appends data and data types that depend on the index. This is designed to -// exercise all types of combinations of data, future data, files, future files, -// and disk cache entries. -size_t AppendDataInBuilder(BlobDataBuilder* builder, - size_t index, - disk_cache::Entry* cache_entry) { - size_t size = 0; - // We can't have both future data and future files, so split those up. - if (index % 2 != 0) { - builder->AppendFutureData(5u); - size += 5u; - if (index % 3 == 1) { - builder->AppendData("abcdefghij", 4u); - size += 4u; - } - if (index % 3 == 0) { - builder->AppendFutureData(1u); - size += 1u; - } - } else if (index % 3 == 0) { - builder->AppendFutureFile(0lu, 3lu, 0); - size += 3u; - } - if (index % 5 != 0) { - builder->AppendFile( - base::FilePath::FromUTF8Unsafe(base::SizeTToString(index)), 0ul, 20ul, - base::Time::Max()); - size += 20u; - } - if (index % 3 != 0) { - scoped_refptr<BlobDataBuilder::DataHandle> disk_cache_data_handle = - new EmptyDataHandle(); - builder->AppendDiskCacheEntry(disk_cache_data_handle, cache_entry, - kTestDiskCacheStreamIndex); - size += strlen(kTestDiskCacheData); - } - return size; -} - -bool DoesBuilderHaveFutureData(size_t index) { - return index < kTotalRawBlobs && (index % 2 != 0 || index % 3 == 0); -} - -void PopulateDataInBuilder(BlobDataBuilder* builder, - size_t index, - base::TaskRunner* file_runner) { - if (index % 2 != 0) { - builder->PopulateFutureData(0, "abcde", 0, 5); - if (index % 3 == 0) { - builder->PopulateFutureData(1, "z", 0, 1); - } - } else if (index % 3 == 0) { - scoped_refptr<ShareableFileReference> file_ref = - ShareableFileReference::GetOrCreate( - base::FilePath::FromUTF8Unsafe( - base::SizeTToString(index + kTotalRawBlobs)), - ShareableFileReference::DONT_DELETE_ON_FINAL_RELEASE, file_runner); - builder->PopulateFutureFile(0, file_ref, base::Time::Max()); - } -} -} // namespace - -TEST_F(BlobStorageContextTest, BuildBlobCombinations) { - const std::string kId("id"); - - context_ = - base::MakeUnique<BlobStorageContext>(temp_dir_.GetPath(), file_runner_); - - SetTestMemoryLimits(); - std::unique_ptr<disk_cache::Backend> cache = CreateInMemoryDiskCache(); - ASSERT_TRUE(cache); - disk_cache::ScopedEntryPtr entry = - CreateDiskCacheEntry(cache.get(), "test entry", kTestDiskCacheData); - - // This tests mixed blob content with both synchronous and asynchronous - // construction. Blobs should also be paged to disk during execution. - std::vector<std::unique_ptr<BlobDataBuilder>> builders; - std::vector<size_t> sizes; - for (size_t i = 0; i < kTotalRawBlobs; i++) { - builders.emplace_back(new BlobDataBuilder(base::SizeTToString(i))); - auto& builder = *builders.back(); - size_t size = AppendDataInBuilder(&builder, i, entry.get()); - EXPECT_NE(0u, size); - sizes.push_back(size); - } - - for (size_t i = 0; i < kTotalSlicedBlobs; i++) { - builders.emplace_back( - new BlobDataBuilder(base::SizeTToString(i + kTotalRawBlobs))); - size_t source_size = sizes[i]; - size_t offset = source_size == 1 ? 0 : i % (source_size - 1); - size_t size = (i % (source_size - offset)) + 1; - builders.back()->AppendBlob(base::SizeTToString(i), offset, size); - } - - size_t total_finished_blobs = 0; - std::vector<std::unique_ptr<BlobDataHandle>> handles; - std::vector<BlobStatus> statuses; - std::vector<bool> populated; - statuses.resize(kTotalRawBlobs, - BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS); - populated.resize(kTotalRawBlobs, false); - for (size_t i = 0; i < builders.size(); i++) { - BlobDataBuilder& builder = *builders[i]; - builder.set_content_type("text/plain"); - bool has_pending_memory = DoesBuilderHaveFutureData(i); - std::unique_ptr<BlobDataHandle> handle = context_->BuildBlob( - builder, - has_pending_memory - ? base::Bind(&SaveBlobStatusAndFiles, &statuses[0] + i, &files_) - : BlobStorageContext::TransportAllowedCallback()); - handle->RunOnConstructionComplete( - base::Bind(&IncrementNumber, &total_finished_blobs)); - handles.push_back(std::move(handle)); - } - base::RunLoop().RunUntilIdle(); - - // We should be needing to send a page or two to disk. - EXPECT_TRUE(file_runner_->HasPendingTask()); - do { - file_runner_->RunPendingTasks(); - base::RunLoop().RunUntilIdle(); - // Continue populating data for items that can fit. - for (size_t i = 0; i < kTotalRawBlobs; i++) { - BlobDataBuilder* builder = builders[i].get(); - if (DoesBuilderHaveFutureData(i) && !populated[i] && - statuses[i] == BlobStatus::PENDING_TRANSPORT) { - PopulateDataInBuilder(builder, i, file_runner_.get()); - context_->NotifyTransportComplete(base::SizeTToString(i)); - populated[i] = true; - } - } - base::RunLoop().RunUntilIdle(); - } while (file_runner_->HasPendingTask()); - - // Check all builders with future items were signalled and populated. - for (size_t i = 0; i < populated.size(); i++) { - if (DoesBuilderHaveFutureData(i)) { - EXPECT_EQ(BlobStatus::PENDING_TRANSPORT, statuses[i]) << i; - EXPECT_TRUE(populated[i]) << i; - } - } - base::RunLoop().RunUntilIdle(); - - // We should be completely built now. - EXPECT_EQ(kTotalRawBlobs + kTotalSlicedBlobs, total_finished_blobs); - for (std::unique_ptr<BlobDataHandle>& handle : handles) { - EXPECT_EQ(BlobStatus::DONE, handle->GetBlobStatus()); - } - handles.clear(); - base::RunLoop().RunUntilIdle(); - files_.clear(); - // We should have file cleanup tasks. - EXPECT_TRUE(file_runner_->HasPendingTask()); - file_runner_->RunPendingTasks(); - base::RunLoop().RunUntilIdle(); - EXPECT_EQ(0lu, context_->memory_controller().memory_usage()); - EXPECT_EQ(0lu, context_->memory_controller().disk_usage()); -} - -// TODO(michaeln): tests for the deprecated url stuff - -} // namespace storage +} // namespace content
diff --git a/content/browser/blob_storage/chrome_blob_storage_context.cc b/content/browser/blob_storage/chrome_blob_storage_context.cc index bd02e514..533d3f1 100644 --- a/content/browser/blob_storage/chrome_blob_storage_context.cc +++ b/content/browser/blob_storage/chrome_blob_storage_context.cc
@@ -7,14 +7,7 @@ #include <utility> #include "base/bind.h" -#include "base/files/file.h" -#include "base/files/file_enumerator.h" -#include "base/files/file_util.h" #include "base/guid.h" -#include "base/metrics/histogram_macros.h" -#include "base/single_thread_task_runner.h" -#include "base/task_runner.h" -#include "base/threading/sequenced_worker_pool.h" #include "content/public/browser/blob_handle.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_thread.h" @@ -22,35 +15,14 @@ #include "storage/browser/blob/blob_data_handle.h" #include "storage/browser/blob/blob_storage_context.h" -using base::FilePath; using base::UserDataAdapter; using storage::BlobStorageContext; namespace content { namespace { -const FilePath::CharType kBlobStorageContextKeyName[] = - FILE_PATH_LITERAL("content_blob_storage_context"); -const FilePath::CharType kBlobStorageParentDirectory[] = - FILE_PATH_LITERAL("blob_storage"); -// Removes all folders in the parent directory except for the -// |current_run_dir| folder. If this path is empty, then we delete all folders. -void RemoveOldBlobStorageDirectories(FilePath blob_storage_parent, - const FilePath& current_run_dir) { - if (!base::DirectoryExists(blob_storage_parent)) { - return; - } - base::FileEnumerator enumerator(blob_storage_parent, false /* recursive */, - base::FileEnumerator::DIRECTORIES); - bool success = true; - for (FilePath name = enumerator.Next(); !name.empty(); - name = enumerator.Next()) { - if (current_run_dir.empty() || name != current_run_dir) - success &= base::DeleteFile(name, true /* recursive */); - } - LOCAL_HISTOGRAM_BOOLEAN("Storage.Blob.CleanupSuccess", success); -} +const char kBlobStorageContextKeyName[] = "content_blob_storage_context"; class BlobHandleImpl : public BlobHandle { public: @@ -77,39 +49,11 @@ context->SetUserData( kBlobStorageContextKeyName, new UserDataAdapter<ChromeBlobStorageContext>(blob.get())); - // Check first to avoid memory leak in unittests. - bool io_thread_valid = BrowserThread::IsMessageLoopValid(BrowserThread::IO); - - // Resolve our storage directories. - FilePath blob_storage_parent = - context->GetPath().Append(kBlobStorageParentDirectory); - FilePath blob_storage_dir = blob_storage_parent.Append( - FilePath::FromUTF8Unsafe(base::GenerateGUID())); - - // Only populate the task runner if we're not off the record. This enables - // paging/saving blob data to disk. - scoped_refptr<base::TaskRunner> file_task_runner; - - // If we're not incognito mode, schedule all of our file tasks to enable - // disk on the storage context. - if (!context->IsOffTheRecord() && io_thread_valid) { - file_task_runner = - BrowserThread::GetBlockingPool()->GetTaskRunnerWithShutdownBehavior( - base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); - // Removes our old blob directories if they exist. - BrowserThread::PostAfterStartupTask( - FROM_HERE, file_task_runner, - base::Bind(&RemoveOldBlobStorageDirectories, - base::Passed(&blob_storage_parent), blob_storage_dir)); - } - - if (io_thread_valid) { + if (BrowserThread::IsMessageLoopValid(BrowserThread::IO)) { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, - base::Bind(&ChromeBlobStorageContext::InitializeOnIOThread, blob, - base::Passed(&blob_storage_dir), - base::Passed(&file_task_runner))); + base::Bind(&ChromeBlobStorageContext::InitializeOnIOThread, blob)); } } @@ -117,12 +61,9 @@ context, kBlobStorageContextKeyName); } -void ChromeBlobStorageContext::InitializeOnIOThread( - FilePath blob_storage_dir, - scoped_refptr<base::TaskRunner> file_task_runner) { +void ChromeBlobStorageContext::InitializeOnIOThread() { DCHECK_CURRENTLY_ON(BrowserThread::IO); - context_.reset(new BlobStorageContext(std::move(blob_storage_dir), - std::move(file_task_runner))); + context_.reset(new BlobStorageContext()); } std::unique_ptr<BlobHandle> ChromeBlobStorageContext::CreateMemoryBackedBlob( @@ -145,7 +86,7 @@ } std::unique_ptr<BlobHandle> ChromeBlobStorageContext::CreateFileBackedBlob( - const FilePath& path, + const base::FilePath& path, int64_t offset, int64_t size, const base::Time& expected_modification_time) {
diff --git a/content/browser/blob_storage/chrome_blob_storage_context.h b/content/browser/blob_storage/chrome_blob_storage_context.h index 4c7cfac..bd02cb1 100644 --- a/content/browser/blob_storage/chrome_blob_storage_context.h +++ b/content/browser/blob_storage/chrome_blob_storage_context.h
@@ -10,14 +10,12 @@ #include <memory> -#include "base/files/file_path.h" #include "base/memory/ref_counted.h" #include "base/sequenced_task_runner_helpers.h" #include "content/common/content_export.h" namespace base { class FilePath; -class TaskRunner; class Time; } @@ -46,8 +44,7 @@ static ChromeBlobStorageContext* GetFor( BrowserContext* browser_context); - void InitializeOnIOThread(base::FilePath blob_storage_dir, - scoped_refptr<base::TaskRunner> file_task_runner); + void InitializeOnIOThread(); storage::BlobStorageContext* context() const { return context_.get(); }
diff --git a/content/browser/devtools/protocol/color_picker.cc b/content/browser/devtools/protocol/color_picker.cc index a5b1777..40e119b 100644 --- a/content/browser/devtools/protocol/color_picker.cc +++ b/content/browser/devtools/protocol/color_picker.cc
@@ -210,7 +210,7 @@ SkPath clip_path; clip_path.addOval(SkRect::MakeXYWH(padding, padding, kDiameter, kDiameter)); clip_path.close(); - canvas.clipPath(clip_path, SkRegion::kIntersect_Op, true); + canvas.clipPath(clip_path, kIntersect_SkClipOp, true); // Project pixels. int pixel_count = kDiameter / kPixelSize;
diff --git a/content/browser/frame_host/frame_tree_node.cc b/content/browser/frame_host/frame_tree_node.cc index d4d0059..a67de69 100644 --- a/content/browser/frame_host/frame_tree_node.cc +++ b/content/browser/frame_host/frame_tree_node.cc
@@ -255,8 +255,9 @@ replication_state_.unique_name = unique_name; } -void FrameTreeNode::SetFeaturePolicyHeader(const std::string& header) { - replication_state_.feature_policy_header = header; +void FrameTreeNode::SetFeaturePolicyHeader( + const ParsedFeaturePolicy& parsed_header) { + replication_state_.feature_policy_header = parsed_header; } void FrameTreeNode::ResetFeaturePolicy() {
diff --git a/content/browser/frame_host/frame_tree_node.h b/content/browser/frame_host/frame_tree_node.h index 77a0df9..6ee699e 100644 --- a/content/browser/frame_host/frame_tree_node.h +++ b/content/browser/frame_host/frame_tree_node.h
@@ -151,7 +151,7 @@ // Set the frame's feature policy from an HTTP header, clearing any existing // policy. - void SetFeaturePolicyHeader(const std::string& header); + void SetFeaturePolicyHeader(const ParsedFeaturePolicy& parsed_header); // Clear any feature policy associated with the frame. void ResetFeaturePolicy();
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc index 75654d2..f71cdd0 100644 --- a/content/browser/frame_host/render_frame_host_impl.cc +++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -1735,8 +1735,8 @@ } void RenderFrameHostImpl::OnDidSetFeaturePolicyHeader( - const std::string& header) { - frame_tree_node()->SetFeaturePolicyHeader(header); + const ParsedFeaturePolicy& parsed_header) { + frame_tree_node()->SetFeaturePolicyHeader(parsed_header); } void RenderFrameHostImpl::OnDidAddContentSecurityPolicy(
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h index bf18923..0d56287 100644 --- a/content/browser/frame_host/render_frame_host_impl.h +++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -663,7 +663,7 @@ void OnDidAccessInitialDocument(); void OnDidChangeOpener(int32_t opener_routing_id); void OnDidChangeName(const std::string& name, const std::string& unique_name); - void OnDidSetFeaturePolicyHeader(const std::string& header); + void OnDidSetFeaturePolicyHeader(const ParsedFeaturePolicy& parsed_header); void OnDidAddContentSecurityPolicy(const ContentSecurityPolicyHeader& header); void OnEnforceInsecureRequestPolicy(blink::WebInsecureRequestPolicy policy); void OnUpdateToUniqueOrigin(bool is_potentially_trustworthy_unique_origin);
diff --git a/content/browser/media/capture/desktop_capture_device_aura.cc b/content/browser/media/capture/desktop_capture_device_aura.cc index 7dafda9..acd236d 100644 --- a/content/browser/media/capture/desktop_capture_device_aura.cc +++ b/content/browser/media/capture/desktop_capture_device_aura.cc
@@ -74,4 +74,9 @@ core_->StopAndDeAllocate(); } +void DesktopCaptureDeviceAura::OnUtilizationReport(int frame_feedback_id, + double utilization) { + core_->OnConsumerReportingUtilization(frame_feedback_id, utilization); +} + } // namespace content
diff --git a/content/browser/media/capture/desktop_capture_device_aura.h b/content/browser/media/capture/desktop_capture_device_aura.h index a3a9374..6eee43a 100644 --- a/content/browser/media/capture/desktop_capture_device_aura.h +++ b/content/browser/media/capture/desktop_capture_device_aura.h
@@ -32,6 +32,7 @@ std::unique_ptr<Client> client) override; void RequestRefreshFrame() override; void StopAndDeAllocate() override; + void OnUtilizationReport(int frame_feedback_id, double utilization) override; private: explicit DesktopCaptureDeviceAura(const DesktopMediaID& source);
diff --git a/content/browser/media/capture/desktop_capture_device_aura_unittest.cc b/content/browser/media/capture/desktop_capture_device_aura_unittest.cc index a5bba004..414a5fc 100644 --- a/content/browser/media/capture/desktop_capture_device_aura_unittest.cc +++ b/content/browser/media/capture/desktop_capture_device_aura_unittest.cc
@@ -41,13 +41,14 @@ class MockDeviceClient : public media::VideoCaptureDevice::Client { public: - MOCK_METHOD6(OnIncomingCapturedData, + MOCK_METHOD7(OnIncomingCapturedData, void(const uint8_t* data, int length, const media::VideoCaptureFormat& frame_format, int rotation, base::TimeTicks reference_time, - base::TimeDelta tiemstamp)); + base::TimeDelta tiemstamp, + int frame_feedback_id)); MOCK_METHOD0(DoReserveOutputBuffer, void(void)); MOCK_METHOD0(DoOnIncomingCapturedBuffer, void(void)); MOCK_METHOD0(DoOnIncomingCapturedVideoFrame, void(void)); @@ -57,10 +58,10 @@ const std::string& reason)); // Trampoline methods to workaround GMOCK problems with std::unique_ptr<>. - std::unique_ptr<Buffer> ReserveOutputBuffer( - const gfx::Size& dimensions, - media::VideoPixelFormat format, - media::VideoPixelStorage storage) override { + std::unique_ptr<Buffer> ReserveOutputBuffer(const gfx::Size& dimensions, + media::VideoPixelFormat format, + media::VideoPixelStorage storage, + int frame_feedback_id) override { EXPECT_EQ(media::PIXEL_FORMAT_I420, format); EXPECT_EQ(media::PIXEL_STORAGE_CPU, storage); DoReserveOutputBuffer(); @@ -80,7 +81,8 @@ std::unique_ptr<Buffer> ResurrectLastOutputBuffer( const gfx::Size& dimensions, media::VideoPixelFormat format, - media::VideoPixelStorage storage) override { + media::VideoPixelStorage storage, + int frame_feedback_id) override { EXPECT_EQ(media::PIXEL_FORMAT_I420, format); EXPECT_EQ(media::PIXEL_STORAGE_CPU, storage); DoResurrectLastOutputBuffer();
diff --git a/content/browser/media/capture/desktop_capture_device_unittest.cc b/content/browser/media/capture/desktop_capture_device_unittest.cc index da791c2e..4fe54c08 100644 --- a/content/browser/media/capture/desktop_capture_device_unittest.cc +++ b/content/browser/media/capture/desktop_capture_device_unittest.cc
@@ -60,13 +60,14 @@ class MockDeviceClient : public media::VideoCaptureDevice::Client { public: - MOCK_METHOD6(OnIncomingCapturedData, + MOCK_METHOD7(OnIncomingCapturedData, void(const uint8_t* data, int length, const media::VideoCaptureFormat& frame_format, int rotation, base::TimeTicks reference_time, - base::TimeDelta timestamp)); + base::TimeDelta timestamp, + int frame_feedback_id)); MOCK_METHOD0(DoReserveOutputBuffer, void(void)); MOCK_METHOD0(DoOnIncomingCapturedBuffer, void(void)); MOCK_METHOD0(DoOnIncomingCapturedVideoFrame, void(void)); @@ -76,17 +77,17 @@ const std::string& reason)); // Trampoline methods to workaround GMOCK problems with std::unique_ptr<>. - std::unique_ptr<Buffer> ReserveOutputBuffer( - const gfx::Size& dimensions, - media::VideoPixelFormat format, - media::VideoPixelStorage storage) override { + std::unique_ptr<Buffer> ReserveOutputBuffer(const gfx::Size& dimensions, + media::VideoPixelFormat format, + media::VideoPixelStorage storage, + int frame_feedback_id) override { EXPECT_TRUE(format == media::PIXEL_FORMAT_I420 && storage == media::PIXEL_STORAGE_CPU); DoReserveOutputBuffer(); return std::unique_ptr<Buffer>(); } void OnIncomingCapturedBuffer(std::unique_ptr<Buffer> buffer, - const media::VideoCaptureFormat& frame_format, + const media::VideoCaptureFormat& format, base::TimeTicks reference_time, base::TimeDelta timestamp) override { DoOnIncomingCapturedBuffer(); @@ -99,7 +100,8 @@ std::unique_ptr<Buffer> ResurrectLastOutputBuffer( const gfx::Size& dimensions, media::VideoPixelFormat format, - media::VideoPixelStorage storage) override { + media::VideoPixelStorage storage, + int frame_feedback_id) override { EXPECT_TRUE(format == media::PIXEL_FORMAT_I420 && storage == media::PIXEL_STORAGE_CPU); DoResurrectLastOutputBuffer(); @@ -264,7 +266,8 @@ const media::VideoCaptureFormat&, int, base::TimeTicks, - base::TimeDelta) { + base::TimeDelta, + int) { ASSERT_TRUE(output_frame_); ASSERT_EQ(output_frame_->stride() * output_frame_->size().height(), size); memcpy(output_frame_->data(), frame, size); @@ -297,7 +300,7 @@ std::unique_ptr<MockDeviceClient> client(new MockDeviceClient()); EXPECT_CALL(*client, OnError(_, _)).Times(0); - EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _, _)) + EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _, _, _)) .WillRepeatedly( DoAll(SaveArg<1>(&frame_size), SaveArg<2>(&format), InvokeWithoutArgs(&done_event, &base::WaitableEvent::Signal))); @@ -334,7 +337,7 @@ std::unique_ptr<MockDeviceClient> client(new MockDeviceClient()); EXPECT_CALL(*client, OnError(_, _)).Times(0); - EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _, _)) + EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _, _, _)) .WillRepeatedly( DoAll(WithArg<2>(Invoke(&format_checker, &FormatChecker::ExpectAcceptableSize)), @@ -378,7 +381,7 @@ std::unique_ptr<MockDeviceClient> client(new MockDeviceClient()); EXPECT_CALL(*client, OnError(_,_)).Times(0); - EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _, _)) + EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _, _, _)) .WillRepeatedly( DoAll(WithArg<2>(Invoke(&format_checker, &FormatChecker::ExpectAcceptableSize)), @@ -426,7 +429,7 @@ std::unique_ptr<MockDeviceClient> client(new MockDeviceClient()); EXPECT_CALL(*client, OnError(_,_)).Times(0); - EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _, _)) + EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _, _, _)) .WillRepeatedly( DoAll(WithArg<2>(Invoke(&format_checker, &FormatChecker::ExpectAcceptableSize)), @@ -476,7 +479,7 @@ std::unique_ptr<MockDeviceClient> client(new MockDeviceClient()); EXPECT_CALL(*client, OnError(_,_)).Times(0); - EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _, _)) + EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _, _, _)) .WillRepeatedly( DoAll(Invoke(this, &DesktopCaptureDeviceTest::CopyFrame), SaveArg<1>(&frame_size), @@ -523,7 +526,7 @@ std::unique_ptr<MockDeviceClient> client(new MockDeviceClient()); EXPECT_CALL(*client, OnError(_,_)).Times(0); - EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _, _)) + EXPECT_CALL(*client, OnIncomingCapturedData(_, _, _, _, _, _, _)) .WillRepeatedly( DoAll(Invoke(this, &DesktopCaptureDeviceTest::CopyFrame), SaveArg<1>(&frame_size),
diff --git a/content/browser/media/capture/screen_capture_device_android.cc b/content/browser/media/capture/screen_capture_device_android.cc index d03e31c..acdd5eb 100644 --- a/content/browser/media/capture/screen_capture_device_android.cc +++ b/content/browser/media/capture/screen_capture_device_android.cc
@@ -31,4 +31,10 @@ void ScreenCaptureDeviceAndroid::RequestRefreshFrame() { core_.RequestRefreshFrame(); } + +void ScreenCaptureDeviceAndroid::OnUtilizationReport(int frame_feedback_id, + double utilization) { + core_.OnConsumerReportingUtilization(frame_feedback_id, utilization); +} + } // namespace content
diff --git a/content/browser/media/capture/screen_capture_device_android.h b/content/browser/media/capture/screen_capture_device_android.h index 09c8998..7d2d518 100644 --- a/content/browser/media/capture/screen_capture_device_android.h +++ b/content/browser/media/capture/screen_capture_device_android.h
@@ -27,6 +27,7 @@ std::unique_ptr<Client> client) override; void StopAndDeAllocate() override; void RequestRefreshFrame() override; + void OnUtilizationReport(int frame_feedback_id, double utilization) override; private: media::ScreenCaptureDeviceCore core_;
diff --git a/content/browser/media/capture/screen_capture_device_android_unittest.cc b/content/browser/media/capture/screen_capture_device_android_unittest.cc index f903f19f..1eaa60c 100644 --- a/content/browser/media/capture/screen_capture_device_android_unittest.cc +++ b/content/browser/media/capture/screen_capture_device_android_unittest.cc
@@ -16,13 +16,14 @@ class MockDeviceClient : public media::VideoCaptureDevice::Client { public: - MOCK_METHOD6(OnIncomingCapturedData, + MOCK_METHOD7(OnIncomingCapturedData, void(const uint8_t* data, int length, const media::VideoCaptureFormat& frame_format, int rotation, base::TimeTicks reference_time, - base::TimeDelta tiemstamp)); + base::TimeDelta tiemstamp, + int frame_feedback_id)); MOCK_METHOD0(DoReserveOutputBuffer, void(void)); MOCK_METHOD0(DoOnIncomingCapturedBuffer, void(void)); MOCK_METHOD0(DoOnIncomingCapturedVideoFrame, void(void)); @@ -33,10 +34,10 @@ MOCK_CONST_METHOD0(GetBufferPoolUtilization, double(void)); // Trampoline methods to workaround GMOCK problems with std::unique_ptr<>. - std::unique_ptr<Buffer> ReserveOutputBuffer( - const gfx::Size& dimensions, - media::VideoPixelFormat format, - media::VideoPixelStorage storage) override { + std::unique_ptr<Buffer> ReserveOutputBuffer(const gfx::Size& dimensions, + media::VideoPixelFormat format, + media::VideoPixelStorage storage, + int frame_feedback_id) override { EXPECT_EQ(media::PIXEL_FORMAT_I420, format); EXPECT_EQ(media::PIXEL_STORAGE_CPU, storage); DoReserveOutputBuffer(); @@ -56,7 +57,8 @@ std::unique_ptr<Buffer> ResurrectLastOutputBuffer( const gfx::Size& dimensions, media::VideoPixelFormat format, - media::VideoPixelStorage storage) override { + media::VideoPixelStorage storage, + int frame_feedback_id) override { EXPECT_EQ(media::PIXEL_FORMAT_I420, format); EXPECT_EQ(media::PIXEL_STORAGE_CPU, storage); DoResurrectLastOutputBuffer();
diff --git a/content/browser/media/capture/web_contents_video_capture_device.cc b/content/browser/media/capture/web_contents_video_capture_device.cc index 5212e7c..d258238e 100644 --- a/content/browser/media/capture/web_contents_video_capture_device.cc +++ b/content/browser/media/capture/web_contents_video_capture_device.cc
@@ -957,4 +957,9 @@ core_->StopAndDeAllocate(); } +void WebContentsVideoCaptureDevice::OnUtilizationReport(int frame_feedback_id, + double utilization) { + core_->OnConsumerReportingUtilization(frame_feedback_id, utilization); +} + } // namespace content
diff --git a/content/browser/media/capture/web_contents_video_capture_device.h b/content/browser/media/capture/web_contents_video_capture_device.h index 6063f7e..ab81c35 100644 --- a/content/browser/media/capture/web_contents_video_capture_device.h +++ b/content/browser/media/capture/web_contents_video_capture_device.h
@@ -42,6 +42,7 @@ void MaybeSuspend() override; void Resume() override; void StopAndDeAllocate() override; + void OnUtilizationReport(int frame_feedback_id, double utilization) override; private: WebContentsVideoCaptureDevice(
diff --git a/content/browser/media/capture/web_contents_video_capture_device_unittest.cc b/content/browser/media/capture/web_contents_video_capture_device_unittest.cc index cc6bc3a3..3843028 100644 --- a/content/browser/media/capture/web_contents_video_capture_device_unittest.cc +++ b/content/browser/media/capture/web_contents_video_capture_device_unittest.cc
@@ -338,36 +338,39 @@ } ~StubClient() override {} - MOCK_METHOD6(OnIncomingCapturedData, + MOCK_METHOD7(OnIncomingCapturedData, void(const uint8_t* data, int length, const media::VideoCaptureFormat& frame_format, int rotation, base::TimeTicks reference_time, - base::TimeDelta timestamp)); + base::TimeDelta timestamp, + int frame_feedback_id)); MOCK_METHOD0(DoOnIncomingCapturedBuffer, void(void)); std::unique_ptr<media::VideoCaptureDevice::Client::Buffer> ReserveOutputBuffer(const gfx::Size& dimensions, media::VideoPixelFormat format, - media::VideoPixelStorage storage) override { + media::VideoPixelStorage storage, + int frame_feedback_id) override { CHECK_EQ(format, media::PIXEL_FORMAT_I420); int buffer_id_to_drop = media::VideoCaptureBufferPool::kInvalidId; // Ignored. const int buffer_id = buffer_pool_->ReserveForProducer( - dimensions, format, storage, &buffer_id_to_drop); + dimensions, format, storage, frame_feedback_id, &buffer_id_to_drop); if (buffer_id == media::VideoCaptureBufferPool::kInvalidId) return NULL; return std::unique_ptr<media::VideoCaptureDevice::Client::Buffer>( - new AutoReleaseBuffer( - buffer_pool_, buffer_pool_->GetBufferHandle(buffer_id), buffer_id)); + new AutoReleaseBuffer(buffer_pool_, + buffer_pool_->GetBufferHandle(buffer_id), + buffer_id, frame_feedback_id)); } // Trampoline method to workaround GMOCK problems with std::unique_ptr<>. void OnIncomingCapturedBuffer(std::unique_ptr<Buffer> buffer, - const media::VideoCaptureFormat& frame_format, + const media::VideoCaptureFormat& format, base::TimeTicks reference_time, base::TimeDelta timestamp) override { DoOnIncomingCapturedBuffer(); @@ -404,15 +407,17 @@ std::unique_ptr<media::VideoCaptureDevice::Client::Buffer> ResurrectLastOutputBuffer(const gfx::Size& dimensions, media::VideoPixelFormat format, - media::VideoPixelStorage storage) override { + media::VideoPixelStorage storage, + int frame_feedback_id) override { CHECK_EQ(format, media::PIXEL_FORMAT_I420); const int buffer_id = buffer_pool_->ResurrectLastForProducer(dimensions, format, storage); if (buffer_id == media::VideoCaptureBufferPool::kInvalidId) return nullptr; return std::unique_ptr<media::VideoCaptureDevice::Client::Buffer>( - new AutoReleaseBuffer( - buffer_pool_, buffer_pool_->GetBufferHandle(buffer_id), buffer_id)); + new AutoReleaseBuffer(buffer_pool_, + buffer_pool_->GetBufferHandle(buffer_id), + buffer_id, frame_feedback_id)); } void OnError(const tracked_objects::Location& from_here, @@ -428,13 +433,16 @@ AutoReleaseBuffer( const scoped_refptr<media::VideoCaptureBufferPool>& pool, std::unique_ptr<media::VideoCaptureBufferHandle> buffer_handle, - int buffer_id) + int buffer_id, + int frame_feedback_id) : id_(buffer_id), + frame_feedback_id_(frame_feedback_id), pool_(pool), buffer_handle_(std::move(buffer_handle)) { DCHECK(pool_); } int id() const override { return id_; } + int frame_feedback_id() const override { return frame_feedback_id_; } gfx::Size dimensions() const override { return buffer_handle_->dimensions(); } @@ -458,6 +466,7 @@ ~AutoReleaseBuffer() override { pool_->RelinquishProducerReservation(id_); } const int id_; + const int frame_feedback_id_; const scoped_refptr<media::VideoCaptureBufferPool> pool_; const std::unique_ptr<media::VideoCaptureBufferHandle> buffer_handle_; };
diff --git a/content/browser/renderer_host/media/video_capture_buffer_pool_unittest.cc b/content/browser/renderer_host/media/video_capture_buffer_pool_unittest.cc index 13d4b46b..69c0468 100644 --- a/content/browser/renderer_host/media/video_capture_buffer_pool_unittest.cc +++ b/content/browser/renderer_host/media/video_capture_buffer_pool_unittest.cc
@@ -90,9 +90,11 @@ format_and_storage.pixel_storage) << " " << media::VideoPixelFormatToString(format_and_storage.pixel_format) << " " << dimensions.ToString(); + const int arbitrary_frame_feedback_id = 0; const int buffer_id = pool_->ReserveForProducer( dimensions, format_and_storage.pixel_format, - format_and_storage.pixel_storage, &buffer_id_to_drop); + format_and_storage.pixel_storage, arbitrary_frame_feedback_id, + &buffer_id_to_drop); if (buffer_id == media::VideoCaptureBufferPool::kInvalidId) return std::unique_ptr<Buffer>(); EXPECT_EQ(expected_dropped_id_, buffer_id_to_drop);
diff --git a/content/browser/renderer_host/media/video_capture_controller.cc b/content/browser/renderer_host/media/video_capture_controller.cc index 83f264cd..5f226b6 100644 --- a/content/browser/renderer_host/media/video_capture_controller.cc +++ b/content/browser/renderer_host/media/video_capture_controller.cc
@@ -46,40 +46,6 @@ name, \ (height) ? ((width) * 100) / (height) : kInfiniteRatio); -class SyncTokenClientImpl : public VideoFrame::SyncTokenClient { - public: - explicit SyncTokenClientImpl(display_compositor::GLHelper* gl_helper) - : gl_helper_(gl_helper) {} - ~SyncTokenClientImpl() override {} - void GenerateSyncToken(gpu::SyncToken* sync_token) override { - gl_helper_->GenerateSyncToken(sync_token); - } - void WaitSyncToken(const gpu::SyncToken& sync_token) override { - gl_helper_->WaitSyncToken(sync_token); - } - - private: - display_compositor::GLHelper* gl_helper_; -}; - -void ReturnVideoFrame(const scoped_refptr<VideoFrame>& video_frame, - const gpu::SyncToken& sync_token) { - DCHECK_CURRENTLY_ON(BrowserThread::UI); -#if defined(OS_ANDROID) - NOTREACHED(); -#else - display_compositor::GLHelper* gl_helper = - ImageTransportFactory::GetInstance()->GetGLHelper(); - // UpdateReleaseSyncToken() creates a new sync_token using |gl_helper|, so - // wait the given |sync_token| using |gl_helper|. - if (gl_helper) { - gl_helper->WaitSyncToken(sync_token); - SyncTokenClientImpl client(gl_helper); - video_frame->UpdateReleaseSyncToken(&client); - } -#endif -} - std::unique_ptr<media::VideoCaptureJpegDecoder> CreateGpuJpegDecoder( const media::VideoCaptureJpegDecoder::DecodeDoneCB& decode_done_cb) { return base::MakeUnique<VideoCaptureGpuJpegDecoder>(decode_done_cb); @@ -148,12 +114,10 @@ const media::VideoCaptureParams parameters; // Buffers that are currently known to this client. - std::set<int> known_buffers; + std::vector<int> known_buffers; - // Buffers currently held by this client, and sync token callback to call when - // they are returned from the client. - typedef std::map<int, scoped_refptr<VideoFrame>> ActiveBufferMap; - ActiveBufferMap active_buffers; + // Buffers currently held by this client. + std::vector<int> buffers_in_use; // State of capture session, controlled by VideoCaptureManager directly. This // transitions to true as soon as StopSession() occurs, at which point the @@ -172,10 +136,69 @@ bool paused; }; +VideoCaptureController::BufferState::BufferState( + int buffer_id, + int frame_feedback_id, + media::VideoFrameConsumerFeedbackObserver* consumer_feedback_observer, + scoped_refptr<media::VideoCaptureBufferPool> buffer_pool, + scoped_refptr<media::VideoFrame> frame) + : buffer_id_(buffer_id), + frame_feedback_id_(frame_feedback_id), + consumer_feedback_observer_(consumer_feedback_observer), + buffer_pool_(std::move(buffer_pool)), + frame_(std::move(frame)), + max_consumer_utilization_( + media::VideoFrameConsumerFeedbackObserver::kNoUtilizationRecorded), + consumer_hold_count_(0) {} + +VideoCaptureController::BufferState::~BufferState() = default; + +VideoCaptureController::BufferState::BufferState( + const VideoCaptureController::BufferState& other) = default; + +void VideoCaptureController::BufferState::RecordConsumerUtilization( + double utilization) { + if (std::isfinite(utilization) && utilization >= 0.0) { + max_consumer_utilization_ = + std::max(max_consumer_utilization_, utilization); + } +} + +void VideoCaptureController::BufferState::IncreaseConsumerCount() { + if (consumer_hold_count_ == 0) + buffer_pool_->HoldForConsumers(buffer_id_, 1); + consumer_hold_count_++; +} + +void VideoCaptureController::BufferState::DecreaseConsumerCount() { + consumer_hold_count_--; + if (consumer_hold_count_ == 0) { + if (consumer_feedback_observer_ != nullptr && + max_consumer_utilization_ != + media::VideoFrameConsumerFeedbackObserver::kNoUtilizationRecorded) { + consumer_feedback_observer_->OnUtilizationReport( + frame_feedback_id_, max_consumer_utilization_); + } + buffer_pool_->RelinquishConsumerHold(buffer_id_, 1); + max_consumer_utilization_ = + media::VideoFrameConsumerFeedbackObserver::kNoUtilizationRecorded; + } +} + +bool VideoCaptureController::BufferState::HasZeroConsumerHoldCount() { + return consumer_hold_count_ == 0; +} + +void VideoCaptureController::BufferState::SetConsumerFeedbackObserver( + media::VideoFrameConsumerFeedbackObserver* consumer_feedback_observer) { + consumer_feedback_observer_ = consumer_feedback_observer; +} + VideoCaptureController::VideoCaptureController(int max_buffers) : buffer_pool_(new media::VideoCaptureBufferPoolImpl( base::MakeUnique<media::VideoCaptureBufferTrackerFactoryImpl>(), max_buffers)), + consumer_feedback_observer_(nullptr), state_(VIDEO_CAPTURE_STATE_STARTED), has_received_frames_(false), weak_ptr_factory_(this) { @@ -187,6 +210,16 @@ return weak_ptr_factory_.GetWeakPtr(); } +void VideoCaptureController::SetConsumerFeedbackObserver( + std::unique_ptr<media::VideoFrameConsumerFeedbackObserver> + consumer_feedback_observer) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + consumer_feedback_observer_ = std::move(consumer_feedback_observer); + // Update existing BufferState entries. + for (auto& entry : buffer_id_to_state_map_) + entry.second.SetConsumerFeedbackObserver(consumer_feedback_observer_.get()); +} + std::unique_ptr<media::VideoCaptureDevice::Client> VideoCaptureController::NewDeviceClient() { DCHECK_CURRENTLY_ON(BrowserThread::IO); @@ -259,9 +292,9 @@ return kInvalidMediaCaptureSessionId; // Take back all buffers held by the |client|. - for (const auto& buffer : client->active_buffers) - buffer_pool_->RelinquishConsumerHold(buffer.first, 1); - client->active_buffers.clear(); + for (const auto& buffer_id : client->buffers_in_use) + buffer_id_to_state_map_.at(buffer_id).DecreaseConsumerCount(); + client->buffers_in_use.clear(); int session_id = client->session_id; controller_clients_.remove_if( @@ -353,42 +386,22 @@ // If this buffer is not held by this client, or this client doesn't exist // in controller, do nothing. - ControllerClient::ActiveBufferMap::iterator iter; - if (!client || (iter = client->active_buffers.find(buffer_id)) == - client->active_buffers.end()) { + if (!client) { + NOTREACHED(); + return; + } + auto buffers_in_use_entry_iter = + std::find(std::begin(client->buffers_in_use), + std::end(client->buffers_in_use), buffer_id); + if (buffers_in_use_entry_iter == std::end(client->buffers_in_use)) { NOTREACHED(); return; } - // Set the RESOURCE_UTILIZATION to the maximum of those provided by each - // consumer (via separate calls to this method that refer to the same - // VideoFrame). The producer of this VideoFrame may check this value, after - // all consumer holds are relinquished, to make quality versus performance - // trade-off decisions. - scoped_refptr<VideoFrame> frame = iter->second; - if (std::isfinite(consumer_resource_utilization) && - consumer_resource_utilization >= 0.0) { - double resource_utilization = -1.0; - if (frame->metadata()->GetDouble(VideoFrameMetadata::RESOURCE_UTILIZATION, - &resource_utilization)) { - frame->metadata()->SetDouble(VideoFrameMetadata::RESOURCE_UTILIZATION, - std::max(consumer_resource_utilization, - resource_utilization)); - } else { - frame->metadata()->SetDouble(VideoFrameMetadata::RESOURCE_UTILIZATION, - consumer_resource_utilization); - } - } - - client->active_buffers.erase(iter); - buffer_pool_->RelinquishConsumerHold(buffer_id, 1); - -#if defined(OS_ANDROID) - DCHECK(!sync_token.HasData()); -#endif - if (sync_token.HasData()) - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - base::Bind(&ReturnVideoFrame, frame, sync_token)); + BufferState& buffer_state = buffer_id_to_state_map_.at(buffer_id); + buffer_state.RecordConsumerUtilization(consumer_resource_utilization); + buffer_state.DecreaseConsumerCount(); + client->buffers_in_use.erase(buffers_in_use_entry_iter); } const media::VideoCaptureFormat& @@ -407,7 +420,17 @@ const int buffer_id = buffer->id(); DCHECK_NE(buffer_id, media::VideoCaptureBufferPool::kInvalidId); - int count = 0; + // Insert if not exists. + const auto it = + buffer_id_to_state_map_ + .insert(std::make_pair( + buffer_id, BufferState(buffer_id, buffer->frame_feedback_id(), + consumer_feedback_observer_.get(), + buffer_pool_, frame))) + .first; + BufferState& buffer_state = it->second; + DCHECK(buffer_state.HasZeroConsumerHoldCount()); + if (state_ == VIDEO_CAPTURE_STATE_STARTED) { if (!frame->metadata()->HasKey(VideoFrameMetadata::FRAME_RATE)) { frame->metadata()->SetDouble(VideoFrameMetadata::FRAME_RATE, @@ -436,17 +459,28 @@ continue; // On the first use of a buffer on a client, share the memory handles. - const bool is_new_buffer = client->known_buffers.insert(buffer_id).second; + auto known_buffers_entry_iter = + std::find(std::begin(client->known_buffers), + std::end(client->known_buffers), buffer_id); + bool is_new_buffer = false; + if (known_buffers_entry_iter == std::end(client->known_buffers)) { + client->known_buffers.push_back(buffer_id); + is_new_buffer = true; + } if (is_new_buffer) DoNewBufferOnIOThread(client.get(), buffer.get(), frame); client->event_handler->OnBufferReady(client->controller_id, buffer_id, frame); - const bool inserted = - client->active_buffers.insert(std::make_pair(buffer_id, frame)) - .second; - DCHECK(inserted) << "Unexpected duplicate buffer: " << buffer_id; - count++; + + auto buffers_in_use_entry_iter = + std::find(std::begin(client->buffers_in_use), + std::end(client->buffers_in_use), buffer_id); + if (buffers_in_use_entry_iter == std::end(client->buffers_in_use)) + client->buffers_in_use.push_back(buffer_id); + else + DCHECK(false) << "Unexpected duplicate buffer: " << buffer_id; + buffer_state.IncreaseConsumerCount(); } } @@ -466,8 +500,6 @@ UMA_HISTOGRAM_COUNTS("Media.VideoCapture.FrameRate", frame_rate); has_received_frames_ = true; } - - buffer_pool_->HoldForConsumers(buffer_id, count); } void VideoCaptureController::OnError() { @@ -493,11 +525,17 @@ if (client->session_closed) continue; - if (client->known_buffers.erase(buffer_id_to_drop)) { + auto known_buffers_entry_iter = + std::find(std::begin(client->known_buffers), + std::end(client->known_buffers), buffer_id_to_drop); + if (known_buffers_entry_iter != std::end(client->known_buffers)) { + client->known_buffers.erase(known_buffers_entry_iter); client->event_handler->OnBufferDestroyed(client->controller_id, buffer_id_to_drop); } } + + buffer_id_to_state_map_.erase(buffer_id_to_drop); } void VideoCaptureController::DoNewBufferOnIOThread(
diff --git a/content/browser/renderer_host/media/video_capture_controller.h b/content/browser/renderer_host/media/video_capture_controller.h index 8aaf97c..b1a455f 100644 --- a/content/browser/renderer_host/media/video_capture_controller.h +++ b/content/browser/renderer_host/media/video_capture_controller.h
@@ -72,6 +72,14 @@ base::WeakPtr<VideoCaptureController> GetWeakPtrForIOThread(); + // Factory code creating instances of VideoCaptureController may optionally + // set a VideoFrameConsumerFeedbackObserver. Setting the observer is done in + // this method separate from the constructor to allow clients to create and + // use instances before they can provide the observer. (This is the case with + // VideoCaptureManager). + void SetConsumerFeedbackObserver( + std::unique_ptr<media::VideoFrameConsumerFeedbackObserver> observer); + // Return a new VideoCaptureDeviceClient to forward capture events to this // instance. std::unique_ptr<media::VideoCaptureDevice::Client> NewDeviceClient(); @@ -139,6 +147,33 @@ struct ControllerClient; typedef std::list<std::unique_ptr<ControllerClient>> ControllerClients; + class BufferState { + public: + explicit BufferState( + int buffer_id, + int frame_feedback_id, + media::VideoFrameConsumerFeedbackObserver* consumer_feedback_observer, + scoped_refptr<media::VideoCaptureBufferPool> buffer_pool, + scoped_refptr<media::VideoFrame> frame); + ~BufferState(); + BufferState(const BufferState& other); + void RecordConsumerUtilization(double utilization); + void IncreaseConsumerCount(); + void DecreaseConsumerCount(); + bool HasZeroConsumerHoldCount(); + void SetConsumerFeedbackObserver( + media::VideoFrameConsumerFeedbackObserver* consumer_feedback_observer); + + private: + const int buffer_id_; + const int frame_feedback_id_; + media::VideoFrameConsumerFeedbackObserver* consumer_feedback_observer_; + const scoped_refptr<media::VideoCaptureBufferPool> buffer_pool_; + const scoped_refptr<media::VideoFrame> frame_; + double max_consumer_utilization_; + int consumer_hold_count_; + }; + // Notify renderer that a new buffer has been created. void DoNewBufferOnIOThread(ControllerClient* client, media::VideoCaptureDevice::Client::Buffer* buffer, @@ -156,6 +191,11 @@ // The pool of shared-memory buffers used for capturing. const scoped_refptr<media::VideoCaptureBufferPool> buffer_pool_; + std::unique_ptr<media::VideoFrameConsumerFeedbackObserver> + consumer_feedback_observer_; + + std::map<int, BufferState> buffer_id_to_state_map_; + // All clients served by this controller. ControllerClients controller_clients_;
diff --git a/content/browser/renderer_host/media/video_capture_controller_unittest.cc b/content/browser/renderer_host/media/video_capture_controller_unittest.cc index 21c5fbe..2529e895 100644 --- a/content/browser/renderer_host/media/video_capture_controller_unittest.cc +++ b/content/browser/renderer_host/media/video_capture_controller_unittest.cc
@@ -33,6 +33,7 @@ #include "testing/gtest/include/gtest/gtest.h" using ::testing::_; +using ::testing::AnyNumber; using ::testing::InSequence; using ::testing::Mock; using ::testing::SaveArg; @@ -95,6 +96,13 @@ double resource_utilization_; }; +class MockConsumerFeedbackObserver + : public media::VideoFrameConsumerFeedbackObserver { + public: + MOCK_METHOD2(OnUtilizationReport, + void(int frame_feedback_id, double utilization)); +}; + // Test class. class VideoCaptureControllerTest : public testing::Test, @@ -109,6 +117,11 @@ void SetUp() override { controller_.reset(new VideoCaptureController(kPoolSize)); device_ = controller_->NewDeviceClient(); + auto consumer_feedback_observer = + base::MakeUnique<MockConsumerFeedbackObserver>(); + mock_consumer_feedback_observer_ = consumer_feedback_observer.get(); + controller_->SetConsumerFeedbackObserver( + std::move(consumer_feedback_observer)); client_a_.reset(new MockVideoCaptureControllerEventHandler( controller_.get())); client_b_.reset(new MockVideoCaptureControllerEventHandler( @@ -135,6 +148,7 @@ std::unique_ptr<MockVideoCaptureControllerEventHandler> client_b_; std::unique_ptr<VideoCaptureController> controller_; std::unique_ptr<media::VideoCaptureDevice::Client> device_; + MockConsumerFeedbackObserver* mock_consumer_feedback_observer_; private: DISALLOW_COPY_AND_ASSIGN(VideoCaptureControllerTest); @@ -284,10 +298,12 @@ // Now, simulate an incoming captured buffer from the capture device. As a // side effect this will cause the first buffer to be shared with clients. uint8_t buffer_no = 1; + const int arbitrary_frame_feedback_id = 101; ASSERT_EQ(0.0, device_->GetBufferPoolUtilization()); std::unique_ptr<media::VideoCaptureDevice::Client::Buffer> buffer( device_->ReserveOutputBuffer(capture_resolution, format, - media::PIXEL_STORAGE_CPU)); + media::PIXEL_STORAGE_CPU, + arbitrary_frame_feedback_id)); ASSERT_TRUE(buffer.get()); ASSERT_EQ(1.0 / kPoolSize, device_->GetBufferPoolUtilization()); memset(buffer->data(), buffer_no++, buffer->mapped_size()); @@ -316,6 +332,13 @@ media::VideoFrameMetadata::RESOURCE_UTILIZATION)); client_a_->resource_utilization_ = 0.5; client_b_->resource_utilization_ = -1.0; + + // Expect VideoCaptureController to call the load observer with a + // resource utilization of 0.5 (the largest of all reported values). + EXPECT_CALL(*mock_consumer_feedback_observer_, + OnUtilizationReport(arbitrary_frame_feedback_id, 0.5)) + .Times(1); + video_frame->metadata()->SetTimeTicks( media::VideoFrameMetadata::REFERENCE_TIME, base::TimeTicks()); device_->OnIncomingCapturedVideoFrame(std::move(buffer), video_frame); @@ -323,32 +346,30 @@ base::RunLoop().RunUntilIdle(); Mock::VerifyAndClearExpectations(client_a_.get()); Mock::VerifyAndClearExpectations(client_b_.get()); - - // Expect VideoCaptureController set the metadata in |video_frame| to hold a - // resource utilization of 0.5 (the largest of all reported values). - double resource_utilization_in_metadata = -1.0; - ASSERT_TRUE(video_frame->metadata()->GetDouble( - media::VideoFrameMetadata::RESOURCE_UTILIZATION, - &resource_utilization_in_metadata)); - ASSERT_EQ(0.5, resource_utilization_in_metadata); + Mock::VerifyAndClearExpectations(mock_consumer_feedback_observer_); // Second buffer which ought to use the same shared memory buffer. In this // case pretend that the Buffer pointer is held by the device for a long // delay. This shouldn't affect anything. + const int arbitrary_frame_feedback_id_2 = 102; std::unique_ptr<media::VideoCaptureDevice::Client::Buffer> buffer2 = device_->ReserveOutputBuffer(capture_resolution, format, - media::PIXEL_STORAGE_CPU); + media::PIXEL_STORAGE_CPU, + arbitrary_frame_feedback_id_2); ASSERT_TRUE(buffer2.get()); memset(buffer2->data(), buffer_no++, buffer2->mapped_size()); video_frame = WrapBuffer(capture_resolution, static_cast<uint8_t*>(buffer2->data()), format); - ASSERT_TRUE(video_frame); - ASSERT_FALSE(video_frame->metadata()->HasKey( - media::VideoFrameMetadata::RESOURCE_UTILIZATION)); client_a_->resource_utilization_ = 0.5; client_b_->resource_utilization_ = 3.14; video_frame->metadata()->SetTimeTicks( media::VideoFrameMetadata::REFERENCE_TIME, base::TimeTicks()); + // Expect VideoCaptureController to call the load observer with a + // resource utilization of 3.14 (the largest of all reported values). + EXPECT_CALL(*mock_consumer_feedback_observer_, + OnUtilizationReport(arbitrary_frame_feedback_id_2, 3.14)) + .Times(1); + device_->OnIncomingCapturedVideoFrame(std::move(buffer2), video_frame); // The buffer should be delivered to the clients in any order. @@ -373,13 +394,7 @@ base::RunLoop().RunUntilIdle(); Mock::VerifyAndClearExpectations(client_a_.get()); Mock::VerifyAndClearExpectations(client_b_.get()); - // Expect VideoCaptureController set the metadata in |video_frame| to hold a - // resource utilization of 3.14 (the largest of all reported values). - resource_utilization_in_metadata = -1.0; - ASSERT_TRUE(video_frame->metadata()->GetDouble( - media::VideoFrameMetadata::RESOURCE_UTILIZATION, - &resource_utilization_in_metadata)); - ASSERT_EQ(3.14, resource_utilization_in_metadata); + Mock::VerifyAndClearExpectations(mock_consumer_feedback_observer_); // Add a fourth client now that some buffers have come through. controller_->AddClient(client_b_route_2, @@ -390,9 +405,11 @@ // Third, fourth, and fifth buffers. Pretend they all arrive at the same time. for (int i = 0; i < kPoolSize; i++) { + const int arbitrary_frame_feedback_id = 200 + i; std::unique_ptr<media::VideoCaptureDevice::Client::Buffer> buffer = device_->ReserveOutputBuffer(capture_resolution, format, - media::PIXEL_STORAGE_CPU); + media::PIXEL_STORAGE_CPU, + arbitrary_frame_feedback_id); ASSERT_TRUE(buffer.get()); memset(buffer->data(), buffer_no++, buffer->mapped_size()); video_frame = WrapBuffer(capture_resolution, @@ -405,7 +422,8 @@ // ReserveOutputBuffer ought to fail now, because the pool is depleted. ASSERT_FALSE(device_ ->ReserveOutputBuffer(capture_resolution, format, - media::PIXEL_STORAGE_CPU) + media::PIXEL_STORAGE_CPU, + arbitrary_frame_feedback_id) .get()); // The new client needs to be notified of the creation of |kPoolSize| buffers; @@ -438,7 +456,8 @@ // Queue up another buffer. std::unique_ptr<media::VideoCaptureDevice::Client::Buffer> buffer3 = device_->ReserveOutputBuffer(capture_resolution, format, - media::PIXEL_STORAGE_CPU); + media::PIXEL_STORAGE_CPU, + arbitrary_frame_feedback_id); ASSERT_TRUE(buffer3.get()); memset(buffer3->data(), buffer_no++, buffer3->mapped_size()); video_frame = WrapBuffer(capture_resolution, @@ -450,7 +469,8 @@ std::unique_ptr<media::VideoCaptureDevice::Client::Buffer> buffer4 = device_->ReserveOutputBuffer(capture_resolution, format, - media::PIXEL_STORAGE_CPU); + media::PIXEL_STORAGE_CPU, + arbitrary_frame_feedback_id); { // Kill A2 via session close (posts a task to disconnect, but A2 must not // be sent either of these two buffers). @@ -506,9 +526,11 @@ base::RunLoop().RunUntilIdle(); Mock::VerifyAndClearExpectations(client_b_.get()); + const int arbitrary_frame_feedback_id = 101; std::unique_ptr<media::VideoCaptureDevice::Client::Buffer> buffer( device_->ReserveOutputBuffer(capture_resolution, media::PIXEL_FORMAT_I420, - media::PIXEL_STORAGE_CPU)); + media::PIXEL_STORAGE_CPU, + arbitrary_frame_feedback_id)); ASSERT_TRUE(buffer.get()); scoped_refptr<media::VideoFrame> video_frame = WrapBuffer(capture_resolution, static_cast<uint8_t*>(buffer->data())); @@ -543,9 +565,11 @@ Mock::VerifyAndClearExpectations(client_a_.get()); const gfx::Size dims(320, 240); + const int arbitrary_frame_feedback_id = 101; std::unique_ptr<media::VideoCaptureDevice::Client::Buffer> buffer( device_->ReserveOutputBuffer(dims, media::PIXEL_FORMAT_I420, - media::PIXEL_STORAGE_CPU)); + media::PIXEL_STORAGE_CPU, + arbitrary_frame_feedback_id)); ASSERT_TRUE(buffer.get()); scoped_refptr<media::VideoFrame> video_frame =
diff --git a/content/browser/renderer_host/media/video_capture_manager.cc b/content/browser/renderer_host/media/video_capture_manager.cc index e8664c1..e1830ee 100644 --- a/content/browser/renderer_host/media/video_capture_manager.cc +++ b/content/browser/renderer_host/media/video_capture_manager.cc
@@ -47,6 +47,27 @@ namespace { +class VideoFrameConsumerFeedbackObserverOnTaskRunner + : public media::VideoFrameConsumerFeedbackObserver { + public: + VideoFrameConsumerFeedbackObserverOnTaskRunner( + media::VideoFrameConsumerFeedbackObserver* observer, + scoped_refptr<base::SingleThreadTaskRunner> task_runner) + : observer_(observer), task_runner_(std::move(task_runner)) {} + + void OnUtilizationReport(int frame_feedback_id, double utilization) override { + task_runner_->PostTask( + FROM_HERE, + base::Bind( + &media::VideoFrameConsumerFeedbackObserver::OnUtilizationReport, + base::Unretained(observer_), frame_feedback_id, utilization)); + } + + private: + media::VideoFrameConsumerFeedbackObserver* const observer_; + const scoped_refptr<base::SingleThreadTaskRunner> task_runner_; +}; + // Compares two VideoCaptureFormat by checking smallest frame_size area, then // by _largest_ frame_rate. Used to order a VideoCaptureFormats vector so that // the first entry for a given resolution has the largest frame rate, as needed @@ -126,9 +147,17 @@ namespace content { -// This class owns a pair VideoCaptureDevice - VideoCaptureController. -// VideoCaptureManager owns all such pairs and is responsible for deleting the -// instances when they are not used any longer. +// Instances of this struct go through 3 different phases during their lifetime. +// Phase 1: When first created (in GetOrCreateDeviceEntry()), this consists of +// only a controller. Clients can already connect to the controller, but there +// is no device present. +// Phase 2: When a request to "start" the entry comes in (via +// HandleQueuedStartRequest()), a VideoCaptureDevice::Client is created +// via video_capture_controller()->NewDeviceClient() and is used to schedule the +// creation and start of a VideoCaptureDevice on the Device Thread. +// Phase 3: As soon as the creation of the VideoCaptureDevice is complete, this +// newly created VideoCaptureDevice instance is connected to the +// VideoCaptureController via SetConsumerFeedbackObserver(). class VideoCaptureManager::DeviceEntry { public: DeviceEntry(MediaStreamType stream_type, @@ -406,9 +435,11 @@ << " serial_id = " << entry->serial_id << "."; entry->video_capture_controller()->OnLog( base::StringPrintf("Stopping device: id: %s", entry->id.c_str())); + entry->video_capture_controller()->SetConsumerFeedbackObserver(nullptr); + // |entry->video_capture_device| can be null if creating the device has + // failed. if (entry->video_capture_device()) { - // |entry->video_capture_device| can be null if creating the device fails. device_task_runner_->PostTask( FROM_HERE, base::Bind(&VideoCaptureManager::DoStopDeviceOnDeviceThread, this, @@ -504,9 +535,9 @@ DVLOG(3) << __func__; DCHECK_CURRENTLY_ON(BrowserThread::IO); DCHECK_EQ(serial_id, device_start_queue_.begin()->serial_id()); + // |device| can be null if creation failed in + // DoStartDeviceCaptureOnDeviceThread. if (device_start_queue_.front().abort_start()) { - // |device| can be null if creation failed in - // DoStartDeviceCaptureOnDeviceThread. // The device is no longer wanted. Stop the device again. DVLOG(3) << "OnDeviceStarted but start request have been aborted."; media::VideoCaptureDevice* device_ptr = device.get(); @@ -521,6 +552,13 @@ DeviceEntry* const entry = GetDeviceEntryBySerialId(serial_id); DCHECK(entry); DCHECK(!entry->video_capture_device()); + // Passing raw pointer |device.get()| to the controller is safe, + // because we transfer ownership of it to |entry|. We are calling + // SetConsumerFeedbackObserver(nullptr) before releasing + // |entry->video_capture_device_| on the |device_task_runner_|. + entry->video_capture_controller()->SetConsumerFeedbackObserver( + base::MakeUnique<VideoFrameConsumerFeedbackObserverOnTaskRunner>( + device.get(), device_task_runner_)); entry->SetVideoCaptureDevice(std::move(device)); if (entry->stream_type == MEDIA_DESKTOP_VIDEO_CAPTURE) {
diff --git a/content/browser/service_worker/foreign_fetch_request_handler.cc b/content/browser/service_worker/foreign_fetch_request_handler.cc index 5298f1c..ab2d134 100644 --- a/content/browser/service_worker/foreign_fetch_request_handler.cc +++ b/content/browser/service_worker/foreign_fetch_request_handler.cc
@@ -91,7 +91,12 @@ if (!initiated_in_secure_context) return; - if (ServiceWorkerUtils::IsMainResourceType(resource_type)) + // ServiceWorkerUtils::IsMainResource doesn't consider all worker types to + // be main resources. This code shouldn't handle any main resource requests + // though, so explicitly exclude the extra worker types. + if (ServiceWorkerUtils::IsMainResourceType(resource_type) || + resource_type == RESOURCE_TYPE_WORKER || + resource_type == RESOURCE_TYPE_SERVICE_WORKER) return; if (request->initiator().has_value() && @@ -99,6 +104,17 @@ return; } + ServiceWorkerProviderHost* provider_host = + context_wrapper->context()->GetProviderHost(process_id, provider_id); + if (!provider_host || !provider_host->IsContextAlive()) + return; + + base::Optional<base::TimeDelta> timeout; + if (provider_host->IsHostToRunningServiceWorker()) { + timeout = base::make_optional( + provider_host->running_hosted_version()->remaining_timeout()); + } + if (!context_wrapper->OriginHasForeignFetchRegistrations( request->url().GetOrigin())) { return; @@ -110,7 +126,7 @@ new ForeignFetchRequestHandler( context_wrapper, blob_storage_context->AsWeakPtr(), request_mode, credentials_mode, redirect_mode, resource_type, request_context_type, - frame_type, body)); + frame_type, body, timeout)); request->SetUserData(&kUserDataKey, handler.release()); } @@ -163,7 +179,7 @@ request, network_delegate, std::string(), blob_storage_context_, resource_context, request_mode_, credentials_mode_, redirect_mode_, resource_type_, request_context_type_, frame_type_, body_, - ServiceWorkerFetchType::FOREIGN_FETCH, this); + ServiceWorkerFetchType::FOREIGN_FETCH, timeout_, this); job_ = job->GetWeakPtr(); resource_context_ = resource_context; @@ -184,7 +200,8 @@ ResourceType resource_type, RequestContextType request_context_type, RequestContextFrameType frame_type, - scoped_refptr<ResourceRequestBodyImpl> body) + scoped_refptr<ResourceRequestBodyImpl> body, + const base::Optional<base::TimeDelta>& timeout) : context_(context), blob_storage_context_(blob_storage_context), resource_type_(resource_type), @@ -194,6 +211,7 @@ request_context_type_(request_context_type), frame_type_(frame_type), body_(body), + timeout_(timeout), weak_factory_(this) {} void ForeignFetchRequestHandler::DidFindRegistration(
diff --git a/content/browser/service_worker/foreign_fetch_request_handler.h b/content/browser/service_worker/foreign_fetch_request_handler.h index 48cb85e..c047dc3 100644 --- a/content/browser/service_worker/foreign_fetch_request_handler.h +++ b/content/browser/service_worker/foreign_fetch_request_handler.h
@@ -7,6 +7,7 @@ #include "base/macros.h" #include "base/memory/weak_ptr.h" +#include "base/optional.h" #include "base/supports_user_data.h" #include "base/time/time.h" #include "content/browser/service_worker/service_worker_url_request_job.h" @@ -94,7 +95,8 @@ ResourceType resource_type, RequestContextType request_context_type, RequestContextFrameType frame_type, - scoped_refptr<ResourceRequestBodyImpl> body); + scoped_refptr<ResourceRequestBodyImpl> body, + const base::Optional<base::TimeDelta>& timeout); // Called when a ServiceWorkerRegistration has (or hasn't) been found for the // request being handled. @@ -128,6 +130,7 @@ RequestContextFrameType frame_type_; scoped_refptr<ResourceRequestBodyImpl> body_; ResourceContext* resource_context_; + base::Optional<base::TimeDelta> timeout_; base::WeakPtr<ServiceWorkerURLRequestJob> job_; scoped_refptr<ServiceWorkerVersion> target_worker_;
diff --git a/content/browser/service_worker/foreign_fetch_request_handler_unittest.cc b/content/browser/service_worker/foreign_fetch_request_handler_unittest.cc index 8a12374b..c1410fe1 100644 --- a/content/browser/service_worker/foreign_fetch_request_handler_unittest.cc +++ b/content/browser/service_worker/foreign_fetch_request_handler_unittest.cc
@@ -6,18 +6,24 @@ #include "base/memory/ptr_util.h" #include "base/run_loop.h" +#include "base/test/simple_test_tick_clock.h" #include "content/browser/browser_thread_impl.h" +#include "content/browser/fileapi/mock_url_request_delegate.h" #include "content/browser/service_worker/embedded_worker_test_helper.h" #include "content/browser/service_worker/service_worker_context_core.h" #include "content/browser/service_worker/service_worker_registration.h" #include "content/browser/service_worker/service_worker_test_utils.h" #include "content/browser/service_worker/service_worker_version.h" +#include "content/common/service_worker/service_worker_utils.h" #include "content/public/common/content_client.h" #include "content/public/common/origin_trial_policy.h" #include "content/public/test/test_browser_thread_bundle.h" +#include "content/test/test_content_browser_client.h" #include "net/http/http_response_info.h" #include "net/test/cert_test_util.h" #include "net/test/test_data_directory.h" +#include "net/url_request/url_request_context.h" +#include "storage/browser/blob/blob_storage_context.h" #include "testing/gtest/include/gtest/gtest.h" namespace content { @@ -39,6 +45,12 @@ 0x64, 0x90, 0x08, 0x8e, 0xa8, 0xe0, 0x56, 0x3a, 0x04, 0xd0, }; +int kMockProviderId = 1; + +const char* kValidUrl = "https://valid.example.com/foo/bar"; + +void EmptyCallback() {} + } // namespace class ForeignFetchRequestHandlerTest : public testing::Test { @@ -46,6 +58,7 @@ ForeignFetchRequestHandlerTest() : browser_thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP) { SetContentClient(&test_content_client_); + SetBrowserClientForTesting(&test_content_browser_client_); } ~ForeignFetchRequestHandlerTest() override {} @@ -59,6 +72,35 @@ context()->AsWeakPtr()); version_ = new ServiceWorkerVersion(registration_.get(), kResource1, kVersionId, context()->AsWeakPtr()); + + version_->set_foreign_fetch_scopes({kScope}); + + // An empty host. + std::unique_ptr<ServiceWorkerProviderHost> host( + new ServiceWorkerProviderHost( + helper_->mock_render_process_id(), MSG_ROUTING_NONE, + kMockProviderId, SERVICE_WORKER_PROVIDER_FOR_WINDOW, + ServiceWorkerProviderHost::FrameSecurityLevel::SECURE, + context()->AsWeakPtr(), nullptr)); + host->SetDocumentUrl(GURL("https://host/scope/")); + provider_host_ = host->AsWeakPtr(); + context()->AddProviderHost(std::move(host)); + + context()->storage()->LazyInitialize(base::Bind(&EmptyCallback)); + base::RunLoop().RunUntilIdle(); + + std::vector<ServiceWorkerDatabase::ResourceRecord> records; + records.push_back( + ServiceWorkerDatabase::ResourceRecord(10, version_->script_url(), 100)); + version_->script_cache_map()->SetResources(records); + version_->set_fetch_handler_existence( + ServiceWorkerVersion::FetchHandlerExistence::EXISTS); + version_->SetStatus(ServiceWorkerVersion::ACTIVATED); + registration_->SetActiveVersion(version_); + context()->storage()->StoreRegistration( + registration_.get(), version_.get(), + base::Bind(&ServiceWorkerUtils::NoOpStatusCallback)); + base::RunLoop().RunUntilIdle(); } void TearDown() override { @@ -70,11 +112,22 @@ protected: ServiceWorkerContextCore* context() const { return helper_->context(); } + ServiceWorkerContextWrapper* context_wrapper() const { + return helper_->context_wrapper(); + } + ServiceWorkerProviderHost* provider_host() const { + return provider_host_.get(); + } bool CheckOriginTrialToken(const ServiceWorkerVersion* const version) const { return ForeignFetchRequestHandler::CheckOriginTrialToken(version); } + base::Optional<base::TimeDelta> timeout_for_request( + ForeignFetchRequestHandler* handler) { + return handler->timeout_; + } + ServiceWorkerVersion* version() const { return version_.get(); } static std::unique_ptr<net::HttpResponseInfo> CreateTestHttpResponseInfo() { @@ -90,6 +143,59 @@ return http_info; } + ForeignFetchRequestHandler* InitializeHandler(const std::string& url, + ResourceType resource_type, + const char* initiator) { + request_ = url_request_context_.CreateRequest( + GURL(url), net::DEFAULT_PRIORITY, &url_request_delegate_); + if (initiator) + request_->set_initiator(url::Origin(GURL(initiator))); + ForeignFetchRequestHandler::InitializeHandler( + request_.get(), context_wrapper(), &blob_storage_context_, + helper_->mock_render_process_id(), kMockProviderId, + SkipServiceWorker::NONE, FETCH_REQUEST_MODE_CORS, + FETCH_CREDENTIALS_MODE_OMIT, FetchRedirectMode::FOLLOW_MODE, + resource_type, REQUEST_CONTEXT_TYPE_FETCH, + REQUEST_CONTEXT_FRAME_TYPE_NONE, nullptr, + true /* initiated_in_secure_context */); + + return ForeignFetchRequestHandler::GetHandler(request_.get()); + } + + void CreateServiceWorkerTypeProviderHost() { + std::unique_ptr<ServiceWorkerProviderHost> host( + new ServiceWorkerProviderHost( + helper_->mock_render_process_id(), MSG_ROUTING_NONE, + kMockProviderId, SERVICE_WORKER_PROVIDER_FOR_CONTROLLER, + ServiceWorkerProviderHost::FrameSecurityLevel::UNINITIALIZED, + context()->AsWeakPtr(), nullptr)); + provider_host_ = host->AsWeakPtr(); + context()->RemoveProviderHost(host->process_id(), host->provider_id()); + context()->AddProviderHost(std::move(host)); + + scoped_refptr<ServiceWorkerRegistration> registration = + new ServiceWorkerRegistration(GURL("https://host/scope"), 1L, + context()->AsWeakPtr()); + scoped_refptr<ServiceWorkerVersion> version = new ServiceWorkerVersion( + registration.get(), GURL("https://host/script.js"), 1L, + context()->AsWeakPtr()); + + std::vector<ServiceWorkerDatabase::ResourceRecord> records; + records.push_back( + ServiceWorkerDatabase::ResourceRecord(10, version->script_url(), 100)); + version->script_cache_map()->SetResources(records); + version->set_fetch_handler_existence( + ServiceWorkerVersion::FetchHandlerExistence::EXISTS); + version->SetStatus(ServiceWorkerVersion::ACTIVATED); + registration->SetActiveVersion(version); + context()->storage()->StoreRegistration( + registration.get(), version.get(), + base::Bind(&ServiceWorkerUtils::NoOpStatusCallback)); + base::RunLoop().RunUntilIdle(); + + provider_host_->running_hosted_version_ = version; + } + private: class TestContentClient : public ContentClient { public: @@ -116,9 +222,16 @@ scoped_refptr<ServiceWorkerRegistration> registration_; scoped_refptr<ServiceWorkerVersion> version_; TestContentClient test_content_client_; + TestContentBrowserClient test_content_browser_client_; std::unique_ptr<EmbeddedWorkerTestHelper> helper_; TestBrowserThreadBundle browser_thread_bundle_; + net::URLRequestContext url_request_context_; + MockURLRequestDelegate url_request_delegate_; + base::WeakPtr<ServiceWorkerProviderHost> provider_host_; + storage::BlobStorageContext blob_storage_context_; + std::unique_ptr<net::URLRequest> request_; + DISALLOW_COPY_AND_ASSIGN(ForeignFetchRequestHandlerTest); }; @@ -185,4 +298,69 @@ EXPECT_FALSE(CheckOriginTrialToken(version())); } +TEST_F(ForeignFetchRequestHandlerTest, InitializeHandler_Success) { + EXPECT_TRUE(InitializeHandler(kValidUrl, RESOURCE_TYPE_IMAGE, + nullptr /* initiator */)); +} + +TEST_F(ForeignFetchRequestHandlerTest, InitializeHandler_WrongResourceType) { + EXPECT_FALSE(InitializeHandler(kValidUrl, RESOURCE_TYPE_MAIN_FRAME, + nullptr /* initiator */)); + EXPECT_FALSE(InitializeHandler(kValidUrl, RESOURCE_TYPE_SUB_FRAME, + nullptr /* initiator */)); + EXPECT_FALSE(InitializeHandler(kValidUrl, RESOURCE_TYPE_WORKER, + nullptr /* initiator */)); + EXPECT_FALSE(InitializeHandler(kValidUrl, RESOURCE_TYPE_SHARED_WORKER, + nullptr /* initiator */)); + EXPECT_FALSE(InitializeHandler(kValidUrl, RESOURCE_TYPE_SERVICE_WORKER, + nullptr /* initiator */)); +} + +TEST_F(ForeignFetchRequestHandlerTest, InitializeHandler_SameOriginRequest) { + EXPECT_FALSE(InitializeHandler(kValidUrl, RESOURCE_TYPE_IMAGE, + kValidUrl /* initiator */)); +} + +TEST_F(ForeignFetchRequestHandlerTest, InitializeHandler_NoRegisteredHandlers) { + EXPECT_FALSE(InitializeHandler("https://invalid.example.com/foo", + RESOURCE_TYPE_IMAGE, nullptr /* initiator */)); +} + +TEST_F(ForeignFetchRequestHandlerTest, InitializeHandler_TimeoutBehavior) { + ForeignFetchRequestHandler* handler = + InitializeHandler("https://valid.example.com/foo", RESOURCE_TYPE_IMAGE, + nullptr /* initiator */); + ASSERT_TRUE(handler); + + EXPECT_EQ(base::nullopt, timeout_for_request(handler)); + + CreateServiceWorkerTypeProviderHost(); + ServiceWorkerVersion* version = provider_host()->running_hosted_version(); + + // Set mock clock on version to check timeout behavior. + base::SimpleTestTickClock* tick_clock = new base::SimpleTestTickClock(); + tick_clock->SetNowTicks(base::TimeTicks::Now()); + version->SetTickClockForTesting(base::WrapUnique(tick_clock)); + + // Make sure worker has a non-zero timeout. + version->StartWorker(ServiceWorkerMetrics::EventType::UNKNOWN, + base::Bind(&ServiceWorkerUtils::NoOpStatusCallback)); + base::RunLoop().RunUntilIdle(); + version->StartRequestWithCustomTimeout( + ServiceWorkerMetrics::EventType::ACTIVATE, + base::Bind(&ServiceWorkerUtils::NoOpStatusCallback), + base::TimeDelta::FromSeconds(10), ServiceWorkerVersion::KILL_ON_TIMEOUT); + + // Advance clock by a couple seconds. + tick_clock->Advance(base::TimeDelta::FromSeconds(4)); + base::TimeDelta remaining_time = version->remaining_timeout(); + EXPECT_EQ(base::TimeDelta::FromSeconds(6), remaining_time); + + // Make sure new request only gets remaining timeout. + handler = InitializeHandler("https://valid.example.com/foo", + RESOURCE_TYPE_IMAGE, nullptr /* initiator */); + ASSERT_TRUE(handler); + EXPECT_EQ(remaining_time, timeout_for_request(handler)); +} + } // namespace content
diff --git a/content/browser/service_worker/service_worker_browsertest.cc b/content/browser/service_worker/service_worker_browsertest.cc index 8be954f..9b2189a 100644 --- a/content/browser/service_worker/service_worker_browsertest.cc +++ b/content/browser/service_worker/service_worker_browsertest.cc
@@ -791,7 +791,8 @@ version_->SetStatus(ServiceWorkerVersion::ACTIVATED); fetch_dispatcher_.reset(new ServiceWorkerFetchDispatcher( std::move(request), version_.get(), RESOURCE_TYPE_MAIN_FRAME, - net::NetLogWithSource(), CreatePrepareReceiver(prepare_result), + base::nullopt, net::NetLogWithSource(), + CreatePrepareReceiver(prepare_result), CreateResponseReceiver(done, blob_context_.get(), result))); fetch_dispatcher_->Run(); }
diff --git a/content/browser/service_worker/service_worker_context_wrapper.h b/content/browser/service_worker/service_worker_context_wrapper.h index a130f94..cadaaad 100644 --- a/content/browser/service_worker/service_worker_context_wrapper.h +++ b/content/browser/service_worker/service_worker_context_wrapper.h
@@ -217,6 +217,7 @@ friend class base::RefCountedThreadSafe<ServiceWorkerContextWrapper>; friend class EmbeddedWorkerTestHelper; friend class EmbeddedWorkerBrowserTest; + friend class ForeignFetchRequestHandler; friend class ServiceWorkerDispatcherHost; friend class ServiceWorkerInternalsUI; friend class ServiceWorkerNavigationHandleCore;
diff --git a/content/browser/service_worker/service_worker_controllee_request_handler.cc b/content/browser/service_worker/service_worker_controllee_request_handler.cc index e12c224c..58e7c16 100644 --- a/content/browser/service_worker/service_worker_controllee_request_handler.cc +++ b/content/browser/service_worker/service_worker_controllee_request_handler.cc
@@ -126,7 +126,7 @@ blob_storage_context_, resource_context, request_mode_, credentials_mode_, redirect_mode_, resource_type_, request_context_type_, frame_type_, body_, - ServiceWorkerFetchType::FETCH, this)); + ServiceWorkerFetchType::FETCH, base::nullopt, this)); job_ = job->GetWeakPtr(); resource_context_ = resource_context;
diff --git a/content/browser/service_worker/service_worker_fetch_dispatcher.cc b/content/browser/service_worker/service_worker_fetch_dispatcher.cc index 23446d5..c899c8e 100644 --- a/content/browser/service_worker/service_worker_fetch_dispatcher.cc +++ b/content/browser/service_worker/service_worker_fetch_dispatcher.cc
@@ -210,6 +210,7 @@ std::unique_ptr<ServiceWorkerFetchRequest> request, ServiceWorkerVersion* version, ResourceType resource_type, + const base::Optional<base::TimeDelta>& timeout, const net::NetLogWithSource& net_log, const base::Closure& prepare_callback, const FetchCallback& fetch_callback) @@ -219,6 +220,7 @@ fetch_callback_(fetch_callback), request_(std::move(request)), resource_type_(resource_type), + timeout_(timeout), did_complete_(false), weak_factory_(this) { net_log_.BeginEvent(net::NetLogEventType::SERVICE_WORKER_DISPATCH_FETCH_EVENT, @@ -297,13 +299,27 @@ prepare_callback.Run(); net_log_.BeginEvent(net::NetLogEventType::SERVICE_WORKER_FETCH_EVENT); - int fetch_event_id = version_->StartRequest( - GetEventType(), - base::Bind(&ServiceWorkerFetchDispatcher::DidFailToDispatch, - weak_factory_.GetWeakPtr())); - int event_finish_id = version_->StartRequest( - FetchTypeToWaitUntilEventType(request_->fetch_type), - base::Bind(&ServiceWorkerUtils::NoOpStatusCallback)); + int fetch_event_id; + int event_finish_id; + if (timeout_) { + fetch_event_id = version_->StartRequestWithCustomTimeout( + GetEventType(), + base::Bind(&ServiceWorkerFetchDispatcher::DidFailToDispatch, + weak_factory_.GetWeakPtr()), + *timeout_, ServiceWorkerVersion::CONTINUE_ON_TIMEOUT); + event_finish_id = version_->StartRequestWithCustomTimeout( + FetchTypeToWaitUntilEventType(request_->fetch_type), + base::Bind(&ServiceWorkerUtils::NoOpStatusCallback), *timeout_, + ServiceWorkerVersion::CONTINUE_ON_TIMEOUT); + } else { + fetch_event_id = version_->StartRequest( + GetEventType(), + base::Bind(&ServiceWorkerFetchDispatcher::DidFailToDispatch, + weak_factory_.GetWeakPtr())); + event_finish_id = version_->StartRequest( + FetchTypeToWaitUntilEventType(request_->fetch_type), + base::Bind(&ServiceWorkerUtils::NoOpStatusCallback)); + } ResponseCallback* response_callback = new ResponseCallback(weak_factory_.GetWeakPtr(), version_.get());
diff --git a/content/browser/service_worker/service_worker_fetch_dispatcher.h b/content/browser/service_worker/service_worker_fetch_dispatcher.h index c28818e..319f031 100644 --- a/content/browser/service_worker/service_worker_fetch_dispatcher.h +++ b/content/browser/service_worker/service_worker_fetch_dispatcher.h
@@ -10,6 +10,8 @@ #include "base/callback.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "base/time/time.h" #include "content/browser/service_worker/service_worker_metrics.h" #include "content/common/content_export.h" #include "content/common/service_worker/service_worker_event_dispatcher.mojom.h" @@ -41,6 +43,7 @@ std::unique_ptr<ServiceWorkerFetchRequest> request, ServiceWorkerVersion* version, ResourceType resource_type, + const base::Optional<base::TimeDelta>& timeout, const net::NetLogWithSource& net_log, const base::Closure& prepare_callback, const FetchCallback& fetch_callback); @@ -81,6 +84,7 @@ FetchCallback fetch_callback_; std::unique_ptr<ServiceWorkerFetchRequest> request_; ResourceType resource_type_; + base::Optional<base::TimeDelta> timeout_; bool did_complete_; mojom::URLLoaderFactoryPtr url_loader_factory_; std::unique_ptr<mojom::URLLoader> url_loader_;
diff --git a/content/browser/service_worker/service_worker_provider_host.h b/content/browser/service_worker/service_worker_provider_host.h index a53f8a3a75..3cfc6ea 100644 --- a/content/browser/service_worker/service_worker_provider_host.h +++ b/content/browser/service_worker/service_worker_provider_host.h
@@ -262,6 +262,7 @@ void NotifyControllerLost(); private: + friend class ForeignFetchRequestHandlerTest; friend class LinkHeaderServiceWorkerTest; friend class ServiceWorkerProviderHostTestP; friend class ServiceWorkerWriteToCacheJobTest;
diff --git a/content/browser/service_worker/service_worker_storage.h b/content/browser/service_worker/service_worker_storage.h index 95c227dd..d08e5330 100644 --- a/content/browser/service_worker/service_worker_storage.h +++ b/content/browser/service_worker/service_worker_storage.h
@@ -240,6 +240,7 @@ void PurgeResources(const ResourceList& resources); private: + friend class ForeignFetchRequestHandlerTest; friend class ServiceWorkerDispatcherHostTest; friend class ServiceWorkerHandleTest; friend class ServiceWorkerStorageTest;
diff --git a/content/browser/service_worker/service_worker_url_request_job.cc b/content/browser/service_worker/service_worker_url_request_job.cc index 22403a2..ff176f2 100644 --- a/content/browser/service_worker/service_worker_url_request_job.cc +++ b/content/browser/service_worker/service_worker_url_request_job.cc
@@ -234,6 +234,7 @@ RequestContextFrameType frame_type, scoped_refptr<ResourceRequestBodyImpl> body, ServiceWorkerFetchType fetch_type, + const base::Optional<base::TimeDelta>& timeout, Delegate* delegate) : net::URLRequestJob(request, network_delegate), delegate_(delegate), @@ -252,6 +253,7 @@ fall_back_required_(false), body_(body), fetch_type_(fetch_type), + timeout_(timeout), weak_factory_(this) { DCHECK(delegate_) << "ServiceWorkerURLRequestJob requires a delegate"; } @@ -852,7 +854,8 @@ DCHECK(!fetch_dispatcher_); fetch_dispatcher_.reset(new ServiceWorkerFetchDispatcher( - CreateFetchRequest(), active_worker, resource_type_, request()->net_log(), + CreateFetchRequest(), active_worker, resource_type_, timeout_, + request()->net_log(), base::Bind(&ServiceWorkerURLRequestJob::DidPrepareFetchEvent, weak_factory_.GetWeakPtr(), active_worker), base::Bind(&ServiceWorkerURLRequestJob::DidDispatchFetchEvent,
diff --git a/content/browser/service_worker/service_worker_url_request_job.h b/content/browser/service_worker/service_worker_url_request_job.h index 020842dd..42b34d5d 100644 --- a/content/browser/service_worker/service_worker_url_request_job.h +++ b/content/browser/service_worker/service_worker_url_request_job.h
@@ -14,6 +14,7 @@ #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" +#include "base/optional.h" #include "base/time/time.h" #include "content/browser/service_worker/embedded_worker_status.h" #include "content/browser/service_worker/service_worker_metrics.h" @@ -93,6 +94,7 @@ RequestContextFrameType frame_type, scoped_refptr<ResourceRequestBodyImpl> body, ServiceWorkerFetchType fetch_type, + const base::Optional<base::TimeDelta>& timeout, Delegate* delegate); ~ServiceWorkerURLRequestJob() override; @@ -269,6 +271,7 @@ scoped_refptr<ResourceRequestBodyImpl> body_; std::unique_ptr<storage::BlobDataHandle> request_body_blob_data_handle_; ServiceWorkerFetchType fetch_type_; + base::Optional<base::TimeDelta> timeout_; ResponseBodyType response_body_type_ = UNKNOWN; bool did_record_result_ = false;
diff --git a/content/browser/service_worker/service_worker_url_request_job_unittest.cc b/content/browser/service_worker/service_worker_url_request_job_unittest.cc index 88ed182..f6cca34 100644 --- a/content/browser/service_worker/service_worker_url_request_job_unittest.cc +++ b/content/browser/service_worker/service_worker_url_request_job_unittest.cc
@@ -15,6 +15,7 @@ #include "base/memory/ptr_util.h" #include "base/run_loop.h" #include "base/single_thread_task_runner.h" +#include "base/test/simple_test_tick_clock.h" #include "base/threading/thread_task_runner_handle.h" #include "base/time/time.h" #include "content/browser/blob_storage/chrome_blob_storage_context.h" @@ -83,6 +84,9 @@ ~MockHttpProtocolHandler() override {} void set_resource_type(ResourceType type) { resource_type_ = type; } + void set_custom_timeout(base::Optional<base::TimeDelta> timeout) { + custom_timeout_ = timeout; + } net::URLRequestJob* MaybeCreateJob( net::URLRequest* request, @@ -101,7 +105,7 @@ resource_type_, REQUEST_CONTEXT_TYPE_HYPERLINK, REQUEST_CONTEXT_FRAME_TYPE_TOP_LEVEL, scoped_refptr<ResourceRequestBodyImpl>(), ServiceWorkerFetchType::FETCH, - delegate_); + custom_timeout_, delegate_); job_->ForwardToServiceWorker(); return job_; } @@ -114,6 +118,7 @@ mutable ServiceWorkerURLRequestJob* job_; ServiceWorkerURLRequestJob::Delegate* delegate_; ResourceType resource_type_; + base::Optional<base::TimeDelta> custom_timeout_; }; // Returns a BlobProtocolHandler that uses |blob_storage_context|. Caller owns @@ -359,6 +364,19 @@ EXPECT_EQ(std::string(), info->response_cache_storage_cache_name()); } +TEST_P(ServiceWorkerURLRequestJobTestP, CustomTimeout) { + version_->SetStatus(ServiceWorkerVersion::ACTIVATED); + + // Set mock clock on version_ to check timeout behavior. + base::SimpleTestTickClock* tick_clock = new base::SimpleTestTickClock(); + tick_clock->SetNowTicks(base::TimeTicks::Now()); + version_->SetTickClockForTesting(base::WrapUnique(tick_clock)); + + http_protocol_handler_->set_custom_timeout(base::TimeDelta::FromSeconds(5)); + TestRequest(200, "OK", std::string(), true /* expect_valid_ssl */); + EXPECT_EQ(base::TimeDelta::FromSeconds(5), version_->remaining_timeout()); +} + class ProviderDeleteHelper : public EmbeddedWorkerTestHelper { public: ProviderDeleteHelper() : EmbeddedWorkerTestHelper(base::FilePath()) {}
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc index 5e0a3e2..1681357 100644 --- a/content/browser/site_per_process_browsertest.cc +++ b/content/browser/site_per_process_browsertest.cc
@@ -635,8 +635,35 @@ command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures, "FeaturePolicy"); } + + ParsedFeaturePolicy CreateFPHeader(const std::string& feature_name, + const std::vector<GURL>& origins) { + ParsedFeaturePolicy result(1); + result[0].feature_name = feature_name; + result[0].matches_all_origins = false; + DCHECK(!origins.empty()); + for (const GURL& origin : origins) + result[0].origins.push_back(url::Origin(origin)); + return result; + } + + ParsedFeaturePolicy CreateFPHeaderMatchesAll( + const std::string& feature_name) { + ParsedFeaturePolicy result(1); + result[0].feature_name = feature_name; + result[0].matches_all_origins = true; + return result; + } }; +bool operator==(const FeaturePolicyParsedWhitelist& first, + const FeaturePolicyParsedWhitelist& second) { + return std::tie(first.feature_name, first.matches_all_origins, + first.origins) == std::tie(second.feature_name, + second.matches_all_origins, + second.origins); +} + double GetFrameDeviceScaleFactor(const ToRenderFrameHost& adapter) { double device_scale_factor; const char kGetFrameDeviceScaleFactor[] = @@ -8686,19 +8713,19 @@ EXPECT_TRUE(NavigateToURL(shell(), start_url)); FrameTreeNode* root = web_contents()->GetFrameTree()->root(); - EXPECT_EQ("{\"vibrate\":[\"self\"]}", + EXPECT_EQ(CreateFPHeader("vibrate", {start_url.GetOrigin()}), root->current_replication_state().feature_policy_header); // When the main frame navigates to a page with a new policy, it should // overwrite the old one. EXPECT_TRUE(NavigateToURL(shell(), first_nav_url)); - EXPECT_EQ("{\"vibrate\":[\"*\"]}", + EXPECT_EQ(CreateFPHeaderMatchesAll("vibrate"), root->current_replication_state().feature_policy_header); // When the main frame navigates to a page without a policy, the replicated // policy header should be cleared. EXPECT_TRUE(NavigateToURL(shell(), second_nav_url)); - EXPECT_EQ("", root->current_replication_state().feature_policy_header); + EXPECT_TRUE(root->current_replication_state().feature_policy_header.empty()); } IN_PROC_BROWSER_TEST_F(SitePerProcessFeaturePolicyBrowserTest, @@ -8712,19 +8739,19 @@ EXPECT_TRUE(NavigateToURL(shell(), start_url)); FrameTreeNode* root = web_contents()->GetFrameTree()->root(); - EXPECT_EQ("{\"vibrate\":[\"self\"]}", + EXPECT_EQ(CreateFPHeader("vibrate", {start_url.GetOrigin()}), root->current_replication_state().feature_policy_header); // When the main frame navigates to a page with a new policy, it should // overwrite the old one. EXPECT_TRUE(NavigateToURL(shell(), first_nav_url)); - EXPECT_EQ("{\"vibrate\":[\"*\"]}", + EXPECT_EQ(CreateFPHeaderMatchesAll("vibrate"), root->current_replication_state().feature_policy_header); // When the main frame navigates to a page without a policy, the replicated // policy header should be cleared. EXPECT_TRUE(NavigateToURL(shell(), second_nav_url)); - EXPECT_EQ("", root->current_replication_state().feature_policy_header); + EXPECT_TRUE(root->current_replication_state().feature_policy_header.empty()); } // Test that the replicated feature policy header is correct in subframes as @@ -8740,28 +8767,30 @@ EXPECT_TRUE(NavigateToURL(shell(), main_url)); FrameTreeNode* root = web_contents()->GetFrameTree()->root(); - EXPECT_EQ("{\"vibrate\":[\"self\", \"http://example.com/\"]}", + EXPECT_EQ(CreateFPHeader("vibrate", + {main_url.GetOrigin(), GURL("http://example.com/")}), root->current_replication_state().feature_policy_header); EXPECT_EQ(1UL, root->child_count()); EXPECT_EQ( - "{\"vibrate\":[\"self\"]}", + CreateFPHeader("vibrate", {main_url.GetOrigin()}), root->child_at(0)->current_replication_state().feature_policy_header); // Navigate the iframe cross-site. NavigateFrameToURL(root->child_at(0), first_nav_url); EXPECT_EQ( - "{\"vibrate\":[\"*\"]}", + CreateFPHeaderMatchesAll("vibrate"), root->child_at(0)->current_replication_state().feature_policy_header); // Navigate the iframe to another location, this one with no policy header NavigateFrameToURL(root->child_at(0), second_nav_url); - EXPECT_EQ( - "", root->child_at(0)->current_replication_state().feature_policy_header); + EXPECT_TRUE(root->child_at(0) + ->current_replication_state() + .feature_policy_header.empty()); // Navigate the iframe back to a page with a policy NavigateFrameToURL(root->child_at(0), first_nav_url); EXPECT_EQ( - "{\"vibrate\":[\"*\"]}", + CreateFPHeaderMatchesAll("vibrate"), root->child_at(0)->current_replication_state().feature_policy_header); }
diff --git a/content/child/blob_storage/blob_transport_controller.cc b/content/child/blob_storage/blob_transport_controller.cc index 1b01792..5db46a24 100644 --- a/content/child/blob_storage/blob_transport_controller.cc +++ b/content/child/blob_storage/blob_transport_controller.cc
@@ -90,23 +90,27 @@ return true; } -bool WriteSingleRequestToDisk(const BlobConsolidation* consolidation, - const BlobItemBytesRequest& request, - File* file) { +base::Optional<base::Time> WriteSingleRequestToDisk( + const BlobConsolidation* consolidation, + const BlobItemBytesRequest& request, + File* file) { if (!file->IsValid()) - return false; + return base::nullopt; int64_t seek_distance = file->Seek( File::FROM_BEGIN, base::checked_cast<int64_t>(request.handle_offset)); bool seek_failed = seek_distance < 0; UMA_HISTOGRAM_BOOLEAN("Storage.Blob.RendererFileSeekFailed", seek_failed); - if (seek_failed) - return false; + if (seek_failed) { + return base::nullopt; + } BlobConsolidation::ReadStatus status = consolidation->VisitMemory( request.renderer_item_index, request.renderer_item_offset, request.size, base::Bind(&WriteSingleChunk, file)); if (status != BlobConsolidation::ReadStatus::OK) - return false; - return true; + return base::nullopt; + File::Info info; + file->GetInfo(&info); + return base::make_optional(info.last_modified); } base::Optional<std::vector<BlobItemBytesResponse>> WriteDiskRequests( @@ -114,6 +118,8 @@ std::unique_ptr<std::vector<BlobItemBytesRequest>> requests, const std::vector<IPC::PlatformFileForTransit>& file_handles) { std::vector<BlobItemBytesResponse> responses; + std::vector<base::Time> last_modified_times; + last_modified_times.resize(file_handles.size()); // We grab ownership of the file handles here. When this vector is destroyed // it will close the files. std::vector<File> files; @@ -122,24 +128,13 @@ files.emplace_back(IPC::PlatformFileForTransitToFile(file_handle)); } for (const auto& request : *requests) { - if (!WriteSingleRequestToDisk(consolidation.get(), request, - &files[request.handle_index])) { + base::Optional<base::Time> last_modified = WriteSingleRequestToDisk( + consolidation.get(), request, &files[request.handle_index]); + if (!last_modified) { return base::nullopt; } + last_modified_times[request.handle_index] = last_modified.value(); } - // The last modified time needs to be collected after we flush the file. - std::vector<base::Time> last_modified_times; - last_modified_times.resize(file_handles.size()); - for (size_t i = 0; i < files.size(); ++i) { - auto& file = files[i]; - if (!file.Flush()) - return base::nullopt; - File::Info info; - if (!file.GetInfo(&info)) - return base::nullopt; - last_modified_times[i] = info.last_modified; - } - for (const auto& request : *requests) { responses.push_back(BlobItemBytesResponse(request.request_number)); responses.back().time_file_modified =
diff --git a/content/common/frame_messages.h b/content/common/frame_messages.h index 63aadc0..a925cb1 100644 --- a/content/common/frame_messages.h +++ b/content/common/frame_messages.h
@@ -392,6 +392,12 @@ #endif IPC_STRUCT_TRAITS_END() +IPC_STRUCT_TRAITS_BEGIN(content::FeaturePolicyParsedWhitelist) + IPC_STRUCT_TRAITS_MEMBER(feature_name) + IPC_STRUCT_TRAITS_MEMBER(matches_all_origins) + IPC_STRUCT_TRAITS_MEMBER(origins) +IPC_STRUCT_TRAITS_END() + IPC_STRUCT_TRAITS_BEGIN(content::FrameReplicationState) IPC_STRUCT_TRAITS_MEMBER(origin) IPC_STRUCT_TRAITS_MEMBER(sandbox_flags) @@ -980,8 +986,10 @@ std::string /* unique_name */) // Notifies the browser process that a non-empty Feature-Policy HTTP header was -// delivered with the document being loaded into the frame. -IPC_MESSAGE_ROUTED1(FrameHostMsg_DidSetFeaturePolicyHeader, std::string) +// delivered with the document being loaded into the frame. |parsed_header| is +// a list of an origin whitelist for each feature in the policy. +IPC_MESSAGE_ROUTED1(FrameHostMsg_DidSetFeaturePolicyHeader, + content::ParsedFeaturePolicy /* parsed_header */) // Notifies the browser process about a new Content Security Policy that needs // to be applies to the frame. This message is sent when a frame commits
diff --git a/content/common/frame_replication_state.cc b/content/common/frame_replication_state.cc index 239c9ab..a726525 100644 --- a/content/common/frame_replication_state.cc +++ b/content/common/frame_replication_state.cc
@@ -3,11 +3,20 @@ // found in the LICENSE file. #include "content/common/frame_replication_state.h" + #include "third_party/WebKit/public/web/WebSandboxFlags.h" #include "third_party/WebKit/public/web/WebTreeScopeType.h" namespace content { +FeaturePolicyParsedWhitelist::FeaturePolicyParsedWhitelist() + : matches_all_origins(false) {} + +FeaturePolicyParsedWhitelist::FeaturePolicyParsedWhitelist( + const FeaturePolicyParsedWhitelist& fppw) = default; + +FeaturePolicyParsedWhitelist::~FeaturePolicyParsedWhitelist() {} + FrameReplicationState::FrameReplicationState() : sandbox_flags(blink::WebSandboxFlags::None), scope(blink::WebTreeScopeType::Document),
diff --git a/content/common/frame_replication_state.h b/content/common/frame_replication_state.h index cc8d4780..291287f6 100644 --- a/content/common/frame_replication_state.h +++ b/content/common/frame_replication_state.h
@@ -20,6 +20,21 @@ namespace content { +// This struct holds feature policy whitelist data that needs to be replicated +// between a RenderFrame and any of its associated RenderFrameProxies. A list of +// these form part of the FrameReplicationState below (one entry per feature). +struct CONTENT_EXPORT FeaturePolicyParsedWhitelist { + FeaturePolicyParsedWhitelist(); + FeaturePolicyParsedWhitelist(const FeaturePolicyParsedWhitelist& fppw); + ~FeaturePolicyParsedWhitelist(); + + std::string feature_name; + bool matches_all_origins; + std::vector<url::Origin> origins; +}; + +using ParsedFeaturePolicy = std::vector<FeaturePolicyParsedWhitelist>; + // This structure holds information that needs to be replicated between a // RenderFrame and any of its associated RenderFrameProxies. struct CONTENT_EXPORT FrameReplicationState { @@ -84,9 +99,9 @@ // scratch. std::string unique_name; - // Feature policy header. May be empty if no header was sent with the + // Parsed feature policy header. May be empty if no header was sent with the // document. - std::string feature_policy_header; + ParsedFeaturePolicy feature_policy_header; // Accumulated CSP headers - gathered from http headers, <meta> elements, // parent frames (in case of about:blank frames).
diff --git a/content/renderer/pepper/pepper_graphics_2d_host.cc b/content/renderer/pepper/pepper_graphics_2d_host.cc index c6cb580..23137b0 100644 --- a/content/renderer/pepper/pepper_graphics_2d_host.cc +++ b/content/renderer/pepper/pepper_graphics_2d_host.cc
@@ -346,7 +346,7 @@ SkAutoCanvasRestore auto_restore(canvas, true); SkRect image_data_rect = gfx::RectToSkRect(gfx::Rect(plugin_rect.origin(), image_size)); - canvas->clipRect(image_data_rect, SkRegion::kDifference_Op); + canvas->clipRect(image_data_rect, kDifference_SkClipOp); SkPaint paint; paint.setBlendMode(SkBlendMode::kSrc);
diff --git a/content/renderer/pepper/video_decoder_shim.cc b/content/renderer/pepper/video_decoder_shim.cc index 9d731b30..6af89b6 100644 --- a/content/renderer/pepper/video_decoder_shim.cc +++ b/content/renderer/pepper/video_decoder_shim.cc
@@ -869,7 +869,7 @@ DCHECK(RenderThreadImpl::current()); DCHECK_EQ(state_, UNINITIALIZED); - if (vda_config.is_encrypted) { + if (vda_config.is_encrypted()) { NOTREACHED() << "Encrypted streams are not supported"; return false; }
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc index f68bb2e5..50c14548 100644 --- a/content/renderer/render_frame_impl.cc +++ b/content/renderer/render_frame_impl.cc
@@ -881,6 +881,21 @@ return (time_ticks - base::TimeTicks()).InSecondsF(); } +ParsedFeaturePolicy ToParsedFeaturePolicy( + const blink::WebParsedFeaturePolicy& web_parsed_whitelists) { + ParsedFeaturePolicy result; + for (const blink::WebFeaturePolicy::ParsedWhitelist& web_whitelist : + web_parsed_whitelists) { + FeaturePolicyParsedWhitelist whitelist; + whitelist.feature_name = web_whitelist.featureName.utf8(); + whitelist.matches_all_origins = web_whitelist.matchesAllOrigins; + for (const blink::WebSecurityOrigin& web_origin : web_whitelist.origins) + whitelist.origins.push_back(web_origin); + result.push_back(whitelist); + } + return result; +} + } // namespace struct RenderFrameImpl::PendingFileChooser { @@ -3135,9 +3150,9 @@ } void RenderFrameImpl::didSetFeaturePolicyHeader( - const blink::WebString& header_value) { - Send(new FrameHostMsg_DidSetFeaturePolicyHeader(routing_id_, - header_value.utf8())); + const blink::WebParsedFeaturePolicy& parsed_header) { + Send(new FrameHostMsg_DidSetFeaturePolicyHeader( + routing_id_, ToParsedFeaturePolicy(parsed_header))); } void RenderFrameImpl::didAddContentSecurityPolicy(
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h index e45410b1..0f30095 100644 --- a/content/renderer/render_frame_impl.h +++ b/content/renderer/render_frame_impl.h
@@ -51,6 +51,7 @@ #include "services/service_manager/public/interfaces/connector.mojom.h" #include "services/service_manager/public/interfaces/interface_provider.mojom.h" #include "third_party/WebKit/public/platform/WebEffectiveConnectionType.h" +#include "third_party/WebKit/public/platform/WebFeaturePolicy.h" #include "third_party/WebKit/public/platform/WebFocusType.h" #include "third_party/WebKit/public/platform/WebLoadingBehaviorFlag.h" #include "third_party/WebKit/public/platform/WebMediaPlayer.h" @@ -487,7 +488,8 @@ bool is_potentially_trustworthy_unique_origin) override; void didChangeSandboxFlags(blink::WebFrame* child_frame, blink::WebSandboxFlags flags) override; - void didSetFeaturePolicyHeader(const blink::WebString& header_value) override; + void didSetFeaturePolicyHeader( + const blink::WebParsedFeaturePolicy& parsed_header) override; void didAddContentSecurityPolicy( const blink::WebString& header_value, blink::WebContentSecurityPolicyType type,
diff --git a/content/renderer/render_frame_proxy.cc b/content/renderer/render_frame_proxy.cc index 9131d4c..aedf2fa4 100644 --- a/content/renderer/render_frame_proxy.cc +++ b/content/renderer/render_frame_proxy.cc
@@ -29,6 +29,7 @@ #include "content/renderer/render_widget.h" #include "ipc/ipc_message_macros.h" #include "third_party/WebKit/public/platform/URLConversion.h" +#include "third_party/WebKit/public/platform/WebFeaturePolicy.h" #include "third_party/WebKit/public/platform/WebString.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" #include "third_party/WebKit/public/web/WebUserGestureIndicator.h" @@ -47,6 +48,22 @@ typedef std::map<blink::WebFrame*, RenderFrameProxy*> FrameMap; base::LazyInstance<FrameMap> g_frame_map = LAZY_INSTANCE_INITIALIZER; +blink::WebParsedFeaturePolicy ToWebParsedFeaturePolicy( + const ParsedFeaturePolicy& parsed_whitelists) { + std::vector<blink::WebFeaturePolicy::ParsedWhitelist> result; + for (const FeaturePolicyParsedWhitelist& whitelist : parsed_whitelists) { + blink::WebFeaturePolicy::ParsedWhitelist web_whitelist; + web_whitelist.featureName = + blink::WebString::fromUTF8(whitelist.feature_name); + web_whitelist.matchesAllOrigins = whitelist.matches_all_origins; + std::vector<blink::WebSecurityOrigin> web_origins; + for (const url::Origin& origin : whitelist.origins) + web_origins.push_back(origin); + result.push_back(web_whitelist); + } + return result; +} + } // namespace // static @@ -224,7 +241,7 @@ web_frame_->setReplicatedPotentiallyTrustworthyUniqueOrigin( state.has_potentially_trustworthy_unique_origin); web_frame_->setReplicatedFeaturePolicyHeader( - blink::WebString::fromUTF8(state.feature_policy_header)); + ToWebParsedFeaturePolicy(state.feature_policy_header)); web_frame_->resetReplicatedContentSecurityPolicy(); for (const auto& header : state.accumulated_csp_headers)
diff --git a/content/renderer/skia_benchmarking_extension_unittest.cc b/content/renderer/skia_benchmarking_extension_unittest.cc index 87ea6a0..b7519f1 100644 --- a/content/renderer/skia_benchmarking_extension_unittest.cc +++ b/content/renderer/skia_benchmarking_extension_unittest.cc
@@ -53,7 +53,7 @@ // Draw a trivial scene. benchmarking_canvas.save(); - benchmarking_canvas.clipRect(fullRect, SkRegion::kIntersect_Op, false); + benchmarking_canvas.clipRect(fullRect); benchmarking_canvas.setMatrix(trans); benchmarking_canvas.drawRect(fillRect, red_paint); benchmarking_canvas.restore();
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn index 2dc8a3c..0550e19 100644 --- a/content/test/BUILD.gn +++ b/content/test/BUILD.gn
@@ -547,7 +547,6 @@ "../browser/background_sync/background_sync_browsertest.cc", "../browser/battery_status/battery_monitor_impl_browsertest.cc", "../browser/battery_status/battery_monitor_integration_browsertest.cc", - "../browser/blob_storage/blob_storage_browsertest.cc", "../browser/blob_storage/blob_url_browsertest.cc", "../browser/bookmarklet_browsertest.cc", "../browser/browser_side_navigation_browsertest.cc",
diff --git a/content/test/data/blob_storage/blob_creation_and_slicing.html b/content/test/data/blob_storage/blob_creation_and_slicing.html deleted file mode 100644 index e3f36c0..0000000 --- a/content/test/data/blob_storage/blob_creation_and_slicing.html +++ /dev/null
@@ -1,134 +0,0 @@ -<html> - <head> - <title>Blob Creation & Slicing</title> - <script type="text/javascript" src="common.js"></script> - <script type="text/javascript"> -// We create < 3000 bytes of data, as that is the max for the browsertest. -var MAX_BYTES = 3000; - -// Number of blobs constructed with raw data. -var NUM_RAW_BLOBS = 100; -// Number of blobs we create by slicing the raw data blobs. -var NUM_SLICES = 100; -// Number of blobs we create by sandwiching sliced blobs in between new data. -var NUM_SANDWICHES = 100; -var totalSize = 0; - -var generatePsuedoRandomUint8Array = function(length) { - var array = new Uint8Array(length); - for (let i = 0; i < length; ++i) { - array[i] = (17 + 23 * i) & 0xFF; - } - return array; -} - -var test = function() { - var blobs = []; - - // This should cause us to go straight to file. - var savedToDiskDataSize = 190; - var savedToDiskData = generatePsuedoRandomUint8Array(savedToDiskDataSize); - - // This should require multiple shared memory segments. - var sharedMemoryDataSize = 15; - var sharedMemoryData = generatePsuedoRandomUint8Array(sharedMemoryDataSize); - - var sandwichDataSize = 7; - - // This should fit in IPC. - var ipcDataSize = 2; - var ipcData = generatePsuedoRandomUint8Array(ipcDataSize); - - var expectedBlobData = []; - for (let i = 0; i < NUM_RAW_BLOBS; ++i) { - let data = []; - if (i % 5 == 0) { - data.push(sharedMemoryData); - } else if (i % 13 == 0) { - data.push(savedToDiskData); - } else { - data.push(ipcData); - } - expectedBlobData.push(data[data.length - 1]); - let blob = new Blob(data, { content_type: "text/plain" }); - blobs.push(blob); - totalSize += blob.size; - } - - for (let i = 0; i < NUM_SLICES; ++i) { - let origSize; - let origData; - let rawIndex = i % NUM_RAW_BLOBS; - if (rawIndex % 5 == 0) { - origSize = sharedMemoryDataSize; - origData = sharedMemoryData; - } else if (rawIndex % 13 == 0) { - origSize = savedToDiskDataSize; - origData = savedToDiskData; - } else { - origSize = ipcDataSize; - origData = ipcData; - } - let blob; - let expectedData; - if (i % 2 == 0) { - blob = blobs[i].slice(origSize / 2); - expectedData = origData.slice(origSize / 2); - } else { - blob = blobs[i].slice(0, origSize / 2); - expectedData = origData.slice(0, origSize / 2); - } - expectedBlobData.push(expectedData); - blobs.push(blob); - } - - var getBytes = function(string) { - var bytes = []; - for (let i = 0; i < string.length; ++i) { - bytes.push(string.charCodeAt(i)); - } - return bytes; - } - - for (let i = 0; i < NUM_SANDWICHES; ++i) { - let sliceIndex = NUM_RAW_BLOBS + (i % NUM_SLICES); - let slicedDataSize = expectedBlobData[sliceIndex].length; - blobs.push(new Blob(['pre', blobs[sliceIndex], 'post'], { content_type: "text/plain" })); - totalSize += sandwichDataSize; - - let expectedData = new Uint8Array(sandwichDataSize + slicedDataSize); - expectedData.set(getBytes("pre"), 0); - expectedData.set(expectedBlobData[sliceIndex], 3); - expectedData.set(getBytes("post"), 3 + slicedDataSize); - expectedBlobData.push(expectedData); - } - shouldBeTrue("totalSize <= MAX_BYTES"); - - var numRead = 0; - for (let i = 0; i < blobs.length; i++) { - (function(index, d) { - var reader = new FileReader(); - reader.onloadend = function(e) { - if (reader.error) { - fail('Error when reading blob ' + index + ': ' + reader.error); - return; - } - numRead++; - debug('Finished reading blob ' + index); - shouldBe('event.target.result.byteLength', d.length + ''); - shouldBe('new Uint8Array(event.target.result)', - '[' + d + ']'); - if (numRead >= blobs.length) { - done('Done!'); - } - } - return reader; - })(i, expectedBlobData[i]).readAsArrayBuffer(blobs[i]); - } -}; - </script> - </head> - <body onLoad="test()"> - <div id="status">Starting...<br/></div> - </body> -</html>
diff --git a/content/test/data/blob_storage/common.js b/content/test/data/blob_storage/common.js deleted file mode 100644 index e9b3cc6a..0000000 --- a/content/test/data/blob_storage/common.js +++ /dev/null
@@ -1,105 +0,0 @@ -// Copyright 2016 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. - -function debug(message) { - var span = document.createElement("span"); - span.appendChild(document.createTextNode(message)); - span.appendChild(document.createElement("br")); - document.getElementById('status').appendChild(span); -} - -function done(message) { - if (document.location.hash == '#fail') - return; - if (message) - debug('PASS: ' + message); - else - debug('PASS'); - document.location.hash = '#pass'; -} - -function fail(message) { - debug('FAILED: ' + message); - document.location.hash = '#fail'; -} - -function getLog() { - return "" + document.getElementById('status').innerHTML; -} - -// The following functions are based on -// WebKit/LayoutTests/fast/js/resources/js-test-pre.js -// so that the tests will look similar to the existing layout tests. -function stringify(v) { - if (v === 0 && 1/v < 0) - return "-0"; - else return "" + v; -} - -function areArraysEqual(a, b) { - try { - if (a.length !== b.length) - return false; - for (var i = 0; i < a.length; i++) { - if (a[i] !== b[i]) - return false; - } - } catch (ex) { - return false; - } - return true; -} - -function isResultCorrect(_actual, _expected) -{ - if (_expected === 0) - return _actual === _expected && (1/_actual) === (1/_expected); - if (_actual === _expected) - return true; - if (typeof(_expected) == "number" && isNaN(_expected)) - return typeof(_actual) == "number" && isNaN(_actual); - if (Object.prototype.toString.call(_expected) == - Object.prototype.toString.call([])) - return areArraysEqual(_actual, _expected); - return false; -} - -function shouldBe(_a, _b) -{ - if (typeof _a != "string" || typeof _b != "string") - debug("WARN: shouldBe() expects string arguments"); - var exception; - var _av; - try { - _av = eval(_a); - } catch (e) { - exception = e; - } - var _bv = eval(_b); - - if (exception) - fail(_a + " should be " + _bv + ". Threw exception " + exception); - else if (isResultCorrect(_av, _bv)) - debug(_a + " is " + _b); - else if (typeof(_av) == typeof(_bv)) - fail(_a + " should be " + _bv + ". Was " + stringify(_av) + "."); - else - fail(_a + " should be " + _bv + " (of type " + typeof _bv + "). " + - "Was " + _av + " (of type " + typeof _av + ")."); -} - -function shouldBeTrue(_a) { shouldBe(_a, "true"); } -function shouldBeFalse(_a) { shouldBe(_a, "false"); } -function shouldBeNaN(_a) { shouldBe(_a, "NaN"); } -function shouldBeNull(_a) { shouldBe(_a, "null"); } -function shouldBeEqualToString(a, b) { - var unevaledString = '"' + b.replace(/\\/g, "\\\\").replace(/"/g, "\"") + '"'; - shouldBe(a, unevaledString); -} - -if (typeof String.prototype.startsWith !== 'function') { - String.prototype.startsWith = function (str) { - return this.indexOf(str) === 0; - }; -} \ No newline at end of file
diff --git a/content/test/data/media/depth_stream_test_utilities.js b/content/test/data/media/depth_stream_test_utilities.js index c48df6d..44c51c72 100644 --- a/content/test/data/media/depth_stream_test_utilities.js +++ b/content/test/data/media/depth_stream_test_utilities.js
@@ -61,7 +61,7 @@ data[i + 1] + ", " + data[i + 2] + " differ at index " + i); } - var calculated = (data[0] + + var calculated = (data[0] + wrap_around + step * ((flip_y ? -row : row) + column)) % wrap_around; if (Math.abs(calculated - data[i]) > tolerance) { return Promise.reject(test_name + ": reference value " + data[i] +
diff --git a/content/test/gpu/generate_buildbot_json.py b/content/test/gpu/generate_buildbot_json.py index 9e51c1d..3e6b0f6 100755 --- a/content/test/gpu/generate_buildbot_json.py +++ b/content/test/gpu/generate_buildbot_json.py
@@ -1220,6 +1220,20 @@ '--use-gl=angle', ], }, + 'webgl_conformance_d3d11_passthrough': { + 'tester_configs': [ + { + 'fyi_only': True, + 'os_types': ['win'], + 'run_on_optional': True, + } + ], + 'target_name': 'webgl_conformance', + 'extra_browser_args': [ + '--use-angle=d3d11', + '--use-passthrough-cmd-decoder', + ], + }, 'webgl2_conformance_tests': { 'tester_configs': [ {
diff --git a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py index 46e5e20..92b9b11 100644 --- a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py +++ b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
@@ -43,12 +43,13 @@ # All platforms. self.Flaky('conformance2/query/occlusion-query.html', bug=603168) self.Fail('conformance2/glsl3/tricky-loop-conditions.html', bug=483282) + self.Fail('conformance2/rendering/depth-stencil-feedback-loop.html', + bug=660844) # WebGL 2.0.1 self.Fail('conformance2/rendering/rendering-sampling-feedback-loop.html', bug=660844) self.Fail('conformance2/textures/misc/' + 'integer-cubemap-specification-order-bug.html', bug=483282) # owner:cwallez, test might be buggy - # Windows only. self.Fail('conformance2/textures/misc/tex-srgb-mipmap.html', ['win'], bug=634519)
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc index 82d78839..eea4e8c 100644 --- a/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc +++ b/gpu/command_buffer/service/gles2_cmd_decoder_passthrough.cc
@@ -171,9 +171,9 @@ // Check for required extensions if (!feature_info_->feature_flags().angle_robust_client_memory || - !feature_info_->feature_flags().chromium_bind_generates_resource || - (feature_info_->IsWebGLContext() != - feature_info_->feature_flags().angle_webgl_compatibility)) { + !feature_info_->feature_flags().chromium_bind_generates_resource) { + // TODO(geofflang): Verify that ANGLE_webgl_compatibility is enabled if this + // is a WebGL context (depends on crbug.com/671217). Destroy(true); return false; }
diff --git a/ios/chrome/browser/physical_web/physical_web_prefs_registration.cc b/ios/chrome/browser/physical_web/physical_web_prefs_registration.cc index 8f94b9b6..7490366 100644 --- a/ios/chrome/browser/physical_web/physical_web_prefs_registration.cc +++ b/ios/chrome/browser/physical_web/physical_web_prefs_registration.cc
@@ -5,6 +5,7 @@ #include "ios/chrome/browser/physical_web/physical_web_prefs_registration.h" #include "components/pref_registry/pref_registry_syncable.h" +#include "components/prefs/pref_registry_simple.h" #include "ios/chrome/browser/physical_web/physical_web_constants.h" #include "ios/chrome/browser/pref_names.h" @@ -14,3 +15,8 @@ prefs::kIosPhysicalWebEnabled, physical_web::kPhysicalWebOnboarding, user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); } + +void RegisterPhysicalWebLocalStatePrefs(PrefRegistrySimple* registry) { + registry->RegisterIntegerPref(prefs::kIosPhysicalWebEnabled, + physical_web::kPhysicalWebOnboarding); +}
diff --git a/ios/chrome/browser/physical_web/physical_web_prefs_registration.h b/ios/chrome/browser/physical_web/physical_web_prefs_registration.h index c18f4a8c..bccfa9c 100644 --- a/ios/chrome/browser/physical_web/physical_web_prefs_registration.h +++ b/ios/chrome/browser/physical_web/physical_web_prefs_registration.h
@@ -5,12 +5,16 @@ #ifndef IOS_CHROME_BROWSER_PHYSICAL_WEB_PHYSICAL_WEB_PREFS_REGISTRATION_H_ #define IOS_CHROME_BROWSER_PHYSICAL_WEB_PHYSICAL_WEB_PREFS_REGISTRATION_H_ +class PrefRegistrySimple; namespace user_prefs { class PrefRegistrySyncable; } -// Registers the prefs needed by physical web. +// Registers browser state prefs needed by the Physical Web. void RegisterPhysicalWebBrowserStatePrefs( user_prefs::PrefRegistrySyncable* registry); +// Registers local state prefs needed by the Physical Web. +void RegisterPhysicalWebLocalStatePrefs(PrefRegistrySimple* registry); + #endif // IOS_CHROME_BROWSER_PHYSICAL_WEB_PHYSICAL_WEB_PREFS_REGISTRATION_H_
diff --git a/ios/chrome/browser/prefs/BUILD.gn b/ios/chrome/browser/prefs/BUILD.gn index 5f17600..459855a6 100644 --- a/ios/chrome/browser/prefs/BUILD.gn +++ b/ios/chrome/browser/prefs/BUILD.gn
@@ -49,6 +49,7 @@ "//ios/chrome/browser/memory", "//ios/chrome/browser/metrics", "//ios/chrome/browser/net", + "//ios/chrome/browser/physical_web", "//ios/chrome/browser/reading_list", "//ios/chrome/browser/signin", "//ios/chrome/browser/voice:prefs",
diff --git a/ios/chrome/browser/prefs/browser_prefs.mm b/ios/chrome/browser/prefs/browser_prefs.mm index 5bacf8d9..57f18dfb 100644 --- a/ios/chrome/browser/prefs/browser_prefs.mm +++ b/ios/chrome/browser/prefs/browser_prefs.mm
@@ -40,6 +40,7 @@ #import "ios/chrome/browser/metrics/ios_chrome_metrics_service_client.h" #include "ios/chrome/browser/net/http_server_properties_manager_factory.h" #include "ios/chrome/browser/notification_promo.h" +#include "ios/chrome/browser/physical_web/physical_web_prefs_registration.h" #include "ios/chrome/browser/pref_names.h" #include "ios/chrome/browser/signin/signin_manager_factory.h" #include "ios/chrome/browser/voice/voice_search_prefs_registration.h" @@ -59,6 +60,7 @@ ssl_config::SSLConfigServiceManager::RegisterPrefs(registry); update_client::RegisterPrefs(registry); variations::VariationsService::RegisterPrefs(registry); + RegisterPhysicalWebLocalStatePrefs(registry); // Preferences related to the browser state manager. registry->RegisterStringPref(prefs::kBrowserStateLastUsed, std::string());
diff --git a/media/base/android/java/src/org/chromium/media/MediaCodecBridge.java b/media/base/android/java/src/org/chromium/media/MediaCodecBridge.java index 4b3dce08..d33bfa43 100644 --- a/media/base/android/java/src/org/chromium/media/MediaCodecBridge.java +++ b/media/base/android/java/src/org/chromium/media/MediaCodecBridge.java
@@ -7,6 +7,7 @@ import android.annotation.TargetApi; import android.media.AudioFormat; import android.media.MediaCodec; +import android.media.MediaCodec.CryptoInfo; import android.media.MediaCrypto; import android.media.MediaFormat; import android.os.Build; @@ -54,6 +55,14 @@ // We use only one output audio format (PCM16) that has 2 bytes per sample private static final int PCM16_BYTES_PER_SAMPLE = 2; + // The following values should be kept in sync with the media::EncryptionScheme::CipherMode + // enum in media/base/encryption_scheme.h + private static final int MEDIA_ENCRYPTION_SCHEME_CIPHER_MODE_UNENCRYPTED = 0; + private static final int MEDIA_ENCRYPTION_SCHEME_CIPHER_MODE_AES_CTR = 1; + private static final int MEDIA_ENCRYPTION_SCHEME_CIPHER_MODE_AES_CBC = 2; + + private static final int MEDIA_CODEC_UNKNOWN_CIPHER_MODE = -1; + // TODO(qinmin): Use MediaFormat constants when part of the public API. private static final String KEY_CROP_LEFT = "crop-left"; private static final String KEY_CROP_RIGHT = "crop-right"; @@ -401,15 +410,50 @@ } } + // Incoming |native| values are as defined in media/base/encryption_scheme.h. Translated values + // are from MediaCodec. At present, these values are in sync. Returns + // MEDIA_CODEC_UNKNOWN_CIPHER_MODE in the case of unknown incoming value. + private int translateCipherModeValue(int nativeValue) { + switch (nativeValue) { + case MEDIA_ENCRYPTION_SCHEME_CIPHER_MODE_UNENCRYPTED: + return MediaCodec.CRYPTO_MODE_UNENCRYPTED; + case MEDIA_ENCRYPTION_SCHEME_CIPHER_MODE_AES_CTR: + return MediaCodec.CRYPTO_MODE_AES_CTR; + case MEDIA_ENCRYPTION_SCHEME_CIPHER_MODE_AES_CBC: + return MediaCodec.CRYPTO_MODE_AES_CBC; + default: + Log.e(TAG, "Unsupported cipher mode: " + nativeValue); + return MEDIA_CODEC_UNKNOWN_CIPHER_MODE; + } + } + @CalledByNative - private int queueSecureInputBuffer( - int index, int offset, byte[] iv, byte[] keyId, int[] numBytesOfClearData, - int[] numBytesOfEncryptedData, int numSubSamples, long presentationTimeUs) { + private int queueSecureInputBuffer(int index, int offset, byte[] iv, byte[] keyId, + int[] numBytesOfClearData, int[] numBytesOfEncryptedData, int numSubSamples, + int cipherMode, int patternEncrypt, int patternSkip, long presentationTimeUs) { resetLastPresentationTimeIfNeeded(presentationTimeUs); try { - MediaCodec.CryptoInfo cryptoInfo = new MediaCodec.CryptoInfo(); - cryptoInfo.set(numSubSamples, numBytesOfClearData, numBytesOfEncryptedData, - keyId, iv, MediaCodec.CRYPTO_MODE_AES_CTR); + cipherMode = translateCipherModeValue(cipherMode); + if (cipherMode == MEDIA_CODEC_UNKNOWN_CIPHER_MODE) { + return MEDIA_CODEC_ERROR; + } + boolean usesCbcs = cipherMode == MediaCodec.CRYPTO_MODE_AES_CBC; + if (usesCbcs && !MediaCodecUtil.platformSupportsCbcsEncryption()) { + Log.e(TAG, "Encryption scheme 'cbcs' not supported on this platform."); + return MEDIA_CODEC_ERROR; + } + CryptoInfo cryptoInfo = new CryptoInfo(); + cryptoInfo.set(numSubSamples, numBytesOfClearData, numBytesOfEncryptedData, keyId, iv, + cipherMode); + if (patternEncrypt != 0 && patternSkip != 0) { + if (usesCbcs) { + // Above platform check ensured that setting the pattern is indeed supported. + MediaCodecUtil.setPatternIfSupported(cryptoInfo, patternEncrypt, patternSkip); + } else { + Log.e(TAG, "Pattern encryption only supported for 'cbcs' scheme (CBC mode)."); + return MEDIA_CODEC_ERROR; + } + } mMediaCodec.queueSecureInputBuffer(index, offset, cryptoInfo, presentationTimeUs, 0); } catch (MediaCodec.CryptoException e) { if (e.getErrorCode() == MediaCodec.CryptoException.ERROR_NO_KEY) {
diff --git a/media/base/android/java/src/org/chromium/media/MediaCodecUtil.java b/media/base/android/java/src/org/chromium/media/MediaCodecUtil.java index 5bec035..e16cef8 100644 --- a/media/base/android/java/src/org/chromium/media/MediaCodecUtil.java +++ b/media/base/android/java/src/org/chromium/media/MediaCodecUtil.java
@@ -6,6 +6,7 @@ import android.annotation.TargetApi; import android.media.MediaCodec; +import android.media.MediaCodec.CryptoInfo; import android.media.MediaCodecInfo; import android.media.MediaCodecList; import android.os.Build; @@ -514,4 +515,27 @@ Log.w(TAG, "HW encoder for " + mime + " is not available on this device."); return null; } + + /** + * Returns true if and only if the platform we are running on supports the 'cbcs' + * encryption scheme, specifically AES CBC encryption with possibility of pattern + * encryption. + * While 'cbcs' scheme was originally implemented in N, there was a bug (in the + * DRM code) which means that it didn't really work properly until post-N). + */ + static boolean platformSupportsCbcsEncryption() { + return Build.VERSION.SDK_INT > Build.VERSION_CODES.N; + } + + /** + * Sets the encryption pattern value if and only if CryptoInfo.setPattern method is + * supported. + * This method was introduced in Android N. Note that if platformSupportsCbcsEncryption + * returns true, then this function will set the pattern. + */ + static void setPatternIfSupported(CryptoInfo cryptoInfo, int encrypt, int skip) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + cryptoInfo.setPattern(new CryptoInfo.Pattern(encrypt, skip)); + } + } }
diff --git a/media/base/android/media_codec_bridge.cc b/media/base/android/media_codec_bridge.cc index de8bac3..0231c007 100644 --- a/media/base/android/media_codec_bridge.cc +++ b/media/base/android/media_codec_bridge.cc
@@ -24,12 +24,14 @@ const std::string& key_id, const std::string& iv, const std::vector<SubsampleEntry>& subsamples, + const EncryptionScheme& encryption_scheme, base::TimeDelta presentation_time) { const std::vector<char> key_vec(key_id.begin(), key_id.end()); const std::vector<char> iv_vec(iv.begin(), iv.end()); return QueueSecureInputBuffer(index, data, data_size, key_vec, iv_vec, subsamples.empty() ? nullptr : &subsamples[0], - (int)subsamples.size(), presentation_time); + (int)subsamples.size(), encryption_scheme, + presentation_time); } MediaCodecStatus MediaCodecBridge::CopyFromOutputBuffer(int index,
diff --git a/media/base/android/media_codec_bridge.h b/media/base/android/media_codec_bridge.h index d7377366..dd31591 100644 --- a/media/base/android/media_codec_bridge.h +++ b/media/base/android/media_codec_bridge.h
@@ -21,6 +21,7 @@ namespace media { +class EncryptionScheme; struct SubsampleEntry; // These must be in sync with MediaCodecBridge.MEDIA_CODEC_XXX constants in @@ -101,6 +102,7 @@ const std::string& key_id, const std::string& iv, const std::vector<SubsampleEntry>& subsamples, + const EncryptionScheme& encryption_scheme, base::TimeDelta presentation_time); // Same QueueSecureInputBuffer overriden for the use with @@ -115,6 +117,7 @@ const std::vector<char>& iv, const SubsampleEntry* subsamples, int subsamples_size, + const EncryptionScheme& encryption_scheme, base::TimeDelta presentation_time) = 0; // Submits an empty buffer with a EOS (END OF STREAM) flag.
diff --git a/media/base/android/media_codec_loop.cc b/media/base/android/media_codec_loop.cc index 43acf0af..ff2d04eb 100644 --- a/media/base/android/media_codec_loop.cc +++ b/media/base/android/media_codec_loop.cc
@@ -39,7 +39,7 @@ subsamples(other.subsamples), presentation_time(other.presentation_time), is_eos(other.is_eos), - is_encrypted(other.is_encrypted) {} + encryption_scheme(other.encryption_scheme) {} MediaCodecLoop::InputData::~InputData() {} @@ -199,14 +199,14 @@ media::MediaCodecStatus status = MEDIA_CODEC_OK; - if (input_data.is_encrypted) { + if (input_data.encryption_scheme.is_encrypted()) { // Note that input_data might not have a valid memory ptr if this is a // re-send of a buffer that was sent before decryption keys arrived. status = media_codec_->QueueSecureInputBuffer( input_buffer.index, input_data.memory, input_data.length, input_data.key_id, input_data.iv, input_data.subsamples, - input_data.presentation_time); + input_data.encryption_scheme, input_data.presentation_time); } else { status = media_codec_->QueueInputBuffer(
diff --git a/media/base/android/media_codec_loop.h b/media/base/android/media_codec_loop.h index 6413698..2ee738c 100644 --- a/media/base/android/media_codec_loop.h +++ b/media/base/android/media_codec_loop.h
@@ -17,6 +17,7 @@ #include "base/timer/timer.h" #include "media/base/android/media_codec_bridge.h" #include "media/base/decode_status.h" +#include "media/base/encryption_scheme.h" #include "media/base/media_export.h" #include "media/base/subsample_entry.h" @@ -128,7 +129,7 @@ base::TimeDelta presentation_time; bool is_eos = false; - bool is_encrypted = false; + EncryptionScheme encryption_scheme; }; // Handy enum for "no buffer".
diff --git a/media/base/android/mock_media_codec_bridge.cc b/media/base/android/mock_media_codec_bridge.cc index 50ef75d8..79c62d6 100644 --- a/media/base/android/mock_media_codec_bridge.cc +++ b/media/base/android/mock_media_codec_bridge.cc
@@ -4,6 +4,7 @@ #include "media/base/android/mock_media_codec_bridge.h" +#include "media/base/encryption_scheme.h" #include "media/base/subsample_entry.h" using ::testing::_;
diff --git a/media/base/android/mock_media_codec_bridge.h b/media/base/android/mock_media_codec_bridge.h index dfb52233..bcf9bc0 100644 --- a/media/base/android/mock_media_codec_bridge.h +++ b/media/base/android/mock_media_codec_bridge.h
@@ -24,15 +24,16 @@ MOCK_METHOD1(GetOutputChannelCount, MediaCodecStatus(int*)); MOCK_METHOD4(QueueInputBuffer, MediaCodecStatus(int, const uint8_t*, size_t, base::TimeDelta)); - MOCK_METHOD7(QueueSecureInputBuffer, + MOCK_METHOD8(QueueSecureInputBuffer, MediaCodecStatus(int, const uint8_t*, size_t, const std::string&, const std::string&, const std::vector<SubsampleEntry>&, + const EncryptionScheme& encryption_scheme, base::TimeDelta)); - MOCK_METHOD8(QueueSecureInputBuffer, + MOCK_METHOD9(QueueSecureInputBuffer, MediaCodecStatus(int, const uint8_t*, size_t, @@ -40,6 +41,7 @@ const std::vector<char>&, const SubsampleEntry*, int, + const EncryptionScheme& encryption_scheme, base::TimeDelta)); MOCK_METHOD1(QueueEOS, void(int)); MOCK_METHOD2(DequeueInputBuffer, MediaCodecStatus(base::TimeDelta, int*));
diff --git a/media/base/android/ndk_media_codec_bridge.cc b/media/base/android/ndk_media_codec_bridge.cc index b7d7575..5fe8b5a9 100644 --- a/media/base/android/ndk_media_codec_bridge.cc +++ b/media/base/android/ndk_media_codec_bridge.cc
@@ -13,6 +13,7 @@ #include "base/logging.h" #include "base/native_library.h" #include "base/strings/string_util.h" +#include "media/base/encryption_scheme.h" #include "media/base/subsample_entry.h" namespace { @@ -136,6 +137,7 @@ const std::vector<char>& iv, const SubsampleEntry* subsamples, int subsamples_size, + const EncryptionScheme& encryption_scheme, base::TimeDelta presentation_time) { if (data_size > base::checked_cast<size_t>(std::numeric_limits<int32_t>::max())) { @@ -145,6 +147,9 @@ return MEDIA_CODEC_ERROR; if (data && !FillInputBuffer(index, data, data_size)) return MEDIA_CODEC_ERROR; + if (encryption_scheme.mode() != EncryptionScheme::CIPHER_MODE_AES_CTR || + encryption_scheme.pattern().IsInEffect()) + return MEDIA_CODEC_ERROR; int new_subsamples_size = subsamples_size == 0 ? 1 : subsamples_size; std::vector<size_t> clear_data, encrypted_data;
diff --git a/media/base/android/ndk_media_codec_bridge.h b/media/base/android/ndk_media_codec_bridge.h index 0b93a1ef..38ad4b0 100644 --- a/media/base/android/ndk_media_codec_bridge.h +++ b/media/base/android/ndk_media_codec_bridge.h
@@ -43,6 +43,7 @@ const std::vector<char>& iv, const SubsampleEntry* subsamples, int subsamples_size, + const EncryptionScheme& encryption_scheme, base::TimeDelta presentation_time) override; void QueueEOS(int input_buffer_index) override; MediaCodecStatus DequeueInputBuffer(base::TimeDelta timeout,
diff --git a/media/base/android/sdk_media_codec_bridge.cc b/media/base/android/sdk_media_codec_bridge.cc index aed2457..d80e727 100644 --- a/media/base/android/sdk_media_codec_bridge.cc +++ b/media/base/android/sdk_media_codec_bridge.cc
@@ -182,6 +182,7 @@ const std::vector<char>& iv, const SubsampleEntry* subsamples, int subsamples_size, + const EncryptionScheme& encryption_scheme, base::TimeDelta presentation_time) { DVLOG(3) << __func__ << index << ": " << data_size; if (data_size > @@ -234,6 +235,9 @@ Java_MediaCodecBridge_queueSecureInputBuffer( env, j_media_codec_.obj(), index, 0, j_iv.obj(), j_key_id.obj(), clear_array, cypher_array, new_subsamples_size, + static_cast<int>(encryption_scheme.mode()), + static_cast<int>(encryption_scheme.pattern().encrypt_blocks()), + static_cast<int>(encryption_scheme.pattern().skip_blocks()), presentation_time.InMicroseconds())); }
diff --git a/media/base/android/sdk_media_codec_bridge.h b/media/base/android/sdk_media_codec_bridge.h index d1841e3..c8e854a7 100644 --- a/media/base/android/sdk_media_codec_bridge.h +++ b/media/base/android/sdk_media_codec_bridge.h
@@ -48,6 +48,7 @@ const std::vector<char>& iv, const SubsampleEntry* subsamples, int subsamples_size, + const EncryptionScheme& encryption_scheme, base::TimeDelta presentation_time) override; void QueueEOS(int input_buffer_index) override; MediaCodecStatus DequeueInputBuffer(base::TimeDelta timeout,
diff --git a/media/base/demuxer.h b/media/base/demuxer.h index ae07e60..6fa71a5 100644 --- a/media/base/demuxer.h +++ b/media/base/demuxer.h
@@ -89,7 +89,8 @@ bool enable_text_tracks) = 0; // Aborts any pending read operations that the demuxer is involved with; any - // read aborted will be aborted with a status of kAborted. + // read aborted will be aborted with a status of kAborted. Future reads will + // also be aborted until Seek() is called. virtual void AbortPendingReads() = 0; // Indicates that a new Seek() call is on its way. Implementations may abort
diff --git a/media/base/encryption_scheme.cc b/media/base/encryption_scheme.cc index 70d133c6..dea08ed 100644 --- a/media/base/encryption_scheme.cc +++ b/media/base/encryption_scheme.cc
@@ -14,6 +14,14 @@ EncryptionScheme::Pattern::~Pattern() {} +uint32_t EncryptionScheme::Pattern::encrypt_blocks() const { + return encrypt_blocks_; +} + +uint32_t EncryptionScheme::Pattern::skip_blocks() const { + return skip_blocks_; +} + bool EncryptionScheme::Pattern::Matches(const Pattern& other) const { return encrypt_blocks_ == other.encrypt_blocks() && skip_blocks_ == other.skip_blocks(); @@ -30,6 +38,18 @@ EncryptionScheme::~EncryptionScheme() {} +bool EncryptionScheme::is_encrypted() const { + return mode_ != CIPHER_MODE_UNENCRYPTED; +} + +EncryptionScheme::CipherMode EncryptionScheme::mode() const { + return mode_; +} + +const EncryptionScheme::Pattern& EncryptionScheme::pattern() const { + return pattern_; +} + bool EncryptionScheme::Matches(const EncryptionScheme& other) const { return mode_ == other.mode_ && pattern_.Matches(other.pattern_); }
diff --git a/media/base/encryption_scheme.h b/media/base/encryption_scheme.h index 37bea67..572db86 100644 --- a/media/base/encryption_scheme.h +++ b/media/base/encryption_scheme.h
@@ -42,8 +42,8 @@ bool Matches(const Pattern& other) const; - uint32_t encrypt_blocks() const { return encrypt_blocks_; } - uint32_t skip_blocks() const { return skip_blocks_; } + uint32_t encrypt_blocks() const; + uint32_t skip_blocks() const; bool IsInEffect() const; @@ -63,9 +63,9 @@ bool Matches(const EncryptionScheme& other) const; - bool is_encrypted() const { return mode_ != CIPHER_MODE_UNENCRYPTED; } - CipherMode mode() const { return mode_; } - const Pattern& pattern() const { return pattern_; } + bool is_encrypted() const; + CipherMode mode() const; + const Pattern& pattern() const; private: CipherMode mode_ = CIPHER_MODE_UNENCRYPTED;
diff --git a/media/base/ipc/media_param_traits.cc b/media/base/ipc/media_param_traits.cc index 6de97b32..8233f3d 100644 --- a/media/base/ipc/media_param_traits.cc +++ b/media/base/ipc/media_param_traits.cc
@@ -8,6 +8,7 @@ #include "ipc/ipc_message_utils.h" #include "media/base/audio_parameters.h" #include "media/base/audio_point.h" +#include "media/base/encryption_scheme.h" #include "media/base/limits.h" #include "ui/gfx/ipc/geometry/gfx_param_traits.h" #include "ui/gfx/ipc/gfx_param_traits.h" @@ -80,6 +81,74 @@ l->append(base::StringPrintf("<AudioParameters>")); } +template <> +struct ParamTraits<media::EncryptionScheme::Pattern> { + typedef media::EncryptionScheme::Pattern param_type; + static void GetSize(base::PickleSizer* s, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +void ParamTraits<media::EncryptionScheme>::GetSize(base::PickleSizer* s, + const param_type& p) { + GetParamSize(s, p.mode()); + GetParamSize(s, p.pattern()); +} + +void ParamTraits<media::EncryptionScheme>::Write(base::Pickle* m, + const param_type& p) { + WriteParam(m, p.mode()); + WriteParam(m, p.pattern()); +} + +bool ParamTraits<media::EncryptionScheme>::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + media::EncryptionScheme::CipherMode mode; + media::EncryptionScheme::Pattern pattern; + if (!ReadParam(m, iter, &mode) || !ReadParam(m, iter, &pattern)) + return false; + *r = media::EncryptionScheme(mode, pattern); + return true; +} + +void ParamTraits<media::EncryptionScheme>::Log(const param_type& p, + std::string* l) { + l->append(base::StringPrintf("<EncryptionScheme>")); +} + +void ParamTraits<media::EncryptionScheme::Pattern>::GetSize( + base::PickleSizer* s, + const param_type& p) { + GetParamSize(s, p.encrypt_blocks()); + GetParamSize(s, p.skip_blocks()); +} + +void ParamTraits<media::EncryptionScheme::Pattern>::Write(base::Pickle* m, + const param_type& p) { + WriteParam(m, p.encrypt_blocks()); + WriteParam(m, p.skip_blocks()); +} + +bool ParamTraits<media::EncryptionScheme::Pattern>::Read( + const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + uint8_t encrypt_blocks, skip_blocks; + if (!ReadParam(m, iter, &encrypt_blocks) || !ReadParam(m, iter, &skip_blocks)) + return false; + *r = media::EncryptionScheme::Pattern(encrypt_blocks, skip_blocks); + return true; +} + +void ParamTraits<media::EncryptionScheme::Pattern>::Log(const param_type& p, + std::string* l) { + l->append(base::StringPrintf("<Pattern>")); +} + } // namespace IPC // Generate param traits size methods.
diff --git a/media/base/ipc/media_param_traits.h b/media/base/ipc/media_param_traits.h index 37f8938..dcc5833 100644 --- a/media/base/ipc/media_param_traits.h +++ b/media/base/ipc/media_param_traits.h
@@ -11,6 +11,7 @@ namespace media { class AudioParameters; +class EncryptionScheme; } namespace IPC { @@ -26,6 +27,17 @@ static void Log(const param_type& p, std::string* l); }; +template <> +struct ParamTraits<media::EncryptionScheme> { + typedef media::EncryptionScheme param_type; + static void GetSize(base::PickleSizer* s, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + } // namespace IPC #endif // MEDIA_BASE_IPC_MEDIA_PARAM_TRAITS_H_
diff --git a/media/capture/content/screen_capture_device_core.cc b/media/capture/content/screen_capture_device_core.cc index f65ea59..88513f08 100644 --- a/media/capture/content/screen_capture_device_core.cc +++ b/media/capture/content/screen_capture_device_core.cc
@@ -113,6 +113,14 @@ capture_machine_->Stop(base::Bind(&base::DoNothing)); } +void ScreenCaptureDeviceCore::OnConsumerReportingUtilization( + int frame_feedback_id, + double utilization) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(oracle_proxy_); + oracle_proxy_->OnConsumerReportingUtilization(frame_feedback_id, utilization); +} + void ScreenCaptureDeviceCore::CaptureStarted(bool success) { DCHECK(thread_checker_.CalledOnValidThread()); if (!success)
diff --git a/media/capture/content/screen_capture_device_core.h b/media/capture/content/screen_capture_device_core.h index c965043..60faf27 100644 --- a/media/capture/content/screen_capture_device_core.h +++ b/media/capture/content/screen_capture_device_core.h
@@ -89,6 +89,8 @@ void Suspend(); void Resume(); void StopAndDeAllocate(); + void OnConsumerReportingUtilization(int frame_feedback_id, + double utilization); private: // Flag indicating current state.
diff --git a/media/capture/content/thread_safe_capture_oracle.cc b/media/capture/content/thread_safe_capture_oracle.cc index c406396e..9fc8f93 100644 --- a/media/capture/content/thread_safe_capture_oracle.cc +++ b/media/capture/content/thread_safe_capture_oracle.cc
@@ -81,6 +81,7 @@ return false; } + frame_number = oracle_.next_frame_number(); visible_size = oracle_.capture_size(); // TODO(miu): Clients should request exact padding, instead of this // memory-wasting hack to make frames that are compatible with all HW @@ -91,7 +92,7 @@ if (event == VideoCaptureOracle::kPassiveRefreshRequest) { output_buffer = client_->ResurrectLastOutputBuffer( coded_size, params_.requested_format.pixel_format, - params_.requested_format.pixel_storage); + params_.requested_format.pixel_storage, frame_number); if (!output_buffer) { TRACE_EVENT_INSTANT0("gpu.capture", "ResurrectionFailed", TRACE_EVENT_SCOPE_THREAD); @@ -100,7 +101,7 @@ } else { output_buffer = client_->ReserveOutputBuffer( coded_size, params_.requested_format.pixel_format, - params_.requested_format.pixel_storage); + params_.requested_format.pixel_storage, frame_number); } // Get the current buffer pool utilization and attenuate it: The utilization @@ -118,7 +119,7 @@ return false; } - frame_number = oracle_.RecordCapture(attenuated_utilization); + oracle_.RecordCapture(attenuated_utilization); estimated_frame_duration = oracle_.estimated_frame_duration(); } // End of critical section. @@ -228,25 +229,15 @@ frame->metadata()->SetTimeTicks(VideoFrameMetadata::REFERENCE_TIME, reference_time); - frame->AddDestructionObserver( - base::Bind(&ThreadSafeCaptureOracle::DidConsumeFrame, this, - frame_number, frame->metadata())); - client_->OnIncomingCapturedVideoFrame(std::move(buffer), std::move(frame)); } } -void ThreadSafeCaptureOracle::DidConsumeFrame( +void ThreadSafeCaptureOracle::OnConsumerReportingUtilization( int frame_number, - const media::VideoFrameMetadata* metadata) { - // Note: This function may be called on any thread by the VideoFrame - // destructor. |metadata| is still valid for read-access at this point. - double utilization = -1.0; - if (metadata->GetDouble(media::VideoFrameMetadata::RESOURCE_UTILIZATION, - &utilization)) { - base::AutoLock guard(lock_); - oracle_.RecordConsumerFeedback(frame_number, utilization); - } + double utilization) { + base::AutoLock guard(lock_); + oracle_.RecordConsumerFeedback(frame_number, utilization); } } // namespace media
diff --git a/media/capture/content/thread_safe_capture_oracle.h b/media/capture/content/thread_safe_capture_oracle.h index 7cc554d..e35c0b4 100644 --- a/media/capture/content/thread_safe_capture_oracle.h +++ b/media/capture/content/thread_safe_capture_oracle.h
@@ -91,6 +91,8 @@ void ReportError(const tracked_objects::Location& from_here, const std::string& reason); + void OnConsumerReportingUtilization(int frame_number, double utilization); + private: friend class base::RefCountedThreadSafe<ThreadSafeCaptureOracle>; virtual ~ThreadSafeCaptureOracle();
diff --git a/media/capture/content/video_capture_oracle.cc b/media/capture/content/video_capture_oracle.cc index e8dd752..b1dcb80 100644 --- a/media/capture/content/video_capture_oracle.cc +++ b/media/capture/content/video_capture_oracle.cc
@@ -214,7 +214,11 @@ return true; } -int VideoCaptureOracle::RecordCapture(double pool_utilization) { +int VideoCaptureOracle::next_frame_number() const { + return next_frame_number_; +} + +void VideoCaptureOracle::RecordCapture(double pool_utilization) { DCHECK(std::isfinite(pool_utilization) && pool_utilization >= 0.0); smoothing_sampler_.RecordSample(); @@ -227,7 +231,7 @@ } num_frames_pending_++; - return next_frame_number_++; + next_frame_number_++; } void VideoCaptureOracle::RecordWillNotCapture(double pool_utilization) {
diff --git a/media/capture/content/video_capture_oracle.h b/media/capture/content/video_capture_oracle.h index 82898f9..8d545e2 100644 --- a/media/capture/content/video_capture_oracle.h +++ b/media/capture/content/video_capture_oracle.h
@@ -50,13 +50,15 @@ const gfx::Rect& damage_rect, base::TimeTicks event_time); + // Returns the |frame_number| to be used with CompleteCapture(). + int next_frame_number() const; + // Record and update internal state based on whether the frame capture will be // started. |pool_utilization| is a value in the range 0.0 to 1.0 to indicate // the current buffer pool utilization relative to a sustainable maximum (not // the absolute maximum). This method should only be called if the last call - // to ObserveEventAndDecideCapture() returned true. The first method returns - // the |frame_number| to be used with CompleteCapture(). - int RecordCapture(double pool_utilization); + // to ObserveEventAndDecideCapture() returned true. + void RecordCapture(double pool_utilization); void RecordWillNotCapture(double pool_utilization); // Notify of the completion of a capture, and whether it was successful.
diff --git a/media/capture/content/video_capture_oracle_unittest.cc b/media/capture/content/video_capture_oracle_unittest.cc index dbfcae2..fcfa40c 100644 --- a/media/capture/content/video_capture_oracle_unittest.cc +++ b/media/capture/content/video_capture_oracle_unittest.cc
@@ -83,7 +83,8 @@ t += event_increment; ASSERT_TRUE(oracle.ObserveEventAndDecideCapture( VideoCaptureOracle::kCompositorUpdate, damage_rect, t)); - last_frame_number = oracle.RecordCapture(0.0); + last_frame_number = oracle.next_frame_number(); + oracle.RecordCapture(0.0); ASSERT_TRUE(oracle.CompleteCapture(last_frame_number, true, &ignored)); } @@ -94,7 +95,8 @@ t += event_increment; ASSERT_TRUE(oracle.ObserveEventAndDecideCapture( VideoCaptureOracle::kCompositorUpdate, damage_rect, t)); - last_frame_number = oracle.RecordCapture(0.0); + last_frame_number = oracle.next_frame_number(); + oracle.RecordCapture(0.0); } for (int j = num_in_flight - 1; j >= 0; --j) { ASSERT_TRUE( @@ -110,7 +112,8 @@ t += event_increment; ASSERT_TRUE(oracle.ObserveEventAndDecideCapture( VideoCaptureOracle::kCompositorUpdate, damage_rect, t)); - last_frame_number = oracle.RecordCapture(0.0); + last_frame_number = oracle.next_frame_number(); + oracle.RecordCapture(0.0); } ASSERT_TRUE(oracle.CompleteCapture(last_frame_number, true, &ignored)); for (int j = 1; j < num_in_flight; ++j) { @@ -127,7 +130,8 @@ t += event_increment; ASSERT_TRUE(oracle.ObserveEventAndDecideCapture( VideoCaptureOracle::kCompositorUpdate, damage_rect, t)); - last_frame_number = oracle.RecordCapture(0.0); + last_frame_number = oracle.next_frame_number(); + oracle.RecordCapture(0.0); } // Report the last frame as an out of order failure. ASSERT_FALSE(oracle.CompleteCapture(last_frame_number, false, &ignored)); @@ -178,7 +182,8 @@ } ASSERT_LT(base::TimeDelta(), oracle.estimated_frame_duration()); - const int frame_number = oracle.RecordCapture(0.0); + const int frame_number = oracle.next_frame_number(); + oracle.RecordCapture(0.0); base::TimeTicks frame_timestamp; ASSERT_TRUE(oracle.CompleteCapture(frame_number, true, &frame_timestamp)); @@ -217,8 +222,9 @@ t += vsync_interval; if (oracle.ObserveEventAndDecideCapture( VideoCaptureOracle::kCompositorUpdate, gfx::Rect(), t)) { - ASSERT_TRUE( - oracle.CompleteCapture(oracle.RecordCapture(0.0), true, &ignored)); + const int frame_number = oracle.next_frame_number(); + oracle.RecordCapture(0.0); + ASSERT_TRUE(oracle.CompleteCapture(frame_number, true, &ignored)); did_complete_a_capture = true; } } @@ -234,7 +240,8 @@ break; } } - int frame_number = oracle.RecordCapture(0.0); + int frame_number = oracle.next_frame_number(); + oracle.RecordCapture(0.0); // Stop providing the compositor events and start providing refresh request // events. No overdue samplings should be recommended because of the @@ -254,8 +261,9 @@ t += refresh_interval; if (oracle.ObserveEventAndDecideCapture( VideoCaptureOracle::kPassiveRefreshRequest, gfx::Rect(), t)) { - ASSERT_TRUE( - oracle.CompleteCapture(oracle.RecordCapture(0.0), true, &ignored)); + const int frame_number = oracle.next_frame_number(); + oracle.RecordCapture(0.0); + ASSERT_TRUE(oracle.CompleteCapture(frame_number, true, &ignored)); did_complete_a_capture = true; } } @@ -270,7 +278,8 @@ break; } } - frame_number = oracle.RecordCapture(0.0); + frame_number = oracle.next_frame_number(); + oracle.RecordCapture(0.0); // Confirm that the oracle does not recommend sampling until the outstanding // "refresh" capture completes. @@ -306,8 +315,9 @@ VideoCaptureOracle::kCompositorUpdate, gfx::Rect(), t)); ASSERT_EQ(Get720pSize(), oracle.capture_size()); base::TimeTicks ignored; - ASSERT_TRUE( - oracle.CompleteCapture(oracle.RecordCapture(0.0), true, &ignored)); + const int frame_number = oracle.next_frame_number(); + oracle.RecordCapture(0.0); + ASSERT_TRUE(oracle.CompleteCapture(frame_number, true, &ignored)); } // Now run 30 seconds of frame captures with lots of random source size @@ -334,8 +344,9 @@ } base::TimeTicks ignored; - ASSERT_TRUE( - oracle.CompleteCapture(oracle.RecordCapture(0.0), true, &ignored)); + const int frame_number = oracle.next_frame_number(); + oracle.RecordCapture(0.0); + ASSERT_TRUE(oracle.CompleteCapture(frame_number, true, &ignored)); } } @@ -370,8 +381,8 @@ is_content_animating ? gfx::Rect(Get720pSize()) : gfx::Rect(), t)); ASSERT_EQ(Get720pSize(), oracle.capture_size()); const double utilization = 0.9; - const int frame_number = - oracle.RecordCapture(with_consumer_feedback ? 0.25 : utilization); + const int frame_number = oracle.next_frame_number(); + oracle.RecordCapture(with_consumer_feedback ? 0.25 : utilization); base::TimeTicks ignored; ASSERT_TRUE(oracle.CompleteCapture(frame_number, true, &ignored)); if (with_consumer_feedback) @@ -406,8 +417,8 @@ } const double utilization = stepped_down_size.IsEmpty() ? 1.5 : 0.9; - const int frame_number = - oracle.RecordCapture(with_consumer_feedback ? 0.25 : utilization); + const int frame_number = oracle.next_frame_number(); + oracle.RecordCapture(with_consumer_feedback ? 0.25 : utilization); base::TimeTicks ignored; ASSERT_TRUE(oracle.CompleteCapture(frame_number, true, &ignored)); if (with_consumer_feedback) @@ -447,8 +458,8 @@ } const double utilization = stepped_up_size.IsEmpty() ? 0.0 : 0.9; - const int frame_number = - oracle.RecordCapture(with_consumer_feedback ? 0.25 : utilization); + const int frame_number = oracle.next_frame_number(); + oracle.RecordCapture(with_consumer_feedback ? 0.25 : utilization); base::TimeTicks ignored; ASSERT_TRUE(oracle.CompleteCapture(frame_number, true, &ignored)); if (with_consumer_feedback) @@ -494,7 +505,8 @@ continue; } ASSERT_EQ(Get360pSize(), oracle.capture_size()); - const int frame_number = oracle.RecordCapture(0.25); + const int frame_number = oracle.next_frame_number(); + oracle.RecordCapture(0.25); base::TimeTicks ignored; ASSERT_TRUE(oracle.CompleteCapture(frame_number, true, &ignored)); } @@ -514,7 +526,8 @@ ASSERT_LE(last_capture_size.width(), oracle.capture_size().width()); ASSERT_LE(last_capture_size.height(), oracle.capture_size().height()); last_capture_size = oracle.capture_size(); - const int frame_number = oracle.RecordCapture(0.25); + const int frame_number = oracle.next_frame_number(); + oracle.RecordCapture(0.25); base::TimeTicks ignored; ASSERT_TRUE(oracle.CompleteCapture(frame_number, true, &ignored)); } @@ -544,7 +557,8 @@ } const double utilization = stepped_down_size.IsEmpty() ? 1.5 : 0.9; - const int frame_number = oracle.RecordCapture(utilization); + const int frame_number = oracle.next_frame_number(); + oracle.RecordCapture(utilization); base::TimeTicks ignored; ASSERT_TRUE(oracle.CompleteCapture(frame_number, true, &ignored)); } @@ -576,7 +590,8 @@ } const double utilization = stepped_up_size.IsEmpty() ? 0.25 : 0.9; - const int frame_number = oracle.RecordCapture(utilization); + const int frame_number = oracle.next_frame_number(); + oracle.RecordCapture(utilization); base::TimeTicks ignored; ASSERT_TRUE(oracle.CompleteCapture(frame_number, true, &ignored)); } @@ -599,8 +614,9 @@ VideoCaptureOracle::kCompositorUpdate, gfx::Rect(), t)); ASSERT_EQ(Get720pSize(), oracle.capture_size()); base::TimeTicks ignored; - ASSERT_TRUE( - oracle.CompleteCapture(oracle.RecordCapture(0.9), true, &ignored)); + const int frame_number = oracle.next_frame_number(); + oracle.RecordCapture(0.9); + ASSERT_TRUE(oracle.CompleteCapture(frame_number, true, &ignored)); } // Now run 10 seconds with overload indicated. Still, expect no capture size @@ -611,8 +627,9 @@ VideoCaptureOracle::kCompositorUpdate, gfx::Rect(), t)); ASSERT_EQ(Get720pSize(), oracle.capture_size()); base::TimeTicks ignored; - ASSERT_TRUE( - oracle.CompleteCapture(oracle.RecordCapture(2.0), true, &ignored)); + const int frame_number = oracle.next_frame_number(); + oracle.RecordCapture(2.0); + ASSERT_TRUE(oracle.CompleteCapture(frame_number, true, &ignored)); } }
diff --git a/media/capture/video/fake_video_capture_device.cc b/media/capture/video/fake_video_capture_device.cc index 31fe10b..8ea60bc 100644 --- a/media/capture/video/fake_video_capture_device.cc +++ b/media/capture/video/fake_video_capture_device.cc
@@ -319,10 +319,11 @@ base::TimeTicks expected_execution_time) { DCHECK(thread_checker_.CalledOnValidThread()); + const int arbitrary_frame_feedback_id = 0; std::unique_ptr<VideoCaptureDevice::Client::Buffer> capture_buffer( - client_->ReserveOutputBuffer(capture_format_.frame_size, - capture_format_.pixel_format, - capture_format_.pixel_storage)); + client_->ReserveOutputBuffer( + capture_format_.frame_size, capture_format_.pixel_format, + capture_format_.pixel_storage, arbitrary_frame_feedback_id)); DLOG_IF(ERROR, !capture_buffer) << "Couldn't allocate Capture Buffer"; DCHECK(capture_buffer->data()) << "Buffer has NO backing memory";
diff --git a/media/capture/video/fake_video_capture_device_unittest.cc b/media/capture/video/fake_video_capture_device_unittest.cc index 5b6fe3a..c9bbae9 100644 --- a/media/capture/video/fake_video_capture_device_unittest.cc +++ b/media/capture/video/fake_video_capture_device_unittest.cc
@@ -37,13 +37,15 @@ // This class is a Client::Buffer that allocates and frees the requested |size|. class MockBuffer : public VideoCaptureDevice::Client::Buffer { public: - MockBuffer(int buffer_id, size_t mapped_size) + MockBuffer(int buffer_id, int frame_feedback_id, size_t mapped_size) : id_(buffer_id), + frame_feedback_id_(frame_feedback_id), mapped_size_(mapped_size), data_(new uint8_t[mapped_size]) {} ~MockBuffer() override { delete[] data_; } int id() const override { return id_; } + int frame_feedback_id() const override { return frame_feedback_id_; } gfx::Size dimensions() const override { return gfx::Size(); } size_t mapped_size() const override { return mapped_size_; } void* data(int plane) override { return data_; } @@ -57,6 +59,7 @@ private: const int id_; + const int frame_feedback_id_; const size_t mapped_size_; uint8_t* const data_; }; @@ -76,28 +79,31 @@ const VideoCaptureFormat& format, int rotation, base::TimeTicks reference_time, - base::TimeDelta timestamp) override { + base::TimeDelta timestamp, + int frame_feedback_id) override { frame_cb_.Run(format); } // Virtual methods for capturing using Client's Buffers. - std::unique_ptr<Buffer> ReserveOutputBuffer( - const gfx::Size& dimensions, - media::VideoPixelFormat format, - media::VideoPixelStorage storage) { + std::unique_ptr<Buffer> ReserveOutputBuffer(const gfx::Size& dimensions, + media::VideoPixelFormat format, + media::VideoPixelStorage storage, + int frame_feedback_id) override { EXPECT_TRUE((format == media::PIXEL_FORMAT_ARGB && storage == media::PIXEL_STORAGE_CPU)); EXPECT_GT(dimensions.GetArea(), 0); const VideoCaptureFormat frame_format(dimensions, 0.0, format); - return base::MakeUnique<MockBuffer>(0, frame_format.ImageAllocationSize()); + return base::MakeUnique<MockBuffer>(0, frame_feedback_id, + frame_format.ImageAllocationSize()); } void OnIncomingCapturedBuffer(std::unique_ptr<Buffer> buffer, - const VideoCaptureFormat& frame_format, + const VideoCaptureFormat& format, base::TimeTicks reference_time, - base::TimeDelta timestamp) { - frame_cb_.Run(frame_format); + base::TimeDelta timestamp) override { + frame_cb_.Run(format); } - void OnIncomingCapturedVideoFrame(std::unique_ptr<Buffer> buffer, - scoped_refptr<media::VideoFrame> frame) { + void OnIncomingCapturedVideoFrame( + std::unique_ptr<Buffer> buffer, + scoped_refptr<media::VideoFrame> frame) override { VideoCaptureFormat format(frame->natural_size(), 30.0, PIXEL_FORMAT_I420); frame_cb_.Run(format); @@ -105,7 +111,8 @@ std::unique_ptr<Buffer> ResurrectLastOutputBuffer( const gfx::Size& dimensions, media::VideoPixelFormat format, - media::VideoPixelStorage storage) { + media::VideoPixelStorage storage, + int frame_feedback_id) override { return std::unique_ptr<Buffer>(); } double GetBufferPoolUtilization() const override { return 0.0; }
diff --git a/media/capture/video/video_capture_buffer_pool.h b/media/capture/video/video_capture_buffer_pool.h index 4b32f0d..2d04957 100644 --- a/media/capture/video/video_capture_buffer_pool.h +++ b/media/capture/video/video_capture_buffer_pool.h
@@ -64,6 +64,7 @@ virtual int ReserveForProducer(const gfx::Size& dimensions, media::VideoPixelFormat format, media::VideoPixelStorage storage, + int frame_feedback_id, int* buffer_id_to_drop) = 0; // Indicate that a buffer held for the producer should be returned back to the
diff --git a/media/capture/video/video_capture_buffer_pool_impl.cc b/media/capture/video/video_capture_buffer_pool_impl.cc index 3b5860a..7bc10e16 100644 --- a/media/capture/video/video_capture_buffer_pool_impl.cc +++ b/media/capture/video/video_capture_buffer_pool_impl.cc
@@ -57,10 +57,11 @@ const gfx::Size& dimensions, media::VideoPixelFormat format, media::VideoPixelStorage storage, + int frame_feedback_id, int* buffer_id_to_drop) { base::AutoLock lock(lock_); return ReserveForProducerInternal(dimensions, format, storage, - buffer_id_to_drop); + frame_feedback_id, buffer_id_to_drop); } void VideoCaptureBufferPoolImpl::RelinquishProducerReservation(int buffer_id) { @@ -151,6 +152,7 @@ const gfx::Size& dimensions, media::VideoPixelFormat pixel_format, media::VideoPixelStorage storage_type, + int frame_feedback_id, int* buffer_id_to_drop) { lock_.AssertAcquired(); @@ -177,6 +179,7 @@ // Existing tracker is big enough and has correct format. Reuse it. tracker->set_dimensions(dimensions); tracker->set_held_by_producer(true); + tracker->set_frame_feedback_id(frame_feedback_id); return it->first; } if (tracker->max_pixel_count() > largest_size_in_pixels) { @@ -194,6 +197,7 @@ last_relinquished_buffer_id_ = kInvalidId; tracker_of_last_resort->second->set_dimensions(dimensions); tracker_of_last_resort->second->set_held_by_producer(true); + tracker_of_last_resort->second->set_frame_feedback_id(frame_feedback_id); return tracker_of_last_resort->first; } if (tracker_to_drop == trackers_.end()) { @@ -219,6 +223,7 @@ } tracker->set_held_by_producer(true); + tracker->set_frame_feedback_id(frame_feedback_id); trackers_[buffer_id] = std::move(tracker); return buffer_id;
diff --git a/media/capture/video/video_capture_buffer_pool_impl.h b/media/capture/video/video_capture_buffer_pool_impl.h index 5f3a166..08c8475 100644 --- a/media/capture/video/video_capture_buffer_pool_impl.h +++ b/media/capture/video/video_capture_buffer_pool_impl.h
@@ -41,6 +41,7 @@ int ReserveForProducer(const gfx::Size& dimensions, media::VideoPixelFormat format, media::VideoPixelStorage storage, + int frame_feedback_id, int* buffer_id_to_drop) override; void RelinquishProducerReservation(int buffer_id) override; int ResurrectLastForProducer(const gfx::Size& dimensions, @@ -57,6 +58,7 @@ int ReserveForProducerInternal(const gfx::Size& dimensions, media::VideoPixelFormat format, media::VideoPixelStorage storage, + int frame_feedback_id, int* tracker_id_to_drop); VideoCaptureBufferTracker* GetTracker(int buffer_id);
diff --git a/media/capture/video/video_capture_buffer_tracker.h b/media/capture/video/video_capture_buffer_tracker.h index 776f33f..3e3d7bf 100644 --- a/media/capture/video/video_capture_buffer_tracker.h +++ b/media/capture/video/video_capture_buffer_tracker.h
@@ -23,7 +23,8 @@ VideoCaptureBufferTracker() : max_pixel_count_(0), held_by_producer_(false), - consumer_hold_count_(0) {} + consumer_hold_count_(0), + frame_feedback_id_(0) {} virtual bool Init(const gfx::Size& dimensions, media::VideoPixelFormat format, media::VideoPixelStorage storage_type, @@ -46,6 +47,8 @@ void set_held_by_producer(bool value) { held_by_producer_ = value; } int consumer_hold_count() const { return consumer_hold_count_; } void set_consumer_hold_count(int value) { consumer_hold_count_ = value; } + void set_frame_feedback_id(int value) { frame_feedback_id_ = value; } + int frame_feedback_id() { return frame_feedback_id_; } // Returns a scoped handle to the underlying storage. virtual std::unique_ptr<VideoCaptureBufferHandle> GetBufferHandle() = 0; @@ -67,6 +70,8 @@ // Number of consumer processes which hold this VideoCaptureBufferTracker. int consumer_hold_count_; + + int frame_feedback_id_; }; } // namespace content
diff --git a/media/capture/video/video_capture_device.h b/media/capture/video/video_capture_device.h index 211f4d4..dc0b77f 100644 --- a/media/capture/video/video_capture_device.h +++ b/media/capture/video/video_capture_device.h
@@ -41,7 +41,35 @@ namespace media { -class CAPTURE_EXPORT VideoCaptureDevice { +class CAPTURE_EXPORT VideoFrameConsumerFeedbackObserver { + public: + virtual ~VideoFrameConsumerFeedbackObserver() {} + + // During processing of a video frame, consumers may report back their + // utilization level to the source device. The device may use this information + // to adjust the rate of data it pushes out. Values are interpreted as + // follows: + // Less than 0.0 is meaningless and should be ignored. 1.0 indicates a + // maximum sustainable utilization. Greater than 1.0 indicates the consumer + // is likely to stall or drop frames if the data volume is not reduced. + // + // Example: In a system that encodes and transmits video frames over the + // network, this value can be used to indicate whether sufficient CPU + // is available for encoding and/or sufficient bandwidth is available for + // transmission over the network. The maximum of the two utilization + // measurements would be used as feedback. + // + // The parameter |frame_feedback_id| must match a |frame_feedback_id| + // previously sent out by the VideoCaptureDevice we are giving feedback about. + // It is used to indicate which particular frame the reported utilization + // corresponds to. + virtual void OnUtilizationReport(int frame_feedback_id, double utilization) {} + + static constexpr double kNoUtilizationRecorded = -1.0; +}; + +class CAPTURE_EXPORT VideoCaptureDevice + : public VideoFrameConsumerFeedbackObserver { public: // Interface defining the methods that clients of VideoCapture must have. It @@ -55,6 +83,7 @@ public: virtual ~Buffer() = 0; virtual int id() const = 0; + virtual int frame_feedback_id() const = 0; virtual gfx::Size dimensions() const = 0; virtual size_t mapped_size() const = 0; virtual void* data(int plane) = 0; @@ -80,12 +109,18 @@ // first frame in the stream and the current frame; however, the time source // is determined by the platform's device driver and is often not the system // clock, or even has a drift with respect to system clock. + // |frame_feedback_id| is an identifier that allows clients to refer back to + // this particular frame when reporting consumer feedback via + // OnConsumerReportingUtilization(). This identifier is needed because + // frames are consumed asynchronously and multiple frames can be "in flight" + // at the same time. virtual void OnIncomingCapturedData(const uint8_t* data, int length, const VideoCaptureFormat& frame_format, int clockwise_rotation, base::TimeTicks reference_time, - base::TimeDelta timestamp) = 0; + base::TimeDelta timestamp, + int frame_feedback_id = 0) = 0; // Reserve an output buffer into which contents can be captured directly. // The returned Buffer will always be allocated with a memory size suitable @@ -100,7 +135,8 @@ virtual std::unique_ptr<Buffer> ReserveOutputBuffer( const gfx::Size& dimensions, VideoPixelFormat format, - VideoPixelStorage storage) = 0; + VideoPixelStorage storage, + int frame_feedback_id) = 0; // Captured new video data, held in |frame| or |buffer|, respectively for // OnIncomingCapturedVideoFrame() and OnIncomingCapturedBuffer(). @@ -129,7 +165,8 @@ virtual std::unique_ptr<Buffer> ResurrectLastOutputBuffer( const gfx::Size& dimensions, VideoPixelFormat format, - VideoPixelStorage storage) = 0; + VideoPixelStorage storage, + int new_frame_feedback_id) = 0; // An error has occurred that cannot be handled and VideoCaptureDevice must // be StopAndDeAllocate()-ed. |reason| is a text description of the error. @@ -144,7 +181,7 @@ virtual double GetBufferPoolUtilization() const = 0; }; - virtual ~VideoCaptureDevice(); + ~VideoCaptureDevice() override; // Prepares the video capturer for use. StopAndDeAllocate() must be called // before the object is deleted.
diff --git a/media/capture/video/video_capture_device_client.cc b/media/capture/video/video_capture_device_client.cc index 3590c7aa..ab0b916 100644 --- a/media/capture/video/video_capture_device_client.cc +++ b/media/capture/video/video_capture_device_client.cc
@@ -41,13 +41,17 @@ // implementation to guarantee proper cleanup on destruction on our side. class AutoReleaseBuffer : public media::VideoCaptureDevice::Client::Buffer { public: - AutoReleaseBuffer(scoped_refptr<VideoCaptureBufferPool> pool, int buffer_id) - : id_(buffer_id), - pool_(std::move(pool)), + AutoReleaseBuffer(scoped_refptr<VideoCaptureBufferPool> pool, + int buffer_id, + int frame_feedback_id) + : pool_(std::move(pool)), + id_(buffer_id), + frame_feedback_id_(frame_feedback_id), buffer_handle_(pool_->GetBufferHandle(buffer_id)) { DCHECK(pool_.get()); } int id() const override { return id_; } + int frame_feedback_id() const override { return frame_feedback_id_; } gfx::Size dimensions() const override { return buffer_handle_->dimensions(); } size_t mapped_size() const override { return buffer_handle_->mapped_size(); } void* data(int plane) override { return buffer_handle_->data(plane); } @@ -66,8 +70,9 @@ private: ~AutoReleaseBuffer() override { pool_->RelinquishProducerReservation(id_); } - const int id_; const scoped_refptr<VideoCaptureBufferPool> pool_; + const int id_; + const int frame_feedback_id_; const std::unique_ptr<VideoCaptureBufferHandle> buffer_handle_; }; @@ -93,7 +98,8 @@ const VideoCaptureFormat& frame_format, int rotation, base::TimeTicks reference_time, - base::TimeDelta timestamp) { + base::TimeDelta timestamp, + int frame_feedback_id) { TRACE_EVENT0("video", "VideoCaptureDeviceClient::OnIncomingCapturedData"); DCHECK_EQ(media::PIXEL_STORAGE_CPU, frame_format.pixel_storage); @@ -115,7 +121,7 @@ if (frame_format.pixel_format == media::PIXEL_FORMAT_Y16) { return OnIncomingCapturedY16Data(data, length, frame_format, reference_time, - timestamp); + timestamp, frame_feedback_id); } // |chopped_{width,height} and |new_unrotated_{width,height}| are the lowest @@ -142,9 +148,9 @@ const gfx::Size dimensions(destination_width, destination_height); uint8_t *y_plane_data, *u_plane_data, *v_plane_data; - std::unique_ptr<Buffer> buffer( - ReserveI420OutputBuffer(dimensions, media::PIXEL_STORAGE_CPU, - &y_plane_data, &u_plane_data, &v_plane_data)); + std::unique_ptr<Buffer> buffer(ReserveI420OutputBuffer( + dimensions, media::PIXEL_STORAGE_CPU, frame_feedback_id, &y_plane_data, + &u_plane_data, &v_plane_data)); #if DCHECK_IS_ON() dropped_frame_counter_ = buffer.get() ? 0 : dropped_frame_counter_ + 1; if (dropped_frame_counter_ >= kMaxDroppedFrames) @@ -266,7 +272,8 @@ VideoCaptureDeviceClient::ReserveOutputBuffer( const gfx::Size& frame_size, media::VideoPixelFormat pixel_format, - media::VideoPixelStorage pixel_storage) { + media::VideoPixelStorage pixel_storage, + int frame_feedback_id) { DCHECK_GT(frame_size.width(), 0); DCHECK_GT(frame_size.height(), 0); DCHECK(IsFormatSupported(pixel_format)); @@ -274,14 +281,15 @@ // TODO(mcasas): For PIXEL_STORAGE_GPUMEMORYBUFFER, find a way to indicate if // it's a ShMem GMB or a DmaBuf GMB. int buffer_id_to_drop = VideoCaptureBufferPool::kInvalidId; - const int buffer_id = buffer_pool_->ReserveForProducer( - frame_size, pixel_format, pixel_storage, &buffer_id_to_drop); + const int buffer_id = + buffer_pool_->ReserveForProducer(frame_size, pixel_format, pixel_storage, + frame_feedback_id, &buffer_id_to_drop); if (buffer_id_to_drop != VideoCaptureBufferPool::kInvalidId) receiver_->OnBufferDestroyed(buffer_id_to_drop); if (buffer_id == VideoCaptureBufferPool::kInvalidId) return nullptr; return base::WrapUnique<Buffer>( - new AutoReleaseBuffer(buffer_pool_, buffer_id)); + new AutoReleaseBuffer(buffer_pool_, buffer_id, frame_feedback_id)); } void VideoCaptureDeviceClient::OnIncomingCapturedBuffer( @@ -324,13 +332,14 @@ VideoCaptureDeviceClient::ResurrectLastOutputBuffer( const gfx::Size& dimensions, media::VideoPixelFormat format, - media::VideoPixelStorage storage) { + media::VideoPixelStorage storage, + int new_frame_feedback_id) { const int buffer_id = buffer_pool_->ResurrectLastForProducer(dimensions, format, storage); if (buffer_id == VideoCaptureBufferPool::kInvalidId) return nullptr; return base::WrapUnique<Buffer>( - new AutoReleaseBuffer(buffer_pool_, buffer_id)); + new AutoReleaseBuffer(buffer_pool_, buffer_id, new_frame_feedback_id)); } void VideoCaptureDeviceClient::OnError( @@ -358,6 +367,7 @@ VideoCaptureDeviceClient::ReserveI420OutputBuffer( const gfx::Size& dimensions, media::VideoPixelStorage storage, + int frame_feedback_id, uint8_t** y_plane_data, uint8_t** u_plane_data, uint8_t** v_plane_data) { @@ -366,8 +376,8 @@ DCHECK(dimensions.width()); const media::VideoPixelFormat format = media::PIXEL_FORMAT_I420; - std::unique_ptr<Buffer> buffer( - ReserveOutputBuffer(dimensions, media::PIXEL_FORMAT_I420, storage)); + std::unique_ptr<Buffer> buffer(ReserveOutputBuffer( + dimensions, media::PIXEL_FORMAT_I420, storage, frame_feedback_id)); if (!buffer) return std::unique_ptr<Buffer>(); // TODO(emircan): See http://crbug.com/521068, move this pointer @@ -387,10 +397,11 @@ int length, const VideoCaptureFormat& frame_format, base::TimeTicks reference_time, - base::TimeDelta timestamp) { - std::unique_ptr<Buffer> buffer(ReserveOutputBuffer(frame_format.frame_size, - media::PIXEL_FORMAT_Y16, - media::PIXEL_STORAGE_CPU)); + base::TimeDelta timestamp, + int frame_feedback_id) { + std::unique_ptr<Buffer> buffer( + ReserveOutputBuffer(frame_format.frame_size, media::PIXEL_FORMAT_Y16, + media::PIXEL_STORAGE_CPU, frame_feedback_id)); // The input |length| can be greater than the required buffer size because of // paddings and/or alignments, but it cannot be smaller. DCHECK_GE(static_cast<size_t>(length), frame_format.ImageAllocationSize());
diff --git a/media/capture/video/video_capture_device_client.h b/media/capture/video/video_capture_device_client.h index 9a36f9d..0c85539 100644 --- a/media/capture/video/video_capture_device_client.h +++ b/media/capture/video/video_capture_device_client.h
@@ -55,13 +55,14 @@ const media::VideoCaptureFormat& frame_format, int rotation, base::TimeTicks reference_time, - base::TimeDelta timestamp) override; - std::unique_ptr<Buffer> ReserveOutputBuffer( - const gfx::Size& dimensions, - media::VideoPixelFormat format, - media::VideoPixelStorage storage) override; + base::TimeDelta timestamp, + int frame_feedback_id = 0) override; + std::unique_ptr<Buffer> ReserveOutputBuffer(const gfx::Size& dimensions, + media::VideoPixelFormat format, + media::VideoPixelStorage storage, + int frame_feedback_id) override; void OnIncomingCapturedBuffer(std::unique_ptr<Buffer> buffer, - const media::VideoCaptureFormat& frame_format, + const VideoCaptureFormat& format, base::TimeTicks reference_time, base::TimeDelta timestamp) override; void OnIncomingCapturedVideoFrame( @@ -70,7 +71,8 @@ std::unique_ptr<Buffer> ResurrectLastOutputBuffer( const gfx::Size& dimensions, media::VideoPixelFormat format, - media::VideoPixelStorage storage) override; + media::VideoPixelStorage storage, + int new_frame_feedback_id) override; void OnError(const tracked_objects::Location& from_here, const std::string& reason) override; void OnLog(const std::string& message) override; @@ -90,6 +92,7 @@ std::unique_ptr<Buffer> ReserveI420OutputBuffer( const gfx::Size& dimensions, media::VideoPixelStorage storage, + int frame_feedback_id, uint8_t** y_plane_data, uint8_t** u_plane_data, uint8_t** v_plane_data); @@ -99,7 +102,8 @@ int length, const VideoCaptureFormat& frame_format, base::TimeTicks reference_time, - base::TimeDelta timestamp); + base::TimeDelta timestamp, + int frame_feedback_id); // The receiver to which we post events. const std::unique_ptr<VideoFrameReceiver> receiver_;
diff --git a/media/capture/video/video_capture_device_unittest.cc b/media/capture/video/video_capture_device_unittest.cc index 20e7a06..b3289d21 100644 --- a/media/capture/video/video_capture_device_unittest.cc +++ b/media/capture/video/video_capture_device_unittest.cc
@@ -113,23 +113,24 @@ const VideoCaptureFormat& format, int rotation, base::TimeTicks reference_time, - base::TimeDelta timestamp) override { + base::TimeDelta timestamp, + int frame_feedback_id) override { ASSERT_GT(length, 0); ASSERT_TRUE(data); main_thread_->PostTask(FROM_HERE, base::Bind(frame_cb_, format)); } // Trampoline methods to workaround GMOCK problems with std::unique_ptr<>. - std::unique_ptr<Buffer> ReserveOutputBuffer( - const gfx::Size& dimensions, - media::VideoPixelFormat format, - media::VideoPixelStorage storage) override { + std::unique_ptr<Buffer> ReserveOutputBuffer(const gfx::Size& dimensions, + media::VideoPixelFormat format, + media::VideoPixelStorage storage, + int frame_feedback_id) override { DoReserveOutputBuffer(); NOTREACHED() << "This should never be called"; return std::unique_ptr<Buffer>(); } void OnIncomingCapturedBuffer(std::unique_ptr<Buffer> buffer, - const VideoCaptureFormat& frame_format, + const VideoCaptureFormat& format, base::TimeTicks reference_time, base::TimeDelta timestamp) override { DoOnIncomingCapturedBuffer(); @@ -141,7 +142,8 @@ std::unique_ptr<Buffer> ResurrectLastOutputBuffer( const gfx::Size& dimensions, media::VideoPixelFormat format, - media::VideoPixelStorage storage) { + media::VideoPixelStorage storage, + int frame_feedback_id) { DoResurrectLastOutputBuffer(); NOTREACHED() << "This should never be called"; return std::unique_ptr<Buffer>();
diff --git a/media/capture/video/video_frame_receiver.h b/media/capture/video/video_frame_receiver.h index b2af10c6..e772f33 100644 --- a/media/capture/video/video_frame_receiver.h +++ b/media/capture/video/video_frame_receiver.h
@@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#ifndef MEDIA_CAPTURE_VIDEO_VIDEO_FRAME_RECEIVER_H_ +#define MEDIA_CAPTURE_VIDEO_VIDEO_FRAME_RECEIVER_H_ + #include "media/capture/capture_export.h" #include "media/capture/video/video_capture_device.h" @@ -22,3 +25,5 @@ }; } // namespace media + +#endif // MEDIA_CAPTURE_VIDEO_VIDEO_FRAME_RECEIVER_H_
diff --git a/media/filters/android/media_codec_audio_decoder.cc b/media/filters/android/media_codec_audio_decoder.cc index 79744ea..a38e57a 100644 --- a/media/filters/android/media_codec_audio_decoder.cc +++ b/media/filters/android/media_codec_audio_decoder.cc
@@ -275,10 +275,10 @@ input_data.length = decoder_buffer->data_size(); const DecryptConfig* decrypt_config = decoder_buffer->decrypt_config(); if (decrypt_config && decrypt_config->is_encrypted()) { - input_data.is_encrypted = true; input_data.key_id = decrypt_config->key_id(); input_data.iv = decrypt_config->iv(); input_data.subsamples = decrypt_config->subsamples(); + input_data.encryption_scheme = config_.encryption_scheme(); } input_data.presentation_time = decoder_buffer->timestamp(); }
diff --git a/media/filters/decoder_stream_traits.cc b/media/filters/decoder_stream_traits.cc index deaedddc..ec6c799 100644 --- a/media/filters/decoder_stream_traits.cc +++ b/media/filters/decoder_stream_traits.cc
@@ -13,10 +13,38 @@ namespace media { +// Audio decoder stream traits implementation. + +// static std::string DecoderStreamTraits<DemuxerStream::AUDIO>::ToString() { return "audio"; } +// static +bool DecoderStreamTraits<DemuxerStream::AUDIO>::NeedsBitstreamConversion( + DecoderType* decoder) { + return decoder->NeedsBitstreamConversion(); +} + +// static +void DecoderStreamTraits<DemuxerStream::AUDIO>::ReportStatistics( + const StatisticsCB& statistics_cb, + int bytes_decoded) { + PipelineStatistics statistics; + statistics.audio_bytes_decoded = bytes_decoded; + statistics_cb.Run(statistics); +} + +// static +scoped_refptr<DecoderStreamTraits<DemuxerStream::AUDIO>::OutputType> + DecoderStreamTraits<DemuxerStream::AUDIO>::CreateEOSOutput() { + return OutputType::CreateEOSBuffer(); +} + +DecoderStreamTraits<DemuxerStream::AUDIO>::DecoderStreamTraits( + const scoped_refptr<MediaLog>& media_log) + : media_log_(media_log) {} + void DecoderStreamTraits<DemuxerStream::AUDIO>::InitializeDecoder( DecoderType* decoder, DemuxerStream* stream, @@ -28,62 +56,6 @@ output_cb); } -bool DecoderStreamTraits<DemuxerStream::AUDIO>::NeedsBitstreamConversion( - DecoderType* decoder) { - return decoder->NeedsBitstreamConversion(); -} - -void DecoderStreamTraits<DemuxerStream::AUDIO>::ReportStatistics( - const StatisticsCB& statistics_cb, - int bytes_decoded) { - PipelineStatistics statistics; - statistics.audio_bytes_decoded = bytes_decoded; - statistics_cb.Run(statistics); -} - -scoped_refptr<DecoderStreamTraits<DemuxerStream::AUDIO>::OutputType> - DecoderStreamTraits<DemuxerStream::AUDIO>::CreateEOSOutput() { - return OutputType::CreateEOSBuffer(); -} - -std::string DecoderStreamTraits<DemuxerStream::VIDEO>::ToString() { - return "video"; -} - -void DecoderStreamTraits<DemuxerStream::VIDEO>::InitializeDecoder( - DecoderType* decoder, - DemuxerStream* stream, - CdmContext* cdm_context, - const InitCB& init_cb, - const OutputCB& output_cb) { - DCHECK(stream->video_decoder_config().IsValidConfig()); - decoder->Initialize(stream->video_decoder_config(), - stream->liveness() == DemuxerStream::LIVENESS_LIVE, - cdm_context, init_cb, output_cb); -} - -bool DecoderStreamTraits<DemuxerStream::VIDEO>::NeedsBitstreamConversion( - DecoderType* decoder) { - return decoder->NeedsBitstreamConversion(); -} - -void DecoderStreamTraits<DemuxerStream::VIDEO>::ReportStatistics( - const StatisticsCB& statistics_cb, - int bytes_decoded) { - PipelineStatistics statistics; - statistics.video_bytes_decoded = bytes_decoded; - statistics_cb.Run(statistics); -} - -scoped_refptr<DecoderStreamTraits<DemuxerStream::VIDEO>::OutputType> - DecoderStreamTraits<DemuxerStream::VIDEO>::CreateEOSOutput() { - return OutputType::CreateEOSFrame(); -} - -DecoderStreamTraits<DemuxerStream::AUDIO>::DecoderStreamTraits( - const scoped_refptr<MediaLog>& media_log) - : media_log_(media_log) {} - void DecoderStreamTraits<DemuxerStream::AUDIO>::OnStreamReset( DemuxerStream* stream) { DCHECK(stream); @@ -103,4 +75,44 @@ audio_ts_validator_->RecordOutputDuration(buffer); } +// Video decoder stream traits implementation. + +// static +std::string DecoderStreamTraits<DemuxerStream::VIDEO>::ToString() { + return "video"; +} + +// static +bool DecoderStreamTraits<DemuxerStream::VIDEO>::NeedsBitstreamConversion( + DecoderType* decoder) { + return decoder->NeedsBitstreamConversion(); +} + +// static +void DecoderStreamTraits<DemuxerStream::VIDEO>::ReportStatistics( + const StatisticsCB& statistics_cb, + int bytes_decoded) { + PipelineStatistics statistics; + statistics.video_bytes_decoded = bytes_decoded; + statistics_cb.Run(statistics); +} + +// static +scoped_refptr<DecoderStreamTraits<DemuxerStream::VIDEO>::OutputType> + DecoderStreamTraits<DemuxerStream::VIDEO>::CreateEOSOutput() { + return OutputType::CreateEOSFrame(); +} + +void DecoderStreamTraits<DemuxerStream::VIDEO>::InitializeDecoder( + DecoderType* decoder, + DemuxerStream* stream, + CdmContext* cdm_context, + const InitCB& init_cb, + const OutputCB& output_cb) { + DCHECK(stream->video_decoder_config().IsValidConfig()); + decoder->Initialize(stream->video_decoder_config(), + stream->liveness() == DemuxerStream::LIVENESS_LIVE, + cdm_context, init_cb, output_cb); +} + } // namespace media
diff --git a/media/filters/decoder_stream_traits.h b/media/filters/decoder_stream_traits.h index 46311cc7..63951c1 100644 --- a/media/filters/decoder_stream_traits.h +++ b/media/filters/decoder_stream_traits.h
@@ -34,21 +34,22 @@ typedef base::Callback<void(bool success)> InitCB; typedef base::Callback<void(const scoped_refptr<OutputType>&)> OutputCB; + static std::string ToString(); + static bool NeedsBitstreamConversion(DecoderType* decoder); + static void ReportStatistics(const StatisticsCB& statistics_cb, + int bytes_decoded); + static scoped_refptr<OutputType> CreateEOSOutput(); + explicit DecoderStreamTraits(const scoped_refptr<MediaLog>& media_log); - static std::string ToString(); void InitializeDecoder(DecoderType* decoder, DemuxerStream* stream, CdmContext* cdm_context, const InitCB& init_cb, const OutputCB& output_cb); - static bool NeedsBitstreamConversion(DecoderType* decoder); void OnDecode(const scoped_refptr<DecoderBuffer>& buffer); void OnDecodeDone(const scoped_refptr<OutputType>& buffer); void OnStreamReset(DemuxerStream* stream); - static void ReportStatistics(const StatisticsCB& statistics_cb, - int bytes_decoded); - static scoped_refptr<OutputType> CreateEOSOutput(); private: // Validates encoded timestamps match decoded output duration. MEDIA_LOG warns @@ -68,21 +69,22 @@ typedef base::Callback<void(bool success)> InitCB; typedef base::Callback<void(const scoped_refptr<OutputType>&)> OutputCB; + static std::string ToString(); + static bool NeedsBitstreamConversion(DecoderType* decoder); + static void ReportStatistics(const StatisticsCB& statistics_cb, + int bytes_decoded); + static scoped_refptr<OutputType> CreateEOSOutput(); + explicit DecoderStreamTraits(const scoped_refptr<MediaLog>& media_log) {} - static std::string ToString(); void InitializeDecoder(DecoderType* decoder, DemuxerStream* stream, CdmContext* cdm_context, const InitCB& init_cb, const OutputCB& output_cb); - static bool NeedsBitstreamConversion(DecoderType* decoder); void OnDecode(const scoped_refptr<DecoderBuffer>& buffer) {} void OnDecodeDone(const scoped_refptr<OutputType>& buffer) {} void OnStreamReset(DemuxerStream* stream) {} - static void ReportStatistics(const StatisticsCB& statistics_cb, - int bytes_decoded); - static scoped_refptr<OutputType> CreateEOSOutput(); }; } // namespace media
diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc index 093e4f5..eab193e 100644 --- a/media/filters/ffmpeg_demuxer.cc +++ b/media/filters/ffmpeg_demuxer.cc
@@ -275,6 +275,7 @@ video_rotation_(VIDEO_ROTATION_0), is_enabled_(true), waiting_for_keyframe_(false), + aborted_(false), fixup_negative_timestamps_(false) { DCHECK(demuxer_); @@ -591,9 +592,11 @@ end_of_stream_ = false; last_packet_timestamp_ = kNoTimestamp; last_packet_duration_ = kNoTimestamp; + aborted_ = false; } void FFmpegDemuxerStream::Abort() { + aborted_ = true; if (!read_cb_.is_null()) base::ResetAndReturn(&read_cb_).Run(DemuxerStream::kAborted, nullptr); } @@ -641,6 +644,11 @@ return; } + if (aborted_) { + base::ResetAndReturn(&read_cb_).Run(kAborted, nullptr); + return; + } + SatisfyPendingRead(); }
diff --git a/media/filters/ffmpeg_demuxer.h b/media/filters/ffmpeg_demuxer.h index 6c065de..8b9320ed 100644 --- a/media/filters/ffmpeg_demuxer.h +++ b/media/filters/ffmpeg_demuxer.h
@@ -185,6 +185,7 @@ VideoRotation video_rotation_; bool is_enabled_; bool waiting_for_keyframe_; + bool aborted_; DecoderBufferQueue buffer_queue_; ReadCB read_cb_;
diff --git a/media/filters/ffmpeg_demuxer_unittest.cc b/media/filters/ffmpeg_demuxer_unittest.cc index 4225530a..eda72002 100644 --- a/media/filters/ffmpeg_demuxer_unittest.cc +++ b/media/filters/ffmpeg_demuxer_unittest.cc
@@ -437,6 +437,10 @@ demuxer_->AbortPendingReads(); base::RunLoop().Run(); + // Additional reads should also be aborted (until a Seek()). + audio->Read(NewReadCB(FROM_HERE, 29, 0, true, DemuxerStream::kAborted)); + base::RunLoop().Run(); + // Ensure blocking thread has completed outstanding work. demuxer_->Stop(); EXPECT_EQ(format_context()->pb->eof_reached, 0);
diff --git a/media/filters/gpu_video_decoder.cc b/media/filters/gpu_video_decoder.cc index 54fa866..6a4bcad 100644 --- a/media/filters/gpu_video_decoder.cc +++ b/media/filters/gpu_video_decoder.cc
@@ -357,8 +357,8 @@ VideoDecodeAccelerator::Config vda_config; vda_config.profile = config_.profile(); vda_config.cdm_id = cdm_id_; - vda_config.is_encrypted = config_.is_encrypted(); vda_config.surface_id = surface_id; + vda_config.encryption_scheme = config_.encryption_scheme(); vda_config.is_deferred_initialization_allowed = true; vda_config.initial_expected_coded_size = config_.coded_size();
diff --git a/media/gpu/android_video_decode_accelerator.cc b/media/gpu/android_video_decode_accelerator.cc index b02f45e..abf3776 100644 --- a/media/gpu/android_video_decode_accelerator.cc +++ b/media/gpu/android_video_decode_accelerator.cc
@@ -333,7 +333,7 @@ // We signaled that we support deferred initialization, so see if the client // does also. deferred_initialization_pending_ = config.is_deferred_initialization_allowed; - if (config_.is_encrypted && !deferred_initialization_pending_) { + if (config_.is_encrypted() && !deferred_initialization_pending_) { DLOG(ERROR) << "Deferred initialization must be used for encrypted streams"; return false; } @@ -374,7 +374,7 @@ return false; // If we are encrypted, then we aren't able to create the codec yet. - if (config_.is_encrypted) { + if (config_.is_encrypted()) { InitializeCdm(); return true; } @@ -504,7 +504,7 @@ } else { status = media_codec_->QueueSecureInputBuffer( input_buf_index, memory, bitstream_buffer.size(), key_id, iv, - subsamples, presentation_timestamp); + subsamples, config_.encryption_scheme, presentation_timestamp); } DVLOG(2) << __func__ @@ -1452,8 +1452,8 @@ const { // Prevent MediaCodec from using its internal software decoders when we have // more secure and up to date versions in the renderer process. - return !config_.is_encrypted && (codec_config_->codec == kCodecVP8 || - codec_config_->codec == kCodecVP9); + return !config_.is_encrypted() && (codec_config_->codec == kCodecVP8 || + codec_config_->codec == kCodecVP9); } bool AndroidVideoDecodeAccelerator::UpdateSurface() {
diff --git a/media/gpu/dxva_video_decode_accelerator_win.cc b/media/gpu/dxva_video_decode_accelerator_win.cc index 935cd72..6af7501 100644 --- a/media/gpu/dxva_video_decode_accelerator_win.cc +++ b/media/gpu/dxva_video_decode_accelerator_win.cc
@@ -536,7 +536,7 @@ return false; } - if (config.is_encrypted) { + if (config.is_encrypted()) { NOTREACHED() << "Encrypted streams are not supported for this VDA"; return false; }
diff --git a/media/gpu/fake_video_decode_accelerator.cc b/media/gpu/fake_video_decode_accelerator.cc index 533cabf..f4386c6a 100644 --- a/media/gpu/fake_video_decode_accelerator.cc +++ b/media/gpu/fake_video_decode_accelerator.cc
@@ -49,7 +49,7 @@ LOG(ERROR) << "unknown codec profile"; return false; } - if (config.is_encrypted) { + if (config.is_encrypted()) { NOTREACHED() << "encrypted streams are not supported"; return false; }
diff --git a/media/gpu/ipc/common/media_param_traits_macros.h b/media/gpu/ipc/common/media_param_traits_macros.h index e650f05..6904202d 100644 --- a/media/gpu/ipc/common/media_param_traits_macros.h +++ b/media/gpu/ipc/common/media_param_traits_macros.h
@@ -7,7 +7,7 @@ #include "gpu/config/gpu_info.h" #include "ipc/ipc_message_macros.h" -#include "media/base/ipc/media_param_traits_macros.h" +#include "media/base/ipc/media_param_traits.h" #include "media/gpu/ipc/common/create_video_encoder_params.h" #include "media/video/jpeg_decode_accelerator.h" #include "media/video/video_decode_accelerator.h" @@ -21,7 +21,7 @@ IPC_STRUCT_TRAITS_BEGIN(media::VideoDecodeAccelerator::Config) IPC_STRUCT_TRAITS_MEMBER(profile) - IPC_STRUCT_TRAITS_MEMBER(is_encrypted) + IPC_STRUCT_TRAITS_MEMBER(encryption_scheme) IPC_STRUCT_TRAITS_MEMBER(cdm_id) IPC_STRUCT_TRAITS_MEMBER(is_deferred_initialization_allowed) IPC_STRUCT_TRAITS_MEMBER(surface_id)
diff --git a/media/gpu/ipc/service/gpu_video_decode_accelerator.cc b/media/gpu/ipc/service/gpu_video_decode_accelerator.cc index d6d18b1..cb0b3c8c 100644 --- a/media/gpu/ipc/service/gpu_video_decode_accelerator.cc +++ b/media/gpu/ipc/service/gpu_video_decode_accelerator.cc
@@ -371,7 +371,7 @@ vda_factory->CreateVDA(this, config, gpu_workarounds, gpu_preferences); if (!video_decode_accelerator_) { LOG(ERROR) << "HW video decode not available for profile " << config.profile - << (config.is_encrypted ? " with encryption" : ""); + << (config.is_encrypted() ? " with encryption" : ""); return false; }
diff --git a/media/gpu/v4l2_slice_video_decode_accelerator.cc b/media/gpu/v4l2_slice_video_decode_accelerator.cc index 029d92cf..f825ca4b 100644 --- a/media/gpu/v4l2_slice_video_decode_accelerator.cc +++ b/media/gpu/v4l2_slice_video_decode_accelerator.cc
@@ -518,7 +518,7 @@ DCHECK(child_task_runner_->BelongsToCurrentThread()); DCHECK_EQ(state_, kUninitialized); - if (config.is_encrypted) { + if (config.is_encrypted()) { NOTREACHED() << "Encrypted streams are not supported for this VDA"; return false; }
diff --git a/media/gpu/v4l2_video_decode_accelerator.cc b/media/gpu/v4l2_video_decode_accelerator.cc index 15408f1d..0df948c 100644 --- a/media/gpu/v4l2_video_decode_accelerator.cc +++ b/media/gpu/v4l2_video_decode_accelerator.cc
@@ -205,7 +205,7 @@ DCHECK(child_task_runner_->BelongsToCurrentThread()); DCHECK_EQ(decoder_state_, kUninitialized); - if (config.is_encrypted) { + if (config.is_encrypted()) { NOTREACHED() << "Encrypted streams are not supported for this VDA"; return false; }
diff --git a/media/gpu/vaapi_video_decode_accelerator.cc b/media/gpu/vaapi_video_decode_accelerator.cc index 372a257..755e9ab 100644 --- a/media/gpu/vaapi_video_decode_accelerator.cc +++ b/media/gpu/vaapi_video_decode_accelerator.cc
@@ -333,7 +333,7 @@ Client* client) { DCHECK(task_runner_->BelongsToCurrentThread()); - if (config.is_encrypted) { + if (config.is_encrypted()) { NOTREACHED() << "Encrypted streams are not supported for this VDA"; return false; }
diff --git a/media/gpu/vt_video_decode_accelerator_mac.cc b/media/gpu/vt_video_decode_accelerator_mac.cc index f79b2da9..59cb2ce 100644 --- a/media/gpu/vt_video_decode_accelerator_mac.cc +++ b/media/gpu/vt_video_decode_accelerator_mac.cc
@@ -306,7 +306,7 @@ return false; } - if (config.is_encrypted) { + if (config.is_encrypted()) { NOTREACHED() << "Encrypted streams are not supported for this VDA"; return false; }
diff --git a/media/mojo/clients/BUILD.gn b/media/mojo/clients/BUILD.gn index 98d2bcd..e1d9bdf 100644 --- a/media/mojo/clients/BUILD.gn +++ b/media/mojo/clients/BUILD.gn
@@ -11,7 +11,7 @@ # TODO(xhwang): Only allow //media/mojo:media_mojo_unittests "//media/mojo:*", - # TODO(xhwang): Only allow //media/mojo/services:media_mojo_shell_unittests + # TODO(xhwang): Only allow //media/mojo/services:media_service_unittests "//media/mojo/services:*", "//media/test/*",
diff --git a/media/mojo/services/BUILD.gn b/media/mojo/services/BUILD.gn index dd57bde..a27ce33 100644 --- a/media/mojo/services/BUILD.gn +++ b/media/mojo/services/BUILD.gn
@@ -150,11 +150,11 @@ ] } -test("media_mojo_shell_unittests") { +test("media_service_unittests") { testonly = true sources = [ - "media_mojo_unittest.cc", + "media_service_unittest.cc", ] deps = [ @@ -181,7 +181,7 @@ } service_manifest("test_manifest") { - name = "media_mojo_shell_unittests" + name = "media_service_unittests" source = "test_manifest.json" }
diff --git a/media/mojo/services/media_mojo_unittest.cc b/media/mojo/services/media_service_unittest.cc similarity index 99% rename from media/mojo/services/media_mojo_unittest.cc rename to media/mojo/services/media_service_unittest.cc index ed5e770..c3156ad 100644 --- a/media/mojo/services/media_mojo_unittest.cc +++ b/media/mojo/services/media_service_unittest.cc
@@ -68,7 +68,7 @@ class MediaServiceTest : public service_manager::test::ServiceTest { public: MediaServiceTest() - : ServiceTest("media_mojo_shell_unittests"), + : ServiceTest("media_service_unittests"), renderer_client_binding_(&renderer_client_), video_stream_(DemuxerStream::VIDEO) {} ~MediaServiceTest() override {}
diff --git a/media/mojo/services/test_manifest.json b/media/mojo/services/test_manifest.json index 27063eb..1ff56985 100644 --- a/media/mojo/services/test_manifest.json +++ b/media/mojo/services/test_manifest.json
@@ -1,6 +1,6 @@ { - "name": "media_mojo_shell_unittests", - "display_name": "Media Mojo Shell Unittests", + "name": "media_service_unittests", + "display_name": "Media Service Unittests", "interface_provider_specs": { "service_manager:connector": { "requires": {
diff --git a/media/video/video_decode_accelerator.cc b/media/video/video_decode_accelerator.cc index 399607c..b2b8a53 100644 --- a/media/video/video_decode_accelerator.cc +++ b/media/video/video_decode_accelerator.cc
@@ -20,7 +20,7 @@ std::string VideoDecodeAccelerator::Config::AsHumanReadableString() const { std::ostringstream s; s << "profile: " << GetProfileName(profile) << " encrypted? " - << (is_encrypted ? "true" : "false"); + << (is_encrypted() ? "true" : "false"); return s.str(); }
diff --git a/media/video/video_decode_accelerator.h b/media/video/video_decode_accelerator.h index c14050c6..462c08b 100644 --- a/media/video/video_decode_accelerator.h +++ b/media/video/video_decode_accelerator.h
@@ -14,6 +14,7 @@ #include "base/memory/weak_ptr.h" #include "media/base/bitstream_buffer.h" #include "media/base/cdm_context.h" +#include "media/base/encryption_scheme.h" #include "media/base/surface_manager.h" #include "media/base/video_decoder_config.h" #include "media/video/picture.h" @@ -127,12 +128,13 @@ ~Config(); std::string AsHumanReadableString() const; + bool is_encrypted() const { return encryption_scheme.is_encrypted(); } // The video codec and profile. VideoCodecProfile profile = VIDEO_CODEC_PROFILE_UNKNOWN; - // Whether the stream is encrypted. - bool is_encrypted = false; + // Whether the stream is encrypted, and, if so, the scheme used. + EncryptionScheme encryption_scheme; // The CDM that the VDA should use to decode encrypted streams. Must be // set to a valid ID if |is_encrypted|.
diff --git a/net/http/transport_security_state_static.json b/net/http/transport_security_state_static.json index 367ef139..b38cbd2 100644 --- a/net/http/transport_security_state_static.json +++ b/net/http/transport_security_state_static.json
@@ -8783,7 +8783,9 @@ { "name": "winebid.com", "include_subdomains": true, "mode": "force-https" }, // Entries submitted through hstspreload.appspot.com in Chrome 51 or later. - // START OF BULK ADDITIONS + // These entries are subject to the continued requirements documented at + // https://hstspreload.org/#continued-requirements + // START OF BULK ENTRIES { "name": "050media.nl", "include_subdomains": true, "mode": "force-https" }, { "name": "0x.sk", "include_subdomains": true, "mode": "force-https" }, { "name": "0x44.net", "include_subdomains": true, "mode": "force-https" }, @@ -18746,11 +18748,11 @@ { "name": "rijk-catering.nl", "include_subdomains": true, "mode": "force-https" }, { "name": "rvsbevestigingen.nl", "include_subdomains": true, "mode": "force-https" }, { "name": "yhb.io", "include_subdomains": true, "mode": "force-https" }, - // END OF BULK ADDITIONS + // END OF BULK ENTRIES - // Manual additions in Chrome 51 or later that do not belong in a + // Manual additions and changes in Chrome 51 or later that do not belong in a // special section above. - // START OF MANUAL ADDITIONS + // START OF MANUAL ENTRIES { "name": "bodhi.fedoraproject.org", "include_subdomains": true, "mode": "force-https" }, { "name": "communityblog.fedoraproject.org", "include_subdomains": true, "mode": "force-https" }, { "name": "keys.fedoraproject.org", "include_subdomains": true, "mode": "force-https" }, @@ -18799,7 +18801,7 @@ { "name": "www.amazon.nl", "include_subdomains": true, "mode": "force-https" }, { "name": "crypto.is", "include_subdomains": true, "mode": "force-https", "expect_ct": true, "expect_ct_report_uri": "https://clients3.google.com/ct_upload" }, { "name": "ritter.vg", "expect_ct": true, "expect_ct_report_uri": "https://clients3.google.com/ct_upload", "expect_staple": true, "expect_staple_report_uri": "https://asac.casa/expectstaple.jsp" }, - // END OF MANUAL ADDITIONS + // END OF MANUAL ENTRIES // To avoid trailing comma changes from showing up in diffs, we place a // single entry at the end.
diff --git a/net/http2/decoder/decode_buffer.cc b/net/http2/decoder/decode_buffer.cc new file mode 100644 index 0000000..15235bae --- /dev/null +++ b/net/http2/decoder/decode_buffer.cc
@@ -0,0 +1,90 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/decode_buffer.h" + +namespace net { + +bool DecodeBuffer::SlowDecodeUnsignedInt(uint32_t field_size, + uint32_t field_offset, + uint32_t* decode_offset, + uint32_t* value) { + DCHECK_LT(0u, field_size); + DCHECK_LE(field_size, 4u); + DCHECK(decode_offset != nullptr); + DCHECK_LE(field_offset, *decode_offset); + const uint32_t next_field_offset = field_offset + field_size; + if (*decode_offset == field_offset) { + // Starting to decode field. It is possible we will reach this point + // twice, once when we've just exhausted the input, and once when + // resuming decoding with a new input buffer. + // Clear the field; we do NOT assume that the caller has done so + // previously. + *value = 0; + } else if (*decode_offset >= next_field_offset) { + // We already decoded this field. + return true; + } + do { + if (Empty()) { + return false; // Not done decoding. + } + *value = *value << 8 | DecodeUInt8(); + (*decode_offset)++; + } while (*decode_offset < next_field_offset); + return true; +} + +bool DecodeBuffer::SlowDecodeUInt8(uint32_t field_offset, + uint32_t* decode_offset, + uint8_t* value) { + uint32_t tmp = *value; + const bool done = SlowDecodeUnsignedInt(1 /* field_size */, field_offset, + decode_offset, &tmp); + *value = tmp & 0xff; + DCHECK_EQ(tmp, *value); + return done; +} + +bool DecodeBuffer::SlowDecodeUInt16(uint32_t field_offset, + uint32_t* decode_offset, + uint16_t* value) { + uint32_t tmp = *value; + const bool done = SlowDecodeUnsignedInt(2 /* field_size */, field_offset, + decode_offset, &tmp); + *value = tmp & 0xffff; + DCHECK_EQ(tmp, *value); + return done; +} + +bool DecodeBuffer::SlowDecodeUInt24(uint32_t field_offset, + uint32_t* decode_offset, + uint32_t* value) { + uint32_t tmp = *value; + const bool done = SlowDecodeUnsignedInt(3 /* field_size */, field_offset, + decode_offset, &tmp); + *value = tmp & 0xffffff; + DCHECK_EQ(tmp, *value); + return done; +} + +bool DecodeBuffer::SlowDecodeUInt31(uint32_t field_offset, + uint32_t* decode_offset, + uint32_t* value) { + uint32_t tmp = *value; + const bool done = SlowDecodeUnsignedInt(4 /* field_size */, field_offset, + decode_offset, &tmp); + *value = tmp & 0x7fffffff; + DCHECK_EQ(tmp & 0x7fffffff, *value); + return done; +} + +bool DecodeBuffer::SlowDecodeUInt32(uint32_t field_offset, + uint32_t* decode_offset, + uint32_t* value) { + return SlowDecodeUnsignedInt(4 /* field_size */, field_offset, decode_offset, + value); +} + +} // namespace net
diff --git a/net/http2/decoder/decode_buffer.h b/net/http2/decoder/decode_buffer.h new file mode 100644 index 0000000..56f695f4 --- /dev/null +++ b/net/http2/decoder/decode_buffer.h
@@ -0,0 +1,289 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_DECODE_BUFFER_H_ +#define NET_HTTP2_DECODER_DECODE_BUFFER_H_ + +// DecodeBuffer provides primitives for decoding various integer types found +// in HTTP/2 frames. +// DecodeBuffer wraps a byte array from which we can read and decode serialized +// HTTP/2 frames, or parts thereof. DecodeBuffer is intended only for stack +// allocation, where the caller is typically going to use the DecodeBuffer +// instance as part of decoding the entire buffer before returning to its own +// caller. Only the concrete Slow* methods are defined in the cc file, +// all other methods are defined in this header file to enable inlining. + +#include <stddef.h> +#include <stdint.h> + +#include <algorithm> + +#include "base/logging.h" +#include "base/macros.h" +#include "base/strings/string_piece.h" +#include "net/base/net_export.h" + +namespace net { +class DecodeBufferSubset; + +class NET_EXPORT_PRIVATE DecodeBuffer { + public: + DecodeBuffer(const char* buffer, size_t len) + : buffer_(buffer), cursor_(buffer), beyond_(buffer + len) { + DCHECK_NE(buffer, nullptr); + DCHECK_LE(len, MaxDecodeBufferLength()); + } + explicit DecodeBuffer(base::StringPiece s) + : DecodeBuffer(s.data(), s.size()) {} + // Constructor for character arrays, typically in tests. For example: + // const char input[] = { 0x11 }; + // DecodeBuffer b(input); + template <size_t N> + explicit DecodeBuffer(const char (&buf)[N]) : DecodeBuffer(buf, N) {} + + bool Empty() const { return cursor_ >= beyond_; } + bool HasData() const { return cursor_ < beyond_; } + size_t Remaining() const { + DCHECK_LE(cursor_, beyond_); + return beyond_ - cursor_; + } + size_t Offset() const { return cursor_ - buffer_; } + size_t FullSize() const { return beyond_ - buffer_; } + + // Returns the minimum of the number of bytes remaining in this DecodeBuffer + // and |length|, in support of determining how much of some structure/payload + // is in this DecodeBuffer. + size_t MinLengthRemaining(size_t length) const { + return std::min(length, Remaining()); + } + + // For string decoding, returns a pointer to the next byte/char to be decoded. + const char* cursor() const { return cursor_; } + // Advances the cursor (pointer to the next byte/char to be decoded). + void AdvanceCursor(size_t amount) { + DCHECK_LE(amount, Remaining()); // Need at least that much remaining. + DCHECK_EQ(subset_, nullptr) << "Access via subset only when present."; + cursor_ += amount; + } + + // Only call methods starting "Decode" when there is enough input remaining. + char DecodeChar() { + DCHECK_LE(1u, Remaining()); // Need at least one byte remaining. + DCHECK_EQ(subset_, nullptr) << "Access via subset only when present."; + return *cursor_++; + } + + uint8_t DecodeUInt8() { return static_cast<uint8_t>(DecodeChar()); } + + uint16_t DecodeUInt16() { + DCHECK_LE(2u, Remaining()); + const uint8_t b1 = DecodeUInt8(); + const uint8_t b2 = DecodeUInt8(); + // Note that chars are automatically promoted to ints during arithmetic, + // so the b1 << 8 doesn't end up as zero before being or-ed with b2. + // And the left-shift operator has higher precedence than the or operator. + return b1 << 8 | b2; + } + + uint32_t DecodeUInt24() { + DCHECK_LE(3u, Remaining()); + const uint8_t b1 = DecodeUInt8(); + const uint8_t b2 = DecodeUInt8(); + const uint8_t b3 = DecodeUInt8(); + return b1 << 16 | b2 << 8 | b3; + } + + // For 31-bit unsigned integers, where the 32nd bit is reserved for future + // use (i.e. the high-bit of the first byte of the encoding); examples: + // the Stream Id in a frame header or the Window Size Increment in a + // WINDOW_UPDATE frame. + uint32_t DecodeUInt31() { + DCHECK_LE(4u, Remaining()); + const uint8_t b1 = DecodeUInt8() & 0x7f; // Mask out the high order bit. + const uint8_t b2 = DecodeUInt8(); + const uint8_t b3 = DecodeUInt8(); + const uint8_t b4 = DecodeUInt8(); + return b1 << 24 | b2 << 16 | b3 << 8 | b4; + } + + uint32_t DecodeUInt32() { + DCHECK_LE(4u, Remaining()); + const uint8_t b1 = DecodeUInt8(); + const uint8_t b2 = DecodeUInt8(); + const uint8_t b3 = DecodeUInt8(); + const uint8_t b4 = DecodeUInt8(); + return b1 << 24 | b2 << 16 | b3 << 8 | b4; + } + + // SlowDecode* routines are used for decoding a multi-field structure when + // there may not be enough bytes in the buffer to decode the entirety of the + // structure. + + // Read as much of an unsigned int field of an encoded structure as possible, + // keeping track via decode_offset of our position in the encoded structure. + // Returns true if the field has been fully decoded. + // |field_size| is the number of bytes of the encoding of the field (usually + // a compile time fixed value). + // |field_offset| is the offset of the first byte of the encoding of the field + // within the encoding of that structure (usually a compile time fixed value). + // |*decode_offset| is the offset of the byte to be decoded next. + // |*value| is the storage for the decoded value, and is used for storing + // partially decoded values; if some, but not all, bytes of the encoding are + // available then this method will return having stored the decoded bytes into + // *value. + bool SlowDecodeUnsignedInt(uint32_t field_size, + uint32_t field_offset, + uint32_t* decode_offset, + uint32_t* value); + + // Like SlowDecodeUnsignedInt, but specifically for 8-bit unsigned integers. + // Obviously a byte can't be split (on our byte addressable machines), but + // a larger structure containing such a field might be. + bool SlowDecodeUInt8(uint32_t field_offset, + uint32_t* decode_offset, + uint8_t* value); + + // Like SlowDecodeUnsignedInt, but specifically for 16-bit unsigned integers. + bool SlowDecodeUInt16(uint32_t field_offset, + uint32_t* decode_offset, + uint16_t* value); + + // Like SlowDecodeUnsignedInt, but specifically for 24-bit unsigned integers. + bool SlowDecodeUInt24(uint32_t field_offset, + uint32_t* decode_offset, + uint32_t* value); + + // Like SlowDecodeUnsignedInt, but specifically for 31-bit unsigned integers. + // (same definition as for DecodeUInt31). + bool SlowDecodeUInt31(uint32_t field_offset, + uint32_t* decode_offset, + uint32_t* value); + + // Like SlowDecodeUnsignedInt, but specifically for 31-bit unsigned integers. + bool SlowDecodeUInt32(uint32_t field_offset, + uint32_t* decode_offset, + uint32_t* value); + + // Decodes an enum value, where the size (in bytes) of the encoding must be + // stated explicitly. It is assumed that under the covers enums are really + // just integers, and that we can static_cast them to and from uint32. + template <typename E> + bool SlowDecodeEnum(uint32_t field_size, + uint32_t field_offset, + uint32_t* decode_offset, + E* value) { + uint32_t tmp = static_cast<uint32_t>(*value); + const bool done = + SlowDecodeUnsignedInt(field_size, field_offset, decode_offset, &tmp); + *value = static_cast<E>(tmp); + DCHECK_EQ(tmp, static_cast<uint32_t>(*value)); + return done; + } + + // We assume the decode buffers will typically be modest in size (i.e. a few + // K). + // Let's make sure during testing that we don't go very high, with 32MB + // selected rather arbitrarily. + static constexpr size_t MaxDecodeBufferLength() { return 1 << 25; } + + protected: +#ifndef NDEBUG + // These are part of validating during tests that there is at most one + // DecodeBufferSubset instance at a time for any DecodeBuffer instance. + void set_subset_of_base(DecodeBuffer* base, + const DecodeBufferSubset* subset) { + DCHECK_EQ(this, subset); + base->set_subset(subset); + } + void clear_subset_of_base(DecodeBuffer* base, + const DecodeBufferSubset* subset) { + DCHECK_EQ(this, subset); + base->clear_subset(subset); + } +#endif + + private: +#ifndef NDEBUG + void set_subset(const DecodeBufferSubset* subset) { + DCHECK(subset != nullptr); + DCHECK_EQ(subset_, nullptr) << "There is already a subset"; + subset_ = subset; + } + void clear_subset(const DecodeBufferSubset* subset) { + DCHECK(subset != nullptr); + DCHECK_EQ(subset_, subset); + subset_ = nullptr; + } +#endif + + // Prevent heap allocation of DecodeBuffer. + static void* operator new(size_t s); + static void* operator new[](size_t s); + static void operator delete(void* p); + static void operator delete[](void* p); + + const char* const buffer_; + const char* cursor_; + const char* const beyond_; + const DecodeBufferSubset* subset_ = nullptr; // Used for DCHECKs. + + DISALLOW_COPY_AND_ASSIGN(DecodeBuffer); +}; + +// DecodeBufferSubset is used when decoding a known sized chunk of data, which +// starts at base->cursor(), and continues for subset_len, which may be +// entirely in |base|, or may extend beyond it (hence the MinLengthRemaining +// in the constructor). +// There are two benefits to using DecodeBufferSubset: it ensures that the +// cursor of |base| is advanced when the subset's destructor runs, and it +// ensures that the consumer of the subset can't go beyond the subset which +// it is intended to decode. +// There must be only a single DecodeBufferSubset at a time for a base +// DecodeBuffer, though they can be nested (i.e. a DecodeBufferSubset's +// base may itself be a DecodeBufferSubset). This avoids the AdvanceCursor +// being called erroneously. +class DecodeBufferSubset : public DecodeBuffer { + public: + DecodeBufferSubset(DecodeBuffer* base, size_t subset_len) + : DecodeBuffer(base->cursor(), base->MinLengthRemaining(subset_len)), +#ifndef NDEBUG + start_base_offset_(base->Offset()), + max_base_offset_(start_base_offset_ + FullSize()), +#endif + base_buffer_(base) { +#ifndef NDEBUG + DCHECK_LE(max_base_offset_, base->FullSize()); + set_subset_of_base(base_buffer_, this); +#endif + } + + ~DecodeBufferSubset() { + size_t offset = Offset(); +#ifndef NDEBUG + clear_subset_of_base(base_buffer_, this); + DCHECK_LE(Offset(), FullSize()); + DCHECK_EQ(start_base_offset_, base_buffer_->Offset()) + << "The base buffer was modified"; + DCHECK_LE(offset, FullSize()); + DCHECK_LE(start_base_offset_ + offset, base_buffer_->FullSize()); +#endif + base_buffer_->AdvanceCursor(offset); +#ifndef NDEBUG + DCHECK_GE(max_base_offset_, base_buffer_->Offset()); +#endif + } + + private: +#ifndef NDEBUG + const size_t start_base_offset_; // Used for DCHECKs. + const size_t max_base_offset_; // Used for DCHECKs. +#endif + DecodeBuffer* const base_buffer_; + + DISALLOW_COPY_AND_ASSIGN(DecodeBufferSubset); +}; + +} // namespace net + +#endif // NET_HTTP2_DECODER_DECODE_BUFFER_H_
diff --git a/net/http2/decoder/decode_buffer_test.cc b/net/http2/decoder/decode_buffer_test.cc new file mode 100644 index 0000000..35a9fbb --- /dev/null +++ b/net/http2/decoder/decode_buffer_test.cc
@@ -0,0 +1,406 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/decode_buffer.h" + +#include <string> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/strings/string_piece.h" +#include "net/http2/tools/http2_random.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::StringPiece; +using std::string; + +namespace net { +namespace test { + +enum class TestEnumClass32 { + kValue1 = 1, + kValue99 = 99, + kValue1M = 1000000, +}; + +enum class TestEnumClass8 { + kValue1 = 1, + kValue2 = 1, + kValue99 = 99, + kValue255 = 255, +}; + +enum TestEnum8 { + kMaskLo = 0x01, + kMaskHi = 0x80, +}; + +struct TestStruct { + uint8_t f1; + uint16_t f2; + uint32_t f3; // Decoded as a uint24 + uint32_t f4; + uint32_t f5; // Decoded as if uint31 + TestEnumClass32 f6; + TestEnumClass8 f7; + TestEnum8 f8; +}; + +const size_t kF1Offset = 0; +const size_t kF2Offset = 1; +const size_t kF3Offset = 3; +const size_t kF4Offset = 6; +const size_t kF5Offset = 10; +const size_t kF6Offset = 14; +const size_t kF7Offset = 18; +const size_t kF8Offset = 19; + +class DecodeBufferTest : public ::testing::Test { + public: + DecodeBufferTest() {} + + protected: + // Double checks the call fn(f). + template <typename T> + bool SlowDecodeField(DecodeBuffer* b, + size_t field_size, + size_t field_offset, + const base::Callback<bool(DecodeBuffer*)>& fn, + T* f) { + VLOG(2) << "Remaining: " << b->Remaining(); + VLOG(2) << "field_size: " << field_size; + VLOG(2) << "field_offset: " << field_offset; + VLOG(2) << "decode_offset_: " << decode_offset_; + EXPECT_GE(decode_offset_, field_offset); + bool had_data = b->HasData(); + VLOG(2) << "had_data: " << had_data; + uint32_t old = static_cast<uint32_t>(*f); + VLOG(2) << "old: " << old; + size_t old_decode_offset = decode_offset_; + bool done = fn.Run(b); + VLOG(2) << "done: " << done; + if (old_decode_offset == decode_offset_) { + // Didn't do any decoding (may have no input, or may have already + // decoded this field). + if (done) { + EXPECT_LE(field_offset + field_size, decode_offset_); + // Shouldn't have modified already decoded field. + EXPECT_EQ(old, static_cast<uint32_t>(*f)); + } else { + EXPECT_TRUE(!had_data); + } + } else { + // Did some decoding. + EXPECT_TRUE(had_data); + EXPECT_LT(old_decode_offset, decode_offset_); + if (done) { + EXPECT_EQ(field_offset + field_size, decode_offset_); + } else { + EXPECT_GT(field_offset + field_size, decode_offset_); + } + } + VLOG(2) << "---------------------------------------"; + return done; + } + + bool decode_f1(TestStruct* p, DecodeBuffer* db) { + return db->SlowDecodeUInt8(kF1Offset, &decode_offset_, &p->f1); + } + bool decode_f2(TestStruct* p, DecodeBuffer* db) { + return db->SlowDecodeUInt16(kF2Offset, &decode_offset_, &p->f2); + } + bool decode_f3(TestStruct* p, DecodeBuffer* db) { + return db->SlowDecodeUInt24(kF3Offset, &decode_offset_, &p->f3); + } + bool decode_f4(TestStruct* p, DecodeBuffer* db) { + return db->SlowDecodeUInt32(kF4Offset, &decode_offset_, &p->f4); + } + bool decode_f5(TestStruct* p, DecodeBuffer* db) { + return db->SlowDecodeUInt31(kF5Offset, &decode_offset_, &p->f5); + } + bool decode_f6(TestStruct* p, DecodeBuffer* db) { + return db->SlowDecodeEnum(4, kF6Offset, &decode_offset_, &p->f6); + } + bool decode_f7(TestStruct* p, DecodeBuffer* db) { + return db->SlowDecodeEnum(1, kF7Offset, &decode_offset_, &p->f7); + } + bool decode_f8(TestStruct* p, DecodeBuffer* db) { + return db->SlowDecodeEnum(1, kF8Offset, &decode_offset_, &p->f8); + } + + void SlowDecodeTestStruct(StringPiece input, TestStruct* p) { + VLOG(2) << "############################################################"; + EXPECT_LE(10u, input.size()); + decode_offset_ = 0; + while (input.size() > 0) { + size_t size = input.size(); + // Sometimes check that zero length input is OK. + auto r = random_.Next(); + if (r % 100 == 0) { + size = 0; + } else if (size > 1) { + auto r = random_.Next(); + size = (r % size) + 1; + } + VLOG(2) << "================= input size " << size; + DecodeBuffer b(input.data(), size); + size_t old_decode_offset = decode_offset_; + if (SlowDecodeField(&b, 1, kF1Offset, + base::Bind(&DecodeBufferTest::decode_f1, + base::Unretained(this), p), + &p->f1) && + SlowDecodeField(&b, 2, kF2Offset, + base::Bind(&DecodeBufferTest::decode_f2, + base::Unretained(this), p), + &p->f2) && + SlowDecodeField(&b, 3, kF3Offset, + base::Bind(&DecodeBufferTest::decode_f3, + base::Unretained(this), p), + &p->f3) && + SlowDecodeField(&b, 4, kF4Offset, + base::Bind(&DecodeBufferTest::decode_f4, + base::Unretained(this), p), + &p->f4) && + SlowDecodeField(&b, 4, kF5Offset, + base::Bind(&DecodeBufferTest::decode_f5, + base::Unretained(this), p), + &p->f5) && + SlowDecodeField(&b, 4, kF6Offset, + base::Bind(&DecodeBufferTest::decode_f6, + base::Unretained(this), p), + &p->f6) && + SlowDecodeField(&b, 1, kF7Offset, + base::Bind(&DecodeBufferTest::decode_f7, + base::Unretained(this), p), + &p->f7) && + SlowDecodeField(&b, 1, kF8Offset, + base::Bind(&DecodeBufferTest::decode_f8, + base::Unretained(this), p), + &p->f8)) { + EXPECT_TRUE(b.Empty()); + EXPECT_EQ(size, input.size()); + EXPECT_EQ(input.size(), b.Offset()); // All input consumed. + return; + } + EXPECT_EQ(old_decode_offset + size, decode_offset_); + EXPECT_TRUE(b.Empty()); + EXPECT_EQ(size, b.Offset()); // All input consumed. + EXPECT_LT(size, input.size()); // More remains. + input = StringPiece(input.data() + size, input.size() - size); + } + ADD_FAILURE() << "Ran out of input! decode_offset_ = " << decode_offset_; + } + + Http2Random random_; + uint32_t decode_offset_; +}; + +TEST_F(DecodeBufferTest, DecodesFixedInts) { + const char data[] = "\x01\x12\x23\x34\x45\x56\x67\x78\x89\x9a"; + DecodeBuffer b1(data, strlen(data)); + EXPECT_EQ(1, b1.DecodeUInt8()); + EXPECT_EQ(0x1223u, b1.DecodeUInt16()); + EXPECT_EQ(0x344556u, b1.DecodeUInt24()); + EXPECT_EQ(0x6778899Au, b1.DecodeUInt32()); + + DecodeBuffer b2(data, strlen(data)); + uint8_t b; + decode_offset_ = 0; + EXPECT_TRUE(b2.SlowDecodeUInt8(0, &decode_offset_, &b)); + EXPECT_EQ(1, b); + uint16_t s; + decode_offset_ = 0; + EXPECT_TRUE(b2.SlowDecodeUInt16(0, &decode_offset_, &s)); + EXPECT_EQ(0x1223, s); + uint32_t i; + decode_offset_ = 0; + EXPECT_TRUE(b2.SlowDecodeUInt24(0, &decode_offset_, &i)); + // EXPECT_EQ(0x344556, b1.DecodeUInt24()); + // EXPECT_EQ(0x6778899a, b1.DecodeUInt32()); +} + +// Decode the structure many times, where we'll pass different partitions +// into DecodeSlowly. +TEST_F(DecodeBufferTest, SlowDecodeTestStruct) { + // clang-format off + const char data[] = { + 0x12u, // f1 + 0x23u, 0x34u, // f2 + 0x45u, 0x56u, 0x67u, // f3 + 0x78u, 0x89u, 0x9au, 0xabu, // f4 + 0xfeu, 0xedu, 0xdcu, 0xcbu, // f5 (high-bit will be cleared.) + 0x00u, 0x0fu, 0x42u, 0x40u, // f6 (kValue1M) + 0x63u, // f7 (kValue99) + 0x81u, // f8 (kMaskLo | kMaskHi) + }; + // clang-format on + StringPiece input(data, sizeof data); + for (int i = 0; i < 200; ++i) { + TestStruct ts; + // Init the struct to random garbage. + ts.f1 = random_.Rand8(); + ts.f2 = random_.Rand16(); + ts.f3 = random_.Rand32(); + ts.f4 = random_.Rand32(); + // Ensure high-bit is set. + ts.f5 = 0x80000000 | random_.Rand32(); // Ensure high-bit is set. + ts.f6 = static_cast<TestEnumClass32>(random_.Rand32()); + ts.f7 = static_cast<TestEnumClass8>(random_.Rand8()); + ts.f8 = static_cast<TestEnum8>(random_.Rand8()); + SlowDecodeTestStruct(input, &ts); + ASSERT_EQ(0x12u, ts.f1); + ASSERT_EQ(0x2334u, ts.f2); + ASSERT_EQ(0x455667u, ts.f3); + ASSERT_EQ(0x78899AABu, ts.f4); + ASSERT_EQ(0x7EEDDCCBu, ts.f5); + ASSERT_EQ(TestEnumClass32::kValue1M, ts.f6); + ASSERT_EQ(TestEnumClass8::kValue99, ts.f7); + ASSERT_EQ(kMaskLo | kMaskHi, ts.f8); + } +} + +// Make sure that DecodeBuffer is not copying input, just pointing into +// provided input buffer. +TEST_F(DecodeBufferTest, HasNotCopiedInput) { + const char data[] = "ab"; + DecodeBuffer b1(data, 2); + + EXPECT_EQ(2u, b1.Remaining()); + EXPECT_EQ(0u, b1.Offset()); + EXPECT_FALSE(b1.Empty()); + EXPECT_EQ(data, b1.cursor()); // cursor points to input buffer + EXPECT_TRUE(b1.HasData()); + + b1.AdvanceCursor(1); + + EXPECT_EQ(1u, b1.Remaining()); + EXPECT_EQ(1u, b1.Offset()); + EXPECT_FALSE(b1.Empty()); + EXPECT_EQ(&data[1], b1.cursor()); + EXPECT_TRUE(b1.HasData()); + + b1.AdvanceCursor(1); + + EXPECT_EQ(0u, b1.Remaining()); + EXPECT_EQ(2u, b1.Offset()); + EXPECT_TRUE(b1.Empty()); + EXPECT_EQ(&data[2], b1.cursor()); + EXPECT_FALSE(b1.HasData()); + + DecodeBuffer b2(data, 0); + + EXPECT_EQ(0u, b2.Remaining()); + EXPECT_EQ(0u, b2.Offset()); + EXPECT_TRUE(b2.Empty()); + EXPECT_EQ(data, b2.cursor()); + EXPECT_FALSE(b2.HasData()); +} + +// DecodeBufferSubset can't go beyond the end of the base buffer. +TEST_F(DecodeBufferTest, DecodeBufferSubsetLimited) { + const char data[] = "abc"; + DecodeBuffer base(data, 3); + base.AdvanceCursor(1); + DecodeBufferSubset subset(&base, 100); + EXPECT_EQ(2u, subset.FullSize()); +} + +// DecodeBufferSubset advances the cursor of its base upon destruction. +TEST_F(DecodeBufferTest, DecodeBufferSubsetAdvancesCursor) { + const char data[] = "abc"; + const size_t size = sizeof(data) - 1; + EXPECT_EQ(3u, size); + DecodeBuffer base(data, size); + { + // First no change to the cursor. + DecodeBufferSubset subset(&base, size + 100); + EXPECT_EQ(size, subset.FullSize()); + EXPECT_EQ(base.FullSize(), subset.FullSize()); + EXPECT_EQ(0u, subset.Offset()); + } + EXPECT_EQ(0u, base.Offset()); + EXPECT_EQ(size, base.Remaining()); +} + +// Make sure that DecodeBuffer ctor complains about bad args. +#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) +TEST(DecodeBufferDeathTest, NonNullBufferRequired) { + EXPECT_DEBUG_DEATH({ DecodeBuffer b(nullptr, 3); }, "nullptr"); +} + +// Make sure that DecodeBuffer ctor complains about bad args. +TEST(DecodeBufferDeathTest, ModestBufferSizeRequired) { + EXPECT_DEBUG_DEATH( + { + const char data[] = "abc"; + size_t len = 0; + DecodeBuffer b(data, ~len); + }, + "Max.*Length"); +} + +// Make sure that DecodeBuffer detects advance beyond end, in debug mode. +TEST(DecodeBufferDeathTest, LimitedAdvance) { + { + // Advance right up to end is OK. + const char data[] = "abc"; + DecodeBuffer b(data, 3); + b.AdvanceCursor(3); // OK + EXPECT_TRUE(b.Empty()); + } + EXPECT_DEBUG_DEATH( + { + // Going beyond is not OK. + const char data[] = "abc"; + DecodeBuffer b(data, 3); + b.AdvanceCursor(4); + }, + "4 vs. 3"); +} + +// Make sure that DecodeBuffer detects decode beyond end, in debug mode. +TEST(DecodeBufferDeathTest, DecodeUInt8PastEnd) { + const char data[] = {0x12, 0x23}; + DecodeBuffer b(data, sizeof data); + EXPECT_EQ(2u, b.FullSize()); + EXPECT_EQ(0x1223, b.DecodeUInt16()); + EXPECT_DEBUG_DEATH({ b.DecodeUInt8(); }, "1 vs. 0"); +} + +// Make sure that DecodeBuffer detects decode beyond end, in debug mode. +TEST(DecodeBufferDeathTest, DecodeUInt16OverEnd) { + const char data[] = {0x12, 0x23, 0x34}; + DecodeBuffer b(data, sizeof data); + EXPECT_EQ(3u, b.FullSize()); + EXPECT_EQ(0x1223, b.DecodeUInt16()); + EXPECT_DEBUG_DEATH({ b.DecodeUInt16(); }, "2 vs. 1"); +} + +// Make sure that DecodeBuffer doesn't agree with having two subsets. +TEST(DecodeBufferSubsetDeathTest, TwoSubsets) { + const char data[] = "abc"; + DecodeBuffer base(data, 3); + DecodeBufferSubset subset1(&base, 1); + EXPECT_DEBUG_DEATH({ DecodeBufferSubset subset2(&base, 1); }, + "There is already a subset"); +} + +// Make sure that DecodeBufferSubset notices when the base's cursor has moved. +TEST(DecodeBufferSubsetDeathTest, BaseCursorAdvanced) { + const char data[] = "abc"; + DecodeBuffer base(data, 3); + base.AdvanceCursor(1); + EXPECT_DEBUG_DEATH( + { + DecodeBufferSubset subset1(&base, 2); + base.AdvanceCursor(1); + }, + "Access via subset only when present"); +} +#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG) + +} // namespace test +} // namespace net
diff --git a/net/http2/decoder/decode_http2_structures.cc b/net/http2/decoder/decode_http2_structures.cc new file mode 100644 index 0000000..cd419e3 --- /dev/null +++ b/net/http2/decoder/decode_http2_structures.cc
@@ -0,0 +1,355 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/decode_http2_structures.h" + +#include <string.h> + +#include "base/logging.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/http2_constants.h" + +namespace net { + +// Http2FrameHeader decoding: + +void DoDecode(Http2FrameHeader* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_LE(Http2FrameHeader::EncodedSize(), b->Remaining()); + out->payload_length = b->DecodeUInt24(); + out->type = static_cast<Http2FrameType>(b->DecodeUInt8()); + out->flags = static_cast<Http2FrameFlag>(b->DecodeUInt8()); + out->stream_id = b->DecodeUInt31(); +} + +bool MaybeDecode(Http2FrameHeader* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + if (b->Remaining() >= Http2FrameHeader::EncodedSize()) { + DoDecode(out, b); + return true; + } + return false; +} + +bool SlowDecode(Http2FrameHeader* out, DecodeBuffer* b, uint32_t* offset) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_NE(nullptr, offset); + DCHECK_GT(Http2FrameHeader::EncodedSize(), *offset); + if (b->SlowDecodeUInt24(0 /* field_offset */, offset, &out->payload_length) && + b->SlowDecodeEnum(1 /* field_size */, 3 /* field_offset */, offset, + &out->type) && + b->SlowDecodeEnum(1 /* field_size */, 4 /* field_offset */, offset, + &out->flags) && + b->SlowDecodeUInt31(5 /* field_offset */, offset, &out->stream_id)) { + DCHECK_EQ(Http2FrameHeader::EncodedSize(), *offset); + return true; + } + DCHECK_GT(Http2FrameHeader::EncodedSize(), *offset); + return false; +} + +// Http2PriorityFields decoding: + +void DoDecode(Http2PriorityFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_LE(Http2PriorityFields::EncodedSize(), b->Remaining()); + uint32_t stream_id_and_flag = b->DecodeUInt32(); + out->stream_dependency = stream_id_and_flag & StreamIdMask(); + if (out->stream_dependency == stream_id_and_flag) { + out->is_exclusive = false; + } else { + out->is_exclusive = true; + } + // Note that chars are automatically promoted to ints during arithmetic, + // so 255 + 1 doesn't end up as zero. + out->weight = b->DecodeUInt8() + 1; +} + +bool MaybeDecode(Http2PriorityFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + if (b->Remaining() >= Http2PriorityFields::EncodedSize()) { + DoDecode(out, b); + return true; + } + return false; +} + +bool SlowDecode(Http2PriorityFields* out, DecodeBuffer* b, uint32_t* offset) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_NE(nullptr, offset); + DCHECK_GT(Http2PriorityFields::EncodedSize(), *offset); + const uint32_t start_offset = *offset; + if (b->SlowDecodeUInt32(0 /* field_offset */, offset, + &out->stream_dependency) && + b->SlowDecodeUnsignedInt(1, // field_size + 4, // field_offset + offset, &out->weight)) { + DCHECK_EQ(Http2PriorityFields::EncodedSize(), *offset); + if (start_offset < *offset) { + // First time here. Extract is_exclusive from stream_dependency. + const uint32_t stream_id_only = out->stream_dependency & StreamIdMask(); + if (out->stream_dependency != stream_id_only) { + out->stream_dependency = stream_id_only; + out->is_exclusive = true; + } else { + out->is_exclusive = false; + } + // Need to add one to the weight field because the encoding is 0-255, but + // interpreted as 1-256. + ++(out->weight); + } + return true; + } + DCHECK_GT(Http2PriorityFields::EncodedSize(), *offset); + return false; +} + +// Http2RstStreamFields decoding: + +void DoDecode(Http2RstStreamFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_LE(Http2RstStreamFields::EncodedSize(), b->Remaining()); + out->error_code = static_cast<Http2ErrorCode>(b->DecodeUInt32()); +} + +bool MaybeDecode(Http2RstStreamFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + if (b->Remaining() >= Http2RstStreamFields::EncodedSize()) { + DoDecode(out, b); + return true; + } + return false; +} + +bool SlowDecode(Http2RstStreamFields* out, DecodeBuffer* b, uint32_t* offset) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_NE(nullptr, offset); + DCHECK_GT(Http2RstStreamFields::EncodedSize(), *offset); + + if (b->SlowDecodeEnum(4 /* field_size */, 0 /* field_offset */, offset, + &out->error_code)) { + DCHECK_EQ(Http2RstStreamFields::EncodedSize(), *offset); + return true; + } + DCHECK_GT(Http2RstStreamFields::EncodedSize(), *offset); + return false; +} + +// Http2SettingFields decoding: + +void DoDecode(Http2SettingFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_LE(Http2SettingFields::EncodedSize(), b->Remaining()); + out->parameter = static_cast<Http2SettingsParameter>(b->DecodeUInt16()); + out->value = b->DecodeUInt32(); +} + +bool MaybeDecode(Http2SettingFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + if (b->Remaining() >= Http2SettingFields::EncodedSize()) { + DoDecode(out, b); + return true; + } + return false; +} + +bool SlowDecode(Http2SettingFields* out, DecodeBuffer* b, uint32_t* offset) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_NE(nullptr, offset); + DCHECK_LT(*offset, Http2SettingFields::EncodedSize()); + + if (b->SlowDecodeEnum(2 /* field_size */, 0 /* field_offset */, offset, + &out->parameter) && + b->SlowDecodeUInt32(2 /* field_offset */, offset, &out->value)) { + DCHECK_EQ(Http2SettingFields::EncodedSize(), *offset); + return true; + } + DCHECK_LT(*offset, Http2SettingFields::EncodedSize()); + return false; +} + +// Http2PushPromiseFields decoding: + +void DoDecode(Http2PushPromiseFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_LE(Http2PushPromiseFields::EncodedSize(), b->Remaining()); + out->promised_stream_id = b->DecodeUInt31(); +} + +bool MaybeDecode(Http2PushPromiseFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + if (b->Remaining() >= Http2PushPromiseFields::EncodedSize()) { + DoDecode(out, b); + return true; + } + return false; +} + +bool SlowDecode(Http2PushPromiseFields* out, + DecodeBuffer* b, + uint32_t* offset) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_NE(nullptr, offset); + DCHECK_LT(*offset, Http2PushPromiseFields::EncodedSize()); + if (b->SlowDecodeUInt31(0 /* field_offset */, offset, + &out->promised_stream_id)) { + DCHECK_EQ(Http2PushPromiseFields::EncodedSize(), *offset); + return true; + } + DCHECK_LT(*offset, Http2PushPromiseFields::EncodedSize()); + return false; +} + +// Http2PingFields decoding: + +void DoDecode(Http2PingFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_LE(Http2PingFields::EncodedSize(), b->Remaining()); + memcpy(out->opaque_data, b->cursor(), Http2PingFields::EncodedSize()); + b->AdvanceCursor(Http2PingFields::EncodedSize()); +} + +bool MaybeDecode(Http2PingFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + if (b->Remaining() >= Http2PingFields::EncodedSize()) { + DoDecode(out, b); + return true; + } + return false; +} + +bool SlowDecode(Http2PingFields* out, DecodeBuffer* b, uint32_t* offset) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_NE(nullptr, offset); + DCHECK_LT(*offset, Http2PingFields::EncodedSize()); + while (*offset < Http2PingFields::EncodedSize()) { + if (b->Empty()) { + return false; + } + out->opaque_data[(*offset)++] = b->DecodeUInt8(); + } + return true; +} + +// Http2GoAwayFields decoding: + +void DoDecode(Http2GoAwayFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_LE(Http2GoAwayFields::EncodedSize(), b->Remaining()); + out->last_stream_id = b->DecodeUInt31(); + out->error_code = static_cast<Http2ErrorCode>(b->DecodeUInt32()); +} + +bool MaybeDecode(Http2GoAwayFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + if (b->Remaining() >= Http2GoAwayFields::EncodedSize()) { + DoDecode(out, b); + return true; + } + return false; +} + +bool SlowDecode(Http2GoAwayFields* out, DecodeBuffer* b, uint32_t* offset) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_NE(nullptr, offset); + DCHECK_LT(*offset, Http2GoAwayFields::EncodedSize()); + if (b->SlowDecodeUInt31(0 /* field_offset */, offset, &out->last_stream_id) && + b->SlowDecodeEnum(4 /* field_size */, 4 /* field_offset */, offset, + &out->error_code)) { + DCHECK_EQ(Http2GoAwayFields::EncodedSize(), *offset); + return true; + } + DCHECK_LT(*offset, Http2GoAwayFields::EncodedSize()); + return false; +} + +// Http2WindowUpdateFields decoding: + +void DoDecode(Http2WindowUpdateFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_LE(Http2WindowUpdateFields::EncodedSize(), b->Remaining()); + out->window_size_increment = b->DecodeUInt31(); +} + +bool MaybeDecode(Http2WindowUpdateFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + if (b->Remaining() >= Http2WindowUpdateFields::EncodedSize()) { + DoDecode(out, b); + return true; + } + return false; +} + +bool SlowDecode(Http2WindowUpdateFields* out, + DecodeBuffer* b, + uint32_t* offset) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_NE(nullptr, offset); + DCHECK_LT(*offset, Http2WindowUpdateFields::EncodedSize()); + if (b->SlowDecodeUInt31(0 /* field_offset */, offset, + &out->window_size_increment)) { + DCHECK_EQ(Http2WindowUpdateFields::EncodedSize(), *offset); + return true; + } + DCHECK_LT(*offset, Http2WindowUpdateFields::EncodedSize()); + return false; +} + +// Http2AltSvcFields decoding: + +void DoDecode(Http2AltSvcFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_LE(Http2AltSvcFields::EncodedSize(), b->Remaining()); + out->origin_length = b->DecodeUInt16(); +} + +bool MaybeDecode(Http2AltSvcFields* out, DecodeBuffer* b) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + if (b->Remaining() >= Http2AltSvcFields::EncodedSize()) { + DoDecode(out, b); + return true; + } + return false; +} + +bool SlowDecode(Http2AltSvcFields* out, DecodeBuffer* b, uint32_t* offset) { + DCHECK_NE(nullptr, out); + DCHECK_NE(nullptr, b); + DCHECK_NE(nullptr, offset); + DCHECK_LT(*offset, Http2AltSvcFields::EncodedSize()); + if (b->SlowDecodeUInt16(0 /* field_offset */, offset, &out->origin_length)) { + DCHECK_EQ(Http2AltSvcFields::EncodedSize(), *offset); + return true; + } + DCHECK_LT(*offset, Http2AltSvcFields::EncodedSize()); + return false; +} + +} // namespace net
diff --git a/net/http2/decoder/decode_http2_structures.h b/net/http2/decoder/decode_http2_structures.h new file mode 100644 index 0000000..7cee46f --- /dev/null +++ b/net/http2/decoder/decode_http2_structures.h
@@ -0,0 +1,94 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_DECODE_HTTP2_STRUCTURES_H_ +#define NET_HTTP2_DECODER_DECODE_HTTP2_STRUCTURES_H_ + +// Provides functions for decoding the fixed size structures in the HTTP/2 spec. + +// TODO(jamessynge): Consider whether the value of the SlowDecode methods is +// worth their complexity; in particular, dropping back to buffering at most +// 9 bytes (the largest fixed size structure) may actually be more efficient +// than using the SlowDecode methods, or at least worth the complexity +// reduction. +// See http2_structure_decoder.h et al for an experiment in removing all except +// DoDecode. + +#include "net/base/net_export.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/http2_structures.h" + +namespace net { + +// DoDecode(STRUCTURE* out, DecodeBuffer* b) decodes the structure from start +// to end, advancing the cursor by STRUCTURE::EncodedSize(). The decoder buffer +// must be large enough (i.e. b->Remaining() >= STRUCTURE::EncodedSize()). + +NET_EXPORT_PRIVATE void DoDecode(Http2FrameHeader* out, DecodeBuffer* b); +NET_EXPORT_PRIVATE void DoDecode(Http2PriorityFields* out, DecodeBuffer* b); +NET_EXPORT_PRIVATE void DoDecode(Http2RstStreamFields* out, DecodeBuffer* b); +NET_EXPORT_PRIVATE void DoDecode(Http2SettingFields* out, DecodeBuffer* b); +NET_EXPORT_PRIVATE void DoDecode(Http2PushPromiseFields* out, DecodeBuffer* b); +NET_EXPORT_PRIVATE void DoDecode(Http2PingFields* out, DecodeBuffer* b); +NET_EXPORT_PRIVATE void DoDecode(Http2GoAwayFields* out, DecodeBuffer* b); +NET_EXPORT_PRIVATE void DoDecode(Http2WindowUpdateFields* out, DecodeBuffer* b); +NET_EXPORT_PRIVATE void DoDecode(Http2AltSvcFields* out, DecodeBuffer* b); + +// MaybeDecode(STRUCTURE* out, DecodeBuffer* b) decodes the structure from +// start to end if the decoder buffer is large enough, advancing the cursor +// by STRUCTURE::EncodedSize(), then returns true. +// If the decode buffer isn't large enough, does nothing and returns false. +// The buffer is large enough if b->Remaining() >= STRUCTURE::EncodedSize(). + +NET_EXPORT_PRIVATE bool MaybeDecode(Http2FrameHeader* out, DecodeBuffer* b); +NET_EXPORT_PRIVATE bool MaybeDecode(Http2PriorityFields* out, DecodeBuffer* b); +NET_EXPORT_PRIVATE bool MaybeDecode(Http2RstStreamFields* out, DecodeBuffer* b); +NET_EXPORT_PRIVATE bool MaybeDecode(Http2SettingFields* out, DecodeBuffer* b); +NET_EXPORT_PRIVATE bool MaybeDecode(Http2PushPromiseFields* out, + DecodeBuffer* b); +NET_EXPORT_PRIVATE bool MaybeDecode(Http2PingFields* out, DecodeBuffer* b); +NET_EXPORT_PRIVATE bool MaybeDecode(Http2GoAwayFields* out, DecodeBuffer* b); +NET_EXPORT_PRIVATE bool MaybeDecode(Http2WindowUpdateFields* out, + DecodeBuffer* b); +NET_EXPORT_PRIVATE bool MaybeDecode(Http2AltSvcFields* out, DecodeBuffer* b); + +// SlowDecode(STRUCTURE* out, DecodeBuffer* b, uint32_t* offset) provides +// incremental decoding of a structure, supporting cases where the structure +// is split across multiple input buffers. *offset represents the offset within +// the encoding of the structure, in the range [0, STRUCTURE::EncodedSize()]. +// Returns true when it is able to completely decode the structure, false +// before that. Updates *offset to record the progress decoding the structure; +// if false is returned, then b->Remaining() == 0 when SlowDecode returns. + +NET_EXPORT_PRIVATE bool SlowDecode(Http2FrameHeader* out, + DecodeBuffer* b, + uint32_t* offset); +NET_EXPORT_PRIVATE bool SlowDecode(Http2PriorityFields* out, + DecodeBuffer* b, + uint32_t* offset); +NET_EXPORT_PRIVATE bool SlowDecode(Http2RstStreamFields* out, + DecodeBuffer* b, + uint32_t* offset); +NET_EXPORT_PRIVATE bool SlowDecode(Http2SettingFields* out, + DecodeBuffer* b, + uint32_t* offset); +NET_EXPORT_PRIVATE bool SlowDecode(Http2PushPromiseFields* out, + DecodeBuffer* b, + uint32_t* offset); +NET_EXPORT_PRIVATE bool SlowDecode(Http2PingFields* out, + DecodeBuffer* b, + uint32_t* offset); +NET_EXPORT_PRIVATE bool SlowDecode(Http2GoAwayFields* out, + DecodeBuffer* b, + uint32_t* offset); +NET_EXPORT_PRIVATE bool SlowDecode(Http2WindowUpdateFields* out, + DecodeBuffer* b, + uint32_t* offset); +NET_EXPORT_PRIVATE bool SlowDecode(Http2AltSvcFields* out, + DecodeBuffer* b, + uint32_t* offset); + +} // namespace net + +#endif // NET_HTTP2_DECODER_DECODE_HTTP2_STRUCTURES_H_
diff --git a/net/http2/decoder/decode_http2_structures_test.cc b/net/http2/decoder/decode_http2_structures_test.cc new file mode 100644 index 0000000..4b09e7a --- /dev/null +++ b/net/http2/decoder/decode_http2_structures_test.cc
@@ -0,0 +1,542 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/decode_http2_structures.h" + +// Tests decoding all of the fixed size HTTP/2 structures (i.e. those defined +// in net/http2/http2_structures.h). + +// TODO(jamessynge): Combine tests of DoDecode, MaybeDecode, SlowDecode and +// Http2StructureDecoder test using gUnit's support for tests parameterized +// by type. + +#include <stddef.h> +#include <string> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/logging.h" +#include "base/strings/string_piece.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures_test_util.h" +#include "net/http2/tools/http2_frame_builder.h" +#include "net/http2/tools/random_decoder_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::AssertionFailure; +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using base::StringPiece; +using std::string; + +namespace net { +namespace test { +namespace { + +template <class S> +string SerializeStructure(const S& s) { + Http2FrameBuilder fb; + fb.Append(s); + EXPECT_EQ(S::EncodedSize(), fb.size()); + return fb.buffer(); +} + +template <class S> +class StructureDecoderTest : public RandomDecoderTest { + protected: + typedef S Structure; + + StructureDecoderTest() { + // IF the test adds more data after the encoded structure, stop as + // soon as the structure is decoded. + stop_decode_on_done_ = true; + } + + // Reset the decoding to the start of the structure, and overwrite the + // current contents of |structure_|, in to which we'll decode the buffer. + DecodeStatus StartDecoding(DecodeBuffer* b) override { + decode_offset_ = 0; + Randomize(&structure_); + return ResumeDecoding(b); + } + + DecodeStatus ResumeDecoding(DecodeBuffer* b) override { + // If we're at the start... + if (decode_offset_ == 0) { + const uint32_t start_offset = b->Offset(); + const char* const start_cursor = b->cursor(); + // ... attempt to decode the entire structure. + if (MaybeDecode(&structure_, b)) { + ++fast_decode_count_; + EXPECT_EQ(S::EncodedSize(), b->Offset() - start_offset); + + if (!HasFailure()) { + // Success. Confirm that SlowDecode produces the same result. + DecodeBuffer b2(start_cursor, b->Offset() - start_offset); + S second; + Randomize(&second); + uint32_t second_offset = 0; + EXPECT_TRUE(SlowDecode(&second, &b2, &second_offset)); + EXPECT_EQ(S::EncodedSize(), second_offset); + EXPECT_EQ(structure_, second); + } + + // Test can't easily tell if MaybeDecode or SlowDecode is used, so + // update decode_offset_ as if SlowDecode had been used to completely + // decode. + decode_offset_ = S::EncodedSize(); + return DecodeStatus::kDecodeDone; + } + } + + // We didn't have enough in the first buffer to decode everything, so we'll + // reach here multiple times until we've completely decoded the structure. + if (SlowDecode(&structure_, b, &decode_offset_)) { + ++slow_decode_count_; + EXPECT_EQ(S::EncodedSize(), decode_offset_); + return DecodeStatus::kDecodeDone; + } + + // Drained the input buffer, but not yet done. + EXPECT_TRUE(b->Empty()); + EXPECT_GT(S::EncodedSize(), decode_offset_); + + return DecodeStatus::kDecodeInProgress; + } + + // Set the fields of |*p| to random values. + void Randomize(S* p) { ::net::test::Randomize(p, RandomPtr()); } + + AssertionResult ValidatorForDecodeLeadingStructure(const S* expected, + const DecodeBuffer& db, + DecodeStatus status) { + if (expected != nullptr && *expected != structure_) { + return AssertionFailure() + << "Expected structs to be equal\nExpected: " << *expected + << "\n Actual: " << structure_; + } + return AssertionSuccess(); + } + + // Fully decodes the Structure at the start of data, and confirms it matches + // *expected (if provided). + void DecodeLeadingStructure(const S* expected, StringPiece data) { + ASSERT_LE(S::EncodedSize(), data.size()); + DecodeBuffer original(data); + + // The validator is called after each of the several times that the input + // DecodeBuffer is decoded, each with a different segmentation of the input. + // Validate that structure_ matches the expected value, if provided. + Validator validator = + base::Bind(&StructureDecoderTest::ValidatorForDecodeLeadingStructure, + base::Unretained(this), expected); + + // First validate that decoding is done and that we've advanced the cursor + // the expected amount. + Validator wrapped_validator = + ValidateDoneAndOffset(S::EncodedSize(), validator); + + // Decode several times, with several segmentations of the input buffer. + fast_decode_count_ = 0; + slow_decode_count_ = 0; + EXPECT_TRUE(DecodeAndValidateSeveralWays( + &original, false /*return_non_zero_on_first*/, wrapped_validator)); + + if (!HasFailure()) { + EXPECT_EQ(S::EncodedSize(), decode_offset_); + EXPECT_EQ(S::EncodedSize(), original.Offset()); + EXPECT_LT(0u, fast_decode_count_); + EXPECT_LT(0u, slow_decode_count_); + if (expected != nullptr) { + DVLOG(1) << "DecodeLeadingStructure expected: " << *expected; + DVLOG(1) << "DecodeLeadingStructure actual: " << structure_; + EXPECT_EQ(*expected, structure_); + } + } + } + + template <size_t N> + void DecodeLeadingStructure(const char (&data)[N]) { + DecodeLeadingStructure(nullptr, StringPiece(data, N)); + } + + // Encode the structure |in_s| into bytes, then decode the bytes + // and validate that the decoder produced the same field values. + void EncodeThenDecode(const S& in_s) { + string bytes = SerializeStructure(in_s); + EXPECT_EQ(S::EncodedSize(), bytes.size()); + DecodeLeadingStructure(&in_s, bytes); + } + + // Generate + void TestDecodingRandomizedStructures(size_t count) { + for (size_t i = 0; i < count && !HasFailure(); ++i) { + Structure input; + Randomize(&input); + EncodeThenDecode(input); + } + } + + uint32_t decode_offset_ = 0; + S structure_; + size_t fast_decode_count_ = 0; + size_t slow_decode_count_ = 0; +}; + +class FrameHeaderDecoderTest : public StructureDecoderTest<Http2FrameHeader> {}; + +TEST_F(FrameHeaderDecoderTest, DecodesLiteral) { + { + // Realistic input. + const char kData[] = { + 0x00, 0x00, 0x05, // Payload length: 5 + 0x01, // Frame type: HEADERS + 0x08, // Flags: PADDED + 0x00, 0x00, 0x00, 0x01, // Stream ID: 1 + 0x04, // Padding length: 4 + 0x00, 0x00, 0x00, 0x00, // Padding bytes + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(5u, structure_.payload_length); + EXPECT_EQ(Http2FrameType::HEADERS, structure_.type); + EXPECT_EQ(Http2FrameFlag::FLAG_PADDED, structure_.flags); + EXPECT_EQ(1u, structure_.stream_id); + } + } + { + // Unlikely input. + const char kData[] = { + 0xffu, 0xffu, 0xffu, // Payload length: uint24 max + 0xffu, // Frame type: Unknown + 0xffu, // Flags: Unknown/All + 0xffu, 0xffu, 0xffu, 0xffu, // Stream ID: uint31 max, plus R-bit + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ((1u << 24) - 1, structure_.payload_length); + EXPECT_EQ(static_cast<Http2FrameType>(255), structure_.type); + EXPECT_EQ(255, structure_.flags); + EXPECT_EQ(0x7FFFFFFFu, structure_.stream_id); + } + } +} + +TEST_F(FrameHeaderDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(100); +} + +//------------------------------------------------------------------------------ + +class PriorityFieldsDecoderTest + : public StructureDecoderTest<Http2PriorityFields> {}; + +TEST_F(PriorityFieldsDecoderTest, DecodesLiteral) { + { + const char kData[] = { + 0x80u, 0x00, 0x00, 0x05, // Exclusive (yes) and Dependency (5) + 0xffu, // Weight: 256 (after adding 1) + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(5u, structure_.stream_dependency); + EXPECT_EQ(256u, structure_.weight); + EXPECT_EQ(true, structure_.is_exclusive); + } + } + { + const char kData[] = { + 0x7f, 0xffu, + 0xffu, 0xffu, // Exclusive (no) and Dependency (0x7fffffff) + 0x00, // Weight: 1 (after adding 1) + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(StreamIdMask(), structure_.stream_dependency); + EXPECT_EQ(1u, structure_.weight); + EXPECT_FALSE(structure_.is_exclusive); + } + } +} + +TEST_F(PriorityFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(100); +} + +//------------------------------------------------------------------------------ + +class RstStreamFieldsDecoderTest + : public StructureDecoderTest<Http2RstStreamFields> {}; + +TEST_F(RstStreamFieldsDecoderTest, DecodesLiteral) { + { + const char kData[] = { + 0x00, 0x00, 0x00, 0x01, // Error: PROTOCOL_ERROR + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_TRUE(structure_.IsSupportedErrorCode()); + EXPECT_EQ(Http2ErrorCode::PROTOCOL_ERROR, structure_.error_code); + } + } + { + const char kData[] = { + 0xffu, 0xffu, 0xffu, 0xffu, // Error: max uint32 (Unknown error code) + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_FALSE(structure_.IsSupportedErrorCode()); + EXPECT_EQ(static_cast<Http2ErrorCode>(0xffffffff), structure_.error_code); + } + } +} + +TEST_F(RstStreamFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(100); +} + +//------------------------------------------------------------------------------ + +class SettingFieldsDecoderTest + : public StructureDecoderTest<Http2SettingFields> {}; + +TEST_F(SettingFieldsDecoderTest, DecodesLiteral) { + { + const char kData[] = { + 0x00, 0x01, // Setting: HEADER_TABLE_SIZE + 0x00, 0x00, 0x40, 0x00, // Value: 16K + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_TRUE(structure_.IsSupportedParameter()); + EXPECT_EQ(Http2SettingsParameter::HEADER_TABLE_SIZE, + structure_.parameter); + EXPECT_EQ(1u << 14, structure_.value); + } + } + { + const char kData[] = { + 0x00, 0x00, // Setting: Unknown (0) + 0xffu, 0xffu, 0xffu, 0xffu, // Value: max uint32 + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_FALSE(structure_.IsSupportedParameter()); + EXPECT_EQ(static_cast<Http2SettingsParameter>(0), structure_.parameter); + } + } +} + +TEST_F(SettingFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(100); +} + +//------------------------------------------------------------------------------ + +class PushPromiseFieldsDecoderTest + : public StructureDecoderTest<Http2PushPromiseFields> {}; + +TEST_F(PushPromiseFieldsDecoderTest, DecodesLiteral) { + { + const char kData[] = { + 0x00, 0x01, 0x8au, 0x92u, // Promised Stream ID: 101010 + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(101010u, structure_.promised_stream_id); + } + } + { + // Promised stream id has R-bit (reserved for future use) set, which + // should be cleared by the decoder. + const char kData[] = { + 0xffu, 0xffu, 0xffu, 0xffu, // Promised Stream ID: max uint31 and R-bit + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(StreamIdMask(), structure_.promised_stream_id); + } + } +} + +TEST_F(PushPromiseFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(100); +} + +//------------------------------------------------------------------------------ + +class PingFieldsDecoderTest : public StructureDecoderTest<Http2PingFields> {}; + +TEST_F(PingFieldsDecoderTest, DecodesLiteral) { + { + // Each byte is different, so can detect if order changed. + const char kData[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(StringPiece(kData, 8), ToStringPiece(structure_.opaque_data)); + } + } + { + // All zeros, detect problems handling NULs. + const char kData[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(StringPiece(kData, 8), ToStringPiece(structure_.opaque_data)); + } + } + { + const char kData[] = { + 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(StringPiece(kData, 8), ToStringPiece(structure_.opaque_data)); + } + } +} + +TEST_F(PingFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(100); +} + +//------------------------------------------------------------------------------ + +class GoAwayFieldsDecoderTest : public StructureDecoderTest<Http2GoAwayFields> { +}; + +TEST_F(GoAwayFieldsDecoderTest, DecodesLiteral) { + { + const char kData[] = { + 0x00, 0x00, 0x00, 0x00, // Last Stream ID: 0 + 0x00, 0x00, 0x00, 0x00, // Error: NO_ERROR (0) + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(0u, structure_.last_stream_id); + EXPECT_TRUE(structure_.IsSupportedErrorCode()); + EXPECT_EQ(Http2ErrorCode::HTTP2_NO_ERROR, structure_.error_code); + } + } + { + const char kData[] = { + 0x00, 0x00, 0x00, 0x01, // Last Stream ID: 1 + 0x00, 0x00, 0x00, 0x0d, // Error: HTTP_1_1_REQUIRED + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(1u, structure_.last_stream_id); + EXPECT_TRUE(structure_.IsSupportedErrorCode()); + EXPECT_EQ(Http2ErrorCode::HTTP_1_1_REQUIRED, structure_.error_code); + } + } + { + const char kData[] = { + 0xffu, 0xffu, 0xffu, 0xffu, // Last Stream ID: max uint31 and R-bit + 0xffu, 0xffu, 0xffu, 0xffu, // Error: max uint32 (Unknown error code) + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(StreamIdMask(), structure_.last_stream_id); // No high-bit. + EXPECT_FALSE(structure_.IsSupportedErrorCode()); + EXPECT_EQ(static_cast<Http2ErrorCode>(0xffffffff), structure_.error_code); + } + } +} + +TEST_F(GoAwayFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(100); +} + +//------------------------------------------------------------------------------ + +class WindowUpdateFieldsDecoderTest + : public StructureDecoderTest<Http2WindowUpdateFields> {}; + +TEST_F(WindowUpdateFieldsDecoderTest, DecodesLiteral) { + { + const char kData[] = { + 0x00, 0x01, 0x00, 0x00, // Window Size Increment: 2 ^ 16 + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(1u << 16, structure_.window_size_increment); + } + } + { + // Increment must be non-zero, but we need to be able to decode the invalid + // zero to detect it. + const char kData[] = { + 0x00, 0x00, 0x00, 0x00, // Window Size Increment: 0 + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(0u, structure_.window_size_increment); + } + } + { + // Increment has R-bit (reserved for future use) set, which + // should be cleared by the decoder. + const char kData[] = { + 0xffu, 0xffu, 0xffu, + 0xffu, // Window Size Increment: max uint31 and R-bit + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(StreamIdMask(), structure_.window_size_increment); + } + } +} + +TEST_F(WindowUpdateFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(100); +} + +//------------------------------------------------------------------------------ + +class AltSvcFieldsDecoderTest : public StructureDecoderTest<Http2AltSvcFields> { +}; + +TEST_F(AltSvcFieldsDecoderTest, DecodesLiteral) { + { + const char kData[] = { + 0x00, 0x00, // Origin Length: 0 + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(0, structure_.origin_length); + } + } + { + const char kData[] = { + 0x00, 0x14, // Origin Length: 20 + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(20, structure_.origin_length); + } + } + { + const char kData[] = { + 0xffu, 0xffu, // Origin Length: uint16 max + }; + DecodeLeadingStructure(kData); + if (!HasFailure()) { + EXPECT_EQ(65535, structure_.origin_length); + } + } +} + +TEST_F(AltSvcFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(100); +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/decoder/decode_status.cc b/net/http2/decoder/decode_status.cc new file mode 100644 index 0000000..99a1de0b5 --- /dev/null +++ b/net/http2/decoder/decode_status.cc
@@ -0,0 +1,27 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/decode_status.h" + +#include "base/logging.h" + +namespace net { + +std::ostream& operator<<(std::ostream& out, DecodeStatus v) { + switch (v) { + case DecodeStatus::kDecodeDone: + return out << "DecodeDone"; + case DecodeStatus::kDecodeInProgress: + return out << "DecodeInProgress"; + case DecodeStatus::kDecodeError: + return out << "DecodeError"; + } + // Since the value doesn't come over the wire, only a programming bug should + // result in reaching this point. + int unknown = static_cast<int>(v); + LOG(DFATAL) << "Unknown DecodeStatus " << unknown << std::hex << unknown; + return out << "UnknownDecodeStatus(" << unknown << ")"; +} + +} // namespace net
diff --git a/net/http2/decoder/decode_status.h b/net/http2/decoder/decode_status.h new file mode 100644 index 0000000..e5fbeeb --- /dev/null +++ b/net/http2/decoder/decode_status.h
@@ -0,0 +1,32 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_DECODE_STATUS_H_ +#define NET_HTTP2_DECODER_DECODE_STATUS_H_ + +// Enum DecodeStatus is used to report the status of decoding of many +// types of HTTP/2 and HPACK objects. + +#include <ostream> + +#include "net/base/net_export.h" + +namespace net { + +enum class DecodeStatus { + // Decoding is done. + kDecodeDone, + + // Decoder needs more input to be able to make progress. + kDecodeInProgress, + + // Decoding failed (e.g. HPACK variable length integer is too large, or + // an HTTP/2 frame has padding declared to be larger than the payload). + kDecodeError, +}; +NET_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, DecodeStatus v); + +} // namespace net + +#endif // NET_HTTP2_DECODER_DECODE_STATUS_H_
diff --git a/net/http2/decoder/frame_decoder_state.cc b/net/http2/decoder/frame_decoder_state.cc new file mode 100644 index 0000000..e2b4406 --- /dev/null +++ b/net/http2/decoder/frame_decoder_state.cc
@@ -0,0 +1,81 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/frame_decoder_state.h" + +namespace net { + +DecodeStatus FrameDecoderState::ReadPadLength(DecodeBuffer* db, + bool report_pad_length) { + DVLOG(2) << "ReadPadLength db->Remaining=" << db->Remaining() + << "; payload_length=" << frame_header().payload_length; + DCHECK(IsPaddable()); + DCHECK(frame_header().IsPadded()); + + // Pad Length is always at the start of the frame, so remaining_payload_ + // should equal payload_length at this point. + const uint32_t total_payload = frame_header().payload_length; + DCHECK_EQ(total_payload, remaining_payload_); + DCHECK_EQ(0u, remaining_padding_); + + if (db->HasData()) { + const uint32_t pad_length = db->DecodeUInt8(); + const uint32_t total_padding = pad_length + 1; + if (total_padding <= total_payload) { + remaining_padding_ = pad_length; + remaining_payload_ = total_payload - total_padding; + if (report_pad_length) { + listener()->OnPadLength(pad_length); + } + return DecodeStatus::kDecodeDone; + } + const uint32_t missing_length = total_padding - total_payload; + // To allow for the possibility of recovery, record the number of + // remaining bytes of the frame's payload (invalid though it is) + // in remaining_payload_. + remaining_payload_ = total_payload - 1; // 1 for sizeof(Pad Length). + remaining_padding_ = 0; + listener()->OnPaddingTooLong(frame_header(), missing_length); + return DecodeStatus::kDecodeError; + } + + if (total_payload == 0) { + remaining_payload_ = 0; + remaining_padding_ = 0; + listener()->OnPaddingTooLong(frame_header(), 1); + return DecodeStatus::kDecodeError; + } + // Need to wait for another buffer. + return DecodeStatus::kDecodeInProgress; +} + +bool FrameDecoderState::SkipPadding(DecodeBuffer* db) { + DVLOG(2) << "SkipPadding remaining_padding_=" << remaining_padding_ + << ", db->Remaining=" << db->Remaining() + << ", header: " << frame_header(); + DCHECK_EQ(remaining_payload_, 0u); + DCHECK(IsPaddable()) << "header: " << frame_header(); + DCHECK_GE(remaining_padding_, 0u); + DCHECK(remaining_padding_ == 0 || frame_header().IsPadded()) + << "remaining_padding_=" << remaining_padding_ + << ", header: " << frame_header(); + const size_t avail = AvailablePadding(db); + if (avail > 0) { + listener()->OnPadding(db->cursor(), avail); + db->AdvanceCursor(avail); + remaining_padding_ -= avail; + } + return remaining_padding_ == 0; +} + +DecodeStatus FrameDecoderState::ReportFrameSizeError() { + DVLOG(2) << "FrameDecoderState::ReportFrameSizeError: " + << " remaining_payload_=" << remaining_payload_ + << "; remaining_padding_=" << remaining_padding_ + << ", header: " << frame_header(); + listener()->OnFrameSizeError(frame_header()); + return DecodeStatus::kDecodeError; +} + +} // namespace net
diff --git a/net/http2/decoder/frame_decoder_state.h b/net/http2/decoder/frame_decoder_state.h new file mode 100644 index 0000000..ddf64d1f --- /dev/null +++ b/net/http2/decoder/frame_decoder_state.h
@@ -0,0 +1,252 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_FRAME_DECODER_STATE_H_ +#define NET_HTTP2_DECODER_FRAME_DECODER_STATE_H_ + +// FrameDecoderState provides state and behaviors in support of decoding +// the common frame header and the payload of all frame types. +// It is an input to all of the payload decoders. + +// TODO(jamessynge): Since FrameDecoderState has far more than state in it, +// rename to FrameDecoderHelper, or similar. + +#include <stddef.h> + +#include "base/logging.h" +#include "net/base/net_export.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/decoder/http2_structure_decoder.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures.h" + +namespace net { +namespace test { +class FrameDecoderStatePeer; +} // namespace test + +class NET_EXPORT_PRIVATE FrameDecoderState { + public: + FrameDecoderState() {} + + // Sets the listener which the decoders should call as they decode HTTP/2 + // frames. The listener can be changed at any time, which allows for replacing + // it with a no-op listener when an error is detected, either by the payload + // decoder (OnPaddingTooLong or OnFrameSizeError) or by the "real" listener. + // That in turn allows us to define Http2FrameDecoderListener such that all + // methods have return type void, with no direct way to indicate whether the + // decoder should stop, and to eliminate from the decoder all checks of the + // return value. Instead the listener/caller can simply replace the current + // listener with a no-op listener implementation. + // TODO(jamessynge): Make set_listener private as only Http2FrameDecoder + // and tests need to set it, so it doesn't need to be public. + void set_listener(Http2FrameDecoderListener* listener) { + listener_ = listener; + } + Http2FrameDecoderListener* listener() const { return listener_; } + + // The most recently decoded frame header. + const Http2FrameHeader& frame_header() const { return frame_header_; } + + // Decode a structure in the payload, adjusting remaining_payload_ to account + // for the consumed portion of the payload. Returns kDecodeDone when fully + // decoded, kDecodeError if it ran out of payload before decoding completed, + // and kDecodeInProgress if the decode buffer didn't have enough of the + // remaining payload. + template <class S> + DecodeStatus StartDecodingStructureInPayload(S* out, DecodeBuffer* db) { + DVLOG(2) << __func__ << "\n\tdb->Remaining=" << db->Remaining() + << "\n\tremaining_payload_=" << remaining_payload_ + << "\n\tneed=" << S::EncodedSize(); + DecodeStatus status = + structure_decoder_.Start(out, db, &remaining_payload_); + if (status != DecodeStatus::kDecodeError) { + return status; + } + DVLOG(2) << "StartDecodingStructureInPayload: detected frame size error"; + return ReportFrameSizeError(); + } + + // Resume decoding of a structure that has been split across buffers, + // adjusting remaining_payload_ to account for the consumed portion of + // the payload. Returns values are as for StartDecodingStructureInPayload. + template <class S> + DecodeStatus ResumeDecodingStructureInPayload(S* out, DecodeBuffer* db) { + DVLOG(2) << __func__ << "\n\tdb->Remaining=" << db->Remaining() + << "\n\tremaining_payload_=" << remaining_payload_; + if (structure_decoder_.Resume(out, db, &remaining_payload_)) { + return DecodeStatus::kDecodeDone; + } else if (remaining_payload_ > 0) { + return DecodeStatus::kDecodeInProgress; + } else { + DVLOG(2) << "ResumeDecodingStructureInPayload: detected frame size error"; + return ReportFrameSizeError(); + } + } + + // Initializes the two remaining* fields, which is needed if the frame's + // payload is split across buffers, or the decoder calls ReadPadLength or + // StartDecodingStructureInPayload, and of course the methods below which + // read those fields, as their names imply. + void InitializeRemainders() { + remaining_payload_ = frame_header().payload_length; + // Note that remaining_total_payload() relies on remaining_padding_ being + // zero for frames that have no padding. + remaining_padding_ = 0; + } + + // Returns the number of bytes of the frame's payload that remain to be + // decoded, including any trailing padding. This method must only be called + // after the variables have been initialized, which in practice means once a + // payload decoder has called InitializeRemainders and/or ReadPadLength. + size_t remaining_total_payload() const { + DCHECK(IsPaddable() || remaining_padding_ == 0) << frame_header(); + return remaining_payload_ + remaining_padding_; + } + + // Returns the number of bytes of the frame's payload that remain to be + // decoded, excluding any trailing padding. This method must only be called + // after the variable has been initialized, which in practice means once a + // payload decoder has called InitializeRemainders; ReadPadLength will deduct + // the total number of padding bytes from remaining_payload_, including the + // size of the Pad Length field itself (1 byte). + size_t remaining_payload() const { return remaining_payload_; } + + // Returns the number of bytes of the frame's payload that remain to be + // decoded, including any trailing padding. This method must only be called if + // the frame type allows padding, and after the variable has been initialized, + // which in practice means once a payload decoder has called + // InitializeRemainders and/or ReadPadLength. + size_t remaining_payload_and_padding() const { + DCHECK(IsPaddable()) << frame_header(); + return remaining_payload_ + remaining_padding_; + } + + // Returns the number of bytes of trailing padding after the payload that + // remain to be decoded. This method must only be called if the frame type + // allows padding, and after the variable has been initialized, which in + // practice means once a payload decoder has called InitializeRemainders, + // and isn't set to a non-zero value until ReadPadLength has been called. + uint32_t remaining_padding() const { + DCHECK(IsPaddable()) << frame_header(); + return remaining_padding_; + } + + // Returns the amount of trailing padding after the payload that remains to be + // decoded. + uint32_t remaining_padding_for_test() const { return remaining_padding_; } + + // How many bytes of the remaining payload are in db? + size_t AvailablePayload(DecodeBuffer* db) const { + return db->MinLengthRemaining(remaining_payload_); + } + + // How many bytes of the remaining payload and padding are in db? + // Call only for frames whose type is paddable. + size_t AvailablePayloadAndPadding(DecodeBuffer* db) const { + DCHECK(IsPaddable()) << frame_header(); + return db->MinLengthRemaining(remaining_payload_ + remaining_padding_); + } + + // How many bytes of the padding that have not yet been skipped are in db? + // Call only after remaining_padding_ has been set (for padded frames), or + // been cleared (for unpadded frames); and after all of the non-padding + // payload has been decoded. + size_t AvailablePadding(DecodeBuffer* db) const { + DCHECK(IsPaddable()) << frame_header(); + DCHECK_EQ(remaining_payload_, 0u); + return db->MinLengthRemaining(remaining_padding_); + } + + // Reduces remaining_payload_ by amount. To be called by a payload decoder + // after it has passed a variable length portion of the payload to the + // listener; remaining_payload_ will be automatically reduced when fixed + // size structures and padding, including the Pad Length field, are decoded. + void ConsumePayload(size_t amount) { + DCHECK_LE(amount, remaining_payload_); + remaining_payload_ -= amount; + } + + // Reads the Pad Length field into remaining_padding_, and appropriately sets + // remaining_payload_. When present, the Pad Length field is always the first + // field in the payload, which this method relies on so that the caller need + // not set remaining_payload_ before calling this method. + // If report_pad_length is true, calls the listener's OnPadLength method when + // it decodes the Pad Length field. + // Returns kDecodeDone if the decode buffer was not empty (i.e. because the + // field is only a single byte long, it can always be decoded if the buffer is + // not empty). + // Returns kDecodeError if the buffer is empty because the frame has no + // payload (i.e. payload_length() == 0). + // Returns kDecodeInProgress if the buffer is empty but the frame has a + // payload. + DecodeStatus ReadPadLength(DecodeBuffer* db, bool report_pad_length); + + // Skip the trailing padding bytes; only call once remaining_payload_==0. + // Returns true when the padding has been skipped. + // Does NOT check that the padding is all zeroes. + bool SkipPadding(DecodeBuffer* db); + + // Calls the listener's OnFrameSizeError method and returns kDecodeError. + DecodeStatus ReportFrameSizeError(); + + private: + friend class Http2FrameDecoder; + friend class test::FrameDecoderStatePeer; + + // Starts the decoding of a common frame header. Returns true if completed the + // decoding, false if the decode buffer didn't have enough data in it, in + // which case the decode buffer will have been drained and the caller should + // call ResumeDecodingFrameHeader when more data is available. This is called + // from Http2FrameDecoder, a friend class. + bool StartDecodingFrameHeader(DecodeBuffer* db) { + return structure_decoder_.Start(&frame_header_, db); + } + + // Resumes decoding the common frame header after the preceding call to + // StartDecodingFrameHeader returned false, as did any subsequent calls to + // ResumeDecodingFrameHeader. This is called from Http2FrameDecoder, + // a friend class. + bool ResumeDecodingFrameHeader(DecodeBuffer* db) { + return structure_decoder_.Resume(&frame_header_, db); + } + + // Clear any of the flags in the frame header that aren't set in valid_flags. + void RetainFlags(uint8_t valid_flags) { + frame_header_.RetainFlags(valid_flags); + } + + // Clear all of the flags in the frame header; for use with frame types that + // don't define any flags, such as WINDOW_UPDATE. + void ClearFlags() { frame_header_.flags = Http2FrameFlag(); } + + // Returns true if the type of frame being decoded can have padding. + bool IsPaddable() const { + return frame_header().type == Http2FrameType::DATA || + frame_header().type == Http2FrameType::HEADERS || + frame_header().type == Http2FrameType::PUSH_PROMISE; + } + + Http2FrameDecoderListener* listener_ = nullptr; + Http2FrameHeader frame_header_; + + // Number of bytes remaining to be decoded, if set; does not include the + // trailing padding once the length of padding has been determined. + // See ReadPadLength. + uint32_t remaining_payload_; + + // The amount of trailing padding after the payload that remains to be + // decoded. See ReadPadLength. + uint32_t remaining_padding_; + + // Generic decoder of structures, which takes care of buffering the needed + // bytes if the encoded structure is split across decode buffers. + Http2StructureDecoder structure_decoder_; +}; + +} // namespace net + +#endif // NET_HTTP2_DECODER_FRAME_DECODER_STATE_H_
diff --git a/net/http2/decoder/frame_decoder_state_test_util.cc b/net/http2/decoder/frame_decoder_state_test_util.cc new file mode 100644 index 0000000..5988b8c --- /dev/null +++ b/net/http2/decoder/frame_decoder_state_test_util.cc
@@ -0,0 +1,34 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/frame_decoder_state_test_util.h" + +#include "base/logging.h" +#include "net/http2/decoder/http2_structure_decoder_test_util.h" +#include "net/http2/http2_structures.h" +#include "net/http2/http2_structures_test_util.h" +#include "net/http2/tools/http2_random.h" +#include "net/http2/tools/random_decoder_test.h" + +namespace net { +namespace test { + +// static +void FrameDecoderStatePeer::Randomize(FrameDecoderState* p, RandomBase* rng) { + VLOG(1) << "FrameDecoderStatePeer::Randomize"; + ::net::test::Randomize(&p->frame_header_, rng); + p->remaining_payload_ = rng->Rand32(); + p->remaining_padding_ = rng->Rand32(); + Http2StructureDecoderPeer::Randomize(&p->structure_decoder_, rng); +} + +// static +void FrameDecoderStatePeer::set_frame_header(const Http2FrameHeader& header, + FrameDecoderState* p) { + VLOG(1) << "FrameDecoderStatePeer::set_frame_header " << header; + p->frame_header_ = header; +} + +} // namespace test +} // namespace net
diff --git a/net/http2/decoder/frame_decoder_state_test_util.h b/net/http2/decoder/frame_decoder_state_test_util.h new file mode 100644 index 0000000..e32a401 --- /dev/null +++ b/net/http2/decoder/frame_decoder_state_test_util.h
@@ -0,0 +1,36 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_FRAME_DECODER_STATE_TEST_UTIL_H_ +#define NET_HTTP2_DECODER_FRAME_DECODER_STATE_TEST_UTIL_H_ + +#include "net/http2/decoder/frame_decoder_state.h" +#include "net/http2/http2_structures.h" +#include "net/http2/tools/random_decoder_test.h" + +namespace net { +namespace test { + +class FrameDecoderStatePeer { + public: + // Randomizes (i.e. corrupts) the fields of the FrameDecoderState. + // PayloadDecoderBaseTest::StartDecoding calls this before passing the first + // decode buffer to the payload decoder, which increases the likelihood of + // detecting any use of prior states of the decoder on the decoding of + // future payloads. + static void Randomize(FrameDecoderState* p, RandomBase* rng); + + // Inject a frame header into the FrameDecoderState. + // PayloadDecoderBaseTest::StartDecoding calls this just after calling + // Randomize (above), to simulate a full frame decoder having just finished + // decoding the common frame header and then calling the appropriate payload + // decoder based on the frame type in that frame header. + static void set_frame_header(const Http2FrameHeader& header, + FrameDecoderState* p); +}; + +} // namespace test +} // namespace net + +#endif // NET_HTTP2_DECODER_FRAME_DECODER_STATE_TEST_UTIL_H_
diff --git a/net/http2/decoder/frame_parts.cc b/net/http2/decoder/frame_parts.cc new file mode 100644 index 0000000..33cd7bf --- /dev/null +++ b/net/http2/decoder/frame_parts.cc
@@ -0,0 +1,527 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/frame_parts.h" + +#include <type_traits> + +#include "base/logging.h" +#include "net/base/escape.h" +#include "net/http2/http2_structures_test_util.h" +#include "net/http2/tools/failure.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::StringPiece; +using std::string; +using ::testing::AssertionFailure; +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using ::testing::ContainerEq; + +namespace net { +namespace test { +namespace { + +static_assert(std::is_base_of<Http2FrameDecoderListener, FrameParts>::value && + !std::is_abstract<FrameParts>::value, + "FrameParts needs to implement all of the methods of " + "Http2FrameDecoderListener"); + +// Compare two optional variables of the same type. +// TODO(jamessynge): Maybe create a ::testing::Matcher for this. +template <class T> +AssertionResult VerifyOptionalEq(const T& opt_a, const T& opt_b) { + if (opt_a) { + if (opt_b) { + VERIFY_EQ(opt_a.value(), opt_b.value()); + } else { + return AssertionFailure() << "opt_b is not set; opt_a.value()=" + << opt_a.value(); + } + } else if (opt_b) { + return AssertionFailure() << "opt_a is not set; opt_b.value()=" + << opt_b.value(); + } + return AssertionSuccess(); +} + +} // namespace + +FrameParts::FrameParts(const Http2FrameHeader& header) : frame_header(header) { + VLOG(1) << "FrameParts, header: " << frame_header; +} + +FrameParts::FrameParts(const Http2FrameHeader& header, StringPiece payload) + : FrameParts(header) { + VLOG(1) << "FrameParts with payload.size() = " << payload.size(); + payload.AppendToString(&this->payload); + opt_payload_length = payload.size(); +} +FrameParts::FrameParts(const Http2FrameHeader& header, + StringPiece payload, + size_t total_pad_length) + : FrameParts(header, payload) { + VLOG(1) << "FrameParts with total_pad_length=" << total_pad_length; + SetTotalPadLength(total_pad_length); +} + +FrameParts::FrameParts(const FrameParts& other) = default; + +FrameParts::~FrameParts() {} + +AssertionResult FrameParts::VerifyEquals(const FrameParts& that) const { +#define COMMON_MESSAGE "\n this: " << *this << "\n that: " << that + + VERIFY_EQ(frame_header, that.frame_header) << COMMON_MESSAGE; + VERIFY_EQ(payload, that.payload) << COMMON_MESSAGE; + VERIFY_EQ(padding, that.padding) << COMMON_MESSAGE; + VERIFY_EQ(altsvc_origin, that.altsvc_origin) << COMMON_MESSAGE; + VERIFY_EQ(altsvc_value, that.altsvc_value) << COMMON_MESSAGE; + VERIFY_EQ(settings, that.settings) << COMMON_MESSAGE; + +#define VERIFY_OPTIONAL_FIELD(field_name) \ + VERIFY_SUCCESS(VerifyOptionalEq(field_name, that.field_name)) + + VERIFY_OPTIONAL_FIELD(opt_altsvc_origin_length) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_altsvc_value_length) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_goaway) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_missing_length) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_pad_length) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_ping) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_priority) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_push_promise) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_rst_stream_error_code) << COMMON_MESSAGE; + VERIFY_OPTIONAL_FIELD(opt_window_update_increment) << COMMON_MESSAGE; + +#undef VERIFY_OPTIONAL_FIELD + + return AssertionSuccess(); +} + +void FrameParts::SetTotalPadLength(size_t total_pad_length) { + opt_pad_length.reset(); + padding.clear(); + if (total_pad_length > 0) { + ASSERT_LE(total_pad_length, 256u); + ASSERT_TRUE(frame_header.IsPadded()); + opt_pad_length = total_pad_length - 1; + char zero = 0; + padding.append(opt_pad_length.value(), zero); + } + + if (opt_pad_length) { + VLOG(1) << "SetTotalPadLength: pad_length=" << opt_pad_length.value(); + } else { + VLOG(1) << "SetTotalPadLength: has no pad length"; + } +} + +void FrameParts::SetAltSvcExpected(StringPiece origin, StringPiece value) { + origin.AppendToString(&altsvc_origin); + value.AppendToString(&altsvc_value); + opt_altsvc_origin_length = origin.size(); + opt_altsvc_value_length = value.size(); +} + +bool FrameParts::OnFrameHeader(const Http2FrameHeader& header) { + ADD_FAILURE() << "OnFrameHeader: " << *this; + return true; +} + +void FrameParts::OnDataStart(const Http2FrameHeader& header) { + VLOG(1) << "OnDataStart: " << header; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::DATA)) << *this; + opt_payload_length = header.payload_length; +} + +void FrameParts::OnDataPayload(const char* data, size_t len) { + VLOG(1) << "OnDataPayload: len=" << len << "; frame_header: " << frame_header; + ASSERT_TRUE(InFrameOfType(Http2FrameType::DATA)) << *this; + ASSERT_TRUE( + AppendString(StringPiece(data, len), &payload, &opt_payload_length)); +} + +void FrameParts::OnDataEnd() { + VLOG(1) << "OnDataEnd; frame_header: " << frame_header; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::DATA)) << *this; +} + +void FrameParts::OnHeadersStart(const Http2FrameHeader& header) { + VLOG(1) << "OnHeadersStart: " << header; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::HEADERS)) << *this; + opt_payload_length = header.payload_length; +} + +void FrameParts::OnHeadersPriority(const Http2PriorityFields& priority) { + VLOG(1) << "OnHeadersPriority: priority: " << priority + << "; frame_header: " << frame_header; + ASSERT_TRUE(InFrameOfType(Http2FrameType::HEADERS)) << *this; + ASSERT_FALSE(opt_priority); + opt_priority = priority; + ASSERT_TRUE(opt_payload_length); + opt_payload_length = + opt_payload_length.value() - Http2PriorityFields::EncodedSize(); +} + +void FrameParts::OnHpackFragment(const char* data, size_t len) { + VLOG(1) << "OnHpackFragment: len=" << len + << "; frame_header: " << frame_header; + ASSERT_TRUE(got_start_callback); + ASSERT_FALSE(got_end_callback); + ASSERT_TRUE(FrameCanHaveHpackPayload(frame_header)) << *this; + ASSERT_TRUE( + AppendString(StringPiece(data, len), &payload, &opt_payload_length)); +} + +void FrameParts::OnHeadersEnd() { + VLOG(1) << "OnHeadersEnd; frame_header: " << frame_header; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::HEADERS)) << *this; +} + +void FrameParts::OnPriorityFrame(const Http2FrameHeader& header, + const Http2PriorityFields& priority) { + VLOG(1) << "OnPriorityFrame: " << header << "; priority: " << priority; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::PRIORITY)) << *this; + ASSERT_FALSE(opt_priority); + opt_priority = priority; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::PRIORITY)) << *this; +} + +void FrameParts::OnContinuationStart(const Http2FrameHeader& header) { + VLOG(1) << "OnContinuationStart: " << header; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::CONTINUATION)) << *this; + opt_payload_length = header.payload_length; +} + +void FrameParts::OnContinuationEnd() { + VLOG(1) << "OnContinuationEnd; frame_header: " << frame_header; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::CONTINUATION)) << *this; +} + +void FrameParts::OnPadLength(size_t trailing_length) { + VLOG(1) << "OnPadLength: trailing_length=" << trailing_length; + ASSERT_TRUE(InPaddedFrame()) << *this; + ASSERT_FALSE(opt_pad_length); + ASSERT_TRUE(opt_payload_length); + size_t total_padding_length = trailing_length + 1; + ASSERT_GE(opt_payload_length.value(), static_cast<int>(total_padding_length)); + opt_payload_length = opt_payload_length.value() - total_padding_length; + opt_pad_length = trailing_length; +} + +void FrameParts::OnPadding(const char* pad, size_t skipped_length) { + VLOG(1) << "OnPadding: skipped_length=" << skipped_length; + ASSERT_TRUE(InPaddedFrame()) << *this; + ASSERT_TRUE(opt_pad_length); + ASSERT_TRUE(AppendString(StringPiece(pad, skipped_length), &padding, + &opt_pad_length)); +} + +void FrameParts::OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode error_code) { + VLOG(1) << "OnRstStream: " << header << "; code=" << error_code; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::RST_STREAM)) << *this; + ASSERT_FALSE(opt_rst_stream_error_code); + opt_rst_stream_error_code = error_code; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::RST_STREAM)) << *this; +} + +void FrameParts::OnSettingsStart(const Http2FrameHeader& header) { + VLOG(1) << "OnSettingsStart: " << header; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::SETTINGS)) << *this; + ASSERT_EQ(0u, settings.size()); + ASSERT_FALSE(header.IsAck()) << header; +} + +void FrameParts::OnSetting(const Http2SettingFields& setting_fields) { + VLOG(1) << "OnSetting: " << setting_fields; + ASSERT_TRUE(InFrameOfType(Http2FrameType::SETTINGS)) << *this; + settings.push_back(setting_fields); +} + +void FrameParts::OnSettingsEnd() { + VLOG(1) << "OnSettingsEnd; frame_header: " << frame_header; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::SETTINGS)) << *this; +} + +void FrameParts::OnSettingsAck(const Http2FrameHeader& header) { + VLOG(1) << "OnSettingsAck: " << header; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::SETTINGS)) << *this; + ASSERT_EQ(0u, settings.size()); + ASSERT_TRUE(header.IsAck()); + ASSERT_TRUE(EndFrameOfType(Http2FrameType::SETTINGS)) << *this; +} + +void FrameParts::OnPushPromiseStart(const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) { + VLOG(1) << "OnPushPromiseStart header: " << header << "; promise: " << promise + << "; total_padding_length: " << total_padding_length; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::PUSH_PROMISE)) << *this; + ASSERT_GE(header.payload_length, Http2PushPromiseFields::EncodedSize()); + opt_payload_length = + header.payload_length - Http2PushPromiseFields::EncodedSize(); + ASSERT_FALSE(opt_push_promise); + opt_push_promise = promise; + if (total_padding_length > 0) { + ASSERT_GE(opt_payload_length.value(), + static_cast<int>(total_padding_length)); + OnPadLength(total_padding_length - 1); + } else { + ASSERT_FALSE(header.IsPadded()); + } +} + +void FrameParts::OnPushPromiseEnd() { + VLOG(1) << "OnPushPromiseEnd; frame_header: " << frame_header; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::PUSH_PROMISE)) << *this; +} + +void FrameParts::OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) { + VLOG(1) << "OnPing header: " << header << " ping: " << ping; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::PING)) << *this; + ASSERT_FALSE(header.IsAck()); + ASSERT_FALSE(opt_ping); + opt_ping = ping; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::PING)) << *this; +} + +void FrameParts::OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) { + VLOG(1) << "OnPingAck header: " << header << " ping: " << ping; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::PING)) << *this; + ASSERT_TRUE(header.IsAck()); + ASSERT_FALSE(opt_ping); + opt_ping = ping; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::PING)) << *this; +} + +void FrameParts::OnGoAwayStart(const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) { + VLOG(1) << "OnGoAwayStart: " << goaway; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::GOAWAY)) << *this; + ASSERT_FALSE(opt_goaway); + opt_goaway = goaway; + opt_payload_length = header.payload_length - Http2GoAwayFields::EncodedSize(); +} + +void FrameParts::OnGoAwayOpaqueData(const char* data, size_t len) { + VLOG(1) << "OnGoAwayOpaqueData: len=" << len; + ASSERT_TRUE(InFrameOfType(Http2FrameType::GOAWAY)) << *this; + ASSERT_TRUE( + AppendString(StringPiece(data, len), &payload, &opt_payload_length)); +} + +void FrameParts::OnGoAwayEnd() { + VLOG(1) << "OnGoAwayEnd; frame_header: " << frame_header; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::GOAWAY)) << *this; +} + +void FrameParts::OnWindowUpdate(const Http2FrameHeader& header, + uint32_t increment) { + VLOG(1) << "OnWindowUpdate header: " << header + << " increment=" << increment; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::WINDOW_UPDATE)) << *this; + ASSERT_FALSE(opt_window_update_increment); + opt_window_update_increment = increment; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::WINDOW_UPDATE)) << *this; +} + +void FrameParts::OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) { + VLOG(1) << "OnAltSvcStart: " << header + << " origin_length: " << origin_length + << " value_length: " << value_length; + ASSERT_TRUE(StartFrameOfType(header, Http2FrameType::ALTSVC)) << *this; + ASSERT_FALSE(opt_altsvc_origin_length); + opt_altsvc_origin_length = origin_length; + ASSERT_FALSE(opt_altsvc_value_length); + opt_altsvc_value_length = value_length; +} + +void FrameParts::OnAltSvcOriginData(const char* data, size_t len) { + VLOG(1) << "OnAltSvcOriginData: len=" << len; + ASSERT_TRUE(InFrameOfType(Http2FrameType::ALTSVC)) << *this; + ASSERT_TRUE(AppendString(StringPiece(data, len), &altsvc_origin, + &opt_altsvc_origin_length)); +} + +void FrameParts::OnAltSvcValueData(const char* data, size_t len) { + VLOG(1) << "OnAltSvcValueData: len=" << len; + ASSERT_TRUE(InFrameOfType(Http2FrameType::ALTSVC)) << *this; + ASSERT_TRUE(AppendString(StringPiece(data, len), &altsvc_value, + &opt_altsvc_value_length)); +} + +void FrameParts::OnAltSvcEnd() { + VLOG(1) << "OnAltSvcEnd; frame_header: " << frame_header; + ASSERT_TRUE(EndFrameOfType(Http2FrameType::ALTSVC)) << *this; +} + +void FrameParts::OnUnknownStart(const Http2FrameHeader& header) { + VLOG(1) << "OnUnknownStart: " << header; + ASSERT_FALSE(IsSupportedHttp2FrameType(header.type)) << header; + ASSERT_FALSE(got_start_callback); + ASSERT_EQ(frame_header, header); + got_start_callback = true; + opt_payload_length = header.payload_length; +} + +void FrameParts::OnUnknownPayload(const char* data, size_t len) { + VLOG(1) << "OnUnknownPayload: len=" << len; + ASSERT_FALSE(IsSupportedHttp2FrameType(frame_header.type)) << *this; + ASSERT_TRUE(got_start_callback); + ASSERT_FALSE(got_end_callback); + ASSERT_TRUE( + AppendString(StringPiece(data, len), &payload, &opt_payload_length)); +} + +void FrameParts::OnUnknownEnd() { + VLOG(1) << "OnUnknownEnd; frame_header: " << frame_header; + ASSERT_FALSE(IsSupportedHttp2FrameType(frame_header.type)) << *this; + ASSERT_TRUE(got_start_callback); + ASSERT_FALSE(got_end_callback); + got_end_callback = true; +} + +void FrameParts::OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) { + VLOG(1) << "OnPaddingTooLong: " << header + << "; missing_length: " << missing_length; + ASSERT_EQ(frame_header, header); + ASSERT_FALSE(got_end_callback); + ASSERT_TRUE(FrameIsPadded(header)); + ASSERT_FALSE(opt_pad_length); + ASSERT_FALSE(opt_missing_length); + opt_missing_length = missing_length; + got_start_callback = true; + got_end_callback = true; +} + +void FrameParts::OnFrameSizeError(const Http2FrameHeader& header) { + VLOG(1) << "OnFrameSizeError: " << header; + ASSERT_EQ(frame_header, header); + ASSERT_FALSE(got_end_callback); + ASSERT_FALSE(has_frame_size_error); + has_frame_size_error = true; + got_end_callback = true; +} + +void FrameParts::OutputTo(std::ostream& out) const { + out << "FrameParts{\n frame_header: " << frame_header << "\n"; + if (!payload.empty()) { + out << " payload=\"" << EscapeQueryParamValue(payload, false) << "\"\n"; + } + if (!padding.empty()) { + out << " padding=\"" << EscapeQueryParamValue(padding, false) << "\"\n"; + } + if (!altsvc_origin.empty()) { + out << " altsvc_origin=\"" << EscapeQueryParamValue(altsvc_origin, false) + << "\"\n"; + } + if (!altsvc_value.empty()) { + out << " altsvc_value=\"" << EscapeQueryParamValue(altsvc_value, false) + << "\"\n"; + } + if (opt_priority) { + out << " priority=" << opt_priority.value() << "\n"; + } + if (opt_rst_stream_error_code) { + out << " rst_stream=" << opt_rst_stream_error_code.value() << "\n"; + } + if (opt_push_promise) { + out << " push_promise=" << opt_push_promise.value() << "\n"; + } + if (opt_ping) { + out << " ping=" << opt_ping.value() << "\n"; + } + if (opt_goaway) { + out << " goaway=" << opt_goaway.value() << "\n"; + } + if (opt_window_update_increment) { + out << " window_update=" << opt_window_update_increment.value() << "\n"; + } + if (opt_payload_length) { + out << " payload_length=" << opt_payload_length.value() << "\n"; + } + if (opt_pad_length) { + out << " pad_length=" << opt_pad_length.value() << "\n"; + } + if (opt_missing_length) { + out << " missing_length=" << opt_missing_length.value() << "\n"; + } + if (opt_altsvc_origin_length) { + out << " origin_length=" << opt_altsvc_origin_length.value() << "\n"; + } + if (opt_altsvc_value_length) { + out << " value_length=" << opt_altsvc_value_length.value() << "\n"; + } + if (has_frame_size_error) { + out << " has_frame_size_error\n"; + } + if (got_start_callback) { + out << " got_start_callback\n"; + } + if (got_end_callback) { + out << " got_end_callback\n"; + } + for (size_t ndx = 0; ndx < settings.size(); ++ndx) { + out << " setting[" << ndx << "]=" << settings[ndx]; + } + out << "}"; +} + +AssertionResult FrameParts::StartFrameOfType( + const Http2FrameHeader& header, + Http2FrameType expected_frame_type) { + VERIFY_EQ(header.type, expected_frame_type); + VERIFY_FALSE(got_start_callback); + VERIFY_FALSE(got_end_callback); + VERIFY_EQ(frame_header, header); + got_start_callback = true; + return AssertionSuccess(); +} + +AssertionResult FrameParts::InFrameOfType(Http2FrameType expected_frame_type) { + VERIFY_TRUE(got_start_callback); + VERIFY_FALSE(got_end_callback); + VERIFY_EQ(frame_header.type, expected_frame_type); + return AssertionSuccess(); +} + +AssertionResult FrameParts::EndFrameOfType(Http2FrameType expected_frame_type) { + VERIFY_SUCCESS(InFrameOfType(expected_frame_type)); + got_end_callback = true; + return AssertionSuccess(); +} + +AssertionResult FrameParts::InPaddedFrame() { + VERIFY_TRUE(got_start_callback); + VERIFY_FALSE(got_end_callback); + VERIFY_TRUE(FrameIsPadded(frame_header)); + return AssertionSuccess(); +} + +AssertionResult FrameParts::AppendString(StringPiece source, + string* target, + base::Optional<int>* opt_length) { + source.AppendToString(target); + if (opt_length != nullptr) { + VERIFY_TRUE(*opt_length) << "Length is not set yet\n" << *this; + VERIFY_LE(target->size(), static_cast<size_t>(opt_length->value())) + << "String too large; source.size() = " << source.size() << "\n" + << *this; + } + return ::testing::AssertionSuccess(); +} + +std::ostream& operator<<(std::ostream& out, const FrameParts& v) { + v.OutputTo(out); + return out; +} + +} // namespace test +} // namespace net
diff --git a/net/http2/decoder/frame_parts.h b/net/http2/decoder/frame_parts.h new file mode 100644 index 0000000..c64dea69 --- /dev/null +++ b/net/http2/decoder/frame_parts.h
@@ -0,0 +1,176 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_FRAME_PARTS_H_ +#define NET_HTTP2_DECODER_FRAME_PARTS_H_ + +// FrameParts implements Http2FrameDecoderListener, recording the callbacks +// during the decoding of a single frame. It is also used for comparing the +// info that a test expects to be recorded during the decoding of a frame +// with the actual recorded value (i.e. by providing a comparator). + +// TODO(jamessynge): Convert FrameParts to a class, hide the members, add +// getters/setters. + +#include <stddef.h> + +#include <string> +#include <vector> + +#include "base/logging.h" +#include "base/optional.h" +#include "base/strings/string_piece.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace test { + +struct FrameParts; + +std::ostream& operator<<(std::ostream& out, const FrameParts& v); + +struct FrameParts : public Http2FrameDecoderListener { + // The first callback for every type of frame includes the frame header; this + // is the only constructor used during decoding of a frame. + explicit FrameParts(const Http2FrameHeader& header); + + // For use in tests where the expected frame has a variable size payload. + FrameParts(const Http2FrameHeader& header, base::StringPiece payload); + + // For use in tests where the expected frame has a variable size payload + // and may be padded. + FrameParts(const Http2FrameHeader& header, + base::StringPiece payload, + size_t total_pad_length); + + FrameParts(const FrameParts& other); + + ~FrameParts() override; + + // Returns AssertionSuccess() if they're equal, else AssertionFailure() + // with info about the difference. + ::testing::AssertionResult VerifyEquals(const FrameParts& other) const; + + // Format this FrameParts object. + void OutputTo(std::ostream& out) const; + + // Set the total padding length (0 to 256). + void SetTotalPadLength(size_t total_pad_length); + + // Set the origin and value expected in an ALTSVC frame. + void SetAltSvcExpected(base::StringPiece origin, base::StringPiece value); + + // Http2FrameDecoderListener methods: + bool OnFrameHeader(const Http2FrameHeader& header) override; + void OnDataStart(const Http2FrameHeader& header) override; + void OnDataPayload(const char* data, size_t len) override; + void OnDataEnd() override; + void OnHeadersStart(const Http2FrameHeader& header) override; + void OnHeadersPriority(const Http2PriorityFields& priority) override; + void OnHpackFragment(const char* data, size_t len) override; + void OnHeadersEnd() override; + void OnPriorityFrame(const Http2FrameHeader& header, + const Http2PriorityFields& priority) override; + void OnContinuationStart(const Http2FrameHeader& header) override; + void OnContinuationEnd() override; + void OnPadLength(size_t trailing_length) override; + void OnPadding(const char* pad, size_t skipped_length) override; + void OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode error_code) override; + void OnSettingsStart(const Http2FrameHeader& header) override; + void OnSetting(const Http2SettingFields& setting_fields) override; + void OnSettingsEnd() override; + void OnSettingsAck(const Http2FrameHeader& header) override; + void OnPushPromiseStart(const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) override; + void OnPushPromiseEnd() override; + void OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) override; + void OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) override; + void OnGoAwayStart(const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) override; + void OnGoAwayOpaqueData(const char* data, size_t len) override; + void OnGoAwayEnd() override; + void OnWindowUpdate(const Http2FrameHeader& header, + uint32_t increment) override; + void OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) override; + void OnAltSvcOriginData(const char* data, size_t len) override; + void OnAltSvcValueData(const char* data, size_t len) override; + void OnAltSvcEnd() override; + void OnUnknownStart(const Http2FrameHeader& header) override; + void OnUnknownPayload(const char* data, size_t len) override; + void OnUnknownEnd() override; + void OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) override; + void OnFrameSizeError(const Http2FrameHeader& header) override; + + // The fields are public for access by tests. + + const Http2FrameHeader frame_header; + + std::string payload; + std::string padding; + std::string altsvc_origin; + std::string altsvc_value; + + base::Optional<Http2PriorityFields> opt_priority; + base::Optional<Http2ErrorCode> opt_rst_stream_error_code; + base::Optional<Http2PushPromiseFields> opt_push_promise; + base::Optional<Http2PingFields> opt_ping; + base::Optional<Http2GoAwayFields> opt_goaway; + + base::Optional<int> opt_pad_length; + base::Optional<int> opt_payload_length; + base::Optional<int> opt_missing_length; + base::Optional<int> opt_altsvc_origin_length; + base::Optional<int> opt_altsvc_value_length; + + base::Optional<size_t> opt_window_update_increment; + + bool has_frame_size_error = false; + + std::vector<Http2SettingFields> settings; + + // These booleans are not checked by CompareCollectedFrames. + bool got_start_callback = false; + bool got_end_callback = false; + + private: + // ASSERT during an On* method that we're handling a frame of type + // expected_frame_type, and have not already received other On* methods + // (i.e. got_start_callback is false). + ::testing::AssertionResult StartFrameOfType( + const Http2FrameHeader& header, + Http2FrameType expected_frame_type); + + // ASSERT that StartFrameOfType has already been called with + // expected_frame_type (i.e. got_start_callback has been called), and that + // EndFrameOfType has not yet been called (i.e. got_end_callback is false). + ::testing::AssertionResult InFrameOfType(Http2FrameType expected_frame_type); + + // ASSERT that we're InFrameOfType, and then sets got_end_callback=true. + ::testing::AssertionResult EndFrameOfType(Http2FrameType expected_frame_type); + + // ASSERT that we're in the middle of processing a frame that is padded. + ::testing::AssertionResult InPaddedFrame(); + + // Append source to target. If opt_length is not nullptr, then verifies that + // the optional has a value (i.e. that the necessary On*Start method has been + // called), and that target is not longer than opt_length->value(). + ::testing::AssertionResult AppendString(base::StringPiece source, + std::string* target, + base::Optional<int>* opt_length); +}; + +} // namespace test +} // namespace net + +#endif // NET_HTTP2_DECODER_FRAME_PARTS_H_
diff --git a/net/http2/decoder/frame_parts_collector.cc b/net/http2/decoder/frame_parts_collector.cc new file mode 100644 index 0000000..05c54880 --- /dev/null +++ b/net/http2/decoder/frame_parts_collector.cc
@@ -0,0 +1,112 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/frame_parts_collector.h" + +#include <utility> + +#include "base/logging.h" +#include "net/http2/http2_structures_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace test { + +FramePartsCollector::FramePartsCollector() {} +FramePartsCollector::~FramePartsCollector() {} + +void FramePartsCollector::Reset() { + current_frame_.reset(); + collected_frames_.clear(); + expected_header_set_ = false; +} + +const FrameParts* FramePartsCollector::frame(size_t n) const { + if (n < size()) { + return collected_frames_.at(n).get(); + } + CHECK(n == size()); + return current_frame(); +} + +void FramePartsCollector::ExpectFrameHeader(const Http2FrameHeader& header) { + EXPECT_FALSE(IsInProgress()); + EXPECT_FALSE(expected_header_set_) << "expected_header_: " + << expected_header_; + expected_header_ = header; + expected_header_set_ = true; + // OnFrameHeader is called before the flags are scrubbed, but the other + // methods are called after, so scrub the invalid flags from expected_header_. + ScrubFlagsOfHeader(&expected_header_); +} + +void FramePartsCollector::TestExpectedHeader(const Http2FrameHeader& header) { + if (expected_header_set_) { + EXPECT_EQ(header, expected_header_); + expected_header_set_ = false; + } +} + +Http2FrameDecoderListener* FramePartsCollector::StartFrame( + const Http2FrameHeader& header) { + TestExpectedHeader(header); + EXPECT_FALSE(IsInProgress()); + if (current_frame_ == nullptr) { + current_frame_.reset(new FrameParts(header)); + } + return current_frame(); +} + +Http2FrameDecoderListener* FramePartsCollector::StartAndEndFrame( + const Http2FrameHeader& header) { + TestExpectedHeader(header); + EXPECT_FALSE(IsInProgress()); + if (current_frame_ == nullptr) { + current_frame_.reset(new FrameParts(header)); + } + Http2FrameDecoderListener* result = current_frame(); + collected_frames_.push_back(std::move(current_frame_)); + return result; +} + +Http2FrameDecoderListener* FramePartsCollector::CurrentFrame() { + EXPECT_TRUE(IsInProgress()); + if (current_frame_ == nullptr) { + return &failing_listener_; + } + return current_frame(); +} + +Http2FrameDecoderListener* FramePartsCollector::EndFrame() { + EXPECT_TRUE(IsInProgress()); + if (current_frame_ == nullptr) { + return &failing_listener_; + } + Http2FrameDecoderListener* result = current_frame(); + collected_frames_.push_back(std::move(current_frame_)); + return result; +} + +Http2FrameDecoderListener* FramePartsCollector::FrameError( + const Http2FrameHeader& header) { + TestExpectedHeader(header); + if (current_frame_ == nullptr) { + // The decoder may detect an error before making any calls to the listener + // regarding the frame, in which case current_frame_==nullptr and we need + // to create a FrameParts instance. + current_frame_.reset(new FrameParts(header)); + } else { + // Similarly, the decoder may have made calls to the listener regarding the + // frame before detecting the error; for example, the DATA payload decoder + // calls OnDataStart before it can detect padding errors, hence before it + // can call OnPaddingTooLong. + EXPECT_EQ(header, current_frame_->frame_header); + } + Http2FrameDecoderListener* result = current_frame(); + collected_frames_.push_back(std::move(current_frame_)); + return result; +} + +} // namespace test +} // namespace net
diff --git a/net/http2/decoder/frame_parts_collector.h b/net/http2/decoder/frame_parts_collector.h new file mode 100644 index 0000000..c411e48e --- /dev/null +++ b/net/http2/decoder/frame_parts_collector.h
@@ -0,0 +1,116 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_FRAME_PARTS_COLLECTOR_H_ +#define NET_HTTP2_DECODER_FRAME_PARTS_COLLECTOR_H_ + +// FramePartsCollector is a base class for Http2FrameDecoderListener +// implementations that create one FrameParts instance for each decoded frame. + +#include <stddef.h> + +#include <memory> +#include <vector> + +#include "net/http2/decoder/frame_parts.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/decoder/http2_frame_decoder_listener_test_util.h" +#include "net/http2/http2_structures.h" + +namespace net { +namespace test { + +class FramePartsCollector : public FailingHttp2FrameDecoderListener { + public: + FramePartsCollector(); + ~FramePartsCollector() override; + + // Toss out the collected data. + void Reset(); + + // Returns true if has started recording the info for a frame and has not yet + // finished doing so. + bool IsInProgress() const { return current_frame_ != nullptr; } + + // Returns the FrameParts instance into which we're currently recording + // callback info if IsInProgress, else nullptr. + const FrameParts* current_frame() const { return current_frame_.get(); } + + // Returns the completely collected FrameParts instances. + const std::vector<std::unique_ptr<FrameParts>>& collected_frames() const { + return collected_frames_; + } + + // Returns the number of completely collected FrameParts instances. + size_t size() const { return collected_frames_.size(); } + + // Returns the n'th frame, where 0 is the oldest of the collected frames, + // and n==size() is the frame currently being collected, if there is one. + // Returns nullptr if the requested index is not valid. + const FrameParts* frame(size_t n) const; + + protected: + // In support of OnFrameHeader, set the header that we expect to be used in + // the next call. + // TODO(jamessynge): Remove ExpectFrameHeader et al. once done with supporting + // SpdyFramer's exact states. + void ExpectFrameHeader(const Http2FrameHeader& header); + + // For use in implementing On*Start methods of Http2FrameDecoderListener, + // returns a FrameParts instance, which will be newly created if + // IsInProgress==false (which the caller should ensure), else will be the + // current_frame(); never returns nullptr. + // If called when IsInProgress==true, a test failure will be recorded. + Http2FrameDecoderListener* StartFrame(const Http2FrameHeader& header); + + // For use in implementing On* callbacks, such as OnPingAck, that are the only + // call expected for the frame being decoded; not for On*Start methods. + // Returns a FrameParts instance, which will be newly created if + // IsInProgress==false (which the caller should ensure), else will be the + // current_frame(); never returns nullptr. + // If called when IsInProgress==true, a test failure will be recorded. + Http2FrameDecoderListener* StartAndEndFrame(const Http2FrameHeader& header); + + // If IsInProgress==true, returns the FrameParts into which the current + // frame is being recorded; else records a test failure and returns + // failing_listener_, which will record a test failure when any of its + // On* methods is called. + Http2FrameDecoderListener* CurrentFrame(); + + // For use in implementing On*End methods, pushes the current frame onto + // the vector of completed frames, and returns a pointer to it for recording + // the info in the final call. If IsInProgress==false, records a test failure + // and returns failing_listener_, which will record a test failure when any + // of its On* methods is called. + Http2FrameDecoderListener* EndFrame(); + + // For use in implementing OnPaddingTooLong and OnFrameSizeError, is + // equivalent to EndFrame() if IsInProgress==true, else equivalent to + // StartAndEndFrame(). + Http2FrameDecoderListener* FrameError(const Http2FrameHeader& header); + + private: + // Returns the mutable FrameParts instance into which we're currently + // recording callback info if IsInProgress, else nullptr. + FrameParts* current_frame() { return current_frame_.get(); } + + // If expected header is set, verify that it matches the header param. + // TODO(jamessynge): Remove TestExpectedHeader et al. once done + // with supporting SpdyFramer's exact states. + void TestExpectedHeader(const Http2FrameHeader& header); + + std::unique_ptr<FrameParts> current_frame_; + std::vector<std::unique_ptr<FrameParts>> collected_frames_; + FailingHttp2FrameDecoderListener failing_listener_; + + // TODO(jamessynge): Remove expected_header_ et al. once done with supporting + // SpdyFramer's exact states. + Http2FrameHeader expected_header_; + bool expected_header_set_ = false; +}; + +} // namespace test +} // namespace net + +#endif // NET_HTTP2_DECODER_FRAME_PARTS_COLLECTOR_H_
diff --git a/net/http2/decoder/frame_parts_collector_listener.cc b/net/http2/decoder/frame_parts_collector_listener.cc new file mode 100644 index 0000000..83a1ea34 --- /dev/null +++ b/net/http2/decoder/frame_parts_collector_listener.cc
@@ -0,0 +1,230 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/frame_parts_collector_listener.h" + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace test { + +bool FramePartsCollectorListener::OnFrameHeader( + const Http2FrameHeader& header) { + VLOG(1) << "OnFrameHeader: " << header; + ExpectFrameHeader(header); + return true; +} + +void FramePartsCollectorListener::OnDataStart(const Http2FrameHeader& header) { + VLOG(1) << "OnDataStart: " << header; + StartFrame(header)->OnDataStart(header); +} + +void FramePartsCollectorListener::OnDataPayload(const char* data, size_t len) { + VLOG(1) << "OnDataPayload: len=" << len; + CurrentFrame()->OnDataPayload(data, len); +} + +void FramePartsCollectorListener::OnDataEnd() { + VLOG(1) << "OnDataEnd"; + EndFrame()->OnDataEnd(); +} + +void FramePartsCollectorListener::OnHeadersStart( + const Http2FrameHeader& header) { + VLOG(1) << "OnHeadersStart: " << header; + StartFrame(header)->OnHeadersStart(header); +} + +void FramePartsCollectorListener::OnHeadersPriority( + const Http2PriorityFields& priority) { + VLOG(1) << "OnHeadersPriority: " << priority; + CurrentFrame()->OnHeadersPriority(priority); +} + +void FramePartsCollectorListener::OnHpackFragment(const char* data, + size_t len) { + VLOG(1) << "OnHpackFragment: len=" << len; + CurrentFrame()->OnHpackFragment(data, len); +} + +void FramePartsCollectorListener::OnHeadersEnd() { + VLOG(1) << "OnHeadersEnd"; + EndFrame()->OnHeadersEnd(); +} + +void FramePartsCollectorListener::OnPriorityFrame( + const Http2FrameHeader& header, + const Http2PriorityFields& priority_fields) { + VLOG(1) << "OnPriority: " << header << "; " << priority_fields; + StartAndEndFrame(header)->OnPriorityFrame(header, priority_fields); +} + +void FramePartsCollectorListener::OnContinuationStart( + const Http2FrameHeader& header) { + VLOG(1) << "OnContinuationStart: " << header; + StartFrame(header)->OnContinuationStart(header); +} + +void FramePartsCollectorListener::OnContinuationEnd() { + VLOG(1) << "OnContinuationEnd"; + EndFrame()->OnContinuationEnd(); +} + +void FramePartsCollectorListener::OnPadLength(size_t pad_length) { + VLOG(1) << "OnPadLength: " << pad_length; + CurrentFrame()->OnPadLength(pad_length); +} + +void FramePartsCollectorListener::OnPadding(const char* padding, + size_t skipped_length) { + VLOG(1) << "OnPadding: " << skipped_length; + CurrentFrame()->OnPadding(padding, skipped_length); +} + +void FramePartsCollectorListener::OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode error_code) { + VLOG(1) << "OnRstStream: " << header << "; error_code=" << error_code; + StartAndEndFrame(header)->OnRstStream(header, error_code); +} + +void FramePartsCollectorListener::OnSettingsStart( + const Http2FrameHeader& header) { + VLOG(1) << "OnSettingsStart: " << header; + EXPECT_EQ(Http2FrameType::SETTINGS, header.type) << header; + EXPECT_EQ(Http2FrameFlag(), header.flags) << header; + StartFrame(header)->OnSettingsStart(header); +} + +void FramePartsCollectorListener::OnSetting( + const Http2SettingFields& setting_fields) { + VLOG(1) << "Http2SettingFields: setting_fields=" << setting_fields; + CurrentFrame()->OnSetting(setting_fields); +} + +void FramePartsCollectorListener::OnSettingsEnd() { + VLOG(1) << "OnSettingsEnd"; + EndFrame()->OnSettingsEnd(); +} + +void FramePartsCollectorListener::OnSettingsAck( + const Http2FrameHeader& header) { + VLOG(1) << "OnSettingsAck: " << header; + StartAndEndFrame(header)->OnSettingsAck(header); +} + +void FramePartsCollectorListener::OnPushPromiseStart( + const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) { + VLOG(1) << "OnPushPromiseStart header: " << header << " promise: " << promise + << " total_padding_length: " << total_padding_length; + EXPECT_EQ(Http2FrameType::PUSH_PROMISE, header.type); + StartFrame(header)->OnPushPromiseStart(header, promise, total_padding_length); +} + +void FramePartsCollectorListener::OnPushPromiseEnd() { + VLOG(1) << "OnPushPromiseEnd"; + EndFrame()->OnPushPromiseEnd(); +} + +void FramePartsCollectorListener::OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) { + VLOG(1) << "OnPing: " << header << "; " << ping; + StartAndEndFrame(header)->OnPing(header, ping); +} + +void FramePartsCollectorListener::OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) { + VLOG(1) << "OnPingAck: " << header << "; " << ping; + StartAndEndFrame(header)->OnPingAck(header, ping); +} + +void FramePartsCollectorListener::OnGoAwayStart( + const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) { + VLOG(1) << "OnGoAwayStart header: " << header << "; goaway: " << goaway; + StartFrame(header)->OnGoAwayStart(header, goaway); +} + +void FramePartsCollectorListener::OnGoAwayOpaqueData(const char* data, + size_t len) { + VLOG(1) << "OnGoAwayOpaqueData: len=" << len; + CurrentFrame()->OnGoAwayOpaqueData(data, len); +} + +void FramePartsCollectorListener::OnGoAwayEnd() { + VLOG(1) << "OnGoAwayEnd"; + EndFrame()->OnGoAwayEnd(); +} + +void FramePartsCollectorListener::OnWindowUpdate( + const Http2FrameHeader& header, + uint32_t window_size_increment) { + VLOG(1) << "OnWindowUpdate: " << header + << "; window_size_increment=" << window_size_increment; + EXPECT_EQ(Http2FrameType::WINDOW_UPDATE, header.type); + StartAndEndFrame(header)->OnWindowUpdate(header, window_size_increment); +} + +void FramePartsCollectorListener::OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) { + VLOG(1) << "OnAltSvcStart header: " << header + << "; origin_length=" << origin_length + << "; value_length=" << value_length; + StartFrame(header)->OnAltSvcStart(header, origin_length, value_length); +} + +void FramePartsCollectorListener::OnAltSvcOriginData(const char* data, + size_t len) { + VLOG(1) << "OnAltSvcOriginData: len=" << len; + CurrentFrame()->OnAltSvcOriginData(data, len); +} + +void FramePartsCollectorListener::OnAltSvcValueData(const char* data, + size_t len) { + VLOG(1) << "OnAltSvcValueData: len=" << len; + CurrentFrame()->OnAltSvcValueData(data, len); +} + +void FramePartsCollectorListener::OnAltSvcEnd() { + VLOG(1) << "OnAltSvcEnd"; + EndFrame()->OnAltSvcEnd(); +} + +void FramePartsCollectorListener::OnUnknownStart( + const Http2FrameHeader& header) { + VLOG(1) << "OnUnknownStart: " << header; + StartFrame(header)->OnUnknownStart(header); +} + +void FramePartsCollectorListener::OnUnknownPayload(const char* data, + size_t len) { + VLOG(1) << "OnUnknownPayload: len=" << len; + CurrentFrame()->OnUnknownPayload(data, len); +} + +void FramePartsCollectorListener::OnUnknownEnd() { + VLOG(1) << "OnUnknownEnd"; + EndFrame()->OnUnknownEnd(); +} + +void FramePartsCollectorListener::OnPaddingTooLong( + const Http2FrameHeader& header, + size_t missing_length) { + VLOG(1) << "OnPaddingTooLong: " << header + << " missing_length: " << missing_length; + EndFrame()->OnPaddingTooLong(header, missing_length); +} + +void FramePartsCollectorListener::OnFrameSizeError( + const Http2FrameHeader& header) { + VLOG(1) << "OnFrameSizeError: " << header; + FrameError(header)->OnFrameSizeError(header); +} + +} // namespace test +} // namespace net
diff --git a/net/http2/decoder/frame_parts_collector_listener.h b/net/http2/decoder/frame_parts_collector_listener.h new file mode 100644 index 0000000..89b7fed --- /dev/null +++ b/net/http2/decoder/frame_parts_collector_listener.h
@@ -0,0 +1,83 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_FRAME_PARTS_COLLECTOR_LISTENER_H_ +#define NET_HTTP2_DECODER_FRAME_PARTS_COLLECTOR_LISTENER_H_ + +// FramePartsCollectorListener extends FramePartsCollector with an +// implementation of every method of Http2FrameDecoderListener; it is +// essentially the union of all the Listener classes in the tests of the +// payload decoders (i.e. in ./payload_decoders/*_test.cc files), with the +// addition of the OnFrameHeader method. +// FramePartsCollectorListener supports tests of Http2FrameDecoder. + +#include <stddef.h> + +#include "net/http2/decoder/frame_parts_collector.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures.h" + +namespace net { +namespace test { + +class FramePartsCollectorListener : public FramePartsCollector { + public: + FramePartsCollectorListener() {} + ~FramePartsCollectorListener() override {} + + // TODO(jamessynge): Remove OnFrameHeader once done with supporting + // SpdyFramer's exact states. + bool OnFrameHeader(const Http2FrameHeader& header) override; + void OnDataStart(const Http2FrameHeader& header) override; + void OnDataPayload(const char* data, size_t len) override; + void OnDataEnd() override; + void OnHeadersStart(const Http2FrameHeader& header) override; + void OnHeadersPriority(const Http2PriorityFields& priority) override; + void OnHpackFragment(const char* data, size_t len) override; + void OnHeadersEnd() override; + void OnPriorityFrame(const Http2FrameHeader& header, + const Http2PriorityFields& priority_fields) override; + void OnContinuationStart(const Http2FrameHeader& header) override; + void OnContinuationEnd() override; + void OnPadLength(size_t pad_length) override; + void OnPadding(const char* padding, size_t skipped_length) override; + void OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode error_code) override; + void OnSettingsStart(const Http2FrameHeader& header) override; + void OnSetting(const Http2SettingFields& setting_fields) override; + void OnSettingsEnd() override; + void OnSettingsAck(const Http2FrameHeader& header) override; + void OnPushPromiseStart(const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) override; + void OnPushPromiseEnd() override; + void OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) override; + void OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) override; + void OnGoAwayStart(const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) override; + void OnGoAwayOpaqueData(const char* data, size_t len) override; + void OnGoAwayEnd() override; + void OnWindowUpdate(const Http2FrameHeader& header, + uint32_t window_size_increment) override; + void OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) override; + void OnAltSvcOriginData(const char* data, size_t len) override; + void OnAltSvcValueData(const char* data, size_t len) override; + void OnAltSvcEnd() override; + void OnUnknownStart(const Http2FrameHeader& header) override; + void OnUnknownPayload(const char* data, size_t len) override; + void OnUnknownEnd() override; + void OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) override; + void OnFrameSizeError(const Http2FrameHeader& header) override; +}; + +} // namespace test +} // namespace net + +#endif // NET_HTTP2_DECODER_FRAME_PARTS_COLLECTOR_LISTENER_H_
diff --git a/net/http2/decoder/http2_frame_decoder.cc b/net/http2/decoder/http2_frame_decoder.cc new file mode 100644 index 0000000..fe24f91 --- /dev/null +++ b/net/http2/decoder/http2_frame_decoder.cc
@@ -0,0 +1,426 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/http2_frame_decoder.h" + +#include "net/http2/http2_constants.h" + +namespace net { + +std::ostream& operator<<(std::ostream& out, Http2FrameDecoder::State v) { + switch (v) { + case Http2FrameDecoder::State::kStartDecodingHeader: + return out << "kStartDecodingHeader"; + case Http2FrameDecoder::State::kResumeDecodingHeader: + return out << "kResumeDecodingHeader"; + case Http2FrameDecoder::State::kResumeDecodingPayload: + return out << "kResumeDecodingPayload"; + case Http2FrameDecoder::State::kDiscardPayload: + return out << "kDiscardPayload"; + } + return out << static_cast<int>(v); +} + +Http2FrameDecoder::Http2FrameDecoder(Http2FrameDecoderListener* listener) + : state_(State::kStartDecodingHeader), + maximum_payload_size_(Http2SettingsInfo::DefaultMaxFrameSize()) { + set_listener(listener); +} + +void Http2FrameDecoder::set_listener(Http2FrameDecoderListener* listener) { + if (listener == nullptr) { + listener = &no_op_listener_; + } + frame_decoder_state_.set_listener(listener); +} + +Http2FrameDecoderListener* Http2FrameDecoder::listener() const { + return frame_decoder_state_.listener(); +} + +DecodeStatus Http2FrameDecoder::DecodeFrame(DecodeBuffer* db) { + DVLOG(2) << "Http2FrameDecoder::DecodeFrame state=" << state_; + switch (state_) { + case State::kStartDecodingHeader: + if (frame_decoder_state_.StartDecodingFrameHeader(db)) { + return StartDecodingPayload(db); + } + state_ = State::kResumeDecodingHeader; + return DecodeStatus::kDecodeInProgress; + + case State::kResumeDecodingHeader: + if (frame_decoder_state_.ResumeDecodingFrameHeader(db)) { + return StartDecodingPayload(db); + } + return DecodeStatus::kDecodeInProgress; + + case State::kResumeDecodingPayload: + return ResumeDecodingPayload(db); + + case State::kDiscardPayload: + return DiscardPayload(db); + } + + NOTREACHED(); + return DecodeStatus::kDecodeError; +} + +size_t Http2FrameDecoder::remaining_payload() const { + return frame_decoder_state_.remaining_payload(); +} + +uint32_t Http2FrameDecoder::remaining_padding() const { + return frame_decoder_state_.remaining_padding(); +} + +DecodeStatus Http2FrameDecoder::StartDecodingPayload(DecodeBuffer* db) { + const Http2FrameHeader& header = frame_header(); + + // TODO(jamessynge): Remove OnFrameHeader once done with supporting + // SpdyFramer's exact states. + if (!listener()->OnFrameHeader(header)) { + DVLOG(2) << "OnFrameHeader rejected the frame, will discard; header: " + << header; + state_ = State::kDiscardPayload; + frame_decoder_state_.InitializeRemainders(); + return DecodeStatus::kDecodeError; + } + + if (header.payload_length > maximum_payload_size_) { + DVLOG(2) << "Payload length is greater than allowed: " + << header.payload_length << " > " << maximum_payload_size_ + << "\n header: " << header; + state_ = State::kDiscardPayload; + frame_decoder_state_.InitializeRemainders(); + listener()->OnFrameSizeError(header); + return DecodeStatus::kDecodeError; + } + + // The decode buffer can extend across many frames. Make sure that the + // buffer we pass to the start method that is specific to the frame type + // does not exend beyond this frame. + DecodeBufferSubset subset(db, header.payload_length); + DecodeStatus status; + switch (header.type) { + case Http2FrameType::DATA: + status = StartDecodingDataPayload(&subset); + break; + + case Http2FrameType::HEADERS: + status = StartDecodingHeadersPayload(&subset); + break; + + case Http2FrameType::PRIORITY: + status = StartDecodingPriorityPayload(&subset); + break; + + case Http2FrameType::RST_STREAM: + status = StartDecodingRstStreamPayload(&subset); + break; + + case Http2FrameType::SETTINGS: + status = StartDecodingSettingsPayload(&subset); + break; + + case Http2FrameType::PUSH_PROMISE: + status = StartDecodingPushPromisePayload(&subset); + break; + + case Http2FrameType::PING: + status = StartDecodingPingPayload(&subset); + break; + + case Http2FrameType::GOAWAY: + status = StartDecodingGoAwayPayload(&subset); + break; + + case Http2FrameType::WINDOW_UPDATE: + status = StartDecodingWindowUpdatePayload(&subset); + break; + + case Http2FrameType::CONTINUATION: + status = StartDecodingContinuationPayload(&subset); + break; + + case Http2FrameType::ALTSVC: + status = StartDecodingAltSvcPayload(&subset); + break; + + default: + status = StartDecodingUnknownPayload(&subset); + break; + } + + if (status == DecodeStatus::kDecodeDone) { + state_ = State::kStartDecodingHeader; + return status; + } else if (status == DecodeStatus::kDecodeInProgress) { + state_ = State::kResumeDecodingPayload; + return status; + } else { + state_ = State::kDiscardPayload; + return status; + } +} + +DecodeStatus Http2FrameDecoder::ResumeDecodingPayload(DecodeBuffer* db) { + // The decode buffer can extend across many frames. Make sure that the + // buffer we pass to the start method that is specific to the frame type + // does not exend beyond this frame. + size_t remaining = frame_decoder_state_.remaining_total_payload(); + DCHECK_LE(remaining, frame_header().payload_length); + DecodeBufferSubset subset(db, remaining); + DecodeStatus status; + switch (frame_header().type) { + case Http2FrameType::DATA: + status = ResumeDecodingDataPayload(&subset); + break; + + case Http2FrameType::HEADERS: + status = ResumeDecodingHeadersPayload(&subset); + break; + + case Http2FrameType::PRIORITY: + status = ResumeDecodingPriorityPayload(&subset); + break; + + case Http2FrameType::RST_STREAM: + status = ResumeDecodingRstStreamPayload(&subset); + break; + + case Http2FrameType::SETTINGS: + status = ResumeDecodingSettingsPayload(&subset); + break; + + case Http2FrameType::PUSH_PROMISE: + status = ResumeDecodingPushPromisePayload(&subset); + break; + + case Http2FrameType::PING: + status = ResumeDecodingPingPayload(&subset); + break; + + case Http2FrameType::GOAWAY: + status = ResumeDecodingGoAwayPayload(&subset); + break; + + case Http2FrameType::WINDOW_UPDATE: + status = ResumeDecodingWindowUpdatePayload(&subset); + break; + + case Http2FrameType::CONTINUATION: + status = ResumeDecodingContinuationPayload(&subset); + break; + + case Http2FrameType::ALTSVC: + status = ResumeDecodingAltSvcPayload(&subset); + break; + + default: + status = ResumeDecodingUnknownPayload(&subset); + break; + } + + if (status == DecodeStatus::kDecodeDone) { + state_ = State::kStartDecodingHeader; + return status; + } else if (status == DecodeStatus::kDecodeInProgress) { + return status; + } else { + state_ = State::kDiscardPayload; + return status; + } +} + +// Clear any of the flags in the frame header that aren't set in valid_flags. +void Http2FrameDecoder::RetainFlags(uint8_t valid_flags) { + frame_decoder_state_.RetainFlags(valid_flags); +} + +// Clear all of the flags in the frame header; for use with frame types that +// don't define any flags, such as WINDOW_UPDATE. +void Http2FrameDecoder::ClearFlags() { + frame_decoder_state_.ClearFlags(); +} + +DecodeStatus Http2FrameDecoder::StartDecodingAltSvcPayload(DecodeBuffer* db) { + ClearFlags(); + return altsvc_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, + db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingAltSvcPayload(DecodeBuffer* db) { + // The frame is not paddable. + DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), + frame_decoder_state_.remaining_payload()); + return altsvc_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, + db); +} + +DecodeStatus Http2FrameDecoder::StartDecodingContinuationPayload( + DecodeBuffer* db) { + RetainFlags(Http2FrameFlag::FLAG_END_HEADERS); + return continuation_payload_decoder_.StartDecodingPayload( + &frame_decoder_state_, db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingContinuationPayload( + DecodeBuffer* db) { + // The frame is not paddable. + DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), + frame_decoder_state_.remaining_payload()); + return continuation_payload_decoder_.ResumeDecodingPayload( + &frame_decoder_state_, db); +} + +DecodeStatus Http2FrameDecoder::StartDecodingDataPayload(DecodeBuffer* db) { + RetainFlags(Http2FrameFlag::FLAG_END_STREAM | Http2FrameFlag::FLAG_PADDED); + return data_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingDataPayload(DecodeBuffer* db) { + return data_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, db); +} + +DecodeStatus Http2FrameDecoder::StartDecodingGoAwayPayload(DecodeBuffer* db) { + ClearFlags(); + return goaway_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, + db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingGoAwayPayload(DecodeBuffer* db) { + // The frame is not paddable. + DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), + frame_decoder_state_.remaining_payload()); + return goaway_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, + db); +} + +DecodeStatus Http2FrameDecoder::StartDecodingHeadersPayload(DecodeBuffer* db) { + RetainFlags(Http2FrameFlag::FLAG_END_STREAM | + Http2FrameFlag::FLAG_END_HEADERS | Http2FrameFlag::FLAG_PADDED | + Http2FrameFlag::FLAG_PRIORITY); + return headers_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, + db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingHeadersPayload(DecodeBuffer* db) { + DCHECK_LE(frame_decoder_state_.remaining_payload_and_padding(), + frame_header().payload_length); + return headers_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, + db); +} + +DecodeStatus Http2FrameDecoder::StartDecodingPingPayload(DecodeBuffer* db) { + RetainFlags(Http2FrameFlag::FLAG_ACK); + return ping_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingPingPayload(DecodeBuffer* db) { + // The frame is not paddable. + DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), + frame_decoder_state_.remaining_payload()); + return ping_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, db); +} + +DecodeStatus Http2FrameDecoder::StartDecodingPriorityPayload(DecodeBuffer* db) { + ClearFlags(); + return priority_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, + db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingPriorityPayload( + DecodeBuffer* db) { + // The frame is not paddable. + DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), + frame_decoder_state_.remaining_payload()); + return priority_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, + db); +} + +DecodeStatus Http2FrameDecoder::StartDecodingPushPromisePayload( + DecodeBuffer* db) { + RetainFlags(Http2FrameFlag::FLAG_END_HEADERS | Http2FrameFlag::FLAG_PADDED); + return push_promise_payload_decoder_.StartDecodingPayload( + &frame_decoder_state_, db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingPushPromisePayload( + DecodeBuffer* db) { + DCHECK_LE(frame_decoder_state_.remaining_payload_and_padding(), + frame_header().payload_length); + return push_promise_payload_decoder_.ResumeDecodingPayload( + &frame_decoder_state_, db); +} + +DecodeStatus Http2FrameDecoder::StartDecodingRstStreamPayload( + DecodeBuffer* db) { + ClearFlags(); + return rst_stream_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, + db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingRstStreamPayload( + DecodeBuffer* db) { + // The frame is not paddable. + DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), + frame_decoder_state_.remaining_payload()); + return rst_stream_payload_decoder_.ResumeDecodingPayload( + &frame_decoder_state_, db); +} + +DecodeStatus Http2FrameDecoder::StartDecodingSettingsPayload(DecodeBuffer* db) { + RetainFlags(Http2FrameFlag::FLAG_ACK); + return settings_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, + db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingSettingsPayload( + DecodeBuffer* db) { + // The frame is not paddable. + DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), + frame_decoder_state_.remaining_payload()); + return settings_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, + db); +} + +DecodeStatus Http2FrameDecoder::StartDecodingUnknownPayload(DecodeBuffer* db) { + // We don't known what type of frame this is, so we don't know which flags + // are valid, so we don't touch them. + return unknown_payload_decoder_.StartDecodingPayload(&frame_decoder_state_, + db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingUnknownPayload(DecodeBuffer* db) { + // We don't known what type of frame this is, so we treat it as not paddable. + DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), + frame_decoder_state_.remaining_payload()); + return unknown_payload_decoder_.ResumeDecodingPayload(&frame_decoder_state_, + db); +} + +DecodeStatus Http2FrameDecoder::StartDecodingWindowUpdatePayload( + DecodeBuffer* db) { + ClearFlags(); + return window_update_payload_decoder_.StartDecodingPayload( + &frame_decoder_state_, db); +} +DecodeStatus Http2FrameDecoder::ResumeDecodingWindowUpdatePayload( + DecodeBuffer* db) { + // The frame is not paddable. + DCHECK_EQ(frame_decoder_state_.remaining_total_payload(), + frame_decoder_state_.remaining_payload()); + return window_update_payload_decoder_.ResumeDecodingPayload( + &frame_decoder_state_, db); +} + +DecodeStatus Http2FrameDecoder::DiscardPayload(DecodeBuffer* db) { + DVLOG(2) << "remaining_payload=" << frame_decoder_state_.remaining_payload_ + << "; remaining_padding=" << frame_decoder_state_.remaining_padding_; + frame_decoder_state_.remaining_payload_ += + frame_decoder_state_.remaining_padding_; + frame_decoder_state_.remaining_padding_ = 0; + const size_t avail = frame_decoder_state_.AvailablePayload(db); + DVLOG(2) << "avail=" << avail; + if (avail > 0) { + frame_decoder_state_.ConsumePayload(avail); + db->AdvanceCursor(avail); + } + if (frame_decoder_state_.remaining_payload_ == 0) { + state_ = State::kStartDecodingHeader; + return DecodeStatus::kDecodeDone; + } + return DecodeStatus::kDecodeInProgress; +} + +} // namespace net
diff --git a/net/http2/decoder/http2_frame_decoder.h b/net/http2/decoder/http2_frame_decoder.h new file mode 100644 index 0000000..f440993 --- /dev/null +++ b/net/http2/decoder/http2_frame_decoder.h
@@ -0,0 +1,203 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_HTTP2_FRAME_DECODER_H_ +#define NET_HTTP2_DECODER_HTTP2_FRAME_DECODER_H_ + +// Http2FrameDecoder decodes the available input until it reaches the end of +// the input or it reaches the end of the first frame in the input. +// Note that Http2FrameDecoder does only minimal validation; for example, +// stream ids are not checked, nor is the sequence of frames such as +// CONTINUATION frame placement. +// +// Http2FrameDecoder enters state kError once it has called the listener's +// OnFrameSizeError or OnPaddingTooLong methods, and at this time has no +// provision for leaving that state. While the HTTP/2 spec (RFC7540) allows +// for some such errors to be considered as just stream errors in some cases, +// this implementation treats them all as connection errors. + +#include <stddef.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "net/base/net_export.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/decoder/frame_decoder_state.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/decoder/payload_decoders/altsvc_payload_decoder.h" +#include "net/http2/decoder/payload_decoders/continuation_payload_decoder.h" +#include "net/http2/decoder/payload_decoders/data_payload_decoder.h" +#include "net/http2/decoder/payload_decoders/goaway_payload_decoder.h" +#include "net/http2/decoder/payload_decoders/headers_payload_decoder.h" +#include "net/http2/decoder/payload_decoders/ping_payload_decoder.h" +#include "net/http2/decoder/payload_decoders/priority_payload_decoder.h" +#include "net/http2/decoder/payload_decoders/push_promise_payload_decoder.h" +#include "net/http2/decoder/payload_decoders/rst_stream_payload_decoder.h" +#include "net/http2/decoder/payload_decoders/settings_payload_decoder.h" +#include "net/http2/decoder/payload_decoders/unknown_payload_decoder.h" +#include "net/http2/decoder/payload_decoders/window_update_payload_decoder.h" +#include "net/http2/http2_structures.h" + +namespace net { +namespace test { +class Http2FrameDecoderPeer; +} // namespace test + +class NET_EXPORT_PRIVATE Http2FrameDecoder { + public: + explicit Http2FrameDecoder(Http2FrameDecoderListener* listener); + Http2FrameDecoder() : Http2FrameDecoder(nullptr) {} + + // The decoder will call the listener's methods as it decodes a frame. + void set_listener(Http2FrameDecoderListener* listener); + Http2FrameDecoderListener* listener() const; + + // The decoder will reject frame's whose payload + // length field exceeds the maximum payload size. + void set_maximum_payload_size(size_t v) { maximum_payload_size_ = v; } + size_t maximum_payload_size() const { return maximum_payload_size_; } + + // Decodes the input up to the next frame boundary (i.e. at most one frame). + // + // Returns kDecodeDone if it decodes the final byte of a frame, OR if there + // is no input and it is awaiting the start of a new frame (e.g. if this + // is the first call to DecodeFrame, or if the previous call returned + // kDecodeDone). + // + // Returns kDecodeInProgress if it decodes all of the decode buffer, but has + // not reached the end of the frame. + // + // Returns kDecodeError if the frame's padding or length wasn't valid (i.e. if + // the decoder called either the listener's OnPaddingTooLong or + // OnFrameSizeError method). + DecodeStatus DecodeFrame(DecodeBuffer* db); + + ////////////////////////////////////////////////////////////////////////////// + // Methods that support Http2FrameDecoderAdapter. + + // Is the remainder of the frame's payload being discarded? + bool IsDiscardingPayload() const { return state_ == State::kDiscardPayload; } + + // Returns the number of bytes of the frame's payload that remain to be + // decoded, excluding any trailing padding. This method must only be called + // after the frame header has been decoded AND DecodeFrame has returned + // kDecodeInProgress. + size_t remaining_payload() const; + + // Returns the number of bytes of trailing padding after the payload that + // remain to be decoded. This method must only be called if the frame type + // allows padding, and after the frame header has been decoded AND + // DecodeFrame has returned. Will return 0 if the Pad Length field has not + // yet been decoded. + uint32_t remaining_padding() const; + + private: + enum class State { + // Ready to start decoding a new frame's header. + kStartDecodingHeader, + // Was in state kStartDecodingHeader, but unable to read the entire frame + // header, so needs more input to complete decoding the header. + kResumeDecodingHeader, + + // Have decoded the frame header, and started decoding the available bytes + // of the frame's payload, but need more bytes to finish the job. + kResumeDecodingPayload, + + // Decoding of the most recently started frame resulted in an error: + // OnPaddingTooLong or OnFrameSizeError was called to indicate that the + // decoder detected a problem, or OnFrameHeader returned false, indicating + // that the listener detected a problem. Regardless of which, the decoder + // will stay in state kDiscardPayload until it has been passed the rest + // of the bytes of the frame's payload that it hasn't yet seen, after + // which it will be ready to decode another frame. + kDiscardPayload, + }; + + friend class test::Http2FrameDecoderPeer; + friend std::ostream& operator<<(std::ostream& out, State v); + + DecodeStatus StartDecodingPayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingPayload(DecodeBuffer* db); + DecodeStatus DiscardPayload(DecodeBuffer* db); + + const Http2FrameHeader& frame_header() const { + return frame_decoder_state_.frame_header(); + } + + // Clear any of the flags in the frame header that aren't set in valid_flags. + void RetainFlags(uint8_t valid_flags); + + // Clear all of the flags in the frame header; for use with frame types that + // don't define any flags, such as WINDOW_UPDATE. + void ClearFlags(); + + // These methods call the StartDecodingPayload() method of the frame type's + // payload decoder, after first clearing invalid flags in the header. The + // caller must ensure that the decode buffer does not extend beyond the + // end of the payload (handled by Http2FrameDecoder::StartDecodingPayload). + DecodeStatus StartDecodingAltSvcPayload(DecodeBuffer* db); + DecodeStatus StartDecodingContinuationPayload(DecodeBuffer* db); + DecodeStatus StartDecodingDataPayload(DecodeBuffer* db); + DecodeStatus StartDecodingGoAwayPayload(DecodeBuffer* db); + DecodeStatus StartDecodingHeadersPayload(DecodeBuffer* db); + DecodeStatus StartDecodingPingPayload(DecodeBuffer* db); + DecodeStatus StartDecodingPriorityPayload(DecodeBuffer* db); + DecodeStatus StartDecodingPushPromisePayload(DecodeBuffer* db); + DecodeStatus StartDecodingRstStreamPayload(DecodeBuffer* db); + DecodeStatus StartDecodingSettingsPayload(DecodeBuffer* db); + DecodeStatus StartDecodingUnknownPayload(DecodeBuffer* db); + DecodeStatus StartDecodingWindowUpdatePayload(DecodeBuffer* db); + + // These methods call the ResumeDecodingPayload() method of the frame type's + // payload decoder; they are called only if the preceding call to the + // corresponding Start method (above) returned kDecodeInProgress, as did any + // subsequent calls to the resume method. + // Unlike the Start methods, the decode buffer may extend beyond the + // end of the payload, so the method will create a DecodeBufferSubset + // before calling the ResumeDecodingPayload method of the frame type's + // payload decoder. + DecodeStatus ResumeDecodingAltSvcPayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingContinuationPayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingDataPayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingGoAwayPayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingHeadersPayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingPingPayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingPriorityPayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingPushPromisePayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingRstStreamPayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingSettingsPayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingUnknownPayload(DecodeBuffer* db); + DecodeStatus ResumeDecodingWindowUpdatePayload(DecodeBuffer* db); + + FrameDecoderState frame_decoder_state_; + + // We only need one payload decoder at a time, so they share the same storage. + union { + AltSvcPayloadDecoder altsvc_payload_decoder_; + ContinuationPayloadDecoder continuation_payload_decoder_; + DataPayloadDecoder data_payload_decoder_; + GoAwayPayloadDecoder goaway_payload_decoder_; + HeadersPayloadDecoder headers_payload_decoder_; + PingPayloadDecoder ping_payload_decoder_; + PriorityPayloadDecoder priority_payload_decoder_; + PushPromisePayloadDecoder push_promise_payload_decoder_; + RstStreamPayloadDecoder rst_stream_payload_decoder_; + SettingsPayloadDecoder settings_payload_decoder_; + UnknownPayloadDecoder unknown_payload_decoder_; + WindowUpdatePayloadDecoder window_update_payload_decoder_; + }; + + State state_; + size_t maximum_payload_size_; + + // Listener used whenever caller passes nullptr to set_listener. + Http2FrameDecoderNoOpListener no_op_listener_; + + DISALLOW_COPY_AND_ASSIGN(Http2FrameDecoder); +}; + +} // namespace net + +#endif // NET_HTTP2_DECODER_HTTP2_FRAME_DECODER_H_
diff --git a/net/http2/decoder/http2_frame_decoder_listener.cc b/net/http2/decoder/http2_frame_decoder_listener.cc new file mode 100644 index 0000000..bf62ae4f --- /dev/null +++ b/net/http2/decoder/http2_frame_decoder_listener.cc
@@ -0,0 +1,14 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/http2_frame_decoder_listener.h" + +namespace net { + +bool Http2FrameDecoderNoOpListener::OnFrameHeader( + const Http2FrameHeader& header) { + return true; +} + +} // namespace net
diff --git a/net/http2/decoder/http2_frame_decoder_listener.h b/net/http2/decoder/http2_frame_decoder_listener.h new file mode 100644 index 0000000..3345ab6f --- /dev/null +++ b/net/http2/decoder/http2_frame_decoder_listener.h
@@ -0,0 +1,356 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_HTTP2_FRAME_DECODER_LISTENER_H_ +#define NET_HTTP2_DECODER_HTTP2_FRAME_DECODER_LISTENER_H_ + +// Http2FrameDecoderListener is the interface which the HTTP/2 decoder uses +// to report the decoded frames to a listener. +// +// The general design is to assume that the listener will copy the data it needs +// (e.g. frame headers) and will keep track of the implicit state of the +// decoding process (i.e. the decoder maintains just the information it needs in +// order to perform the decoding). Therefore, the parameters are just those with +// (potentially) new data, not previously provided info about the current frame. +// +// The calls are described as if they are made in quick succession, i.e. one +// after another, but of course the decoder needs input to decode, and the +// decoder will only call the listener once the necessary input has been +// provided. For example: OnDataStart can only be called once the 9 bytes of +// of an HTTP/2 common frame header have been received. The decoder will call +// the listener methods as soon as possible to avoid almost all buffering. +// +// The listener interface is designed so that it is possible to exactly +// reconstruct the serialized frames, with the exception of reserved bits, +// including in the frame header's flags and stream_id fields, which will have +// been cleared before the methods below are called. + +#include <stddef.h> + +#include <type_traits> + +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures.h" + +namespace net { + +// TODO(jamessynge): Consider sorting the methods by frequency of call, if that +// helps at all. +class Http2FrameDecoderListener { + public: + Http2FrameDecoderListener() {} + virtual ~Http2FrameDecoderListener() {} + + // Called once the common frame header has been decoded for any frame, and + // before any of the methods below, which will also be called. This method is + // included in this interface only for the purpose of supporting SpdyFramer + // semantics via an adapter. This is the only method that has a non-void + // return type, and this is just so that Http2FrameDecoderAdapter (called + // from SpdyFramer) can more readily pass existing tests that expect decoding + // to stop if the headers alone indicate an error. Return false to stop + // decoding just after decoding the header, else return true to continue + // decoding. + // TODO(jamessynge): Remove OnFrameHeader once done with supporting + // SpdyFramer's exact states. + virtual bool OnFrameHeader(const Http2FrameHeader& header) = 0; + + ////////////////////////////////////////////////////////////////////////////// + + // Called once the common frame header has been decoded for a DATA frame, + // before examining the frame's payload, after which: + // OnPadLength will be called if header.IsPadded() is true, i.e. if the + // PADDED flag is set; + // OnDataPayload will be called as the non-padding portion of the payload + // is available until all of it has been provided; + // OnPadding will be called if the frame is padded AND the Pad Length field + // is greater than zero; + // OnDataEnd will be called last. If the frame is unpadded and has no + // payload, then this will be called immediately after OnDataStart. + virtual void OnDataStart(const Http2FrameHeader& header) = 0; + + // Called when the next non-padding portion of a DATA frame's payload is + // received. + // |data| The start of |len| bytes of data. + // |len| The length of the data buffer. Maybe zero in some cases, which does + // not mean anything special. + virtual void OnDataPayload(const char* data, size_t len) = 0; + + // Called after an entire DATA frame has been received. + // If header.IsEndStream() == true, this is the last data for the stream. + virtual void OnDataEnd() = 0; + + // Called once the common frame header has been decoded for a HEADERS frame, + // before examining the frame's payload, after which: + // OnPadLength will be called if header.IsPadded() is true, i.e. if the + // PADDED flag is set; + // OnHeadersPriority will be called if header.HasPriority() is true, i.e. if + // the frame has the PRIORITY flag; + // OnHpackFragment as the remainder of the non-padding payload is available + // until all if has been provided; + // OnPadding will be called if the frame is padded AND the Pad Length field + // is greater than zero; + // OnHeadersEnd will be called last; If the frame is unpadded and has no + // payload, then this will be called immediately after OnHeadersStart; + // OnHeadersEnd indicates the end of the HPACK block only if the frame + // header had the END_HEADERS flag set, else the END_HEADERS should be + // looked for on a subsequent CONTINUATION frame. + virtual void OnHeadersStart(const Http2FrameHeader& header) = 0; + + // Called when a HEADERS frame is received with the PRIORITY flag set and + // the priority fields have been decoded. + virtual void OnHeadersPriority( + const Http2PriorityFields& priority_fields) = 0; + + // Called when a fragment (i.e. some or all of an HPACK Block) is received; + // this may be part of a HEADERS, PUSH_PROMISE or CONTINUATION frame. + // |data| The start of |len| bytes of data. + // |len| The length of the data buffer. Maybe zero in some cases, which does + // not mean anything special, except that it simplified the decoder. + virtual void OnHpackFragment(const char* data, size_t len) = 0; + + // Called after an entire HEADERS frame has been received. The frame is the + // end of the HEADERS if the END_HEADERS flag is set; else there should be + // CONTINUATION frames after this frame. + virtual void OnHeadersEnd() = 0; + + // Called when an entire PRIORITY frame has been decoded. + virtual void OnPriorityFrame(const Http2FrameHeader& header, + const Http2PriorityFields& priority_fields) = 0; + + // Called once the common frame header has been decoded for a CONTINUATION + // frame, before examining the frame's payload, after which: + // OnHpackFragment as the frame's payload is available until all of it + // has been provided; + // OnContinuationEnd will be called last; If the frame has no payload, + // then this will be called immediately after OnContinuationStart; + // the HPACK block is at an end if and only if the frame header passed + // to OnContinuationStart had the END_HEADERS flag set. + virtual void OnContinuationStart(const Http2FrameHeader& header) = 0; + + // Called after an entire CONTINUATION frame has been received. The frame is + // the end of the HEADERS if the END_HEADERS flag is set. + virtual void OnContinuationEnd() = 0; + + // Called when Pad Length field has been read. Applies to DATA and HEADERS + // frames. For PUSH_PROMISE frames, the Pad Length + 1 is provided in the + // OnPushPromiseStart call as total_padding_length. + virtual void OnPadLength(size_t pad_length) = 0; + + // Called when padding is skipped over. + virtual void OnPadding(const char* padding, size_t skipped_length) = 0; + + // Called when an entire RST_STREAM frame has been decoded. + // This is the only callback for RST_STREAM frames. + virtual void OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode error_code) = 0; + + // Called once the common frame header has been decoded for a SETTINGS frame + // without the ACK flag, before examining the frame's payload, after which: + // OnSetting will be called in turn for each pair of settings parameter and + // value found in the payload; + // OnSettingsEnd will be called last; If the frame has no payload, + // then this will be called immediately after OnSettingsStart. + // The frame header is passed so that the caller can check the stream_id, + // which should be zero, but that hasn't been checked by the decoder. + virtual void OnSettingsStart(const Http2FrameHeader& header) = 0; + + // Called for each setting parameter and value within a SETTINGS frame. + virtual void OnSetting(const Http2SettingFields& setting_fields) = 0; + + // Called after parsing the complete payload of SETTINGS frame (non-ACK). + virtual void OnSettingsEnd() = 0; + + // Called when an entire SETTINGS frame, with the ACK flag, has been decoded. + virtual void OnSettingsAck(const Http2FrameHeader& header) = 0; + + // Called just before starting to process the HPACK block of a PUSH_PROMISE + // frame. The Pad Length field has already been decoded at this point, so + // OnPadLength will not be called; note that total_padding_length is Pad + // Length + 1. After OnPushPromiseStart: + // OnHpackFragment as the remainder of the non-padding payload is available + // until all if has been provided; + // OnPadding will be called if the frame is padded AND the Pad Length field + // is greater than zero (i.e. total_padding_length > 1); + // OnPushPromiseEnd will be called last; If the frame is unpadded and has no + // payload, then this will be called immediately after OnPushPromiseStart. + virtual void OnPushPromiseStart(const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) = 0; + + // Called after all of the HPACK block fragment and padding of a PUSH_PROMISE + // has been decoded and delivered to the listener. This call indicates the end + // of the HPACK block if and only if the frame header had the END_HEADERS flag + // set (i.e. header.IsEndHeaders() is true); otherwise the next block must be + // a CONTINUATION frame with the same stream id (not the same promised stream + // id). + virtual void OnPushPromiseEnd() = 0; + + // Called when an entire PING frame, without the ACK flag, has been decoded. + virtual void OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) = 0; + + // Called when an entire PING frame, with the ACK flag, has been decoded. + virtual void OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) = 0; + + // Called after parsing a GOAWAY frame's header and fixed size fields, after + // which: + // OnGoAwayOpaqueData will be called as opaque data of the payload becomes + // available to the decoder, until all of it has been provided to the + // listener; + // OnGoAwayEnd will be called last, after all the opaque data has been + // provided to the listener; if there is no opaque data, then OnGoAwayEnd + // will be called immediately after OnGoAwayStart. + virtual void OnGoAwayStart(const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) = 0; + + // Called when the next portion of a GOAWAY frame's payload is received. + // |data| The start of |len| bytes of opaque data. + // |len| The length of the opaque data buffer. Maybe zero in some cases, + // which does not mean anything special. + virtual void OnGoAwayOpaqueData(const char* data, size_t len) = 0; + + // Called after finishing decoding all of a GOAWAY frame. + virtual void OnGoAwayEnd() = 0; + + // Called when an entire WINDOW_UPDATE frame has been decoded. The + // window_size_increment is required to be non-zero, but that has not been + // checked. If header.stream_id==0, the connection's flow control window is + // being increased, else the specified stream's flow control is being + // increased. + virtual void OnWindowUpdate(const Http2FrameHeader& header, + uint32_t window_size_increment) = 0; + + // Called when an ALTSVC frame header and origin length have been parsed. + // Either or both lengths may be zero. After OnAltSvcStart: + // OnAltSvcOriginData will be called until all of the (optional) Origin + // has been provided; + // OnAltSvcValueData will be called until all of the Alt-Svc-Field-Value + // has been provided; + // OnAltSvcEnd will called last, after all of the origin and + // Alt-Svc-Field-Value have been delivered to the listener. + virtual void OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) = 0; + + // Called when decoding the (optional) origin of an ALTSVC; + // the field is uninterpreted. + virtual void OnAltSvcOriginData(const char* data, size_t len) = 0; + + // Called when decoding the Alt-Svc-Field-Value of an ALTSVC; + // the field is uninterpreted. + virtual void OnAltSvcValueData(const char* data, size_t len) = 0; + + // Called after decoding all of a ALTSVC frame and providing to the listener + // via the above methods. + virtual void OnAltSvcEnd() = 0; + + // Called when the common frame header has been decoded, but the frame type + // is unknown, after which: + // OnUnknownPayload is called as the payload of the frame is provided to the + // decoder, until all of the payload has been decoded; + // OnUnknownEnd will called last, after the entire frame of the unknown type + // has been decoded and provided to the listener. + virtual void OnUnknownStart(const Http2FrameHeader& header) = 0; + + // Called when the payload of an unknown frame type is received. + // |data| A buffer containing the data received. + // |len| The length of the data buffer. + virtual void OnUnknownPayload(const char* data, size_t len) = 0; + + // Called after decoding all of the payload of an unknown frame type. + virtual void OnUnknownEnd() = 0; + + ////////////////////////////////////////////////////////////////////////////// + // Below here are events indicating a problem has been detected during + // decoding (i.e. the received frames are malformed in some way). + + // Padding field (uint8) has a value that is too large (i.e. the amount of + // padding is greater than the remainder of the payload that isn't required). + // From RFC Section 6.1, DATA: + // If the length of the padding is the length of the frame payload or + // greater, the recipient MUST treat this as a connection error + // (Section 5.4.1) of type PROTOCOL_ERROR. + // The same is true for HEADERS and PUSH_PROMISE. + virtual void OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) = 0; + + // Frame size error. Depending upon the effected frame, this may or may not + // require terminating the connection, though that is probably the best thing + // to do. + // From RFC Section 4.2, Frame Size: + // An endpoint MUST send an error code of FRAME_SIZE_ERROR if a frame + // exceeds the size defined in SETTINGS_MAX_FRAME_SIZE, exceeds any limit + // defined for the frame type, or is too small to contain mandatory frame + // data. A frame size error in a frame that could alter the state of the + // the entire connection MUST be treated as a connection error + // (Section 5.4.1); this includes any frame carrying a header block + // (Section 4.3) (that is, HEADERS, PUSH_PROMISE, and CONTINUATION), + // SETTINGS, and any frame with a stream identifier of 0. + virtual void OnFrameSizeError(const Http2FrameHeader& header) = 0; +}; + +// Do nothing for each call. Useful for ignoring a frame that is invalid. +class Http2FrameDecoderNoOpListener : public Http2FrameDecoderListener { + public: + Http2FrameDecoderNoOpListener() {} + ~Http2FrameDecoderNoOpListener() override {} + + // TODO(jamessynge): Remove OnFrameHeader once done with supporting + // SpdyFramer's exact states. + bool OnFrameHeader(const Http2FrameHeader& header) override; + + void OnDataStart(const Http2FrameHeader& header) override {} + void OnDataPayload(const char* data, size_t len) override {} + void OnDataEnd() override {} + void OnHeadersStart(const Http2FrameHeader& header) override {} + void OnHeadersPriority(const Http2PriorityFields& priority) override {} + void OnHpackFragment(const char* data, size_t len) override {} + void OnHeadersEnd() override {} + void OnPriorityFrame(const Http2FrameHeader& header, + const Http2PriorityFields& priority) override {} + void OnContinuationStart(const Http2FrameHeader& header) override {} + void OnContinuationEnd() override {} + void OnPadLength(size_t trailing_length) override {} + void OnPadding(const char* padding, size_t skipped_length) override {} + void OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode error_code) override {} + void OnSettingsStart(const Http2FrameHeader& header) override {} + void OnSetting(const Http2SettingFields& setting_fields) override {} + void OnSettingsEnd() override {} + void OnSettingsAck(const Http2FrameHeader& header) override {} + void OnPushPromiseStart(const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) override {} + void OnPushPromiseEnd() override {} + void OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) override {} + void OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) override {} + void OnGoAwayStart(const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) override {} + void OnGoAwayOpaqueData(const char* data, size_t len) override {} + void OnGoAwayEnd() override {} + void OnWindowUpdate(const Http2FrameHeader& header, + uint32_t increment) override {} + void OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) override {} + void OnAltSvcOriginData(const char* data, size_t len) override {} + void OnAltSvcValueData(const char* data, size_t len) override {} + void OnAltSvcEnd() override {} + void OnUnknownStart(const Http2FrameHeader& header) override {} + void OnUnknownPayload(const char* data, size_t len) override {} + void OnUnknownEnd() override {} + void OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) override {} + void OnFrameSizeError(const Http2FrameHeader& header) override {} +}; + +static_assert(!std::is_abstract<Http2FrameDecoderNoOpListener>(), + "Http2FrameDecoderNoOpListener ought to be concrete."); + +} // namespace net + +#endif // NET_HTTP2_DECODER_HTTP2_FRAME_DECODER_LISTENER_H_
diff --git a/net/http2/decoder/http2_frame_decoder_listener_test_util.cc b/net/http2/decoder/http2_frame_decoder_listener_test_util.cc new file mode 100644 index 0000000..9159b6cd --- /dev/null +++ b/net/http2/decoder/http2_frame_decoder_listener_test_util.cc
@@ -0,0 +1,485 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/http2_frame_decoder_listener_test_util.h" + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +FailingHttp2FrameDecoderListener::FailingHttp2FrameDecoderListener() {} +FailingHttp2FrameDecoderListener::~FailingHttp2FrameDecoderListener() {} + +bool FailingHttp2FrameDecoderListener::OnFrameHeader( + const Http2FrameHeader& header) { + ADD_FAILURE() << "OnFrameHeader: " << header; + return false; +} + +void FailingHttp2FrameDecoderListener::OnDataStart( + const Http2FrameHeader& header) { + FAIL() << "OnDataStart: " << header; +} + +void FailingHttp2FrameDecoderListener::OnDataPayload(const char* data, + size_t len) { + FAIL() << "OnDataPayload: len=" << len; +} + +void FailingHttp2FrameDecoderListener::OnDataEnd() { + FAIL() << "OnDataEnd"; +} + +void FailingHttp2FrameDecoderListener::OnHeadersStart( + const Http2FrameHeader& header) { + FAIL() << "OnHeadersStart: " << header; +} + +void FailingHttp2FrameDecoderListener::OnHeadersPriority( + const Http2PriorityFields& priority) { + FAIL() << "OnHeadersPriority: " << priority; +} + +void FailingHttp2FrameDecoderListener::OnHpackFragment(const char* data, + size_t len) { + FAIL() << "OnHpackFragment: len=" << len; +} + +void FailingHttp2FrameDecoderListener::OnHeadersEnd() { + FAIL() << "OnHeadersEnd"; +} + +void FailingHttp2FrameDecoderListener::OnPriorityFrame( + const Http2FrameHeader& header, + const Http2PriorityFields& priority) { + FAIL() << "OnPriorityFrame: " << header << "; priority: " << priority; +} + +void FailingHttp2FrameDecoderListener::OnContinuationStart( + const Http2FrameHeader& header) { + FAIL() << "OnContinuationStart: " << header; +} + +void FailingHttp2FrameDecoderListener::OnContinuationEnd() { + FAIL() << "OnContinuationEnd"; +} + +void FailingHttp2FrameDecoderListener::OnPadLength(size_t trailing_length) { + FAIL() << "OnPadLength: trailing_length=" << trailing_length; +} + +void FailingHttp2FrameDecoderListener::OnPadding(const char* padding, + size_t skipped_length) { + FAIL() << "OnPadding: skipped_length=" << skipped_length; +} + +void FailingHttp2FrameDecoderListener::OnRstStream( + const Http2FrameHeader& header, + Http2ErrorCode error_code) { + FAIL() << "OnRstStream: " << header << "; code=" << error_code; +} + +void FailingHttp2FrameDecoderListener::OnSettingsStart( + const Http2FrameHeader& header) { + FAIL() << "OnSettingsStart: " << header; +} + +void FailingHttp2FrameDecoderListener::OnSetting( + const Http2SettingFields& setting_fields) { + FAIL() << "OnSetting: " << setting_fields; +} + +void FailingHttp2FrameDecoderListener::OnSettingsEnd() { + FAIL() << "OnSettingsEnd"; +} + +void FailingHttp2FrameDecoderListener::OnSettingsAck( + const Http2FrameHeader& header) { + FAIL() << "OnSettingsAck: " << header; +} + +void FailingHttp2FrameDecoderListener::OnPushPromiseStart( + const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) { + FAIL() << "OnPushPromiseStart: " << header << "; promise: " << promise + << "; total_padding_length: " << total_padding_length; +} + +void FailingHttp2FrameDecoderListener::OnPushPromiseEnd() { + FAIL() << "OnPushPromiseEnd"; +} + +void FailingHttp2FrameDecoderListener::OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) { + FAIL() << "OnPing: " << header << "; ping: " << ping; +} + +void FailingHttp2FrameDecoderListener::OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) { + FAIL() << "OnPingAck: " << header << "; ping: " << ping; +} + +void FailingHttp2FrameDecoderListener::OnGoAwayStart( + const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) { + FAIL() << "OnGoAwayStart: " << header << "; goaway: " << goaway; +} + +void FailingHttp2FrameDecoderListener::OnGoAwayOpaqueData(const char* data, + size_t len) { + FAIL() << "OnGoAwayOpaqueData: len=" << len; +} + +void FailingHttp2FrameDecoderListener::OnGoAwayEnd() { + FAIL() << "OnGoAwayEnd"; +} + +void FailingHttp2FrameDecoderListener::OnWindowUpdate( + const Http2FrameHeader& header, + uint32_t increment) { + FAIL() << "OnWindowUpdate: " << header << "; increment=" << increment; +} + +void FailingHttp2FrameDecoderListener::OnAltSvcStart( + const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) { + FAIL() << "OnAltSvcStart: " << header << "; origin_length: " << origin_length + << "; value_length: " << value_length; +} + +void FailingHttp2FrameDecoderListener::OnAltSvcOriginData(const char* data, + size_t len) { + FAIL() << "OnAltSvcOriginData: len=" << len; +} + +void FailingHttp2FrameDecoderListener::OnAltSvcValueData(const char* data, + size_t len) { + FAIL() << "OnAltSvcValueData: len=" << len; +} + +void FailingHttp2FrameDecoderListener::OnAltSvcEnd() { + FAIL() << "OnAltSvcEnd"; +} + +void FailingHttp2FrameDecoderListener::OnUnknownStart( + const Http2FrameHeader& header) { + FAIL() << "OnUnknownStart: " << header; +} + +void FailingHttp2FrameDecoderListener::OnUnknownPayload(const char* data, + size_t len) { + FAIL() << "OnUnknownPayload: len=" << len; +} + +void FailingHttp2FrameDecoderListener::OnUnknownEnd() { + FAIL() << "OnUnknownEnd"; +} + +void FailingHttp2FrameDecoderListener::OnPaddingTooLong( + const Http2FrameHeader& header, + size_t missing_length) { + FAIL() << "OnPaddingTooLong: " << header + << "; missing_length: " << missing_length; +} + +void FailingHttp2FrameDecoderListener::OnFrameSizeError( + const Http2FrameHeader& header) { + FAIL() << "OnFrameSizeError: " << header; +} + +LoggingHttp2FrameDecoderListener::LoggingHttp2FrameDecoderListener() + : wrapped_(nullptr) {} +LoggingHttp2FrameDecoderListener::LoggingHttp2FrameDecoderListener( + Http2FrameDecoderListener* wrapped) + : wrapped_(wrapped) {} +LoggingHttp2FrameDecoderListener::~LoggingHttp2FrameDecoderListener() {} + +bool LoggingHttp2FrameDecoderListener::OnFrameHeader( + const Http2FrameHeader& header) { + VLOG(1) << "OnFrameHeader: " << header; + if (wrapped_ != nullptr) { + return wrapped_->OnFrameHeader(header); + } + return true; +} + +void LoggingHttp2FrameDecoderListener::OnDataStart( + const Http2FrameHeader& header) { + VLOG(1) << "OnDataStart: " << header; + if (wrapped_ != nullptr) { + wrapped_->OnDataStart(header); + } +} + +void LoggingHttp2FrameDecoderListener::OnDataPayload(const char* data, + size_t len) { + VLOG(1) << "OnDataPayload: len=" << len; + if (wrapped_ != nullptr) { + wrapped_->OnDataPayload(data, len); + } +} + +void LoggingHttp2FrameDecoderListener::OnDataEnd() { + VLOG(1) << "OnDataEnd"; + if (wrapped_ != nullptr) { + wrapped_->OnDataEnd(); + } +} + +void LoggingHttp2FrameDecoderListener::OnHeadersStart( + const Http2FrameHeader& header) { + VLOG(1) << "OnHeadersStart: " << header; + if (wrapped_ != nullptr) { + wrapped_->OnHeadersStart(header); + } +} + +void LoggingHttp2FrameDecoderListener::OnHeadersPriority( + const Http2PriorityFields& priority) { + VLOG(1) << "OnHeadersPriority: " << priority; + if (wrapped_ != nullptr) { + wrapped_->OnHeadersPriority(priority); + } +} + +void LoggingHttp2FrameDecoderListener::OnHpackFragment(const char* data, + size_t len) { + VLOG(1) << "OnHpackFragment: len=" << len; + if (wrapped_ != nullptr) { + wrapped_->OnHpackFragment(data, len); + } +} + +void LoggingHttp2FrameDecoderListener::OnHeadersEnd() { + VLOG(1) << "OnHeadersEnd"; + if (wrapped_ != nullptr) { + wrapped_->OnHeadersEnd(); + } +} + +void LoggingHttp2FrameDecoderListener::OnPriorityFrame( + const Http2FrameHeader& header, + const Http2PriorityFields& priority) { + VLOG(1) << "OnPriorityFrame: " << header << "; priority: " << priority; + if (wrapped_ != nullptr) { + wrapped_->OnPriorityFrame(header, priority); + } +} + +void LoggingHttp2FrameDecoderListener::OnContinuationStart( + const Http2FrameHeader& header) { + VLOG(1) << "OnContinuationStart: " << header; + if (wrapped_ != nullptr) { + wrapped_->OnContinuationStart(header); + } +} + +void LoggingHttp2FrameDecoderListener::OnContinuationEnd() { + VLOG(1) << "OnContinuationEnd"; + if (wrapped_ != nullptr) { + wrapped_->OnContinuationEnd(); + } +} + +void LoggingHttp2FrameDecoderListener::OnPadLength(size_t trailing_length) { + VLOG(1) << "OnPadLength: trailing_length=" << trailing_length; + if (wrapped_ != nullptr) { + wrapped_->OnPadLength(trailing_length); + } +} + +void LoggingHttp2FrameDecoderListener::OnPadding(const char* padding, + size_t skipped_length) { + VLOG(1) << "OnPadding: skipped_length=" << skipped_length; + if (wrapped_ != nullptr) { + wrapped_->OnPadding(padding, skipped_length); + } +} + +void LoggingHttp2FrameDecoderListener::OnRstStream( + const Http2FrameHeader& header, + Http2ErrorCode error_code) { + VLOG(1) << "OnRstStream: " << header << "; code=" << error_code; + if (wrapped_ != nullptr) { + wrapped_->OnRstStream(header, error_code); + } +} + +void LoggingHttp2FrameDecoderListener::OnSettingsStart( + const Http2FrameHeader& header) { + VLOG(1) << "OnSettingsStart: " << header; + if (wrapped_ != nullptr) { + wrapped_->OnSettingsStart(header); + } +} + +void LoggingHttp2FrameDecoderListener::OnSetting( + const Http2SettingFields& setting_fields) { + VLOG(1) << "OnSetting: " << setting_fields; + if (wrapped_ != nullptr) { + wrapped_->OnSetting(setting_fields); + } +} + +void LoggingHttp2FrameDecoderListener::OnSettingsEnd() { + VLOG(1) << "OnSettingsEnd"; + if (wrapped_ != nullptr) { + wrapped_->OnSettingsEnd(); + } +} + +void LoggingHttp2FrameDecoderListener::OnSettingsAck( + const Http2FrameHeader& header) { + VLOG(1) << "OnSettingsAck: " << header; + if (wrapped_ != nullptr) { + wrapped_->OnSettingsAck(header); + } +} + +void LoggingHttp2FrameDecoderListener::OnPushPromiseStart( + const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) { + VLOG(1) << "OnPushPromiseStart: " << header << "; promise: " << promise + << "; total_padding_length: " << total_padding_length; + if (wrapped_ != nullptr) { + wrapped_->OnPushPromiseStart(header, promise, total_padding_length); + } +} + +void LoggingHttp2FrameDecoderListener::OnPushPromiseEnd() { + VLOG(1) << "OnPushPromiseEnd"; + if (wrapped_ != nullptr) { + wrapped_->OnPushPromiseEnd(); + } +} + +void LoggingHttp2FrameDecoderListener::OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) { + VLOG(1) << "OnPing: " << header << "; ping: " << ping; + if (wrapped_ != nullptr) { + wrapped_->OnPing(header, ping); + } +} + +void LoggingHttp2FrameDecoderListener::OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) { + VLOG(1) << "OnPingAck: " << header << "; ping: " << ping; + if (wrapped_ != nullptr) { + wrapped_->OnPingAck(header, ping); + } +} + +void LoggingHttp2FrameDecoderListener::OnGoAwayStart( + const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) { + VLOG(1) << "OnGoAwayStart: " << header << "; goaway: " << goaway; + if (wrapped_ != nullptr) { + wrapped_->OnGoAwayStart(header, goaway); + } +} + +void LoggingHttp2FrameDecoderListener::OnGoAwayOpaqueData(const char* data, + size_t len) { + VLOG(1) << "OnGoAwayOpaqueData: len=" << len; + if (wrapped_ != nullptr) { + wrapped_->OnGoAwayOpaqueData(data, len); + } +} + +void LoggingHttp2FrameDecoderListener::OnGoAwayEnd() { + VLOG(1) << "OnGoAwayEnd"; + if (wrapped_ != nullptr) { + wrapped_->OnGoAwayEnd(); + } +} + +void LoggingHttp2FrameDecoderListener::OnWindowUpdate( + const Http2FrameHeader& header, + uint32_t increment) { + VLOG(1) << "OnWindowUpdate: " << header << "; increment=" << increment; + if (wrapped_ != nullptr) { + wrapped_->OnWindowUpdate(header, increment); + } +} + +void LoggingHttp2FrameDecoderListener::OnAltSvcStart( + const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) { + VLOG(1) << "OnAltSvcStart: " << header << "; origin_length: " << origin_length + << "; value_length: " << value_length; + if (wrapped_ != nullptr) { + wrapped_->OnAltSvcStart(header, origin_length, value_length); + } +} + +void LoggingHttp2FrameDecoderListener::OnAltSvcOriginData(const char* data, + size_t len) { + VLOG(1) << "OnAltSvcOriginData: len=" << len; + if (wrapped_ != nullptr) { + wrapped_->OnAltSvcOriginData(data, len); + } +} + +void LoggingHttp2FrameDecoderListener::OnAltSvcValueData(const char* data, + size_t len) { + VLOG(1) << "OnAltSvcValueData: len=" << len; + if (wrapped_ != nullptr) { + wrapped_->OnAltSvcValueData(data, len); + } +} + +void LoggingHttp2FrameDecoderListener::OnAltSvcEnd() { + VLOG(1) << "OnAltSvcEnd"; + if (wrapped_ != nullptr) { + wrapped_->OnAltSvcEnd(); + } +} + +void LoggingHttp2FrameDecoderListener::OnUnknownStart( + const Http2FrameHeader& header) { + VLOG(1) << "OnUnknownStart: " << header; + if (wrapped_ != nullptr) { + wrapped_->OnUnknownStart(header); + } +} + +void LoggingHttp2FrameDecoderListener::OnUnknownPayload(const char* data, + size_t len) { + VLOG(1) << "OnUnknownPayload: len=" << len; + if (wrapped_ != nullptr) { + wrapped_->OnUnknownPayload(data, len); + } +} + +void LoggingHttp2FrameDecoderListener::OnUnknownEnd() { + VLOG(1) << "OnUnknownEnd"; + if (wrapped_ != nullptr) { + wrapped_->OnUnknownEnd(); + } +} + +void LoggingHttp2FrameDecoderListener::OnPaddingTooLong( + const Http2FrameHeader& header, + size_t missing_length) { + VLOG(1) << "OnPaddingTooLong: " << header + << "; missing_length: " << missing_length; + if (wrapped_ != nullptr) { + wrapped_->OnPaddingTooLong(header, missing_length); + } +} + +void LoggingHttp2FrameDecoderListener::OnFrameSizeError( + const Http2FrameHeader& header) { + VLOG(1) << "OnFrameSizeError: " << header; + if (wrapped_ != nullptr) { + wrapped_->OnFrameSizeError(header); + } +} + +} // namespace net
diff --git a/net/http2/decoder/http2_frame_decoder_listener_test_util.h b/net/http2/decoder/http2_frame_decoder_listener_test_util.h new file mode 100644 index 0000000..ead1b93a --- /dev/null +++ b/net/http2/decoder/http2_frame_decoder_listener_test_util.h
@@ -0,0 +1,141 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_HTTP2_FRAME_DECODER_LISTENER_TEST_UTIL_H_ +#define NET_HTTP2_DECODER_HTTP2_FRAME_DECODER_LISTENER_TEST_UTIL_H_ + +#include <stddef.h> + +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures.h" + +namespace net { + +// Fail if any of the methods are called. Allows a test to override only the +// expected calls. +class FailingHttp2FrameDecoderListener : public Http2FrameDecoderListener { + public: + FailingHttp2FrameDecoderListener(); + ~FailingHttp2FrameDecoderListener() override; + + // TODO(jamessynge): Remove OnFrameHeader once done with supporting + // SpdyFramer's exact states. + bool OnFrameHeader(const Http2FrameHeader& header) override; + void OnDataStart(const Http2FrameHeader& header) override; + void OnDataPayload(const char* data, size_t len) override; + void OnDataEnd() override; + void OnHeadersStart(const Http2FrameHeader& header) override; + void OnHeadersPriority(const Http2PriorityFields& priority) override; + void OnHpackFragment(const char* data, size_t len) override; + void OnHeadersEnd() override; + void OnPriorityFrame(const Http2FrameHeader& header, + const Http2PriorityFields& priority) override; + void OnContinuationStart(const Http2FrameHeader& header) override; + void OnContinuationEnd() override; + void OnPadLength(size_t trailing_length) override; + void OnPadding(const char* padding, size_t skipped_length) override; + void OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode error_code) override; + void OnSettingsStart(const Http2FrameHeader& header) override; + void OnSetting(const Http2SettingFields& setting_fields) override; + void OnSettingsEnd() override; + void OnSettingsAck(const Http2FrameHeader& header) override; + void OnPushPromiseStart(const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) override; + void OnPushPromiseEnd() override; + void OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) override; + void OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) override; + void OnGoAwayStart(const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) override; + void OnGoAwayOpaqueData(const char* data, size_t len) override; + void OnGoAwayEnd() override; + void OnWindowUpdate(const Http2FrameHeader& header, + uint32_t increment) override; + void OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) override; + void OnAltSvcOriginData(const char* data, size_t len) override; + void OnAltSvcValueData(const char* data, size_t len) override; + void OnAltSvcEnd() override; + void OnUnknownStart(const Http2FrameHeader& header) override; + void OnUnknownPayload(const char* data, size_t len) override; + void OnUnknownEnd() override; + void OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) override; + void OnFrameSizeError(const Http2FrameHeader& header) override; + + private: + void EnsureNotAbstract() { FailingHttp2FrameDecoderListener instance; } +}; + +// VLOG's all the calls it receives, and forwards those calls to an optional +// listener. +class LoggingHttp2FrameDecoderListener : public Http2FrameDecoderListener { + public: + LoggingHttp2FrameDecoderListener(); + explicit LoggingHttp2FrameDecoderListener(Http2FrameDecoderListener* wrapped); + ~LoggingHttp2FrameDecoderListener() override; + + // TODO(jamessynge): Remove OnFrameHeader once done with supporting + // SpdyFramer's exact states. + bool OnFrameHeader(const Http2FrameHeader& header) override; + void OnDataStart(const Http2FrameHeader& header) override; + void OnDataPayload(const char* data, size_t len) override; + void OnDataEnd() override; + void OnHeadersStart(const Http2FrameHeader& header) override; + void OnHeadersPriority(const Http2PriorityFields& priority) override; + void OnHpackFragment(const char* data, size_t len) override; + void OnHeadersEnd() override; + void OnPriorityFrame(const Http2FrameHeader& header, + const Http2PriorityFields& priority) override; + void OnContinuationStart(const Http2FrameHeader& header) override; + void OnContinuationEnd() override; + void OnPadLength(size_t trailing_length) override; + void OnPadding(const char* padding, size_t skipped_length) override; + void OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode error_code) override; + void OnSettingsStart(const Http2FrameHeader& header) override; + void OnSetting(const Http2SettingFields& setting_fields) override; + void OnSettingsEnd() override; + void OnSettingsAck(const Http2FrameHeader& header) override; + void OnPushPromiseStart(const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) override; + void OnPushPromiseEnd() override; + void OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) override; + void OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) override; + void OnGoAwayStart(const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) override; + void OnGoAwayOpaqueData(const char* data, size_t len) override; + void OnGoAwayEnd() override; + void OnWindowUpdate(const Http2FrameHeader& header, + uint32_t increment) override; + void OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) override; + void OnAltSvcOriginData(const char* data, size_t len) override; + void OnAltSvcValueData(const char* data, size_t len) override; + void OnAltSvcEnd() override; + void OnUnknownStart(const Http2FrameHeader& header) override; + void OnUnknownPayload(const char* data, size_t len) override; + void OnUnknownEnd() override; + void OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) override; + void OnFrameSizeError(const Http2FrameHeader& header) override; + + private: + void EnsureNotAbstract() { LoggingHttp2FrameDecoderListener instance; } + + Http2FrameDecoderListener* wrapped_; +}; + +} // namespace net + +#endif // NET_HTTP2_DECODER_HTTP2_FRAME_DECODER_LISTENER_TEST_UTIL_H_
diff --git a/net/http2/decoder/http2_frame_decoder_test.cc b/net/http2/decoder/http2_frame_decoder_test.cc new file mode 100644 index 0000000..c2ec0a0d --- /dev/null +++ b/net/http2/decoder/http2_frame_decoder_test.cc
@@ -0,0 +1,946 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/http2_frame_decoder.h" + +// Tests of Http2FrameDecoder. + +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/logging.h" +#include "net/http2/decoder/frame_parts.h" +#include "net/http2/decoder/frame_parts_collector_listener.h" +#include "net/http2/http2_constants.h" +#include "net/http2/tools/failure.h" +#include "net/http2/tools/http2_random.h" +#include "net/http2/tools/random_decoder_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::StringPiece; +using std::string; +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; + +namespace net { +namespace test { +class Http2FrameDecoderPeer { + public: + static size_t remaining_total_payload(Http2FrameDecoder* decoder) { + return decoder->frame_decoder_state_.remaining_total_payload(); + } +}; + +namespace { + +class Http2FrameDecoderTest : public RandomDecoderTest { + public: + AssertionResult ValidatorForDecodePayloadExpectingError( + const FrameParts& expected, + const DecodeBuffer& input, + DecodeStatus status) { + VERIFY_EQ(status, DecodeStatus::kDecodeError); + VERIFY_AND_RETURN_SUCCESS(VerifyCollected(expected)); + } + + AssertionResult ValidatorForBeyondMaximum(const FrameParts& expected, + const DecodeBuffer& input, + DecodeStatus status) { + VERIFY_EQ(status, DecodeStatus::kDecodeError); + // The decoder detects this error after decoding the header, and without + // trying to decode the payload. + VERIFY_EQ(input.Offset(), Http2FrameHeader::EncodedSize()); + VERIFY_AND_RETURN_SUCCESS(VerifyCollected(expected)); + } + + protected: + void SetUp() override { + // On any one run of this suite, we'll always choose the same value for + // use_default_reconstruct_ because the random seed is the same for each + // test case, but across runs the random seed changes. + use_default_reconstruct_ = Random().OneIn(2); + } + + DecodeStatus StartDecoding(DecodeBuffer* db) override { + DVLOG(2) << "StartDecoding, db->Remaining=" << db->Remaining(); + collector_.Reset(); + PrepareDecoder(); + + DecodeStatus status = decoder_.DecodeFrame(db); + if (status != DecodeStatus::kDecodeInProgress) { + // Keep track of this so that a concrete test can verify that both fast + // and slow decoding paths have been tested. + ++fast_decode_count_; + if (status == DecodeStatus::kDecodeError) { + ConfirmDiscardsRemainingPayload(); + } + } + return status; + } + + DecodeStatus ResumeDecoding(DecodeBuffer* db) override { + DVLOG(2) << "ResumeDecoding, db->Remaining=" << db->Remaining(); + DecodeStatus status = decoder_.DecodeFrame(db); + if (status != DecodeStatus::kDecodeInProgress) { + // Keep track of this so that a concrete test can verify that both fast + // and slow decoding paths have been tested. + ++slow_decode_count_; + if (status == DecodeStatus::kDecodeError) { + ConfirmDiscardsRemainingPayload(); + } + } + return status; + } + + // When an error is returned, the decoder is in state kDiscardPayload, and + // stays there until the remaining bytes of the frame's payload have been + // skipped over. There are no callbacks for this situation. + void ConfirmDiscardsRemainingPayload() { + ASSERT_TRUE(decoder_.IsDiscardingPayload()); + size_t remaining = + Http2FrameDecoderPeer::remaining_total_payload(&decoder_); + // The decoder will discard the remaining bytes, but not go beyond that, + // which these conditions verify. + size_t extra = 10; + string junk(remaining + extra, '0'); + DecodeBuffer tmp(junk); + EXPECT_EQ(DecodeStatus::kDecodeDone, decoder_.DecodeFrame(&tmp)); + EXPECT_EQ(remaining, tmp.Offset()); + EXPECT_EQ(extra, tmp.Remaining()); + EXPECT_FALSE(decoder_.IsDiscardingPayload()); + } + + void PrepareDecoder() { + // Save and restore the maximum_payload_size when reconstructing + // the decoder. + size_t maximum_payload_size = decoder_.maximum_payload_size(); + + // Alternate which constructor is used. + if (use_default_reconstruct_) { + decoder_.~Http2FrameDecoder(); + new (&decoder_) Http2FrameDecoder; + decoder_.set_listener(&collector_); + } else { + decoder_.~Http2FrameDecoder(); + new (&decoder_) Http2FrameDecoder(&collector_); + } + decoder_.set_maximum_payload_size(maximum_payload_size); + + use_default_reconstruct_ = !use_default_reconstruct_; + } + + void ResetDecodeSpeedCounters() { + fast_decode_count_ = 0; + slow_decode_count_ = 0; + } + + AssertionResult VerifyCollected(const FrameParts& expected) { + VERIFY_FALSE(collector_.IsInProgress()); + VERIFY_EQ(1u, collector_.size()); + VERIFY_AND_RETURN_SUCCESS(expected.VerifyEquals(*collector_.frame(0))); + } + + AssertionResult DecodePayloadAndValidateSeveralWays(StringPiece payload, + Validator validator) { + DecodeBuffer db(payload); + bool start_decoding_requires_non_empty = false; + return DecodeAndValidateSeveralWays(&db, start_decoding_requires_non_empty, + validator); + } + + AssertionResult ValidatorForDecodePayloadAndValidateSeveralWays( + const FrameParts& expected, + const DecodeBuffer& input, + DecodeStatus status) { + VERIFY_EQ(status, DecodeStatus::kDecodeDone); + VERIFY_AND_RETURN_SUCCESS(VerifyCollected(expected)); + } + + // Decode one frame's payload and confirm that the listener recorded the + // expected FrameParts instance, and only one FrameParts instance. The + // payload will be decoded several times with different partitionings + // of the payload, and after each the validator will be called. + AssertionResult DecodePayloadAndValidateSeveralWays( + StringPiece payload, + const FrameParts& expected) { + Validator validator = base::Bind( + &Http2FrameDecoderTest::ValidatorForDecodePayloadAndValidateSeveralWays, + base::Unretained(this), base::ConstRef(expected)); + ResetDecodeSpeedCounters(); + VERIFY_SUCCESS(DecodePayloadAndValidateSeveralWays( + payload, ValidateDoneAndEmpty(validator))); + VERIFY_GT(fast_decode_count_, 0u); + VERIFY_GT(slow_decode_count_, 0u); + + // Repeat with more input; it should stop without reading that input. + string next_frame = Random().RandString(10); + string input; + payload.AppendToString(&input); + input += next_frame; + + ResetDecodeSpeedCounters(); + VERIFY_SUCCESS(DecodePayloadAndValidateSeveralWays( + payload, ValidateDoneAndOffset(payload.size(), validator))); + VERIFY_GT(fast_decode_count_, 0u); + VERIFY_GT(slow_decode_count_, 0u); + + return AssertionSuccess(); + } + + template <size_t N> + AssertionResult DecodePayloadAndValidateSeveralWays( + const char (&buf)[N], + const FrameParts& expected) { + return DecodePayloadAndValidateSeveralWays(StringPiece(buf, N), expected); + } + + template <size_t N> + AssertionResult DecodePayloadAndValidateSeveralWays( + const char (&buf)[N], + const Http2FrameHeader& header) { + return DecodePayloadAndValidateSeveralWays(StringPiece(buf, N), + FrameParts(header)); + } + + template <size_t N> + AssertionResult DecodePayloadExpectingError(const char (&buf)[N], + const FrameParts& expected) { + ResetDecodeSpeedCounters(); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays( + ToStringPiece(buf), + base::Bind( + &Http2FrameDecoderTest::ValidatorForDecodePayloadExpectingError, + base::Unretained(this), expected))); + EXPECT_GT(fast_decode_count_, 0u); + EXPECT_GT(slow_decode_count_, 0u); + return AssertionSuccess(); + } + + template <size_t N> + AssertionResult DecodePayloadExpectingFrameSizeError(const char (&buf)[N], + FrameParts expected) { + expected.has_frame_size_error = true; + VERIFY_AND_RETURN_SUCCESS(DecodePayloadExpectingError(buf, expected)); + } + + template <size_t N> + AssertionResult DecodePayloadExpectingFrameSizeError( + const char (&buf)[N], + const Http2FrameHeader& header) { + return DecodePayloadExpectingFrameSizeError(buf, FrameParts(header)); + } + + // Count of payloads that are fully decoded by StartDecodingPayload or for + // which an error was detected by StartDecodingPayload. + size_t fast_decode_count_ = 0; + + // Count of payloads that required calling ResumeDecodingPayload in order to + // decode completely, or for which an error was detected by + // ResumeDecodingPayload. + size_t slow_decode_count_ = 0; + + FramePartsCollectorListener collector_; + Http2FrameDecoder decoder_; + bool use_default_reconstruct_; +}; + +//////////////////////////////////////////////////////////////////////////////// +// Tests that pass the minimum allowed size for the frame type, which is often +// empty. The tests are in order by frame type value (i.e. 0 for DATA frames). + +TEST_F(Http2FrameDecoderTest, DataEmpty) { + const char kFrameData[] = { + 0x00, 0x00, 0x00, // Payload length: 0 + 0x00, // DATA + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x00, // Stream ID: 0 (invalid but unchecked here) + }; + Http2FrameHeader header(0, Http2FrameType::DATA, 0, 0); + FrameParts expected(header, ""); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, HeadersEmpty) { + const char kFrameData[] = { + 0x00, 0x00, 0x00, // Payload length: 0 + 0x01, // HEADERS + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream ID: 0 (REQUIRES ID) + }; + Http2FrameHeader header(0, Http2FrameType::HEADERS, 0, 1); + FrameParts expected(header, ""); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, Priority) { + const char kFrameData[] = { + 0x00, 0x00, 0x05, // Length: 5 + 0x02, // Type: PRIORITY + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x02, // Stream: 2 + 0x80u, 0x00, 0x00, 0x01, // Parent: 1 (Exclusive) + 0x10, // Weight: 17 + }; + Http2FrameHeader header(5, Http2FrameType::PRIORITY, 0, 2); + FrameParts expected(header); + expected.opt_priority = Http2PriorityFields(1, 17, true); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, RstStream) { + const char kFrameData[] = { + 0x00, 0x00, 0x04, // Length: 4 + 0x03, // Type: RST_STREAM + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x00, 0x00, 0x01, // Error: PROTOCOL_ERROR + }; + Http2FrameHeader header(4, Http2FrameType::RST_STREAM, 0, 1); + FrameParts expected(header); + expected.opt_rst_stream_error_code = Http2ErrorCode::PROTOCOL_ERROR; + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, SettingsEmpty) { + const char kFrameData[] = { + 0x00, 0x00, 0x00, // Length: 0 + 0x04, // Type: SETTINGS + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 (invalid but unchecked here) + }; + Http2FrameHeader header(0, Http2FrameType::SETTINGS, 0, 1); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, SettingsAck) { + const char kFrameData[] = { + 0x00, 0x00, 0x00, // Length: 6 + 0x04, // Type: SETTINGS + 0x01, // Flags: ACK + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + }; + Http2FrameHeader header(0, Http2FrameType::SETTINGS, Http2FrameFlag::FLAG_ACK, + 0); + FrameParts expected(header); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, PushPromiseMinimal) { + const char kFrameData[] = { + 0x00, 0x00, 0x04, // Payload length: 4 + 0x05, // PUSH_PROMISE + 0x04, // Flags: END_HEADERS + 0x00, 0x00, 0x00, 0x02, // Stream: 2 (invalid but unchecked here) + 0x00, 0x00, 0x00, 0x01, // Promised: 1 (invalid but unchecked here) + }; + Http2FrameHeader header(4, Http2FrameType::PUSH_PROMISE, + Http2FrameFlag::FLAG_END_HEADERS, 2); + FrameParts expected(header, ""); + expected.opt_push_promise = Http2PushPromiseFields{1}; + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, Ping) { + const char kFrameData[] = { + 0x00, 0x00, 0x08, // Length: 8 + 0x06, // Type: PING + 0xfeu, // Flags: no valid flags + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + 's', 'o', 'm', 'e', // "some" + 'd', 'a', 't', 'a', // "data" + }; + Http2FrameHeader header(8, Http2FrameType::PING, 0, 0); + FrameParts expected(header); + expected.opt_ping = Http2PingFields{{'s', 'o', 'm', 'e', 'd', 'a', 't', 'a'}}; + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, PingAck) { + const char kFrameData[] = { + 0x00, 0x00, 0x08, // Length: 8 + 0x06, // Type: PING + 0xffu, // Flags: ACK (plus all invalid flags) + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + 's', 'o', 'm', 'e', // "some" + 'd', 'a', 't', 'a', // "data" + }; + Http2FrameHeader header(8, Http2FrameType::PING, Http2FrameFlag::FLAG_ACK, 0); + FrameParts expected(header); + expected.opt_ping = Http2PingFields{{'s', 'o', 'm', 'e', 'd', 'a', 't', 'a'}}; + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, GoAwayMinimal) { + const char kFrameData[] = { + 0x00, 0x00, 0x08, // Length: 8 (no opaque data) + 0x07, // Type: GOAWAY + 0xffu, // Flags: 0xff (no valid flags) + 0x00, 0x00, 0x00, 0x01, // Stream: 1 (invalid but unchecked here) + 0x80u, 0x00, 0x00, 0xffu, // Last: 255 (plus R bit) + 0x00, 0x00, 0x00, 0x09, // Error: COMPRESSION_ERROR + }; + Http2FrameHeader header(8, Http2FrameType::GOAWAY, 0, 1); + FrameParts expected(header); + expected.opt_goaway = + Http2GoAwayFields(255, Http2ErrorCode::COMPRESSION_ERROR); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, WindowUpdate) { + const char kFrameData[] = { + 0x00, 0x00, 0x04, // Length: 4 + 0x08, // Type: WINDOW_UPDATE + 0x0f, // Flags: 0xff (no valid flags) + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x80u, 0x00, 0x04, 0x00, // Incr: 1024 (plus R bit) + }; + Http2FrameHeader header(4, Http2FrameType::WINDOW_UPDATE, 0, 1); + FrameParts expected(header); + expected.opt_window_update_increment = 1024; + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, ContinuationEmpty) { + const char kFrameData[] = { + 0x00, 0x00, 0x00, // Payload length: 0 + 0x09, // CONTINUATION + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x00, // Stream ID: 0 (invalid but unchecked here) + }; + Http2FrameHeader header(0, Http2FrameType::CONTINUATION, 0, 0); + FrameParts expected(header); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, AltSvcMinimal) { + const char kFrameData[] = { + 0x00, 0x00, 0x02, // Payload length: 2 + 0x0a, // ALTSVC + 0xffu, // Flags: none (plus 0xff) + 0x00, 0x00, 0x00, 0x00, // Stream ID: 0 (invalid but unchecked here) + 0x00, 0x00, // Origin Length: 0 + }; + Http2FrameHeader header(2, Http2FrameType::ALTSVC, 0, 0); + FrameParts expected(header); + expected.opt_altsvc_origin_length = 0; + expected.opt_altsvc_value_length = 0; + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, UnknownEmpty) { + const char kFrameData[] = { + 0x00, 0x00, 0x00, // Payload length: 0 + 0x20, // 32 (unknown) + 0xffu, // Flags: all + 0x00, 0x00, 0x00, 0x00, // Stream ID: 0 + }; + Http2FrameHeader header(0, static_cast<Http2FrameType>(32), 0xff, 0); + FrameParts expected(header); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Tests of longer payloads, for those frame types that allow longer payloads. + +TEST_F(Http2FrameDecoderTest, DataPayload) { + const char kFrameData[] = { + 0x00, 0x00, 0x03, // Payload length: 7 + 0x00, // DATA + 0x80u, // Flags: 0x80 + 0x00, 0x00, 0x02, 0x02, // Stream ID: 514 + 'a', 'b', 'c', // Data + }; + Http2FrameHeader header(3, Http2FrameType::DATA, 0, 514); + FrameParts expected(header, "abc"); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, HeadersPayload) { + const char kFrameData[] = { + 0x00, 0x00, 0x03, // Payload length: 3 + 0x01, // HEADERS + 0x05, // Flags: END_STREAM | END_HEADERS + 0x00, 0x00, 0x00, 0x02, // Stream ID: 0 (REQUIRES ID) + 'a', 'b', 'c', // HPACK fragment (doesn't have to be valid) + }; + Http2FrameHeader header( + 3, Http2FrameType::HEADERS, + Http2FrameFlag::FLAG_END_STREAM | Http2FrameFlag::FLAG_END_HEADERS, 2); + FrameParts expected(header, "abc"); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, HeadersPriority) { + const char kFrameData[] = { + 0x00, 0x00, 0x05, // Payload length: 5 + 0x01, // HEADERS + 0x20, // Flags: PRIORITY + 0x00, 0x00, 0x00, 0x02, // Stream ID: 0 (REQUIRES ID) + 0x00, 0x00, 0x00, 0x01, // Parent: 1 (Not Exclusive) + 0xffu, // Weight: 256 + }; + Http2FrameHeader header(5, Http2FrameType::HEADERS, + Http2FrameFlag::FLAG_PRIORITY, 2); + FrameParts expected(header); + expected.opt_priority = Http2PriorityFields(1, 256, false); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, Settings) { + const char kFrameData[] = { + 0x00, 0x00, 0x0c, // Length: 12 + 0x04, // Type: SETTINGS + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + 0x00, 0x04, // Param: INITIAL_WINDOW_SIZE + 0x0a, 0x0b, 0x0c, 0x0d, // Value: 168496141 + 0x00, 0x02, // Param: ENABLE_PUSH + 0x00, 0x00, 0x00, 0x03, // Value: 3 (invalid but unchecked here) + }; + Http2FrameHeader header(12, Http2FrameType::SETTINGS, 0, 0); + FrameParts expected(header); + expected.settings.push_back(Http2SettingFields( + Http2SettingsParameter::INITIAL_WINDOW_SIZE, 168496141)); + expected.settings.push_back( + Http2SettingFields(Http2SettingsParameter::ENABLE_PUSH, 3)); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, PushPromisePayload) { + const char kFrameData[] = { + 0x00, 0x00, 7, // Payload length: 7 + 0x05, // PUSH_PROMISE + 0x04, // Flags: END_HEADERS + 0x00, 0x00, 0x00, 0xffu, // Stream ID: 255 + 0x00, 0x00, 0x01, 0x00, // Promised: 256 + 'a', 'b', 'c', // HPACK fragment (doesn't have to be valid) + }; + Http2FrameHeader header(7, Http2FrameType::PUSH_PROMISE, + Http2FrameFlag::FLAG_END_HEADERS, 255); + FrameParts expected(header, "abc"); + expected.opt_push_promise = Http2PushPromiseFields{256}; + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, GoAwayOpaqueData) { + const char kFrameData[] = { + 0x00, 0x00, 0x0e, // Length: 14 + 0x07, // Type: GOAWAY + 0xffu, // Flags: 0xff (no valid flags) + 0x80u, 0x00, 0x00, 0x00, // Stream: 0 (plus R bit) + 0x00, 0x00, 0x01, 0x00, // Last: 256 + 0x00, 0x00, 0x00, 0x03, // Error: FLOW_CONTROL_ERROR + 'o', 'p', 'a', 'q', 'u', 'e', + }; + Http2FrameHeader header(14, Http2FrameType::GOAWAY, 0, 0); + FrameParts expected(header, "opaque"); + expected.opt_goaway = + Http2GoAwayFields(256, Http2ErrorCode::FLOW_CONTROL_ERROR); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, ContinuationPayload) { + const char kFrameData[] = { + 0x00, 0x00, 0x03, // Payload length: 3 + 0x09, // CONTINUATION + 0xffu, // Flags: END_HEADERS | 0xfb + 0x00, 0x00, 0x00, 0x02, // Stream ID: 2 + 'a', 'b', 'c', // Data + }; + Http2FrameHeader header(3, Http2FrameType::CONTINUATION, + Http2FrameFlag::FLAG_END_HEADERS, 2); + FrameParts expected(header, "abc"); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, AltSvcPayload) { + const char kFrameData[] = { + 0x00, 0x00, 0x08, // Payload length: 3 + 0x0a, // ALTSVC + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x02, // Stream ID: 2 + 0x00, 0x03, // Origin Length: 0 + 'a', 'b', 'c', // Origin + 'd', 'e', 'f', // Value + }; + Http2FrameHeader header(8, Http2FrameType::ALTSVC, 0, 2); + FrameParts expected(header); + expected.SetAltSvcExpected("abc", "def"); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, UnknownPayload) { + const char kFrameData[] = { + 0x00, 0x00, 0x03, // Payload length: 3 + 0x30, // 48 (unknown) + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x02, // Stream ID: 2 + 'a', 'b', 'c', // Payload + }; + Http2FrameHeader header(3, static_cast<Http2FrameType>(48), 0, 2); + FrameParts expected(header, "abc"); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Tests of padded payloads, for those frame types that allow padding. + +TEST_F(Http2FrameDecoderTest, DataPayloadAndPadding) { + const char kFrameData[] = { + 0x00, 0x00, 0x07, // Payload length: 7 + 0x00, // DATA + 0x09, // Flags: END_STREAM | PADDED + 0x00, 0x00, 0x00, 0x02, // Stream ID: 0 (REQUIRES ID) + 0x03, // Pad Len + 'a', 'b', 'c', // Data + 0x00, 0x00, 0x00, // Padding + }; + Http2FrameHeader header( + 7, Http2FrameType::DATA, + Http2FrameFlag::FLAG_END_STREAM | Http2FrameFlag::FLAG_PADDED, 2); + size_t total_pad_length = 4; // Including the Pad Length field. + FrameParts expected(header, "abc", total_pad_length); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, HeadersPayloadAndPadding) { + const char kFrameData[] = { + 0x00, 0x00, 0x07, // Payload length: 7 + 0x01, // HEADERS + 0x08, // Flags: PADDED + 0x00, 0x00, 0x00, 0x02, // Stream ID: 0 (REQUIRES ID) + 0x03, // Pad Len + 'a', 'b', 'c', // HPACK fragment (doesn't have to be valid) + 0x00, 0x00, 0x00, // Padding + }; + Http2FrameHeader header(7, Http2FrameType::HEADERS, + Http2FrameFlag::FLAG_PADDED, 2); + size_t total_pad_length = 4; // Including the Pad Length field. + FrameParts expected(header, "abc", total_pad_length); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, HeadersPayloadPriorityAndPadding) { + const char kFrameData[] = { + 0x00, 0x00, 0x0c, // Payload length: 12 + 0x01, // HEADERS + 0xffu, // Flags: all, including undefined + 0x00, 0x00, 0x00, 0x02, // Stream ID: 0 (REQUIRES ID) + 0x03, // Pad Len + 0x80u, 0x00, 0x00, 0x01, // Parent: 1 (Exclusive) + 0x10, // Weight: 17 + 'a', 'b', 'c', // HPACK fragment (doesn't have to be valid) + 0x00, 0x00, 0x00, // Padding + }; + Http2FrameHeader header( + 12, Http2FrameType::HEADERS, + Http2FrameFlag::FLAG_END_STREAM | Http2FrameFlag::FLAG_END_HEADERS | + Http2FrameFlag::FLAG_PADDED | Http2FrameFlag::FLAG_PRIORITY, + 2); + size_t total_pad_length = 4; // Including the Pad Length field. + FrameParts expected(header, "abc", total_pad_length); + expected.opt_priority = Http2PriorityFields(1, 17, true); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, PushPromisePayloadAndPadding) { + const char kFrameData[] = { + 0x00, 0x00, 11, // Payload length: 11 + 0x05, // PUSH_PROMISE + 0xffu, // Flags: END_HEADERS | PADDED | 0xf3 + 0x00, 0x00, 0x00, 0x01, // Stream ID: 1 + 0x03, // Pad Len + 0x00, 0x00, 0x00, 0x02, // Promised: 2 + 'a', 'b', 'c', // HPACK fragment (doesn't have to be valid) + 0x00, 0x00, 0x00, // Padding + }; + Http2FrameHeader header( + 11, Http2FrameType::PUSH_PROMISE, + Http2FrameFlag::FLAG_END_HEADERS | Http2FrameFlag::FLAG_PADDED, 1); + size_t total_pad_length = 4; // Including the Pad Length field. + FrameParts expected(header, "abc", total_pad_length); + expected.opt_push_promise = Http2PushPromiseFields{2}; + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(kFrameData, expected)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Payload too short errors. + +TEST_F(Http2FrameDecoderTest, DataMissingPadLengthField) { + const char kFrameData[] = { + 0x00, 0x00, 0x00, // Payload length: 0 + 0x00, // DATA + 0x08, // Flags: PADDED + 0x00, 0x00, 0x00, 0x01, // Stream ID: 1 + }; + Http2FrameHeader header(0, Http2FrameType::DATA, Http2FrameFlag::FLAG_PADDED, + 1); + FrameParts expected(header); + expected.opt_missing_length = 1; + EXPECT_TRUE(DecodePayloadExpectingError(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, HeaderPaddingTooLong) { + const char kFrameData[] = { + 0x00, 0x00, 0x02, // Payload length: 0 + 0x01, // HEADERS + 0x08, // Flags: PADDED + 0x00, 0x01, 0x00, 0x00, // Stream ID: 65536 + 0xffu, // Pad Len: 255 + 0x00, // Only one byte of padding + }; + Http2FrameHeader header(2, Http2FrameType::HEADERS, + Http2FrameFlag::FLAG_PADDED, 65536); + FrameParts expected(header); + expected.opt_missing_length = 254; + EXPECT_TRUE(DecodePayloadExpectingError(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, HeaderMissingPriority) { + const char kFrameData[] = { + 0x00, 0x00, 0x04, // Payload length: 0 + 0x01, // HEADERS + 0x20, // Flags: PRIORITY + 0x00, 0x01, 0x00, 0x00, // Stream ID: 65536 + 0x00, 0x00, 0x00, 0x00, // Priority (truncated) + }; + Http2FrameHeader header(4, Http2FrameType::HEADERS, + Http2FrameFlag::FLAG_PRIORITY, 65536); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, PriorityTooShort) { + const char kFrameData[] = { + 0x00, 0x00, 0x04, // Length: 5 + 0x02, // Type: PRIORITY + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x02, // Stream: 2 + 0x80u, 0x00, 0x00, 0x01, // Parent: 1 (Exclusive) + }; + Http2FrameHeader header(4, Http2FrameType::PRIORITY, 0, 2); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, RstStreamTooShort) { + const char kFrameData[] = { + 0x00, 0x00, 0x03, // Length: 4 + 0x03, // Type: RST_STREAM + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x00, 0x00, // Truncated + }; + Http2FrameHeader header(3, Http2FrameType::RST_STREAM, 0, 1); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +// SETTINGS frames must a multiple of 6 bytes long, so an 9 byte payload is +// invalid. +TEST_F(Http2FrameDecoderTest, SettingsWrongSize) { + const char kFrameData[] = { + 0x00, 0x00, 0x09, // Length: 2 + 0x04, // Type: SETTINGS + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + 0x00, 0x02, // Param: ENABLE_PUSH + 0x00, 0x00, 0x00, 0x03, // Value: 1 + 0x00, 0x04, // Param: INITIAL_WINDOW_SIZE + 0x00, // Value: Truncated + }; + Http2FrameHeader header(9, Http2FrameType::SETTINGS, 0, 0); + FrameParts expected(header); + expected.settings.push_back( + Http2SettingFields(Http2SettingsParameter::ENABLE_PUSH, 3)); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, expected)); +} + +TEST_F(Http2FrameDecoderTest, PushPromiseTooShort) { + const char kFrameData[] = { + 0x00, 0x00, 3, // Payload length: 3 + 0x05, // PUSH_PROMISE + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream ID: 1 + 0x00, 0x00, 0x00, // Truncated promise id + }; + Http2FrameHeader header(3, Http2FrameType::PUSH_PROMISE, 0, 1); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, PushPromisePaddedTruncatedPromise) { + const char kFrameData[] = { + 0x00, 0x00, 4, // Payload length: 4 + 0x05, // PUSH_PROMISE + 0x08, // Flags: PADDED + 0x00, 0x00, 0x00, 0x01, // Stream ID: 1 + 0x00, // Pad Len + 0x00, 0x00, 0x00, // Truncated promise id + }; + Http2FrameHeader header(4, Http2FrameType::PUSH_PROMISE, + Http2FrameFlag::FLAG_PADDED, 1); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, PingTooShort) { + const char kFrameData[] = { + 0x00, 0x00, 0x07, // Length: 8 + 0x06, // Type: PING + 0xfeu, // Flags: no valid flags + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + 's', 'o', 'm', 'e', // "some" + 'd', 'a', 't', // Too little + }; + Http2FrameHeader header(7, Http2FrameType::PING, 0, 0); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, GoAwayTooShort) { + const char kFrameData[] = { + 0x00, 0x00, 0x00, // Length: 0 + 0x07, // Type: GOAWAY + 0xffu, // Flags: 0xff (no valid flags) + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + }; + Http2FrameHeader header(0, Http2FrameType::GOAWAY, 0, 0); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, WindowUpdateTooShort) { + const char kFrameData[] = { + 0x00, 0x00, 0x03, // Length: 3 + 0x08, // Type: WINDOW_UPDATE + 0x0f, // Flags: 0xff (no valid flags) + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x80u, 0x00, 0x04, // Truncated + }; + Http2FrameHeader header(3, Http2FrameType::WINDOW_UPDATE, 0, 1); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, AltSvcTruncatedOriginLength) { + const char kFrameData[] = { + 0x00, 0x00, 0x01, // Payload length: 3 + 0x0a, // ALTSVC + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x02, // Stream ID: 2 + 0x00, // Origin Length: truncated + }; + Http2FrameHeader header(1, Http2FrameType::ALTSVC, 0, 2); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, AltSvcTruncatedOrigin) { + const char kFrameData[] = { + 0x00, 0x00, 0x05, // Payload length: 3 + 0x0a, // ALTSVC + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x02, // Stream ID: 2 + 0x00, 0x04, // Origin Length: 4 (too long) + 'a', 'b', 'c', // Origin + }; + Http2FrameHeader header(5, Http2FrameType::ALTSVC, 0, 2); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Payload too long errors. + +// The decoder calls the listener's OnFrameSizeError method if the frame's +// payload is longer than the currently configured maximum payload size. +TEST_F(Http2FrameDecoderTest, BeyondMaximum) { + decoder_.set_maximum_payload_size(2); + const char kFrameData[] = { + 0x00, 0x00, 0x07, // Payload length: 7 + 0x00, // DATA + 0x09, // Flags: END_STREAM | PADDED + 0x00, 0x00, 0x00, 0x02, // Stream ID: 0 (REQUIRES ID) + 0x03, // Pad Len + 'a', 'b', 'c', // Data + 0x00, 0x00, 0x00, // Padding + }; + Http2FrameHeader header( + 7, Http2FrameType::DATA, + Http2FrameFlag::FLAG_END_STREAM | Http2FrameFlag::FLAG_PADDED, 2); + FrameParts expected(header); + expected.has_frame_size_error = true; + ResetDecodeSpeedCounters(); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays( + ToStringPiece(kFrameData), + base::Bind(&Http2FrameDecoderTest::ValidatorForBeyondMaximum, + base::Unretained(this), expected))); + EXPECT_GT(fast_decode_count_, 0u); + EXPECT_GT(slow_decode_count_, 0u); +} + +TEST_F(Http2FrameDecoderTest, PriorityTooLong) { + const char kFrameData[] = { + 0x00, 0x00, 0x06, // Length: 5 + 0x02, // Type: PRIORITY + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x02, // Stream: 2 + 0x80u, 0x00, 0x00, 0x01, // Parent: 1 (Exclusive) + 0x10, // Weight: 17 + 0x00, // Too much + }; + Http2FrameHeader header(6, Http2FrameType::PRIORITY, 0, 2); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, RstStreamTooLong) { + const char kFrameData[] = { + 0x00, 0x00, 0x05, // Length: 4 + 0x03, // Type: RST_STREAM + 0x00, // Flags: none + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x00, 0x00, 0x00, 0x01, // Error: PROTOCOL_ERROR + 0x00, // Too much + }; + Http2FrameHeader header(5, Http2FrameType::RST_STREAM, 0, 1); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, SettingsAckTooLong) { + const char kFrameData[] = { + 0x00, 0x00, 0x06, // Length: 6 + 0x04, // Type: SETTINGS + 0x01, // Flags: ACK + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + 0x00, 0x00, // Extra + 0x00, 0x00, 0x00, 0x00, // Extra + }; + Http2FrameHeader header(6, Http2FrameType::SETTINGS, Http2FrameFlag::FLAG_ACK, + 0); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, PingAckTooLong) { + const char kFrameData[] = { + 0x00, 0x00, 0x09, // Length: 8 + 0x06, // Type: PING + 0xffu, // Flags: ACK | 0xfe + 0x00, 0x00, 0x00, 0x00, // Stream: 0 + 's', 'o', 'm', 'e', // "some" + 'd', 'a', 't', 'a', // "data" + 0x00, // Too much + }; + Http2FrameHeader header(9, Http2FrameType::PING, Http2FrameFlag::FLAG_ACK, 0); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +TEST_F(Http2FrameDecoderTest, WindowUpdateTooLong) { + const char kFrameData[] = { + 0x00, 0x00, 0x05, // Length: 5 + 0x08, // Type: WINDOW_UPDATE + 0x0f, // Flags: 0xff (no valid flags) + 0x00, 0x00, 0x00, 0x01, // Stream: 1 + 0x80u, 0x00, 0x04, 0x00, // Incr: 1024 (plus R bit) + 0x00, // Too much + }; + Http2FrameHeader header(5, Http2FrameType::WINDOW_UPDATE, 0, 1); + EXPECT_TRUE(DecodePayloadExpectingFrameSizeError(kFrameData, header)); +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/decoder/http2_structure_decoder.cc b/net/http2/decoder/http2_structure_decoder.cc new file mode 100644 index 0000000..a7b1dca4 --- /dev/null +++ b/net/http2/decoder/http2_structure_decoder.cc
@@ -0,0 +1,90 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/http2_structure_decoder.h" + +#include <algorithm> + +#include "net/http2/tools/http2_bug_tracker.h" + +namespace net { + +// Below we have some defensive coding: if we somehow run off the end, don't +// overwrite lots of memory. Note that most of this decoder is not defensive +// against bugs in the decoder, only against malicious encoders, but since +// we're copying memory into a buffer here, let's make sure we don't allow a +// small mistake to grow larger. The decoder will get stuck if we hit the +// HTTP2_BUG conditions, but shouldn't corrupt memory. + +uint32_t Http2StructureDecoder::IncompleteStart(DecodeBuffer* db, + uint32_t target_size) { + if (target_size > sizeof buffer_) { + HTTP2_BUG << "target_size too large for buffer: " << target_size; + return 0; + } + const uint32_t num_to_copy = db->MinLengthRemaining(target_size); + memcpy(buffer_, db->cursor(), num_to_copy); + offset_ = num_to_copy; + db->AdvanceCursor(num_to_copy); + return num_to_copy; +} + +DecodeStatus Http2StructureDecoder::IncompleteStart(DecodeBuffer* db, + uint32_t* remaining_payload, + uint32_t target_size) { + DVLOG(1) << "IncompleteStart@" << this + << ": *remaining_payload=" << *remaining_payload + << "; target_size=" << target_size + << "; db->Remaining=" << db->Remaining(); + *remaining_payload -= + IncompleteStart(db, std::min(target_size, *remaining_payload)); + if (*remaining_payload > 0 && db->Empty()) { + return DecodeStatus::kDecodeInProgress; + } + DVLOG(1) << "IncompleteStart: kDecodeError"; + return DecodeStatus::kDecodeError; +} + +bool Http2StructureDecoder::ResumeFillingBuffer(DecodeBuffer* db, + uint32_t target_size) { + DVLOG(2) << "ResumeFillingBuffer@" << this << ": target_size=" << target_size + << "; offset_=" << offset_ << "; db->Remaining=" << db->Remaining(); + if (target_size < offset_) { + HTTP2_BUG << "Already filled buffer_! target_size=" << target_size + << " offset_=" << offset_; + return false; + } + const uint32_t needed = target_size - offset_; + const uint32_t num_to_copy = db->MinLengthRemaining(needed); + DVLOG(2) << "ResumeFillingBuffer num_to_copy=" << num_to_copy; + memcpy(&buffer_[offset_], db->cursor(), num_to_copy); + db->AdvanceCursor(num_to_copy); + offset_ += num_to_copy; + return needed == num_to_copy; +} + +bool Http2StructureDecoder::ResumeFillingBuffer(DecodeBuffer* db, + uint32_t* remaining_payload, + uint32_t target_size) { + DVLOG(2) << "ResumeFillingBuffer@" << this << ": target_size=" << target_size + << "; offset_=" << offset_ + << "; *remaining_payload=" << *remaining_payload + << "; db->Remaining=" << db->Remaining(); + if (target_size < offset_) { + HTTP2_BUG << "Already filled buffer_! target_size=" << target_size + << " offset_=" << offset_; + return false; + } + const uint32_t needed = target_size - offset_; + const uint32_t num_to_copy = + db->MinLengthRemaining(std::min(needed, *remaining_payload)); + DVLOG(2) << "ResumeFillingBuffer num_to_copy=" << num_to_copy; + memcpy(&buffer_[offset_], db->cursor(), num_to_copy); + db->AdvanceCursor(num_to_copy); + offset_ += num_to_copy; + *remaining_payload -= num_to_copy; + return needed == num_to_copy; +} + +} // namespace net
diff --git a/net/http2/decoder/http2_structure_decoder.h b/net/http2/decoder/http2_structure_decoder.h new file mode 100644 index 0000000..8665e96 --- /dev/null +++ b/net/http2/decoder/http2_structure_decoder.h
@@ -0,0 +1,129 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_HTTP2_STRUCTURE_DECODER_H_ +#define NET_HTTP2_DECODER_HTTP2_STRUCTURE_DECODER_H_ + +// Http2StructureDecoder is a class for decoding the fixed size structures in +// the HTTP/2 spec, defined in net/http2/http2_structures.h. This class +// is in aid of deciding whether to keep the SlowDecode methods which I +// (jamessynge) now think may not be worth their complexity. In particular, +// if most transport buffers are large, so it is rare that a structure is +// split across buffer boundaries, than the cost of buffering upon +// those rare occurrences is small, which then simplifies the callers. + +#include "base/logging.h" +#include "net/base/net_export.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_http2_structures.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/http2_structures.h" + +namespace net { +namespace test { +class Http2StructureDecoderPeer; +} // namespace test + +class NET_EXPORT_PRIVATE Http2StructureDecoder { + public: + // The caller needs to keep track of whether to call Start or Resume. + // + // Start has an optimization for the case where the DecodeBuffer holds the + // entire encoded structure; in that case it decodes into *out and returns + // true, and does NOT touch the data members of the Http2StructureDecoder + // instance because the caller won't be calling Resume later. + // + // However, if the DecodeBuffer is too small to hold the entire encoded + // structure, Start copies the available bytes into the Http2StructureDecoder + // instance, and returns false to indicate that it has not been able to + // complete the decoding. + // + template <class S> + bool Start(S* out, DecodeBuffer* db) { + static_assert(S::EncodedSize() <= sizeof buffer_, "buffer_ is too small"); + DVLOG(2) << __func__ << "@" << this << ": db->Remaining=" << db->Remaining() + << "; EncodedSize=" << S::EncodedSize(); + if (db->Remaining() >= S::EncodedSize()) { + DoDecode(out, db); + return true; + } + IncompleteStart(db, S::EncodedSize()); + return false; + } + + template <class S> + bool Resume(S* out, DecodeBuffer* db) { + DVLOG(2) << __func__ << "@" << this << ": offset_=" << offset_ + << "; db->Remaining=" << db->Remaining(); + if (ResumeFillingBuffer(db, S::EncodedSize())) { + // We have the whole thing now. + DVLOG(2) << __func__ << "@" << this << " offset_=" << offset_ + << " Ready to decode from buffer_."; + DecodeBuffer buffer_db(buffer_, S::EncodedSize()); + DoDecode(out, &buffer_db); + return true; + } + DCHECK_LT(offset_, S::EncodedSize()); + return false; + } + + // A second pair of Start and Resume, where the caller has a variable, + // |remaining_payload| that is both tested for sufficiency and updated + // during decoding. Note that the decode buffer may extend beyond the + // remaining payload because the buffer may include padding. + template <class S> + DecodeStatus Start(S* out, DecodeBuffer* db, uint32_t* remaining_payload) { + static_assert(S::EncodedSize() <= sizeof buffer_, "buffer_ is too small"); + DVLOG(2) << __func__ << "@" << this + << ": *remaining_payload=" << *remaining_payload + << "; db->Remaining=" << db->Remaining() + << "; EncodedSize=" << S::EncodedSize(); + if (db->MinLengthRemaining(*remaining_payload) >= S::EncodedSize()) { + DoDecode(out, db); + *remaining_payload -= S::EncodedSize(); + return DecodeStatus::kDecodeDone; + } + return IncompleteStart(db, remaining_payload, S::EncodedSize()); + } + + template <class S> + bool Resume(S* out, DecodeBuffer* db, uint32_t* remaining_payload) { + DVLOG(3) << __func__ << "@" << this << ": offset_=" << offset_ + << "; *remaining_payload=" << *remaining_payload + << "; db->Remaining=" << db->Remaining() + << "; EncodedSize=" << S::EncodedSize(); + if (ResumeFillingBuffer(db, remaining_payload, S::EncodedSize())) { + // We have the whole thing now. + DVLOG(2) << __func__ << "@" << this << ": offset_=" << offset_ + << "; Ready to decode from buffer_."; + DecodeBuffer buffer_db(buffer_, S::EncodedSize()); + DoDecode(out, &buffer_db); + return true; + } + DCHECK_LT(offset_, S::EncodedSize()); + return false; + } + + uint32_t offset() const { return offset_; } + + private: + friend class test::Http2StructureDecoderPeer; + + uint32_t IncompleteStart(DecodeBuffer* db, uint32_t target_size); + DecodeStatus IncompleteStart(DecodeBuffer* db, + uint32_t* remaining_payload, + uint32_t target_size); + + bool ResumeFillingBuffer(DecodeBuffer* db, uint32_t target_size); + bool ResumeFillingBuffer(DecodeBuffer* db, + uint32_t* remaining_payload, + uint32_t target_size); + + uint32_t offset_; + char buffer_[Http2FrameHeader::EncodedSize()]; +}; + +} // namespace net + +#endif // NET_HTTP2_DECODER_HTTP2_STRUCTURE_DECODER_H_
diff --git a/net/http2/decoder/http2_structure_decoder_test.cc b/net/http2/decoder/http2_structure_decoder_test.cc new file mode 100644 index 0000000..68479a1 --- /dev/null +++ b/net/http2/decoder/http2_structure_decoder_test.cc
@@ -0,0 +1,512 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/http2_structure_decoder.h" + +// Tests decoding all of the fixed size HTTP/2 structures (i.e. those defined in +// net/http2/http2_structures.h) using Http2StructureDecoder, which handles +// buffering of structures split across input buffer boundaries, and in turn +// uses DoDecode when it has all of a structure in a contiguous buffer. + +// NOTE: This tests the first pair of Start and Resume, which don't take +// a remaining_payload parameter. The other pair are well tested via the +// payload decoder tests, though... +// TODO(jamessynge): Create type parameterized tests for Http2StructureDecoder +// where the type is the type of structure, and with testing of both pairs of +// Start and Resume methods; note that it appears that the first pair will be +// used only for Http2FrameHeader, and the other pair only for structures in the +// frame payload. + +#include <stddef.h> +#include <string> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/logging.h" +#include "base/strings/string_piece.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures_test_util.h" +#include "net/http2/tools/failure.h" +#include "net/http2/tools/http2_frame_builder.h" +#include "net/http2/tools/random_decoder_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::AssertionFailure; +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using base::StringPiece; +using std::string; + +namespace net { +namespace test { +namespace { +const bool kMayReturnZeroOnFirst = false; + +template <class S> +string SerializeStructure(const S& s) { + Http2FrameBuilder fb; + fb.Append(s); + EXPECT_EQ(S::EncodedSize(), fb.size()); + return fb.buffer(); +} + +template <class S> +class Http2StructureDecoderTest : public RandomDecoderTest { + protected: + typedef S Structure; + + Http2StructureDecoderTest() { + // IF the test adds more data after the encoded structure, stop as + // soon as the structure is decoded. + stop_decode_on_done_ = true; + } + + DecodeStatus StartDecoding(DecodeBuffer* b) override { + // Overwrite the current contents of |structure_|, in to which we'll + // decode the buffer, so that we can be confident that we really decoded + // the structure every time. + structure_.~S(); + new (&structure_) S; + uint32_t old_remaining = b->Remaining(); + if (structure_decoder_.Start(&structure_, b)) { + EXPECT_EQ(old_remaining - S::EncodedSize(), b->Remaining()); + ++fast_decode_count_; + return DecodeStatus::kDecodeDone; + } else { + EXPECT_LT(structure_decoder_.offset(), S::EncodedSize()); + EXPECT_EQ(0u, b->Remaining()); + EXPECT_EQ(old_remaining - structure_decoder_.offset(), b->Remaining()); + ++incomplete_start_count_; + return DecodeStatus::kDecodeInProgress; + } + } + + DecodeStatus ResumeDecoding(DecodeBuffer* b) override { + uint32_t old_offset = structure_decoder_.offset(); + EXPECT_LT(old_offset, S::EncodedSize()); + uint32_t avail = b->Remaining(); + if (structure_decoder_.Resume(&structure_, b)) { + EXPECT_LE(S::EncodedSize(), old_offset + avail); + EXPECT_EQ(b->Remaining(), avail - (S::EncodedSize() - old_offset)); + ++slow_decode_count_; + return DecodeStatus::kDecodeDone; + } else { + EXPECT_LT(structure_decoder_.offset(), S::EncodedSize()); + EXPECT_EQ(0u, b->Remaining()); + EXPECT_GT(S::EncodedSize(), old_offset + avail); + ++incomplete_resume_count_; + return DecodeStatus::kDecodeInProgress; + } + } + + AssertionResult ValidatorForDecodeLeadingStructure(const S* expected, + const DecodeBuffer& db, + DecodeStatus status) { + VERIFY_EQ(*expected, structure_); + return AssertionSuccess(); + } + + // Fully decodes the Structure at the start of data, and confirms it matches + // *expected (if provided). + AssertionResult DecodeLeadingStructure(const S* expected, StringPiece data) { + VERIFY_LE(S::EncodedSize(), data.size()); + DecodeBuffer original(data); + + // The validator is called after each of the several times that the input + // DecodeBuffer is decoded, each with a different segmentation of the input. + // Validate that structure_ matches the expected value, if provided. + Validator validator = + (expected == nullptr) + ? base::Bind(&SucceedingValidator) + : base::Bind(&Http2StructureDecoderTest:: + ValidatorForDecodeLeadingStructure, + base::Unretained(this), expected); + + // Before that, validate that decoding is done and that we've advanced + // the cursor the expected amount. + Validator wrapped_validator = + ValidateDoneAndOffset(S::EncodedSize(), validator); + + // Decode several times, with several segmentations of the input buffer. + fast_decode_count_ = 0; + slow_decode_count_ = 0; + incomplete_start_count_ = 0; + incomplete_resume_count_ = 0; + VERIFY_SUCCESS(DecodeAndValidateSeveralWays( + &original, kMayReturnZeroOnFirst, wrapped_validator)); + VERIFY_FALSE(HasFailure()); + VERIFY_EQ(S::EncodedSize(), structure_decoder_.offset()); + VERIFY_EQ(S::EncodedSize(), original.Offset()); + VERIFY_LT(0u, fast_decode_count_); + VERIFY_LT(0u, slow_decode_count_); + VERIFY_LT(0u, incomplete_start_count_); + + // If the structure is large enough so that SelectZeroOrOne will have + // caused Resume to return false, check that occurred. + if (S::EncodedSize() >= 2) { + VERIFY_LE(0u, incomplete_resume_count_); + } else { + VERIFY_EQ(0u, incomplete_resume_count_); + } + if (expected != nullptr) { + DVLOG(1) << "DecodeLeadingStructure expected: " << *expected; + DVLOG(1) << "DecodeLeadingStructure actual: " << structure_; + VERIFY_EQ(*expected, structure_); + } + return AssertionSuccess(); + } + + template <size_t N> + AssertionResult DecodeLeadingStructure(const char (&data)[N]) { + VERIFY_AND_RETURN_SUCCESS( + DecodeLeadingStructure(nullptr, StringPiece(data, N))); + } + + // Encode the structure |in_s| into bytes, then decode the bytes + // and validate that the decoder produced the same field values. + AssertionResult EncodeThenDecode(const S& in_s) { + string bytes = SerializeStructure(in_s); + VERIFY_EQ(S::EncodedSize(), bytes.size()); + VERIFY_AND_RETURN_SUCCESS(DecodeLeadingStructure(&in_s, bytes)); + } + + // Repeatedly fill a structure with random but valid contents, encode it, then + // decode it, and finally validate that the decoded structure matches the + // random input. Lather-rinse-and-repeat. + AssertionResult TestDecodingRandomizedStructures(size_t count) { + for (size_t i = 0; i < count; ++i) { + Structure input; + Randomize(&input, RandomPtr()); + VERIFY_SUCCESS(EncodeThenDecode(input)); + } + return AssertionSuccess(); + } + + AssertionResult TestDecodingRandomizedStructures() { + VERIFY_SUCCESS(TestDecodingRandomizedStructures(100)); + return AssertionSuccess(); + } + + uint32_t decode_offset_ = 0; + S structure_; + Http2StructureDecoder structure_decoder_; + size_t fast_decode_count_ = 0; + size_t slow_decode_count_ = 0; + size_t incomplete_start_count_ = 0; + size_t incomplete_resume_count_ = 0; +}; + +class Http2FrameHeaderDecoderTest + : public Http2StructureDecoderTest<Http2FrameHeader> {}; + +TEST_F(Http2FrameHeaderDecoderTest, DecodesLiteral) { + { + // Realistic input. + const char kData[] = { + 0x00, 0x00, 0x05, // Payload length: 5 + 0x01, // Frame type: HEADERS + 0x08, // Flags: PADDED + 0x00, 0x00, 0x00, 0x01, // Stream ID: 1 + 0x04, // Padding length: 4 + 0x00, 0x00, 0x00, 0x00, // Padding bytes + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(5u, structure_.payload_length); + EXPECT_EQ(Http2FrameType::HEADERS, structure_.type); + EXPECT_EQ(Http2FrameFlag::FLAG_PADDED, structure_.flags); + EXPECT_EQ(1u, structure_.stream_id); + } + { + // Unlikely input. + const char kData[] = { + 0xffu, 0xffu, 0xffu, // Payload length: uint24 max + 0xffu, // Frame type: Unknown + 0xffu, // Flags: Unknown/All + 0xffu, 0xffu, 0xffu, 0xffu, // Stream ID: uint31 max, plus R-bit + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ((1u << 24) - 1u, structure_.payload_length); + EXPECT_EQ(static_cast<Http2FrameType>(255), structure_.type); + EXPECT_EQ(255, structure_.flags); + EXPECT_EQ(0x7FFFFFFFu, structure_.stream_id); + } +} + +TEST_F(Http2FrameHeaderDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class Http2PriorityFieldsDecoderTest + : public Http2StructureDecoderTest<Http2PriorityFields> {}; + +TEST_F(Http2PriorityFieldsDecoderTest, DecodesLiteral) { + { + const char kData[] = { + 0x80u, 0x00, 0x00, 0x05, // Exclusive (yes) and Dependency (5) + 0xffu, // Weight: 256 (after adding 1) + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(5u, structure_.stream_dependency); + EXPECT_EQ(256u, structure_.weight); + EXPECT_EQ(true, structure_.is_exclusive); + } + { + const char kData[] = { + 0x7fu, 0xffu, + 0xffu, 0xffu, // Exclusive (no) and Dependency (0x7fffffff) + 0x00u, // Weight: 1 (after adding 1) + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(StreamIdMask(), structure_.stream_dependency); + EXPECT_EQ(1u, structure_.weight); + EXPECT_FALSE(structure_.is_exclusive); + } +} + +TEST_F(Http2PriorityFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class Http2RstStreamFieldsDecoderTest + : public Http2StructureDecoderTest<Http2RstStreamFields> {}; + +TEST_F(Http2RstStreamFieldsDecoderTest, DecodesLiteral) { + { + const char kData[] = { + 0x00, 0x00, 0x00, 0x01, // Error: PROTOCOL_ERROR + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_TRUE(structure_.IsSupportedErrorCode()); + EXPECT_EQ(Http2ErrorCode::PROTOCOL_ERROR, structure_.error_code); + } + { + const char kData[] = { + 0xffu, 0xffu, 0xffu, 0xffu, // Error: max uint32 (Unknown error code) + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_FALSE(structure_.IsSupportedErrorCode()); + EXPECT_EQ(static_cast<Http2ErrorCode>(0xffffffff), structure_.error_code); + } +} + +TEST_F(Http2RstStreamFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class Http2SettingFieldsDecoderTest + : public Http2StructureDecoderTest<Http2SettingFields> {}; + +TEST_F(Http2SettingFieldsDecoderTest, DecodesLiteral) { + { + const char kData[] = { + 0x00, 0x01, // Setting: HEADER_TABLE_SIZE + 0x00, 0x00, 0x40, 0x00, // Value: 16K + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_TRUE(structure_.IsSupportedParameter()); + EXPECT_EQ(Http2SettingsParameter::HEADER_TABLE_SIZE, structure_.parameter); + EXPECT_EQ(1u << 14, structure_.value); + } + { + const char kData[] = { + 0x00, 0x00, // Setting: Unknown (0) + 0xffu, 0xffu, 0xffu, 0xffu, // Value: max uint32 + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_FALSE(structure_.IsSupportedParameter()); + EXPECT_EQ(static_cast<Http2SettingsParameter>(0), structure_.parameter); + } +} + +TEST_F(Http2SettingFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class Http2PushPromiseFieldsDecoderTest + : public Http2StructureDecoderTest<Http2PushPromiseFields> {}; + +TEST_F(Http2PushPromiseFieldsDecoderTest, DecodesLiteral) { + { + const char kData[] = { + 0x00, 0x01, 0x8au, 0x92u, // Promised Stream ID: 101010 + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(101010u, structure_.promised_stream_id); + } + { + // Promised stream id has R-bit (reserved for future use) set, which + // should be cleared by the decoder. + const char kData[] = { + 0xffu, 0xffu, 0xffu, 0xffu, // Promised Stream ID: max uint31 and R-bit + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(StreamIdMask(), structure_.promised_stream_id); + } +} + +TEST_F(Http2PushPromiseFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class Http2PingFieldsDecoderTest + : public Http2StructureDecoderTest<Http2PingFields> {}; + +TEST_F(Http2PingFieldsDecoderTest, DecodesLiteral) { + { + // Each byte is different, so can detect if order changed. + const char kData[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(StringPiece(kData, 8), ToStringPiece(structure_.opaque_data)); + } + { + // All zeros, detect problems handling NULs. + const char kData[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(StringPiece(kData, 8), ToStringPiece(structure_.opaque_data)); + } + { + const char kData[] = { + 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, 0xffu, + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(StringPiece(kData, 8), ToStringPiece(structure_.opaque_data)); + } +} + +TEST_F(Http2PingFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class Http2GoAwayFieldsDecoderTest + : public Http2StructureDecoderTest<Http2GoAwayFields> {}; + +TEST_F(Http2GoAwayFieldsDecoderTest, DecodesLiteral) { + { + const char kData[] = { + 0x00, 0x00, 0x00, 0x00, // Last Stream ID: 0 + 0x00, 0x00, 0x00, 0x00, // Error: NO_ERROR (0) + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(0u, structure_.last_stream_id); + EXPECT_TRUE(structure_.IsSupportedErrorCode()); + EXPECT_EQ(Http2ErrorCode::HTTP2_NO_ERROR, structure_.error_code); + } + { + const char kData[] = { + 0x00, 0x00, 0x00, 0x01, // Last Stream ID: 1 + 0x00, 0x00, 0x00, 0x0d, // Error: HTTP_1_1_REQUIRED + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(1u, structure_.last_stream_id); + EXPECT_TRUE(structure_.IsSupportedErrorCode()); + EXPECT_EQ(Http2ErrorCode::HTTP_1_1_REQUIRED, structure_.error_code); + } + { + const char kData[] = { + 0xffu, 0xffu, 0xffu, 0xffu, // Last Stream ID: max uint31 and R-bit + 0xffu, 0xffu, 0xffu, 0xffu, // Error: max uint32 (Unknown error code) + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(StreamIdMask(), structure_.last_stream_id); // No high-bit. + EXPECT_FALSE(structure_.IsSupportedErrorCode()); + EXPECT_EQ(static_cast<Http2ErrorCode>(0xffffffff), structure_.error_code); + } +} + +TEST_F(Http2GoAwayFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class Http2WindowUpdateFieldsDecoderTest + : public Http2StructureDecoderTest<Http2WindowUpdateFields> {}; + +TEST_F(Http2WindowUpdateFieldsDecoderTest, DecodesLiteral) { + { + const char kData[] = { + 0x00, 0x01, 0x00, 0x00, // Window Size Increment: 2 ^ 16 + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(1u << 16, structure_.window_size_increment); + } + { + // Increment must be non-zero, but we need to be able to decode the invalid + // zero to detect it. + const char kData[] = { + 0x00, 0x00, 0x00, 0x00, // Window Size Increment: 0 + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(0u, structure_.window_size_increment); + } + { + // Increment has R-bit (reserved for future use) set, which + // should be cleared by the decoder. + const char kData[] = { + 0xffu, 0xffu, 0xffu, + 0xffu, // Window Size Increment: max uint31 and R-bit + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(StreamIdMask(), structure_.window_size_increment); + } +} + +TEST_F(Http2WindowUpdateFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +//------------------------------------------------------------------------------ + +class Http2AltSvcFieldsDecoderTest + : public Http2StructureDecoderTest<Http2AltSvcFields> {}; + +TEST_F(Http2AltSvcFieldsDecoderTest, DecodesLiteral) { + { + const char kData[] = { + 0x00, 0x00, // Origin Length: 0 + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(0, structure_.origin_length); + } + { + const char kData[] = { + 0x00, 0x14, // Origin Length: 20 + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(20, structure_.origin_length); + } + { + const char kData[] = { + 0xffu, 0xffu, // Origin Length: uint16 max + }; + ASSERT_TRUE(DecodeLeadingStructure(kData)); + EXPECT_EQ(65535, structure_.origin_length); + } +} + +TEST_F(Http2AltSvcFieldsDecoderTest, DecodesRandomized) { + TestDecodingRandomizedStructures(); +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/decoder/http2_structure_decoder_test_util.h b/net/http2/decoder/http2_structure_decoder_test_util.h new file mode 100644 index 0000000..b45211f7 --- /dev/null +++ b/net/http2/decoder/http2_structure_decoder_test_util.h
@@ -0,0 +1,30 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_HTTP2_STRUCTURE_DECODER_TEST_UTIL_H_ +#define NET_HTTP2_DECODER_HTTP2_STRUCTURE_DECODER_TEST_UTIL_H_ + +#include "net/http2/decoder/http2_structure_decoder.h" + +#include <cstddef> + +#include "net/http2/tools/http2_random.h" + +namespace net { +namespace test { + +class Http2StructureDecoderPeer { + public: + static void Randomize(Http2StructureDecoder* p, RandomBase* rng) { + p->offset_ = rng->Rand32(); + for (size_t i = 0; i < sizeof p->buffer_; ++i) { + p->buffer_[i] = rng->Rand8(); + } + } +}; + +} // namespace test +} // namespace net + +#endif // NET_HTTP2_DECODER_HTTP2_STRUCTURE_DECODER_TEST_UTIL_H_
diff --git a/net/http2/decoder/payload_decoders/altsvc_payload_decoder.cc b/net/http2/decoder/payload_decoders/altsvc_payload_decoder.cc new file mode 100644 index 0000000..fe0e8c2 --- /dev/null +++ b/net/http2/decoder/payload_decoders/altsvc_payload_decoder.cc
@@ -0,0 +1,143 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/altsvc_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures.h" +#include "net/http2/tools/http2_bug_tracker.h" + +namespace net { + +std::ostream& operator<<(std::ostream& out, + AltSvcPayloadDecoder::PayloadState v) { + switch (v) { + case AltSvcPayloadDecoder::PayloadState::kStartDecodingStruct: + return out << "kStartDecodingStruct"; + case AltSvcPayloadDecoder::PayloadState::kMaybeDecodedStruct: + return out << "kMaybeDecodedStruct"; + case AltSvcPayloadDecoder::PayloadState::kDecodingStrings: + return out << "kDecodingStrings"; + case AltSvcPayloadDecoder::PayloadState::kResumeDecodingStruct: + return out << "kResumeDecodingStruct"; + } + return out << static_cast<int>(v); +} + +DecodeStatus AltSvcPayloadDecoder::StartDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "AltSvcPayloadDecoder::StartDecodingPayload: " + << state->frame_header(); + DCHECK_EQ(Http2FrameType::ALTSVC, state->frame_header().type); + DCHECK_LE(db->Remaining(), state->frame_header().payload_length); + DCHECK_EQ(0, state->frame_header().flags); + + state->InitializeRemainders(); + payload_state_ = PayloadState::kStartDecodingStruct; + + return ResumeDecodingPayload(state, db); +} + +DecodeStatus AltSvcPayloadDecoder::ResumeDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + const Http2FrameHeader& frame_header = state->frame_header(); + DVLOG(2) << "AltSvcPayloadDecoder::ResumeDecodingPayload: " << frame_header; + DCHECK_EQ(Http2FrameType::ALTSVC, frame_header.type); + DCHECK_LE(state->remaining_payload(), frame_header.payload_length); + DCHECK_LE(db->Remaining(), state->remaining_payload()); + DCHECK_NE(PayloadState::kMaybeDecodedStruct, payload_state_); + // |status| has to be initialized to some value to avoid compiler error in + // case PayloadState::kMaybeDecodedStruct below, but value does not matter, + // see DCHECK_NE above. + DecodeStatus status = DecodeStatus::kDecodeError; + while (true) { + DVLOG(2) << "AltSvcPayloadDecoder::ResumeDecodingPayload payload_state_=" + << payload_state_; + switch (payload_state_) { + case PayloadState::kStartDecodingStruct: + status = state->StartDecodingStructureInPayload(&altsvc_fields_, db); + // FALLTHROUGH_INTENDED + + case PayloadState::kMaybeDecodedStruct: + if (status == DecodeStatus::kDecodeDone && + altsvc_fields_.origin_length <= state->remaining_payload()) { + size_t origin_length = altsvc_fields_.origin_length; + size_t value_length = state->remaining_payload() - origin_length; + state->listener()->OnAltSvcStart(frame_header, origin_length, + value_length); + } else if (status != DecodeStatus::kDecodeDone) { + DCHECK(state->remaining_payload() > 0 || + status == DecodeStatus::kDecodeError) + << "\nremaining_payload: " << state->remaining_payload() + << "\nstatus: " << status << "\nheader: " << frame_header; + // Assume in progress. + payload_state_ = PayloadState::kResumeDecodingStruct; + return status; + } else { + // The origin's length is longer than the remaining payload. + DCHECK_GT(altsvc_fields_.origin_length, state->remaining_payload()); + return state->ReportFrameSizeError(); + } + // FALLTHROUGH_INTENDED + + case PayloadState::kDecodingStrings: + return DecodeStrings(state, db); + + case PayloadState::kResumeDecodingStruct: + status = state->ResumeDecodingStructureInPayload(&altsvc_fields_, db); + payload_state_ = PayloadState::kMaybeDecodedStruct; + continue; + } + HTTP2_BUG << "PayloadState: " << payload_state_; + } +} + +DecodeStatus AltSvcPayloadDecoder::DecodeStrings(FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "AltSvcPayloadDecoder::DecodeStrings remaining_payload=" + << state->remaining_payload() + << ", db->Remaining=" << db->Remaining(); + // Note that we don't explicitly keep track of exactly how far through the + // origin; instead we compute it from how much is left of the original + // payload length and the decoded total length of the origin. + size_t origin_length = altsvc_fields_.origin_length; + size_t value_length = state->frame_header().payload_length - origin_length - + Http2AltSvcFields::EncodedSize(); + if (state->remaining_payload() > value_length) { + size_t remaining_origin_length = state->remaining_payload() - value_length; + size_t avail = db->MinLengthRemaining(remaining_origin_length); + state->listener()->OnAltSvcOriginData(db->cursor(), avail); + db->AdvanceCursor(avail); + state->ConsumePayload(avail); + if (remaining_origin_length > avail) { + payload_state_ = PayloadState::kDecodingStrings; + return DecodeStatus::kDecodeInProgress; + } + } + // All that is left is the value string. + DCHECK_LE(state->remaining_payload(), value_length); + DCHECK_LE(db->Remaining(), state->remaining_payload()); + if (db->HasData()) { + size_t avail = db->Remaining(); + state->listener()->OnAltSvcValueData(db->cursor(), avail); + db->AdvanceCursor(avail); + state->ConsumePayload(avail); + } + if (state->remaining_payload() == 0) { + state->listener()->OnAltSvcEnd(); + return DecodeStatus::kDecodeDone; + } + payload_state_ = PayloadState::kDecodingStrings; + return DecodeStatus::kDecodeInProgress; +} + +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/altsvc_payload_decoder.h b/net/http2/decoder/payload_decoders/altsvc_payload_decoder.h new file mode 100644 index 0000000..f92fa7f9 --- /dev/null +++ b/net/http2/decoder/payload_decoders/altsvc_payload_decoder.h
@@ -0,0 +1,64 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_PAYLOAD_DECODERS_ALTSVC_PAYLOAD_DECODER_H_ +#define NET_HTTP2_DECODER_PAYLOAD_DECODERS_ALTSVC_PAYLOAD_DECODER_H_ + +// Decodes the payload of a ALTSVC frame. + +#include "net/base/net_export.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/decoder/frame_decoder_state.h" +#include "net/http2/http2_structures.h" + +namespace net { +namespace test { +class AltSvcPayloadDecoderPeer; +} // namespace test + +class NET_EXPORT_PRIVATE AltSvcPayloadDecoder { + public: + // States during decoding of a ALTSVC frame. + enum class PayloadState { + // Start decoding the fixed size structure at the start of an ALTSVC + // frame (Http2AltSvcFields). + kStartDecodingStruct, + + // Handle the DecodeStatus returned from starting or resuming the + // decoding of Http2AltSvcFields. If complete, calls OnAltSvcStart. + kMaybeDecodedStruct, + + // Reports the value of the strings (origin and value) of an ALTSVC frame + // to the listener. + kDecodingStrings, + + // The initial decode buffer wasn't large enough for the Http2AltSvcFields, + // so this state resumes the decoding when ResumeDecodingPayload is called + // later with a new DecodeBuffer. + kResumeDecodingStruct, + }; + + // Starts the decoding of a ALTSVC frame's payload, and completes it if the + // entire payload is in the provided decode buffer. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a ALTSVC frame's payload that has been split across + // decode buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); + + private: + friend class test::AltSvcPayloadDecoderPeer; + + // Implements state kDecodingStrings. + DecodeStatus DecodeStrings(FrameDecoderState* state, DecodeBuffer* db); + + Http2AltSvcFields altsvc_fields_; + PayloadState payload_state_; +}; + +} // namespace net + +#endif // NET_HTTP2_DECODER_PAYLOAD_DECODERS_ALTSVC_PAYLOAD_DECODER_H_
diff --git a/net/http2/decoder/payload_decoders/altsvc_payload_decoder_test.cc b/net/http2/decoder/payload_decoders/altsvc_payload_decoder_test.cc new file mode 100644 index 0000000..e10fc50 --- /dev/null +++ b/net/http2/decoder/payload_decoders/altsvc_payload_decoder_test.cc
@@ -0,0 +1,133 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/altsvc_payload_decoder.h" + +#include <stddef.h> + +#include <string> + +#include "base/bind.h" +#include "base/logging.h" +#include "net/http2/decoder/frame_parts.h" +#include "net/http2/decoder/frame_parts_collector.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures_test_util.h" +#include "net/http2/tools/http2_frame_builder.h" +#include "net/http2/tools/http2_random.h" +#include "net/http2/tools/random_decoder_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +using std::string; + +namespace net { +namespace test { + +// Provides friend access to an instance of the payload decoder, and also +// provides info to aid in testing. +class AltSvcPayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { return Http2FrameType::ALTSVC; } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; } + + static void Randomize(AltSvcPayloadDecoder* p, RandomBase* rng) { + CorruptEnum(&p->payload_state_, rng); + test::Randomize(&p->altsvc_fields_, rng); + VLOG(1) << "AltSvcPayloadDecoderPeer::Randomize altsvc_fields_=" + << p->altsvc_fields_; + } +}; + +namespace { + +struct Listener : public FramePartsCollector { + void OnAltSvcStart(const Http2FrameHeader& header, + size_t origin_length, + size_t value_length) override { + VLOG(1) << "OnAltSvcStart header: " << header + << "; origin_length=" << origin_length + << "; value_length=" << value_length; + StartFrame(header)->OnAltSvcStart(header, origin_length, value_length); + } + + void OnAltSvcOriginData(const char* data, size_t len) override { + VLOG(1) << "OnAltSvcOriginData: len=" << len; + CurrentFrame()->OnAltSvcOriginData(data, len); + } + + void OnAltSvcValueData(const char* data, size_t len) override { + VLOG(1) << "OnAltSvcValueData: len=" << len; + CurrentFrame()->OnAltSvcValueData(data, len); + } + + void OnAltSvcEnd() override { + VLOG(1) << "OnAltSvcEnd"; + EndFrame()->OnAltSvcEnd(); + } + + void OnFrameSizeError(const Http2FrameHeader& header) override { + VLOG(1) << "OnFrameSizeError: " << header; + FrameError(header)->OnFrameSizeError(header); + } +}; + +class AltSvcPayloadDecoderTest + : public AbstractPayloadDecoderTest<AltSvcPayloadDecoder, + AltSvcPayloadDecoderPeer, + Listener> {}; + +// Confirm we get an error if the payload is not long enough to hold +// Http2AltSvcFields and the indicated length of origin. +TEST_F(AltSvcPayloadDecoderTest, Truncated) { + Http2FrameBuilder fb; + fb.Append(Http2AltSvcFields{0xffff}); // The longest possible origin length. + fb.Append("Too little origin!"); + EXPECT_TRUE(VerifyDetectsFrameSizeError( + 0, fb.buffer(), + base::Bind(&AbstractPayloadDecoderTest::SucceedingApproveSize))); +} + +class AltSvcPayloadLengthTests : public AltSvcPayloadDecoderTest, + public ::testing::WithParamInterface< + ::testing::tuple<uint16_t, uint32_t>> { + protected: + AltSvcPayloadLengthTests() + : origin_length_(::testing::get<0>(GetParam())), + value_length_(::testing::get<1>(GetParam())) { + VLOG(1) << "################ origin_length_=" << origin_length_ + << " value_length_=" << value_length_ << " ################"; + } + + const uint16_t origin_length_; + const uint32_t value_length_; +}; + +INSTANTIATE_TEST_CASE_P(VariousOriginAndValueLengths, + AltSvcPayloadLengthTests, + ::testing::Combine(::testing::Values(0, 1, 3, 65535), + ::testing::Values(0, 1, 3, 65537))); + +TEST_P(AltSvcPayloadLengthTests, ValidOriginAndValueLength) { + string origin = Random().RandString(origin_length_); + string value = Random().RandString(value_length_); + Http2FrameBuilder fb; + fb.Append(Http2AltSvcFields{origin_length_}); + fb.Append(origin); + fb.Append(value); + Http2FrameHeader header(fb.size(), Http2FrameType::ALTSVC, RandFlags(), + RandStreamId()); + set_frame_header(header); + FrameParts expected(header); + expected.SetAltSvcExpected(origin, value); + ASSERT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected)); +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/continuation_payload_decoder.cc b/net/http2/decoder/payload_decoders/continuation_payload_decoder.cc new file mode 100644 index 0000000..ac5371c0 --- /dev/null +++ b/net/http2/decoder/payload_decoders/continuation_payload_decoder.cc
@@ -0,0 +1,58 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/continuation_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures.h" + +namespace net { + +DecodeStatus ContinuationPayloadDecoder::StartDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + const Http2FrameHeader& frame_header = state->frame_header(); + const uint32_t total_length = frame_header.payload_length; + + DVLOG(2) << "ContinuationPayloadDecoder::StartDecodingPayload: " + << frame_header; + DCHECK_EQ(Http2FrameType::CONTINUATION, frame_header.type); + DCHECK_LE(db->Remaining(), total_length); + DCHECK_EQ(0, frame_header.flags & ~(Http2FrameFlag::FLAG_END_HEADERS)); + + state->InitializeRemainders(); + state->listener()->OnContinuationStart(frame_header); + return ResumeDecodingPayload(state, db); +} + +DecodeStatus ContinuationPayloadDecoder::ResumeDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "ContinuationPayloadDecoder::ResumeDecodingPayload" + << " remaining_payload=" << state->remaining_payload() + << " db->Remaining=" << db->Remaining(); + DCHECK_EQ(Http2FrameType::CONTINUATION, state->frame_header().type); + DCHECK_LE(state->remaining_payload(), state->frame_header().payload_length); + DCHECK_LE(db->Remaining(), state->remaining_payload()); + + size_t avail = db->Remaining(); + DCHECK_LE(avail, state->remaining_payload()); + if (avail > 0) { + state->listener()->OnHpackFragment(db->cursor(), avail); + db->AdvanceCursor(avail); + state->ConsumePayload(avail); + } + if (state->remaining_payload() == 0) { + state->listener()->OnContinuationEnd(); + return DecodeStatus::kDecodeDone; + } + return DecodeStatus::kDecodeInProgress; +} + +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/continuation_payload_decoder.h b/net/http2/decoder/payload_decoders/continuation_payload_decoder.h new file mode 100644 index 0000000..4ab58aa --- /dev/null +++ b/net/http2/decoder/payload_decoders/continuation_payload_decoder.h
@@ -0,0 +1,31 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_PAYLOAD_DECODERS_CONTINUATION_PAYLOAD_DECODER_H_ +#define NET_HTTP2_DECODER_PAYLOAD_DECODERS_CONTINUATION_PAYLOAD_DECODER_H_ + +// Decodes the payload of a CONTINUATION frame. + +#include "net/base/net_export.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/decoder/frame_decoder_state.h" + +namespace net { + +class NET_EXPORT_PRIVATE ContinuationPayloadDecoder { + public: + // Starts the decoding of a CONTINUATION frame's payload, and completes + // it if the entire payload is in the provided decode buffer. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a CONTINUATION frame's payload that has been split across + // decode buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); +}; + +} // namespace net + +#endif // NET_HTTP2_DECODER_PAYLOAD_DECODERS_CONTINUATION_PAYLOAD_DECODER_H_
diff --git a/net/http2/decoder/payload_decoders/continuation_payload_decoder_test.cc b/net/http2/decoder/payload_decoders/continuation_payload_decoder_test.cc new file mode 100644 index 0000000..3244039 --- /dev/null +++ b/net/http2/decoder/payload_decoders/continuation_payload_decoder_test.cc
@@ -0,0 +1,94 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/continuation_payload_decoder.h" + +#include <stddef.h> + +#include <string> +#include <type_traits> + +#include "base/logging.h" +#include "net/http2/decoder/frame_parts.h" +#include "net/http2/decoder/frame_parts_collector.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures.h" +#include "net/http2/tools/random_decoder_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +using std::string; + +namespace net { +namespace test { + +// Provides friend access to an instance of the payload decoder, and also +// provides info to aid in testing. +class ContinuationPayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { + return Http2FrameType::CONTINUATION; + } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; } + + static void Randomize(ContinuationPayloadDecoder* p, RandomBase* rng) { + // ContinuationPayloadDecoder has no fields, + // so there is nothing to randomize. + static_assert(std::is_empty<ContinuationPayloadDecoder>::value, + "Need to randomize fields of ContinuationPayloadDecoder"); + } +}; + +namespace { + +struct Listener : public FramePartsCollector { + void OnContinuationStart(const Http2FrameHeader& header) override { + VLOG(1) << "OnContinuationStart: " << header; + StartFrame(header)->OnContinuationStart(header); + } + + void OnHpackFragment(const char* data, size_t len) override { + VLOG(1) << "OnHpackFragment: len=" << len; + CurrentFrame()->OnHpackFragment(data, len); + } + + void OnContinuationEnd() override { + VLOG(1) << "OnContinuationEnd"; + EndFrame()->OnContinuationEnd(); + } +}; + +class ContinuationPayloadDecoderTest + : public AbstractPayloadDecoderTest<ContinuationPayloadDecoder, + ContinuationPayloadDecoderPeer, + Listener>, + public ::testing::WithParamInterface<uint32_t> { + protected: + ContinuationPayloadDecoderTest() : length_(GetParam()) { + VLOG(1) << "################ length_=" << length_ << " ################"; + } + + const uint32_t length_; +}; + +INSTANTIATE_TEST_CASE_P(VariousLengths, + ContinuationPayloadDecoderTest, + ::testing::Values(0, 1, 2, 3, 4, 5, 6)); + +TEST_P(ContinuationPayloadDecoderTest, ValidLength) { + string hpack_payload = Random().RandString(length_); + Http2FrameHeader frame_header(length_, Http2FrameType::CONTINUATION, + RandFlags(), RandStreamId()); + set_frame_header(frame_header); + FrameParts expected(frame_header, hpack_payload); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(hpack_payload, expected)); +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/data_payload_decoder.cc b/net/http2/decoder/payload_decoders/data_payload_decoder.cc new file mode 100644 index 0000000..60b8a714 --- /dev/null +++ b/net/http2/decoder/payload_decoders/data_payload_decoder.cc
@@ -0,0 +1,123 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/data_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures.h" +#include "net/http2/tools/http2_bug_tracker.h" + +namespace net { + +std::ostream& operator<<(std::ostream& out, + DataPayloadDecoder::PayloadState v) { + switch (v) { + case DataPayloadDecoder::PayloadState::kReadPadLength: + return out << "kReadPadLength"; + case DataPayloadDecoder::PayloadState::kReadPayload: + return out << "kReadPayload"; + case DataPayloadDecoder::PayloadState::kSkipPadding: + return out << "kSkipPadding"; + } + return out << static_cast<int>(v); +} + +DecodeStatus DataPayloadDecoder::StartDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db) { + const Http2FrameHeader& frame_header = state->frame_header(); + const uint32_t total_length = frame_header.payload_length; + + DVLOG(2) << "DataPayloadDecoder::StartDecodingPayload: " << frame_header; + DCHECK_EQ(Http2FrameType::DATA, frame_header.type); + DCHECK_LE(db->Remaining(), total_length); + DCHECK_EQ( + 0, frame_header.flags & + ~(Http2FrameFlag::FLAG_END_STREAM | Http2FrameFlag::FLAG_PADDED)); + + // Special case for the hoped for common case: unpadded and fits fully into + // the decode buffer. TO BE SEEN if that is true. It certainly requires that + // the transport buffers be large (e.g. >> 16KB typically). + // TODO(jamessynge) Add counters. + DVLOG(2) << "StartDecodingPayload total_length=" << total_length; + if (!frame_header.IsPadded()) { + DVLOG(2) << "StartDecodingPayload !IsPadded"; + if (db->Remaining() == total_length) { + DVLOG(2) << "StartDecodingPayload all present"; + // Note that we don't cache the listener field so that the callee can + // replace it if the frame is bad. + // If this case is common enough, consider combining the 3 callbacks + // into one. + state->listener()->OnDataStart(frame_header); + if (total_length > 0) { + state->listener()->OnDataPayload(db->cursor(), total_length); + db->AdvanceCursor(total_length); + } + state->listener()->OnDataEnd(); + return DecodeStatus::kDecodeDone; + } + payload_state_ = PayloadState::kReadPayload; + } else { + payload_state_ = PayloadState::kReadPadLength; + } + state->InitializeRemainders(); + state->listener()->OnDataStart(frame_header); + return ResumeDecodingPayload(state, db); +} + +DecodeStatus DataPayloadDecoder::ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "DataPayloadDecoder::ResumeDecodingPayload payload_state_=" + << payload_state_; + const Http2FrameHeader& frame_header = state->frame_header(); + DCHECK_EQ(Http2FrameType::DATA, frame_header.type); + DCHECK_LE(state->remaining_payload_and_padding(), + frame_header.payload_length); + DCHECK_LE(db->Remaining(), state->remaining_payload_and_padding()); + DecodeStatus status; + size_t avail; + switch (payload_state_) { + case PayloadState::kReadPadLength: + // ReadPadLength handles the OnPadLength callback, and updating the + // remaining_payload and remaining_padding fields. If the amount of + // padding is too large to fit in the frame's payload, ReadPadLength + // instead calls OnPaddingTooLong and returns kDecodeError. + status = state->ReadPadLength(db, /*report_pad_length*/ true); + if (status != DecodeStatus::kDecodeDone) { + return status; + } + // FALLTHROUGH_INTENDED + + case PayloadState::kReadPayload: + avail = state->AvailablePayload(db); + if (avail > 0) { + state->listener()->OnDataPayload(db->cursor(), avail); + db->AdvanceCursor(avail); + state->ConsumePayload(avail); + } + if (state->remaining_payload() > 0) { + payload_state_ = PayloadState::kReadPayload; + return DecodeStatus::kDecodeInProgress; + } + // FALLTHROUGH_INTENDED + + case PayloadState::kSkipPadding: + // SkipPadding handles the OnPadding callback. + if (state->SkipPadding(db)) { + state->listener()->OnDataEnd(); + return DecodeStatus::kDecodeDone; + } + payload_state_ = PayloadState::kSkipPadding; + return DecodeStatus::kDecodeInProgress; + } + HTTP2_BUG << "PayloadState: " << payload_state_; + return DecodeStatus::kDecodeError; +} + +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/data_payload_decoder.h b/net/http2/decoder/payload_decoders/data_payload_decoder.h new file mode 100644 index 0000000..2fe09596 --- /dev/null +++ b/net/http2/decoder/payload_decoders/data_payload_decoder.h
@@ -0,0 +1,54 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_PAYLOAD_DECODERS_DATA_PAYLOAD_DECODER_H_ +#define NET_HTTP2_DECODER_PAYLOAD_DECODERS_DATA_PAYLOAD_DECODER_H_ + +// Decodes the payload of a DATA frame. + +#include "net/base/net_export.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/decoder/frame_decoder_state.h" + +namespace net { +namespace test { +class DataPayloadDecoderPeer; +} // namespace test + +class NET_EXPORT_PRIVATE DataPayloadDecoder { + public: + // States during decoding of a DATA frame. + enum class PayloadState { + // The frame is padded and we need to read the PAD_LENGTH field (1 byte), + // and then call OnPadLength + kReadPadLength, + + // Report the non-padding portion of the payload to the listener's + // OnDataPayload method. + kReadPayload, + + // The decoder has finished with the non-padding portion of the payload, + // and is now ready to skip the trailing padding, if the frame has any. + kSkipPadding, + }; + + // Starts decoding a DATA frame's payload, and completes it if + // the entire payload is in the provided decode buffer. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a DATA frame's payload that has been split across + // decode buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); + + private: + friend class test::DataPayloadDecoderPeer; + + PayloadState payload_state_; +}; + +} // namespace net + +#endif // NET_HTTP2_DECODER_PAYLOAD_DECODERS_DATA_PAYLOAD_DECODER_H_
diff --git a/net/http2/decoder/payload_decoders/data_payload_decoder_test.cc b/net/http2/decoder/payload_decoders/data_payload_decoder_test.cc new file mode 100644 index 0000000..bedb32f --- /dev/null +++ b/net/http2/decoder/payload_decoders/data_payload_decoder_test.cc
@@ -0,0 +1,120 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/data_payload_decoder.h" + +#include <stddef.h> + +#include <string> + +#include "base/logging.h" +#include "net/http2/decoder/frame_parts.h" +#include "net/http2/decoder/frame_parts_collector.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures.h" +#include "net/http2/http2_structures_test_util.h" +#include "net/http2/tools/failure.h" +#include "net/http2/tools/http2_frame_builder.h" +#include "net/http2/tools/http2_random.h" +#include "net/http2/tools/random_decoder_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::AssertionResult; +using std::string; + +namespace net { +namespace test { + +// Provides friend access to an instance of the payload decoder, and also +// provides info to aid in testing. +class DataPayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { return Http2FrameType::DATA; } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { + return Http2FrameFlag::FLAG_PADDED; + } + + static void Randomize(DataPayloadDecoder* p, RandomBase* rng) { + VLOG(1) << "DataPayloadDecoderPeer::Randomize"; + CorruptEnum(&p->payload_state_, rng); + } +}; + +namespace { + +struct Listener : public FramePartsCollector { + void OnDataStart(const Http2FrameHeader& header) override { + VLOG(1) << "OnDataStart: " << header; + StartFrame(header)->OnDataStart(header); + } + + void OnDataPayload(const char* data, size_t len) override { + VLOG(1) << "OnDataPayload: len=" << len; + CurrentFrame()->OnDataPayload(data, len); + } + + void OnDataEnd() override { + VLOG(1) << "OnDataEnd"; + EndFrame()->OnDataEnd(); + } + + void OnPadLength(size_t pad_length) override { + VLOG(1) << "OnPadLength: " << pad_length; + CurrentFrame()->OnPadLength(pad_length); + } + + void OnPadding(const char* padding, size_t skipped_length) override { + VLOG(1) << "OnPadding: " << skipped_length; + CurrentFrame()->OnPadding(padding, skipped_length); + } + + void OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) override { + VLOG(1) << "OnPaddingTooLong: " << header + << " missing_length: " << missing_length; + EndFrame()->OnPaddingTooLong(header, missing_length); + } +}; + +class DataPayloadDecoderTest + : public AbstractPaddablePayloadDecoderTest<DataPayloadDecoder, + DataPayloadDecoderPeer, + Listener> { + protected: + AssertionResult CreateAndDecodeDataOfSize(size_t data_size) { + Reset(); + uint8_t flags = RandFlags(); + + string data_payload = Random().RandString(data_size); + frame_builder_.Append(data_payload); + MaybeAppendTrailingPadding(); + + Http2FrameHeader frame_header(frame_builder_.size(), Http2FrameType::DATA, + flags, RandStreamId()); + set_frame_header(frame_header); + ScrubFlagsOfHeader(&frame_header); + FrameParts expected(frame_header, data_payload, total_pad_length_); + VERIFY_AND_RETURN_SUCCESS( + DecodePayloadAndValidateSeveralWays(frame_builder_.buffer(), expected)); + } +}; + +INSTANTIATE_TEST_CASE_P(VariousPadLengths, + DataPayloadDecoderTest, + ::testing::Values(0, 1, 2, 3, 4, 254, 255, 256)); + +TEST_P(DataPayloadDecoderTest, VariousDataPayloadSizes) { + for (size_t data_size : {0, 1, 2, 3, 255, 256, 1024}) { + EXPECT_TRUE(CreateAndDecodeDataOfSize(data_size)); + } +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/goaway_payload_decoder.cc b/net/http2/decoder/payload_decoders/goaway_payload_decoder.cc new file mode 100644 index 0000000..f5b597f --- /dev/null +++ b/net/http2/decoder/payload_decoders/goaway_payload_decoder.cc
@@ -0,0 +1,118 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/goaway_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures.h" +#include "net/http2/tools/http2_bug_tracker.h" + +namespace net { + +std::ostream& operator<<(std::ostream& out, + GoAwayPayloadDecoder::PayloadState v) { + switch (v) { + case GoAwayPayloadDecoder::PayloadState::kStartDecodingFixedFields: + return out << "kStartDecodingFixedFields"; + case GoAwayPayloadDecoder::PayloadState::kHandleFixedFieldsStatus: + return out << "kHandleFixedFieldsStatus"; + case GoAwayPayloadDecoder::PayloadState::kReadOpaqueData: + return out << "kReadOpaqueData"; + case GoAwayPayloadDecoder::PayloadState::kResumeDecodingFixedFields: + return out << "kResumeDecodingFixedFields"; + } + + NOTREACHED(); + return out; +} + +DecodeStatus GoAwayPayloadDecoder::StartDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "GoAwayPayloadDecoder::StartDecodingPayload: " + << state->frame_header(); + DCHECK_EQ(Http2FrameType::GOAWAY, state->frame_header().type); + DCHECK_LE(db->Remaining(), state->frame_header().payload_length); + DCHECK_EQ(0, state->frame_header().flags); + + state->InitializeRemainders(); + payload_state_ = PayloadState::kStartDecodingFixedFields; + return ResumeDecodingPayload(state, db); +} + +DecodeStatus GoAwayPayloadDecoder::ResumeDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "GoAwayPayloadDecoder::ResumeDecodingPayload: remaining_payload=" + << state->remaining_payload() + << ", db->Remaining=" << db->Remaining(); + + const Http2FrameHeader& frame_header = state->frame_header(); + DCHECK_EQ(Http2FrameType::GOAWAY, frame_header.type); + DCHECK_LE(db->Remaining(), frame_header.payload_length); + DCHECK_NE(PayloadState::kHandleFixedFieldsStatus, payload_state_); + + // |status| has to be initialized to some value to avoid compiler error in + // case PayloadState::kHandleFixedFieldsStatus below, but value does not + // matter, see DCHECK_NE above. + DecodeStatus status = DecodeStatus::kDecodeError; + size_t avail; + while (true) { + DVLOG(2) << "GoAwayPayloadDecoder::ResumeDecodingPayload payload_state_=" + << payload_state_; + switch (payload_state_) { + case PayloadState::kStartDecodingFixedFields: + status = state->StartDecodingStructureInPayload(&goaway_fields_, db); + // FALLTHROUGH_INTENDED + + case PayloadState::kHandleFixedFieldsStatus: + if (status == DecodeStatus::kDecodeDone) { + state->listener()->OnGoAwayStart(frame_header, goaway_fields_); + } else { + // Not done decoding the structure. Either we've got more payload + // to decode, or we've run out because the payload is too short, + // in which case OnFrameSizeError will have already been called. + DCHECK((status == DecodeStatus::kDecodeInProgress && + state->remaining_payload() > 0) || + (status == DecodeStatus::kDecodeError && + state->remaining_payload() == 0)) + << "\n status=" << status + << "; remaining_payload=" << state->remaining_payload(); + payload_state_ = PayloadState::kResumeDecodingFixedFields; + return status; + } + // FALLTHROUGH_INTENDED + + case PayloadState::kReadOpaqueData: + // The opaque data is all the remains to be decoded, so anything left + // in the decode buffer is opaque data. + avail = db->Remaining(); + if (avail > 0) { + state->listener()->OnGoAwayOpaqueData(db->cursor(), avail); + db->AdvanceCursor(avail); + state->ConsumePayload(avail); + } + if (state->remaining_payload() > 0) { + payload_state_ = PayloadState::kReadOpaqueData; + return DecodeStatus::kDecodeInProgress; + } + state->listener()->OnGoAwayEnd(); + return DecodeStatus::kDecodeDone; + + case PayloadState::kResumeDecodingFixedFields: + status = state->ResumeDecodingStructureInPayload(&goaway_fields_, db); + payload_state_ = PayloadState::kHandleFixedFieldsStatus; + continue; + } + HTTP2_BUG << "PayloadState: " << payload_state_; + } +} + +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/goaway_payload_decoder.h b/net/http2/decoder/payload_decoders/goaway_payload_decoder.h new file mode 100644 index 0000000..5aefc6c --- /dev/null +++ b/net/http2/decoder/payload_decoders/goaway_payload_decoder.h
@@ -0,0 +1,66 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_PAYLOAD_DECODERS_GOAWAY_PAYLOAD_DECODER_H_ +#define NET_HTTP2_DECODER_PAYLOAD_DECODERS_GOAWAY_PAYLOAD_DECODER_H_ + +// Decodes the payload of a GOAWAY frame. + +// TODO(jamessynge): Sweep through all payload decoders, changing the names of +// the PayloadState enums so that they are really states, and not actions. + +#include "net/base/net_export.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/decoder/frame_decoder_state.h" +#include "net/http2/http2_structures.h" + +namespace net { +namespace test { +class GoAwayPayloadDecoderPeer; +} // namespace test + +class NET_EXPORT_PRIVATE GoAwayPayloadDecoder { + public: + // States during decoding of a GOAWAY frame. + enum class PayloadState { + // At the start of the GOAWAY frame payload, ready to start decoding the + // fixed size fields into goaway_fields_. + kStartDecodingFixedFields, + + // Handle the DecodeStatus returned from starting or resuming the + // decoding of Http2GoAwayFields into goaway_fields_. If complete, + // calls OnGoAwayStart. + kHandleFixedFieldsStatus, + + // Report the Opaque Data portion of the payload to the listener's + // OnGoAwayOpaqueData method, and call OnGoAwayEnd when the end of the + // payload is reached. + kReadOpaqueData, + + // The fixed size fields weren't all available when the decoder first + // tried to decode them (state kStartDecodingFixedFields); this state + // resumes the decoding when ResumeDecodingPayload is called later. + kResumeDecodingFixedFields, + }; + + // Starts the decoding of a GOAWAY frame's payload, and completes it if + // the entire payload is in the provided decode buffer. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a GOAWAY frame's payload that has been split across + // decode buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); + + private: + friend class test::GoAwayPayloadDecoderPeer; + + Http2GoAwayFields goaway_fields_; + PayloadState payload_state_; +}; + +} // namespace net + +#endif // NET_HTTP2_DECODER_PAYLOAD_DECODERS_GOAWAY_PAYLOAD_DECODER_H_
diff --git a/net/http2/decoder/payload_decoders/goaway_payload_decoder_test.cc b/net/http2/decoder/payload_decoders/goaway_payload_decoder_test.cc new file mode 100644 index 0000000..248ac0d --- /dev/null +++ b/net/http2/decoder/payload_decoders/goaway_payload_decoder_test.cc
@@ -0,0 +1,122 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/goaway_payload_decoder.h" + +#include <stddef.h> + +#include <string> + +#include "base/bind.h" +#include "base/logging.h" +#include "net/http2/decoder/frame_parts.h" +#include "net/http2/decoder/frame_parts_collector.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures_test_util.h" +#include "net/http2/tools/http2_frame_builder.h" +#include "net/http2/tools/http2_random.h" +#include "net/http2/tools/random_decoder_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +using std::string; + +namespace net { +namespace test { + +class GoAwayPayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { return Http2FrameType::GOAWAY; } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; } + + static void Randomize(GoAwayPayloadDecoder* p, RandomBase* rng) { + CorruptEnum(&p->payload_state_, rng); + test::Randomize(&p->goaway_fields_, rng); + VLOG(1) << "GoAwayPayloadDecoderPeer::Randomize goaway_fields: " + << p->goaway_fields_; + } +}; + +namespace { + +struct Listener : public FramePartsCollector { + void OnGoAwayStart(const Http2FrameHeader& header, + const Http2GoAwayFields& goaway) override { + VLOG(1) << "OnGoAwayStart header: " << header << "; goaway: " << goaway; + StartFrame(header)->OnGoAwayStart(header, goaway); + } + + void OnGoAwayOpaqueData(const char* data, size_t len) override { + VLOG(1) << "OnGoAwayOpaqueData: len=" << len; + CurrentFrame()->OnGoAwayOpaqueData(data, len); + } + + void OnGoAwayEnd() override { + VLOG(1) << "OnGoAwayEnd"; + EndFrame()->OnGoAwayEnd(); + } + + void OnFrameSizeError(const Http2FrameHeader& header) override { + VLOG(1) << "OnFrameSizeError: " << header; + FrameError(header)->OnFrameSizeError(header); + } +}; + +class GoAwayPayloadDecoderTest + : public AbstractPayloadDecoderTest<GoAwayPayloadDecoder, + GoAwayPayloadDecoderPeer, + Listener> { + public: + static bool ApproveSizeForTruncated(size_t size) { + return size != Http2GoAwayFields::EncodedSize(); + } +}; + +// Confirm we get an error if the payload is not long enough to hold +// Http2GoAwayFields. +TEST_F(GoAwayPayloadDecoderTest, Truncated) { + Http2FrameBuilder fb; + fb.Append(Http2GoAwayFields(123, Http2ErrorCode::ENHANCE_YOUR_CALM)); + EXPECT_TRUE(VerifyDetectsFrameSizeError( + 0, fb.buffer(), + base::Bind(&GoAwayPayloadDecoderTest::ApproveSizeForTruncated))); +} + +class GoAwayOpaqueDataLengthTests + : public GoAwayPayloadDecoderTest, + public ::testing::WithParamInterface<uint32_t> { + protected: + GoAwayOpaqueDataLengthTests() : length_(GetParam()) { + VLOG(1) << "################ length_=" << length_ << " ################"; + } + + const uint32_t length_; +}; + +INSTANTIATE_TEST_CASE_P(VariousLengths, + GoAwayOpaqueDataLengthTests, + ::testing::Values(0, 1, 2, 3, 4, 5, 6)); + +TEST_P(GoAwayOpaqueDataLengthTests, ValidLength) { + Http2GoAwayFields goaway; + Randomize(&goaway, RandomPtr()); + string opaque_data = Random().RandString(length_); + Http2FrameBuilder fb; + fb.Append(goaway); + fb.Append(opaque_data); + Http2FrameHeader header(fb.size(), Http2FrameType::GOAWAY, RandFlags(), + RandStreamId()); + set_frame_header(header); + FrameParts expected(header, opaque_data); + expected.opt_goaway = goaway; + ASSERT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected)); +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/headers_payload_decoder.cc b/net/http2/decoder/payload_decoders/headers_payload_decoder.cc new file mode 100644 index 0000000..eff1d3b --- /dev/null +++ b/net/http2/decoder/payload_decoders/headers_payload_decoder.cc
@@ -0,0 +1,173 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/headers_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures.h" +#include "net/http2/tools/http2_bug_tracker.h" + +namespace net { + +std::ostream& operator<<(std::ostream& out, + HeadersPayloadDecoder::PayloadState v) { + switch (v) { + case HeadersPayloadDecoder::PayloadState::kReadPadLength: + return out << "kReadPadLength"; + case HeadersPayloadDecoder::PayloadState::kStartDecodingPriorityFields: + return out << "kStartDecodingPriorityFields"; + case HeadersPayloadDecoder::PayloadState::kResumeDecodingPriorityFields: + return out << "kResumeDecodingPriorityFields"; + case HeadersPayloadDecoder::PayloadState::kReadPayload: + return out << "kReadPayload"; + case HeadersPayloadDecoder::PayloadState::kSkipPadding: + return out << "kSkipPadding"; + } + return out << static_cast<int>(v); +} + +DecodeStatus HeadersPayloadDecoder::StartDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + const Http2FrameHeader& frame_header = state->frame_header(); + const uint32_t total_length = frame_header.payload_length; + + DVLOG(2) << "HeadersPayloadDecoder::StartDecodingPayload: " << frame_header; + + DCHECK_EQ(Http2FrameType::HEADERS, frame_header.type); + DCHECK_LE(db->Remaining(), total_length); + DCHECK_EQ( + 0, + frame_header.flags & + ~(Http2FrameFlag::FLAG_END_STREAM | Http2FrameFlag::FLAG_END_HEADERS | + Http2FrameFlag::FLAG_PADDED | Http2FrameFlag::FLAG_PRIORITY)); + + // Special case for HEADERS frames that contain only the HPACK block + // (fragment or whole) and that fit fully into the decode buffer. + // Why? Unencoded browser GET requests are typically under 1K and HPACK + // commonly shrinks request headers by 80%, so we can expect this to + // be common. + // TODO(jamessynge) Add counters here and to Spdy for determining how + // common this situation is. A possible approach is to create a + // Http2FrameDecoderListener that counts the callbacks and then forwards + // them on to another listener, which makes it easy to add and remove + // counting on a connection or even frame basis. + + // PADDED and PRIORITY both extra steps to decode, but if neither flag is + // set then we can decode faster. + const auto payload_flags = + Http2FrameFlag::FLAG_PADDED | Http2FrameFlag::FLAG_PRIORITY; + if (!frame_header.HasAnyFlags(payload_flags)) { + DVLOG(2) << "StartDecodingPayload !IsPadded && !HasPriority"; + if (db->Remaining() == total_length) { + DVLOG(2) << "StartDecodingPayload all present"; + // Note that we don't cache the listener field so that the callee can + // replace it if the frame is bad. + // If this case is common enough, consider combining the 3 callbacks + // into one, especially if END_HEADERS is also set. + state->listener()->OnHeadersStart(frame_header); + if (total_length > 0) { + state->listener()->OnHpackFragment(db->cursor(), total_length); + db->AdvanceCursor(total_length); + } + state->listener()->OnHeadersEnd(); + return DecodeStatus::kDecodeDone; + } + payload_state_ = PayloadState::kReadPayload; + } else if (frame_header.IsPadded()) { + payload_state_ = PayloadState::kReadPadLength; + } else { + DCHECK(frame_header.HasPriority()) << frame_header; + payload_state_ = PayloadState::kStartDecodingPriorityFields; + } + state->InitializeRemainders(); + state->listener()->OnHeadersStart(frame_header); + return ResumeDecodingPayload(state, db); +} + +DecodeStatus HeadersPayloadDecoder::ResumeDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "HeadersPayloadDecoder::ResumeDecodingPayload " + << "remaining_payload=" << state->remaining_payload() + << "; db->Remaining=" << db->Remaining(); + + const Http2FrameHeader& frame_header = state->frame_header(); + + DCHECK_EQ(Http2FrameType::HEADERS, frame_header.type); + DCHECK_LE(state->remaining_payload_and_padding(), + frame_header.payload_length); + DCHECK_LE(db->Remaining(), state->remaining_payload_and_padding()); + DecodeStatus status; + size_t avail; + while (true) { + DVLOG(2) << "HeadersPayloadDecoder::ResumeDecodingPayload payload_state_=" + << payload_state_; + switch (payload_state_) { + case PayloadState::kReadPadLength: + // ReadPadLength handles the OnPadLength callback, and updating the + // remaining_payload and remaining_padding fields. If the amount of + // padding is too large to fit in the frame's payload, ReadPadLength + // instead calls OnPaddingTooLong and returns kDecodeError. + status = state->ReadPadLength(db, /*report_pad_length*/ true); + if (status != DecodeStatus::kDecodeDone) { + return status; + } + if (!frame_header.HasPriority()) { + payload_state_ = PayloadState::kReadPayload; + continue; + } + // FALLTHROUGH_INTENDED + + case PayloadState::kStartDecodingPriorityFields: + status = state->StartDecodingStructureInPayload(&priority_fields_, db); + if (status != DecodeStatus::kDecodeDone) { + payload_state_ = PayloadState::kResumeDecodingPriorityFields; + return status; + } + state->listener()->OnHeadersPriority(priority_fields_); + // FALLTHROUGH_INTENDED + + case PayloadState::kReadPayload: + avail = state->AvailablePayload(db); + if (avail > 0) { + state->listener()->OnHpackFragment(db->cursor(), avail); + db->AdvanceCursor(avail); + state->ConsumePayload(avail); + } + if (state->remaining_payload() > 0) { + payload_state_ = PayloadState::kReadPayload; + return DecodeStatus::kDecodeInProgress; + } + // FALLTHROUGH_INTENDED + + case PayloadState::kSkipPadding: + // SkipPadding handles the OnPadding callback. + if (state->SkipPadding(db)) { + state->listener()->OnHeadersEnd(); + return DecodeStatus::kDecodeDone; + } + payload_state_ = PayloadState::kSkipPadding; + return DecodeStatus::kDecodeInProgress; + + case PayloadState::kResumeDecodingPriorityFields: + status = state->ResumeDecodingStructureInPayload(&priority_fields_, db); + if (status != DecodeStatus::kDecodeDone) { + return status; + } + state->listener()->OnHeadersPriority(priority_fields_); + payload_state_ = PayloadState::kReadPayload; + continue; + } + HTTP2_BUG << "PayloadState: " << payload_state_; + } +} + +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/headers_payload_decoder.h b/net/http2/decoder/payload_decoders/headers_payload_decoder.h new file mode 100644 index 0000000..76e6a082 --- /dev/null +++ b/net/http2/decoder/payload_decoders/headers_payload_decoder.h
@@ -0,0 +1,67 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_PAYLOAD_DECODERS_HEADERS_PAYLOAD_DECODER_H_ +#define NET_HTTP2_DECODER_PAYLOAD_DECODERS_HEADERS_PAYLOAD_DECODER_H_ + +// Decodes the payload of a HEADERS frame. + +#include "net/base/net_export.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/decoder/frame_decoder_state.h" +#include "net/http2/http2_structures.h" + +namespace net { +namespace test { +class HeadersPayloadDecoderPeer; +} // namespace test + +class NET_EXPORT_PRIVATE HeadersPayloadDecoder { + public: + // States during decoding of a HEADERS frame, unless the fast path kicks + // in, in which case the state machine will be bypassed. + enum class PayloadState { + // The PADDED flag is set, and we now need to read the Pad Length field + // (the first byte of the payload, after the common frame header). + kReadPadLength, + + // The PRIORITY flag is set, and we now need to read the fixed size priority + // fields (E, Stream Dependency, Weight) into priority_fields_. Calls on + // OnHeadersPriority if completely decodes those fields. + kStartDecodingPriorityFields, + + // The decoder passes the non-padding portion of the remaining payload + // (i.e. the HPACK block fragment) to the listener's OnHpackFragment method. + kReadPayload, + + // The decoder has finished with the HPACK block fragment, and is now + // ready to skip the trailing padding, if the frame has any. + kSkipPadding, + + // The fixed size fields weren't all available when the decoder first tried + // to decode them (state kStartDecodingPriorityFields); this state resumes + // the decoding when ResumeDecodingPayload is called later. + kResumeDecodingPriorityFields, + }; + + // Starts the decoding of a HEADERS frame's payload, and completes it if + // the entire payload is in the provided decode buffer. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a HEADERS frame's payload that has been split across + // decode buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); + + private: + friend class test::HeadersPayloadDecoderPeer; + + PayloadState payload_state_; + Http2PriorityFields priority_fields_; +}; + +} // namespace net + +#endif // NET_HTTP2_DECODER_PAYLOAD_DECODERS_HEADERS_PAYLOAD_DECODER_H_
diff --git a/net/http2/decoder/payload_decoders/headers_payload_decoder_test.cc b/net/http2/decoder/payload_decoders/headers_payload_decoder_test.cc new file mode 100644 index 0000000..89bd29f --- /dev/null +++ b/net/http2/decoder/payload_decoders/headers_payload_decoder_test.cc
@@ -0,0 +1,173 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/headers_payload_decoder.h" + +#include <stddef.h> + +#include <string> + +#include "base/bind.h" +#include "base/logging.h" +#include "net/http2/decoder/frame_parts.h" +#include "net/http2/decoder/frame_parts_collector.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures_test_util.h" +#include "net/http2/tools/http2_frame_builder.h" +#include "net/http2/tools/http2_random.h" +#include "net/http2/tools/random_decoder_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +using std::string; + +namespace net { +namespace test { + +class HeadersPayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { + return Http2FrameType::HEADERS; + } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { + return Http2FrameFlag::FLAG_PADDED | Http2FrameFlag::FLAG_PRIORITY; + } + + static void Randomize(HeadersPayloadDecoder* p, RandomBase* rng) { + CorruptEnum(&p->payload_state_, rng); + test::Randomize(&p->priority_fields_, rng); + VLOG(1) << "HeadersPayloadDecoderPeer::Randomize priority_fields_: " + << p->priority_fields_; + } +}; + +namespace { + +// Listener handles all On* methods that are expected to be called. If any other +// On* methods of Http2FrameDecoderListener is called then the test fails; this +// is achieved by way of FailingHttp2FrameDecoderListener, the base class of +// FramePartsCollector. +// These On* methods make use of StartFrame, EndFrame, etc. of the base class +// to create and access to FrameParts instance(s) that will record the details. +// After decoding, the test validation code can access the FramePart instance(s) +// via the public methods of FramePartsCollector. +struct Listener : public FramePartsCollector { + void OnHeadersStart(const Http2FrameHeader& header) override { + VLOG(1) << "OnHeadersStart: " << header; + StartFrame(header)->OnHeadersStart(header); + } + + void OnHeadersPriority(const Http2PriorityFields& priority) override { + VLOG(1) << "OnHeadersPriority: " << priority; + CurrentFrame()->OnHeadersPriority(priority); + } + + void OnHpackFragment(const char* data, size_t len) override { + VLOG(1) << "OnHpackFragment: len=" << len; + CurrentFrame()->OnHpackFragment(data, len); + } + + void OnHeadersEnd() override { + VLOG(1) << "OnHeadersEnd"; + EndFrame()->OnHeadersEnd(); + } + + void OnPadLength(size_t pad_length) override { + VLOG(1) << "OnPadLength: " << pad_length; + CurrentFrame()->OnPadLength(pad_length); + } + + void OnPadding(const char* padding, size_t skipped_length) override { + VLOG(1) << "OnPadding: " << skipped_length; + CurrentFrame()->OnPadding(padding, skipped_length); + } + + void OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) override { + VLOG(1) << "OnPaddingTooLong: " << header + << "; missing_length: " << missing_length; + FrameError(header)->OnPaddingTooLong(header, missing_length); + } + + void OnFrameSizeError(const Http2FrameHeader& header) override { + VLOG(1) << "OnFrameSizeError: " << header; + FrameError(header)->OnFrameSizeError(header); + } +}; + +class HeadersPayloadDecoderTest + : public AbstractPaddablePayloadDecoderTest<HeadersPayloadDecoder, + HeadersPayloadDecoderPeer, + Listener> { + public: + static bool ApproveSizeForTruncated(size_t size) { + return size != Http2PriorityFields::EncodedSize(); + } +}; + +INSTANTIATE_TEST_CASE_P(VariousPadLengths, + HeadersPayloadDecoderTest, + ::testing::Values(0, 1, 2, 3, 4, 254, 255, 256)); + +// Decode various sizes of (fake) HPACK payload, both with and without the +// PRIORITY flag set. +TEST_P(HeadersPayloadDecoderTest, VariousHpackPayloadSizes) { + for (size_t hpack_size : {0, 1, 2, 3, 255, 256, 1024}) { + LOG(INFO) << "########### hpack_size = " << hpack_size << " ###########"; + Http2PriorityFields priority(RandStreamId(), 1 + Random().Rand8(), + Random().OneIn(2)); + + for (bool has_priority : {false, true}) { + Reset(); + ASSERT_EQ(IsPadded() ? 1u : 0u, frame_builder_.size()); + uint8_t flags = RandFlags(); + if (has_priority) { + flags |= Http2FrameFlag::FLAG_PRIORITY; + frame_builder_.Append(priority); + } + + string hpack_payload = Random().RandString(hpack_size); + frame_builder_.Append(hpack_payload); + + MaybeAppendTrailingPadding(); + Http2FrameHeader frame_header(frame_builder_.size(), + Http2FrameType::HEADERS, flags, + RandStreamId()); + set_frame_header(frame_header); + ScrubFlagsOfHeader(&frame_header); + FrameParts expected(frame_header, hpack_payload, total_pad_length_); + if (has_priority) { + expected.opt_priority = priority; + } + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(frame_builder_.buffer(), + expected)); + } + } +} + +// Confirm we get an error if the PRIORITY flag is set but the payload is +// not long enough, regardless of the amount of (valid) padding. +TEST_P(HeadersPayloadDecoderTest, Truncated) { + Http2FrameBuilder fb; + fb.Append(Http2PriorityFields(RandStreamId(), 1 + Random().Rand8(), + Random().OneIn(2))); + EXPECT_TRUE(VerifyDetectsMultipleFrameSizeErrors( + Http2FrameFlag::FLAG_PRIORITY, fb.buffer(), + base::Bind(&HeadersPayloadDecoderTest::ApproveSizeForTruncated), + total_pad_length_)); +} + +// Confirm we get an error if the PADDED flag is set but the payload is not +// long enough to hold even the Pad Length amount of padding. +TEST_P(HeadersPayloadDecoderTest, PaddingTooLong) { + EXPECT_TRUE(VerifyDetectsPaddingTooLong()); +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/payload_decoder_base_test_util.cc b/net/http2/decoder/payload_decoders/payload_decoder_base_test_util.cc new file mode 100644 index 0000000..d96d481b --- /dev/null +++ b/net/http2/decoder/payload_decoders/payload_decoder_base_test_util.cc
@@ -0,0 +1,98 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" + +#include "net/http2/decoder/frame_decoder_state_test_util.h" +#include "net/http2/http2_structures_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace test { +PayloadDecoderBaseTest::PayloadDecoderBaseTest() { + // If the test adds more data after the frame payload, + // stop as soon as the payload is decoded. + stop_decode_on_done_ = true; + frame_header_is_set_ = false; + Randomize(&frame_header_, RandomPtr()); +} + +DecodeStatus PayloadDecoderBaseTest::StartDecoding(DecodeBuffer* db) { + DVLOG(2) << "StartDecoding, db->Remaining=" << db->Remaining(); + // Make sure sub-class has set frame_header_ so that we can inject it + // into the payload decoder below. + if (!frame_header_is_set_) { + ADD_FAILURE() << "frame_header_ is not set"; + return DecodeStatus::kDecodeError; + } + // The contract with the payload decoders is that they won't receive a + // decode buffer that extends beyond the end of the frame. + if (db->Remaining() > frame_header_.payload_length) { + ADD_FAILURE() << "DecodeBuffer has too much data: " << db->Remaining() + << " > " << frame_header_.payload_length; + return DecodeStatus::kDecodeError; + } + + // Prepare the payload decoder. + PreparePayloadDecoder(); + + // Reconstruct the FrameDecoderState, prepare the listener, and add it to + // the FrameDecoderState. + frame_decoder_state_.~FrameDecoderState(); + new (&frame_decoder_state_) FrameDecoderState; + frame_decoder_state_.set_listener(PrepareListener()); + + // Make sure that a listener was provided. + if (frame_decoder_state_.listener() == nullptr) { + ADD_FAILURE() << "PrepareListener must return a listener."; + return DecodeStatus::kDecodeError; + } + + // Now that nothing in the payload decoder should be valid, inject the + // Http2FrameHeader whose payload we're about to decode. That header is the + // only state that a payload decoder should expect is valid when its Start + // method is called. + FrameDecoderStatePeer::set_frame_header(frame_header_, &frame_decoder_state_); + DecodeStatus status = StartDecodingPayload(db); + if (status != DecodeStatus::kDecodeInProgress) { + // Keep track of this so that a concrete test can verify that both fast + // and slow decoding paths have been tested. + ++fast_decode_count_; + } + return status; +} + +DecodeStatus PayloadDecoderBaseTest::ResumeDecoding(DecodeBuffer* db) { + DVLOG(2) << "ResumeDecoding, db->Remaining=" << db->Remaining(); + DecodeStatus status = ResumeDecodingPayload(db); + if (status != DecodeStatus::kDecodeInProgress) { + // Keep track of this so that a concrete test can verify that both fast + // and slow decoding paths have been tested. + ++slow_decode_count_; + } + return status; +} + +::testing::AssertionResult +PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays( + base::StringPiece payload, + Validator validator) { + VERIFY_TRUE(frame_header_is_set_); + // Cap the payload to be decoded at the declared payload length. This is + // required by the decoders' preconditions; they are designed on the + // assumption that they're never passed more than they're permitted to + // consume. + // Note that it is OK if the payload is too short; the validator may be + // designed to check for that. + if (payload.size() > frame_header_.payload_length) { + payload = base::StringPiece(payload.data(), frame_header_.payload_length); + } + DecodeBuffer db(payload); + ResetDecodeSpeedCounters(); + const bool kMayReturnZeroOnFirst = false; + return DecodeAndValidateSeveralWays(&db, kMayReturnZeroOnFirst, validator); +} + +} // namespace test +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/payload_decoder_base_test_util.h b/net/http2/decoder/payload_decoders/payload_decoder_base_test_util.h new file mode 100644 index 0000000..b49b15ef --- /dev/null +++ b/net/http2/decoder/payload_decoders/payload_decoder_base_test_util.h
@@ -0,0 +1,486 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_PAYLOAD_DECODERS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_ +#define NET_HTTP2_DECODER_PAYLOAD_DECODERS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_ + +// Base class for testing concrete payload decoder classes. + +#include <stddef.h> + +#include <string> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/strings/string_piece.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/decoder/frame_decoder_state.h" +#include "net/http2/decoder/frame_parts.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_constants_test_util.h" +#include "net/http2/http2_structures.h" +#include "net/http2/tools/http2_frame_builder.h" +#include "net/http2/tools/random_decoder_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace test { + +// Base class for tests of payload decoders. Below this there is a templated +// sub-class that adds a bunch of type specific features. +class PayloadDecoderBaseTest : public RandomDecoderTest { + protected: + PayloadDecoderBaseTest(); + + // Virtual functions to be implemented by the test classes for the individual + // payload decoders... + + // Start decoding the payload. + virtual DecodeStatus StartDecodingPayload(DecodeBuffer* db) = 0; + + // Resume decoding the payload. + virtual DecodeStatus ResumeDecodingPayload(DecodeBuffer* db) = 0; + + // In support of ensuring that we're really accessing and updating the + // decoder, prepare the decoder by, for example, overwriting the decoder. + virtual void PreparePayloadDecoder() = 0; + + // Get the listener to be inserted into the FrameDecoderState, ready for + // listening (e.g. reset if it is a FramePartsCollector). + virtual Http2FrameDecoderListener* PrepareListener() = 0; + + // Record a frame header for use on each call to StartDecoding. + void set_frame_header(const Http2FrameHeader& header) { + EXPECT_EQ(0, InvalidFlagMaskForFrameType(header.type) & header.flags); + if (!frame_header_is_set_ || frame_header_ != header) { + VLOG(2) << "set_frame_header: " << frame_header_; + } + frame_header_ = header; + frame_header_is_set_ = true; + } + + const Http2FrameHeader& frame_header() const { + CHECK(frame_header_is_set_); + return frame_header_; + } + + FrameDecoderState* mutable_state() { return &frame_decoder_state_; } + + // Randomize the payload decoder, sets the payload decoder's frame_header_, + // then start decoding the payload. Called by RandomDecoderTest. This method + // is final so that we can always perform certain actions when + // RandomDecoderTest starts the decoding of a payload, such as randomizing the + // the payload decoder, injecting the frame header and counting fast decoding + // cases. Sub-classes must implement StartDecodingPayload to perform their + // initial decoding of a frame's payload. + DecodeStatus StartDecoding(DecodeBuffer* db) final; + + // Called by RandomDecoderTest. This method is final so that we can always + // perform certain actions when RandomDecoderTest calls it, such as counting + // slow decode cases. Sub-classes must implement ResumeDecodingPayload to + // continue decoding the frame's payload, which must not all be in one buffer. + DecodeStatus ResumeDecoding(DecodeBuffer* db) final; + + // Given the specified payload (without the common frame header), decode + // it with several partitionings of the payload. + ::testing::AssertionResult DecodePayloadAndValidateSeveralWays( + base::StringPiece payload, + Validator validator); + + // TODO(jamessynge): Add helper method for verifying these are both non-zero, + // and call the new method from tests that expect successful decoding. + void ResetDecodeSpeedCounters() { + fast_decode_count_ = 0; + slow_decode_count_ = 0; + } + + // Count of payloads that are full decoded by StartDecodingPayload, or that + // an error was detected by StartDecodingPayload. + size_t fast_decode_count_ = 0; + + // Count of payloads that require calling ResumeDecodingPayload in order to + // decode them completely (or to detect an error during decoding). + size_t slow_decode_count_ = 0; + + private: + bool frame_header_is_set_ = false; + Http2FrameHeader frame_header_; + FrameDecoderState frame_decoder_state_; +}; + +// Base class for payload decoders of type Decoder, with corresponding test +// peer of type DecoderPeer, and using class Listener as the implementation +// of Http2FrameDecoderListenerInterface to be used during decoding. +// Typically Listener is a sub-class of FramePartsCollector. +// SupportedFrameType is set to false only for UnknownPayloadDecoder. +template <class Decoder, + class DecoderPeer, + class Listener, + bool SupportedFrameType = true> +class AbstractPayloadDecoderTest : public PayloadDecoderBaseTest { + public: + static bool SucceedingApproveSize(size_t size) { return true; } + + protected: + // An ApproveSize function returns true to approve decoding the specified + // size of payload, else false to skip that size. Typically used for negative + // tests; for example, decoding a SETTINGS frame at all sizes except for + // multiples of 6. + typedef base::Callback<bool(size_t size)> ApproveSize; + + AbstractPayloadDecoderTest() {} + + // These tests are in setup rather than the constructor for two reasons: + // 1) Constructors are not allowed to fail, so gUnit documents that EXPECT_* + // and ASSERT_* are not allowed in constructors, and should instead be in + // SetUp if they are needed before the body of the test is executed. + // 2) To allow the sub-class constructor to make any desired modifications to + // the DecoderPeer before these tests are executed; in particular, + // UnknownPayloadDecoderPeer has not got a fixed frame type, but it is + // instead set during the test's constructor. + void SetUp() override { + PayloadDecoderBaseTest::SetUp(); + + // Confirm that DecoderPeer et al returns sensible values. Using auto as the + // variable type so that no (narrowing) conversions take place that hide + // problems; i.e. if someone changes KnownFlagsMaskForFrameType so that it + // doesn't return a uint8, and has bits above the low-order 8 bits set, this + // bit of paranoia should detect the problem before we get too far. + auto frame_type = DecoderPeer::FrameType(); + if (SupportedFrameType) { + EXPECT_TRUE(IsSupportedHttp2FrameType(frame_type)) << frame_type; + } else { + EXPECT_FALSE(IsSupportedHttp2FrameType(frame_type)) << frame_type; + } + + auto known_flags = KnownFlagsMaskForFrameType(frame_type); + EXPECT_EQ(known_flags, known_flags & 0xff); + + auto flags_to_avoid = DecoderPeer::FlagsAffectingPayloadDecoding(); + EXPECT_EQ(flags_to_avoid, flags_to_avoid & known_flags); + } + + void PreparePayloadDecoder() override { + payload_decoder_.~Decoder(); + new (&payload_decoder_) Decoder; + } + + Http2FrameDecoderListener* PrepareListener() override { + listener_.Reset(); + return &listener_; + } + + // Returns random flags, but only those valid for the frame type, yet not + // those that the DecoderPeer says will affect the decoding of the payload + // (e.g. the PRIORTY flag on a HEADERS frame or PADDED on DATA frames). + uint8_t RandFlags() { + return Random().Rand8() & + KnownFlagsMaskForFrameType(DecoderPeer::FrameType()) & + ~DecoderPeer::FlagsAffectingPayloadDecoding(); + } + + // Start decoding the payload. + DecodeStatus StartDecodingPayload(DecodeBuffer* db) override { + DVLOG(2) << "StartDecodingPayload, db->Remaining=" << db->Remaining(); + return payload_decoder_.StartDecodingPayload(mutable_state(), db); + } + + // Resume decoding the payload. + DecodeStatus ResumeDecodingPayload(DecodeBuffer* db) override { + DVLOG(2) << "ResumeDecodingPayload, db->Remaining=" << db->Remaining(); + return payload_decoder_.ResumeDecodingPayload(mutable_state(), db); + } + + // Wrap |validator| in another one which will check that we've reached the + // expected state of kDecodeError with OnFrameSizeError having been called by + AssertionResult ValidatorForDecodePayloadAndValidateSeveralWays( + const FrameParts& expected) { + VERIFY_FALSE(listener_.IsInProgress()); + VERIFY_EQ(1u, listener_.size()); + VERIFY_AND_RETURN_SUCCESS(expected.VerifyEquals(*listener_.frame(0))); + } + + // Decode one frame's payload and confirm that the listener recorded the + // expected FrameParts instance, and only FrameParts instance. The payload + // will be decoded several times with different partitionings of the payload, + // and after each the validator will be called. + AssertionResult DecodePayloadAndValidateSeveralWays( + base::StringPiece payload, + const FrameParts& expected) { + return PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays( + payload, this->ValidateDoneAndEmpty(base::Bind( + &AbstractPayloadDecoderTest:: + ValidatorForDecodePayloadAndValidateSeveralWays, + base::Unretained(this), base::ConstRef(expected)))); + } + + // Wrap |validator| in another one which will check that we've reached the + // expected state of kDecodeError with OnFrameSizeError having been called by + // the payload decoder. + AssertionResult ValidatorForVerifyDetectsFrameSizeError( + const Http2FrameHeader& header, + const Validator& validator, + const DecodeBuffer& input, + DecodeStatus status) { + DVLOG(2) << "VerifyDetectsFrameSizeError validator; status=" << status + << "; input.Remaining=" << input.Remaining(); + VERIFY_EQ(DecodeStatus::kDecodeError, status); + VERIFY_FALSE(listener_.IsInProgress()); + VERIFY_EQ(1u, listener_.size()); + const FrameParts* frame = listener_.frame(0); + VERIFY_EQ(header, frame->frame_header); + VERIFY_TRUE(frame->has_frame_size_error); + // Verify did not get OnPaddingTooLong, as we should only ever produce + // one of these two errors for a single frame. + VERIFY_FALSE(frame->opt_missing_length); + return validator.Run(input, status); + } + + // Decode one frame's payload, expecting that the final status will be + // kDecodeError, and that OnFrameSizeError will have been called on the + // listener. The payload will be decoded several times with different + // partitionings of the payload. The type WrappedValidator is either + // RandomDecoderTest::Validator, RandomDecoderTest::NoArgValidator or + // std::nullptr_t (not extra validation). + template <typename WrappedValidator> + ::testing::AssertionResult VerifyDetectsFrameSizeError( + base::StringPiece payload, + const Http2FrameHeader& header, + WrappedValidator wrapped_validator) { + set_frame_header(header); + // If wrapped_validator is not a RandomDecoderTest::Validator, make it so. + Validator validator = this->ToValidator(wrapped_validator); + VERIFY_AND_RETURN_SUCCESS( + PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays( + payload, base::Bind(&AbstractPayloadDecoderTest:: + ValidatorForVerifyDetectsFrameSizeError, + base::Unretained(this), base::ConstRef(header), + base::ConstRef(validator)))); + } + + // Confirm that we get OnFrameSizeError when trying to decode unpadded_payload + // at all sizes from zero to unpadded_payload.size(), except those sizes not + // approved by approve_size. + // If total_pad_length is greater than zero, then that amount of padding + // is added to the payload (including the Pad Length field). + // The flags will be required_flags, PADDED if total_pad_length > 0, and some + // randomly selected flag bits not excluded by FlagsAffectingPayloadDecoding. + ::testing::AssertionResult VerifyDetectsMultipleFrameSizeErrors( + uint8_t required_flags, + base::StringPiece unpadded_payload, + ApproveSize approve_size, + int total_pad_length) { + // required_flags should come from those that are defined for the frame + // type AND are those that affect the decoding of the payload (otherwise, + // the flag shouldn't be required). + Http2FrameType frame_type = DecoderPeer::FrameType(); + VERIFY_EQ(required_flags, + required_flags & KnownFlagsMaskForFrameType(frame_type)); + VERIFY_EQ(required_flags, + required_flags & DecoderPeer::FlagsAffectingPayloadDecoding()); + + if (0 != (Http2FrameFlag::FLAG_PADDED & + KnownFlagsMaskForFrameType(frame_type))) { + // Frame type supports padding. + if (total_pad_length == 0) { + required_flags &= ~Http2FrameFlag::FLAG_PADDED; + } else { + required_flags |= Http2FrameFlag::FLAG_PADDED; + } + } else { + VERIFY_EQ(0, total_pad_length); + } + + bool validated = false; + for (size_t real_payload_size = 0; + real_payload_size <= unpadded_payload.size(); ++real_payload_size) { + if (!approve_size.Run(real_payload_size)) { + continue; + } + VLOG(1) << "real_payload_size=" << real_payload_size; + uint8_t flags = required_flags | RandFlags(); + Http2FrameBuilder fb; + if (total_pad_length > 0) { + // total_pad_length_ includes the size of the Pad Length field, and thus + // ranges from 0 (no PADDED flag) to 256 (Pad Length == 255). + fb.AppendUInt8(total_pad_length - 1); + } + // Append a subset of the unpadded_payload, which the decoder should + // determine is not a valid amount. + fb.Append(unpadded_payload.substr(0, real_payload_size)); + if (total_pad_length > 0) { + fb.AppendZeroes(total_pad_length - 1); + } + // We choose a random stream id because the payload decoders aren't + // checking stream ids. + uint32_t stream_id = RandStreamId(); + Http2FrameHeader header(fb.size(), frame_type, flags, stream_id); + VERIFY_SUCCESS(VerifyDetectsFrameSizeError( + fb.buffer(), header, base::Bind(&SucceedingValidator))); + validated = true; + } + VERIFY_TRUE(validated); + return ::testing::AssertionSuccess(); + } + + // As above, but for frames without padding. + ::testing::AssertionResult VerifyDetectsFrameSizeError( + uint8_t required_flags, + base::StringPiece unpadded_payload, + ApproveSize approve_size) { + Http2FrameType frame_type = DecoderPeer::FrameType(); + uint8_t known_flags = KnownFlagsMaskForFrameType(frame_type); + VERIFY_EQ(0, known_flags & Http2FrameFlag::FLAG_PADDED); + VERIFY_EQ(0, required_flags & Http2FrameFlag::FLAG_PADDED); + VERIFY_AND_RETURN_SUCCESS(VerifyDetectsMultipleFrameSizeErrors( + required_flags, unpadded_payload, approve_size, 0)); + } + + Listener listener_; + union { + // Confirm at compile time that Decoder can be in an anonymous union, + // i.e. complain loudly if Decoder has members that prevent this, as it + // becomes annoying and possibly difficult to deal with non-anonymous + // unions and such union members. + Decoder payload_decoder_; + }; +}; + +// A base class for tests parameterized by the total number of bytes of +// padding, including the Pad Length field (i.e. a total_pad_length of 0 +// means unpadded as there is then no room for the Pad Length field). +// The frame type must support padding. +template <class Decoder, class DecoderPeer, class Listener> +class AbstractPaddablePayloadDecoderTest + : public AbstractPayloadDecoderTest<Decoder, DecoderPeer, Listener>, + public ::testing::WithParamInterface<int> { + typedef AbstractPayloadDecoderTest<Decoder, DecoderPeer, Listener> Base; + + protected: + using Base::Random; + using Base::RandStreamId; + using Base::set_frame_header; + using Base::listener_; + typedef typename Base::Validator Validator; + + AbstractPaddablePayloadDecoderTest() : total_pad_length_(GetParam()) { + LOG(INFO) << "total_pad_length_ = " << total_pad_length_; + } + + // Note that total_pad_length_ includes the size of the Pad Length field, + // and thus ranges from 0 (no PADDED flag) to 256 (Pad Length == 255). + bool IsPadded() const { return total_pad_length_ > 0; } + + // Value of the Pad Length field. Only call if IsPadded. + size_t pad_length() const { + EXPECT_TRUE(IsPadded()); + return total_pad_length_ - 1; + } + + // Clear the frame builder and add the Pad Length field if appropriate. + void Reset() { + frame_builder_ = Http2FrameBuilder(); + if (IsPadded()) { + frame_builder_.AppendUInt8(pad_length()); + } + } + + void MaybeAppendTrailingPadding() { + if (IsPadded()) { + frame_builder_.AppendZeroes(pad_length()); + } + } + + uint8_t RandFlags() { + uint8_t flags = Base::RandFlags(); + if (IsPadded()) { + flags |= Http2FrameFlag::FLAG_PADDED; + } else { + flags &= ~Http2FrameFlag::FLAG_PADDED; + } + return flags; + } + + static ::testing::AssertionResult ValidatorForVerifyDetectsPaddingTooLong( + const Http2FrameHeader& header, + int expected_missing_length, + const Listener& listener, + const DecodeBuffer& input, + DecodeStatus status) { + VERIFY_EQ(DecodeStatus::kDecodeError, status); + VERIFY_FALSE(listener.IsInProgress()); + VERIFY_EQ(1u, listener.size()); + const FrameParts* frame = listener.frame(0); + VERIFY_EQ(header, frame->frame_header); + VERIFY_TRUE(frame->opt_missing_length); + VERIFY_EQ(expected_missing_length, frame->opt_missing_length.value()); + // Verify did not get OnFrameSizeError. + VERIFY_FALSE(frame->has_frame_size_error); + return ::testing::AssertionSuccess(); + } + + // Verify that we get OnPaddingTooLong when decoding payload, and that the + // amount of missing padding is as specified. header.IsPadded must be true, + // and the payload must be empty or the PadLength field must be too large. + ::testing::AssertionResult VerifyDetectsPaddingTooLong( + base::StringPiece payload, + const Http2FrameHeader& header, + int expected_missing_length) { + set_frame_header(header); + auto& listener = listener_; + VERIFY_AND_RETURN_SUCCESS( + PayloadDecoderBaseTest::DecodePayloadAndValidateSeveralWays( + payload, base::Bind(&AbstractPaddablePayloadDecoderTest:: + ValidatorForVerifyDetectsPaddingTooLong, + header, expected_missing_length, + base::ConstRef(listener)))); + } + + // Verifies that we get OnPaddingTooLong for a padded frame payload whose + // (randomly selected) payload length is less than total_pad_length_. + // Flags will be selected at random, except PADDED will be set and + // flags_to_avoid will not be set. The stream id is selected at random. + ::testing::AssertionResult VerifyDetectsPaddingTooLong() { + uint8_t flags = RandFlags() | Http2FrameFlag::FLAG_PADDED; + + // Create an all padding payload for total_pad_length_. + int payload_length = 0; + Http2FrameBuilder fb; + if (IsPadded()) { + fb.AppendUInt8(pad_length()); + fb.AppendZeroes(pad_length()); + VLOG(1) << "fb.size=" << fb.size(); + // Pick a random length for the payload that is shorter than neccesary. + payload_length = Random().Rand32() % fb.size(); + } + + VLOG(1) << "payload_length=" << payload_length; + std::string payload = fb.buffer().substr(0, payload_length); + + // The missing length is the amount we cut off the end, unless + // payload_length is zero, in which case the decoder knows only that 1 + // byte, the Pad Length field, is missing. + int missing_length = payload_length == 0 ? 1 : fb.size() - payload_length; + VLOG(1) << "missing_length=" << missing_length; + + const Http2FrameHeader header(payload_length, DecoderPeer::FrameType(), + flags, RandStreamId()); + VERIFY_AND_RETURN_SUCCESS( + VerifyDetectsPaddingTooLong(payload, header, missing_length)); + } + + // total_pad_length_ includes the size of the Pad Length field, and thus + // ranges from 0 (no PADDED flag) to 256 (Pad Length == 255). + const size_t total_pad_length_; + Http2FrameBuilder frame_builder_; +}; + +} // namespace test +} // namespace net + +#endif // NET_HTTP2_DECODER_PAYLOAD_DECODERS_PAYLOAD_DECODER_BASE_TEST_UTIL_H_
diff --git a/net/http2/decoder/payload_decoders/ping_payload_decoder.cc b/net/http2/decoder/payload_decoders/ping_payload_decoder.cc new file mode 100644 index 0000000..abe43d0a --- /dev/null +++ b/net/http2/decoder/payload_decoders/ping_payload_decoder.cc
@@ -0,0 +1,89 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/ping_payload_decoder.h" + +#include "base/logging.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/http2_constants.h" + +namespace net { +namespace { +constexpr auto kOpaqueSize = Http2PingFields::EncodedSize(); +} + +DecodeStatus PingPayloadDecoder::StartDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db) { + const Http2FrameHeader& frame_header = state->frame_header(); + const uint32_t total_length = frame_header.payload_length; + + DVLOG(2) << "PingPayloadDecoder::StartDecodingPayload: " << frame_header; + DCHECK_EQ(Http2FrameType::PING, frame_header.type); + DCHECK_LE(db->Remaining(), total_length); + DCHECK_EQ(0, frame_header.flags & ~(Http2FrameFlag::FLAG_ACK)); + + // Is the payload entirely in the decode buffer and is it the correct size? + // Given the size of the header and payload (17 bytes total), this is most + // likely the case the vast majority of the time. + if (db->Remaining() == kOpaqueSize && total_length == kOpaqueSize) { + // Special case this situation as it allows us to avoid any copying; + // the other path makes two copies, first into the buffer in + // Http2StructureDecoder as it accumulates the 8 bytes of opaque data, + // and a second copy into the Http2PingFields member of in this class. + // This supports the claim that this decoder is (mostly) non-buffering. + static_assert(sizeof(Http2PingFields) == kOpaqueSize, + "If not, then can't enter this block!"); + auto ping = reinterpret_cast<const Http2PingFields*>(db->cursor()); + if (frame_header.IsAck()) { + state->listener()->OnPingAck(frame_header, *ping); + } else { + state->listener()->OnPing(frame_header, *ping); + } + db->AdvanceCursor(kOpaqueSize); + return DecodeStatus::kDecodeDone; + } + state->InitializeRemainders(); + return HandleStatus( + state, state->StartDecodingStructureInPayload(&ping_fields_, db)); +} + +DecodeStatus PingPayloadDecoder::ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "ResumeDecodingPayload: remaining_payload=" + << state->remaining_payload(); + DCHECK_EQ(Http2FrameType::PING, state->frame_header().type); + DCHECK_LE(db->Remaining(), state->frame_header().payload_length); + return HandleStatus( + state, state->ResumeDecodingStructureInPayload(&ping_fields_, db)); +} + +DecodeStatus PingPayloadDecoder::HandleStatus(FrameDecoderState* state, + DecodeStatus status) { + DVLOG(2) << "HandleStatus: status=" << status + << "; remaining_payload=" << state->remaining_payload(); + if (status == DecodeStatus::kDecodeDone) { + if (state->remaining_payload() == 0) { + const Http2FrameHeader& frame_header = state->frame_header(); + if (frame_header.IsAck()) { + state->listener()->OnPingAck(frame_header, ping_fields_); + } else { + state->listener()->OnPing(frame_header, ping_fields_); + } + return DecodeStatus::kDecodeDone; + } + // Payload is too long. + return state->ReportFrameSizeError(); + } + // Not done decoding the structure. Either we've got more payload to decode, + // or we've run out because the payload is too short. + DCHECK( + (status == DecodeStatus::kDecodeInProgress && + state->remaining_payload() > 0) || + (status == DecodeStatus::kDecodeError && state->remaining_payload() == 0)) + << "\n status=" << status + << "; remaining_payload=" << state->remaining_payload(); + return status; +} + +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/ping_payload_decoder.h b/net/http2/decoder/payload_decoders/ping_payload_decoder.h new file mode 100644 index 0000000..0eb10a2e --- /dev/null +++ b/net/http2/decoder/payload_decoders/ping_payload_decoder.h
@@ -0,0 +1,43 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_PAYLOAD_DECODERS_PING_PAYLOAD_DECODER_H_ +#define NET_HTTP2_DECODER_PAYLOAD_DECODERS_PING_PAYLOAD_DECODER_H_ + +// Decodes the payload of a PING frame; for the RFC, see: +// http://httpwg.org/specs/rfc7540.html#PING + +#include "net/base/net_export.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/decoder/frame_decoder_state.h" +#include "net/http2/http2_structures.h" + +namespace net { +namespace test { +class PingPayloadDecoderPeer; +} // namespace test + +class NET_EXPORT_PRIVATE PingPayloadDecoder { + public: + // Starts the decoding of a PING frame's payload, and completes it if the + // entire payload is in the provided decode buffer. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a PING frame's payload that has been split across + // decode buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); + + private: + friend class test::PingPayloadDecoderPeer; + + DecodeStatus HandleStatus(FrameDecoderState* state, DecodeStatus status); + + Http2PingFields ping_fields_; +}; + +} // namespace net + +#endif // NET_HTTP2_DECODER_PAYLOAD_DECODERS_PING_PAYLOAD_DECODER_H_
diff --git a/net/http2/decoder/payload_decoders/ping_payload_decoder_test.cc b/net/http2/decoder/payload_decoders/ping_payload_decoder_test.cc new file mode 100644 index 0000000..c3696f7c --- /dev/null +++ b/net/http2/decoder/payload_decoders/ping_payload_decoder_test.cc
@@ -0,0 +1,122 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/ping_payload_decoder.h" + +#include <stddef.h> + +#include "base/bind.h" +#include "base/logging.h" +#include "net/http2/decoder/frame_parts.h" +#include "net/http2/decoder/frame_parts_collector.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures_test_util.h" +#include "net/http2/tools/http2_frame_builder.h" +#include "net/http2/tools/http2_random.h" +#include "net/http2/tools/random_decoder_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace test { + +class PingPayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { return Http2FrameType::PING; } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; } + + static void Randomize(PingPayloadDecoder* p, RandomBase* rng) { + VLOG(1) << "PingPayloadDecoderPeer::Randomize"; + test::Randomize(&p->ping_fields_, rng); + } +}; + +namespace { + +struct Listener : public FramePartsCollector { + void OnPing(const Http2FrameHeader& header, + const Http2PingFields& ping) override { + VLOG(1) << "OnPing: " << header << "; " << ping; + StartAndEndFrame(header)->OnPing(header, ping); + } + + void OnPingAck(const Http2FrameHeader& header, + const Http2PingFields& ping) override { + VLOG(1) << "OnPingAck: " << header << "; " << ping; + StartAndEndFrame(header)->OnPingAck(header, ping); + } + + void OnFrameSizeError(const Http2FrameHeader& header) override { + VLOG(1) << "OnFrameSizeError: " << header; + FrameError(header)->OnFrameSizeError(header); + } +}; + +class PingPayloadDecoderTest + : public AbstractPayloadDecoderTest<PingPayloadDecoder, + PingPayloadDecoderPeer, + Listener> { + public: + static bool ApproveSizeForWrongSize(size_t size) { + return size != Http2PingFields::EncodedSize(); + } + + protected: + Http2PingFields RandPingFields() { + Http2PingFields fields; + test::Randomize(&fields, RandomPtr()); + return fields; + } +}; + +// Confirm we get an error if the payload is not the correct size to hold +// exactly one Http2PingFields. +TEST_F(PingPayloadDecoderTest, WrongSize) { + Http2FrameBuilder fb; + fb.Append(RandPingFields()); + fb.Append(RandPingFields()); + fb.Append(RandPingFields()); + EXPECT_TRUE(VerifyDetectsFrameSizeError( + 0, fb.buffer(), + base::Bind(&PingPayloadDecoderTest::ApproveSizeForWrongSize))); +} + +TEST_F(PingPayloadDecoderTest, Ping) { + for (int n = 0; n < 100; ++n) { + Http2PingFields fields = RandPingFields(); + Http2FrameBuilder fb; + fb.Append(fields); + Http2FrameHeader header(fb.size(), Http2FrameType::PING, + RandFlags() & ~Http2FrameFlag::FLAG_ACK, + RandStreamId()); + set_frame_header(header); + FrameParts expected(header); + expected.opt_ping = fields; + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected)); + } +} + +TEST_F(PingPayloadDecoderTest, PingAck) { + for (int n = 0; n < 100; ++n) { + Http2PingFields fields; + Randomize(&fields, RandomPtr()); + Http2FrameBuilder fb; + fb.Append(fields); + Http2FrameHeader header(fb.size(), Http2FrameType::PING, + RandFlags() | Http2FrameFlag::FLAG_ACK, + RandStreamId()); + set_frame_header(header); + FrameParts expected(header); + expected.opt_ping = fields; + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected)); + } +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/priority_payload_decoder.cc b/net/http2/decoder/payload_decoders/priority_payload_decoder.cc new file mode 100644 index 0000000..51ae80b --- /dev/null +++ b/net/http2/decoder/payload_decoders/priority_payload_decoder.cc
@@ -0,0 +1,64 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/priority_payload_decoder.h" + +#include "base/logging.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures.h" + +namespace net { + +DecodeStatus PriorityPayloadDecoder::StartDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "PriorityPayloadDecoder::StartDecodingPayload: " + << state->frame_header(); + DCHECK_EQ(Http2FrameType::PRIORITY, state->frame_header().type); + DCHECK_LE(db->Remaining(), state->frame_header().payload_length); + // PRIORITY frames have no flags. + DCHECK_EQ(0, state->frame_header().flags); + state->InitializeRemainders(); + return HandleStatus( + state, state->StartDecodingStructureInPayload(&priority_fields_, db)); +} + +DecodeStatus PriorityPayloadDecoder::ResumeDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "PriorityPayloadDecoder::ResumeDecodingPayload" + << " remaining_payload=" << state->remaining_payload() + << " db->Remaining=" << db->Remaining(); + DCHECK_EQ(Http2FrameType::PRIORITY, state->frame_header().type); + DCHECK_LE(db->Remaining(), state->frame_header().payload_length); + return HandleStatus( + state, state->ResumeDecodingStructureInPayload(&priority_fields_, db)); +} + +DecodeStatus PriorityPayloadDecoder::HandleStatus(FrameDecoderState* state, + DecodeStatus status) { + if (status == DecodeStatus::kDecodeDone) { + if (state->remaining_payload() == 0) { + state->listener()->OnPriorityFrame(state->frame_header(), + priority_fields_); + return DecodeStatus::kDecodeDone; + } + // Payload is too long. + return state->ReportFrameSizeError(); + } + // Not done decoding the structure. Either we've got more payload to decode, + // or we've run out because the payload is too short, in which case + // OnFrameSizeError will have already been called. + DCHECK( + (status == DecodeStatus::kDecodeInProgress && + state->remaining_payload() > 0) || + (status == DecodeStatus::kDecodeError && state->remaining_payload() == 0)) + << "\n status=" << status + << "; remaining_payload=" << state->remaining_payload(); + return status; +} + +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/priority_payload_decoder.h b/net/http2/decoder/payload_decoders/priority_payload_decoder.h new file mode 100644 index 0000000..d41a404 --- /dev/null +++ b/net/http2/decoder/payload_decoders/priority_payload_decoder.h
@@ -0,0 +1,44 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_PAYLOAD_DECODERS_PRIORITY_PAYLOAD_DECODER_H_ +#define NET_HTTP2_DECODER_PAYLOAD_DECODERS_PRIORITY_PAYLOAD_DECODER_H_ + +// Decodes the payload of a PRIORITY frame. + +#include "net/base/net_export.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/decoder/frame_decoder_state.h" +#include "net/http2/http2_structures.h" + +namespace net { +namespace test { +class PriorityPayloadDecoderPeer; +} // namespace test + +class NET_EXPORT_PRIVATE PriorityPayloadDecoder { + public: + // Starts the decoding of a PRIORITY frame's payload, and completes it if + // the entire payload is in the provided decode buffer. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a PRIORITY frame that has been split across decode + // buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); + + private: + friend class test::PriorityPayloadDecoderPeer; + + // Determines whether to report the PRIORITY to the listener, wait for more + // input, or to report a Frame Size Error. + DecodeStatus HandleStatus(FrameDecoderState* state, DecodeStatus status); + + Http2PriorityFields priority_fields_; +}; + +} // namespace net + +#endif // NET_HTTP2_DECODER_PAYLOAD_DECODERS_PRIORITY_PAYLOAD_DECODER_H_
diff --git a/net/http2/decoder/payload_decoders/priority_payload_decoder_test.cc b/net/http2/decoder/payload_decoders/priority_payload_decoder_test.cc new file mode 100644 index 0000000..dabcba5 --- /dev/null +++ b/net/http2/decoder/payload_decoders/priority_payload_decoder_test.cc
@@ -0,0 +1,100 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/priority_payload_decoder.h" + +#include <stddef.h> + +#include "base/bind.h" +#include "base/logging.h" +#include "net/http2/decoder/frame_parts.h" +#include "net/http2/decoder/frame_parts_collector.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures_test_util.h" +#include "net/http2/tools/http2_frame_builder.h" +#include "net/http2/tools/http2_random.h" +#include "net/http2/tools/random_decoder_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace test { + +class PriorityPayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { + return Http2FrameType::PRIORITY; + } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; } + + static void Randomize(PriorityPayloadDecoder* p, RandomBase* rng) { + VLOG(1) << "PriorityPayloadDecoderPeer::Randomize"; + test::Randomize(&p->priority_fields_, rng); + } +}; + +namespace { + +struct Listener : public FramePartsCollector { + void OnPriorityFrame(const Http2FrameHeader& header, + const Http2PriorityFields& priority_fields) override { + VLOG(1) << "OnPriority: " << header << "; " << priority_fields; + StartAndEndFrame(header)->OnPriorityFrame(header, priority_fields); + } + + void OnFrameSizeError(const Http2FrameHeader& header) override { + VLOG(1) << "OnFrameSizeError: " << header; + FrameError(header)->OnFrameSizeError(header); + } +}; + +class PriorityPayloadDecoderTest + : public AbstractPayloadDecoderTest<PriorityPayloadDecoder, + PriorityPayloadDecoderPeer, + Listener> { + public: + static bool ApproveSizeForWrongSize(size_t size) { + return size != Http2PriorityFields::EncodedSize(); + } + + protected: + Http2PriorityFields RandPriorityFields() { + Http2PriorityFields fields; + test::Randomize(&fields, RandomPtr()); + return fields; + } +}; + +// Confirm we get an error if the payload is not the correct size to hold +// exactly one Http2PriorityFields. +TEST_F(PriorityPayloadDecoderTest, WrongSize) { + Http2FrameBuilder fb; + fb.Append(RandPriorityFields()); + fb.Append(RandPriorityFields()); + EXPECT_TRUE(VerifyDetectsFrameSizeError( + 0, fb.buffer(), + base::Bind(&PriorityPayloadDecoderTest::ApproveSizeForWrongSize))); +} + +TEST_F(PriorityPayloadDecoderTest, VariousPayloads) { + for (int n = 0; n < 100; ++n) { + Http2PriorityFields fields = RandPriorityFields(); + Http2FrameBuilder fb; + fb.Append(fields); + Http2FrameHeader header(fb.size(), Http2FrameType::PRIORITY, RandFlags(), + RandStreamId()); + set_frame_header(header); + FrameParts expected(header); + expected.opt_priority = fields; + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected)); + } +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/push_promise_payload_decoder.cc b/net/http2/decoder/payload_decoders/push_promise_payload_decoder.cc new file mode 100644 index 0000000..f160d1e --- /dev/null +++ b/net/http2/decoder/payload_decoders/push_promise_payload_decoder.cc
@@ -0,0 +1,172 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/push_promise_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures.h" +#include "net/http2/tools/http2_bug_tracker.h" + +namespace net { + +std::ostream& operator<<(std::ostream& out, + PushPromisePayloadDecoder::PayloadState v) { + switch (v) { + case PushPromisePayloadDecoder::PayloadState::kReadPadLength: + return out << "kReadPadLength"; + case PushPromisePayloadDecoder::PayloadState:: + kStartDecodingPushPromiseFields: + return out << "kStartDecodingPushPromiseFields"; + case PushPromisePayloadDecoder::PayloadState::kReadPayload: + return out << "kReadPayload"; + case PushPromisePayloadDecoder::PayloadState::kSkipPadding: + return out << "kSkipPadding"; + case PushPromisePayloadDecoder::PayloadState:: + kResumeDecodingPushPromiseFields: + return out << "kResumeDecodingPushPromiseFields"; + } + return out << static_cast<int>(v); +} + +DecodeStatus PushPromisePayloadDecoder::StartDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + const Http2FrameHeader& frame_header = state->frame_header(); + const uint32_t total_length = frame_header.payload_length; + + DVLOG(2) << "PushPromisePayloadDecoder::StartDecodingPayload: " + << frame_header; + + DCHECK_EQ(Http2FrameType::PUSH_PROMISE, frame_header.type); + DCHECK_LE(db->Remaining(), total_length); + DCHECK_EQ( + 0, frame_header.flags & + ~(Http2FrameFlag::FLAG_END_HEADERS | Http2FrameFlag::FLAG_PADDED)); + + if (!frame_header.IsPadded()) { + // If it turns out that PUSH_PROMISE frames without padding are sufficiently + // common, and that they are usually short enough that they fit entirely + // into one DecodeBuffer, we can detect that here and implement a special + // case, avoiding the state machine in ResumeDecodingPayload. + payload_state_ = PayloadState::kStartDecodingPushPromiseFields; + } else { + payload_state_ = PayloadState::kReadPadLength; + } + state->InitializeRemainders(); + return ResumeDecodingPayload(state, db); +} + +DecodeStatus PushPromisePayloadDecoder::ResumeDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "UnknownPayloadDecoder::ResumeDecodingPayload" + << " remaining_payload=" << state->remaining_payload() + << " db->Remaining=" << db->Remaining(); + + const Http2FrameHeader& frame_header = state->frame_header(); + DCHECK_EQ(Http2FrameType::PUSH_PROMISE, frame_header.type); + DCHECK_LE(state->remaining_payload(), frame_header.payload_length); + DCHECK_LE(db->Remaining(), frame_header.payload_length); + + DecodeStatus status; + while (true) { + DVLOG(2) + << "PushPromisePayloadDecoder::ResumeDecodingPayload payload_state_=" + << payload_state_; + switch (payload_state_) { + case PayloadState::kReadPadLength: + DCHECK_EQ(state->remaining_payload(), frame_header.payload_length); + // ReadPadLength handles the OnPadLength callback, and updating the + // remaining_payload and remaining_padding fields. If the amount of + // padding is too large to fit in the frame's payload, ReadPadLength + // instead calls OnPaddingTooLong and returns kDecodeError. + // Suppress the call to OnPadLength because we haven't yet called + // OnPushPromiseStart, which needs to wait until we've decoded the + // Promised Stream ID. + status = state->ReadPadLength(db, /*report_pad_length*/ false); + if (status != DecodeStatus::kDecodeDone) { + payload_state_ = PayloadState::kReadPadLength; + return status; + } + // FALLTHROUGH_INTENDED + + case PayloadState::kStartDecodingPushPromiseFields: + status = + state->StartDecodingStructureInPayload(&push_promise_fields_, db); + if (status != DecodeStatus::kDecodeDone) { + payload_state_ = PayloadState::kResumeDecodingPushPromiseFields; + return status; + } + // Finished decoding the Promised Stream ID. Can now tell the listener + // that we're starting to decode a PUSH_PROMISE frame. + ReportPushPromise(state); + // FALLTHROUGH_INTENDED + + case PayloadState::kReadPayload: + DCHECK_LT(state->remaining_payload(), frame_header.payload_length); + DCHECK_LE(state->remaining_payload(), + frame_header.payload_length - + Http2PushPromiseFields::EncodedSize()); + DCHECK_LE( + state->remaining_payload(), + frame_header.payload_length - + Http2PushPromiseFields::EncodedSize() - + (frame_header.IsPadded() ? (1 + state->remaining_padding()) + : 0)); + { + size_t avail = state->AvailablePayload(db); + state->listener()->OnHpackFragment(db->cursor(), avail); + db->AdvanceCursor(avail); + state->ConsumePayload(avail); + } + if (state->remaining_payload() > 0) { + payload_state_ = PayloadState::kReadPayload; + return DecodeStatus::kDecodeInProgress; + } + // FALLTHROUGH_INTENDED + + case PayloadState::kSkipPadding: + // SkipPadding handles the OnPadding callback. + if (state->SkipPadding(db)) { + state->listener()->OnPushPromiseEnd(); + return DecodeStatus::kDecodeDone; + } + payload_state_ = PayloadState::kSkipPadding; + return DecodeStatus::kDecodeInProgress; + + case PayloadState::kResumeDecodingPushPromiseFields: + status = + state->ResumeDecodingStructureInPayload(&push_promise_fields_, db); + if (status == DecodeStatus::kDecodeDone) { + // Finished decoding the Promised Stream ID. Can now tell the listener + // that we're starting to decode a PUSH_PROMISE frame. + ReportPushPromise(state); + payload_state_ = PayloadState::kReadPayload; + continue; + } + payload_state_ = PayloadState::kResumeDecodingPushPromiseFields; + return status; + } + HTTP2_BUG << "PayloadState: " << payload_state_; + } +} + +void PushPromisePayloadDecoder::ReportPushPromise(FrameDecoderState* state) { + const Http2FrameHeader& frame_header = state->frame_header(); + if (frame_header.IsPadded()) { + state->listener()->OnPushPromiseStart(frame_header, push_promise_fields_, + 1 + state->remaining_padding()); + } else { + state->listener()->OnPushPromiseStart(frame_header, push_promise_fields_, + 0); + } +} + +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/push_promise_payload_decoder.h b/net/http2/decoder/payload_decoders/push_promise_payload_decoder.h new file mode 100644 index 0000000..fe6c523 --- /dev/null +++ b/net/http2/decoder/payload_decoders/push_promise_payload_decoder.h
@@ -0,0 +1,66 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_PAYLOAD_DECODERS_PUSH_PROMISE_PAYLOAD_DECODER_H_ +#define NET_HTTP2_DECODER_PAYLOAD_DECODERS_PUSH_PROMISE_PAYLOAD_DECODER_H_ + +// Decodes the payload of a PUSH_PROMISE frame. + +#include "net/base/net_export.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/decoder/frame_decoder_state.h" +#include "net/http2/http2_structures.h" + +namespace net { +namespace test { +class PushPromisePayloadDecoderPeer; +} // namespace test + +class NET_EXPORT_PRIVATE PushPromisePayloadDecoder { + public: + // States during decoding of a PUSH_PROMISE frame. + enum class PayloadState { + // The frame is padded and we need to read the PAD_LENGTH field (1 byte). + kReadPadLength, + + // Ready to start decoding the fixed size fields of the PUSH_PROMISE + // frame into push_promise_fields_. + kStartDecodingPushPromiseFields, + + // The decoder has already called OnPushPromiseStart, and is now reporting + // the HPACK block fragment to the listener's OnHpackFragment method. + kReadPayload, + + // The decoder has finished with the HPACK block fragment, and is now + // ready to skip the trailing padding, if the frame has any. + kSkipPadding, + + // The fixed size fields weren't all available when the decoder first tried + // to decode them (state kStartDecodingPushPromiseFields); this state + // resumes the decoding when ResumeDecodingPayload is called later. + kResumeDecodingPushPromiseFields, + }; + + // Starts the decoding of a PUSH_PROMISE frame's payload, and completes it if + // the entire payload is in the provided decode buffer. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a PUSH_PROMISE frame's payload that has been split across + // decode buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); + + private: + friend class test::PushPromisePayloadDecoderPeer; + + void ReportPushPromise(FrameDecoderState* state); + + PayloadState payload_state_; + Http2PushPromiseFields push_promise_fields_; +}; + +} // namespace net + +#endif // NET_HTTP2_DECODER_PAYLOAD_DECODERS_PUSH_PROMISE_PAYLOAD_DECODER_H_
diff --git a/net/http2/decoder/payload_decoders/push_promise_payload_decoder_test.cc b/net/http2/decoder/payload_decoders/push_promise_payload_decoder_test.cc new file mode 100644 index 0000000..ba1a815 --- /dev/null +++ b/net/http2/decoder/payload_decoders/push_promise_payload_decoder_test.cc
@@ -0,0 +1,151 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/push_promise_payload_decoder.h" + +#include <stddef.h> + +#include <string> + +#include "base/bind.h" +#include "base/logging.h" +#include "net/http2/decoder/frame_parts.h" +#include "net/http2/decoder/frame_parts_collector.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures_test_util.h" +#include "net/http2/tools/http2_frame_builder.h" +#include "net/http2/tools/http2_random.h" +#include "net/http2/tools/random_decoder_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +using std::string; + +namespace net { +namespace test { + +// Provides friend access to an instance of the payload decoder, and also +// provides info to aid in testing. +class PushPromisePayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { + return Http2FrameType::PUSH_PROMISE; + } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { + return Http2FrameFlag::FLAG_PADDED; + } + + static void Randomize(PushPromisePayloadDecoder* p, RandomBase* rng) { + VLOG(1) << "PushPromisePayloadDecoderPeer::Randomize"; + CorruptEnum(&p->payload_state_, rng); + test::Randomize(&p->push_promise_fields_, rng); + } +}; + +namespace { + +// Listener listens for only those methods expected by the payload decoder +// under test, and forwards them onto the FrameParts instance for the current +// frame. +struct Listener : public FramePartsCollector { + void OnPushPromiseStart(const Http2FrameHeader& header, + const Http2PushPromiseFields& promise, + size_t total_padding_length) override { + VLOG(1) << "OnPushPromiseStart header: " << header + << " promise: " << promise + << " total_padding_length: " << total_padding_length; + EXPECT_EQ(Http2FrameType::PUSH_PROMISE, header.type); + StartFrame(header)->OnPushPromiseStart(header, promise, + total_padding_length); + } + + void OnHpackFragment(const char* data, size_t len) override { + VLOG(1) << "OnHpackFragment: len=" << len; + CurrentFrame()->OnHpackFragment(data, len); + } + + void OnPushPromiseEnd() override { + VLOG(1) << "OnPushPromiseEnd"; + EndFrame()->OnPushPromiseEnd(); + } + + void OnPadding(const char* padding, size_t skipped_length) override { + VLOG(1) << "OnPadding: " << skipped_length; + CurrentFrame()->OnPadding(padding, skipped_length); + } + + void OnPaddingTooLong(const Http2FrameHeader& header, + size_t missing_length) override { + VLOG(1) << "OnPaddingTooLong: " << header + << "; missing_length: " << missing_length; + FrameError(header)->OnPaddingTooLong(header, missing_length); + } + + void OnFrameSizeError(const Http2FrameHeader& header) override { + VLOG(1) << "OnFrameSizeError: " << header; + FrameError(header)->OnFrameSizeError(header); + } +}; + +class PushPromisePayloadDecoderTest + : public AbstractPaddablePayloadDecoderTest<PushPromisePayloadDecoder, + PushPromisePayloadDecoderPeer, + Listener> { + public: + static bool ApproveSizeForTruncated(size_t size) { + return size != Http2PushPromiseFields::EncodedSize(); + } +}; + +INSTANTIATE_TEST_CASE_P(VariousPadLengths, + PushPromisePayloadDecoderTest, + ::testing::Values(0, 1, 2, 3, 4, 254, 255, 256)); + +// Payload contains the required Http2PushPromiseFields, followed by some +// (fake) HPACK payload. +TEST_P(PushPromisePayloadDecoderTest, VariousHpackPayloadSizes) { + for (size_t hpack_size : {0, 1, 2, 3, 255, 256, 1024}) { + LOG(INFO) << "########### hpack_size = " << hpack_size << " ###########"; + Reset(); + string hpack_payload = Random().RandString(hpack_size); + Http2PushPromiseFields push_promise{RandStreamId()}; + frame_builder_.Append(push_promise); + frame_builder_.Append(hpack_payload); + MaybeAppendTrailingPadding(); + Http2FrameHeader frame_header(frame_builder_.size(), + Http2FrameType::PUSH_PROMISE, RandFlags(), + RandStreamId()); + set_frame_header(frame_header); + FrameParts expected(frame_header, hpack_payload, total_pad_length_); + expected.opt_push_promise = push_promise; + EXPECT_TRUE( + DecodePayloadAndValidateSeveralWays(frame_builder_.buffer(), expected)); + } +} + +// Confirm we get an error if the payload is not long enough for the required +// portion of the payload, regardless of the amount of (valid) padding. +TEST_P(PushPromisePayloadDecoderTest, Truncated) { + Http2PushPromiseFields push_promise{RandStreamId()}; + Http2FrameBuilder fb; + fb.Append(push_promise); + EXPECT_TRUE(VerifyDetectsMultipleFrameSizeErrors( + 0, fb.buffer(), + base::Bind(&PushPromisePayloadDecoderTest::ApproveSizeForTruncated), + total_pad_length_)); +} + +// Confirm we get an error if the PADDED flag is set but the payload is not +// long enough to hold even the Pad Length amount of padding. +TEST_P(PushPromisePayloadDecoderTest, PaddingTooLong) { + EXPECT_TRUE(VerifyDetectsPaddingTooLong()); +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/rst_stream_payload_decoder.cc b/net/http2/decoder/payload_decoders/rst_stream_payload_decoder.cc new file mode 100644 index 0000000..e9d86fb --- /dev/null +++ b/net/http2/decoder/payload_decoders/rst_stream_payload_decoder.cc
@@ -0,0 +1,66 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/rst_stream_payload_decoder.h" + +#include "base/logging.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures.h" + +namespace net { + +DecodeStatus RstStreamPayloadDecoder::StartDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "RstStreamPayloadDecoder::StartDecodingPayload: " + << state->frame_header(); + DCHECK_EQ(Http2FrameType::RST_STREAM, state->frame_header().type); + DCHECK_LE(db->Remaining(), state->frame_header().payload_length); + // RST_STREAM has no flags. + DCHECK_EQ(0, state->frame_header().flags); + state->InitializeRemainders(); + return HandleStatus( + state, state->StartDecodingStructureInPayload(&rst_stream_fields_, db)); +} + +DecodeStatus RstStreamPayloadDecoder::ResumeDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "RstStreamPayloadDecoder::ResumeDecodingPayload" + << " remaining_payload=" << state->remaining_payload() + << " db->Remaining=" << db->Remaining(); + DCHECK_EQ(Http2FrameType::RST_STREAM, state->frame_header().type); + DCHECK_LE(db->Remaining(), state->frame_header().payload_length); + return HandleStatus( + state, state->ResumeDecodingStructureInPayload(&rst_stream_fields_, db)); +} + +DecodeStatus RstStreamPayloadDecoder::HandleStatus(FrameDecoderState* state, + DecodeStatus status) { + DVLOG(2) << "HandleStatus: status=" << status + << "; remaining_payload=" << state->remaining_payload(); + if (status == DecodeStatus::kDecodeDone) { + if (state->remaining_payload() == 0) { + state->listener()->OnRstStream(state->frame_header(), + rst_stream_fields_.error_code); + return DecodeStatus::kDecodeDone; + } + // Payload is too long. + return state->ReportFrameSizeError(); + } + // Not done decoding the structure. Either we've got more payload to decode, + // or we've run out because the payload is too short, in which case + // OnFrameSizeError will have already been called by the FrameDecoderState. + DCHECK( + (status == DecodeStatus::kDecodeInProgress && + state->remaining_payload() > 0) || + (status == DecodeStatus::kDecodeError && state->remaining_payload() == 0)) + << "\n status=" << status + << "; remaining_payload=" << state->remaining_payload(); + return status; +} + +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/rst_stream_payload_decoder.h b/net/http2/decoder/payload_decoders/rst_stream_payload_decoder.h new file mode 100644 index 0000000..cdfd6587 --- /dev/null +++ b/net/http2/decoder/payload_decoders/rst_stream_payload_decoder.h
@@ -0,0 +1,42 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_PAYLOAD_DECODERS_RST_STREAM_PAYLOAD_DECODER_H_ +#define NET_HTTP2_DECODER_PAYLOAD_DECODERS_RST_STREAM_PAYLOAD_DECODER_H_ + +// Decodes the payload of a RST_STREAM frame. + +#include "net/base/net_export.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/decoder/frame_decoder_state.h" +#include "net/http2/http2_structures.h" + +namespace net { +namespace test { +class RstStreamPayloadDecoderPeer; +} // namespace test + +class NET_EXPORT_PRIVATE RstStreamPayloadDecoder { + public: + // Starts the decoding of a RST_STREAM frame's payload, and completes it if + // the entire payload is in the provided decode buffer. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a RST_STREAM frame's payload that has been split across + // decode buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); + + private: + friend class test::RstStreamPayloadDecoderPeer; + + DecodeStatus HandleStatus(FrameDecoderState* state, DecodeStatus status); + + Http2RstStreamFields rst_stream_fields_; +}; + +} // namespace net + +#endif // NET_HTTP2_DECODER_PAYLOAD_DECODERS_RST_STREAM_PAYLOAD_DECODER_H_
diff --git a/net/http2/decoder/payload_decoders/rst_stream_payload_decoder_test.cc b/net/http2/decoder/payload_decoders/rst_stream_payload_decoder_test.cc new file mode 100644 index 0000000..703d98ff --- /dev/null +++ b/net/http2/decoder/payload_decoders/rst_stream_payload_decoder_test.cc
@@ -0,0 +1,102 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/rst_stream_payload_decoder.h" + +#include <stddef.h> + +#include "base/bind.h" +#include "base/logging.h" +#include "net/http2/decoder/frame_parts.h" +#include "net/http2/decoder/frame_parts_collector.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_constants_test_util.h" +#include "net/http2/http2_structures_test_util.h" +#include "net/http2/tools/http2_frame_builder.h" +#include "net/http2/tools/http2_random.h" +#include "net/http2/tools/random_decoder_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace test { + +class RstStreamPayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { + return Http2FrameType::RST_STREAM; + } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; } + + static void Randomize(RstStreamPayloadDecoder* p, RandomBase* rng) { + VLOG(1) << "RstStreamPayloadDecoderPeer::Randomize"; + test::Randomize(&p->rst_stream_fields_, rng); + } +}; + +namespace { + +struct Listener : public FramePartsCollector { + void OnRstStream(const Http2FrameHeader& header, + Http2ErrorCode error_code) override { + VLOG(1) << "OnRstStream: " << header << "; error_code=" << error_code; + StartAndEndFrame(header)->OnRstStream(header, error_code); + } + + void OnFrameSizeError(const Http2FrameHeader& header) override { + VLOG(1) << "OnFrameSizeError: " << header; + FrameError(header)->OnFrameSizeError(header); + } +}; + +class RstStreamPayloadDecoderTest + : public AbstractPayloadDecoderTest<RstStreamPayloadDecoder, + RstStreamPayloadDecoderPeer, + Listener> { + public: + static bool ApproveSizeForWrongSize(size_t size) { + return size != Http2RstStreamFields::EncodedSize(); + } + + protected: + Http2RstStreamFields RandRstStreamFields() { + Http2RstStreamFields fields; + test::Randomize(&fields, RandomPtr()); + return fields; + } +}; + +// Confirm we get an error if the payload is not the correct size to hold +// exactly one Http2RstStreamFields. +TEST_F(RstStreamPayloadDecoderTest, WrongSize) { + Http2FrameBuilder fb; + fb.Append(RandRstStreamFields()); + fb.Append(RandRstStreamFields()); + fb.Append(RandRstStreamFields()); + EXPECT_TRUE(VerifyDetectsFrameSizeError( + 0, fb.buffer(), + base::Bind(&RstStreamPayloadDecoderTest::ApproveSizeForWrongSize))); +} + +TEST_F(RstStreamPayloadDecoderTest, AllErrors) { + for (auto error_code : AllHttp2ErrorCodes()) { + Http2RstStreamFields fields{error_code}; + Http2FrameBuilder fb; + fb.Append(fields); + Http2FrameHeader header(fb.size(), Http2FrameType::RST_STREAM, RandFlags(), + RandStreamId()); + set_frame_header(header); + FrameParts expected(header); + expected.opt_rst_stream_error_code = error_code; + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected)); + } +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/settings_payload_decoder.cc b/net/http2/decoder/payload_decoders/settings_payload_decoder.cc new file mode 100644 index 0000000..f8cc52cb --- /dev/null +++ b/net/http2/decoder/payload_decoders/settings_payload_decoder.cc
@@ -0,0 +1,97 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/settings_payload_decoder.h" + +#include "base/logging.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures.h" + +namespace net { + +DecodeStatus SettingsPayloadDecoder::StartDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + const Http2FrameHeader& frame_header = state->frame_header(); + const uint32_t total_length = frame_header.payload_length; + + DVLOG(2) << "SettingsPayloadDecoder::StartDecodingPayload: " << frame_header; + DCHECK_EQ(Http2FrameType::SETTINGS, frame_header.type); + DCHECK_LE(db->Remaining(), total_length); + DCHECK_EQ(0, frame_header.flags & ~(Http2FrameFlag::FLAG_ACK)); + + if (frame_header.IsAck()) { + if (total_length == 0) { + state->listener()->OnSettingsAck(frame_header); + return DecodeStatus::kDecodeDone; + } else { + state->InitializeRemainders(); + return state->ReportFrameSizeError(); + } + } else { + state->InitializeRemainders(); + state->listener()->OnSettingsStart(frame_header); + return StartDecodingSettings(state, db); + } +} + +DecodeStatus SettingsPayloadDecoder::ResumeDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "SettingsPayloadDecoder::ResumeDecodingPayload" + << " remaining_payload=" << state->remaining_payload() + << " db->Remaining=" << db->Remaining(); + DCHECK_EQ(Http2FrameType::SETTINGS, state->frame_header().type); + DCHECK_LE(db->Remaining(), state->frame_header().payload_length); + + DecodeStatus status = + state->ResumeDecodingStructureInPayload(&setting_fields_, db); + if (status == DecodeStatus::kDecodeDone) { + state->listener()->OnSetting(setting_fields_); + return StartDecodingSettings(state, db); + } + return HandleNotDone(state, db, status); +} + +DecodeStatus SettingsPayloadDecoder::StartDecodingSettings( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "SettingsPayloadDecoder::StartDecodingSettings" + << " remaining_payload=" << state->remaining_payload() + << " db->Remaining=" << db->Remaining(); + while (state->remaining_payload() > 0) { + DecodeStatus status = + state->StartDecodingStructureInPayload(&setting_fields_, db); + if (status == DecodeStatus::kDecodeDone) { + state->listener()->OnSetting(setting_fields_); + continue; + } + return HandleNotDone(state, db, status); + } + DVLOG(2) << "LEAVING SettingsPayloadDecoder::StartDecodingSettings" + << "\n\tdb->Remaining=" << db->Remaining() + << "\n\t remaining_payload=" << state->remaining_payload(); + state->listener()->OnSettingsEnd(); + return DecodeStatus::kDecodeDone; +} + +DecodeStatus SettingsPayloadDecoder::HandleNotDone(FrameDecoderState* state, + DecodeBuffer* db, + DecodeStatus status) { + // Not done decoding the structure. Either we've got more payload to decode, + // or we've run out because the payload is too short, in which case + // OnFrameSizeError will have already been called. + DCHECK( + (status == DecodeStatus::kDecodeInProgress && + state->remaining_payload() > 0) || + (status == DecodeStatus::kDecodeError && state->remaining_payload() == 0)) + << "\n status=" << status + << "; remaining_payload=" << state->remaining_payload() + << "; db->Remaining=" << db->Remaining(); + return status; +} + +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/settings_payload_decoder.h b/net/http2/decoder/payload_decoders/settings_payload_decoder.h new file mode 100644 index 0000000..40d3c36 --- /dev/null +++ b/net/http2/decoder/payload_decoders/settings_payload_decoder.h
@@ -0,0 +1,54 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_PAYLOAD_DECODERS_SETTINGS_PAYLOAD_DECODER_H_ +#define NET_HTTP2_DECODER_PAYLOAD_DECODERS_SETTINGS_PAYLOAD_DECODER_H_ + +// Decodes the payload of a SETTINGS frame; for the RFC, see: +// http://httpwg.org/specs/rfc7540.html#SETTINGS + +#include "net/base/net_export.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/decoder/frame_decoder_state.h" +#include "net/http2/http2_structures.h" + +namespace net { +namespace test { +class SettingsPayloadDecoderPeer; +} // namespace test + +class NET_EXPORT_PRIVATE SettingsPayloadDecoder { + public: + // Starts the decoding of a SETTINGS frame's payload, and completes it if + // the entire payload is in the provided decode buffer. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a SETTINGS frame that has been split across decode + // buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); + + private: + friend class test::SettingsPayloadDecoderPeer; + + // Decodes as many settings as are available in the decode buffer, starting at + // the first byte of one setting; if a single setting is split across buffers, + // ResumeDecodingPayload will handle starting from where the previous call + // left off, and then will call StartDecodingSettings. + DecodeStatus StartDecodingSettings(FrameDecoderState* state, + DecodeBuffer* db); + + // Decoding a single SETTING returned a status other than kDecodeDone; this + // method just brings together the DCHECKs to reduce duplication. + DecodeStatus HandleNotDone(FrameDecoderState* state, + DecodeBuffer* db, + DecodeStatus status); + + Http2SettingFields setting_fields_; +}; + +} // namespace net + +#endif // NET_HTTP2_DECODER_PAYLOAD_DECODERS_SETTINGS_PAYLOAD_DECODER_H_
diff --git a/net/http2/decoder/payload_decoders/settings_payload_decoder_test.cc b/net/http2/decoder/payload_decoders/settings_payload_decoder_test.cc new file mode 100644 index 0000000..7a53797 --- /dev/null +++ b/net/http2/decoder/payload_decoders/settings_payload_decoder_test.cc
@@ -0,0 +1,176 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/settings_payload_decoder.h" + +#include <stddef.h> + +#include <vector> + +#include "base/bind.h" +#include "base/logging.h" +#include "net/http2/decoder/frame_parts.h" +#include "net/http2/decoder/frame_parts_collector.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_constants_test_util.h" +#include "net/http2/http2_structures_test_util.h" +#include "net/http2/tools/http2_frame_builder.h" +#include "net/http2/tools/http2_random.h" +#include "net/http2/tools/random_decoder_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace test { + +class SettingsPayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { + return Http2FrameType::SETTINGS; + } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { + return Http2FrameFlag::FLAG_ACK; + } + + static void Randomize(SettingsPayloadDecoder* p, RandomBase* rng) { + VLOG(1) << "SettingsPayloadDecoderPeer::Randomize"; + test::Randomize(&p->setting_fields_, rng); + } +}; + +namespace { + +struct Listener : public FramePartsCollector { + void OnSettingsStart(const Http2FrameHeader& header) override { + VLOG(1) << "OnSettingsStart: " << header; + EXPECT_EQ(Http2FrameType::SETTINGS, header.type) << header; + EXPECT_EQ(Http2FrameFlag(), header.flags) << header; + StartFrame(header)->OnSettingsStart(header); + } + + void OnSetting(const Http2SettingFields& setting_fields) override { + VLOG(1) << "Http2SettingFields: setting_fields=" << setting_fields; + CurrentFrame()->OnSetting(setting_fields); + } + + void OnSettingsEnd() override { + VLOG(1) << "OnSettingsEnd"; + EndFrame()->OnSettingsEnd(); + } + + void OnSettingsAck(const Http2FrameHeader& header) override { + VLOG(1) << "OnSettingsAck: " << header; + StartAndEndFrame(header)->OnSettingsAck(header); + } + + void OnFrameSizeError(const Http2FrameHeader& header) override { + VLOG(1) << "OnFrameSizeError: " << header; + FrameError(header)->OnFrameSizeError(header); + } +}; + +class SettingsPayloadDecoderTest + : public AbstractPayloadDecoderTest<SettingsPayloadDecoder, + SettingsPayloadDecoderPeer, + Listener> { + public: + static bool ApproveSizeForSettingsWrongSize(size_t size) { + // Should get an error if size is not an integral multiple of the size + // of one setting. + return 0 != (size % Http2SettingFields::EncodedSize()); + } + + static bool ApproveSizeForSettingsAkcWrongSize(size_t size) { + return size != 0; + } + + protected: + Http2SettingFields RandSettingsFields() { + Http2SettingFields fields; + test::Randomize(&fields, RandomPtr()); + return fields; + } +}; + +// Confirm we get an error if the SETTINGS payload is not the correct size +// to hold exactly zero or more whole Http2SettingFields. +TEST_F(SettingsPayloadDecoderTest, SettingsWrongSize) { + Http2FrameBuilder fb; + fb.Append(RandSettingsFields()); + fb.Append(RandSettingsFields()); + fb.Append(RandSettingsFields()); + EXPECT_TRUE(VerifyDetectsFrameSizeError( + 0, fb.buffer(), + base::Bind( + &SettingsPayloadDecoderTest::ApproveSizeForSettingsWrongSize))); +} + +// Confirm we get an error if the SETTINGS ACK payload is not empty. +TEST_F(SettingsPayloadDecoderTest, SettingsAkcWrongSize) { + Http2FrameBuilder fb; + fb.Append(RandSettingsFields()); + fb.Append(RandSettingsFields()); + fb.Append(RandSettingsFields()); + EXPECT_TRUE(VerifyDetectsFrameSizeError( + Http2FrameFlag::FLAG_ACK, fb.buffer(), + base::Bind( + &SettingsPayloadDecoderTest::ApproveSizeForSettingsAkcWrongSize))); +} + +// SETTINGS must have stream_id==0, but the payload decoder doesn't check that. +TEST_F(SettingsPayloadDecoderTest, SettingsAck) { + for (int stream_id = 0; stream_id < 3; ++stream_id) { + Http2FrameHeader header(0, Http2FrameType::SETTINGS, + RandFlags() | Http2FrameFlag::FLAG_ACK, stream_id); + set_frame_header(header); + FrameParts expected(header); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays("", expected)); + } +} + +// Try several values of each known SETTINGS parameter. +TEST_F(SettingsPayloadDecoderTest, OneRealSetting) { + std::vector<uint32_t> values = {0, 1, 0xffffffff, Random().Rand32()}; + for (auto param : AllHttp2SettingsParameters()) { + for (uint32_t value : values) { + Http2SettingFields fields(param, value); + Http2FrameBuilder fb; + fb.Append(fields); + Http2FrameHeader header(fb.size(), Http2FrameType::SETTINGS, RandFlags(), + RandStreamId()); + set_frame_header(header); + FrameParts expected(header); + expected.settings.push_back(fields); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected)); + } + } +} + +// Decode a SETTINGS frame with lots of fields. +TEST_F(SettingsPayloadDecoderTest, ManySettings) { + const uint32_t num_settings = 100; + const uint32_t size = Http2SettingFields::EncodedSize() * num_settings; + Http2FrameHeader header(size, Http2FrameType::SETTINGS, + RandFlags(), // & ~Http2FrameFlag::FLAG_ACK, + RandStreamId()); + set_frame_header(header); + FrameParts expected(header); + Http2FrameBuilder fb; + for (size_t n = 0; n < num_settings; ++n) { + Http2SettingFields fields(static_cast<Http2SettingsParameter>(n), + Random().Rand32()); + fb.Append(fields); + expected.settings.push_back(fields); + } + ASSERT_EQ(size, fb.size()); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected)); +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/unknown_payload_decoder.cc b/net/http2/decoder/payload_decoders/unknown_payload_decoder.cc new file mode 100644 index 0000000..ec020c1 --- /dev/null +++ b/net/http2/decoder/payload_decoders/unknown_payload_decoder.cc
@@ -0,0 +1,55 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/unknown_payload_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures.h" + +namespace net { + +DecodeStatus UnknownPayloadDecoder::StartDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + const Http2FrameHeader& frame_header = state->frame_header(); + + DVLOG(2) << "UnknownPayloadDecoder::StartDecodingPayload: " << frame_header; + DCHECK(!IsSupportedHttp2FrameType(frame_header.type)) << frame_header; + DCHECK_LE(db->Remaining(), frame_header.payload_length); + + state->InitializeRemainders(); + state->listener()->OnUnknownStart(frame_header); + return ResumeDecodingPayload(state, db); +} + +DecodeStatus UnknownPayloadDecoder::ResumeDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "UnknownPayloadDecoder::ResumeDecodingPayload " + << "remaining_payload=" << state->remaining_payload() + << "; db->Remaining=" << db->Remaining(); + DCHECK(!IsSupportedHttp2FrameType(state->frame_header().type)) + << state->frame_header(); + DCHECK_LE(state->remaining_payload(), state->frame_header().payload_length); + DCHECK_LE(db->Remaining(), state->remaining_payload()); + + size_t avail = db->Remaining(); + if (avail > 0) { + state->listener()->OnUnknownPayload(db->cursor(), avail); + db->AdvanceCursor(avail); + state->ConsumePayload(avail); + } + if (state->remaining_payload() == 0) { + state->listener()->OnUnknownEnd(); + return DecodeStatus::kDecodeDone; + } + return DecodeStatus::kDecodeInProgress; +} + +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/unknown_payload_decoder.h b/net/http2/decoder/payload_decoders/unknown_payload_decoder.h new file mode 100644 index 0000000..26a1ee2 --- /dev/null +++ b/net/http2/decoder/payload_decoders/unknown_payload_decoder.h
@@ -0,0 +1,33 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_PAYLOAD_DECODERS_UNKNOWN_PAYLOAD_DECODER_H_ +#define NET_HTTP2_DECODER_PAYLOAD_DECODERS_UNKNOWN_PAYLOAD_DECODER_H_ + +// Decodes the payload of a frame whose type unknown. According to the HTTP/2 +// specification (http://httpwg.org/specs/rfc7540.html#FrameHeader): +// Implementations MUST ignore and discard any frame that has +// a type that is unknown. + +#include "net/base/net_export.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/decoder/frame_decoder_state.h" + +namespace net { + +class NET_EXPORT_PRIVATE UnknownPayloadDecoder { + public: + // Starts decoding a payload of unknown type; just passes it to the listener. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a payload of unknown type that has been split across + // decode buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); +}; + +} // namespace net + +#endif // NET_HTTP2_DECODER_PAYLOAD_DECODERS_UNKNOWN_PAYLOAD_DECODER_H_
diff --git a/net/http2/decoder/payload_decoders/unknown_payload_decoder_test.cc b/net/http2/decoder/payload_decoders/unknown_payload_decoder_test.cc new file mode 100644 index 0000000..302cec47 --- /dev/null +++ b/net/http2/decoder/payload_decoders/unknown_payload_decoder_test.cc
@@ -0,0 +1,108 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/unknown_payload_decoder.h" + +#include <stddef.h> + +#include <string> +#include <type_traits> + +#include "base/logging.h" +#include "net/http2/decoder/frame_parts.h" +#include "net/http2/decoder/frame_parts_collector.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures.h" +#include "net/http2/tools/http2_random.h" +#include "net/http2/tools/random_decoder_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +using std::string; + +namespace net { +namespace test { +namespace { +Http2FrameType g_unknown_frame_type; +} // namespace + +// Provides friend access to an instance of the payload decoder, and also +// provides info to aid in testing. +class UnknownPayloadDecoderPeer { + public: + static Http2FrameType FrameType() { return g_unknown_frame_type; } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; } + + static void Randomize(UnknownPayloadDecoder* p, RandomBase* rng) { + // UnknownPayloadDecoder has no fields, so there is nothing to randomize. + static_assert(std::is_empty<UnknownPayloadDecoder>::value, + "Need to randomize fields of UnknownPayloadDecoder"); + } +}; + +namespace { + +struct Listener : public FramePartsCollector { + void OnUnknownStart(const Http2FrameHeader& header) override { + VLOG(1) << "OnUnknownStart: " << header; + StartFrame(header)->OnUnknownStart(header); + } + + void OnUnknownPayload(const char* data, size_t len) override { + VLOG(1) << "OnUnknownPayload: len=" << len; + CurrentFrame()->OnUnknownPayload(data, len); + } + + void OnUnknownEnd() override { + VLOG(1) << "OnUnknownEnd"; + EndFrame()->OnUnknownEnd(); + } +}; + +constexpr bool SupportedFrameType = false; + +class UnknownPayloadDecoderTest + : public AbstractPayloadDecoderTest<UnknownPayloadDecoder, + UnknownPayloadDecoderPeer, + Listener, + SupportedFrameType>, + public ::testing::WithParamInterface<uint32_t> { + protected: + UnknownPayloadDecoderTest() : length_(GetParam()) { + VLOG(1) << "################ length_=" << length_ << " ################"; + + // Each test case will choose a random frame type that isn't supported. + do { + g_unknown_frame_type = static_cast<Http2FrameType>(Random().Rand8()); + } while (IsSupportedHttp2FrameType(g_unknown_frame_type)); + } + + const uint32_t length_; +}; + +INSTANTIATE_TEST_CASE_P(VariousLengths, + UnknownPayloadDecoderTest, + ::testing::Values(0, 1, 2, 3, 255, 256)); + +TEST_P(UnknownPayloadDecoderTest, ValidLength) { + string unknown_payload = Random().RandString(length_); + Http2FrameHeader frame_header(length_, g_unknown_frame_type, Random().Rand8(), + RandStreamId()); + set_frame_header(frame_header); + FrameParts expected(frame_header, unknown_payload); + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(unknown_payload, expected)); + // TODO(jamessynge): Check here (and in other such tests) that the fast + // and slow decode counts are both non-zero. Perhaps also add some kind of + // test for the listener having been called. That could simply be a test + // that there is a single collected FrameParts instance, and that it matches + // expected. +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/window_update_payload_decoder.cc b/net/http2/decoder/payload_decoders/window_update_payload_decoder.cc new file mode 100644 index 0000000..bc1e54ce1 --- /dev/null +++ b/net/http2/decoder/payload_decoders/window_update_payload_decoder.cc
@@ -0,0 +1,82 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/window_update_payload_decoder.h" + +#include "base/logging.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_http2_structures.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures.h" + +namespace net { + +DecodeStatus WindowUpdatePayloadDecoder::StartDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + const Http2FrameHeader& frame_header = state->frame_header(); + const uint32_t total_length = frame_header.payload_length; + + DVLOG(2) << "WindowUpdatePayloadDecoder::StartDecodingPayload: " + << frame_header; + + DCHECK_EQ(Http2FrameType::WINDOW_UPDATE, frame_header.type); + DCHECK_LE(db->Remaining(), total_length); + + // WINDOW_UPDATE frames have no flags. + DCHECK_EQ(0, frame_header.flags); + + // Special case for when the payload is the correct size and entirely in + // the buffer. + if (db->Remaining() == Http2WindowUpdateFields::EncodedSize() && + total_length == Http2WindowUpdateFields::EncodedSize()) { + DoDecode(&window_update_fields_, db); + state->listener()->OnWindowUpdate( + frame_header, window_update_fields_.window_size_increment); + return DecodeStatus::kDecodeDone; + } + state->InitializeRemainders(); + return HandleStatus(state, state->StartDecodingStructureInPayload( + &window_update_fields_, db)); +} + +DecodeStatus WindowUpdatePayloadDecoder::ResumeDecodingPayload( + FrameDecoderState* state, + DecodeBuffer* db) { + DVLOG(2) << "ResumeDecodingPayload: remaining_payload=" + << state->remaining_payload() + << "; db->Remaining=" << db->Remaining(); + DCHECK_EQ(Http2FrameType::WINDOW_UPDATE, state->frame_header().type); + DCHECK_LE(db->Remaining(), state->frame_header().payload_length); + return HandleStatus(state, state->ResumeDecodingStructureInPayload( + &window_update_fields_, db)); +} + +DecodeStatus WindowUpdatePayloadDecoder::HandleStatus(FrameDecoderState* state, + DecodeStatus status) { + DVLOG(2) << "HandleStatus: status=" << status + << "; remaining_payload=" << state->remaining_payload(); + if (status == DecodeStatus::kDecodeDone) { + if (state->remaining_payload() == 0) { + state->listener()->OnWindowUpdate( + state->frame_header(), window_update_fields_.window_size_increment); + return DecodeStatus::kDecodeDone; + } + // Payload is too long. + return state->ReportFrameSizeError(); + } + // Not done decoding the structure. Either we've got more payload to decode, + // or we've run out because the payload is too short, in which case + // OnFrameSizeError will have already been called. + DCHECK( + (status == DecodeStatus::kDecodeInProgress && + state->remaining_payload() > 0) || + (status == DecodeStatus::kDecodeError && state->remaining_payload() == 0)) + << "\n status=" << status + << "; remaining_payload=" << state->remaining_payload(); + return status; +} + +} // namespace net
diff --git a/net/http2/decoder/payload_decoders/window_update_payload_decoder.h b/net/http2/decoder/payload_decoders/window_update_payload_decoder.h new file mode 100644 index 0000000..5764361 --- /dev/null +++ b/net/http2/decoder/payload_decoders/window_update_payload_decoder.h
@@ -0,0 +1,42 @@ +// Copyright 2016 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 NET_HTTP2_DECODER_PAYLOAD_DECODERS_WINDOW_UPDATE_PAYLOAD_DECODER_H_ +#define NET_HTTP2_DECODER_PAYLOAD_DECODERS_WINDOW_UPDATE_PAYLOAD_DECODER_H_ + +// Decodes the payload of a WINDOW_UPDATE frame. + +#include "net/base/net_export.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/decoder/frame_decoder_state.h" +#include "net/http2/http2_structures.h" + +namespace net { +namespace test { +class WindowUpdatePayloadDecoderPeer; +} // namespace test + +class NET_EXPORT_PRIVATE WindowUpdatePayloadDecoder { + public: + // Starts decoding a WINDOW_UPDATE frame's payload, and completes it if + // the entire payload is in the provided decode buffer. + DecodeStatus StartDecodingPayload(FrameDecoderState* state, DecodeBuffer* db); + + // Resumes decoding a WINDOW_UPDATE frame's payload that has been split across + // decode buffers. + DecodeStatus ResumeDecodingPayload(FrameDecoderState* state, + DecodeBuffer* db); + + private: + friend class test::WindowUpdatePayloadDecoderPeer; + + DecodeStatus HandleStatus(FrameDecoderState* state, DecodeStatus status); + + Http2WindowUpdateFields window_update_fields_; +}; + +} // namespace net + +#endif // NET_HTTP2_DECODER_PAYLOAD_DECODERS_WINDOW_UPDATE_PAYLOAD_DECODER_H_
diff --git a/net/http2/decoder/payload_decoders/window_update_payload_decoder_test.cc b/net/http2/decoder/payload_decoders/window_update_payload_decoder_test.cc new file mode 100644 index 0000000..5517dab --- /dev/null +++ b/net/http2/decoder/payload_decoders/window_update_payload_decoder_test.cc
@@ -0,0 +1,106 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/decoder/payload_decoders/window_update_payload_decoder.h" + +#include <stddef.h> + +#include "base/bind.h" +#include "base/logging.h" +#include "net/http2/decoder/frame_parts.h" +#include "net/http2/decoder/frame_parts_collector.h" +#include "net/http2/decoder/http2_frame_decoder_listener.h" +#include "net/http2/decoder/payload_decoders/payload_decoder_base_test_util.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures_test_util.h" +#include "net/http2/tools/http2_frame_builder.h" +#include "net/http2/tools/http2_random.h" +#include "net/http2/tools/random_decoder_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace test { + +class WindowUpdatePayloadDecoderPeer { + public: + static constexpr Http2FrameType FrameType() { + return Http2FrameType::WINDOW_UPDATE; + } + + // Returns the mask of flags that affect the decoding of the payload (i.e. + // flags that that indicate the presence of certain fields or padding). + static constexpr uint8_t FlagsAffectingPayloadDecoding() { return 0; } + + static void Randomize(WindowUpdatePayloadDecoder* p, RandomBase* rng) { + test::Randomize(&p->window_update_fields_, rng); + VLOG(1) << "WindowUpdatePayloadDecoderPeer::Randomize " + << "window_update_fields_: " << p->window_update_fields_; + } +}; + +namespace { + +struct Listener : public FramePartsCollector { + void OnWindowUpdate(const Http2FrameHeader& header, + uint32_t window_size_increment) override { + VLOG(1) << "OnWindowUpdate: " << header + << "; window_size_increment=" << window_size_increment; + EXPECT_EQ(Http2FrameType::WINDOW_UPDATE, header.type); + StartAndEndFrame(header)->OnWindowUpdate(header, window_size_increment); + } + + void OnFrameSizeError(const Http2FrameHeader& header) override { + VLOG(1) << "OnFrameSizeError: " << header; + FrameError(header)->OnFrameSizeError(header); + } +}; + +class WindowUpdatePayloadDecoderTest + : public AbstractPayloadDecoderTest<WindowUpdatePayloadDecoder, + WindowUpdatePayloadDecoderPeer, + Listener> { + public: + static bool ApproveSizeForWrongSize(size_t size) { + return size != Http2WindowUpdateFields::EncodedSize(); + } + + protected: + Http2WindowUpdateFields RandWindowUpdateFields() { + Http2WindowUpdateFields fields; + test::Randomize(&fields, RandomPtr()); + VLOG(3) << "RandWindowUpdateFields: " << fields; + return fields; + } +}; + +// Confirm we get an error if the payload is not the correct size to hold +// exactly one Http2WindowUpdateFields. +TEST_F(WindowUpdatePayloadDecoderTest, WrongSize) { + Http2FrameBuilder fb; + fb.Append(RandWindowUpdateFields()); + fb.Append(RandWindowUpdateFields()); + fb.Append(RandWindowUpdateFields()); + EXPECT_TRUE(VerifyDetectsFrameSizeError( + 0, fb.buffer(), + base::Bind(&WindowUpdatePayloadDecoderTest::ApproveSizeForWrongSize))); +} + +TEST_F(WindowUpdatePayloadDecoderTest, VariousPayloads) { + for (int n = 0; n < 100; ++n) { + uint32_t stream_id = n == 0 ? 0 : RandStreamId(); + Http2WindowUpdateFields fields = RandWindowUpdateFields(); + Http2FrameBuilder fb; + fb.Append(fields); + Http2FrameHeader header(fb.size(), Http2FrameType::WINDOW_UPDATE, + RandFlags(), stream_id); + set_frame_header(header); + FrameParts expected(header); + expected.opt_window_update_increment = fields.window_size_increment; + EXPECT_TRUE(DecodePayloadAndValidateSeveralWays(fb.buffer(), expected)); + } +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/hpack/decoder/hpack_block_collector.cc b/net/http2/hpack/decoder/hpack_block_collector.cc new file mode 100644 index 0000000..94f58ee4 --- /dev/null +++ b/net/http2/hpack/decoder/hpack_block_collector.cc
@@ -0,0 +1,154 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/decoder/hpack_block_collector.h" + +#include <algorithm> +#include <memory> + +#include "base/logging.h" +#include "net/http2/tools/failure.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using std::string; + +using base::StringPiece; + +namespace net { +namespace test { + +HpackBlockCollector::HpackBlockCollector() {} +HpackBlockCollector::HpackBlockCollector(const HpackBlockCollector& other) + : pending_entry_(other.pending_entry_), entries_(other.entries_) {} +HpackBlockCollector::~HpackBlockCollector() {} + +void HpackBlockCollector::OnIndexedHeader(size_t index) { + pending_entry_.OnIndexedHeader(index); + PushPendingEntry(); +} +void HpackBlockCollector::OnDynamicTableSizeUpdate(size_t size) { + pending_entry_.OnDynamicTableSizeUpdate(size); + PushPendingEntry(); +} +void HpackBlockCollector::OnStartLiteralHeader(HpackEntryType header_type, + size_t maybe_name_index) { + pending_entry_.OnStartLiteralHeader(header_type, maybe_name_index); +} +void HpackBlockCollector::OnNameStart(bool huffman_encoded, size_t len) { + pending_entry_.OnNameStart(huffman_encoded, len); +} +void HpackBlockCollector::OnNameData(const char* data, size_t len) { + pending_entry_.OnNameData(data, len); +} +void HpackBlockCollector::OnNameEnd() { + pending_entry_.OnNameEnd(); +} +void HpackBlockCollector::OnValueStart(bool huffman_encoded, size_t len) { + pending_entry_.OnValueStart(huffman_encoded, len); +} +void HpackBlockCollector::OnValueData(const char* data, size_t len) { + pending_entry_.OnValueData(data, len); +} +void HpackBlockCollector::OnValueEnd() { + pending_entry_.OnValueEnd(); + PushPendingEntry(); +} + +void HpackBlockCollector::PushPendingEntry() { + EXPECT_TRUE(pending_entry_.IsComplete()); + DVLOG(2) << "PushPendingEntry: " << pending_entry_; + entries_.push_back(pending_entry_); + EXPECT_TRUE(entries_.back().IsComplete()); + pending_entry_.Clear(); +} +void HpackBlockCollector::Clear() { + pending_entry_.Clear(); + entries_.clear(); +} + +void HpackBlockCollector::ExpectIndexedHeader(size_t index) { + entries_.push_back( + HpackEntryCollector(HpackEntryType::kIndexedHeader, index)); +} +void HpackBlockCollector::ExpectDynamicTableSizeUpdate(size_t size) { + entries_.push_back( + HpackEntryCollector(HpackEntryType::kDynamicTableSizeUpdate, size)); +} +void HpackBlockCollector::ExpectNameIndexAndLiteralValue(HpackEntryType type, + size_t index, + bool value_huffman, + const string& value) { + entries_.push_back(HpackEntryCollector(type, index, value_huffman, value)); +} +void HpackBlockCollector::ExpectLiteralNameAndValue(HpackEntryType type, + bool name_huffman, + const string& name, + bool value_huffman, + const string& value) { + entries_.push_back( + HpackEntryCollector(type, name_huffman, name, value_huffman, value)); +} + +void HpackBlockCollector::ShuffleEntries(RandomBase* rng) { + std::random_shuffle(entries_.begin(), entries_.end()); +} + +void HpackBlockCollector::AppendToHpackBlockBuilder( + HpackBlockBuilder* hbb) const { + CHECK(IsNotPending()); + for (const auto& entry : entries_) { + entry.AppendToHpackBlockBuilder(hbb); + } +} + +AssertionResult HpackBlockCollector::ValidateSoleIndexedHeader( + size_t ndx) const { + VERIFY_TRUE(pending_entry_.IsClear()); + VERIFY_EQ(1u, entries_.size()); + VERIFY_TRUE(entries_.front().ValidateIndexedHeader(ndx)); + return AssertionSuccess(); +} +AssertionResult HpackBlockCollector::ValidateSoleLiteralValueHeader( + HpackEntryType expected_type, + size_t expected_index, + bool expected_value_huffman, + StringPiece expected_value) const { + VERIFY_TRUE(pending_entry_.IsClear()); + VERIFY_EQ(1u, entries_.size()); + VERIFY_TRUE(entries_.front().ValidateLiteralValueHeader( + expected_type, expected_index, expected_value_huffman, expected_value)); + return AssertionSuccess(); +} +AssertionResult HpackBlockCollector::ValidateSoleLiteralNameValueHeader( + HpackEntryType expected_type, + bool expected_name_huffman, + StringPiece expected_name, + bool expected_value_huffman, + StringPiece expected_value) const { + VERIFY_TRUE(pending_entry_.IsClear()); + VERIFY_EQ(1u, entries_.size()); + VERIFY_TRUE(entries_.front().ValidateLiteralNameValueHeader( + expected_type, expected_name_huffman, expected_name, + expected_value_huffman, expected_value)); + return AssertionSuccess(); +} +AssertionResult HpackBlockCollector::ValidateSoleDynamicTableSizeUpdate( + size_t size) const { + VERIFY_TRUE(pending_entry_.IsClear()); + VERIFY_EQ(1u, entries_.size()); + VERIFY_TRUE(entries_.front().ValidateDynamicTableSizeUpdate(size)); + return AssertionSuccess(); +} + +AssertionResult HpackBlockCollector::VerifyEq( + const HpackBlockCollector& that) const { + VERIFY_EQ(pending_entry_, that.pending_entry_); + VERIFY_EQ(entries_, that.entries_); + return AssertionSuccess(); +} + +} // namespace test +} // namespace net
diff --git a/net/http2/hpack/decoder/hpack_block_collector.h b/net/http2/hpack/decoder/hpack_block_collector.h new file mode 100644 index 0000000..2b283b9 --- /dev/null +++ b/net/http2/hpack/decoder/hpack_block_collector.h
@@ -0,0 +1,128 @@ +// Copyright 2016 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 NET_HTTP2_HPACK_DECODER_HPACK_BLOCK_COLLECTOR_H_ +#define NET_HTTP2_HPACK_DECODER_HPACK_BLOCK_COLLECTOR_H_ + +// HpackBlockCollector implements HpackEntryDecoderListener in order to record +// the calls using HpackEntryCollector instances (one per HPACK entry). This +// supports testing of HpackBlockDecoder, which decodes entire HPACK blocks. +// +// In addition to implementing the callback methods, HpackBlockCollector also +// supports comparing two HpackBlockCollector instances (i.e. an expected and +// an actual), or a sole HPACK entry against an expected value. + +#include <stddef.h> + +#include <string> +#include <vector> + +#include "base/strings/string_piece.h" +#include "net/http2/hpack/decoder/hpack_entry_collector.h" +#include "net/http2/hpack/decoder/hpack_entry_decoder_listener.h" +#include "net/http2/hpack/http2_hpack_constants.h" +#include "net/http2/hpack/tools/hpack_block_builder.h" + +namespace net { +namespace test { + +class RandomBase; + +class HpackBlockCollector : public HpackEntryDecoderListener { + public: + // Implementations of HpackEntryDecoderListener, forwarding to pending_entry_, + // an HpackEntryCollector for the "in-progress" HPACK entry. OnIndexedHeader + // and OnDynamicTableSizeUpdate are pending only for that one call, while + // OnStartLiteralHeader is followed by many calls, ending with OnValueEnd. + // Once all the calls for one HPACK entry have been received, PushPendingEntry + // is used to append the pending_entry_ entry to the collected entries_. + HpackBlockCollector(); + HpackBlockCollector(const HpackBlockCollector& other); + ~HpackBlockCollector() override; + void OnIndexedHeader(size_t index) override; + void OnDynamicTableSizeUpdate(size_t size) override; + void OnStartLiteralHeader(HpackEntryType header_type, + size_t maybe_name_index) override; + void OnNameStart(bool huffman_encoded, size_t len) override; + void OnNameData(const char* data, size_t len) override; + void OnNameEnd() override; + void OnValueStart(bool huffman_encoded, size_t len) override; + void OnValueData(const char* data, size_t len) override; + void OnValueEnd() override; + + // Methods for creating a set of expectations (i.e. HPACK entries to compare + // against those collected by another instance of HpackBlockCollector). + + // Add an HPACK entry for an indexed header. + void ExpectIndexedHeader(size_t index); + + // Add an HPACK entry for a dynamic table size update. + void ExpectDynamicTableSizeUpdate(size_t size); + + // Add an HPACK entry for a header entry with an index for the name, and a + // literal value. + void ExpectNameIndexAndLiteralValue(HpackEntryType type, + size_t index, + bool value_huffman, + const std::string& value); + + // Add an HPACK entry for a header entry with a literal name and value. + void ExpectLiteralNameAndValue(HpackEntryType type, + bool name_huffman, + const std::string& name, + bool value_huffman, + const std::string& value); + + // Shuffle the entries, in support of generating an HPACK block of entries + // in some random order. + void ShuffleEntries(RandomBase* rng); + + // Serialize entries_ to the HpackBlockBuilder. + void AppendToHpackBlockBuilder(HpackBlockBuilder* hbb) const; + + // Return AssertionSuccess if there is just one entry, and it is an + // Indexed Header with the specified index. + ::testing::AssertionResult ValidateSoleIndexedHeader(size_t ndx) const; + + // Return AssertionSuccess if there is just one entry, and it is a + // Dynamic Table Size Update with the specified size. + ::testing::AssertionResult ValidateSoleDynamicTableSizeUpdate( + size_t size) const; + + // Return AssertionSuccess if there is just one entry, and it is a Header + // entry with an index for the name and a literal value. + ::testing::AssertionResult ValidateSoleLiteralValueHeader( + HpackEntryType expected_type, + size_t expected_index, + bool expected_value_huffman, + base::StringPiece expected_value) const; + + // Return AssertionSuccess if there is just one entry, and it is a Header + // with a literal name and literal value. + ::testing::AssertionResult ValidateSoleLiteralNameValueHeader( + HpackEntryType expected_type, + bool expected_name_huffman, + base::StringPiece expected_name, + bool expected_value_huffman, + base::StringPiece expected_value) const; + + bool IsNotPending() const { return pending_entry_.IsClear(); } + bool IsClear() const { return IsNotPending() && entries_.empty(); } + void Clear(); + + ::testing::AssertionResult VerifyEq(const HpackBlockCollector& that) const; + + private: + // Push the value of pending_entry_ onto entries_, and clear pending_entry_. + // The pending_entry_ must be complete. + void PushPendingEntry(); + + HpackEntryCollector pending_entry_; + std::vector<HpackEntryCollector> entries_; +}; + +} // namespace test +} // namespace net + +#endif // NET_HTTP2_HPACK_DECODER_HPACK_BLOCK_COLLECTOR_H_
diff --git a/net/http2/hpack/decoder/hpack_block_decoder.cc b/net/http2/hpack/decoder/hpack_block_decoder.cc new file mode 100644 index 0000000..29b6869 --- /dev/null +++ b/net/http2/hpack/decoder/hpack_block_decoder.cc
@@ -0,0 +1,65 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/decoder/hpack_block_decoder.h" + +#include <stdint.h> + +#include <sstream> +#include <string> + +#include "base/logging.h" + +namespace net { + +DecodeStatus HpackBlockDecoder::Decode(DecodeBuffer* db) { + if (!before_entry_) { + DVLOG(2) << "HpackBlockDecoder::Decode resume entry, db->Remaining=" + << db->Remaining(); + DecodeStatus status = entry_decoder_.Resume(db, listener_); + switch (status) { + case DecodeStatus::kDecodeDone: + before_entry_ = true; + break; + case DecodeStatus::kDecodeInProgress: + DCHECK_EQ(0u, db->Remaining()); + return DecodeStatus::kDecodeInProgress; + case DecodeStatus::kDecodeError: + return DecodeStatus::kDecodeError; + } + } + DCHECK(before_entry_); + while (db->HasData()) { + DVLOG(2) << "HpackBlockDecoder::Decode start entry, db->Remaining=" + << db->Remaining(); + DecodeStatus status = entry_decoder_.Start(db, listener_); + switch (status) { + case DecodeStatus::kDecodeDone: + continue; + case DecodeStatus::kDecodeInProgress: + DCHECK_EQ(0u, db->Remaining()); + before_entry_ = false; + return DecodeStatus::kDecodeInProgress; + case DecodeStatus::kDecodeError: + return DecodeStatus::kDecodeError; + } + DCHECK(false); + } + DCHECK(before_entry_); + return DecodeStatus::kDecodeDone; +} + +std::string HpackBlockDecoder::DebugString() const { + std::stringstream ss; + ss << "HpackBlockDecoder(" << entry_decoder_.DebugString() << ", listener@" + << std::hex << reinterpret_cast<intptr_t>(listener_) + << (before_entry_ ? ", between entries)" : ", in an entry)"); + return ss.str(); +} + +std::ostream& operator<<(std::ostream& out, const HpackBlockDecoder& v) { + return out << v.DebugString(); +} + +} // namespace net
diff --git a/net/http2/hpack/decoder/hpack_block_decoder.h b/net/http2/hpack/decoder/hpack_block_decoder.h new file mode 100644 index 0000000..49e8ae1 --- /dev/null +++ b/net/http2/hpack/decoder/hpack_block_decoder.h
@@ -0,0 +1,69 @@ +// Copyright 2016 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 NET_HTTP2_HPACK_DECODER_HPACK_BLOCK_DECODER_H_ +#define NET_HTTP2_HPACK_DECODER_HPACK_BLOCK_DECODER_H_ + +// HpackBlockDecoder decodes an entire HPACK block (or the available portion +// thereof in the DecodeBuffer) into entries, but doesn't include HPACK static +// or dynamic table support, so table indices remain indices at this level. +// Reports the entries to an HpackEntryDecoderListener. + +#include <string> + +#include "base/logging.h" +#include "base/macros.h" +#include "net/base/net_export.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/hpack/decoder/hpack_entry_decoder.h" +#include "net/http2/hpack/decoder/hpack_entry_decoder_listener.h" + +namespace net { + +class NET_EXPORT_PRIVATE HpackBlockDecoder { + public: + explicit HpackBlockDecoder(HpackEntryDecoderListener* listener) + : listener_(listener) { + DCHECK_NE(listener_, nullptr); + } + ~HpackBlockDecoder() {} + + // The listener may be changed at any time. The change takes effect on the + // next entry into the decode loop of the Decode() method below. + void set_listener(HpackEntryDecoderListener* listener) { + DCHECK_NE(nullptr, listener); + listener_ = listener; + } + HpackEntryDecoderListener* listener() { return listener_; } + + // Prepares the decoder to start decoding a new HPACK block. Expected + // to be called from an implementation of Http2FrameDecoderListener's + // OnHeadersStart or OnPushPromiseStart methods. + void Reset() { + DVLOG(2) << "HpackBlockDecoder::Reset"; + before_entry_ = true; + } + + // Decode the fragment of the HPACK block contained in the decode buffer. + // Expected to be called from an implementation of Http2FrameDecoderListener's + // OnHpackFragment method. + DecodeStatus Decode(DecodeBuffer* db); + + std::string DebugString() const; + + private: + HpackEntryDecoder entry_decoder_; + HpackEntryDecoderListener* listener_; + bool before_entry_ = true; + + DISALLOW_COPY_AND_ASSIGN(HpackBlockDecoder); +}; + +NET_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const HpackBlockDecoder& v); + +} // namespace net + +#endif // NET_HTTP2_HPACK_DECODER_HPACK_BLOCK_DECODER_H_
diff --git a/net/http2/hpack/decoder/hpack_block_decoder_test.cc b/net/http2/hpack/decoder/hpack_block_decoder_test.cc new file mode 100644 index 0000000..421f0fc5 --- /dev/null +++ b/net/http2/hpack/decoder/hpack_block_decoder_test.cc
@@ -0,0 +1,315 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/decoder/hpack_block_decoder.h" + +// Tests of HpackBlockDecoder. + +#include <sstream> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/hpack/decoder/hpack_block_collector.h" +#include "net/http2/hpack/http2_hpack_constants.h" +#include "net/http2/hpack/tools/hpack_block_builder.h" +#include "net/http2/hpack/tools/hpack_example.h" +#include "net/http2/tools/failure.h" +#include "net/http2/tools/http2_random.h" +#include "net/http2/tools/random_decoder_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::AssertionSuccess; +using std::string; +using base::StringPiece; + +namespace net { +namespace test { +namespace { + +class HpackBlockDecoderTest : public RandomDecoderTest { + public: + AssertionResult VerifyExpected(const HpackBlockCollector& expected) { + VERIFY_AND_RETURN_SUCCESS(collector_.VerifyEq(expected)); + } + + AssertionResult ValidateForSpecExample_C_2_1() { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateSoleLiteralNameValueHeader( + HpackEntryType::kIndexedLiteralHeader, false, "custom-key", false, + "custom-header")); + } + + AssertionResult ValidateForSpecExample_C_2_2() { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateSoleLiteralValueHeader( + HpackEntryType::kUnindexedLiteralHeader, 4, false, "/sample/path")); + } + + AssertionResult ValidateForSpecExample_C_2_3() { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateSoleLiteralNameValueHeader( + HpackEntryType::kNeverIndexedLiteralHeader, false, "password", false, + "secret")); + } + + AssertionResult ValidateForSpecExample_C_2_4() { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateSoleIndexedHeader(2)); + } + + protected: + HpackBlockDecoderTest() : listener_(&collector_), decoder_(&listener_) { + stop_decode_on_done_ = false; + decoder_.Reset(); + // Make sure logging doesn't crash. Not examining the result. + std::ostringstream strm; + strm << decoder_; + } + + DecodeStatus StartDecoding(DecodeBuffer* db) override { + collector_.Clear(); + decoder_.Reset(); + return ResumeDecoding(db); + } + + DecodeStatus ResumeDecoding(DecodeBuffer* db) override { + DecodeStatus status = decoder_.Decode(db); + + // Make sure logging doesn't crash. Not examining the result. + std::ostringstream strm; + strm << decoder_; + + return status; + } + + AssertionResult DecodeAndValidateSeveralWays(DecodeBuffer* db, + Validator validator) { + bool return_non_zero_on_first = false; + return RandomDecoderTest::DecodeAndValidateSeveralWays( + db, return_non_zero_on_first, validator); + } + + AssertionResult DecodeAndValidateSeveralWays(const HpackBlockBuilder& hbb, + Validator validator) { + DecodeBuffer db(hbb.buffer()); + return DecodeAndValidateSeveralWays(&db, validator); + } + + AssertionResult DecodeHpackExampleAndValidateSeveralWays( + StringPiece hpack_example, + Validator validator) { + string input = HpackExampleToStringOrDie(hpack_example); + DecodeBuffer db(input); + return DecodeAndValidateSeveralWays(&db, validator); + } + + uint8_t Rand8() { return Random().Rand8(); } + + string Rand8String() { return Random().RandString(Rand8()); } + + HpackBlockCollector collector_; + HpackEntryDecoderVLoggingListener listener_; + HpackBlockDecoder decoder_; +}; + +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.1 +TEST_F(HpackBlockDecoderTest, SpecExample_C_2_1) { + NoArgValidator do_check = + base::Bind(&HpackBlockDecoderTest::ValidateForSpecExample_C_2_1, + base::Unretained(this)); + EXPECT_TRUE( + DecodeHpackExampleAndValidateSeveralWays(R"( + 40 | == Literal indexed == + 0a | Literal name (len = 10) + 6375 7374 6f6d 2d6b 6579 | custom-key + 0d | Literal value (len = 13) + 6375 7374 6f6d 2d68 6561 6465 72 | custom-header + | -> custom-key: + | custom-header + )", + ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check.Run()); +} + +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.2 +TEST_F(HpackBlockDecoderTest, SpecExample_C_2_2) { + NoArgValidator do_check = + base::Bind(&HpackBlockDecoderTest::ValidateForSpecExample_C_2_2, + base::Unretained(this)); + EXPECT_TRUE( + DecodeHpackExampleAndValidateSeveralWays(R"( + 04 | == Literal not indexed == + | Indexed name (idx = 4) + | :path + 0c | Literal value (len = 12) + 2f73 616d 706c 652f 7061 7468 | /sample/path + | -> :path: /sample/path + )", + ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check.Run()); +} + +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.3 +TEST_F(HpackBlockDecoderTest, SpecExample_C_2_3) { + NoArgValidator do_check = + base::Bind(&HpackBlockDecoderTest::ValidateForSpecExample_C_2_3, + base::Unretained(this)); + EXPECT_TRUE( + DecodeHpackExampleAndValidateSeveralWays(R"( + 10 | == Literal never indexed == + 08 | Literal name (len = 8) + 7061 7373 776f 7264 | password + 06 | Literal value (len = 6) + 7365 6372 6574 | secret + | -> password: secret + )", + ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check.Run()); +} + +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.2.4 +TEST_F(HpackBlockDecoderTest, SpecExample_C_2_4) { + NoArgValidator do_check = + base::Bind(&HpackBlockDecoderTest::ValidateForSpecExample_C_2_4, + base::Unretained(this)); + EXPECT_TRUE( + DecodeHpackExampleAndValidateSeveralWays(R"( + 82 | == Indexed - Add == + | idx = 2 + | -> :method: GET + )", + ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check.Run()); +} +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.3.1 +TEST_F(HpackBlockDecoderTest, SpecExample_C_3_1) { + string example = R"( + 82 | == Indexed - Add == + | idx = 2 + | -> :method: GET + 86 | == Indexed - Add == + | idx = 6 + | -> :scheme: http + 84 | == Indexed - Add == + | idx = 4 + | -> :path: / + 41 | == Literal indexed == + | Indexed name (idx = 1) + | :authority + 0f | Literal value (len = 15) + 7777 772e 6578 616d 706c 652e 636f 6d | www.example.com + | -> :authority: + | www.example.com + )"; + HpackBlockCollector expected; + expected.ExpectIndexedHeader(2); + expected.ExpectIndexedHeader(6); + expected.ExpectIndexedHeader(4); + expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, + 1, false, "www.example.com"); + NoArgValidator do_check = base::Bind(&HpackBlockDecoderTest::VerifyExpected, + base::Unretained(this), expected); + EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays( + example, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check.Run()); +} + +// http://httpwg.org/specs/rfc7541.html#rfc.section.C.5.1 +TEST_F(HpackBlockDecoderTest, SpecExample_C_5_1) { + string example = R"( + 48 | == Literal indexed == + | Indexed name (idx = 8) + | :status + 03 | Literal value (len = 3) + 3330 32 | 302 + | -> :status: 302 + 58 | == Literal indexed == + | Indexed name (idx = 24) + | cache-control + 07 | Literal value (len = 7) + 7072 6976 6174 65 | private + | -> cache-control: private + 61 | == Literal indexed == + | Indexed name (idx = 33) + | date + 1d | Literal value (len = 29) + 4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013 + 2032 303a 3133 3a32 3120 474d 54 | 20:13:21 GMT + | -> date: Mon, 21 Oct 2013 + | 20:13:21 GMT + 6e | == Literal indexed == + | Indexed name (idx = 46) + | location + 17 | Literal value (len = 23) + 6874 7470 733a 2f2f 7777 772e 6578 616d | https://www.exam + 706c 652e 636f 6d | ple.com + | -> location: + | https://www.example.com + )"; + HpackBlockCollector expected; + expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, + 8, false, "302"); + expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, + 24, false, "private"); + expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, + 33, false, + "Mon, 21 Oct 2013 20:13:21 GMT"); + expected.ExpectNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, + 46, false, "https://www.example.com"); + NoArgValidator do_check = base::Bind(&HpackBlockDecoderTest::VerifyExpected, + base::Unretained(this), expected); + EXPECT_TRUE(DecodeHpackExampleAndValidateSeveralWays( + example, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check.Run()); +} + +// Generate a bunch of HPACK block entries to expect, use those expectations +// to generate an HPACK block, then decode it and confirm it matches those +// expectations. Some of these are invalid (such as Indexed, with index=0), +// but well-formed, and the decoder doesn't check for validity, just +// well-formedness. That includes the validity of the strings not being checked, +// such as lower-case ascii for the names, and valid Huffman encodings. +TEST_F(HpackBlockDecoderTest, Computed) { + HpackBlockCollector expected; + expected.ExpectIndexedHeader(0); + expected.ExpectIndexedHeader(1); + expected.ExpectIndexedHeader(126); + expected.ExpectIndexedHeader(127); + expected.ExpectIndexedHeader(128); + expected.ExpectDynamicTableSizeUpdate(0); + expected.ExpectDynamicTableSizeUpdate(1); + expected.ExpectDynamicTableSizeUpdate(14); + expected.ExpectDynamicTableSizeUpdate(15); + expected.ExpectDynamicTableSizeUpdate(30); + expected.ExpectDynamicTableSizeUpdate(31); + expected.ExpectDynamicTableSizeUpdate(4095); + expected.ExpectDynamicTableSizeUpdate(4096); + expected.ExpectDynamicTableSizeUpdate(8192); + for (auto type : {HpackEntryType::kIndexedLiteralHeader, + HpackEntryType::kUnindexedLiteralHeader, + HpackEntryType::kNeverIndexedLiteralHeader}) { + for (bool value_huffman : {false, true}) { + // An entry with an index for the name. Ensure the name index + // is not zero by adding one to the Rand8() result. + expected.ExpectNameIndexAndLiteralValue(type, Rand8() + 1, value_huffman, + Rand8String()); + // And two entries with literal names, one plain, one huffman encoded. + expected.ExpectLiteralNameAndValue(type, false, Rand8String(), + value_huffman, Rand8String()); + expected.ExpectLiteralNameAndValue(type, true, Rand8String(), + value_huffman, Rand8String()); + } + } + // Shuffle the entries and serialize them to produce an HPACK block. + expected.ShuffleEntries(RandomPtr()); + HpackBlockBuilder hbb; + expected.AppendToHpackBlockBuilder(&hbb); + + NoArgValidator do_check = base::Bind(&HpackBlockDecoderTest::VerifyExpected, + base::Unretained(this), expected); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check.Run()); +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/hpack/decoder/hpack_decoder_string_buffer.cc b/net/http2/hpack/decoder/hpack_decoder_string_buffer.cc new file mode 100644 index 0000000..90d0336 --- /dev/null +++ b/net/http2/hpack/decoder/hpack_decoder_string_buffer.cc
@@ -0,0 +1,215 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/decoder/hpack_decoder_string_buffer.h" + +#include "base/logging.h" + +using base::StringPiece; + +namespace net { + +std::ostream& operator<<(std::ostream& out, + const HpackDecoderStringBuffer::State v) { + switch (v) { + case HpackDecoderStringBuffer::State::RESET: + return out << "RESET"; + case HpackDecoderStringBuffer::State::COLLECTING: + return out << "COLLECTING"; + case HpackDecoderStringBuffer::State::COMPLETE: + return out << "COMPLETE"; + default: + return out << "Unknown HpackDecoderStringBuffer::State!"; + } +} + +std::ostream& operator<<(std::ostream& out, + const HpackDecoderStringBuffer::Backing v) { + switch (v) { + case HpackDecoderStringBuffer::Backing::RESET: + return out << "RESET"; + case HpackDecoderStringBuffer::Backing::UNBUFFERED: + return out << "UNBUFFERED"; + case HpackDecoderStringBuffer::Backing::BUFFERED: + return out << "BUFFERED"; + case HpackDecoderStringBuffer::Backing::STATIC: + return out << "STATIC"; + default: + return out << "Unknown HpackDecoderStringBuffer::Backing!"; + } +} + +HpackDecoderStringBuffer::HpackDecoderStringBuffer() { + Reset(); +} +HpackDecoderStringBuffer::~HpackDecoderStringBuffer() {} + +// TODO(jamessynge): Consider eliminating most of Reset (i.e. do less); in +// particular, if a variable won't be read again until after it is next set +// (e.g. is_huffman_encoded_ or remaining_len_), then it doesn't need to be +// cleared here. This will be easier when not supporting both HpackDecoder2 +// (in net/spdy/hpack) and HpackWholeEntryDecoder, so we can eliminate +// the Set() and str() methods. +void HpackDecoderStringBuffer::Reset() { + DVLOG(3) << "HpackDecoderStringBuffer::Reset"; + buffer_.clear(); + value_.clear(); + remaining_len_ = 0; + is_huffman_encoded_ = false; + state_ = State::RESET; + backing_ = Backing::RESET; +} + +void HpackDecoderStringBuffer::Set(StringPiece value, bool is_static) { + DVLOG(2) << "HpackDecoderStringBuffer::Set"; + DCHECK_EQ(state_, State::RESET); + DCHECK_EQ(backing_, Backing::RESET); + value_ = value; + state_ = State::COMPLETE; + backing_ = is_static ? Backing::STATIC : Backing::UNBUFFERED; +} + +void HpackDecoderStringBuffer::OnStart(bool huffman_encoded, size_t len) { + DVLOG(2) << "HpackDecoderStringBuffer::OnStart"; + DCHECK_EQ(state_, State::RESET); + DCHECK_EQ(backing_, Backing::RESET); + buffer_.clear(); + value_.clear(); + + remaining_len_ = len; + is_huffman_encoded_ = huffman_encoded; + + state_ = State::COLLECTING; + + if (huffman_encoded) { + decoder_.Reset(); + backing_ = Backing::BUFFERED; + + // Reserve space in buffer_ for the uncompressed string, assuming the + // maximum expansion. The shortest Huffman codes in the RFC are 5 bits long, + // which then expand to 8 bits during decoding (i.e. each code is for one + // plain text octet, aka byte), so the maximum size is 60% longer than the + // encoded size. + len = len * 8 / 5; + if (buffer_.capacity() < len) { + buffer_.reserve(len); + } + } else { + // Assume for now that we won't need to use buffer_, so don't reserve space + // in it. + backing_ = Backing::RESET; + } +} + +bool HpackDecoderStringBuffer::OnData(const char* data, size_t len) { + DVLOG(2) << "HpackDecoderStringBuffer::OnData state=" << state_ + << ", backing=" << backing_; + DCHECK_EQ(state_, State::COLLECTING); + DCHECK_LE(len, remaining_len_); + remaining_len_ -= len; + + if (is_huffman_encoded_) { + DCHECK_EQ(backing_, Backing::BUFFERED); + // We don't set value_ for buffered strings until OnEnd, + // so it should be empty. + DCHECK_EQ(0u, value_.size()); + return decoder_.Decode(StringPiece(data, len), &buffer_); + } + + if (backing_ == Backing::RESET) { + // This is the first call to OnData. + DCHECK_EQ(0u, buffer_.size()); + DCHECK_EQ(0u, value_.size()); + // If data contains the entire string, don't copy the string. If we later + // find that the HPACK entry is split across input buffers, then we'll + // copy the string into buffer_. + if (remaining_len_ == 0) { + value_ = StringPiece(data, len); + backing_ = Backing::UNBUFFERED; + return true; + } + + // We need to buffer the string because it is split across input buffers. + backing_ = Backing::BUFFERED; + buffer_.assign(data, len); + return true; + } + + // This is not the first call to OnData for this string, so it should be + // buffered. + DCHECK_EQ(backing_, Backing::BUFFERED); + // We don't set value_ for buffered strings until OnEnd, so it should be + // empty. + DCHECK_EQ(0u, value_.size()); + + // Append to the current contents of the buffer. + buffer_.append(data, len); + return true; +} + +bool HpackDecoderStringBuffer::OnEnd() { + DVLOG(2) << "HpackDecoderStringBuffer::OnEnd"; + DCHECK_EQ(state_, State::COLLECTING); + DCHECK_EQ(0u, remaining_len_); + + if (is_huffman_encoded_) { + DCHECK_EQ(backing_, Backing::BUFFERED); + // Did the Huffman encoding of the string end properly? + if (!decoder_.InputProperlyTerminated()) { + return false; // No, it didn't. + } + } + state_ = State::COMPLETE; + if (backing_ == Backing::BUFFERED) { + value_ = buffer_; + } + return true; +} + +void HpackDecoderStringBuffer::BufferStringIfUnbuffered() { + DVLOG(3) << "HpackDecoderStringBuffer::BufferStringIfUnbuffered state=" + << state_ << ", backing=" << backing_; + if (state_ != State::RESET && backing_ == Backing::UNBUFFERED) { + DVLOG(2) << "HpackDecoderStringBuffer buffering string of length " + << value_.size(); + value_.CopyToString(&buffer_); + if (state_ == State::COMPLETE) { + value_ = buffer_; + } + backing_ = Backing::BUFFERED; + } +} + +size_t HpackDecoderStringBuffer::BufferedLength() const { + DVLOG(3) << "HpackDecoderStringBuffer::BufferedLength"; + return backing_ == Backing::BUFFERED ? buffer_.size() : 0; +} + +StringPiece HpackDecoderStringBuffer::str() const { + DVLOG(3) << "HpackDecoderStringBuffer::str"; + DCHECK_EQ(state_, State::COMPLETE); + return value_; +} + +void HpackDecoderStringBuffer::OutputDebugStringTo(std::ostream& out) const { + out << "{state=" << state_; + if (state_ != State::RESET) { + out << ", backing=" << backing_; + out << ", remaining_len=" << remaining_len_; + out << ", is_huffman_encoded=" << is_huffman_encoded_; + if (backing_ == Backing::BUFFERED) { + out << ", buffer: " << buffer_; + } else { + out << ", value: " << value_; + } + } + out << "}"; +} + +std::ostream& operator<<(std::ostream& out, const HpackDecoderStringBuffer& v) { + v.OutputDebugStringTo(out); + return out; +} + +} // namespace net
diff --git a/net/http2/hpack/decoder/hpack_decoder_string_buffer.h b/net/http2/hpack/decoder/hpack_decoder_string_buffer.h new file mode 100644 index 0000000..a0917a2 --- /dev/null +++ b/net/http2/hpack/decoder/hpack_decoder_string_buffer.h
@@ -0,0 +1,82 @@ +// Copyright 2016 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 NET_HTTP2_HPACK_DECODER_HPACK_DECODER_STRING_BUFFER_H_ +#define NET_HTTP2_HPACK_DECODER_HPACK_DECODER_STRING_BUFFER_H_ + +// HpackDecoderStringBuffer helps an HPACK decoder to avoid copies of a string +// literal (name or value) except when necessary (e.g. when split across two +// or more HPACK block fragments). + +#include <stddef.h> + +#include <ostream> +#include <string> + +#include "base/macros.h" +#include "base/strings/string_piece.h" +#include "net/base/net_export.h" +#include "net/http2/hpack/huffman/http2_hpack_huffman_decoder.h" + +namespace net { + +class NET_EXPORT_PRIVATE HpackDecoderStringBuffer { + public: + enum class State : uint8_t { RESET, COLLECTING, COMPLETE }; + enum class Backing : uint8_t { RESET, UNBUFFERED, BUFFERED, STATIC }; + + HpackDecoderStringBuffer(); + ~HpackDecoderStringBuffer(); + + void Reset(); + void Set(base::StringPiece value, bool is_static); + + // Note that for Huffman encoded strings the length of the string after + // decoding may be larger (expected), the same or even smaller; the latter + // are unlikely, but possible if the encoder makes odd choices. + void OnStart(bool huffman_encoded, size_t len); + bool OnData(const char* data, size_t len); + bool OnEnd(); + void BufferStringIfUnbuffered(); + size_t BufferedLength() const; + + base::StringPiece str() const; + + State state_for_testing() const { return state_; } + Backing backing_for_testing() const { return backing_; } + void OutputDebugStringTo(std::ostream& out) const; + + private: + // Storage for the string being buffered, if buffering is necessary + // (e.g. if Huffman encoded, buffer_ is storage for the decoded string). + std::string buffer_; + + // The StringPiece to be returned by HpackDecoderStringBuffer::str(). If a + // string has been collected, but not buffered, value_ points to that string. + base::StringPiece value_; + + // The decoder to use if the string is Huffman encoded. + HpackHuffmanDecoder decoder_; + + // Count of bytes not yet passed to OnData. + size_t remaining_len_ = 0; + + // Is the HPACK string Huffman encoded? + bool is_huffman_encoded_ = false; + + // State of the string decoding process. + State state_; + + // Where is the string stored? + Backing backing_; + + DISALLOW_COPY_AND_ASSIGN(HpackDecoderStringBuffer); +}; + +NET_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const HpackDecoderStringBuffer& v); + +} // namespace net + +#endif // NET_HTTP2_HPACK_DECODER_HPACK_DECODER_STRING_BUFFER_H_
diff --git a/net/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc b/net/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc new file mode 100644 index 0000000..313a457 --- /dev/null +++ b/net/http2/hpack/decoder/hpack_decoder_string_buffer_test.cc
@@ -0,0 +1,245 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/decoder/hpack_decoder_string_buffer.h" + +// Tests of HpackDecoderStringBuffer. + +#include <initializer_list> +#include <sstream> +#include <string> + +#include "base/logging.h" +#include "base/strings/string_piece.h" +#include "net/http2/tools/failure.h" +#include "net/spdy/spdy_test_utils.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using ::testing::HasSubstr; +using base::StringPiece; +using std::string; + +namespace net { +namespace test { +namespace { + +class HpackDecoderStringBufferTest : public ::testing::Test { + protected: + typedef HpackDecoderStringBuffer::State State; + typedef HpackDecoderStringBuffer::Backing Backing; + + State state() const { return buf_.state_for_testing(); } + Backing backing() const { return buf_.backing_for_testing(); } + + // We want to know that LOG(x) << buf_ will work in production should that + // be needed, so we test that it outputs the expected values. + AssertionResult VerifyLogHasSubstrs(std::initializer_list<string> strs) { + VLOG(1) << buf_; + std::ostringstream ss; + buf_.OutputDebugStringTo(ss); + string dbg_str(ss.str()); + for (const auto& expected : strs) { + VERIFY_THAT(dbg_str, HasSubstr(expected)); + } + return AssertionSuccess(); + } + + HpackDecoderStringBuffer buf_; +}; + +TEST_F(HpackDecoderStringBufferTest, SetStatic) { + StringPiece data("static string"); + + EXPECT_EQ(state(), State::RESET); + EXPECT_TRUE(VerifyLogHasSubstrs({"state=RESET"})); + + buf_.Set(data, /*is_static*/ true); + LOG(INFO) << buf_; + EXPECT_EQ(state(), State::COMPLETE); + EXPECT_EQ(backing(), Backing::STATIC); + EXPECT_EQ(data, buf_.str()); + EXPECT_EQ(data.data(), buf_.str().data()); + EXPECT_TRUE(VerifyLogHasSubstrs( + {"state=COMPLETE", "backing=STATIC", "value: static string"})); + + // The string is static, so BufferStringIfUnbuffered won't change anything. + buf_.BufferStringIfUnbuffered(); + EXPECT_EQ(state(), State::COMPLETE); + EXPECT_EQ(backing(), Backing::STATIC); + EXPECT_EQ(data, buf_.str()); + EXPECT_EQ(data.data(), buf_.str().data()); + EXPECT_TRUE(VerifyLogHasSubstrs( + {"state=COMPLETE", "backing=STATIC", "value: static string"})); +} + +TEST_F(HpackDecoderStringBufferTest, PlainWhole) { + StringPiece data("some text."); + + LOG(INFO) << buf_; + EXPECT_EQ(state(), State::RESET); + + buf_.OnStart(/*huffman_encoded*/ false, data.size()); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::RESET); + LOG(INFO) << buf_; + + EXPECT_TRUE(buf_.OnData(data.data(), data.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::UNBUFFERED); + + EXPECT_TRUE(buf_.OnEnd()); + EXPECT_EQ(state(), State::COMPLETE); + EXPECT_EQ(backing(), Backing::UNBUFFERED); + EXPECT_EQ(0u, buf_.BufferedLength()); + EXPECT_TRUE(VerifyLogHasSubstrs( + {"state=COMPLETE", "backing=UNBUFFERED", "value: some text."})); + + // We expect that the string buffer points to the passed in StringPiece's + // backing store. + EXPECT_EQ(data.data(), buf_.str().data()); + + // Now force it to buffer the string, after which it will still have the same + // string value, but the backing store will be different. + buf_.BufferStringIfUnbuffered(); + LOG(INFO) << buf_; + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), data.size()); + EXPECT_EQ(data, buf_.str()); + EXPECT_NE(data.data(), buf_.str().data()); + EXPECT_TRUE(VerifyLogHasSubstrs( + {"state=COMPLETE", "backing=BUFFERED", "buffer: some text."})); +} + +TEST_F(HpackDecoderStringBufferTest, PlainSplit) { + StringPiece data("some text."); + StringPiece part1 = data.substr(0, 1); + StringPiece part2 = data.substr(1); + + EXPECT_EQ(state(), State::RESET); + buf_.OnStart(/*huffman_encoded*/ false, data.size()); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::RESET); + + // OnData with only a part of the data, not the whole, so buf_ will buffer + // the data. + EXPECT_TRUE(buf_.OnData(part1.data(), part1.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), part1.size()); + LOG(INFO) << buf_; + + EXPECT_TRUE(buf_.OnData(part2.data(), part2.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), data.size()); + + EXPECT_TRUE(buf_.OnEnd()); + EXPECT_EQ(state(), State::COMPLETE); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), data.size()); + LOG(INFO) << buf_; + + StringPiece buffered = buf_.str(); + EXPECT_EQ(data, buffered); + EXPECT_NE(data.data(), buffered.data()); + + // The string is already buffered, so BufferStringIfUnbuffered should not make + // any change. + buf_.BufferStringIfUnbuffered(); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), data.size()); + EXPECT_EQ(buffered, buf_.str()); + EXPECT_EQ(buffered.data(), buf_.str().data()); +} + +TEST_F(HpackDecoderStringBufferTest, HuffmanWhole) { + string encoded = a2b_hex("f1e3c2e5f23a6ba0ab90f4ff"); + StringPiece decoded("www.example.com"); + + EXPECT_EQ(state(), State::RESET); + buf_.OnStart(/*huffman_encoded*/ true, encoded.size()); + EXPECT_EQ(state(), State::COLLECTING); + + EXPECT_TRUE(buf_.OnData(encoded.data(), encoded.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + + EXPECT_TRUE(buf_.OnEnd()); + EXPECT_EQ(state(), State::COMPLETE); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), decoded.size()); + EXPECT_EQ(decoded, buf_.str()); + EXPECT_TRUE(VerifyLogHasSubstrs( + {"{state=COMPLETE", "backing=BUFFERED", "buffer: www.example.com}"})); +} + +TEST_F(HpackDecoderStringBufferTest, HuffmanSplit) { + string encoded = a2b_hex("f1e3c2e5f23a6ba0ab90f4ff"); + string part1 = encoded.substr(0, 5); + string part2 = encoded.substr(5); + StringPiece decoded("www.example.com"); + + EXPECT_EQ(state(), State::RESET); + buf_.OnStart(/*huffman_encoded*/ true, encoded.size()); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(0u, buf_.BufferedLength()); + LOG(INFO) << buf_; + + EXPECT_TRUE(buf_.OnData(part1.data(), part1.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_GT(buf_.BufferedLength(), 0u); + EXPECT_LT(buf_.BufferedLength(), decoded.size()); + LOG(INFO) << buf_; + + EXPECT_TRUE(buf_.OnData(part2.data(), part2.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), decoded.size()); + LOG(INFO) << buf_; + + EXPECT_TRUE(buf_.OnEnd()); + EXPECT_EQ(state(), State::COMPLETE); + EXPECT_EQ(backing(), Backing::BUFFERED); + EXPECT_EQ(buf_.BufferedLength(), decoded.size()); + EXPECT_EQ(decoded, buf_.str()); + LOG(INFO) << buf_; +} + +TEST_F(HpackDecoderStringBufferTest, InvalidHuffmanOnData) { + // Explicitly encode the End-of-String symbol, a no-no. + string encoded = a2b_hex("ffffffff"); + + buf_.OnStart(/*huffman_encoded*/ true, encoded.size()); + EXPECT_EQ(state(), State::COLLECTING); + + EXPECT_FALSE(buf_.OnData(encoded.data(), encoded.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + + LOG(INFO) << buf_; +} + +TEST_F(HpackDecoderStringBufferTest, InvalidHuffmanOnEnd) { + // Last byte of string doesn't end with prefix of End-of-String symbol. + string encoded = a2b_hex("00"); + + buf_.OnStart(/*huffman_encoded*/ true, encoded.size()); + EXPECT_EQ(state(), State::COLLECTING); + + EXPECT_TRUE(buf_.OnData(encoded.data(), encoded.size())); + EXPECT_EQ(state(), State::COLLECTING); + EXPECT_EQ(backing(), Backing::BUFFERED); + + EXPECT_FALSE(buf_.OnEnd()); + LOG(INFO) << buf_; +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/hpack/decoder/hpack_entry_collector.cc b/net/http2/hpack/decoder/hpack_entry_collector.cc new file mode 100644 index 0000000..ea4feb6 --- /dev/null +++ b/net/http2/hpack/decoder/hpack_entry_collector.cc
@@ -0,0 +1,317 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/decoder/hpack_entry_collector.h" + +#include <sstream> +#include <string> + +#include "base/logging.h" +#include "net/http2/hpack/decoder/hpack_string_collector.h" +#include "net/http2/hpack/http2_hpack_constants.h" +#include "net/http2/tools/failure.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::AssertionResult; +using std::string; +using base::StringPiece; + +namespace net { +namespace test { +namespace { + +const HpackEntryType kInvalidHeaderType = static_cast<HpackEntryType>(99); +const size_t kInvalidIndex = 99999999; + +} // namespace + +HpackEntryCollector::HpackEntryCollector() { + Clear(); +} + +HpackEntryCollector::HpackEntryCollector(const HpackEntryCollector& other) + : header_type_(other.header_type_), + index_(other.index_), + name_(other.name_), + value_(other.value_), + started_(other.started_), + ended_(other.ended_) {} + +HpackEntryCollector::HpackEntryCollector(HpackEntryType type, + size_t index_or_size) + : header_type_(type), index_(index_or_size), started_(true), ended_(true) {} +HpackEntryCollector::HpackEntryCollector(HpackEntryType type, + size_t index, + bool value_huffman, + const string& value) + : header_type_(type), + index_(index), + value_(value, value_huffman), + started_(true), + ended_(true) {} +HpackEntryCollector::HpackEntryCollector(HpackEntryType type, + bool name_huffman, + const string& name, + bool value_huffman, + const string& value) + : header_type_(type), + index_(0), + name_(name, name_huffman), + value_(value, value_huffman), + started_(true), + ended_(true) {} + +HpackEntryCollector::~HpackEntryCollector() {} + +void HpackEntryCollector::OnIndexedHeader(size_t index) { + ASSERT_FALSE(started_); + ASSERT_TRUE(IsClear()) << ToString(); + Init(HpackEntryType::kIndexedHeader, index); + ended_ = true; +} +void HpackEntryCollector::OnStartLiteralHeader(HpackEntryType header_type, + size_t maybe_name_index) { + ASSERT_FALSE(started_); + ASSERT_TRUE(IsClear()) << ToString(); + Init(header_type, maybe_name_index); +} +void HpackEntryCollector::OnNameStart(bool huffman_encoded, size_t len) { + ASSERT_TRUE(started_); + ASSERT_FALSE(ended_); + ASSERT_FALSE(IsClear()); + ASSERT_TRUE(LiteralNameExpected()) << ToString(); + name_.OnStringStart(huffman_encoded, len); +} +void HpackEntryCollector::OnNameData(const char* data, size_t len) { + ASSERT_TRUE(started_); + ASSERT_FALSE(ended_); + ASSERT_TRUE(LiteralNameExpected()) << ToString(); + ASSERT_TRUE(name_.IsInProgress()); + name_.OnStringData(data, len); +} +void HpackEntryCollector::OnNameEnd() { + ASSERT_TRUE(started_); + ASSERT_FALSE(ended_); + ASSERT_TRUE(LiteralNameExpected()) << ToString(); + ASSERT_TRUE(name_.IsInProgress()); + name_.OnStringEnd(); +} +void HpackEntryCollector::OnValueStart(bool huffman_encoded, size_t len) { + ASSERT_TRUE(started_); + ASSERT_FALSE(ended_); + if (LiteralNameExpected()) { + ASSERT_TRUE(name_.HasEnded()); + } + ASSERT_TRUE(LiteralValueExpected()) << ToString(); + ASSERT_TRUE(value_.IsClear()) << value_.ToString(); + value_.OnStringStart(huffman_encoded, len); +} +void HpackEntryCollector::OnValueData(const char* data, size_t len) { + ASSERT_TRUE(started_); + ASSERT_FALSE(ended_); + ASSERT_TRUE(LiteralValueExpected()) << ToString(); + ASSERT_TRUE(value_.IsInProgress()); + value_.OnStringData(data, len); +} +void HpackEntryCollector::OnValueEnd() { + ASSERT_TRUE(started_); + ASSERT_FALSE(ended_); + ASSERT_TRUE(LiteralValueExpected()) << ToString(); + ASSERT_TRUE(value_.IsInProgress()); + value_.OnStringEnd(); + ended_ = true; +} +void HpackEntryCollector::OnDynamicTableSizeUpdate(size_t size) { + ASSERT_FALSE(started_); + ASSERT_TRUE(IsClear()) << ToString(); + Init(HpackEntryType::kDynamicTableSizeUpdate, size); + ended_ = true; +} + +void HpackEntryCollector::Clear() { + header_type_ = kInvalidHeaderType; + index_ = kInvalidIndex; + name_.Clear(); + value_.Clear(); + started_ = ended_ = false; +} +bool HpackEntryCollector::IsClear() const { + return header_type_ == kInvalidHeaderType && index_ == kInvalidIndex && + name_.IsClear() && value_.IsClear() && !started_ && !ended_; +} +bool HpackEntryCollector::IsComplete() const { + return started_ && ended_; +} +bool HpackEntryCollector::LiteralNameExpected() const { + switch (header_type_) { + case HpackEntryType::kIndexedLiteralHeader: + case HpackEntryType::kUnindexedLiteralHeader: + case HpackEntryType::kNeverIndexedLiteralHeader: + return index_ == 0; + default: + return false; + } +} +bool HpackEntryCollector::LiteralValueExpected() const { + switch (header_type_) { + case HpackEntryType::kIndexedLiteralHeader: + case HpackEntryType::kUnindexedLiteralHeader: + case HpackEntryType::kNeverIndexedLiteralHeader: + return true; + default: + return false; + } +} +AssertionResult HpackEntryCollector::ValidateIndexedHeader( + size_t expected_index) const { + VERIFY_TRUE(started_); + VERIFY_TRUE(ended_); + VERIFY_EQ(HpackEntryType::kIndexedHeader, header_type_); + VERIFY_EQ(expected_index, index_); + return ::testing::AssertionSuccess(); +} +AssertionResult HpackEntryCollector::ValidateLiteralValueHeader( + HpackEntryType expected_type, + size_t expected_index, + bool expected_value_huffman, + StringPiece expected_value) const { + VERIFY_TRUE(started_); + VERIFY_TRUE(ended_); + VERIFY_EQ(expected_type, header_type_); + VERIFY_NE(0u, expected_index); + VERIFY_EQ(expected_index, index_); + VERIFY_TRUE(name_.IsClear()); + VERIFY_SUCCESS(value_.Collected(expected_value, expected_value_huffman)); + return ::testing::AssertionSuccess(); +} +AssertionResult HpackEntryCollector::ValidateLiteralNameValueHeader( + HpackEntryType expected_type, + bool expected_name_huffman, + StringPiece expected_name, + bool expected_value_huffman, + StringPiece expected_value) const { + VERIFY_TRUE(started_); + VERIFY_TRUE(ended_); + VERIFY_EQ(expected_type, header_type_); + VERIFY_EQ(0u, index_); + VERIFY_SUCCESS(name_.Collected(expected_name, expected_name_huffman)); + VERIFY_SUCCESS(value_.Collected(expected_value, expected_value_huffman)); + return ::testing::AssertionSuccess(); +} +AssertionResult HpackEntryCollector::ValidateDynamicTableSizeUpdate( + size_t size) const { + VERIFY_TRUE(started_); + VERIFY_TRUE(ended_); + VERIFY_EQ(HpackEntryType::kDynamicTableSizeUpdate, header_type_); + VERIFY_EQ(index_, size); + return ::testing::AssertionSuccess(); +} + +void HpackEntryCollector::AppendToHpackBlockBuilder( + HpackBlockBuilder* hbb) const { + ASSERT_TRUE(started_ && ended_) << *this; + switch (header_type_) { + case HpackEntryType::kIndexedHeader: + hbb->AppendIndexedHeader(index_); + return; + + case HpackEntryType::kDynamicTableSizeUpdate: + hbb->AppendDynamicTableSizeUpdate(index_); + return; + + case HpackEntryType::kIndexedLiteralHeader: + case HpackEntryType::kUnindexedLiteralHeader: + case HpackEntryType::kNeverIndexedLiteralHeader: + ASSERT_TRUE(value_.HasEnded()) << *this; + if (index_ != 0) { + CHECK(name_.IsClear()); + hbb->AppendNameIndexAndLiteralValue(header_type_, index_, + value_.huffman_encoded, value_.s); + } else { + CHECK(name_.HasEnded()) << *this; + hbb->AppendLiteralNameAndValue(header_type_, name_.huffman_encoded, + name_.s, value_.huffman_encoded, + value_.s); + } + return; + + default: + ADD_FAILURE() << *this; + } +} + +string HpackEntryCollector::ToString() const { + string result("Type="); + switch (header_type_) { + case HpackEntryType::kIndexedHeader: + result += "IndexedHeader"; + break; + case HpackEntryType::kDynamicTableSizeUpdate: + result += "DynamicTableSizeUpdate"; + break; + case HpackEntryType::kIndexedLiteralHeader: + result += "IndexedLiteralHeader"; + break; + case HpackEntryType::kUnindexedLiteralHeader: + result += "UnindexedLiteralHeader"; + break; + case HpackEntryType::kNeverIndexedLiteralHeader: + result += "NeverIndexedLiteralHeader"; + break; + default: + if (header_type_ == kInvalidHeaderType) { + result += "<unset>"; + } else { + std::stringstream ss; + ss << header_type_; + result.append(ss.str()); + } + } + if (index_ != 0) { + result.append(" Index="); + std::stringstream ss; + ss << index_; + result.append(ss.str()); + } + if (!name_.IsClear()) { + result.append(" Name"); + result.append(name_.ToString()); + } + if (!value_.IsClear()) { + result.append(" Value"); + result.append(value_.ToString()); + } + if (!started_) { + EXPECT_FALSE(ended_); + result.append(" !started"); + } else if (!ended_) { + result.append(" !ended"); + } else { + result.append(" Complete"); + } + return result; +} + +void HpackEntryCollector::Init(HpackEntryType type, size_t maybe_index) { + ASSERT_TRUE(IsClear()) << ToString(); + header_type_ = type; + index_ = maybe_index; + started_ = true; +} + +bool operator==(const HpackEntryCollector& a, const HpackEntryCollector& b) { + return a.name() == b.name() && a.value() == b.value() && + a.index() == b.index() && a.header_type() == b.header_type() && + a.started() == b.started() && a.ended() == b.ended(); +} +bool operator!=(const HpackEntryCollector& a, const HpackEntryCollector& b) { + return !(a == b); +} + +std::ostream& operator<<(std::ostream& out, const HpackEntryCollector& v) { + return out << v.ToString(); +} + +} // namespace test +} // namespace net
diff --git a/net/http2/hpack/decoder/hpack_entry_collector.h b/net/http2/hpack/decoder/hpack_entry_collector.h new file mode 100644 index 0000000..876e0b0 --- /dev/null +++ b/net/http2/hpack/decoder/hpack_entry_collector.h
@@ -0,0 +1,154 @@ +// Copyright 2016 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 NET_HTTP2_HPACK_DECODER_HPACK_ENTRY_COLLECTOR_H_ +#define NET_HTTP2_HPACK_DECODER_HPACK_ENTRY_COLLECTOR_H_ + +// HpackEntryCollector records calls to HpackEntryDecoderListener in support +// of tests of HpackEntryDecoder, or which use it. Can only record the callbacks +// for the decoding of a single entry; call Clear() between decoding successive +// entries or use a distinct HpackEntryCollector for each entry. + +#include <stddef.h> + +#include <iosfwd> +#include <string> + +#include "base/strings/string_piece.h" +#include "net/http2/hpack/decoder/hpack_entry_decoder_listener.h" +#include "net/http2/hpack/decoder/hpack_string_collector.h" +#include "net/http2/hpack/http2_hpack_constants.h" +#include "net/http2/hpack/tools/hpack_block_builder.h" + +namespace net { +namespace test { + +class HpackEntryCollector : public HpackEntryDecoderListener { + public: + HpackEntryCollector(); + HpackEntryCollector(const HpackEntryCollector& other); + + // These next three constructors are intended for use in tests that create + // an HpackEntryCollector "manually", and then compare it against another + // that is populated via calls to the HpackEntryDecoderListener methods. + HpackEntryCollector(HpackEntryType type, size_t index_or_size); + HpackEntryCollector(HpackEntryType type, + size_t index, + bool value_huffman, + const std::string& value); + HpackEntryCollector(HpackEntryType type, + bool name_huffman, + const std::string& name, + bool value_huffman, + const std::string& value); + + ~HpackEntryCollector() override; + + // Methods defined by HpackEntryDecoderListener. + void OnIndexedHeader(size_t index) override; + void OnStartLiteralHeader(HpackEntryType header_type, + size_t maybe_name_index) override; + void OnNameStart(bool huffman_encoded, size_t len) override; + void OnNameData(const char* data, size_t len) override; + void OnNameEnd() override; + void OnValueStart(bool huffman_encoded, size_t len) override; + void OnValueData(const char* data, size_t len) override; + void OnValueEnd() override; + void OnDynamicTableSizeUpdate(size_t size) override; + + // Clears the fields of the collector so that it is ready to start collecting + // another HPACK block entry. + void Clear(); + + // Is the collector ready to start collecting another HPACK block entry. + bool IsClear() const; + + // Has a complete entry been collected? + bool IsComplete() const; + + // Based on the HpackEntryType, is a literal name expected? + bool LiteralNameExpected() const; + + // Based on the HpackEntryType, is a literal value expected? + bool LiteralValueExpected() const; + + // Returns success if collected an Indexed Header (i.e. OnIndexedHeader was + // called). + ::testing::AssertionResult ValidateIndexedHeader(size_t expected_index) const; + + // Returns success if collected a Header with an indexed name and literal + // value (i.e. OnStartLiteralHeader was called with a non-zero index for + // the name, which must match expected_index). + ::testing::AssertionResult ValidateLiteralValueHeader( + HpackEntryType expected_type, + size_t expected_index, + bool expected_value_huffman, + base::StringPiece expected_value) const; + + // Returns success if collected a Header with an literal name and literal + // value. + ::testing::AssertionResult ValidateLiteralNameValueHeader( + HpackEntryType expected_type, + bool expected_name_huffman, + base::StringPiece expected_name, + bool expected_value_huffman, + base::StringPiece expected_value) const; + + // Returns success if collected a Dynamic Table Size Update, + // with the specified size. + ::testing::AssertionResult ValidateDynamicTableSizeUpdate( + size_t expected_size) const; + + void set_header_type(HpackEntryType v) { header_type_ = v; } + HpackEntryType header_type() const { return header_type_; } + + void set_index(size_t v) { index_ = v; } + size_t index() const { return index_; } + + void set_name(const HpackStringCollector& v) { name_ = v; } + const HpackStringCollector& name() const { return name_; } + + void set_value(const HpackStringCollector& v) { value_ = v; } + const HpackStringCollector& value() const { return value_; } + + void set_started(bool v) { started_ = v; } + bool started() const { return started_; } + + void set_ended(bool v) { ended_ = v; } + bool ended() const { return ended_; } + + void AppendToHpackBlockBuilder(HpackBlockBuilder* hbb) const; + + // Returns a debug string. + std::string ToString() const; + + private: + void Init(HpackEntryType type, size_t maybe_index); + + HpackEntryType header_type_; + size_t index_; + + HpackStringCollector name_; + HpackStringCollector value_; + + // True if has received a call to an HpackEntryDecoderListener method + // indicating the start of decoding an HPACK entry; for example, + // OnIndexedHeader set it true, but OnNameStart does not change it. + bool started_ = false; + + // True if has received a call to an HpackEntryDecoderListener method + // indicating the end of decoding an HPACK entry; for example, + // OnIndexedHeader and OnValueEnd both set it true, but OnNameEnd does + // not change it. + bool ended_ = false; +}; + +bool operator==(const HpackEntryCollector& a, const HpackEntryCollector& b); +bool operator!=(const HpackEntryCollector& a, const HpackEntryCollector& b); +std::ostream& operator<<(std::ostream& out, const HpackEntryCollector& v); + +} // namespace test +} // namespace net + +#endif // NET_HTTP2_HPACK_DECODER_HPACK_ENTRY_COLLECTOR_H_
diff --git a/net/http2/hpack/decoder/hpack_entry_decoder.cc b/net/http2/hpack/decoder/hpack_entry_decoder.cc new file mode 100644 index 0000000..c47ef0c --- /dev/null +++ b/net/http2/hpack/decoder/hpack_entry_decoder.cc
@@ -0,0 +1,233 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/decoder/hpack_entry_decoder.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "base/macros.h" + +namespace net { +namespace { +// Converts calls from HpackStringDecoder when decoding a header name into the +// appropriate HpackEntryDecoderListener::OnName* calls. +class NameDecoderListener { + public: + explicit NameDecoderListener(HpackEntryDecoderListener* listener) + : listener_(listener) {} + bool OnStringStart(bool huffman_encoded, size_t len) { + listener_->OnNameStart(huffman_encoded, len); + return true; + } + void OnStringData(const char* data, size_t len) { + listener_->OnNameData(data, len); + } + void OnStringEnd() { listener_->OnNameEnd(); } + + private: + HpackEntryDecoderListener* listener_; +}; + +// Converts calls from HpackStringDecoder when decoding a header value into +// the appropriate HpackEntryDecoderListener::OnValue* calls. +class ValueDecoderListener { + public: + explicit ValueDecoderListener(HpackEntryDecoderListener* listener) + : listener_(listener) {} + bool OnStringStart(bool huffman_encoded, size_t len) { + listener_->OnValueStart(huffman_encoded, len); + return true; + } + void OnStringData(const char* data, size_t len) { + listener_->OnValueData(data, len); + } + void OnStringEnd() { listener_->OnValueEnd(); } + + private: + HpackEntryDecoderListener* listener_; +}; +} // namespace + +// Only call Resume if the previous call (Start or Resume) returned +// kDecodeInProgress; Resume is also called from Start when it has succeeded +// in decoding the entry type and its varint. +DecodeStatus HpackEntryDecoder::Resume(DecodeBuffer* db, + HpackEntryDecoderListener* listener) { + DCHECK(db != nullptr); + DCHECK(listener != nullptr); + + DecodeStatus status; + + do { + switch (state_) { + case EntryDecoderState::kResumeDecodingType: + // entry_type_decoder_ returned kDecodeInProgress when last called. + DVLOG(1) << "kResumeDecodingType: db->Remaining=" << db->Remaining(); + status = entry_type_decoder_.Resume(db); + if (status != DecodeStatus::kDecodeDone) { + return status; + } + state_ = EntryDecoderState::kDecodedType; + // FALLTHROUGH_INTENDED + + case EntryDecoderState::kDecodedType: + // entry_type_decoder_ returned kDecodeDone, now need to decide how + // to proceed. + DVLOG(1) << "kDecodedType: db->Remaining=" << db->Remaining(); + if (DispatchOnType(listener)) { + // All done. + return DecodeStatus::kDecodeDone; + } + continue; + + case EntryDecoderState::kStartDecodingName: + DVLOG(1) << "kStartDecodingName: db->Remaining=" << db->Remaining(); + { + NameDecoderListener ncb(listener); + status = string_decoder_.Start(db, &ncb); + } + if (status != DecodeStatus::kDecodeDone) { + // On the assumption that the status is kDecodeInProgress, set + // state_ accordingly; unnecessary if status is kDecodeError, but + // that will only happen if the varint encoding the name's length + // is too long. + state_ = EntryDecoderState::kResumeDecodingName; + return status; + } + state_ = EntryDecoderState::kStartDecodingValue; + // FALLTHROUGH_INTENDED + + case EntryDecoderState::kStartDecodingValue: + DVLOG(1) << "kStartDecodingValue: db->Remaining=" << db->Remaining(); + { + ValueDecoderListener vcb(listener); + status = string_decoder_.Start(db, &vcb); + } + if (status == DecodeStatus::kDecodeDone) { + // Done with decoding the literal value, so we've reached the + // end of the header entry. + return status; + } + // On the assumption that the status is kDecodeInProgress, set + // state_ accordingly; unnecessary if status is kDecodeError, but + // that will only happen if the varint encoding the value's length + // is too long. + state_ = EntryDecoderState::kResumeDecodingValue; + return status; + + case EntryDecoderState::kResumeDecodingName: + // The literal name was split across decode buffers. + DVLOG(1) << "kResumeDecodingName: db->Remaining=" << db->Remaining(); + { + NameDecoderListener ncb(listener); + status = string_decoder_.Resume(db, &ncb); + } + if (status != DecodeStatus::kDecodeDone) { + // On the assumption that the status is kDecodeInProgress, set + // state_ accordingly; unnecessary if status is kDecodeError, but + // that will only happen if the varint encoding the name's length + // is too long. + state_ = EntryDecoderState::kResumeDecodingName; + return status; + } + state_ = EntryDecoderState::kStartDecodingValue; + break; + + case EntryDecoderState::kResumeDecodingValue: + // The literal value was split across decode buffers. + DVLOG(1) << "kResumeDecodingValue: db->Remaining=" << db->Remaining(); + { + ValueDecoderListener vcb(listener); + status = string_decoder_.Resume(db, &vcb); + } + if (status == DecodeStatus::kDecodeDone) { + // Done with decoding the value, therefore the entry as a whole. + return status; + } + // On the assumption that the status is kDecodeInProgress, set + // state_ accordingly; unnecessary if status is kDecodeError, but + // that will only happen if the varint encoding the value's length + // is too long. + state_ = EntryDecoderState::kResumeDecodingValue; + return status; + } + } while (true); +} + +bool HpackEntryDecoder::DispatchOnType(HpackEntryDecoderListener* listener) { + const HpackEntryType entry_type = entry_type_decoder_.entry_type(); + const uint32_t varint = entry_type_decoder_.varint(); + switch (entry_type) { + case HpackEntryType::kIndexedHeader: + // The entry consists solely of the entry type and varint. See: + // http://httpwg.org/specs/rfc7541.html#indexed.header.representation + listener->OnIndexedHeader(varint); + return true; + + case HpackEntryType::kIndexedLiteralHeader: + case HpackEntryType::kUnindexedLiteralHeader: + case HpackEntryType::kNeverIndexedLiteralHeader: + // The entry has a literal value, and if the varint is zero also has a + // literal name preceding the value. See: + // http://httpwg.org/specs/rfc7541.html#literal.header.representation + listener->OnStartLiteralHeader(entry_type, varint); + if (varint == 0) { + state_ = EntryDecoderState::kStartDecodingName; + } else { + state_ = EntryDecoderState::kStartDecodingValue; + } + return false; + + case HpackEntryType::kDynamicTableSizeUpdate: + // The entry consists solely of the entry type and varint. FWIW, I've + // never seen this type of entry in production (primarily browser + // traffic) so if you're designing an HPACK successor someday, consider + // dropping it or giving it a much longer prefix. See: + // http://httpwg.org/specs/rfc7541.html#encoding.context.update + listener->OnDynamicTableSizeUpdate(varint); + return true; + } + + NOTREACHED(); + return true; +} + +void HpackEntryDecoder::OutputDebugString(std::ostream& out) const { + out << "HpackEntryDecoder(state=" << state_ << ", " << entry_type_decoder_ + << ", " << string_decoder_ << ")"; +} + +std::string HpackEntryDecoder::DebugString() const { + std::stringstream s; + s << *this; + return s.str(); +} + +std::ostream& operator<<(std::ostream& out, const HpackEntryDecoder& v) { + v.OutputDebugString(out); + return out; +} + +std::ostream& operator<<(std::ostream& out, + HpackEntryDecoder::EntryDecoderState state) { + typedef HpackEntryDecoder::EntryDecoderState EntryDecoderState; + switch (state) { + case EntryDecoderState::kResumeDecodingType: + return out << "kResumeDecodingType"; + case EntryDecoderState::kDecodedType: + return out << "kDecodedType"; + case EntryDecoderState::kStartDecodingName: + return out << "kStartDecodingName"; + case EntryDecoderState::kResumeDecodingName: + return out << "kResumeDecodingName"; + case EntryDecoderState::kStartDecodingValue: + return out << "kStartDecodingValue"; + case EntryDecoderState::kResumeDecodingValue: + return out << "kResumeDecodingValue"; + } + return out << static_cast<int>(state); +} + +} // namespace net
diff --git a/net/http2/hpack/decoder/hpack_entry_decoder.h b/net/http2/hpack/decoder/hpack_entry_decoder.h new file mode 100644 index 0000000..9b5f696 --- /dev/null +++ b/net/http2/hpack/decoder/hpack_entry_decoder.h
@@ -0,0 +1,117 @@ +// Copyright 2016 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 NET_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_H_ +#define NET_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_H_ + +// HpackEntryDecoder decodes a single HPACK entry (i.e. one header or one +// dynamic table size update), in a resumable fashion. The first call, Start(), +// must provide a non-empty decode buffer. Continue with calls to Resume() if +// Start, and any subsequent calls to Resume, returns kDecodeInProgress. + +#include <string> + +#include "base/logging.h" +#include "net/base/net_export.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/hpack/decoder/hpack_entry_decoder_listener.h" +#include "net/http2/hpack/decoder/hpack_entry_type_decoder.h" +#include "net/http2/hpack/decoder/hpack_string_decoder.h" +#include "net/http2/hpack/http2_hpack_constants.h" + +namespace net { + +class NET_EXPORT_PRIVATE HpackEntryDecoder { + public: + enum class EntryDecoderState { + // Have started decoding the type/varint, but didn't finish on the previous + // attempt. Next state is kResumeDecodingType or kDecodedType. + kResumeDecodingType, + + // Have just finished decoding the type/varint. Final state if the type is + // kIndexedHeader or kDynamicTableSizeUpdate. Otherwise, the next state is + // kStartDecodingName (if the varint is 0), else kStartDecodingValue. + kDecodedType, + + // Ready to start decoding the literal name of a header entry. Next state + // is kResumeDecodingName (if the name is split across decode buffers), + // else kStartDecodingValue. + kStartDecodingName, + + // Resume decoding the literal name of a header that is split across decode + // buffers. + kResumeDecodingName, + + // Ready to start decoding the literal value of a header entry. Final state + // if the value string is entirely in the decode buffer, else the next state + // is kResumeDecodingValue. + kStartDecodingValue, + + // Resume decoding the literal value of a header that is split across decode + // buffers. + kResumeDecodingValue, + }; + + // Only call when the decode buffer has data (i.e. HpackBlockDecoder must + // not call until there is data). + DecodeStatus Start(DecodeBuffer* db, HpackEntryDecoderListener* listener) { + DCHECK(db != nullptr); + DCHECK(listener != nullptr); + DCHECK(db->HasData()); + DecodeStatus status = entry_type_decoder_.Start(db); + switch (status) { + case DecodeStatus::kDecodeDone: + // The type of the entry and its varint fit into the current decode + // buffer. + if (entry_type_decoder_.entry_type() == + HpackEntryType::kIndexedHeader) { + // The entry consists solely of the entry type and varint. This + // is by far the most common case in practice. + listener->OnIndexedHeader(entry_type_decoder_.varint()); + return DecodeStatus::kDecodeDone; + } + state_ = EntryDecoderState::kDecodedType; + return Resume(db, listener); + case DecodeStatus::kDecodeInProgress: + // Hit the end of the decode buffer before fully decoding the entry + // type and varint. + DCHECK_EQ(0u, db->Remaining()); + state_ = EntryDecoderState::kResumeDecodingType; + return status; + case DecodeStatus::kDecodeError: + // The varint must have been invalid (too long). + return status; + } + + NOTREACHED(); + return DecodeStatus::kDecodeError; + } + + // Only call Resume if the previous call (Start or Resume) returned + // kDecodeInProgress; Resume is also called from Start when it has succeeded + // in decoding the entry type and its varint. + DecodeStatus Resume(DecodeBuffer* db, HpackEntryDecoderListener* listener); + + std::string DebugString() const; + void OutputDebugString(std::ostream& out) const; + + private: + // Implements handling state kDecodedType. + bool DispatchOnType(HpackEntryDecoderListener* listener); + + HpackEntryTypeDecoder entry_type_decoder_; + HpackStringDecoder string_decoder_; + EntryDecoderState state_ = EntryDecoderState(); +}; + +NET_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const HpackEntryDecoder& v); +NET_EXPORT_PRIVATE std::ostream& operator<<( + std::ostream& out, + HpackEntryDecoder::EntryDecoderState state); + +} // namespace net + +#endif // NET_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_H_
diff --git a/net/http2/hpack/decoder/hpack_entry_decoder_listener.cc b/net/http2/hpack/decoder/hpack_entry_decoder_listener.cc new file mode 100644 index 0000000..c7e991f --- /dev/null +++ b/net/http2/hpack/decoder/hpack_entry_decoder_listener.cc
@@ -0,0 +1,83 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/decoder/hpack_entry_decoder_listener.h" + +#include "base/logging.h" + +namespace net { + +void HpackEntryDecoderVLoggingListener::OnIndexedHeader(size_t index) { + VLOG(1) << "OnIndexedHeader, index=" << index; + if (wrapped_) { + wrapped_->OnIndexedHeader(index); + } +} + +void HpackEntryDecoderVLoggingListener::OnStartLiteralHeader( + HpackEntryType entry_type, + size_t maybe_name_index) { + VLOG(1) << "OnStartLiteralHeader: entry_type=" << entry_type + << ", maybe_name_index=" << maybe_name_index; + if (wrapped_) { + wrapped_->OnStartLiteralHeader(entry_type, maybe_name_index); + } +} + +void HpackEntryDecoderVLoggingListener::OnNameStart(bool huffman_encoded, + size_t len) { + VLOG(1) << "OnNameStart: H=" << huffman_encoded << ", len=" << len; + if (wrapped_) { + wrapped_->OnNameStart(huffman_encoded, len); + } +} + +void HpackEntryDecoderVLoggingListener::OnNameData(const char* data, + size_t len) { + VLOG(1) << "OnNameData: len=" << len; + if (wrapped_) { + wrapped_->OnNameData(data, len); + } +} + +void HpackEntryDecoderVLoggingListener::OnNameEnd() { + VLOG(1) << "OnNameEnd"; + if (wrapped_) { + wrapped_->OnNameEnd(); + } +} + +void HpackEntryDecoderVLoggingListener::OnValueStart(bool huffman_encoded, + size_t len) { + VLOG(1) << "OnValueStart: H=" << huffman_encoded << ", len=" << len; + if (wrapped_) { + wrapped_->OnValueStart(huffman_encoded, len); + } + return; +} + +void HpackEntryDecoderVLoggingListener::OnValueData(const char* data, + size_t len) { + VLOG(1) << "OnValueData: len=" << len; + if (wrapped_) { + wrapped_->OnValueData(data, len); + } +} + +void HpackEntryDecoderVLoggingListener::OnValueEnd() { + VLOG(1) << "OnValueEnd"; + if (wrapped_) { + wrapped_->OnValueEnd(); + } +} + +void HpackEntryDecoderVLoggingListener::OnDynamicTableSizeUpdate(size_t size) { + VLOG(1) << "OnDynamicTableSizeUpdate: size=" << size; + if (wrapped_) { + wrapped_->OnDynamicTableSizeUpdate(size); + } + return; +} + +} // namespace net
diff --git a/net/http2/hpack/decoder/hpack_entry_decoder_listener.h b/net/http2/hpack/decoder/hpack_entry_decoder_listener.h new file mode 100644 index 0000000..09fbd54 --- /dev/null +++ b/net/http2/hpack/decoder/hpack_entry_decoder_listener.h
@@ -0,0 +1,110 @@ +// Copyright 2016 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 NET_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_LISTENER_H_ +#define NET_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_LISTENER_H_ + +// Defines HpackEntryDecoderListener, the base class of listeners that +// HpackEntryDecoder calls. Also defines HpackEntryDecoderVLoggingListener +// which logs before calling another HpackEntryDecoderListener implementation. + +#include <stddef.h> + +#include "net/base/net_export.h" +#include "net/http2/hpack/http2_hpack_constants.h" + +namespace net { + +class NET_EXPORT_PRIVATE HpackEntryDecoderListener { + public: + virtual ~HpackEntryDecoderListener() {} + + // Called when an indexed header (i.e. one in the static or dynamic table) has + // been decoded from an HPACK block. index is supposed to be non-zero, but + // that has not been checked by the caller. + virtual void OnIndexedHeader(size_t index) = 0; + + // Called when the start of a header with a literal value, and maybe a literal + // name, has been decoded. maybe_name_index is zero if the header has a + // literal name, else it is a reference into the static or dynamic table, from + // which the name should be determined. When the name is literal, the next + // call will be to OnNameStart; else it will be to OnValueStart. entry_type + // indicates whether the peer has added the entry to its dynamic table, and + // whether a proxy is permitted to do so when forwarding the entry. + virtual void OnStartLiteralHeader(HpackEntryType entry_type, + size_t maybe_name_index) = 0; + + // Called when the encoding (Huffman compressed or plain text) and the encoded + // length of a literal name has been decoded. OnNameData will be called next, + // and repeatedly until the sum of lengths passed to OnNameData is len. + virtual void OnNameStart(bool huffman_encoded, size_t len) = 0; + + // Called when len bytes of an encoded header name have been decoded. + virtual void OnNameData(const char* data, size_t len) = 0; + + // Called after the entire name has been passed to OnNameData. + // OnValueStart will be called next. + virtual void OnNameEnd() = 0; + + // Called when the encoding (Huffman compressed or plain text) and the encoded + // length of a literal value has been decoded. OnValueData will be called + // next, and repeatedly until the sum of lengths passed to OnValueData is len. + virtual void OnValueStart(bool huffman_encoded, size_t len) = 0; + + // Called when len bytes of an encoded header value have been decoded. + virtual void OnValueData(const char* data, size_t len) = 0; + + // Called after the entire value has been passed to OnValueData, marking the + // end of a header entry with a literal value, and maybe a literal name. + virtual void OnValueEnd() = 0; + + // Called when an update to the size of the peer's dynamic table has been + // decoded. + virtual void OnDynamicTableSizeUpdate(size_t size) = 0; +}; + +class NET_EXPORT_PRIVATE HpackEntryDecoderVLoggingListener + : public HpackEntryDecoderListener { + public: + HpackEntryDecoderVLoggingListener() : wrapped_(nullptr) {} + explicit HpackEntryDecoderVLoggingListener(HpackEntryDecoderListener* wrapped) + : wrapped_(wrapped) {} + ~HpackEntryDecoderVLoggingListener() override {} + + void OnIndexedHeader(size_t index) override; + void OnStartLiteralHeader(HpackEntryType entry_type, + size_t maybe_name_index) override; + void OnNameStart(bool huffman_encoded, size_t len) override; + void OnNameData(const char* data, size_t len) override; + void OnNameEnd() override; + void OnValueStart(bool huffman_encoded, size_t len) override; + void OnValueData(const char* data, size_t len) override; + void OnValueEnd() override; + void OnDynamicTableSizeUpdate(size_t size) override; + + private: + HpackEntryDecoderListener* const wrapped_; +}; + +// A no-op implementation of HpackEntryDecoderListener. +class NET_EXPORT_PRIVATE HpackEntryDecoderNoOpListener + : public HpackEntryDecoderListener { + public: + ~HpackEntryDecoderNoOpListener() override {} + + void OnIndexedHeader(size_t index) override {} + void OnStartLiteralHeader(HpackEntryType entry_type, + size_t maybe_name_index) override {} + void OnNameStart(bool huffman_encoded, size_t len) override {} + void OnNameData(const char* data, size_t len) override {} + void OnNameEnd() override {} + void OnValueStart(bool huffman_encoded, size_t len) override {} + void OnValueData(const char* data, size_t len) override {} + void OnValueEnd() override {} + void OnDynamicTableSizeUpdate(size_t size) override {} +}; + +} // namespace net + +#endif // NET_HTTP2_HPACK_DECODER_HPACK_ENTRY_DECODER_LISTENER_H_
diff --git a/net/http2/hpack/decoder/hpack_entry_decoder_test.cc b/net/http2/hpack/decoder/hpack_entry_decoder_test.cc new file mode 100644 index 0000000..34581fa --- /dev/null +++ b/net/http2/hpack/decoder/hpack_entry_decoder_test.cc
@@ -0,0 +1,244 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/decoder/hpack_entry_decoder.h" + +// Tests of HpackEntryDecoder. + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "net/http2/hpack/decoder/hpack_entry_collector.h" +#include "net/http2/hpack/tools/hpack_block_builder.h" +#include "net/http2/tools/failure.h" +#include "net/http2/tools/http2_random.h" +#include "net/http2/tools/random_decoder_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::AssertionResult; +using std::string; + +namespace net { +namespace test { +namespace { + +class HpackEntryDecoderTest : public RandomDecoderTest { + public: + AssertionResult ValidateIndexedHeader(uint32_t ndx) { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateIndexedHeader(ndx)); + } + + AssertionResult ValidateForIndexedLiteralValue_Literal() { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateLiteralValueHeader( + HpackEntryType::kIndexedLiteralHeader, 0x40, false, "custom-header")); + } + + AssertionResult ValidateForIndexedLiteralNameValue_Literal() { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateLiteralNameValueHeader( + HpackEntryType::kIndexedLiteralHeader, false, "custom-key", false, + "custom-header")); + } + + AssertionResult ValidateForDynamicTableSizeUpdate_Literal() { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateDynamicTableSizeUpdate(31)); + } + + protected: + HpackEntryDecoderTest() : listener_(&collector_) {} + + DecodeStatus StartDecoding(DecodeBuffer* b) override { + collector_.Clear(); + return decoder_.Start(b, &listener_); + } + + DecodeStatus ResumeDecoding(DecodeBuffer* b) override { + return decoder_.Resume(b, &listener_); + } + + AssertionResult DecodeAndValidateSeveralWays(DecodeBuffer* db, + Validator validator) { + // StartDecoding, above, requires the DecodeBuffer be non-empty so that it + // can call Start with the prefix byte. + bool return_non_zero_on_first = true; + return RandomDecoderTest::DecodeAndValidateSeveralWays( + db, return_non_zero_on_first, validator); + } + + AssertionResult DecodeAndValidateSeveralWays(const HpackBlockBuilder& hbb, + Validator validator) { + DecodeBuffer db(hbb.buffer()); + return DecodeAndValidateSeveralWays(&db, validator); + } + + HpackEntryDecoder decoder_; + HpackEntryCollector collector_; + HpackEntryDecoderVLoggingListener listener_; +}; + +TEST_F(HpackEntryDecoderTest, IndexedHeader_Literals) { + { + const char input[] = {0x82u}; // == Index 2 == + DecodeBuffer b(input); + NoArgValidator do_check = + base::Bind(&HpackEntryDecoderTest::ValidateIndexedHeader, + base::Unretained(this), 2); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check.Run()); + } + collector_.Clear(); + { + const char input[] = {0xfeu}; // == Index 126 == + DecodeBuffer b(input); + NoArgValidator do_check = + base::Bind(&HpackEntryDecoderTest::ValidateIndexedHeader, + base::Unretained(this), 126); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check.Run()); + } + collector_.Clear(); + { + const char input[] = {0xffu, 0x00}; // == Index 127 == + DecodeBuffer b(input); + NoArgValidator do_check = + base::Bind(&HpackEntryDecoderTest::ValidateIndexedHeader, + base::Unretained(this), 127); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check.Run()); + } +} + +TEST_F(HpackEntryDecoderTest, IndexedHeader_Various) { + // Indices chosen to hit encoding and table boundaries. + for (const uint32_t ndx : {1, 2, 61, 62, 63, 126, 127, 254, 255, 256}) { + HpackBlockBuilder hbb; + hbb.AppendIndexedHeader(ndx); + + NoArgValidator do_check = + base::Bind(&HpackEntryDecoderTest::ValidateIndexedHeader, + base::Unretained(this), ndx); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check.Run()); + } +} + +TEST_F(HpackEntryDecoderTest, IndexedLiteralValue_Literal) { + const char input[] = + "\x7f" // == Literal indexed, name index 0x40 == + "\x01" // 2nd byte of name index (0x01 + 0x3f == 0x40) + "\x0d" // Value length (13) + "custom-header"; // Value + DecodeBuffer b(input, sizeof input - 1); + NoArgValidator do_check = + base::Bind(&HpackEntryDecoderTest::ValidateForIndexedLiteralValue_Literal, + base::Unretained(this)); + EXPECT_TRUE(DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check.Run()); +} + +TEST_F(HpackEntryDecoderTest, IndexedLiteralNameValue_Literal) { + const char input[] = + "\x40" // == Literal indexed == + "\x0a" // Name length (10) + "custom-key" // Name + "\x0d" // Value length (13) + "custom-header"; // Value + + DecodeBuffer b(input, sizeof input - 1); + NoArgValidator do_check = base::Bind( + &HpackEntryDecoderTest::ValidateForIndexedLiteralNameValue_Literal, + base::Unretained(this)); + EXPECT_TRUE(DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check.Run()); +} + +TEST_F(HpackEntryDecoderTest, DynamicTableSizeUpdate_Literal) { + // Size update, length 31. + const char input[] = "\x3f\x00"; + DecodeBuffer b(input, 2); + NoArgValidator do_check = base::Bind( + &HpackEntryDecoderTest::ValidateForDynamicTableSizeUpdate_Literal, + base::Unretained(this)); + EXPECT_TRUE(DecodeAndValidateSeveralWays(&b, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check.Run()); +} + +class HpackLiteralEntryDecoderTest + : public HpackEntryDecoderTest, + public ::testing::WithParamInterface<HpackEntryType> { + public: + AssertionResult ValidateForRandNameIndexAndLiteralValue( + uint32_t ndx, + bool value_is_huffman_encoded, + const string& value) { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateLiteralValueHeader( + entry_type_, ndx, value_is_huffman_encoded, value)); + } + + AssertionResult ValidateForRandLiteralNameAndValue( + bool name_is_huffman_encoded, + const string& name, + bool value_is_huffman_encoded, + const string& value) { + VERIFY_AND_RETURN_SUCCESS(collector_.ValidateLiteralNameValueHeader( + entry_type_, name_is_huffman_encoded, name, value_is_huffman_encoded, + value)); + } + + protected: + HpackLiteralEntryDecoderTest() : entry_type_(GetParam()) {} + + const HpackEntryType entry_type_; +}; + +INSTANTIATE_TEST_CASE_P( + AllLiteralTypes, + HpackLiteralEntryDecoderTest, + testing::Values(HpackEntryType::kIndexedLiteralHeader, + HpackEntryType::kUnindexedLiteralHeader, + HpackEntryType::kNeverIndexedLiteralHeader)); + +TEST_P(HpackLiteralEntryDecoderTest, RandNameIndexAndLiteralValue) { + for (int n = 0; n < 10; n++) { + const uint32_t ndx = 1 + Random().Rand8(); + const bool value_is_huffman_encoded = (n % 2) == 0; + const string value = Random().RandString(Random().Rand8()); + HpackBlockBuilder hbb; + hbb.AppendNameIndexAndLiteralValue(entry_type_, ndx, + value_is_huffman_encoded, value); + NoArgValidator do_check = base::Bind( + &HpackLiteralEntryDecoderTest::ValidateForRandNameIndexAndLiteralValue, + base::Unretained(this), ndx, value_is_huffman_encoded, value); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check.Run()); + } +} + +TEST_P(HpackLiteralEntryDecoderTest, RandLiteralNameAndValue) { + for (int n = 0; n < 10; n++) { + const bool name_is_huffman_encoded = (n & 1) == 0; + const int name_len = 1 + Random().Rand8(); + const string name = Random().RandString(name_len); + const bool value_is_huffman_encoded = (n & 2) == 0; + const int value_len = Random().Skewed(10); + const string value = Random().RandString(value_len); + HpackBlockBuilder hbb; + hbb.AppendLiteralNameAndValue(entry_type_, name_is_huffman_encoded, name, + value_is_huffman_encoded, value); + NoArgValidator do_check = base::Bind( + &HpackLiteralEntryDecoderTest::ValidateForRandLiteralNameAndValue, + base::Unretained(this), name_is_huffman_encoded, name, + value_is_huffman_encoded, value); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(hbb, ValidateDoneAndEmpty(do_check))); + EXPECT_TRUE(do_check.Run()); + } +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/hpack/decoder/hpack_entry_type_decoder.cc b/net/http2/hpack/decoder/hpack_entry_type_decoder.cc new file mode 100644 index 0000000..a05d48b --- /dev/null +++ b/net/http2/hpack/decoder/hpack_entry_type_decoder.cc
@@ -0,0 +1,360 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/decoder/hpack_entry_type_decoder.h" + +#include <sstream> + +#include "base/logging.h" + +namespace net { + +std::string HpackEntryTypeDecoder::DebugString() const { + std::stringstream ss; + ss << "HpackEntryTypeDecoder(varint_decoder=" << varint_decoder_.DebugString() + << ", entry_type=" << entry_type_ << ")"; + return ss.str(); +} + +std::ostream& operator<<(std::ostream& out, const HpackEntryTypeDecoder& v) { + return out << v.DebugString(); +} + +// This ridiculous looking function turned out to be the winner in benchmarking +// of several very different alternative implementations. It would be even +// faster (~7%) if inlined in the header file, but I'm not sure if that is +// worth doing... yet. +// TODO(jamessynge): Benchmark again at a higher level (e.g. at least at the +// full HTTP/2 decoder level, but preferably still higher) to determine if the +// alternatives that take less code/data space are preferable in that situation. +DecodeStatus HpackEntryTypeDecoder::Start(DecodeBuffer* db) { + DCHECK(db != nullptr); + DCHECK(db->HasData()); + + // The high four bits (nibble) of first byte of the entry determine the type + // of the entry, and may also be the initial bits of the varint that + // represents an index or table size. Note the use of the word 'initial' + // rather than 'high'; the HPACK encoding of varints is not in network + // order (i.e. not big-endian, the high-order byte isn't first), nor in + // little-endian order. See: + // http://httpwg.org/specs/rfc7541.html#integer.representation + uint8_t byte = db->DecodeUInt8(); + switch (byte) { + case 0b00000000: + case 0b00000001: + case 0b00000010: + case 0b00000011: + case 0b00000100: + case 0b00000101: + case 0b00000110: + case 0b00000111: + case 0b00001000: + case 0b00001001: + case 0b00001010: + case 0b00001011: + case 0b00001100: + case 0b00001101: + case 0b00001110: + // The low 4 bits of |byte| are the initial bits of the varint. + // One of those bits is 0, so the varint is only one byte long. + entry_type_ = HpackEntryType::kUnindexedLiteralHeader; + varint_decoder_.set_value(byte); + return DecodeStatus::kDecodeDone; + + case 0b00001111: + // The low 4 bits of |byte| are the initial bits of the varint. All 4 + // are 1, so the varint extends into another byte. + entry_type_ = HpackEntryType::kUnindexedLiteralHeader; + return varint_decoder_.StartExtended(0x0f, db); + + case 0b00010000: + case 0b00010001: + case 0b00010010: + case 0b00010011: + case 0b00010100: + case 0b00010101: + case 0b00010110: + case 0b00010111: + case 0b00011000: + case 0b00011001: + case 0b00011010: + case 0b00011011: + case 0b00011100: + case 0b00011101: + case 0b00011110: + // The low 4 bits of |byte| are the initial bits of the varint. + // One of those bits is 0, so the varint is only one byte long. + entry_type_ = HpackEntryType::kNeverIndexedLiteralHeader; + varint_decoder_.set_value(byte & 0x0f); + return DecodeStatus::kDecodeDone; + + case 0b00011111: + // The low 4 bits of |byte| are the initial bits of the varint. + // All of those bits are 1, so the varint extends into another byte. + entry_type_ = HpackEntryType::kNeverIndexedLiteralHeader; + return varint_decoder_.StartExtended(0x0f, db); + + case 0b00100000: + case 0b00100001: + case 0b00100010: + case 0b00100011: + case 0b00100100: + case 0b00100101: + case 0b00100110: + case 0b00100111: + case 0b00101000: + case 0b00101001: + case 0b00101010: + case 0b00101011: + case 0b00101100: + case 0b00101101: + case 0b00101110: + case 0b00101111: + case 0b00110000: + case 0b00110001: + case 0b00110010: + case 0b00110011: + case 0b00110100: + case 0b00110101: + case 0b00110110: + case 0b00110111: + case 0b00111000: + case 0b00111001: + case 0b00111010: + case 0b00111011: + case 0b00111100: + case 0b00111101: + case 0b00111110: + entry_type_ = HpackEntryType::kDynamicTableSizeUpdate; + // The low 5 bits of |byte| are the initial bits of the varint. + // One of those bits is 0, so the varint is only one byte long. + varint_decoder_.set_value(byte & 0x01f); + return DecodeStatus::kDecodeDone; + + case 0b00111111: + entry_type_ = HpackEntryType::kDynamicTableSizeUpdate; + // The low 5 bits of |byte| are the initial bits of the varint. + // All of those bits are 1, so the varint extends into another byte. + return varint_decoder_.StartExtended(0x1f, db); + + case 0b01000000: + case 0b01000001: + case 0b01000010: + case 0b01000011: + case 0b01000100: + case 0b01000101: + case 0b01000110: + case 0b01000111: + case 0b01001000: + case 0b01001001: + case 0b01001010: + case 0b01001011: + case 0b01001100: + case 0b01001101: + case 0b01001110: + case 0b01001111: + case 0b01010000: + case 0b01010001: + case 0b01010010: + case 0b01010011: + case 0b01010100: + case 0b01010101: + case 0b01010110: + case 0b01010111: + case 0b01011000: + case 0b01011001: + case 0b01011010: + case 0b01011011: + case 0b01011100: + case 0b01011101: + case 0b01011110: + case 0b01011111: + case 0b01100000: + case 0b01100001: + case 0b01100010: + case 0b01100011: + case 0b01100100: + case 0b01100101: + case 0b01100110: + case 0b01100111: + case 0b01101000: + case 0b01101001: + case 0b01101010: + case 0b01101011: + case 0b01101100: + case 0b01101101: + case 0b01101110: + case 0b01101111: + case 0b01110000: + case 0b01110001: + case 0b01110010: + case 0b01110011: + case 0b01110100: + case 0b01110101: + case 0b01110110: + case 0b01110111: + case 0b01111000: + case 0b01111001: + case 0b01111010: + case 0b01111011: + case 0b01111100: + case 0b01111101: + case 0b01111110: + entry_type_ = HpackEntryType::kIndexedLiteralHeader; + // The low 6 bits of |byte| are the initial bits of the varint. + // One of those bits is 0, so the varint is only one byte long. + varint_decoder_.set_value(byte & 0x03f); + return DecodeStatus::kDecodeDone; + + case 0b01111111: + entry_type_ = HpackEntryType::kIndexedLiteralHeader; + // The low 6 bits of |byte| are the initial bits of the varint. + // All of those bits are 1, so the varint extends into another byte. + return varint_decoder_.StartExtended(0x3f, db); + + case 0b10000000: + case 0b10000001: + case 0b10000010: + case 0b10000011: + case 0b10000100: + case 0b10000101: + case 0b10000110: + case 0b10000111: + case 0b10001000: + case 0b10001001: + case 0b10001010: + case 0b10001011: + case 0b10001100: + case 0b10001101: + case 0b10001110: + case 0b10001111: + case 0b10010000: + case 0b10010001: + case 0b10010010: + case 0b10010011: + case 0b10010100: + case 0b10010101: + case 0b10010110: + case 0b10010111: + case 0b10011000: + case 0b10011001: + case 0b10011010: + case 0b10011011: + case 0b10011100: + case 0b10011101: + case 0b10011110: + case 0b10011111: + case 0b10100000: + case 0b10100001: + case 0b10100010: + case 0b10100011: + case 0b10100100: + case 0b10100101: + case 0b10100110: + case 0b10100111: + case 0b10101000: + case 0b10101001: + case 0b10101010: + case 0b10101011: + case 0b10101100: + case 0b10101101: + case 0b10101110: + case 0b10101111: + case 0b10110000: + case 0b10110001: + case 0b10110010: + case 0b10110011: + case 0b10110100: + case 0b10110101: + case 0b10110110: + case 0b10110111: + case 0b10111000: + case 0b10111001: + case 0b10111010: + case 0b10111011: + case 0b10111100: + case 0b10111101: + case 0b10111110: + case 0b10111111: + case 0b11000000: + case 0b11000001: + case 0b11000010: + case 0b11000011: + case 0b11000100: + case 0b11000101: + case 0b11000110: + case 0b11000111: + case 0b11001000: + case 0b11001001: + case 0b11001010: + case 0b11001011: + case 0b11001100: + case 0b11001101: + case 0b11001110: + case 0b11001111: + case 0b11010000: + case 0b11010001: + case 0b11010010: + case 0b11010011: + case 0b11010100: + case 0b11010101: + case 0b11010110: + case 0b11010111: + case 0b11011000: + case 0b11011001: + case 0b11011010: + case 0b11011011: + case 0b11011100: + case 0b11011101: + case 0b11011110: + case 0b11011111: + case 0b11100000: + case 0b11100001: + case 0b11100010: + case 0b11100011: + case 0b11100100: + case 0b11100101: + case 0b11100110: + case 0b11100111: + case 0b11101000: + case 0b11101001: + case 0b11101010: + case 0b11101011: + case 0b11101100: + case 0b11101101: + case 0b11101110: + case 0b11101111: + case 0b11110000: + case 0b11110001: + case 0b11110010: + case 0b11110011: + case 0b11110100: + case 0b11110101: + case 0b11110110: + case 0b11110111: + case 0b11111000: + case 0b11111001: + case 0b11111010: + case 0b11111011: + case 0b11111100: + case 0b11111101: + case 0b11111110: + entry_type_ = HpackEntryType::kIndexedHeader; + // The low 7 bits of |byte| are the initial bits of the varint. + // One of those bits is 0, so the varint is only one byte long. + varint_decoder_.set_value(byte & 0x07f); + return DecodeStatus::kDecodeDone; + + case 0b11111111: + entry_type_ = HpackEntryType::kIndexedHeader; + // The low 7 bits of |byte| are the initial bits of the varint. + // All of those bits are 1, so the varint extends into another byte. + return varint_decoder_.StartExtended(0x7f, db); + } + CHECK(false) << "Unreachable, byte=" << std::hex + << static_cast<uint32_t>(byte); + return DecodeStatus::kDecodeError; +} + +} // namespace net
diff --git a/net/http2/hpack/decoder/hpack_entry_type_decoder.h b/net/http2/hpack/decoder/hpack_entry_type_decoder.h new file mode 100644 index 0000000..d2c1f54 --- /dev/null +++ b/net/http2/hpack/decoder/hpack_entry_type_decoder.h
@@ -0,0 +1,56 @@ +// Copyright 2016 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 NET_HTTP2_HPACK_DECODER_HPACK_ENTRY_TYPE_DECODER_H_ +#define NET_HTTP2_HPACK_DECODER_HPACK_ENTRY_TYPE_DECODER_H_ + +// Decodes the type of an HPACK entry, and the variable length integer whose +// prefix is in the low-order bits of the same byte, "below" the type bits. +// The integer represents an index into static or dynamic table, which may be +// zero, or is the new size limit of the dynamic table. + +#include <string> + +#include "base/logging.h" +#include "net/base/net_export.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/hpack/decoder/hpack_varint_decoder.h" +#include "net/http2/hpack/http2_hpack_constants.h" + +namespace net { + +class NET_EXPORT_PRIVATE HpackEntryTypeDecoder { + public: + // Only call when the decode buffer has data (i.e. HpackEntryDecoder must + // not call until there is data). + DecodeStatus Start(DecodeBuffer* db); + + // Only call Resume if the previous call (Start or Resume) returned + // DecodeStatus::kDecodeInProgress. + DecodeStatus Resume(DecodeBuffer* db) { return varint_decoder_.Resume(db); } + + // Returns the decoded entry type. Only call if the preceding call to Start + // or Resume returned kDecodeDone. + HpackEntryType entry_type() const { return entry_type_; } + + // Returns the decoded variable length integer. Only call if the + // preceding call to Start or Resume returned kDecodeDone. + uint32_t varint() const { return varint_decoder_.value(); } + + std::string DebugString() const; + + private: + HpackVarintDecoder varint_decoder_; + + // This field is initialized just to keep ASAN happy about reading it + // from DebugString(). + HpackEntryType entry_type_ = HpackEntryType::kIndexedHeader; +}; + +NET_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const HpackEntryTypeDecoder& v); + +} // namespace net +#endif // NET_HTTP2_HPACK_DECODER_HPACK_ENTRY_TYPE_DECODER_H_
diff --git a/net/http2/hpack/decoder/hpack_entry_type_decoder_test.cc b/net/http2/hpack/decoder/hpack_entry_type_decoder_test.cc new file mode 100644 index 0000000..0abf0c9 --- /dev/null +++ b/net/http2/hpack/decoder/hpack_entry_type_decoder_test.cc
@@ -0,0 +1,98 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/decoder/hpack_entry_type_decoder.h" + +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/logging.h" +#include "net/http2/hpack/tools/hpack_block_builder.h" +#include "net/http2/tools/failure.h" +#include "net/http2/tools/random_decoder_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::AssertionFailure; +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; + +namespace net { +namespace test { +namespace { +const bool kReturnNonZeroOnFirst = true; + +class HpackEntryTypeDecoderTest : public RandomDecoderTest { + public: + AssertionResult ValidatorForDynamicTableSizeUpdate(uint32_t size) { + VERIFY_EQ(HpackEntryType::kDynamicTableSizeUpdate, decoder_.entry_type()); + VERIFY_EQ(size, decoder_.varint()); + return AssertionSuccess(); + } + + AssertionResult ValidatorForHeaderWithIndex(const HpackEntryType entry_type, + uint32_t index) { + VERIFY_EQ(entry_type, decoder_.entry_type()); + VERIFY_EQ(index, decoder_.varint()); + return AssertionSuccess(); + } + + protected: + DecodeStatus StartDecoding(DecodeBuffer* b) override { + CHECK_LT(0u, b->Remaining()); + return decoder_.Start(b); + } + + DecodeStatus ResumeDecoding(DecodeBuffer* b) override { + return decoder_.Resume(b); + } + + HpackEntryTypeDecoder decoder_; +}; + +TEST_F(HpackEntryTypeDecoderTest, DynamicTableSizeUpdate) { + for (uint32_t size = 0; size < 1000 * 1000; size += 256) { + HpackBlockBuilder bb; + bb.AppendDynamicTableSizeUpdate(size); + DecodeBuffer db(bb.buffer()); + NoArgValidator validator = base::Bind( + &HpackEntryTypeDecoderTest::ValidatorForDynamicTableSizeUpdate, + base::Unretained(this), size); + EXPECT_TRUE(DecodeAndValidateSeveralWays(&db, kReturnNonZeroOnFirst, + ValidateDoneAndEmpty(validator))) + << "\nentry_type=kDynamicTableSizeUpdate, size=" << size; + // Run the validator again to make sure that DecodeAndValidateSeveralWays + // did the right thing. + EXPECT_TRUE(validator.Run()); + } +} + +TEST_F(HpackEntryTypeDecoderTest, HeaderWithIndex) { + std::vector<HpackEntryType> entry_types = { + HpackEntryType::kIndexedHeader, HpackEntryType::kIndexedLiteralHeader, + HpackEntryType::kUnindexedLiteralHeader, + HpackEntryType::kNeverIndexedLiteralHeader, + }; + for (const HpackEntryType entry_type : entry_types) { + const uint32_t first = entry_type == HpackEntryType::kIndexedHeader ? 1 : 0; + for (uint32_t index = first; index < 1000; ++index) { + HpackBlockBuilder bb; + bb.AppendEntryTypeAndVarint(entry_type, index); + DecodeBuffer db(bb.buffer()); + NoArgValidator validator = + base::Bind(&HpackEntryTypeDecoderTest::ValidatorForHeaderWithIndex, + base::Unretained(this), entry_type, index); + EXPECT_TRUE(DecodeAndValidateSeveralWays(&db, kReturnNonZeroOnFirst, + ValidateDoneAndEmpty(validator))) + << "\nentry_type=" << entry_type << ", index=" << index; + // Run the validator again to make sure that DecodeAndValidateSeveralWays + // did the right thing. + EXPECT_TRUE(validator.Run()); + } + } +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/hpack/decoder/hpack_string_collector.cc b/net/http2/hpack/decoder/hpack_string_collector.cc new file mode 100644 index 0000000..e7e8195 --- /dev/null +++ b/net/http2/hpack/decoder/hpack_string_collector.cc
@@ -0,0 +1,127 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/decoder/hpack_string_collector.h" + +#include <stddef.h> + +#include <iosfwd> +#include <ostream> +#include <string> + +#include "net/base/escape.h" +#include "net/http2/tools/failure.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::StringPiece; + +namespace net { +namespace test { +namespace { + +std::ostream& operator<<(std::ostream& out, + HpackStringCollector::CollectorState v) { + switch (v) { + case HpackStringCollector::CollectorState::kGenesis: + return out << "kGenesis"; + case HpackStringCollector::CollectorState::kStarted: + return out << "kStarted"; + case HpackStringCollector::CollectorState::kEnded: + return out << "kEnded"; + } + return out << "UnknownCollectorState"; +} + +} // namespace + +HpackStringCollector::HpackStringCollector() { + Clear(); +} + +HpackStringCollector::HpackStringCollector(const std::string& str, bool huffman) + : s(str), len(str.size()), huffman_encoded(huffman), state(kEnded) {} + +void HpackStringCollector::Clear() { + s = ""; + len = 0; + huffman_encoded = false; + state = kGenesis; +} + +bool HpackStringCollector::IsClear() const { + return s == "" && len == 0 && huffman_encoded == false && state == kGenesis; +} + +bool HpackStringCollector::IsInProgress() const { + return state == kStarted; +} + +bool HpackStringCollector::HasEnded() const { + return state == kEnded; +} + +void HpackStringCollector::OnStringStart(bool huffman, size_t length) { + EXPECT_TRUE(IsClear()) << ToString(); + state = kStarted; + huffman_encoded = huffman; + len = length; + return; +} + +void HpackStringCollector::OnStringData(const char* data, size_t length) { + StringPiece sp(data, length); + EXPECT_TRUE(IsInProgress()) << ToString(); + EXPECT_LE(sp.size(), len) << ToString(); + sp.AppendToString(&s); + EXPECT_LE(s.size(), len) << ToString(); +} + +void HpackStringCollector::OnStringEnd() { + EXPECT_TRUE(IsInProgress()) << ToString(); + EXPECT_EQ(s.size(), len) << ToString(); + state = kEnded; +} + +::testing::AssertionResult HpackStringCollector::Collected( + StringPiece str, + bool is_huffman_encoded) const { + VERIFY_TRUE(HasEnded()); + VERIFY_EQ(str.size(), len); + VERIFY_EQ(is_huffman_encoded, huffman_encoded); + VERIFY_EQ(str, s); + return ::testing::AssertionSuccess(); +} + +std::string HpackStringCollector::ToString() const { + std::stringstream ss; + ss << *this; + return ss.str(); +} + +bool operator==(const HpackStringCollector& a, const HpackStringCollector& b) { + return a.s == b.s && a.len == b.len && + a.huffman_encoded == b.huffman_encoded && a.state == b.state; +} + +bool operator!=(const HpackStringCollector& a, const HpackStringCollector& b) { + return !(a == b); +} + +std::ostream& operator<<(std::ostream& out, const HpackStringCollector& v) { + out << "HpackStringCollector(state=" << v.state; + if (v.state == HpackStringCollector::kGenesis) { + return out << ")"; + } + if (v.huffman_encoded) { + out << ", Huffman Encoded"; + } + out << ", Length=" << v.len; + if (!v.s.empty() && v.len != v.s.size()) { + out << " (" << v.s.size() << ")"; + } + return out << ", String=\"" << EscapeQueryParamValue(v.s, false) << "\")"; +} + +} // namespace test +} // namespace net
diff --git a/net/http2/hpack/decoder/hpack_string_collector.h b/net/http2/hpack/decoder/hpack_string_collector.h new file mode 100644 index 0000000..1bc5831 --- /dev/null +++ b/net/http2/hpack/decoder/hpack_string_collector.h
@@ -0,0 +1,63 @@ +// Copyright 2016 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 NET_HTTP2_HPACK_DECODER_HPACK_STRING_COLLECTOR_H_ +#define NET_HTTP2_HPACK_DECODER_HPACK_STRING_COLLECTOR_H_ + +// Supports tests of decoding HPACK strings. + +#include <stddef.h> + +#include <iosfwd> +#include <string> + +#include "base/strings/string_piece.h" +#include "net/http2/hpack/decoder/hpack_string_decoder_listener.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace test { + +// Records the callbacks associated with a decoding a string; must +// call Clear() between decoding successive strings. +struct HpackStringCollector : public HpackStringDecoderListener { + enum CollectorState { + kGenesis, + kStarted, + kEnded, + }; + + HpackStringCollector(); + HpackStringCollector(const std::string& str, bool huffman); + + void Clear(); + bool IsClear() const; + bool IsInProgress() const; + bool HasEnded() const; + + void OnStringStart(bool huffman, size_t length) override; + void OnStringData(const char* data, size_t length) override; + void OnStringEnd() override; + + ::testing::AssertionResult Collected(base::StringPiece str, + bool is_huffman_encoded) const; + + std::string ToString() const; + + std::string s; + size_t len; + bool huffman_encoded; + CollectorState state; +}; + +bool operator==(const HpackStringCollector& a, const HpackStringCollector& b); + +bool operator!=(const HpackStringCollector& a, const HpackStringCollector& b); + +std::ostream& operator<<(std::ostream& out, const HpackStringCollector& v); + +} // namespace test +} // namespace net + +#endif // NET_HTTP2_HPACK_DECODER_HPACK_STRING_COLLECTOR_H_
diff --git a/net/http2/hpack/decoder/hpack_string_decoder.cc b/net/http2/hpack/decoder/hpack_string_decoder.cc new file mode 100644 index 0000000..4131e01 --- /dev/null +++ b/net/http2/hpack/decoder/hpack_string_decoder.cc
@@ -0,0 +1,39 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/decoder/hpack_string_decoder.h" + +#include <sstream> + +namespace net { + +std::string HpackStringDecoder::DebugString() const { + std::stringstream ss; + ss << "HpackStringDecoder(state=" << StateToString(state_) + << ", length=" << length_decoder_.DebugString() + << ", remaining=" << remaining_ + << ", huffman=" << (huffman_encoded_ ? "true)" : "false)"); + return ss.str(); +} + +// static +std::string HpackStringDecoder::StateToString(StringDecoderState v) { + switch (v) { + case kStartDecodingLength: + return "kStartDecodingLength"; + case kDecodingString: + return "kDecodingString"; + case kResumeDecodingLength: + return "kResumeDecodingLength"; + } + std::stringstream ss; + ss << "UNKNOWN_STATE(" << static_cast<uint32_t>(v) << ")"; + return ss.str(); +} + +std::ostream& operator<<(std::ostream& out, const HpackStringDecoder& v) { + return out << v.DebugString(); +} + +} // namespace net
diff --git a/net/http2/hpack/decoder/hpack_string_decoder.h b/net/http2/hpack/decoder/hpack_string_decoder.h new file mode 100644 index 0000000..baea422 --- /dev/null +++ b/net/http2/hpack/decoder/hpack_string_decoder.h
@@ -0,0 +1,236 @@ +// Copyright 2016 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 NET_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_ +#define NET_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_ + +// HpackStringDecoder decodes strings encoded per the HPACK spec; this does +// not mean decompressing Huffman encoded strings, just identifying the length, +// encoding and contents for a listener. + +#include <stddef.h> + +#include <algorithm> +#include <string> + +#include "base/logging.h" +#include "base/macros.h" +#include "net/base/net_export.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/hpack/decoder/hpack_varint_decoder.h" + +namespace net { + +// Decodes a single string in an HPACK header entry. The high order bit of +// the first byte of the length is the H (Huffman) bit indicating whether +// the value is Huffman encoded, and the remainder of the byte is the first +// 7 bits of an HPACK varint. +// +// Call Start() to begin decoding; if it returns kDecodeInProgress, then call +// Resume() when more input is available, repeating until kDecodeInProgress is +// not returned. If kDecodeDone or kDecodeError is returned, then Resume() must +// not be called until Start() has been called to start decoding a new string. +// +// There are 3 variants of Start in this class, participants in a performance +// experiment. Perflab experiments show it is generally fastest to call +// StartSpecialCaseShort rather than StartOnly (~9% slower) or +// StartAndDecodeLength (~10% slower). +class NET_EXPORT_PRIVATE HpackStringDecoder { + public: + enum StringDecoderState { + kStartDecodingLength, + kDecodingString, + kResumeDecodingLength, + }; + + // TODO(jamessynge): Get rid of all but one of the Start and Resume methods + // after all of the HPACK decoder is checked in and has been perf tested. + template <class Listener> + DecodeStatus Start(DecodeBuffer* db, Listener* cb) { + return StartSpecialCaseShort(db, cb); + } + + template <class Listener> + DecodeStatus StartOnly(DecodeBuffer* db, Listener* cb) { + state_ = kStartDecodingLength; + return Resume(db, cb); + } + + template <class Listener> + DecodeStatus StartAndDecodeLength(DecodeBuffer* db, Listener* cb) { + DecodeStatus status; + if (StartDecodingLength(db, cb, &status)) { + state_ = kDecodingString; + return DecodeString(db, cb); + } + return status; + } + + template <class Listener> + DecodeStatus StartSpecialCaseShort(DecodeBuffer* db, Listener* cb) { + // Fast decode path is used if the string is under 127 bytes and the + // entire length of the string is in the decode buffer. More than 83% of + // string lengths are encoded in just one byte. + if (db->HasData() && (*db->cursor() & 0x7f) != 0x7f) { + // The string is short. + uint8_t h_and_prefix = db->DecodeUInt8(); + uint8_t length = h_and_prefix & 0x7f; + bool huffman_encoded = (h_and_prefix & 0x80) == 0x80; + cb->OnStringStart(huffman_encoded, length); + if (length <= db->Remaining()) { + // Yeah, we've got the whole thing in the decode buffer. + // Ideally this will be the common case. Note that we don't + // update any of the member variables in this path. + cb->OnStringData(db->cursor(), length); + db->AdvanceCursor(length); + cb->OnStringEnd(); + return DecodeStatus::kDecodeDone; + } + // Not all in the buffer. + huffman_encoded_ = huffman_encoded; + remaining_ = length; + // Call Resume to decode the string body, which is only partially + // in the decode buffer (or not at all). + state_ = kDecodingString; + return Resume(db, cb); + } + // Call Resume to decode the string length, which is either not in + // the decode buffer, or spans multiple bytes. + state_ = kStartDecodingLength; + return Resume(db, cb); + } + + template <class Listener> + DecodeStatus Resume(DecodeBuffer* db, Listener* cb) { + DecodeStatus status; + while (true) { + switch (state_) { + case kStartDecodingLength: + DVLOG(2) << "kStartDecodingLength: db->Remaining=" << db->Remaining(); + if (!StartDecodingLength(db, cb, &status)) { + // The length is split across decode buffers. + return status; + } + // We've finished decoding the length, which spanned one or more + // bytes. Approximately 17% of strings have a length that is greater + // than 126 bytes, and thus the length is encoded in more than one + // byte, and so doesn't get the benefit of the optimization in + // Start() for single byte lengths. But, we still expect that most + // of such strings will be contained entirely in a single decode + // buffer, and hence this fall through skips another trip through the + // switch above and more importantly skips setting the state_ variable + // again in those cases where we don't need it. + + // FALLTHROUGH_INTENDED + + case kDecodingString: + DVLOG(2) << "kDecodingString: db->Remaining=" << db->Remaining() + << " remaining_=" << remaining_; + return DecodeString(db, cb); + + case kResumeDecodingLength: + DVLOG(2) << "kResumeDecodingLength: db->Remaining=" + << db->Remaining(); + if (!ResumeDecodingLength(db, cb, &status)) { + return status; + } + } + } + } + + std::string DebugString() const; + + private: + static std::string StateToString(StringDecoderState v); + + // Returns true if the length is fully decoded and the listener wants the + // decoding to continue, false otherwise; status is set to the status from + // the varint decoder. + // If the length is not fully decoded, case state_ is set appropriately + // for the next call to Resume. + template <class Listener> + bool StartDecodingLength(DecodeBuffer* db, + Listener* cb, + DecodeStatus* status) { + if (db->Empty()) { + *status = DecodeStatus::kDecodeInProgress; + state_ = kStartDecodingLength; + return false; + } + uint8_t h_and_prefix = db->DecodeUInt8(); + huffman_encoded_ = (h_and_prefix & 0x80) == 0x80; + *status = length_decoder_.Start(h_and_prefix, 0x7f, db); + if (*status == DecodeStatus::kDecodeDone) { + OnStringStart(cb, status); + return true; + } + // Set the state to cover the DecodeStatus::kDecodeInProgress case. + // Won't be needed if the status is kDecodeError. + state_ = kResumeDecodingLength; + return false; + } + + // Returns true if the length is fully decoded and the listener wants the + // decoding to continue, false otherwise; status is set to the status from + // the varint decoder; state_ is updated when fully decoded. + // If the length is not fully decoded, case state_ is set appropriately + // for the next call to Resume. + template <class Listener> + bool ResumeDecodingLength(DecodeBuffer* db, + Listener* cb, + DecodeStatus* status) { + DCHECK_EQ(state_, kResumeDecodingLength); + *status = length_decoder_.Resume(db); + if (*status == DecodeStatus::kDecodeDone) { + state_ = kDecodingString; + OnStringStart(cb, status); + return true; + } + return false; + } + + // Returns true if the listener wants the decoding to continue, and + // false otherwise, in which case status set. + template <class Listener> + void OnStringStart(Listener* cb, DecodeStatus* status) { + remaining_ = length_decoder_.value(); + // Make callback so consumer knows what is coming. + cb->OnStringStart(huffman_encoded_, remaining_); + return; + } + + // Passes the available portion of the string to the listener, and signals + // the end of the string when it is reached. Returns kDecodeDone or + // kDecodeInProgress as appropriate. + template <class Listener> + DecodeStatus DecodeString(DecodeBuffer* db, Listener* cb) { + size_t len = std::min(remaining_, db->Remaining()); + if (len > 0) { + cb->OnStringData(db->cursor(), len); + db->AdvanceCursor(len); + remaining_ -= len; + } + if (remaining_ == 0) { + cb->OnStringEnd(); + return DecodeStatus::kDecodeDone; + } + state_ = kDecodingString; + return DecodeStatus::kDecodeInProgress; + } + + HpackVarintDecoder length_decoder_; + + // These fields are initialized just to keep ASAN happy about reading + // them from DebugString(). + size_t remaining_ = 0; + StringDecoderState state_ = kStartDecodingLength; + bool huffman_encoded_ = false; +}; + +NET_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const HpackStringDecoder& v); + +} // namespace net +#endif // NET_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_H_
diff --git a/net/http2/hpack/decoder/hpack_string_decoder_listener.cc b/net/http2/hpack/decoder/hpack_string_decoder_listener.cc new file mode 100644 index 0000000..97ec985 --- /dev/null +++ b/net/http2/hpack/decoder/hpack_string_decoder_listener.cc
@@ -0,0 +1,36 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/decoder/hpack_string_decoder_listener.h" + +#include "base/logging.h" + +namespace net { +namespace test { + +void HpackStringDecoderVLoggingListener::OnStringStart(bool huffman_encoded, + size_t len) { + VLOG(1) << "OnStringStart: H=" << huffman_encoded << ", len=" << len; + if (wrapped_) { + wrapped_->OnStringStart(huffman_encoded, len); + } +} + +void HpackStringDecoderVLoggingListener::OnStringData(const char* data, + size_t len) { + VLOG(1) << "OnStringData: len=" << len; + if (wrapped_) { + return wrapped_->OnStringData(data, len); + } +} + +void HpackStringDecoderVLoggingListener::OnStringEnd() { + VLOG(1) << "OnStringEnd"; + if (wrapped_) { + return wrapped_->OnStringEnd(); + } +} + +} // namespace test +} // namespace net
diff --git a/net/http2/hpack/decoder/hpack_string_decoder_listener.h b/net/http2/hpack/decoder/hpack_string_decoder_listener.h new file mode 100644 index 0000000..fbeeeb4 --- /dev/null +++ b/net/http2/hpack/decoder/hpack_string_decoder_listener.h
@@ -0,0 +1,62 @@ +// Copyright 2016 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 NET_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_LISTENER_H_ +#define NET_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_LISTENER_H_ + +// Defines HpackStringDecoderListener which defines the methods required by an +// HpackStringDecoder. Also defines HpackStringDecoderVLoggingListener which +// logs before calling another HpackStringDecoderListener implementation. +// For now these are only used by tests, so placed in the test namespace. + +#include <stddef.h> + +#include "net/base/net_export.h" + +namespace net { +namespace test { + +// HpackStringDecoder methods require a listener that implements the methods +// below, but it is NOT necessary to extend this class because the methods +// are templates. +class NET_EXPORT_PRIVATE HpackStringDecoderListener { + public: + virtual ~HpackStringDecoderListener() {} + + // Called at the start of decoding an HPACK string. The encoded length of the + // string is |len| bytes, which may be zero. The string is Huffman encoded + // if huffman_encoded is true, else it is plain text (i.e. the encoded length + // is then the plain text length). + virtual void OnStringStart(bool huffman_encoded, size_t len) = 0; + + // Called when some data is available, or once when the string length is zero + // (to simplify the decoder, it doesn't have a special case for len==0). + virtual void OnStringData(const char* data, size_t len) = 0; + + // Called after OnStringData has provided all of the encoded bytes of the + // string. + virtual void OnStringEnd() = 0; +}; + +class NET_EXPORT_PRIVATE HpackStringDecoderVLoggingListener + : public HpackStringDecoderListener { + public: + HpackStringDecoderVLoggingListener() : wrapped_(nullptr) {} + explicit HpackStringDecoderVLoggingListener( + HpackStringDecoderListener* wrapped) + : wrapped_(wrapped) {} + ~HpackStringDecoderVLoggingListener() override {} + + void OnStringStart(bool huffman_encoded, size_t len) override; + void OnStringData(const char* data, size_t len) override; + void OnStringEnd() override; + + private: + HpackStringDecoderListener* const wrapped_; +}; + +} // namespace test +} // namespace net + +#endif // NET_HTTP2_HPACK_DECODER_HPACK_STRING_DECODER_LISTENER_H_
diff --git a/net/http2/hpack/decoder/hpack_string_decoder_test.cc b/net/http2/hpack/decoder/hpack_string_decoder_test.cc new file mode 100644 index 0000000..e4afc61 --- /dev/null +++ b/net/http2/hpack/decoder/hpack_string_decoder_test.cc
@@ -0,0 +1,194 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/decoder/hpack_string_decoder.h" + +// Tests of HpackStringDecoder. + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/strings/string_piece.h" +#include "net/http2/hpack/decoder/hpack_string_collector.h" +#include "net/http2/hpack/decoder/hpack_string_decoder_listener.h" +#include "net/http2/hpack/tools/hpack_block_builder.h" +#include "net/http2/tools/failure.h" +#include "net/http2/tools/http2_random.h" +#include "net/http2/tools/random_decoder_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using base::StringPiece; +using std::string; + +namespace net { +namespace test { +namespace { + +const bool kMayReturnZeroOnFirst = false; +const bool kCompressed = true; +const bool kUncompressed = false; + +enum StartMethod { + kStart, + kStartOnly, + kStartAndDecodeLength, + kStartSpecialCaseShort, +}; + +class HpackStringDecoderTest + : public RandomDecoderTest, + public ::testing::WithParamInterface<StartMethod> { + protected: + HpackStringDecoderTest() + : start_method_(GetParam()), listener_(&collector_) {} + + DecodeStatus StartDecoding(DecodeBuffer* b) override { + ++start_decoding_calls_; + collector_.Clear(); + switch (start_method_) { + case kStart: + return decoder_.Start(b, &listener_); + case kStartOnly: + return decoder_.StartOnly(b, &listener_); + case kStartAndDecodeLength: + return decoder_.StartAndDecodeLength(b, &listener_); + case kStartSpecialCaseShort: + return decoder_.StartSpecialCaseShort(b, &listener_); + default: + return DecodeStatus::kDecodeError; + } + } + + DecodeStatus ResumeDecoding(DecodeBuffer* b) override { + // Provides coverage of DebugString and StateToString. + // Not validating output. + VLOG(1) << decoder_.DebugString(); + VLOG(2) << collector_; + return decoder_.Resume(b, &listener_); + } + + AssertionResult Collected(StringPiece s, bool huffman_encoded) { + VLOG(1) << collector_; + return collector_.Collected(s, huffman_encoded); + } + + // Note that base::Bind() makes a copy of |expected_str| even though it is + // taken as a constant reference, so even if MakeValidator is called with a + // C-style string that is cast to a temporary std::string that gets destroyed + // after the call to MakeValidator, |expected_str| is still valid later when + // the Validator is run. + AssertionResult StringValidator(const string& expected_str, + bool expected_huffman, + const DecodeBuffer& input, + DecodeStatus status) { + AssertionResult result = Collected(expected_str, expected_huffman); + if (result) { + VERIFY_EQ(collector_, + HpackStringCollector(expected_str, expected_huffman)); + } else { + VERIFY_NE(collector_, + HpackStringCollector(expected_str, expected_huffman)); + } + VLOG(2) << collector_.ToString(); + collector_.Clear(); + VLOG(2) << collector_; + return result; + } + + Validator MakeValidator(const string& expected_str, bool expected_huffman) { + return base::Bind(&HpackStringDecoderTest::StringValidator, + base::Unretained(this), expected_str, expected_huffman); + } + + const StartMethod start_method_; + HpackStringDecoder decoder_; + HpackStringCollector collector_; + HpackStringDecoderVLoggingListener listener_; + size_t start_decoding_calls_ = 0; +}; + +TEST_P(HpackStringDecoderTest, DecodeEmptyString) { + { + Validator validator = ValidateDoneAndEmpty(MakeValidator("", kCompressed)); + const char kData[] = {0x80u}; + DecodeBuffer b(kData); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator)); + } + { + // Make sure it stops after decoding the empty string. + Validator validator = + ValidateDoneAndOffset(1, MakeValidator("", kUncompressed)); + const char kData[] = {0x00, 0xffu}; + DecodeBuffer b(kData); + EXPECT_EQ(2u, b.Remaining()); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator)); + EXPECT_EQ(1u, b.Remaining()); + } +} + +TEST_P(HpackStringDecoderTest, DecodeShortString) { + { + // Make sure it stops after decoding the non-empty string. + Validator validator = + ValidateDoneAndOffset(11, MakeValidator("start end.", kCompressed)); + const char kData[] = "\x8astart end.Don't peek at this."; + DecodeBuffer b(kData); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator)); + } + { + Validator validator = + ValidateDoneAndOffset(11, MakeValidator("start end.", kUncompressed)); + StringPiece data("\x0astart end."); + DecodeBuffer b(data); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, kMayReturnZeroOnFirst, validator)); + } +} + +TEST_P(HpackStringDecoderTest, DecodeLongStrings) { + string name = Random().RandString(1024); + string value = Random().RandString(65536); + HpackBlockBuilder hbb; + + hbb.AppendString(false, name); + uint32_t offset_after_name = hbb.size(); + EXPECT_EQ(3 + name.size(), offset_after_name); + + hbb.AppendString(true, value); + uint32_t offset_after_value = hbb.size(); + EXPECT_EQ(3 + name.size() + 4 + value.size(), offset_after_value); + + DecodeBuffer b(hbb.buffer()); + + // Decode the name... + EXPECT_TRUE(DecodeAndValidateSeveralWays( + &b, kMayReturnZeroOnFirst, + ValidateDoneAndOffset(offset_after_name, + MakeValidator(name, kUncompressed)))); + EXPECT_EQ(offset_after_name, b.Offset()); + EXPECT_EQ(offset_after_value - offset_after_name, b.Remaining()); + + // Decode the value... + EXPECT_TRUE(DecodeAndValidateSeveralWays( + &b, kMayReturnZeroOnFirst, + ValidateDoneAndOffset(offset_after_value - offset_after_name, + MakeValidator(value, kCompressed)))); + EXPECT_EQ(offset_after_value, b.Offset()); + EXPECT_EQ(0u, b.Remaining()); +} + +INSTANTIATE_TEST_CASE_P(AllStartMethods, + HpackStringDecoderTest, + ::testing::Values(kStart, + kStartOnly, + kStartAndDecodeLength, + kStartSpecialCaseShort)); + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/hpack/decoder/hpack_varint_decoder.cc b/net/http2/hpack/decoder/hpack_varint_decoder.cc new file mode 100644 index 0000000..f04bd89d --- /dev/null +++ b/net/http2/hpack/decoder/hpack_varint_decoder.cc
@@ -0,0 +1,36 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/decoder/hpack_varint_decoder.h" + +#include <sstream> + +namespace net { + +std::string HpackVarintDecoder::DebugString() const { + std::stringstream ss; + ss << "HpackVarintDecoder(value=" << value_ << ", offset=" << offset_ << ")"; + return ss.str(); +} + +DecodeStatus HpackVarintDecoder::StartForTest(uint8_t prefix_value, + uint8_t prefix_mask, + DecodeBuffer* db) { + return Start(prefix_value, prefix_mask, db); +} + +DecodeStatus HpackVarintDecoder::StartExtendedForTest(uint8_t prefix_mask, + DecodeBuffer* db) { + return StartExtended(prefix_mask, db); +} + +DecodeStatus HpackVarintDecoder::ResumeForTest(DecodeBuffer* db) { + return Resume(db); +} + +std::ostream& operator<<(std::ostream& out, const HpackVarintDecoder& v) { + return out << v.DebugString(); +} + +} // namespace net
diff --git a/net/http2/hpack/decoder/hpack_varint_decoder.h b/net/http2/hpack/decoder/hpack_varint_decoder.h new file mode 100644 index 0000000..b110fad --- /dev/null +++ b/net/http2/hpack/decoder/hpack_varint_decoder.h
@@ -0,0 +1,181 @@ +// Copyright 2016 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. + +// HpackVarintDecoder decodes HPACK variable length unsigned integers. These +// integers are used to identify static or dynamic table index entries, to +// specify string lengths, and to update the size limit of the dynamic table. +// +// The caller will need to validate that the decoded value is in an acceptable +// range. +// +// In order to support naive encoders (i.e. which always output 5 extension +// bytes for a uint32 that is >= prefix_mask), the decoder supports an an +// encoding with up to 5 extension bytes, and a maximum value of 268,435,582 +// (4 "full" extension bytes plus the maximum for a prefix, 127). It could be +// modified to support a lower maximum value (by requiring that extensions bytes +// be "empty"), or a larger value if valuable for some reason I can't see. +// +// For details of the encoding, see: +// http://httpwg.org/specs/rfc7541.html#integer.representation +// +// TODO(jamessynge): Consider dropping support for encodings of more than 4 +// bytes, including the prefix byte, as in practice we only see at most 3 bytes, +// and 4 bytes would cover any desire to support large (but not ridiculously +// large) header values. + +#ifndef NET_HTTP2_HPACK_DECODER_HPACK_VARINT_DECODER_H_ +#define NET_HTTP2_HPACK_DECODER_HPACK_VARINT_DECODER_H_ + +#include <string> + +#include "base/logging.h" +#include "net/base/net_export.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" + +namespace net { +// Decodes an HPACK variable length unsigned integer, in a resumable fashion +// so it can handle running out of input in the DecodeBuffer. Call Start or +// StartExtended the first time (when decoding the byte that contains the +// prefix), then call Resume later if it is necessary to resume. When done, +// call value() to retrieve the decoded value. +// +// No constructor or destructor. Holds no resources, so destruction isn't +// needed. Start and StartExtended handles the initialization of member +// variables. This is necessary in order for HpackVarintDecoder to be part +// of a union. +class NET_EXPORT_PRIVATE HpackVarintDecoder { + public: + // |prefix_value| is the first byte of the encoded varint. + // |prefix_mask| is the mask of the valid bits, i.e. without the top 1 to 4 + // high-bits set, as appropriate for the item being decoded; must be a + // contiguous sequence of set bits, starting with the low-order bits. + DecodeStatus Start(uint8_t prefix_value, + uint8_t prefix_mask, + DecodeBuffer* db) { + DCHECK_LE(15, prefix_mask) << std::hex << prefix_mask; + DCHECK_LE(prefix_mask, 127) << std::hex << prefix_mask; + // Confirm that |prefix_mask| is a contiguous sequence of bits. + DCHECK_EQ(0, (prefix_mask + 1) & prefix_mask) << std::hex << prefix_mask; + + // Ignore the bits that aren't a part of the prefix of the varint. + value_ = prefix_value & prefix_mask; + + if (value_ < prefix_mask) { + MarkDone(); + return DecodeStatus::kDecodeDone; + } + + offset_ = 0; + return Resume(db); + } + + // The caller has already determined that the encoding requires multiple + // bytes, i.e. that the 4 to 7 low-order bits (the number determined by the + // prefix length, a value not passed into this function) of the first byte are + // are all 1. The caller passes in |prefix_mask|, which is 2^prefix_length-1. + DecodeStatus StartExtended(uint8_t prefix_mask, DecodeBuffer* db) { + DCHECK_LE(15, prefix_mask) << std::hex << prefix_mask; + DCHECK_LE(prefix_mask, 127) << std::hex << prefix_mask; + // Confirm that |prefix_mask| is a contiguous sequence of bits. + DCHECK_EQ(0, prefix_mask & (prefix_mask + 1)) << std::hex << prefix_mask; + + value_ = prefix_mask; + offset_ = 0; + return Resume(db); + } + + // Resume decoding a variable length integer after an earlier + // call to Start or StartExtended returned kDecodeInProgress. + DecodeStatus Resume(DecodeBuffer* db) { + CheckNotDone(); + do { + if (db->Empty()) { + return DecodeStatus::kDecodeInProgress; + } + uint8_t byte = db->DecodeUInt8(); + value_ += (byte & 0x7f) << offset_; + if ((byte & 0x80) == 0) { + if (offset_ < MaxOffset() || byte == 0) { + MarkDone(); + return DecodeStatus::kDecodeDone; + } + break; + } + offset_ += 7; + } while (offset_ <= MaxOffset()); + DLOG(WARNING) << "Variable length int encoding is too large or too long. " + << DebugString(); + MarkDone(); + return DecodeStatus::kDecodeError; + } + + uint32_t value() const { + CheckDone(); + return value_; + } + + // This supports optimizations for the case of a varint with zero extension + // bytes, where the handling of the prefix is done by the caller. + void set_value(uint32_t v) { + MarkDone(); + value_ = v; + } + + // All the public methods below are for supporting assertions and tests. + + std::string DebugString() const; + + // For benchmarking, these methods ensure the decoder + // is NOT inlined into the caller. + DecodeStatus StartForTest(uint8_t prefix_value, + uint8_t prefix_mask, + DecodeBuffer* db); + DecodeStatus StartExtendedForTest(uint8_t prefix_mask, DecodeBuffer* db); + DecodeStatus ResumeForTest(DecodeBuffer* db); + + static constexpr uint32_t MaxExtensionBytes() { return 5; } + + // Returns the highest value with the specified number of extension bytes and + // the specified prefix length (bits). + static uint64_t constexpr HiValueOfExtensionBytes(uint32_t extension_bytes, + uint32_t prefix_length) { + return (1 << prefix_length) - 2 + + (extension_bytes == 0 ? 0 : (1LLU << (extension_bytes * 7))); + } + + private: + // Protection in case Resume is called when it shouldn't be. + void MarkDone() { +#ifndef NDEBUG + // We support up to 5 extension bytes, so offset_ should never be > 28 when + // it makes sense to call Resume(). + offset_ = MaxOffset() + 7; +#endif + } + void CheckNotDone() const { +#ifndef NDEBUG + DCHECK_LE(offset_, MaxOffset()); +#endif + } + void CheckDone() const { +#ifndef NDEBUG + DCHECK_GT(offset_, MaxOffset()); +#endif + } + static constexpr uint32_t MaxOffset() { + return 7 * (MaxExtensionBytes() - 1); + } + + // These fields are initialized just to keep ASAN happy about reading + // them from DebugString(). + uint32_t value_ = 0; + uint32_t offset_ = 0; +}; + +std::ostream& operator<<(std::ostream& out, const HpackVarintDecoder& v); + +} // namespace net + +#endif // NET_HTTP2_HPACK_DECODER_HPACK_VARINT_DECODER_H_
diff --git a/net/http2/hpack/decoder/hpack_varint_decoder_test.cc b/net/http2/hpack/decoder/hpack_varint_decoder_test.cc new file mode 100644 index 0000000..e63e9f0 --- /dev/null +++ b/net/http2/hpack/decoder/hpack_varint_decoder_test.cc
@@ -0,0 +1,395 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/decoder/hpack_varint_decoder.h" + +// Tests of HpackVarintDecoder. + +#include <stddef.h> + +#include <ios> +#include <iterator> +#include <ostream> +#include <set> +#include <sstream> +#include <vector> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/strings/stringprintf.h" +#include "net/http2/hpack/tools/hpack_block_builder.h" +#include "net/http2/tools/failure.h" +#include "net/http2/tools/http2_random.h" +#include "net/http2/tools/random_decoder_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::AssertionFailure; +using ::testing::AssertionSuccess; +using base::StringPiece; +using base::StringPrintf; +using std::string; + +namespace net { +namespace test { +namespace { + +class HpackVarintDecoderTest : public RandomDecoderTest { + public: + AssertionResult ValidatorForValueTooLarge(bool* validated, + uint32_t expected_offset, + const DecodeBuffer& db, + DecodeStatus status) { + *validated = true; + VERIFY_EQ(DecodeStatus::kDecodeError, status); + VERIFY_EQ(expected_offset, db.Offset()); + return AssertionSuccess(); + } + + protected: + DecodeStatus StartDecoding(DecodeBuffer* b) override { + CHECK_LT(0u, b->Remaining()); + CHECK_NE(0, prefix_mask_); + uint8_t prefix = b->DecodeUInt8(); + return decoder_.Start(prefix, prefix_mask_, b); + } + + DecodeStatus ResumeDecoding(DecodeBuffer* b) override { + return decoder_.Resume(b); + } + + AssertionResult ValidatorForDecodeSeveralWays(uint32_t expected_value, + const DecodeBuffer& db, + DecodeStatus status) { + if (decoder_.value() != expected_value) { + return AssertionFailure() + << "Value doesn't match expected: " << decoder_.value() + << " != " << expected_value; + } + return AssertionSuccess(); + } + + void DecodeSeveralWays(uint32_t expected_value, uint32_t expected_offset) { + // The validator is called after each of the several times that the input + // DecodeBuffer is decoded, each with a different segmentation of the input. + // Validate that decoder_.value() matches the expected value. + Validator validator = + base::Bind(&HpackVarintDecoderTest::ValidatorForDecodeSeveralWays, + base::Unretained(this), expected_value); + + // First validate that decoding is done and that we've advanced the cursor + // the expected amount. + validator = ValidateDoneAndOffset(expected_offset, validator); + + // StartDecoding, above, requires the DecodeBuffer be non-empty so that it + // can call Start with the prefix byte. + bool return_non_zero_on_first = true; + + DecodeBuffer b(buffer_); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, return_non_zero_on_first, validator)); + + EXPECT_EQ(expected_value, decoder_.value()); + EXPECT_EQ(expected_offset, b.Offset()); + } + + void EncodeNoRandom(uint32_t value, uint8_t prefix_length) { + DCHECK_LE(4, prefix_length); + DCHECK_LE(prefix_length, 7); + prefix_length_ = prefix_length; + + HpackBlockBuilder bb; + bb.AppendHighBitsAndVarint(0, prefix_length_, value); + buffer_ = bb.buffer(); + ASSERT_LT(0u, buffer_.size()); + + // Note: setting member variable prefix_mask_ here, which will be read + // in StartDecoding above. + prefix_mask_ = (1 << prefix_length_) - 1; + ASSERT_EQ(buffer_[0], buffer_[0] & prefix_mask_); + } + + void Encode(uint32_t value, uint8_t prefix_length) { + EncodeNoRandom(value, prefix_length); + // Add some random bits to the prefix (the first byte) above the mask. + uint8_t prefix = buffer_[0]; + buffer_[0] = prefix | (Random().Rand8() << prefix_length); + ASSERT_EQ(prefix, buffer_[0] & prefix_mask_); + } + + // This is really a test of HpackBlockBuilder, making sure that the input to + // HpackVarintDecoder is as expected, which also acts as confirmation that + // my thinking about the encodings being used by the tests, i.e. cover the + // range desired. + void ValidateEncoding(uint32_t value, + uint32_t minimum, + uint32_t maximum, + size_t expected_bytes) { + ASSERT_EQ(expected_bytes, buffer_.size()); + if (expected_bytes > 1) { + EXPECT_EQ(prefix_mask_, buffer_[0] & prefix_mask_); + size_t last = expected_bytes - 1; + for (size_t ndx = 1; ndx < last; ++ndx) { + // Before the last extension byte, we expect the high-bit set. + uint8_t byte = buffer_[ndx]; + if (value == minimum) { + EXPECT_EQ(0x80, byte) << "ndx=" << ndx; + } else if (value == maximum) { + EXPECT_EQ(0xff, byte) << "ndx=" << ndx; + } else { + EXPECT_EQ(0x80, byte & 0x80) << "ndx=" << ndx; + } + } + // The last extension byte should not have the high-bit set. + uint8_t byte = buffer_[last]; + if (value == minimum) { + if (expected_bytes == 2) { + EXPECT_EQ(0x00, byte); + } else { + EXPECT_EQ(0x01, byte); + } + } else if (value == maximum) { + EXPECT_EQ(0x7f, byte); + } else { + EXPECT_EQ(0x00, byte & 0x80); + } + } else { + EXPECT_EQ(value, static_cast<uint32_t>(buffer_[0] & prefix_mask_)); + EXPECT_LT(value, static_cast<uint32_t>(prefix_mask_)); + } + } + + void EncodeAndDecodeValues(const std::set<uint32_t>& values, + uint8_t prefix_length, + size_t expected_bytes) { + CHECK(!values.empty()); + const uint32_t minimum = *values.begin(); + const uint32_t maximum = *values.rbegin(); + for (const uint32_t value : values) { + Encode(value, prefix_length); // Sets prefix_mask_ and buffer_ + + std::stringstream ss; + ss << "value=" << value << " (0x" << std::hex << value + << "), prefix_length=" << std::dec << prefix_length + << ", expected_bytes=" << expected_bytes << std::endl + << HexEncode(buffer_); + string msg(ss.str()); + + if (value == minimum) { + LOG(INFO) << "Checking minimum; " << msg; + } else if (value == maximum) { + LOG(INFO) << "Checking maximum; " << msg; + } + + SCOPED_TRACE(msg); + ValidateEncoding(value, minimum, maximum, expected_bytes); + DecodeSeveralWays(value, expected_bytes); + + // Append some random data to the end of buffer_ and repeat. That random + // data should be ignored. + buffer_.append(Random().RandString(1 + Random().Uniform(10))); + DecodeSeveralWays(value, expected_bytes); + + // If possible, add extension bytes that don't change the value. + if (1 < expected_bytes) { + buffer_.resize(expected_bytes); + for (uint8_t total_bytes = expected_bytes + 1; total_bytes <= 6; + ++total_bytes) { + // Mark the current last byte as not being the last one. + EXPECT_EQ(0x00, 0x80 & buffer_.back()); + buffer_.back() |= 0x80; + buffer_.push_back('\0'); + DecodeSeveralWays(value, total_bytes); + } + } + } + } + + void EncodeAndDecodeValuesInRange(uint32_t start, + uint32_t range, + uint8_t prefix_length, + size_t expected_bytes) { + const uint8_t prefix_mask = (1 << prefix_length) - 1; + const uint32_t beyond = start + range; + + LOG(INFO) << "############################################################"; + LOG(INFO) << "prefix_length=" << static_cast<int>(prefix_length); + LOG(INFO) << "prefix_mask=" << std::hex << static_cast<int>(prefix_mask); + LOG(INFO) << "start=" << start << " (" << std::hex << start << ")"; + LOG(INFO) << "range=" << range << " (" << std::hex << range << ")"; + LOG(INFO) << "beyond=" << beyond << " (" << std::hex << beyond << ")"; + LOG(INFO) << "expected_bytes=" << expected_bytes; + + // Confirm the claim that beyond requires more bytes. + Encode(beyond, prefix_length); + EXPECT_EQ(expected_bytes + 1, buffer_.size()) << HexEncode(buffer_); + + std::set<uint32_t> values; + if (range < 200) { + // Select all values in the range. + for (uint32_t offset = 0; offset < range; ++offset) { + values.insert(start + offset); + } + } else { + // Select some values in this range, including the minimum and maximum + // values that require exactly |expected_bytes| extension bytes. + values.insert({start, start + 1, beyond - 2, beyond - 1}); + while (values.size() < 100) { + values.insert(start + Random().Rand32() % range); + } + } + + EncodeAndDecodeValues(values, prefix_length, expected_bytes); + } + + HpackVarintDecoder decoder_; + string buffer_; + uint8_t prefix_mask_ = 0; + uint8_t prefix_length_ = 0; +}; + +// To help me and future debuggers of varint encodings, this LOGs out the +// transition points where a new extension byte is added. +TEST_F(HpackVarintDecoderTest, Encode) { + for (int prefix_length = 4; prefix_length <= 7; ++prefix_length) { + const uint32_t a = (1 << prefix_length) - 1; + const uint32_t b = a + 128; + const uint32_t c = b + (127 << 7); + const uint32_t d = c + (127 << 14); + const uint32_t e = d + (127 << 21); + + LOG(INFO) << "############################################################"; + LOG(INFO) << "prefix_length=" << prefix_length << " a=" << a + << " b=" << b << " c=" << c; + + EXPECT_EQ(a - 1, + HpackVarintDecoder::HiValueOfExtensionBytes(0, prefix_length)); + EXPECT_EQ(b - 1, + HpackVarintDecoder::HiValueOfExtensionBytes(1, prefix_length)); + EXPECT_EQ(c - 1, + HpackVarintDecoder::HiValueOfExtensionBytes(2, prefix_length)); + EXPECT_EQ(d - 1, + HpackVarintDecoder::HiValueOfExtensionBytes(3, prefix_length)); + EXPECT_EQ(e - 1, + HpackVarintDecoder::HiValueOfExtensionBytes(4, prefix_length)); + + std::vector<uint32_t> values = { + 0, 1, // Force line break. + a - 2, a - 1, a, a + 1, a + 2, // Force line break. + b - 2, b - 1, b, b + 1, b + 2, // Force line break. + c - 2, c - 1, c, c + 1, c + 2, // Force line break. + d - 2, d - 1, d, d + 1, d + 2, // Force line break. + e - 2, e - 1, e, e + 1, e + 2 // Force line break. + }; + + for (uint32_t value : values) { + EncodeNoRandom(value, prefix_length); + string dump = HexEncode(buffer_); + LOG(INFO) << StringPrintf("%10u %0#10x ", value, value) + << HexEncode(buffer_); + } + } +} + +TEST_F(HpackVarintDecoderTest, FromSpec1337) { + DecodeBuffer b(StringPiece("\x1f\x9a\x0a")); + uint32_t prefix_length = 5; + uint32_t prefix_mask = (1 << prefix_length) - 1; + uint8_t p = b.DecodeUInt8(); + EXPECT_EQ(1u, b.Offset()); + EXPECT_EQ(DecodeStatus::kDecodeDone, decoder_.Start(p, prefix_mask, &b)); + EXPECT_EQ(3u, b.Offset()); + EXPECT_EQ(1337u, decoder_.value()); + + EncodeNoRandom(1337, prefix_length); + EXPECT_EQ(3u, buffer_.size()); + EXPECT_EQ('\x1f', buffer_[0]); + EXPECT_EQ('\x9a', buffer_[1]); + EXPECT_EQ('\x0a', buffer_[2]); +} + +// Test all the values that fit into the prefix (one less than the mask). +TEST_F(HpackVarintDecoderTest, ValidatePrefixOnly) { + for (int prefix_length = 4; prefix_length <= 7; ++prefix_length) { + const uint8_t prefix_mask = (1 << prefix_length) - 1; + EncodeAndDecodeValuesInRange(0, prefix_mask, prefix_length, 1); + } +} + +// Test all values that require exactly 1 extension byte. +TEST_F(HpackVarintDecoderTest, ValidateOneExtensionByte) { + for (int prefix_length = 4; prefix_length <= 7; ++prefix_length) { + const uint32_t start = (1 << prefix_length) - 1; + EncodeAndDecodeValuesInRange(start, 128, prefix_length, 2); + } +} + +// Test *some* values that require exactly 2 extension bytes. +TEST_F(HpackVarintDecoderTest, ValidateTwoExtensionBytes) { + for (int prefix_length = 4; prefix_length <= 7; ++prefix_length) { + const uint8_t prefix_mask = (1 << prefix_length) - 1; + const uint32_t start = prefix_mask + 128; + const uint32_t range = 127 << 7; + + EncodeAndDecodeValuesInRange(start, range, prefix_length, 3); + } +} + +// Test *some* values that require 3 extension bytes. +TEST_F(HpackVarintDecoderTest, ValidateThreeExtensionBytes) { + for (int prefix_length = 4; prefix_length <= 7; ++prefix_length) { + const uint8_t prefix_mask = (1 << prefix_length) - 1; + const uint32_t start = prefix_mask + 128 + (127 << 7); + const uint32_t range = 127 << 14; + + EncodeAndDecodeValuesInRange(start, range, prefix_length, 4); + } +} + +// Test *some* values that require 4 extension bytes. +TEST_F(HpackVarintDecoderTest, ValidateFourExtensionBytes) { + for (int prefix_length = 4; prefix_length <= 7; ++prefix_length) { + const uint8_t prefix_mask = (1 << prefix_length) - 1; + const uint32_t start = prefix_mask + 128 + (127 << 7) + (127 << 14); + const uint32_t range = 127 << 21; + + EncodeAndDecodeValuesInRange(start, range, prefix_length, 5); + } +} + +// Test *some* values that require too many extension bytes. +TEST_F(HpackVarintDecoderTest, ValueTooLarge) { + const uint32_t expected_offset = HpackVarintDecoder::MaxExtensionBytes() + 1; + for (prefix_length_ = 4; prefix_length_ <= 7; ++prefix_length_) { + prefix_mask_ = (1 << prefix_length_) - 1; + uint64_t too_large = HpackVarintDecoder::HiValueOfExtensionBytes( + HpackVarintDecoder::MaxExtensionBytes() + 3, prefix_length_); + HpackBlockBuilder bb; + bb.AppendHighBitsAndVarint(0, prefix_length_, too_large); + buffer_ = bb.buffer(); + + // The validator is called after each of the several times that the input + // DecodeBuffer is decoded, each with a different segmentation of the input. + // Validate that decoder_.value() matches the expected value. + bool validated = false; + Validator validator = + base::Bind(&HpackVarintDecoderTest::ValidatorForValueTooLarge, + base::Unretained(this), &validated, expected_offset); + + // StartDecoding, above, requires the DecodeBuffer be non-empty so that it + // can call Start with the prefix byte. + bool return_non_zero_on_first = true; + DecodeBuffer b(buffer_); + EXPECT_TRUE( + DecodeAndValidateSeveralWays(&b, return_non_zero_on_first, validator)); + EXPECT_EQ(expected_offset, b.Offset()); + EXPECT_TRUE(validated); + } +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/hpack/http2_hpack_constants.cc b/net/http2/hpack/http2_hpack_constants.cc new file mode 100644 index 0000000..bc0ee9e --- /dev/null +++ b/net/http2/hpack/http2_hpack_constants.cc
@@ -0,0 +1,33 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/http2_hpack_constants.h" + +#include <sstream> + +namespace net { + +std::string HpackEntryTypeToString(HpackEntryType v) { + switch (v) { + case HpackEntryType::kIndexedHeader: + return "kIndexedHeader"; + case HpackEntryType::kDynamicTableSizeUpdate: + return "kDynamicTableSizeUpdate"; + case HpackEntryType::kIndexedLiteralHeader: + return "kIndexedLiteralHeader"; + case HpackEntryType::kUnindexedLiteralHeader: + return "kUnindexedLiteralHeader"; + case HpackEntryType::kNeverIndexedLiteralHeader: + return "kNeverIndexedLiteralHeader"; + } + std::stringstream ss; + ss << "UnknownHpackEntryType(" << static_cast<int>(v) << ")"; + return ss.str(); +} + +std::ostream& operator<<(std::ostream& out, HpackEntryType v) { + return out << HpackEntryTypeToString(v); +} + +} // namespace net
diff --git a/net/http2/hpack/http2_hpack_constants.h b/net/http2/hpack/http2_hpack_constants.h new file mode 100644 index 0000000..b9883a0 --- /dev/null +++ b/net/http2/hpack/http2_hpack_constants.h
@@ -0,0 +1,61 @@ +// Copyright 2016 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 NET_HTTP2_HPACK_HTTP2_HPACK_CONSTANTS_H_ +#define NET_HTTP2_HPACK_HTTP2_HPACK_CONSTANTS_H_ + +// Enum HpackEntryType identifies the 5 basic types of HPACK Block Entries. +// +// See the spec for details: +// https://http2.github.io/http2-spec/compression.html#rfc.section.6 + +#include <ostream> +#include <string> + +#include "net/base/net_export.h" + +namespace net { + +enum class HpackEntryType { + // Entry is an index into the static or dynamic table. Decoding it has no + // effect on the dynamic table. + kIndexedHeader, + + // The entry contains a literal value. The name may be either a literal or a + // reference to an entry in the static or dynamic table. + // The entry is added to the dynamic table after decoding. + kIndexedLiteralHeader, + + // The entry contains a literal value. The name may be either a literal or a + // reference to an entry in the static or dynamic table. + // The entry is not added to the dynamic table after decoding, but a proxy + // may choose to insert the entry into its dynamic table when forwarding + // to another endpoint. + kUnindexedLiteralHeader, + + // The entry contains a literal value. The name may be either a literal or a + // reference to an entry in the static or dynamic table. + // The entry is not added to the dynamic table after decoding, and a proxy + // must NOT insert the entry into its dynamic table when forwarding to another + // endpoint. + kNeverIndexedLiteralHeader, + + // Entry conveys the size limit of the dynamic table of the encoder to + // the decoder. May be used to flush the table by sending a zero and then + // resetting the size back up to the maximum that the encoder will use + // (within the limits of SETTINGS_HEADER_TABLE_SIZE sent by the + // decoder to the encoder, with the default of 4096 assumed). + kDynamicTableSizeUpdate, +}; + +// Returns the name of the enum member. +NET_EXPORT_PRIVATE std::string HpackEntryTypeToString(HpackEntryType v); + +// Inserts the name of the enum member into |out|. +NET_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + HpackEntryType v); + +} // namespace net + +#endif // NET_HTTP2_HPACK_HTTP2_HPACK_CONSTANTS_H_
diff --git a/net/http2/hpack/http2_hpack_constants_test.cc b/net/http2/hpack/http2_hpack_constants_test.cc new file mode 100644 index 0000000..217b64d --- /dev/null +++ b/net/http2/hpack/http2_hpack_constants_test.cc
@@ -0,0 +1,34 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/http2_hpack_constants.h" + +#include "base/logging.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace test { +namespace { + +class HpackEntryTypeTest : public testing::Test {}; + +TEST(HpackEntryTypeTest, HpackEntryTypeToString) { + EXPECT_EQ("kIndexedHeader", + HpackEntryTypeToString(HpackEntryType::kIndexedHeader)); + EXPECT_EQ("kDynamicTableSizeUpdate", + HpackEntryTypeToString(HpackEntryType::kDynamicTableSizeUpdate)); + EXPECT_EQ("kIndexedLiteralHeader", + HpackEntryTypeToString(HpackEntryType::kIndexedLiteralHeader)); + EXPECT_EQ("kUnindexedLiteralHeader", + HpackEntryTypeToString(HpackEntryType::kUnindexedLiteralHeader)); + EXPECT_EQ("kNeverIndexedLiteralHeader", + HpackEntryTypeToString(HpackEntryType::kNeverIndexedLiteralHeader)); + EXPECT_EQ("UnknownHpackEntryType(12321)", + HpackEntryTypeToString(static_cast<HpackEntryType>(12321))); +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/hpack/huffman/http2_hpack_huffman_decoder.cc b/net/http2/hpack/huffman/http2_hpack_huffman_decoder.cc new file mode 100644 index 0000000..45c32c08 --- /dev/null +++ b/net/http2/hpack/huffman/http2_hpack_huffman_decoder.cc
@@ -0,0 +1,542 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/huffman/http2_hpack_huffman_decoder.h" + +#include <bitset> +#include <limits> + +#include "base/logging.h" + +using base::StringPiece; +using std::string; + +// Terminology: +// +// Symbol - a plain text (unencoded) character (uint8), or the End-of-String +// (EOS) symbol, 256. +// +// Code - the sequence of bits used to encode a symbol, varying in length from +// 5 bits for the most common symbols (e.g. '0', '1', and 'a'), to +// 30 bits for the least common (e.g. the EOS symbol). +// For those symbols whose codes have the same length, their code values +// are sorted such that the lower symbol value has a lower code value. +// +// Canonical - a symbol's cardinal value when sorted first by code length, and +// then by symbol value. For example, canonical 0 is for ASCII '0' +// (uint8 value 0x30), which is the first of the symbols whose code +// is 5 bits long, and the last canonical is EOS, which is the last +// of the symbols whose code is 30 bits long. + +// TODO(jamessynge): Remove use of binary literals, that is a C++ 14 feature. + +namespace net { +namespace { + +// HuffmanCode is used to store the codes associated with symbols (a pattern of +// from 5 to 30 bits). +typedef uint32_t HuffmanCode; + +// HuffmanCodeBitCount is used to store a count of bits in a code. +typedef uint16_t HuffmanCodeBitCount; + +// HuffmanCodeBitSet is used for producing a string version of a code because +// std::bitset logs nicely. +typedef std::bitset<32> HuffmanCodeBitSet; +typedef std::bitset<64> HuffmanAccumulatorBitSet; + +static constexpr HuffmanCodeBitCount kMinCodeBitCount = 5; +static constexpr HuffmanCodeBitCount kMaxCodeBitCount = 30; +static constexpr HuffmanCodeBitCount kHuffmanCodeBitCount = + std::numeric_limits<HuffmanCode>::digits; + +static_assert(std::numeric_limits<HuffmanCode>::digits >= kMaxCodeBitCount, + "HuffmanCode isn't big enough."); + +static_assert(std::numeric_limits<HuffmanAccumulator>::digits >= + kMaxCodeBitCount, + "HuffmanAccumulator isn't big enough."); + +static constexpr HuffmanAccumulatorBitCount kHuffmanAccumulatorBitCount = + std::numeric_limits<HuffmanAccumulator>::digits; +static constexpr HuffmanAccumulatorBitCount kExtraAccumulatorBitCount = + kHuffmanAccumulatorBitCount - kHuffmanCodeBitCount; + +// PrefixInfo holds info about a group of codes that are all of the same length. +struct PrefixInfo { + // Given the leading bits (32 in this case) of the encoded string, and that + // they start with a code of length |code_length|, return the corresponding + // canonical for that leading code. + uint32_t DecodeToCanonical(HuffmanCode bits) const { + // What is the position of the canonical symbol being decoded within + // the canonical symbols of |length|? + HuffmanCode ordinal_in_length = + ((bits - first_code) >> (kHuffmanCodeBitCount - code_length)); + + // Combined with |canonical| to produce the position of the canonical symbol + // being decoded within all of the canonical symbols. + return first_canonical + ordinal_in_length; + } + + const HuffmanCode first_code; // First code of this length, left justified in + // the field (i.e. the first bit of the code is + // the high-order bit). + const uint16_t code_length; // Length of the prefix code |base|. + const uint16_t first_canonical; // First canonical symbol of this length. +}; + +inline std::ostream& operator<<(std::ostream& out, const PrefixInfo& v) { + return out << "{first_code: " << HuffmanCodeBitSet(v.first_code) + << ", code_length: " << v.code_length + << ", first_canonical: " << v.first_canonical << "}"; +} + +// Given |value|, a sequence of the leading bits remaining to be decoded, +// figure out which group of canonicals (by code length) that value starts +// with. This function was generated. +PrefixInfo PrefixToInfo(HuffmanCode value) { + if (value < 0b10111000000000000000000000000000) { + if (value < 0b01010000000000000000000000000000) { + return {0b00000000000000000000000000000000, 5, 0}; + } else { + return {0b01010000000000000000000000000000, 6, 10}; + } + } else { + if (value < 0b11111110000000000000000000000000) { + if (value < 0b11111000000000000000000000000000) { + return {0b10111000000000000000000000000000, 7, 36}; + } else { + return {0b11111000000000000000000000000000, 8, 68}; + } + } else { + if (value < 0b11111111110000000000000000000000) { + if (value < 0b11111111101000000000000000000000) { + if (value < 0b11111111010000000000000000000000) { + return {0b11111110000000000000000000000000, 10, 74}; + } else { + return {0b11111111010000000000000000000000, 11, 79}; + } + } else { + return {0b11111111101000000000000000000000, 12, 82}; + } + } else { + if (value < 0b11111111111111100000000000000000) { + if (value < 0b11111111111110000000000000000000) { + if (value < 0b11111111111100000000000000000000) { + return {0b11111111110000000000000000000000, 13, 84}; + } else { + return {0b11111111111100000000000000000000, 14, 90}; + } + } else { + return {0b11111111111110000000000000000000, 15, 92}; + } + } else { + if (value < 0b11111111111111110100100000000000) { + if (value < 0b11111111111111101110000000000000) { + if (value < 0b11111111111111100110000000000000) { + return {0b11111111111111100000000000000000, 19, 95}; + } else { + return {0b11111111111111100110000000000000, 20, 98}; + } + } else { + return {0b11111111111111101110000000000000, 21, 106}; + } + } else { + if (value < 0b11111111111111111110101000000000) { + if (value < 0b11111111111111111011000000000000) { + return {0b11111111111111110100100000000000, 22, 119}; + } else { + return {0b11111111111111111011000000000000, 23, 145}; + } + } else { + if (value < 0b11111111111111111111101111000000) { + if (value < 0b11111111111111111111100000000000) { + if (value < 0b11111111111111111111011000000000) { + return {0b11111111111111111110101000000000, 24, 174}; + } else { + return {0b11111111111111111111011000000000, 25, 186}; + } + } else { + return {0b11111111111111111111100000000000, 26, 190}; + } + } else { + if (value < 0b11111111111111111111111111110000) { + if (value < 0b11111111111111111111111000100000) { + return {0b11111111111111111111101111000000, 27, 205}; + } else { + return {0b11111111111111111111111000100000, 28, 224}; + } + } else { + return {0b11111111111111111111111111110000, 30, 253}; + } + } + } + } + } + } + } + } +} + +// Mapping from canonical symbol (0 to 255) to actual symbol. +// clang-format off +constexpr unsigned char kCanonicalToSymbol[] = { + '0', '1', '2', 'a', 'c', 'e', 'i', 'o', + 's', 't', 0x20, '%', '-', '.', '/', '3', + '4', '5', '6', '7', '8', '9', '=', 'A', + '_', 'b', 'd', 'f', 'g', 'h', 'l', 'm', + 'n', 'p', 'r', 'u', ':', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'Y', 'j', 'k', 'q', 'v', + 'w', 'x', 'y', 'z', '&', '*', ',', ';', + 'X', 'Z', '!', '\"', '(', ')', '?', '\'', + '+', '|', '#', '>', 0x00, '$', '@', '[', + ']', '~', '^', '}', '<', '`', '{', '\\', + 0xc3, 0xd0, 0x80, 0x82, 0x83, 0xa2, 0xb8, 0xc2, + 0xe0, 0xe2, 0x99, 0xa1, 0xa7, 0xac, 0xb0, 0xb1, + 0xb3, 0xd1, 0xd8, 0xd9, 0xe3, 0xe5, 0xe6, 0x81, + 0x84, 0x85, 0x86, 0x88, 0x92, 0x9a, 0x9c, 0xa0, + 0xa3, 0xa4, 0xa9, 0xaa, 0xad, 0xb2, 0xb5, 0xb9, + 0xba, 0xbb, 0xbd, 0xbe, 0xc4, 0xc6, 0xe4, 0xe8, + 0xe9, 0x01, 0x87, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, + 0x8f, 0x93, 0x95, 0x96, 0x97, 0x98, 0x9b, 0x9d, + 0x9e, 0xa5, 0xa6, 0xa8, 0xae, 0xaf, 0xb4, 0xb6, + 0xb7, 0xbc, 0xbf, 0xc5, 0xe7, 0xef, 0x09, 0x8e, + 0x90, 0x91, 0x94, 0x9f, 0xab, 0xce, 0xd7, 0xe1, + 0xec, 0xed, 0xc7, 0xcf, 0xea, 0xeb, 0xc0, 0xc1, + 0xc8, 0xc9, 0xca, 0xcd, 0xd2, 0xd5, 0xda, 0xdb, + 0xee, 0xf0, 0xf2, 0xf3, 0xff, 0xcb, 0xcc, 0xd3, + 0xd4, 0xd6, 0xdd, 0xde, 0xdf, 0xf1, 0xf4, 0xf5, + 0xf6, 0xf7, 0xf8, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0b, + 0x0c, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, 0x7f, 0xdc, 0xf9, 0x0a, 0x0d, 0x16, +}; +// clang-format on + +constexpr size_t kShortCodeTableSize = 124; +struct ShortCodeInfo { + uint8_t symbol; + uint8_t length; +} kShortCodeTable[kShortCodeTableSize] = { + {0x30, 5}, // Match: 0b0000000, Symbol: 0 + {0x30, 5}, // Match: 0b0000001, Symbol: 0 + {0x30, 5}, // Match: 0b0000010, Symbol: 0 + {0x30, 5}, // Match: 0b0000011, Symbol: 0 + {0x31, 5}, // Match: 0b0000100, Symbol: 1 + {0x31, 5}, // Match: 0b0000101, Symbol: 1 + {0x31, 5}, // Match: 0b0000110, Symbol: 1 + {0x31, 5}, // Match: 0b0000111, Symbol: 1 + {0x32, 5}, // Match: 0b0001000, Symbol: 2 + {0x32, 5}, // Match: 0b0001001, Symbol: 2 + {0x32, 5}, // Match: 0b0001010, Symbol: 2 + {0x32, 5}, // Match: 0b0001011, Symbol: 2 + {0x61, 5}, // Match: 0b0001100, Symbol: a + {0x61, 5}, // Match: 0b0001101, Symbol: a + {0x61, 5}, // Match: 0b0001110, Symbol: a + {0x61, 5}, // Match: 0b0001111, Symbol: a + {0x63, 5}, // Match: 0b0010000, Symbol: c + {0x63, 5}, // Match: 0b0010001, Symbol: c + {0x63, 5}, // Match: 0b0010010, Symbol: c + {0x63, 5}, // Match: 0b0010011, Symbol: c + {0x65, 5}, // Match: 0b0010100, Symbol: e + {0x65, 5}, // Match: 0b0010101, Symbol: e + {0x65, 5}, // Match: 0b0010110, Symbol: e + {0x65, 5}, // Match: 0b0010111, Symbol: e + {0x69, 5}, // Match: 0b0011000, Symbol: i + {0x69, 5}, // Match: 0b0011001, Symbol: i + {0x69, 5}, // Match: 0b0011010, Symbol: i + {0x69, 5}, // Match: 0b0011011, Symbol: i + {0x6f, 5}, // Match: 0b0011100, Symbol: o + {0x6f, 5}, // Match: 0b0011101, Symbol: o + {0x6f, 5}, // Match: 0b0011110, Symbol: o + {0x6f, 5}, // Match: 0b0011111, Symbol: o + {0x73, 5}, // Match: 0b0100000, Symbol: s + {0x73, 5}, // Match: 0b0100001, Symbol: s + {0x73, 5}, // Match: 0b0100010, Symbol: s + {0x73, 5}, // Match: 0b0100011, Symbol: s + {0x74, 5}, // Match: 0b0100100, Symbol: t + {0x74, 5}, // Match: 0b0100101, Symbol: t + {0x74, 5}, // Match: 0b0100110, Symbol: t + {0x74, 5}, // Match: 0b0100111, Symbol: t + {0x20, 6}, // Match: 0b0101000, Symbol: (space) + {0x20, 6}, // Match: 0b0101001, Symbol: (space) + {0x25, 6}, // Match: 0b0101010, Symbol: % + {0x25, 6}, // Match: 0b0101011, Symbol: % + {0x2d, 6}, // Match: 0b0101100, Symbol: - + {0x2d, 6}, // Match: 0b0101101, Symbol: - + {0x2e, 6}, // Match: 0b0101110, Symbol: . + {0x2e, 6}, // Match: 0b0101111, Symbol: . + {0x2f, 6}, // Match: 0b0110000, Symbol: / + {0x2f, 6}, // Match: 0b0110001, Symbol: / + {0x33, 6}, // Match: 0b0110010, Symbol: 3 + {0x33, 6}, // Match: 0b0110011, Symbol: 3 + {0x34, 6}, // Match: 0b0110100, Symbol: 4 + {0x34, 6}, // Match: 0b0110101, Symbol: 4 + {0x35, 6}, // Match: 0b0110110, Symbol: 5 + {0x35, 6}, // Match: 0b0110111, Symbol: 5 + {0x36, 6}, // Match: 0b0111000, Symbol: 6 + {0x36, 6}, // Match: 0b0111001, Symbol: 6 + {0x37, 6}, // Match: 0b0111010, Symbol: 7 + {0x37, 6}, // Match: 0b0111011, Symbol: 7 + {0x38, 6}, // Match: 0b0111100, Symbol: 8 + {0x38, 6}, // Match: 0b0111101, Symbol: 8 + {0x39, 6}, // Match: 0b0111110, Symbol: 9 + {0x39, 6}, // Match: 0b0111111, Symbol: 9 + {0x3d, 6}, // Match: 0b1000000, Symbol: = + {0x3d, 6}, // Match: 0b1000001, Symbol: = + {0x41, 6}, // Match: 0b1000010, Symbol: A + {0x41, 6}, // Match: 0b1000011, Symbol: A + {0x5f, 6}, // Match: 0b1000100, Symbol: _ + {0x5f, 6}, // Match: 0b1000101, Symbol: _ + {0x62, 6}, // Match: 0b1000110, Symbol: b + {0x62, 6}, // Match: 0b1000111, Symbol: b + {0x64, 6}, // Match: 0b1001000, Symbol: d + {0x64, 6}, // Match: 0b1001001, Symbol: d + {0x66, 6}, // Match: 0b1001010, Symbol: f + {0x66, 6}, // Match: 0b1001011, Symbol: f + {0x67, 6}, // Match: 0b1001100, Symbol: g + {0x67, 6}, // Match: 0b1001101, Symbol: g + {0x68, 6}, // Match: 0b1001110, Symbol: h + {0x68, 6}, // Match: 0b1001111, Symbol: h + {0x6c, 6}, // Match: 0b1010000, Symbol: l + {0x6c, 6}, // Match: 0b1010001, Symbol: l + {0x6d, 6}, // Match: 0b1010010, Symbol: m + {0x6d, 6}, // Match: 0b1010011, Symbol: m + {0x6e, 6}, // Match: 0b1010100, Symbol: n + {0x6e, 6}, // Match: 0b1010101, Symbol: n + {0x70, 6}, // Match: 0b1010110, Symbol: p + {0x70, 6}, // Match: 0b1010111, Symbol: p + {0x72, 6}, // Match: 0b1011000, Symbol: r + {0x72, 6}, // Match: 0b1011001, Symbol: r + {0x75, 6}, // Match: 0b1011010, Symbol: u + {0x75, 6}, // Match: 0b1011011, Symbol: u + {0x3a, 7}, // Match: 0b1011100, Symbol: : + {0x42, 7}, // Match: 0b1011101, Symbol: B + {0x43, 7}, // Match: 0b1011110, Symbol: C + {0x44, 7}, // Match: 0b1011111, Symbol: D + {0x45, 7}, // Match: 0b1100000, Symbol: E + {0x46, 7}, // Match: 0b1100001, Symbol: F + {0x47, 7}, // Match: 0b1100010, Symbol: G + {0x48, 7}, // Match: 0b1100011, Symbol: H + {0x49, 7}, // Match: 0b1100100, Symbol: I + {0x4a, 7}, // Match: 0b1100101, Symbol: J + {0x4b, 7}, // Match: 0b1100110, Symbol: K + {0x4c, 7}, // Match: 0b1100111, Symbol: L + {0x4d, 7}, // Match: 0b1101000, Symbol: M + {0x4e, 7}, // Match: 0b1101001, Symbol: N + {0x4f, 7}, // Match: 0b1101010, Symbol: O + {0x50, 7}, // Match: 0b1101011, Symbol: P + {0x51, 7}, // Match: 0b1101100, Symbol: Q + {0x52, 7}, // Match: 0b1101101, Symbol: R + {0x53, 7}, // Match: 0b1101110, Symbol: S + {0x54, 7}, // Match: 0b1101111, Symbol: T + {0x55, 7}, // Match: 0b1110000, Symbol: U + {0x56, 7}, // Match: 0b1110001, Symbol: V + {0x57, 7}, // Match: 0b1110010, Symbol: W + {0x59, 7}, // Match: 0b1110011, Symbol: Y + {0x6a, 7}, // Match: 0b1110100, Symbol: j + {0x6b, 7}, // Match: 0b1110101, Symbol: k + {0x71, 7}, // Match: 0b1110110, Symbol: q + {0x76, 7}, // Match: 0b1110111, Symbol: v + {0x77, 7}, // Match: 0b1111000, Symbol: w + {0x78, 7}, // Match: 0b1111001, Symbol: x + {0x79, 7}, // Match: 0b1111010, Symbol: y + {0x7a, 7}, // Match: 0b1111011, Symbol: z +}; + +} // namespace + +HuffmanBitBuffer::HuffmanBitBuffer() { + Reset(); +} + +void HuffmanBitBuffer::Reset() { + accumulator_ = 0; + count_ = 0; +} + +size_t HuffmanBitBuffer::AppendBytes(StringPiece input) { + HuffmanAccumulatorBitCount free_cnt = free_count(); + size_t bytes_available = input.size(); + if (free_cnt < 8 || bytes_available == 0) { + return 0; + } + + // Top up |accumulator_| until there isn't room for a whole byte. + size_t bytes_used = 0; + auto ptr = reinterpret_cast<const uint8_t*>(input.data()); + do { + auto b = static_cast<HuffmanAccumulator>(*ptr++); + free_cnt -= 8; + accumulator_ |= (b << free_cnt); + ++bytes_used; + } while (free_cnt >= 8 && bytes_used < bytes_available); + count_ += (bytes_used * 8); + return bytes_used; +} + +HuffmanAccumulatorBitCount HuffmanBitBuffer::free_count() const { + return kHuffmanAccumulatorBitCount - count_; +} + +void HuffmanBitBuffer::ConsumeBits(HuffmanAccumulatorBitCount code_length) { + DCHECK_LE(code_length, count_); + accumulator_ <<= code_length; + count_ -= code_length; +} + +bool HuffmanBitBuffer::InputProperlyTerminated() const { + auto cnt = count(); + if (cnt < 8) { + if (cnt == 0) { + return true; + } + HuffmanAccumulator expected = ~(~HuffmanAccumulator() >> cnt); + // We expect all the bits below the high order |cnt| bits of accumulator_ + // to be cleared as we perform left shift operations while decoding. + DCHECK_EQ(accumulator_ & ~expected, 0u) + << "\n expected: " << HuffmanAccumulatorBitSet(expected) << "\n " + << *this; + return accumulator_ == expected; + } + return false; +} + +string HuffmanBitBuffer::DebugString() const { + std::stringstream ss; + ss << "{accumulator: " << HuffmanAccumulatorBitSet(accumulator_) + << "; count: " << count_ << "}"; + return ss.str(); +} + +HpackHuffmanDecoder::HpackHuffmanDecoder() {} + +HpackHuffmanDecoder::~HpackHuffmanDecoder() {} + +bool HpackHuffmanDecoder::Decode(StringPiece input, string* output) { + return DecodeShortCodesFirst(input, output); +} + +// "Legacy" decoder, used until cl/129771019 submitted, which added +// DecodeShortCodesFirst() as primary decoder method. +// TODO(jamessynge): Remove this once satisfied that there is no going back. +bool HpackHuffmanDecoder::DecodeWithIfTreeAndStruct(StringPiece input, + string* output) { + DVLOG(1) << "HpackHuffmanDecoder::DecodeWithIfTreeAndStruct"; + + // Fill bit_buffer_ from input. + input.remove_prefix(bit_buffer_.AppendBytes(input)); + + while (true) { + DVLOG(3) << "Enter Decode Loop, bit_buffer_: " << bit_buffer_; + + HuffmanCode code_prefix = bit_buffer_.value() >> kExtraAccumulatorBitCount; + DVLOG(3) << "code_prefix: " << HuffmanCodeBitSet(code_prefix); + + PrefixInfo prefix_info = PrefixToInfo(code_prefix); + DVLOG(3) << "prefix_info: " << prefix_info; + DCHECK_LE(kMinCodeBitCount, prefix_info.code_length); + DCHECK_LE(prefix_info.code_length, kMaxCodeBitCount); + + if (prefix_info.code_length <= bit_buffer_.count()) { + // We have enough bits for one code. + uint32_t canonical = prefix_info.DecodeToCanonical(code_prefix); + if (canonical < 256) { + // Valid code. + char c = kCanonicalToSymbol[canonical]; + output->push_back(c); + bit_buffer_.ConsumeBits(prefix_info.code_length); + continue; + } + // Encoder is not supposed to explicity encode the EOS symbol. + DLOG(ERROR) << "EOS explicitly encoded!\n " << bit_buffer_ << "\n " + << prefix_info; + return false; + } + // bit_buffer_ doesn't have enough bits in it to decode the next symbol. + // Append to it as many bytes as are available AND fit. + size_t byte_count = bit_buffer_.AppendBytes(input); + if (byte_count == 0) { + DCHECK_EQ(input.size(), 0u); + return true; + } + input.remove_prefix(byte_count); + } +} + +bool HpackHuffmanDecoder::DecodeShortCodesFirst(StringPiece input, + string* output) { + DVLOG(1) << "HpackHuffmanDecoder::DecodeShortCodesFirst"; + + // Fill bit_buffer_ from input. + input.remove_prefix(bit_buffer_.AppendBytes(input)); + + while (true) { + DVLOG(3) << "Enter Decode Loop, bit_buffer_: " << bit_buffer_; + if (bit_buffer_.count() >= 7) { + // Get high 7 bits of the bit buffer, see if that contains a complete + // code of 5, 6 or 7 bits. + uint8_t short_code = + bit_buffer_.value() >> (kHuffmanAccumulatorBitCount - 7); + DCHECK_LT(short_code, 128); + if (short_code < kShortCodeTableSize) { + ShortCodeInfo info = kShortCodeTable[short_code]; + bit_buffer_.ConsumeBits(info.length); + output->push_back(static_cast<char>(info.symbol)); + continue; + } + // The code is more than 7 bits long. Use PrefixToInfo, etc. to decode + // longer codes. + } else { + // We may have (mostly) drained bit_buffer_. If we can top it up, try + // using the table decoder above. + size_t byte_count = bit_buffer_.AppendBytes(input); + if (byte_count > 0) { + input.remove_prefix(byte_count); + continue; + } + } + + HuffmanCode code_prefix = bit_buffer_.value() >> kExtraAccumulatorBitCount; + DVLOG(3) << "code_prefix: " << HuffmanCodeBitSet(code_prefix); + + PrefixInfo prefix_info = PrefixToInfo(code_prefix); + DVLOG(3) << "prefix_info: " << prefix_info; + DCHECK_LE(kMinCodeBitCount, prefix_info.code_length); + DCHECK_LE(prefix_info.code_length, kMaxCodeBitCount); + + if (prefix_info.code_length <= bit_buffer_.count()) { + // We have enough bits for one code. + uint32_t canonical = prefix_info.DecodeToCanonical(code_prefix); + if (canonical < 256) { + // Valid code. + char c = kCanonicalToSymbol[canonical]; + output->push_back(c); + bit_buffer_.ConsumeBits(prefix_info.code_length); + continue; + } + // Encoder is not supposed to explicity encode the EOS symbol. + DLOG(ERROR) << "EOS explicitly encoded!\n " << bit_buffer_ << "\n " + << prefix_info; + return false; + } + // bit_buffer_ doesn't have enough bits in it to decode the next symbol. + // Append to it as many bytes as are available AND fit. + size_t byte_count = bit_buffer_.AppendBytes(input); + if (byte_count == 0) { + DCHECK_EQ(input.size(), 0u); + return true; + } + input.remove_prefix(byte_count); + } +} + +string HpackHuffmanDecoder::DebugString() const { + return bit_buffer_.DebugString(); +} + +} // namespace net
diff --git a/net/http2/hpack/huffman/http2_hpack_huffman_decoder.h b/net/http2/hpack/huffman/http2_hpack_huffman_decoder.h new file mode 100644 index 0000000..7b8edd10 --- /dev/null +++ b/net/http2/hpack/huffman/http2_hpack_huffman_decoder.h
@@ -0,0 +1,149 @@ +// Copyright 2016 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 NET_HTTP2_HPACK_HUFFMAN_HTTP2_HPACK_HUFFMAN_DECODER_H_ +#define NET_HTTP2_HPACK_HUFFMAN_HTTP2_HPACK_HUFFMAN_DECODER_H_ + +// HpackHuffmanDecoder is an incremental decoder of strings that have been +// encoded using the Huffman table defined in the HPACK spec. +// By incremental, we mean that the HpackHuffmanDecoder::Decode method does +// not require the entire string to be provided, and can instead decode the +// string as fragments of it become available (e.g. as HPACK block fragments +// are received for decoding by HpackEntryDecoder). + +#include <stddef.h> + +#include <iosfwd> +#include <string> + +#include "base/strings/string_piece.h" +#include "net/base/net_export.h" + +namespace net { + +// HuffmanAccumulator is used to store bits during decoding, e.g. next N bits +// that have not yet been decoded, but have been extracted from the encoded +// string). An advantage of using a uint64 for the accumulator +// is that it has room for the bits of the longest code plus the bits of a full +// byte; that means that when adding more bits to the accumulator, it can always +// be done in whole bytes. For example, if we currently have 26 bits in the +// accumulator, and need more to decode the current symbol, we can add a whole +// byte to the accumulator, and not have to do juggling with adding 6 bits (to +// reach 30), and then keep track of the last two bits we've not been able to +// add to the accumulator. +typedef uint64_t HuffmanAccumulator; +typedef size_t HuffmanAccumulatorBitCount; + +// HuffmanBitBuffer stores the leading edge of bits to be decoded. The high +// order bit of accumulator_ is the next bit to be decoded. +class NET_EXPORT_PRIVATE HuffmanBitBuffer { + public: + HuffmanBitBuffer(); + + // Prepare for decoding a new Huffman encoded string. + void Reset(); + + // Add as many whole bytes to the accumulator (accumulator_) as possible, + // returning the number of bytes added. + size_t AppendBytes(base::StringPiece input); + + // Get the bits of the accumulator. + HuffmanAccumulator value() const { return accumulator_; } + + // Number of bits of the encoded string that are in the accumulator + // (accumulator_). + HuffmanAccumulatorBitCount count() const { return count_; } + + // Are there no bits in the accumulator? + bool IsEmpty() const { return count_ == 0; } + + // Number of additional bits that can be added to the accumulator. + HuffmanAccumulatorBitCount free_count() const; + + // Consume the leading |code_length| bits of the accumulator. + void ConsumeBits(HuffmanAccumulatorBitCount code_length); + + // Are the contents valid for the end of a Huffman encoded string? The RFC + // states that EOS (end-of-string) symbol must not be explicitly encoded in + // the bit stream, but any unused bits in the final byte must be set to the + // prefix of the EOS symbol, which is all 1 bits. So there can be at most 7 + // such bits. + // Returns true if the bit buffer is empty, or contains at most 7 bits, all + // of them 1. Otherwise returns false. + bool InputProperlyTerminated() const; + + std::string DebugString() const; + + private: + HuffmanAccumulator accumulator_; + HuffmanAccumulatorBitCount count_; +}; + +inline std::ostream& operator<<(std::ostream& out, const HuffmanBitBuffer& v) { + return out << v.DebugString(); +} + +class NET_EXPORT_PRIVATE HpackHuffmanDecoder { + public: + HpackHuffmanDecoder(); + ~HpackHuffmanDecoder(); + + // Prepare for decoding a new Huffman encoded string. + void Reset() { bit_buffer_.Reset(); } + + // Decode the portion of a HPACK Huffman encoded string that is in |input|, + // appending the decoded symbols into |*output|, stopping when more bits are + // needed to determine the next symbol, which/ means that the input has been + // drained, and also that the bit_buffer_ is empty or that the bits that are + // in it are not a whole symbol. + // If |input| is the start of a string, the caller must first call Reset. + // If |input| includes the end of the encoded string, the caller must call + // InputProperlyTerminated after Decode has returned true in order to + // determine if the encoded string was properly terminated. + // Returns false if something went wrong (e.g. the encoding contains the code + // EOS symbol). Otherwise returns true, in which case input has been fully + // decoded or buffered; in particular, if the low-order bit of the final byte + // of the input is not the last bit of an encoded symbol, then bit_buffer_ + // will contain the leading bits of the code for that symbol, but not the + // final bits of that code. + // Note that output should be empty, but that it is not cleared by Decode(). + bool Decode(base::StringPiece input, std::string* output); + + // Is what remains in the bit_buffer_ valid at the end of an encoded string? + // Call after passing the the final portion of a Huffman string to Decode, + // and getting true as the result. + bool InputProperlyTerminated() const { + return bit_buffer_.InputProperlyTerminated(); + } + + bool IsEmptyForTest() const { return bit_buffer_.IsEmpty(); } + + std::string DebugString() const; + + ////////////////////////////////////////////////////////////////////////////// + // Alternate implementations of Decode: + + // As above, implemented using a tree of if statements to determine the code + // length, etc., which are returned as a tree. See PrefixToInfo. This is the + // original implementation, current as of 2016-8-8. + bool DecodeWithIfTreeAndStruct(base::StringPiece input, std::string* output); + + // Based on DecodeWithIfTreeAndStruct, but adds an optimization for the common + // case of short codes (5, 6 or 7), which make up a large fraction of the + // frequency distribution on which the HPACK table was based. + // TODO(jamessynge): Be precise about that fraction. + bool DecodeShortCodesFirst(base::StringPiece input, std::string* output); + + private: + HuffmanBitBuffer bit_buffer_; +}; + +inline std::ostream& operator<<(std::ostream& out, + const HpackHuffmanDecoder& v) { + return out << v.DebugString(); +} + +} // namespace net + +#endif // NET_HTTP2_HPACK_HUFFMAN_HTTP2_HPACK_HUFFMAN_DECODER_H_
diff --git a/net/http2/hpack/huffman/http2_hpack_huffman_decoder_test.cc b/net/http2/hpack/huffman/http2_hpack_huffman_decoder_test.cc new file mode 100644 index 0000000..3ad47351 --- /dev/null +++ b/net/http2/hpack/huffman/http2_hpack_huffman_decoder_test.cc
@@ -0,0 +1,292 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/huffman/http2_hpack_huffman_decoder.h" + +// Tests of HpackHuffmanDecoder and HuffmanBitBuffer. + +#include <iostream> +#include <string> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/macros.h" +#include "base/strings/string_piece.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/tools/failure.h" +#include "net/http2/tools/random_decoder_test.h" +#include "net/spdy/spdy_test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using base::StringPiece; +using std::string; + +namespace net { +namespace test { +namespace { + +TEST(HuffmanBitBufferTest, Reset) { + HuffmanBitBuffer bb; + EXPECT_TRUE(bb.IsEmpty()); + EXPECT_TRUE(bb.InputProperlyTerminated()); + EXPECT_EQ(bb.count(), 0u); + EXPECT_EQ(bb.free_count(), 64u); + EXPECT_EQ(bb.value(), 0u); +} + +TEST(HuffmanBitBufferTest, AppendBytesAligned) { + string s; + s.push_back('\x11'); + s.push_back('\x22'); + s.push_back('\x33'); + StringPiece sp(s); + + HuffmanBitBuffer bb; + sp.remove_prefix(bb.AppendBytes(sp)); + EXPECT_TRUE(sp.empty()); + EXPECT_FALSE(bb.IsEmpty()) << bb; + EXPECT_FALSE(bb.InputProperlyTerminated()); + EXPECT_EQ(bb.count(), 24u) << bb; + EXPECT_EQ(bb.free_count(), 40u) << bb; + EXPECT_EQ(bb.value(), HuffmanAccumulator(0x112233) << 40) << bb; + + s.clear(); + s.push_back('\x44'); + sp = s; + + sp.remove_prefix(bb.AppendBytes(sp)); + EXPECT_TRUE(sp.empty()); + EXPECT_EQ(bb.count(), 32u) << bb; + EXPECT_EQ(bb.free_count(), 32u) << bb; + EXPECT_EQ(bb.value(), HuffmanAccumulator(0x11223344) << 32) << bb; + + s.clear(); + s.push_back('\x55'); + s.push_back('\x66'); + s.push_back('\x77'); + s.push_back('\x88'); + s.push_back('\x99'); + sp = s; + + sp.remove_prefix(bb.AppendBytes(sp)); + EXPECT_EQ(sp.size(), 1u); + EXPECT_EQ('\x99', sp[0]); + EXPECT_EQ(bb.count(), 64u) << bb; + EXPECT_EQ(bb.free_count(), 0u) << bb; + EXPECT_EQ(bb.value(), HuffmanAccumulator(0x1122334455667788LL)) << bb; + + sp.remove_prefix(bb.AppendBytes(sp)); + EXPECT_EQ(sp.size(), 1u); + EXPECT_EQ('\x99', sp[0]); + EXPECT_EQ(bb.count(), 64u) << bb; + EXPECT_EQ(bb.free_count(), 0u) << bb; + EXPECT_EQ(bb.value(), HuffmanAccumulator(0x1122334455667788LL)) << bb; +} + +TEST(HuffmanBitBufferTest, ConsumeBits) { + string s; + s.push_back('\x11'); + s.push_back('\x22'); + s.push_back('\x33'); + StringPiece sp(s); + + HuffmanBitBuffer bb; + sp.remove_prefix(bb.AppendBytes(sp)); + EXPECT_TRUE(sp.empty()); + + bb.ConsumeBits(1); + EXPECT_EQ(bb.count(), 23u) << bb; + EXPECT_EQ(bb.free_count(), 41u) << bb; + EXPECT_EQ(bb.value(), HuffmanAccumulator(0x112233) << 41) << bb; + + bb.ConsumeBits(20); + EXPECT_EQ(bb.count(), 3u) << bb; + EXPECT_EQ(bb.free_count(), 61u) << bb; + EXPECT_EQ(bb.value(), HuffmanAccumulator(0x3) << 61) << bb; +} + +TEST(HuffmanBitBufferTest, AppendBytesUnaligned) { + string s; + s.push_back('\x11'); + s.push_back('\x22'); + s.push_back('\x33'); + s.push_back('\x44'); + s.push_back('\x55'); + s.push_back('\x66'); + s.push_back('\x77'); + s.push_back('\x88'); + s.push_back('\x99'); + s.push_back('\xaa'); + s.push_back('\xbb'); + s.push_back('\xcc'); + s.push_back('\xdd'); + StringPiece sp(s); + + HuffmanBitBuffer bb; + sp.remove_prefix(bb.AppendBytes(sp)); + EXPECT_EQ(sp.size(), 5u); + EXPECT_FALSE(bb.InputProperlyTerminated()); + + bb.ConsumeBits(15); + EXPECT_EQ(bb.count(), 49u) << bb; + EXPECT_EQ(bb.free_count(), 15u) << bb; + + HuffmanAccumulator expected(0x1122334455667788); + expected <<= 15; + EXPECT_EQ(bb.value(), expected); + + sp.remove_prefix(bb.AppendBytes(sp)); + EXPECT_EQ(sp.size(), 4u); + EXPECT_EQ(bb.count(), 57u) << bb; + EXPECT_EQ(bb.free_count(), 7u) << bb; + + expected |= (HuffmanAccumulator(0x99) << 7); + EXPECT_EQ(bb.value(), expected) << bb << std::hex + << "\n actual: " << bb.value() + << "\n expected: " << expected; +} + +enum class DecoderChoice { IF_TREE, SHORT_CODE }; + +class HpackHuffmanDecoderTest + : public RandomDecoderTest, + public ::testing::WithParamInterface<DecoderChoice> { + protected: + HpackHuffmanDecoderTest() { + // The decoder may return true, and its accumulator may be empty, at + // many boundaries while decoding, and yet the whole string hasn't + // been decoded. + stop_decode_on_done_ = false; + } + + DecodeStatus StartDecoding(DecodeBuffer* b) override { + input_bytes_seen_ = 0; + output_buffer_.clear(); + decoder_.Reset(); + return ResumeDecoding(b); + } + + DecodeStatus ResumeDecoding(DecodeBuffer* b) override { + input_bytes_seen_ += b->Remaining(); + StringPiece sp(b->cursor(), b->Remaining()); + if (DecodeFragment(sp)) { + b->AdvanceCursor(b->Remaining()); + // Successfully decoded (or buffered) the bytes in StringPiece. + EXPECT_LE(input_bytes_seen_, input_bytes_expected_); + // Have we reached the end of the encoded string? + if (input_bytes_expected_ == input_bytes_seen_) { + if (decoder_.InputProperlyTerminated()) { + return DecodeStatus::kDecodeDone; + } else { + return DecodeStatus::kDecodeError; + } + } + return DecodeStatus::kDecodeInProgress; + } + return DecodeStatus::kDecodeError; + } + + bool DecodeFragment(StringPiece sp) { + switch (GetParam()) { + case DecoderChoice::IF_TREE: + return decoder_.DecodeWithIfTreeAndStruct(sp, &output_buffer_); + case DecoderChoice::SHORT_CODE: + return decoder_.DecodeShortCodesFirst(sp, &output_buffer_); + } + + NOTREACHED(); + return false; + } + + AssertionResult ValidatorForHuffmanDecodeAndValidateSeveralWays( + StringPiece expected_plain) { + VERIFY_EQ(output_buffer_.size(), expected_plain.size()); + VERIFY_EQ(output_buffer_, expected_plain); + return AssertionSuccess(); + } + + AssertionResult HuffmanDecodeAndValidateSeveralWays( + StringPiece encoded, + StringPiece expected_plain) { + input_bytes_expected_ = encoded.size(); + DecodeBuffer db(encoded); + bool return_non_zero_on_first = false; + return DecodeAndValidateSeveralWays( + &db, return_non_zero_on_first, + ValidateDoneAndEmpty( + base::Bind(&HpackHuffmanDecoderTest:: + ValidatorForHuffmanDecodeAndValidateSeveralWays, + base::Unretained(this), expected_plain))); + } + + HpackHuffmanDecoder decoder_; + string output_buffer_; + size_t input_bytes_seen_; + size_t input_bytes_expected_; +}; +INSTANTIATE_TEST_CASE_P(AllDecoders, + HpackHuffmanDecoderTest, + ::testing::Values(DecoderChoice::IF_TREE, + DecoderChoice::SHORT_CODE)); + +TEST_P(HpackHuffmanDecoderTest, SpecRequestExamples) { + HpackHuffmanDecoder decoder; + string test_table[] = { + a2b_hex("f1e3c2e5f23a6ba0ab90f4ff"), + "www.example.com", + a2b_hex("a8eb10649cbf"), + "no-cache", + a2b_hex("25a849e95ba97d7f"), + "custom-key", + a2b_hex("25a849e95bb8e8b4bf"), + "custom-value", + }; + for (size_t i = 0; i != arraysize(test_table); i += 2) { + const string& huffman_encoded(test_table[i]); + const string& plain_string(test_table[i + 1]); + string buffer; + decoder.Reset(); + EXPECT_TRUE(decoder.Decode(huffman_encoded, &buffer)) << decoder; + EXPECT_TRUE(decoder.InputProperlyTerminated()) << decoder; + EXPECT_EQ(buffer, plain_string); + } +} + +TEST_P(HpackHuffmanDecoderTest, SpecResponseExamples) { + HpackHuffmanDecoder decoder; + // clang-format off + string test_table[] = { + a2b_hex("6402"), + "302", + a2b_hex("aec3771a4b"), + "private", + a2b_hex("d07abe941054d444a8200595040b8166" + "e082a62d1bff"), + "Mon, 21 Oct 2013 20:13:21 GMT", + a2b_hex("9d29ad171863c78f0b97c8e9ae82ae43" + "d3"), + "https://www.example.com", + a2b_hex("94e7821dd7f2e6c7b335dfdfcd5b3960" + "d5af27087f3672c1ab270fb5291f9587" + "316065c003ed4ee5b1063d5007"), + "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1", + }; + // clang-format on + for (size_t i = 0; i != arraysize(test_table); i += 2) { + const string& huffman_encoded(test_table[i]); + const string& plain_string(test_table[i + 1]); + string buffer; + decoder.Reset(); + EXPECT_TRUE(decoder.Decode(huffman_encoded, &buffer)) << decoder; + EXPECT_TRUE(decoder.InputProperlyTerminated()) << decoder; + EXPECT_EQ(buffer, plain_string); + } +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/hpack/tools/hpack_block_builder.cc b/net/http2/hpack/tools/hpack_block_builder.cc new file mode 100644 index 0000000..49eba0d --- /dev/null +++ b/net/http2/hpack/tools/hpack_block_builder.cc
@@ -0,0 +1,84 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/tools/hpack_block_builder.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace test { + +void HpackBlockBuilder::AppendHighBitsAndVarint(uint8_t high_bits, + uint8_t prefix_length, + uint64_t varint) { + EXPECT_LE(4, prefix_length); + EXPECT_LE(prefix_length, 7); + + // prefix_mask defines the sequence of low-order bits of the first byte + // that encode the prefix of the value. It is also the marker in those bits + // of the first byte indicating that at least one extension byte is needed. + uint8_t prefix_mask = (1 << prefix_length) - 1; + EXPECT_EQ(0, high_bits & prefix_mask); + + if (varint < prefix_mask) { + uint8_t b = high_bits | static_cast<uint8_t>(varint); + buffer_.push_back(static_cast<char>(b)); + return; + } + + // We need extension bytes. + buffer_.push_back(static_cast<char>(high_bits | prefix_mask)); + varint -= prefix_mask; + while (varint >= 128) { + uint8_t b = static_cast<uint8_t>((varint % 128) + 128); + buffer_.push_back(static_cast<char>(b)); + varint = varint / 128; + } + char c = static_cast<char>(varint); + buffer_.push_back(c); +} + +void HpackBlockBuilder::AppendEntryTypeAndVarint(HpackEntryType entry_type, + uint64_t varint) { + uint8_t high_bits; + uint8_t prefix_length; // Bits of the varint prefix in the first byte. + switch (entry_type) { + case HpackEntryType::kIndexedHeader: + high_bits = 0x80; + prefix_length = 7; + break; + case HpackEntryType::kDynamicTableSizeUpdate: + high_bits = 0x20; + prefix_length = 5; + break; + case HpackEntryType::kIndexedLiteralHeader: + high_bits = 0x40; + prefix_length = 6; + break; + case HpackEntryType::kUnindexedLiteralHeader: + high_bits = 0x00; + prefix_length = 4; + break; + case HpackEntryType::kNeverIndexedLiteralHeader: + high_bits = 0x10; + prefix_length = 4; + break; + default: + NOTREACHED(); + high_bits = 0; + prefix_length = 0; + } + AppendHighBitsAndVarint(high_bits, prefix_length, varint); +} + +void HpackBlockBuilder::AppendString(bool is_huffman_encoded, + base::StringPiece str) { + uint8_t high_bits = is_huffman_encoded ? 0x80 : 0; + uint8_t prefix_length = 7; + AppendHighBitsAndVarint(high_bits, prefix_length, str.size()); + str.AppendToString(&buffer_); +} + +} // namespace test +} // namespace net
diff --git a/net/http2/hpack/tools/hpack_block_builder.h b/net/http2/hpack/tools/hpack_block_builder.h new file mode 100644 index 0000000..06502b2a --- /dev/null +++ b/net/http2/hpack/tools/hpack_block_builder.h
@@ -0,0 +1,95 @@ +// Copyright 2016 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 NET_HTTP2_HPACK_TOOLS_HPACK_BLOCK_BUILDER_H_ +#define NET_HTTP2_HPACK_TOOLS_HPACK_BLOCK_BUILDER_H_ + +// HpackBlockBuilder builds wire-format HPACK blocks (or fragments thereof) +// from components. + +// Supports very large varints to enable tests to create HPACK blocks with +// values that the decoder should reject. For now, this is only intended for +// use in tests, and thus has EXPECT* in the code. If desired to use it in an +// encoder, it will need optimization work, especially w.r.t memory mgmt, and +// the EXPECT* will need to be removed or replaced with DCHECKs. And of course +// the support for very large varints will not be needed in production code. + +#include <stddef.h> +#include <string> + +#include "base/strings/string_piece.h" +#include "net/http2/hpack/http2_hpack_constants.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace test { + +class HpackBlockBuilder { + public: + explicit HpackBlockBuilder(base::StringPiece initial_contents) { + initial_contents.AppendToString(&buffer_); + } + HpackBlockBuilder() {} + ~HpackBlockBuilder() {} + + size_t size() const { return buffer_.size(); } + const std::string& buffer() const { return buffer_; } + + //---------------------------------------------------------------------------- + // Methods for appending a valid HPACK entry. + + void AppendIndexedHeader(uint64_t index) { + AppendEntryTypeAndVarint(HpackEntryType::kIndexedHeader, index); + } + + void AppendDynamicTableSizeUpdate(uint64_t size) { + AppendEntryTypeAndVarint(HpackEntryType::kDynamicTableSizeUpdate, size); + } + + void AppendNameIndexAndLiteralValue(HpackEntryType entry_type, + uint64_t name_index, + bool value_is_huffman_encoded, + base::StringPiece value) { + // name_index==0 would indicate that the entry includes a literal name. + // Call AppendLiteralNameAndValue in that case. + EXPECT_NE(0u, name_index); + AppendEntryTypeAndVarint(entry_type, name_index); + AppendString(value_is_huffman_encoded, value); + } + + void AppendLiteralNameAndValue(HpackEntryType entry_type, + bool name_is_huffman_encoded, + base::StringPiece name, + bool value_is_huffman_encoded, + base::StringPiece value) { + AppendEntryTypeAndVarint(entry_type, 0); + AppendString(name_is_huffman_encoded, name); + AppendString(value_is_huffman_encoded, value); + } + + //---------------------------------------------------------------------------- + // Primitive methods that are not guaranteed to write a valid HPACK entry. + + // Appends a varint, with the specified high_bits above the prefix of the + // varint. + void AppendHighBitsAndVarint(uint8_t high_bits, + uint8_t prefix_length, + uint64_t varint); + + // Append the start of an HPACK entry for the specified type, with the + // specified varint. + void AppendEntryTypeAndVarint(HpackEntryType entry_type, uint64_t varint); + + // Append a header string (i.e. a header name or value) in HPACK format. + // Does NOT perform Huffman encoding. + void AppendString(bool is_huffman_encoded, base::StringPiece str); + + private: + std::string buffer_; +}; + +} // namespace test +} // namespace net + +#endif // NET_HTTP2_HPACK_TOOLS_HPACK_BLOCK_BUILDER_H_
diff --git a/net/http2/hpack/tools/hpack_block_builder_test.cc b/net/http2/hpack/tools/hpack_block_builder_test.cc new file mode 100644 index 0000000..e591049 --- /dev/null +++ b/net/http2/hpack/tools/hpack_block_builder_test.cc
@@ -0,0 +1,169 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/tools/hpack_block_builder.h" + +#include "net/spdy/spdy_test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::StringPiece; + +namespace net { +namespace test { +namespace { +const bool kUncompressed = false; +const bool kCompressed = true; + +// TODO(jamessynge): Once static table code is checked in, switch to using +// constants from there. +const uint32_t kStaticTableMethodGET = 2; +const uint32_t kStaticTablePathSlash = 4; +const uint32_t kStaticTableSchemeHttp = 6; + +// Tests of encoding per the RFC. See: +// http://httpwg.org/specs/rfc7541.html#header.field.representation.examples +// The expected values have been copied from the RFC. +TEST(HpackBlockBuilderTest, ExamplesFromSpecC2) { + { + HpackBlockBuilder b; + b.AppendLiteralNameAndValue(HpackEntryType::kIndexedLiteralHeader, + kUncompressed, "custom-key", kUncompressed, + "custom-header"); + EXPECT_EQ(26u, b.size()); + + const char kExpected[] = + "\x40" // == Literal indexed == + "\x0a" // Name length (10) + "custom-key" // Name + "\x0d" // Value length (13) + "custom-header"; // Value + EXPECT_EQ(kExpected, b.buffer()); + } + { + HpackBlockBuilder b; + b.AppendNameIndexAndLiteralValue(HpackEntryType::kUnindexedLiteralHeader, 4, + kUncompressed, "/sample/path"); + EXPECT_EQ(14u, b.size()); + + const char kExpected[] = + "\x04" // == Literal unindexed, name index 0x04 == + "\x0c" // Value length (12) + "/sample/path"; // Value + EXPECT_EQ(kExpected, b.buffer()); + } + { + HpackBlockBuilder b; + b.AppendLiteralNameAndValue(HpackEntryType::kNeverIndexedLiteralHeader, + kUncompressed, "password", kUncompressed, + "secret"); + EXPECT_EQ(17u, b.size()); + + const char kExpected[] = + "\x10" // == Literal never indexed == + "\x08" // Name length (8) + "password" // Name + "\x06" // Value length (6) + "secret"; // Value + EXPECT_EQ(kExpected, b.buffer()); + } + { + HpackBlockBuilder b; + b.AppendIndexedHeader(2); + EXPECT_EQ(1u, b.size()); + + const char kExpected[] = "\x82"; // == Indexed (2) == + EXPECT_EQ(kExpected, b.buffer()); + } +} + +// Tests of encoding per the RFC. See: +// http://httpwg.org/specs/rfc7541.html#request.examples.without.huffman.coding +TEST(HpackBlockBuilderTest, ExamplesFromSpecC3) { + { + // Header block to encode: + // :method: GET + // :scheme: http + // :path: / + // :authority: www.example.com + HpackBlockBuilder b; + b.AppendIndexedHeader(2); // :method: GET + b.AppendIndexedHeader(6); // :scheme: http + b.AppendIndexedHeader(4); // :path: / + b.AppendNameIndexAndLiteralValue(HpackEntryType::kIndexedLiteralHeader, 1, + kUncompressed, "www.example.com"); + EXPECT_EQ(20u, b.size()); + + // Hex dump of encoded data (copied from RFC): + // 0x0000: 8286 8441 0f77 7777 2e65 7861 6d70 6c65 ...A.www.example + // 0x0010: 2e63 6f6d .com + + const std::string expected = + a2b_hex("828684410f7777772e6578616d706c652e636f6d"); + EXPECT_EQ(expected, b.buffer()); + } +} + +// Tests of encoding per the RFC. See: +// http://httpwg.org/specs/rfc7541.html#request.examples.with.huffman.coding +TEST(HpackBlockBuilderTest, ExamplesFromSpecC4) { + { + // Header block to encode: + // :method: GET + // :scheme: http + // :path: / + // :authority: www.example.com (Huffman encoded) + HpackBlockBuilder b; + b.AppendIndexedHeader(kStaticTableMethodGET); + b.AppendIndexedHeader(kStaticTableSchemeHttp); + b.AppendIndexedHeader(kStaticTablePathSlash); + const char kHuffmanWwwExampleCom[] = {0xf1u, 0xe3u, 0xc2u, 0xe5u, + 0xf2u, 0x3au, 0x6bu, 0xa0u, + 0xabu, 0x90u, 0xf4u, 0xffu}; + b.AppendNameIndexAndLiteralValue( + HpackEntryType::kIndexedLiteralHeader, 1, kCompressed, + StringPiece(kHuffmanWwwExampleCom, sizeof kHuffmanWwwExampleCom)); + EXPECT_EQ(17u, b.size()); + + // Hex dump of encoded data (copied from RFC): + // 0x0000: 8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4 ...A......:k.... + // 0x0010: ff . + + const std::string expected = a2b_hex("828684418cf1e3c2e5f23a6ba0ab90f4ff"); + EXPECT_EQ(expected, b.buffer()); + } +} + +TEST(HpackBlockBuilderTest, DynamicTableSizeUpdate) { + { + HpackBlockBuilder b; + b.AppendDynamicTableSizeUpdate(0); + EXPECT_EQ(1u, b.size()); + + const char kData[] = {0x20}; + StringPiece expected(kData, sizeof kData); + EXPECT_EQ(expected, b.buffer()); + } + { + HpackBlockBuilder b; + b.AppendDynamicTableSizeUpdate(4096); // The default size. + EXPECT_EQ(3u, b.size()); + + const char kData[] = {0x3f, 0xe1u, 0x1f}; + StringPiece expected(kData, sizeof kData); + EXPECT_EQ(expected, b.buffer()); + } + { + HpackBlockBuilder b; + b.AppendDynamicTableSizeUpdate(1000000000000); // A very large value. + EXPECT_EQ(7u, b.size()); + + const char kData[] = {0x3fu, 0xe1u, 0x9fu, 0x94u, 0xa5u, 0x8du, 0x1du}; + StringPiece expected(kData, sizeof kData); + EXPECT_EQ(expected, b.buffer()); + } +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/hpack/tools/hpack_example.cc b/net/http2/hpack/tools/hpack_example.cc new file mode 100644 index 0000000..72fd581 --- /dev/null +++ b/net/http2/hpack/tools/hpack_example.cc
@@ -0,0 +1,61 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/hpack/tools/hpack_example.h" + +#include <ctype.h> + +#include "base/logging.h" +#include "net/spdy/spdy_test_utils.h" + +using base::StringPiece; +using std::string; + +namespace net { +namespace test { +namespace { + +void HpackExampleToStringOrDie(StringPiece example, string* output) { + while (!example.empty()) { + const char c0 = example[0]; + if (isxdigit(c0)) { + CHECK_GT(example.size(), 1u) << "Truncated hex byte?"; + const char c1 = example[1]; + CHECK(isxdigit(c1)) << "Found half a byte?"; + *output += a2b_hex(example.substr(0, 2).as_string().c_str()); + example.remove_prefix(2); + continue; + } + if (isspace(c0)) { + example.remove_prefix(1); + continue; + } + if (example.starts_with("|")) { + // Start of a comment. Skip to end of line or of input. + auto pos = example.find('\n'); + if (pos == StringPiece::npos) { + // End of input. + break; + } + example.remove_prefix(pos + 1); + continue; + } + CHECK(false) << "Can't parse byte " << static_cast<int>(c0) << " (0x" + << std::hex << c0 << ")" + << "\nExample: " << example; + } + CHECK_LT(0u, output->size()) << "Example is empty."; + return; +} + +} // namespace + +string HpackExampleToStringOrDie(StringPiece example) { + string output; + HpackExampleToStringOrDie(example, &output); + return output; +} + +} // namespace test +} // namespace net
diff --git a/net/http2/hpack/tools/hpack_example.h b/net/http2/hpack/tools/hpack_example.h new file mode 100644 index 0000000..1567cbf --- /dev/null +++ b/net/http2/hpack/tools/hpack_example.h
@@ -0,0 +1,32 @@ +// Copyright 2016 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 NET_HTTP2_HPACK_TOOLS_HPACK_EXAMPLE_H_ +#define NET_HTTP2_HPACK_TOOLS_HPACK_EXAMPLE_H_ + +// Parses HPACK examples in the format seen in the HPACK specification, +// RFC 7541. For example: +// +// 10 | == Literal never indexed == +// 08 | Literal name (len = 8) +// 7061 7373 776f 7264 | password +// 06 | Literal value (len = 6) +// 7365 6372 6574 | secret +// | -> password: secret +// +// (excluding the leading "//"). + +#include <string> + +#include "base/strings/string_piece.h" + +namespace net { +namespace test { + +std::string HpackExampleToStringOrDie(base::StringPiece example); + +} // namespace test +} // namespace net + +#endif // NET_HTTP2_HPACK_TOOLS_HPACK_EXAMPLE_H_
diff --git a/net/http2/http2_constants.cc b/net/http2/http2_constants.cc new file mode 100644 index 0000000..ca28f15e --- /dev/null +++ b/net/http2/http2_constants.cc
@@ -0,0 +1,161 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/http2_constants.h" + +#include <ios> +#include <sstream> + +#include "base/logging.h" +#include "base/strings/string_piece.h" +#include "base/strings/stringprintf.h" + +using base::StringPrintf; +using std::string; + +namespace net { + +string Http2FrameTypeToString(Http2FrameType v) { + switch (v) { + case Http2FrameType::DATA: + return "DATA"; + case Http2FrameType::HEADERS: + return "HEADERS"; + case Http2FrameType::PRIORITY: + return "PRIORITY"; + case Http2FrameType::RST_STREAM: + return "RST_STREAM"; + case Http2FrameType::SETTINGS: + return "SETTINGS"; + case Http2FrameType::PUSH_PROMISE: + return "PUSH_PROMISE"; + case Http2FrameType::PING: + return "PING"; + case Http2FrameType::GOAWAY: + return "GOAWAY"; + case Http2FrameType::WINDOW_UPDATE: + return "WINDOW_UPDATE"; + case Http2FrameType::CONTINUATION: + return "CONTINUATION"; + case Http2FrameType::ALTSVC: + return "ALTSVC"; + } + std::stringstream ss; + ss << "UnknownFrameType(" << static_cast<int>(v) << ")"; + return ss.str(); +} +string Http2FrameTypeToString(uint8_t v) { + return Http2FrameTypeToString(static_cast<Http2FrameType>(v)); +} + +string Http2FrameFlagsToString(Http2FrameType type, uint8_t flags) { + string s; + // Closure to append flag name |v| to the string |s|, and to clear + // |bit| from |flags|. + auto append_and_clear = [&s, &flags](base::StringPiece v, uint8_t bit) { + if (!s.empty()) { + s.push_back('|'); + } + v.AppendToString(&s); + flags ^= bit; + }; + if (flags & 0x01) { + if (type == Http2FrameType::DATA || type == Http2FrameType::HEADERS) { + append_and_clear("END_STREAM", Http2FrameFlag::FLAG_END_STREAM); + } else if (type == Http2FrameType::SETTINGS || + type == Http2FrameType::PING) { + append_and_clear("ACK", Http2FrameFlag::FLAG_ACK); + } + } + if (flags & 0x04) { + if (type == Http2FrameType::HEADERS || + type == Http2FrameType::PUSH_PROMISE || + type == Http2FrameType::CONTINUATION) { + append_and_clear("END_HEADERS", Http2FrameFlag::FLAG_END_HEADERS); + } + } + if (flags & 0x08) { + if (type == Http2FrameType::DATA || type == Http2FrameType::HEADERS || + type == Http2FrameType::PUSH_PROMISE) { + append_and_clear("PADDED", Http2FrameFlag::FLAG_PADDED); + } + } + if (flags & 0x20) { + if (type == Http2FrameType::HEADERS) { + append_and_clear("PRIORITY", Http2FrameFlag::FLAG_PRIORITY); + } + } + if (flags != 0) { + append_and_clear(StringPrintf("0x%02x", flags), flags); + } + DCHECK_EQ(0, flags); + return s; +} +string Http2FrameFlagsToString(uint8_t type, uint8_t flags) { + return Http2FrameFlagsToString(static_cast<Http2FrameType>(type), flags); +} + +string Http2ErrorCodeToString(uint32_t v) { + switch (v) { + case 0x0: + return "NO_ERROR"; + case 0x1: + return "PROTOCOL_ERROR"; + case 0x2: + return "INTERNAL_ERROR"; + case 0x3: + return "FLOW_CONTROL_ERROR"; + case 0x4: + return "SETTINGS_TIMEOUT"; + case 0x5: + return "STREAM_CLOSED"; + case 0x6: + return "FRAME_SIZE_ERROR"; + case 0x7: + return "REFUSED_STREAM"; + case 0x8: + return "CANCEL"; + case 0x9: + return "COMPRESSION_ERROR"; + case 0xa: + return "CONNECT_ERROR"; + case 0xb: + return "ENHANCE_YOUR_CALM"; + case 0xc: + return "INADEQUATE_SECURITY"; + case 0xd: + return "HTTP_1_1_REQUIRED"; + } + std::stringstream ss; + ss << "UnknownErrorCode(0x" << std::hex << v << ")"; + return ss.str(); +} +string Http2ErrorCodeToString(Http2ErrorCode v) { + return Http2ErrorCodeToString(static_cast<uint32_t>(v)); +} + +string Http2SettingsParameterToString(uint32_t v) { + switch (v) { + case 0x1: + return "HEADER_TABLE_SIZE"; + case 0x2: + return "ENABLE_PUSH"; + case 0x3: + return "MAX_CONCURRENT_STREAMS"; + case 0x4: + return "INITIAL_WINDOW_SIZE"; + case 0x5: + return "MAX_FRAME_SIZE"; + case 0x6: + return "MAX_HEADER_LIST_SIZE"; + } + std::stringstream ss; + ss << "UnknownSettingsParameter(0x" << std::hex << v << ")"; + return ss.str(); +} +string Http2SettingsParameterToString(Http2SettingsParameter v) { + return Http2SettingsParameterToString(static_cast<uint32_t>(v)); +} + +} // namespace net
diff --git a/net/http2/http2_constants.h b/net/http2/http2_constants.h new file mode 100644 index 0000000..61c63079 --- /dev/null +++ b/net/http2/http2_constants.h
@@ -0,0 +1,265 @@ +// Copyright 2016 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 NET_HTTP2_HTTP2_CONSTANTS_H_ +#define NET_HTTP2_HTTP2_CONSTANTS_H_ + +// Constants from the HTTP/2 spec, RFC 7540, and associated helper functions. + +#include <stdint.h> + +#include <iosfwd> +#include <ostream> +#include <string> + +#include "net/base/net_export.h" + +namespace net { + +// TODO(jamessynge): create http2_simple_types for types similar to +// SpdyStreamId, but not for structures like Http2FrameHeader. Then will be +// able to move these stream id functions there. +constexpr uint32_t UInt31Mask() { + return 0x7fffffff; +} +constexpr uint32_t StreamIdMask() { + return UInt31Mask(); +} + +// The value used to identify types of frames. Upper case to match the RFC. +// The comments indicate which flags are valid for that frame type. +// ALTSVC is defined in http://httpwg.org/http-extensions/alt-svc.html +// (not yet final standard as of March 2016, but close). +enum class Http2FrameType : uint8_t { + DATA = 0, // END_STREAM | PADDED + HEADERS = 1, // END_STREAM | END_HEADERS | PADDED | PRIORITY + PRIORITY = 2, // + RST_STREAM = 3, // + SETTINGS = 4, // ACK + PUSH_PROMISE = 5, // END_HEADERS | PADDED + PING = 6, // ACK + GOAWAY = 7, // + WINDOW_UPDATE = 8, // + CONTINUATION = 9, // END_HEADERS + ALTSVC = 10, // +}; + +// Is the frame type known/supported? +inline bool IsSupportedHttp2FrameType(uint32_t v) { + return v <= static_cast<uint32_t>(Http2FrameType::ALTSVC); +} +inline bool IsSupportedHttp2FrameType(Http2FrameType v) { + return IsSupportedHttp2FrameType(static_cast<uint32_t>(v)); +} + +// The return type is 'string' so that they can generate a unique string for +// each unsupported value. Since these are just used for debugging/error +// messages, that isn't a cost to we need to worry about. +// The same applies to the functions later in this file. +NET_EXPORT_PRIVATE std::string Http2FrameTypeToString(Http2FrameType v); +NET_EXPORT_PRIVATE std::string Http2FrameTypeToString(uint8_t v); +NET_EXPORT_PRIVATE inline std::ostream& operator<<(std::ostream& out, + Http2FrameType v) { + return out << Http2FrameTypeToString(v); +} + +// Flags that appear in supported frame types. These are treated as bit masks. +// The comments indicate for which frame types the flag is valid. +// TODO(bnc): Remove FLAG_ prefix once enum SpdyFrameType is removed +// (both enums have a PRIORITY member). +enum Http2FrameFlag { + FLAG_END_STREAM = 0x01, // DATA, HEADERS + FLAG_ACK = 0x01, // SETTINGS, PING + FLAG_END_HEADERS = 0x04, // HEADERS, PUSH_PROMISE, CONTINUATION + FLAG_PADDED = 0x08, // DATA, HEADERS, PUSH_PROMISE + FLAG_PRIORITY = 0x20, // HEADERS +}; + +// Formats zero or more flags for the specified type of frame. Returns an +// empty string if flags==0. +NET_EXPORT_PRIVATE std::string Http2FrameFlagsToString(Http2FrameType type, + uint8_t flags); +NET_EXPORT_PRIVATE std::string Http2FrameFlagsToString(uint8_t type, + uint8_t flags); + +// Error codes for GOAWAY and RST_STREAM frames. +enum class Http2ErrorCode : uint32_t { + // The associated condition is not a result of an error. For example, a GOAWAY + // might include this code to indicate graceful shutdown of a connection. + HTTP2_NO_ERROR = 0x0, + + // The endpoint detected an unspecific protocol error. This error is for use + // when a more specific error code is not available. + PROTOCOL_ERROR = 0x1, + + // The endpoint encountered an unexpected internal error. + INTERNAL_ERROR = 0x2, + + // The endpoint detected that its peer violated the flow-control protocol. + FLOW_CONTROL_ERROR = 0x3, + + // The endpoint sent a SETTINGS frame but did not receive a response in a + // timely manner. See Section 6.5.3 ("Settings Synchronization"). + SETTINGS_TIMEOUT = 0x4, + + // The endpoint received a frame after a stream was half-closed. + STREAM_CLOSED = 0x5, + + // The endpoint received a frame with an invalid size. + FRAME_SIZE_ERROR = 0x6, + + // The endpoint refused the stream prior to performing any application + // processing (see Section 8.1.4 for details). + REFUSED_STREAM = 0x7, + + // Used by the endpoint to indicate that the stream is no longer needed. + CANCEL = 0x8, + + // The endpoint is unable to maintain the header compression context + // for the connection. + COMPRESSION_ERROR = 0x9, + + // The connection established in response to a CONNECT request (Section 8.3) + // was reset or abnormally closed. + CONNECT_ERROR = 0xa, + + // The endpoint detected that its peer is exhibiting a behavior that might + // be generating excessive load. + ENHANCE_YOUR_CALM = 0xb, + + // The underlying transport has properties that do not meet minimum + // security requirements (see Section 9.2). + INADEQUATE_SECURITY = 0xc, + + // The endpoint requires that HTTP/1.1 be used instead of HTTP/2. + HTTP_1_1_REQUIRED = 0xd, +}; + +// Is the error code supported? (So far that means it is in RFC 7540.) +inline bool IsSupportedHttp2ErrorCode(uint32_t v) { + return v <= static_cast<uint32_t>(Http2ErrorCode::HTTP_1_1_REQUIRED); +} +inline bool IsSupportedHttp2ErrorCode(Http2ErrorCode v) { + return IsSupportedHttp2ErrorCode(static_cast<uint32_t>(v)); +} + +// Format the specified error code. +NET_EXPORT_PRIVATE std::string Http2ErrorCodeToString(uint32_t v); +NET_EXPORT_PRIVATE std::string Http2ErrorCodeToString(Http2ErrorCode v); +NET_EXPORT_PRIVATE inline std::ostream& operator<<(std::ostream& out, + Http2ErrorCode v) { + return out << Http2ErrorCodeToString(v); +} + +// Supported parameters in SETTINGS frames; so far just those in RFC 7540. +enum class Http2SettingsParameter : uint16_t { + // Allows the sender to inform the remote endpoint of the maximum size of the + // header compression table used to decode header blocks, in octets. The + // encoder can select any size equal to or less than this value by using + // signaling specific to the header compression format inside a header block + // (see [COMPRESSION]). The initial value is 4,096 octets. + HEADER_TABLE_SIZE = 0x1, + + // This setting can be used to disable server push (Section 8.2). An endpoint + // MUST NOT send a PUSH_PROMISE frame if it receives this parameter set to a + // value of 0. An endpoint that has both set this parameter to 0 and had it + // acknowledged MUST treat the receipt of a PUSH_PROMISE frame as a connection + // error (Section 5.4.1) of type PROTOCOL_ERROR. + // + // The initial value is 1, which indicates that server push is permitted. Any + // value other than 0 or 1 MUST be treated as a connection error (Section + // 5.4.1) of type PROTOCOL_ERROR. + ENABLE_PUSH = 0x2, + + // Indicates the maximum number of concurrent streams that the sender will + // allow. This limit is directional: it applies to the number of streams that + // the sender permits the receiver to create. Initially, there is no limit to + // this value. It is recommended that this value be no smaller than 100, so as + // to not unnecessarily limit parallelism. + // + // A value of 0 for MAX_CONCURRENT_STREAMS SHOULD NOT be treated as + // special by endpoints. A zero value does prevent the creation of new + // streams; however, this can also happen for any limit that is exhausted with + // active streams. Servers SHOULD only set a zero value for short durations; + // if a server does not wish to accept requests, closing the connection is + // more appropriate. + MAX_CONCURRENT_STREAMS = 0x3, + + // Indicates the sender's initial window size (in octets) for stream-level + // flow control. The initial value is 2^16-1 (65,535) octets. + // + // This setting affects the window size of all streams (see Section 6.9.2). + // + // Values above the maximum flow-control window size of 2^31-1 MUST be treated + // as a connection error (Section 5.4.1) of type FLOW_CONTROL_ERROR. + INITIAL_WINDOW_SIZE = 0x4, + + // Indicates the size of the largest frame payload that the sender is willing + // to receive, in octets. + // + // The initial value is 2^14 (16,384) octets. The value advertised by an + // endpoint MUST be between this initial value and the maximum allowed frame + // size (2^24-1 or 16,777,215 octets), inclusive. Values outside this range + // MUST be treated as a connection error (Section 5.4.1) of type + // PROTOCOL_ERROR. + MAX_FRAME_SIZE = 0x5, + + // This advisory setting informs a peer of the maximum size of header list + // that the sender is prepared to accept, in octets. The value is based on the + // uncompressed size of header fields, including the length of the name and + // value in octets plus an overhead of 32 octets for each header field. + // + // For any given request, a lower limit than what is advertised MAY be + // enforced. The initial value of this setting is unlimited. + MAX_HEADER_LIST_SIZE = 0x6, +}; + +// Is the settings parameter supported (so far that means it is in RFC 7540)? +inline bool IsSupportedHttp2SettingsParameter(uint32_t v) { + return 0 < v && + v <= static_cast<uint32_t>( + Http2SettingsParameter::MAX_HEADER_LIST_SIZE); +} +inline bool IsSupportedHttp2SettingsParameter(Http2SettingsParameter v) { + return IsSupportedHttp2SettingsParameter(static_cast<uint32_t>(v)); +} + +// Format the specified settings parameter. +NET_EXPORT_PRIVATE std::string Http2SettingsParameterToString(uint32_t v); +NET_EXPORT_PRIVATE std::string Http2SettingsParameterToString( + Http2SettingsParameter v); +inline std::ostream& operator<<(std::ostream& out, Http2SettingsParameter v) { + return out << Http2SettingsParameterToString(v); +} + +// Information about the initial, minimum and maximum value of settings (not +// applicable to all settings parameters). +class Http2SettingsInfo { + public: + // Default value for HEADER_TABLE_SIZE. + static constexpr uint32_t DefaultHeaderTableSize() { return 4096; } + + // Default value for ENABLE_PUSH. + static constexpr bool DefaultEnablePush() { return true; } + + // Default value for INITIAL_WINDOW_SIZE. + static constexpr uint32_t DefaultInitialWindowSize() { return 65535; } + + // Maximum value for INITIAL_WINDOW_SIZE, and for the connection flow control + // window, and for each stream flow control window. + static constexpr uint32_t MaximumWindowSize() { return UInt31Mask(); } + + // Default value for MAX_FRAME_SIZE. + static constexpr uint32_t DefaultMaxFrameSize() { return 16384; } + + // Minimum value for MAX_FRAME_SIZE. + static constexpr uint32_t MinimumMaxFrameSize() { return 16384; } + + // Maximum value for MAX_FRAME_SIZE. + static constexpr uint32_t MaximumMaxFrameSize() { return (1 << 24) - 1; } +}; + +} // namespace net + +#endif // NET_HTTP2_HTTP2_CONSTANTS_H_
diff --git a/net/http2/http2_constants_test.cc b/net/http2/http2_constants_test.cc new file mode 100644 index 0000000..f7f24f7 --- /dev/null +++ b/net/http2/http2_constants_test.cc
@@ -0,0 +1,272 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/http2_constants.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace test { +namespace { + +class Http2ConstantsTest : public testing::Test {}; + +TEST(Http2ConstantsTest, Http2FrameType) { + EXPECT_EQ(Http2FrameType::DATA, static_cast<Http2FrameType>(0)); + EXPECT_EQ(Http2FrameType::HEADERS, static_cast<Http2FrameType>(1)); + EXPECT_EQ(Http2FrameType::PRIORITY, static_cast<Http2FrameType>(2)); + EXPECT_EQ(Http2FrameType::RST_STREAM, static_cast<Http2FrameType>(3)); + EXPECT_EQ(Http2FrameType::SETTINGS, static_cast<Http2FrameType>(4)); + EXPECT_EQ(Http2FrameType::PUSH_PROMISE, static_cast<Http2FrameType>(5)); + EXPECT_EQ(Http2FrameType::PING, static_cast<Http2FrameType>(6)); + EXPECT_EQ(Http2FrameType::GOAWAY, static_cast<Http2FrameType>(7)); + EXPECT_EQ(Http2FrameType::WINDOW_UPDATE, static_cast<Http2FrameType>(8)); + EXPECT_EQ(Http2FrameType::CONTINUATION, static_cast<Http2FrameType>(9)); + EXPECT_EQ(Http2FrameType::ALTSVC, static_cast<Http2FrameType>(10)); +} + +TEST(Http2ConstantsTest, Http2FrameTypeToString) { + EXPECT_EQ("DATA", Http2FrameTypeToString(Http2FrameType::DATA)); + EXPECT_EQ("HEADERS", Http2FrameTypeToString(Http2FrameType::HEADERS)); + EXPECT_EQ("PRIORITY", Http2FrameTypeToString(Http2FrameType::PRIORITY)); + EXPECT_EQ("RST_STREAM", Http2FrameTypeToString(Http2FrameType::RST_STREAM)); + EXPECT_EQ("SETTINGS", Http2FrameTypeToString(Http2FrameType::SETTINGS)); + EXPECT_EQ("PUSH_PROMISE", + Http2FrameTypeToString(Http2FrameType::PUSH_PROMISE)); + EXPECT_EQ("PING", Http2FrameTypeToString(Http2FrameType::PING)); + EXPECT_EQ("GOAWAY", Http2FrameTypeToString(Http2FrameType::GOAWAY)); + EXPECT_EQ("WINDOW_UPDATE", + Http2FrameTypeToString(Http2FrameType::WINDOW_UPDATE)); + EXPECT_EQ("CONTINUATION", + Http2FrameTypeToString(Http2FrameType::CONTINUATION)); + EXPECT_EQ("ALTSVC", Http2FrameTypeToString(Http2FrameType::ALTSVC)); + + EXPECT_EQ("DATA", Http2FrameTypeToString(0)); + EXPECT_EQ("HEADERS", Http2FrameTypeToString(1)); + EXPECT_EQ("PRIORITY", Http2FrameTypeToString(2)); + EXPECT_EQ("RST_STREAM", Http2FrameTypeToString(3)); + EXPECT_EQ("SETTINGS", Http2FrameTypeToString(4)); + EXPECT_EQ("PUSH_PROMISE", Http2FrameTypeToString(5)); + EXPECT_EQ("PING", Http2FrameTypeToString(6)); + EXPECT_EQ("GOAWAY", Http2FrameTypeToString(7)); + EXPECT_EQ("WINDOW_UPDATE", Http2FrameTypeToString(8)); + EXPECT_EQ("CONTINUATION", Http2FrameTypeToString(9)); + EXPECT_EQ("ALTSVC", Http2FrameTypeToString(10)); + + EXPECT_EQ("UnknownFrameType(99)", Http2FrameTypeToString(99)); +} + +TEST(Http2ConstantsTest, Http2FrameFlag) { + EXPECT_EQ(Http2FrameFlag::FLAG_END_STREAM, static_cast<Http2FrameFlag>(0x01)); + EXPECT_EQ(Http2FrameFlag::FLAG_ACK, static_cast<Http2FrameFlag>(0x01)); + EXPECT_EQ(Http2FrameFlag::FLAG_END_HEADERS, + static_cast<Http2FrameFlag>(0x04)); + EXPECT_EQ(Http2FrameFlag::FLAG_PADDED, static_cast<Http2FrameFlag>(0x08)); + EXPECT_EQ(Http2FrameFlag::FLAG_PRIORITY, static_cast<Http2FrameFlag>(0x20)); + + EXPECT_EQ(Http2FrameFlag::FLAG_END_STREAM, 0x01); + EXPECT_EQ(Http2FrameFlag::FLAG_ACK, 0x01); + EXPECT_EQ(Http2FrameFlag::FLAG_END_HEADERS, 0x04); + EXPECT_EQ(Http2FrameFlag::FLAG_PADDED, 0x08); + EXPECT_EQ(Http2FrameFlag::FLAG_PRIORITY, 0x20); +} + +TEST(Http2ConstantsTest, Http2FrameFlagsToString) { + // Single flags... + + // 0b00000001 + EXPECT_EQ("END_STREAM", + Http2FrameFlagsToString(Http2FrameType::DATA, + Http2FrameFlag::FLAG_END_STREAM)); + EXPECT_EQ("END_STREAM", + Http2FrameFlagsToString(Http2FrameType::HEADERS, 0x01)); + EXPECT_EQ("ACK", Http2FrameFlagsToString(Http2FrameType::SETTINGS, + Http2FrameFlag::FLAG_ACK)); + EXPECT_EQ("ACK", Http2FrameFlagsToString(Http2FrameType::PING, 0x01)); + + // 0b00000010 + EXPECT_EQ("0x02", Http2FrameFlagsToString(0xff, 0x02)); + + // 0b00000100 + EXPECT_EQ("END_HEADERS", + Http2FrameFlagsToString(Http2FrameType::HEADERS, + Http2FrameFlag::FLAG_END_HEADERS)); + EXPECT_EQ("END_HEADERS", + Http2FrameFlagsToString(Http2FrameType::PUSH_PROMISE, 0x04)); + EXPECT_EQ("END_HEADERS", Http2FrameFlagsToString(0x09, 0x04)); + EXPECT_EQ("0x04", Http2FrameFlagsToString(0xff, 0x04)); + + // 0b00001000 + EXPECT_EQ("PADDED", Http2FrameFlagsToString(Http2FrameType::DATA, + Http2FrameFlag::FLAG_PADDED)); + EXPECT_EQ("PADDED", Http2FrameFlagsToString(Http2FrameType::HEADERS, 0x08)); + EXPECT_EQ("PADDED", Http2FrameFlagsToString(0x05, 0x08)); + EXPECT_EQ("0x08", Http2FrameFlagsToString(0xff, Http2FrameFlag::FLAG_PADDED)); + + // 0b00010000 + EXPECT_EQ("0x10", Http2FrameFlagsToString(Http2FrameType::SETTINGS, 0x10)); + + // 0b00100000 + EXPECT_EQ("PRIORITY", Http2FrameFlagsToString(Http2FrameType::HEADERS, 0x20)); + EXPECT_EQ("0x20", + Http2FrameFlagsToString(Http2FrameType::PUSH_PROMISE, 0x20)); + + // 0b01000000 + EXPECT_EQ("0x40", Http2FrameFlagsToString(0xff, 0x40)); + + // 0b10000000 + EXPECT_EQ("0x80", Http2FrameFlagsToString(0xff, 0x80)); + + // Combined flags... + + EXPECT_EQ("END_STREAM|PADDED|0xf6", + Http2FrameFlagsToString(Http2FrameType::DATA, 0xff)); + EXPECT_EQ("END_STREAM|END_HEADERS|PADDED|PRIORITY|0xd2", + Http2FrameFlagsToString(Http2FrameType::HEADERS, 0xff)); + EXPECT_EQ("0xff", Http2FrameFlagsToString(Http2FrameType::PRIORITY, 0xff)); + EXPECT_EQ("0xff", Http2FrameFlagsToString(Http2FrameType::RST_STREAM, 0xff)); + EXPECT_EQ("ACK|0xfe", + Http2FrameFlagsToString(Http2FrameType::SETTINGS, 0xff)); + EXPECT_EQ("END_HEADERS|PADDED|0xf3", + Http2FrameFlagsToString(Http2FrameType::PUSH_PROMISE, 0xff)); + EXPECT_EQ("ACK|0xfe", Http2FrameFlagsToString(Http2FrameType::PING, 0xff)); + EXPECT_EQ("0xff", Http2FrameFlagsToString(Http2FrameType::GOAWAY, 0xff)); + EXPECT_EQ("0xff", + Http2FrameFlagsToString(Http2FrameType::WINDOW_UPDATE, 0xff)); + EXPECT_EQ("END_HEADERS|0xfb", + Http2FrameFlagsToString(Http2FrameType::CONTINUATION, 0xff)); + EXPECT_EQ("0xff", Http2FrameFlagsToString(Http2FrameType::ALTSVC, 0xff)); + EXPECT_EQ("0xff", Http2FrameFlagsToString(0xff, 0xff)); +} + +TEST(Http2ConstantsTest, Http2ErrorCode) { + EXPECT_EQ(Http2ErrorCode::HTTP2_NO_ERROR, static_cast<Http2ErrorCode>(0x0)); + EXPECT_EQ(Http2ErrorCode::PROTOCOL_ERROR, static_cast<Http2ErrorCode>(0x1)); + EXPECT_EQ(Http2ErrorCode::INTERNAL_ERROR, static_cast<Http2ErrorCode>(0x2)); + EXPECT_EQ(Http2ErrorCode::FLOW_CONTROL_ERROR, + static_cast<Http2ErrorCode>(0x3)); + EXPECT_EQ(Http2ErrorCode::SETTINGS_TIMEOUT, static_cast<Http2ErrorCode>(0x4)); + EXPECT_EQ(Http2ErrorCode::STREAM_CLOSED, static_cast<Http2ErrorCode>(0x5)); + EXPECT_EQ(Http2ErrorCode::FRAME_SIZE_ERROR, static_cast<Http2ErrorCode>(0x6)); + EXPECT_EQ(Http2ErrorCode::REFUSED_STREAM, static_cast<Http2ErrorCode>(0x7)); + EXPECT_EQ(Http2ErrorCode::CANCEL, static_cast<Http2ErrorCode>(0x8)); + EXPECT_EQ(Http2ErrorCode::COMPRESSION_ERROR, + static_cast<Http2ErrorCode>(0x9)); + EXPECT_EQ(Http2ErrorCode::CONNECT_ERROR, static_cast<Http2ErrorCode>(0xa)); + EXPECT_EQ(Http2ErrorCode::ENHANCE_YOUR_CALM, + static_cast<Http2ErrorCode>(0xb)); + EXPECT_EQ(Http2ErrorCode::INADEQUATE_SECURITY, + static_cast<Http2ErrorCode>(0xc)); + EXPECT_EQ(Http2ErrorCode::HTTP_1_1_REQUIRED, + static_cast<Http2ErrorCode>(0xd)); +} + +TEST(Http2ConstantsTest, Http2ErrorCodeToString) { + EXPECT_EQ("NO_ERROR", Http2ErrorCodeToString(Http2ErrorCode::HTTP2_NO_ERROR)); + EXPECT_EQ("NO_ERROR", Http2ErrorCodeToString(0x0)); + EXPECT_EQ("PROTOCOL_ERROR", + Http2ErrorCodeToString(Http2ErrorCode::PROTOCOL_ERROR)); + EXPECT_EQ("PROTOCOL_ERROR", Http2ErrorCodeToString(0x1)); + EXPECT_EQ("INTERNAL_ERROR", + Http2ErrorCodeToString(Http2ErrorCode::INTERNAL_ERROR)); + EXPECT_EQ("INTERNAL_ERROR", Http2ErrorCodeToString(0x2)); + EXPECT_EQ("FLOW_CONTROL_ERROR", + Http2ErrorCodeToString(Http2ErrorCode::FLOW_CONTROL_ERROR)); + EXPECT_EQ("FLOW_CONTROL_ERROR", Http2ErrorCodeToString(0x3)); + EXPECT_EQ("SETTINGS_TIMEOUT", + Http2ErrorCodeToString(Http2ErrorCode::SETTINGS_TIMEOUT)); + EXPECT_EQ("SETTINGS_TIMEOUT", Http2ErrorCodeToString(0x4)); + EXPECT_EQ("STREAM_CLOSED", + Http2ErrorCodeToString(Http2ErrorCode::STREAM_CLOSED)); + EXPECT_EQ("STREAM_CLOSED", Http2ErrorCodeToString(0x5)); + EXPECT_EQ("FRAME_SIZE_ERROR", + Http2ErrorCodeToString(Http2ErrorCode::FRAME_SIZE_ERROR)); + EXPECT_EQ("FRAME_SIZE_ERROR", Http2ErrorCodeToString(0x6)); + EXPECT_EQ("REFUSED_STREAM", + Http2ErrorCodeToString(Http2ErrorCode::REFUSED_STREAM)); + EXPECT_EQ("REFUSED_STREAM", Http2ErrorCodeToString(0x7)); + EXPECT_EQ("CANCEL", Http2ErrorCodeToString(Http2ErrorCode::CANCEL)); + EXPECT_EQ("CANCEL", Http2ErrorCodeToString(0x8)); + EXPECT_EQ("COMPRESSION_ERROR", + Http2ErrorCodeToString(Http2ErrorCode::COMPRESSION_ERROR)); + EXPECT_EQ("COMPRESSION_ERROR", Http2ErrorCodeToString(0x9)); + EXPECT_EQ("CONNECT_ERROR", + Http2ErrorCodeToString(Http2ErrorCode::CONNECT_ERROR)); + EXPECT_EQ("CONNECT_ERROR", Http2ErrorCodeToString(0xa)); + EXPECT_EQ("ENHANCE_YOUR_CALM", + Http2ErrorCodeToString(Http2ErrorCode::ENHANCE_YOUR_CALM)); + EXPECT_EQ("ENHANCE_YOUR_CALM", Http2ErrorCodeToString(0xb)); + EXPECT_EQ("INADEQUATE_SECURITY", + Http2ErrorCodeToString(Http2ErrorCode::INADEQUATE_SECURITY)); + EXPECT_EQ("INADEQUATE_SECURITY", Http2ErrorCodeToString(0xc)); + EXPECT_EQ("HTTP_1_1_REQUIRED", + Http2ErrorCodeToString(Http2ErrorCode::HTTP_1_1_REQUIRED)); + EXPECT_EQ("HTTP_1_1_REQUIRED", Http2ErrorCodeToString(0xd)); + + EXPECT_EQ("UnknownErrorCode(0x123)", Http2ErrorCodeToString(0x123)); +} + +TEST(Http2ConstantsTest, Http2SettingsParameter) { + EXPECT_EQ(Http2SettingsParameter::HEADER_TABLE_SIZE, + static_cast<Http2SettingsParameter>(0x1)); + EXPECT_EQ(Http2SettingsParameter::ENABLE_PUSH, + static_cast<Http2SettingsParameter>(0x2)); + EXPECT_EQ(Http2SettingsParameter::MAX_CONCURRENT_STREAMS, + static_cast<Http2SettingsParameter>(0x3)); + EXPECT_EQ(Http2SettingsParameter::INITIAL_WINDOW_SIZE, + static_cast<Http2SettingsParameter>(0x4)); + EXPECT_EQ(Http2SettingsParameter::MAX_FRAME_SIZE, + static_cast<Http2SettingsParameter>(0x5)); + EXPECT_EQ(Http2SettingsParameter::MAX_HEADER_LIST_SIZE, + static_cast<Http2SettingsParameter>(0x6)); + + EXPECT_TRUE(IsSupportedHttp2SettingsParameter( + Http2SettingsParameter::HEADER_TABLE_SIZE)); + EXPECT_TRUE( + IsSupportedHttp2SettingsParameter(Http2SettingsParameter::ENABLE_PUSH)); + EXPECT_TRUE(IsSupportedHttp2SettingsParameter( + Http2SettingsParameter::MAX_CONCURRENT_STREAMS)); + EXPECT_TRUE(IsSupportedHttp2SettingsParameter( + Http2SettingsParameter::INITIAL_WINDOW_SIZE)); + EXPECT_TRUE(IsSupportedHttp2SettingsParameter( + Http2SettingsParameter::MAX_FRAME_SIZE)); + EXPECT_TRUE(IsSupportedHttp2SettingsParameter( + Http2SettingsParameter::MAX_HEADER_LIST_SIZE)); + + EXPECT_FALSE(IsSupportedHttp2SettingsParameter( + static_cast<Http2SettingsParameter>(0))); + EXPECT_FALSE(IsSupportedHttp2SettingsParameter( + static_cast<Http2SettingsParameter>(7))); +} + +TEST(Http2ConstantsTest, Http2SettingsParameterToString) { + EXPECT_EQ("HEADER_TABLE_SIZE", + Http2SettingsParameterToString( + Http2SettingsParameter::HEADER_TABLE_SIZE)); + EXPECT_EQ("HEADER_TABLE_SIZE", Http2SettingsParameterToString(0x1)); + EXPECT_EQ("ENABLE_PUSH", Http2SettingsParameterToString( + Http2SettingsParameter::ENABLE_PUSH)); + EXPECT_EQ("ENABLE_PUSH", Http2SettingsParameterToString(0x2)); + EXPECT_EQ("MAX_CONCURRENT_STREAMS", + Http2SettingsParameterToString( + Http2SettingsParameter::MAX_CONCURRENT_STREAMS)); + EXPECT_EQ("MAX_CONCURRENT_STREAMS", Http2SettingsParameterToString(0x3)); + EXPECT_EQ("INITIAL_WINDOW_SIZE", + Http2SettingsParameterToString( + Http2SettingsParameter::INITIAL_WINDOW_SIZE)); + EXPECT_EQ("INITIAL_WINDOW_SIZE", Http2SettingsParameterToString(0x4)); + EXPECT_EQ("MAX_FRAME_SIZE", Http2SettingsParameterToString( + Http2SettingsParameter::MAX_FRAME_SIZE)); + EXPECT_EQ("MAX_FRAME_SIZE", Http2SettingsParameterToString(0x5)); + EXPECT_EQ("MAX_HEADER_LIST_SIZE", + Http2SettingsParameterToString( + Http2SettingsParameter::MAX_HEADER_LIST_SIZE)); + EXPECT_EQ("MAX_HEADER_LIST_SIZE", Http2SettingsParameterToString(0x6)); + + EXPECT_EQ("UnknownSettingsParameter(0x123)", + Http2SettingsParameterToString(0x123)); +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/http2_constants_test_util.cc b/net/http2/http2_constants_test_util.cc new file mode 100644 index 0000000..2f4be40 --- /dev/null +++ b/net/http2/http2_constants_test_util.cc
@@ -0,0 +1,142 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/http2_constants_test_util.h" + +namespace net { +namespace test { + +std::vector<Http2FrameType> AllHttp2FrameTypes() { + // clang-format off + return { + Http2FrameType::DATA, + Http2FrameType::HEADERS, + Http2FrameType::PRIORITY, + Http2FrameType::RST_STREAM, + Http2FrameType::SETTINGS, + Http2FrameType::PUSH_PROMISE, + Http2FrameType::PING, + Http2FrameType::GOAWAY, + Http2FrameType::WINDOW_UPDATE, + Http2FrameType::CONTINUATION, + Http2FrameType::ALTSVC, + }; + // clang-format on +} + +std::vector<Http2FrameFlag> AllHttp2FrameFlagsForFrameType( + Http2FrameType type) { + // clang-format off + switch (type) { + case Http2FrameType::DATA: + return { + Http2FrameFlag::FLAG_END_STREAM, + Http2FrameFlag::FLAG_PADDED, + }; + case Http2FrameType::HEADERS: + return { + Http2FrameFlag::FLAG_END_STREAM, + Http2FrameFlag::FLAG_END_HEADERS, + Http2FrameFlag::FLAG_PADDED, + Http2FrameFlag::FLAG_PRIORITY, + }; + case Http2FrameType::SETTINGS: + return { + Http2FrameFlag::FLAG_ACK, + }; + case Http2FrameType::PUSH_PROMISE: + return { + Http2FrameFlag::FLAG_END_HEADERS, + Http2FrameFlag::FLAG_PADDED, + }; + case Http2FrameType::PING: + return { + Http2FrameFlag::FLAG_ACK, + }; + case Http2FrameType::CONTINUATION: + return { + Http2FrameFlag::FLAG_END_HEADERS, + }; + default: + return std::vector<Http2FrameFlag>{}; + } + // clang-format on +} + +std::vector<Http2ErrorCode> AllHttp2ErrorCodes() { + // clang-format off + return { + Http2ErrorCode::HTTP2_NO_ERROR, + Http2ErrorCode::PROTOCOL_ERROR, + Http2ErrorCode::INTERNAL_ERROR, + Http2ErrorCode::FLOW_CONTROL_ERROR, + Http2ErrorCode::SETTINGS_TIMEOUT, + Http2ErrorCode::STREAM_CLOSED, + Http2ErrorCode::FRAME_SIZE_ERROR, + Http2ErrorCode::REFUSED_STREAM, + Http2ErrorCode::CANCEL, + Http2ErrorCode::COMPRESSION_ERROR, + Http2ErrorCode::CONNECT_ERROR, + Http2ErrorCode::ENHANCE_YOUR_CALM, + Http2ErrorCode::INADEQUATE_SECURITY, + Http2ErrorCode::HTTP_1_1_REQUIRED, + }; + // clang-format on +} + +std::vector<Http2SettingsParameter> AllHttp2SettingsParameters() { + // clang-format off + return { + Http2SettingsParameter::HEADER_TABLE_SIZE, + Http2SettingsParameter::ENABLE_PUSH, + Http2SettingsParameter::MAX_CONCURRENT_STREAMS, + Http2SettingsParameter::INITIAL_WINDOW_SIZE, + Http2SettingsParameter::MAX_FRAME_SIZE, + Http2SettingsParameter::MAX_HEADER_LIST_SIZE, + }; + // clang-format on +} + +// Returns a mask of flags supported for the specified frame type. Returns +// zero for unknown frame types. +uint8_t KnownFlagsMaskForFrameType(Http2FrameType type) { + switch (type) { + case Http2FrameType::DATA: + return Http2FrameFlag::FLAG_END_STREAM | Http2FrameFlag::FLAG_PADDED; + case Http2FrameType::HEADERS: + return Http2FrameFlag::FLAG_END_STREAM | + Http2FrameFlag::FLAG_END_HEADERS | Http2FrameFlag::FLAG_PADDED | + Http2FrameFlag::FLAG_PRIORITY; + case Http2FrameType::PRIORITY: + return 0x00; + case Http2FrameType::RST_STREAM: + return 0x00; + case Http2FrameType::SETTINGS: + return Http2FrameFlag::FLAG_ACK; + case Http2FrameType::PUSH_PROMISE: + return Http2FrameFlag::FLAG_END_HEADERS | Http2FrameFlag::FLAG_PADDED; + case Http2FrameType::PING: + return Http2FrameFlag::FLAG_ACK; + case Http2FrameType::GOAWAY: + return 0x00; + case Http2FrameType::WINDOW_UPDATE: + return 0x00; + case Http2FrameType::CONTINUATION: + return Http2FrameFlag::FLAG_END_HEADERS; + case Http2FrameType::ALTSVC: + return 0x00; + default: + return 0x00; + } +} + +uint8_t InvalidFlagMaskForFrameType(Http2FrameType type) { + if (IsSupportedHttp2FrameType(type)) { + return ~KnownFlagsMaskForFrameType(type); + } + return 0x00; +} + +} // namespace test +} // namespace net
diff --git a/net/http2/http2_constants_test_util.h b/net/http2/http2_constants_test_util.h new file mode 100644 index 0000000..6d20cec --- /dev/null +++ b/net/http2/http2_constants_test_util.h
@@ -0,0 +1,40 @@ +// Copyright 2016 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 NET_HTTP2_HTTP2_CONSTANTS_TEST_UTIL_H_ +#define NET_HTTP2_HTTP2_CONSTANTS_TEST_UTIL_H_ + +#include <vector> + +#include "net/http2/http2_constants.h" + +namespace net { +namespace test { + +// Returns a vector of all supported frame types. +std::vector<Http2FrameType> AllHttp2FrameTypes(); + +// Returns a vector of all supported frame flags for the specified +// frame type. Empty if the type is unknown. +std::vector<Http2FrameFlag> AllHttp2FrameFlagsForFrameType(Http2FrameType type); + +// Returns a vector of all supported RST_STREAM and GOAWAY error codes. +std::vector<Http2ErrorCode> AllHttp2ErrorCodes(); + +// Returns a vector of all supported parameters in SETTINGS frames. +std::vector<Http2SettingsParameter> AllHttp2SettingsParameters(); + +// Returns a mask of flags supported for the specified frame type. Returns +// zero for unknown frame types. +uint8_t KnownFlagsMaskForFrameType(Http2FrameType type); + +// Returns a mask of flag bits known to be invalid for the frame type. +// For unknown frame types, the mask is zero; i.e., we don't know that any +// are invalid. +uint8_t InvalidFlagMaskForFrameType(Http2FrameType type); + +} // namespace test +} // namespace net + +#endif // NET_HTTP2_HTTP2_CONSTANTS_TEST_UTIL_H_
diff --git a/net/http2/http2_structures.cc b/net/http2/http2_structures.cc new file mode 100644 index 0000000..83360430 --- /dev/null +++ b/net/http2/http2_structures.cc
@@ -0,0 +1,138 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/http2_structures.h" + +#include <cstring> // For std::memcmp +#include <sstream> +#include <string> + +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" + +using std::string; +using base::StringPiece; + +namespace net { + +// Http2FrameHeader: + +bool Http2FrameHeader::IsProbableHttpResponse() const { + return (payload_length == 0x485454 && // "HTT" + static_cast<char>(type) == 'P' && // "P" + flags == '/'); // "/" +} + +string Http2FrameHeader::ToString() const { + std::stringstream ss; + ss << "length=" << payload_length << ", type=" << Http2FrameTypeToString(type) + << ", flags=" << FlagsToString() << ", stream=" << stream_id; + return ss.str(); +} + +string Http2FrameHeader::FlagsToString() const { + return Http2FrameFlagsToString(type, flags); +} + +bool operator==(const Http2FrameHeader& a, const Http2FrameHeader& b) { + return a.payload_length == b.payload_length && a.stream_id == b.stream_id && + a.type == b.type && a.flags == b.flags; +} + +std::ostream& operator<<(std::ostream& out, const Http2FrameHeader& v) { + return out << v.ToString(); +} + +// Http2PriorityFields: + +bool operator==(const Http2PriorityFields& a, const Http2PriorityFields& b) { + return a.stream_dependency == b.stream_dependency && a.weight == b.weight; +} + +std::string Http2PriorityFields::ToString() const { + std::stringstream ss; + ss << "E=" << (is_exclusive ? "true" : "false") + << ", stream=" << stream_dependency + << ", weight=" << static_cast<uint32_t>(weight); + return ss.str(); +} + +std::ostream& operator<<(std::ostream& out, const Http2PriorityFields& v) { + return out << v.ToString(); +} + +// Http2RstStreamFields: + +bool operator==(const Http2RstStreamFields& a, const Http2RstStreamFields& b) { + return a.error_code == b.error_code; +} + +std::ostream& operator<<(std::ostream& out, const Http2RstStreamFields& v) { + return out << "error_code=" << v.error_code; +} + +// Http2SettingFields: + +bool operator==(const Http2SettingFields& a, const Http2SettingFields& b) { + return a.parameter == b.parameter && a.value == b.value; +} +std::ostream& operator<<(std::ostream& out, const Http2SettingFields& v) { + return out << "parameter=" << v.parameter << ", value=" << v.value; +} + +// Http2PushPromiseFields: + +bool operator==(const Http2PushPromiseFields& a, + const Http2PushPromiseFields& b) { + return a.promised_stream_id == b.promised_stream_id; +} + +std::ostream& operator<<(std::ostream& out, const Http2PushPromiseFields& v) { + return out << "promised_stream_id=" << v.promised_stream_id; +} + +// Http2PingFields: + +bool operator==(const Http2PingFields& a, const Http2PingFields& b) { + static_assert((sizeof a.opaque_data) == Http2PingFields::EncodedSize(), + "Why not the same size?"); + return 0 == std::memcmp(a.opaque_data, b.opaque_data, sizeof a.opaque_data); +} + +std::ostream& operator<<(std::ostream& out, const Http2PingFields& v) { + string s = base::HexEncode(v.opaque_data, sizeof v.opaque_data); + base::CollapseWhitespaceASCII(s, /*trim_sequences_with_line_breaks=*/false); + return out << "opaque_data=[" << s << "]"; +} + +// Http2GoAwayFields: + +bool operator==(const Http2GoAwayFields& a, const Http2GoAwayFields& b) { + return a.last_stream_id == b.last_stream_id && a.error_code == b.error_code; +} +std::ostream& operator<<(std::ostream& out, const Http2GoAwayFields& v) { + return out << "last_stream_id=" << v.last_stream_id + << ", error_code=" << v.error_code; +} + +// Http2WindowUpdateFields: + +bool operator==(const Http2WindowUpdateFields& a, + const Http2WindowUpdateFields& b) { + return a.window_size_increment == b.window_size_increment; +} +std::ostream& operator<<(std::ostream& out, const Http2WindowUpdateFields& v) { + return out << "window_size_increment=" << v.window_size_increment; +} + +// Http2AltSvcFields: + +bool operator==(const Http2AltSvcFields& a, const Http2AltSvcFields& b) { + return a.origin_length == b.origin_length; +} +std::ostream& operator<<(std::ostream& out, const Http2AltSvcFields& v) { + return out << "origin_length=" << v.origin_length; +} + +} // namespace net
diff --git a/net/http2/http2_structures.h b/net/http2/http2_structures.h new file mode 100644 index 0000000..95328e1 --- /dev/null +++ b/net/http2/http2_structures.h
@@ -0,0 +1,326 @@ +// Copyright 2016 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 NET_HTTP2_HTTP2_STRUCTURES_H_ +#define NET_HTTP2_HTTP2_STRUCTURES_H_ + +// Defines structs for various fixed sized structures in HTTP/2. +// +// Those structs with multiple fields have constructors that take arguments in +// the same order as their encoding (which may be different from their order +// in the struct). For single field structs, use aggregate initialization if +// desired, e.g.: +// +// Http2RstStreamFields var{Http2ErrorCode::ENHANCE_YOUR_CALM}; +// or: +// SomeFunc(Http2RstStreamFields{Http2ErrorCode::ENHANCE_YOUR_CALM}); +// +// Each struct includes a static method EncodedSize which returns the number +// of bytes of the encoding. +// +// With the exception of Http2FrameHeader, all the types are named +// Http2<X>Fields, where X is the title-case form of the frame which always +// includes the fields; the "always" is to cover the case of the PRIORITY frame; +// its fields optionally appear in the HEADERS frame, but the struct is called +// Http2PriorityFields. + +#include <stddef.h> +#include <stdint.h> + +#include <ostream> +#include <string> + +#include "base/logging.h" +#include "net/base/net_export.h" +#include "net/http2/http2_constants.h" + +namespace net { + +struct NET_EXPORT_PRIVATE Http2FrameHeader { + Http2FrameHeader() {} + Http2FrameHeader(uint32_t payload_length, + Http2FrameType type, + uint8_t flags, + uint32_t stream_id) + : payload_length(payload_length), + stream_id(stream_id), + type(type), + flags(static_cast<Http2FrameFlag>(flags)) { + DCHECK_LT(payload_length, static_cast<uint32_t>(1 << 24)) + << "Payload Length is only a 24 bit field\n" + << ToString(); + } + + static constexpr size_t EncodedSize() { return 9; } + + // Keep the current value of those flags that are in + // valid_flags, and clear all the others. + void RetainFlags(uint8_t valid_flags) { + flags = static_cast<Http2FrameFlag>(flags & valid_flags); + } + + // Returns true if any of the flags in flag_mask are set, + // otherwise false. + bool HasAnyFlags(uint8_t flag_mask) const { return 0 != (flags & flag_mask); } + + // Is the END_STREAM flag set? + bool IsEndStream() const { + DCHECK(type == Http2FrameType::DATA || type == Http2FrameType::HEADERS) + << ToString(); + return (flags & Http2FrameFlag::FLAG_END_STREAM) != 0; + } + + // Is the ACK flag set? + bool IsAck() const { + DCHECK(type == Http2FrameType::SETTINGS || type == Http2FrameType::PING) + << ToString(); + return (flags & Http2FrameFlag::FLAG_ACK) != 0; + } + + // Is the END_HEADERS flag set? + bool IsEndHeaders() const { + DCHECK(type == Http2FrameType::HEADERS || + type == Http2FrameType::PUSH_PROMISE || + type == Http2FrameType::CONTINUATION) + << ToString(); + return (flags & Http2FrameFlag::FLAG_END_HEADERS) != 0; + } + + // Is the PADDED flag set? + bool IsPadded() const { + DCHECK(type == Http2FrameType::DATA || type == Http2FrameType::HEADERS || + type == Http2FrameType::PUSH_PROMISE) + << ToString(); + return (flags & Http2FrameFlag::FLAG_PADDED) != 0; + } + + // Is the PRIORITY flag set? + bool HasPriority() const { + DCHECK_EQ(type, Http2FrameType::HEADERS) << ToString(); + return (flags & Http2FrameFlag::FLAG_PRIORITY) != 0; + } + + // Does the encoding of this header start with "HTTP/", indicating that it + // might be from a non-HTTP/2 server. + bool IsProbableHttpResponse() const; + + // Produce strings useful for debugging/logging messages. + std::string ToString() const; + std::string FlagsToString() const; + + // 24 bit length of the payload after the header, including any padding. + // First field in encoding. + uint32_t payload_length; // 24 bits + + // 31 bit stream id, with high bit (32nd bit) reserved (must be zero), + // and is cleared during decoding. + // Fourth field in encoding. + uint32_t stream_id; + + // Type of the frame. + // Second field in encoding. + Http2FrameType type; + + // Flag bits, with interpretations that depend upon the frame type. + // Flag bits not used by the frame type are cleared. + // Third field in encoding. + Http2FrameFlag flags; +}; + +NET_EXPORT_PRIVATE bool operator==(const Http2FrameHeader& a, + const Http2FrameHeader& b); +NET_EXPORT_PRIVATE inline bool operator!=(const Http2FrameHeader& a, + const Http2FrameHeader& b) { + return !(a == b); +} +NET_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2FrameHeader& v); + +// Http2PriorityFields: + +struct NET_EXPORT_PRIVATE Http2PriorityFields { + Http2PriorityFields() {} + Http2PriorityFields(uint32_t stream_dependency, + uint32_t weight, + bool is_exclusive) + : stream_dependency(stream_dependency), + weight(weight), + is_exclusive(is_exclusive) { + // Can't have the high-bit set in the stream id because we need to use + // that for the EXCLUSIVE flag bit. + DCHECK_EQ(stream_dependency, stream_dependency & StreamIdMask()) + << "Stream Dependency is only a 31-bit field.\n" + << ToString(); + DCHECK_LE(1u, weight) << "Weight is too small."; + DCHECK_LE(weight, 256u) << "Weight is too large."; + } + static constexpr size_t EncodedSize() { return 5; } + + // Produce strings useful for debugging/logging messages. + std::string ToString() const; + + // A 31-bit stream identifier for the stream that this stream depends on. + uint32_t stream_dependency; + + // Weight (1 to 256) is encoded as a byte in the range 0 to 255, so we + // add one when decoding, and store it in a field larger than a byte. + uint32_t weight; + + // A single-bit flag indicating that the stream dependency is exclusive; + // extracted from high bit of stream dependency field during decoding. + bool is_exclusive; +}; + +NET_EXPORT_PRIVATE bool operator==(const Http2PriorityFields& a, + const Http2PriorityFields& b); +NET_EXPORT_PRIVATE inline bool operator!=(const Http2PriorityFields& a, + const Http2PriorityFields& b) { + return !(a == b); +} +NET_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2PriorityFields& v); + +// Http2RstStreamFields: + +struct Http2RstStreamFields { + static constexpr size_t EncodedSize() { return 4; } + bool IsSupportedErrorCode() const { + return IsSupportedHttp2ErrorCode(error_code); + } + + Http2ErrorCode error_code; +}; + +NET_EXPORT_PRIVATE bool operator==(const Http2RstStreamFields& a, + const Http2RstStreamFields& b); +NET_EXPORT_PRIVATE inline bool operator!=(const Http2RstStreamFields& a, + const Http2RstStreamFields& b) { + return !(a == b); +} +NET_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2RstStreamFields& v); + +// Http2SettingFields: + +struct Http2SettingFields { + Http2SettingFields() {} + Http2SettingFields(Http2SettingsParameter parameter, uint32_t value) + : parameter(parameter), value(value) {} + static constexpr size_t EncodedSize() { return 6; } + bool IsSupportedParameter() const { + return IsSupportedHttp2SettingsParameter(parameter); + } + + Http2SettingsParameter parameter; + uint32_t value; +}; + +NET_EXPORT_PRIVATE bool operator==(const Http2SettingFields& a, + const Http2SettingFields& b); +NET_EXPORT_PRIVATE inline bool operator!=(const Http2SettingFields& a, + const Http2SettingFields& b) { + return !(a == b); +} +NET_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2SettingFields& v); + +// Http2PushPromiseFields: + +struct Http2PushPromiseFields { + static constexpr size_t EncodedSize() { return 4; } + + uint32_t promised_stream_id; +}; + +NET_EXPORT_PRIVATE bool operator==(const Http2PushPromiseFields& a, + const Http2PushPromiseFields& b); +NET_EXPORT_PRIVATE inline bool operator!=(const Http2PushPromiseFields& a, + const Http2PushPromiseFields& b) { + return !(a == b); +} +NET_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2PushPromiseFields& v); + +// Http2PingFields: + +struct Http2PingFields { + static constexpr size_t EncodedSize() { return 8; } + + // TODO(jamessynge): Rename opaque_data to opaque_bytes. + uint8_t opaque_data[8]; +}; + +NET_EXPORT_PRIVATE bool operator==(const Http2PingFields& a, + const Http2PingFields& b); +NET_EXPORT_PRIVATE inline bool operator!=(const Http2PingFields& a, + const Http2PingFields& b) { + return !(a == b); +} +NET_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2PingFields& v); + +// Http2GoAwayFields: + +struct Http2GoAwayFields { + Http2GoAwayFields() {} + Http2GoAwayFields(uint32_t last_stream_id, Http2ErrorCode error_code) + : last_stream_id(last_stream_id), error_code(error_code) {} + static constexpr size_t EncodedSize() { return 8; } + bool IsSupportedErrorCode() const { + return IsSupportedHttp2ErrorCode(error_code); + } + + uint32_t last_stream_id; + Http2ErrorCode error_code; +}; + +NET_EXPORT_PRIVATE bool operator==(const Http2GoAwayFields& a, + const Http2GoAwayFields& b); +NET_EXPORT_PRIVATE inline bool operator!=(const Http2GoAwayFields& a, + const Http2GoAwayFields& b) { + return !(a == b); +} +NET_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2GoAwayFields& v); + +// Http2WindowUpdateFields: + +struct Http2WindowUpdateFields { + static constexpr size_t EncodedSize() { return 4; } + + // 31-bit, unsigned increase in the window size (only positive values are + // allowed). The high-bit is reserved for the future. + uint32_t window_size_increment; +}; + +NET_EXPORT_PRIVATE bool operator==(const Http2WindowUpdateFields& a, + const Http2WindowUpdateFields& b); +NET_EXPORT_PRIVATE inline bool operator!=(const Http2WindowUpdateFields& a, + const Http2WindowUpdateFields& b) { + return !(a == b); +} +NET_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2WindowUpdateFields& v); + +// Http2AltSvcFields: + +struct Http2AltSvcFields { + static constexpr size_t EncodedSize() { return 2; } + + // This is the one fixed size portion of the ALTSVC payload. + uint16_t origin_length; +}; + +NET_EXPORT_PRIVATE bool operator==(const Http2AltSvcFields& a, + const Http2AltSvcFields& b); +NET_EXPORT_PRIVATE inline bool operator!=(const Http2AltSvcFields& a, + const Http2AltSvcFields& b) { + return !(a == b); +} +NET_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& out, + const Http2AltSvcFields& v); + +} // namespace net + +#endif // NET_HTTP2_HTTP2_STRUCTURES_H_
diff --git a/net/http2/http2_structures_test.cc b/net/http2/http2_structures_test.cc new file mode 100644 index 0000000..96035c77 --- /dev/null +++ b/net/http2/http2_structures_test.cc
@@ -0,0 +1,486 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/http2_structures.h" + +// Tests are focused on Http2FrameHeader because it has by far the most +// methods of any of the structures. +// Note that EXPECT.*DEATH tests are slow (a fork is probably involved). + +// And in case you're wondering, yes, these are ridiculously thorough tests, +// but believe it or not, I've found stupid bugs this way. + +#include <memory> +#include <ostream> +#include <sstream> +#include <tuple> +#include <type_traits> +#include <vector> + +#include "base/template_util.h" +#include "net/http2/http2_structures_test_util.h" +#include "net/http2/tools/failure.h" +#include "net/http2/tools/http2_random.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using ::testing::Combine; +using ::testing::EndsWith; +using ::testing::HasSubstr; +using ::testing::MatchesRegex; +using ::testing::Not; +using ::testing::Values; +using ::testing::ValuesIn; + +namespace net { +namespace test { +namespace { + +template <typename E> +E IncrementEnum(E e) { + typedef typename base::underlying_type<E>::type I; + return static_cast<E>(1 + static_cast<I>(e)); +} + +#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) +std::vector<Http2FrameType> ValidFrameTypes() { + std::vector<Http2FrameType> valid_types{Http2FrameType::DATA}; + while (valid_types.back() != Http2FrameType::ALTSVC) { + valid_types.push_back(IncrementEnum(valid_types.back())); + } + return valid_types; +} +#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG) + +TEST(Http2FrameHeaderTest, Constructor) { + Http2Random random; + uint8_t frame_type = 0; + do { + // Only the payload length is DCHECK'd in the constructor, so we need to + // make sure it is a "uint24". + uint32_t payload_length = random.Rand32() & 0xffffff; + Http2FrameType type = static_cast<Http2FrameType>(frame_type); + uint8_t flags = random.Rand8(); + uint32_t stream_id = random.Rand32(); + + Http2FrameHeader v(payload_length, type, flags, stream_id); + + EXPECT_EQ(payload_length, v.payload_length); + EXPECT_EQ(type, v.type); + EXPECT_EQ(flags, v.flags); + EXPECT_EQ(stream_id, v.stream_id); + } while (frame_type++ == 255); + +#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) + EXPECT_DEBUG_DEATH(Http2FrameHeader(0x01000000, Http2FrameType::DATA, 0, 1), + "payload_length"); +#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG) +} + +TEST(Http2FrameHeaderTest, Eq) { + Http2Random random; + uint32_t payload_length = random.Rand32() & 0xffffff; + Http2FrameType type = static_cast<Http2FrameType>(random.Rand8()); + + uint8_t flags = random.Rand8(); + uint32_t stream_id = random.Rand32(); + + Http2FrameHeader v(payload_length, type, flags, stream_id); + + EXPECT_EQ(payload_length, v.payload_length); + EXPECT_EQ(type, v.type); + EXPECT_EQ(flags, v.flags); + EXPECT_EQ(stream_id, v.stream_id); + + Http2FrameHeader u(0, type, ~flags, stream_id); + + EXPECT_NE(u, v); + EXPECT_NE(v, u); + EXPECT_FALSE(u == v); + EXPECT_FALSE(v == u); + EXPECT_TRUE(u != v); + EXPECT_TRUE(v != u); + + u = v; + + EXPECT_EQ(u, v); + EXPECT_EQ(v, u); + EXPECT_TRUE(u == v); + EXPECT_TRUE(v == u); + EXPECT_FALSE(u != v); + EXPECT_FALSE(v != u); +} + +#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG) +// The tests of the valid frame types include EXPECT_DEBUG_DEATH, which is +// quite slow, so using value parameterized tests in order to allow sharding. +class Http2FrameHeaderTypeAndFlagTest + : public ::testing::TestWithParam< + std::tuple<Http2FrameType, Http2FrameFlag>> { + protected: + Http2FrameHeaderTypeAndFlagTest() + : type_(std::get<0>(GetParam())), flags_(std::get<1>(GetParam())) { + LOG(INFO) << "Frame type: " << type_; + LOG(INFO) << "Frame flags: " << Http2FrameFlagsToString(type_, flags_); + } + + const Http2FrameType type_; + const Http2FrameFlag flags_; +}; + +class IsEndStreamTest : public Http2FrameHeaderTypeAndFlagTest {}; +INSTANTIATE_TEST_CASE_P(IsEndStream, + IsEndStreamTest, + Combine(ValuesIn(ValidFrameTypes()), + Values(~Http2FrameFlag::FLAG_END_STREAM, + 0xff))); +TEST_P(IsEndStreamTest, IsEndStream) { + const bool is_set = (flags_ & Http2FrameFlag::FLAG_END_STREAM) == + Http2FrameFlag::FLAG_END_STREAM; + LOG(INFO) << "is_set=" << is_set; + Http2FrameHeader v(0, type_, flags_, 0); + switch (type_) { + case Http2FrameType::DATA: + case Http2FrameType::HEADERS: + EXPECT_EQ(is_set, v.IsEndStream()) << v; + if (is_set) { + EXPECT_THAT(v.FlagsToString(), MatchesRegex(".*\\|?END_STREAM\\|.*")); + } else { + EXPECT_THAT(v.FlagsToString(), Not(HasSubstr("END_STREAM"))); + } + v.RetainFlags(Http2FrameFlag::FLAG_END_STREAM); + EXPECT_EQ(is_set, v.IsEndStream()) << v; + { + std::stringstream s; + s << v; + EXPECT_EQ(v.ToString(), s.str()); + if (is_set) { + EXPECT_THAT(s.str(), HasSubstr("flags=END_STREAM,")); + } else { + EXPECT_THAT(s.str(), HasSubstr("flags=,")); + } + } + break; + default: + EXPECT_DEBUG_DEATH(v.IsEndStream(), "DATA.*HEADERS") << v; + } +} + +class IsACKTest : public Http2FrameHeaderTypeAndFlagTest {}; +INSTANTIATE_TEST_CASE_P(IsAck, + IsACKTest, + Combine(ValuesIn(ValidFrameTypes()), + Values(~Http2FrameFlag::FLAG_ACK, 0xff))); +TEST_P(IsACKTest, IsAck) { + const bool is_set = + (flags_ & Http2FrameFlag::FLAG_ACK) == Http2FrameFlag::FLAG_ACK; + LOG(INFO) << "is_set=" << is_set; + Http2FrameHeader v(0, type_, flags_, 0); + switch (type_) { + case Http2FrameType::SETTINGS: + case Http2FrameType::PING: + EXPECT_EQ(is_set, v.IsAck()) << v; + if (is_set) { + EXPECT_THAT(v.FlagsToString(), MatchesRegex(".*\\|?ACK\\|.*")); + } else { + EXPECT_THAT(v.FlagsToString(), Not(HasSubstr("ACK"))); + } + v.RetainFlags(Http2FrameFlag::FLAG_ACK); + EXPECT_EQ(is_set, v.IsAck()) << v; + { + std::stringstream s; + s << v; + EXPECT_EQ(v.ToString(), s.str()); + if (is_set) { + EXPECT_THAT(s.str(), HasSubstr("flags=ACK,")); + } else { + EXPECT_THAT(s.str(), HasSubstr("flags=,")); + } + } + break; + default: + EXPECT_DEBUG_DEATH(v.IsAck(), "SETTINGS.*PING") << v; + } +} + +class IsEndHeadersTest : public Http2FrameHeaderTypeAndFlagTest {}; +INSTANTIATE_TEST_CASE_P(IsEndHeaders, + IsEndHeadersTest, + Combine(ValuesIn(ValidFrameTypes()), + Values(~Http2FrameFlag::FLAG_END_HEADERS, + 0xff))); +TEST_P(IsEndHeadersTest, IsEndHeaders) { + const bool is_set = (flags_ & Http2FrameFlag::FLAG_END_HEADERS) == + Http2FrameFlag::FLAG_END_HEADERS; + LOG(INFO) << "is_set=" << is_set; + Http2FrameHeader v(0, type_, flags_, 0); + switch (type_) { + case Http2FrameType::HEADERS: + case Http2FrameType::PUSH_PROMISE: + case Http2FrameType::CONTINUATION: + EXPECT_EQ(is_set, v.IsEndHeaders()) << v; + if (is_set) { + EXPECT_THAT(v.FlagsToString(), MatchesRegex(".*\\|?END_HEADERS\\|.*")); + } else { + EXPECT_THAT(v.FlagsToString(), Not(HasSubstr("END_HEADERS"))); + } + v.RetainFlags(Http2FrameFlag::FLAG_END_HEADERS); + EXPECT_EQ(is_set, v.IsEndHeaders()) << v; + { + std::stringstream s; + s << v; + EXPECT_EQ(v.ToString(), s.str()); + if (is_set) { + EXPECT_THAT(s.str(), HasSubstr("flags=END_HEADERS,")); + } else { + EXPECT_THAT(s.str(), HasSubstr("flags=,")); + } + } + break; + default: + EXPECT_DEBUG_DEATH(v.IsEndHeaders(), + "HEADERS.*PUSH_PROMISE.*CONTINUATION") + << v; + } +} + +class IsPaddedTest : public Http2FrameHeaderTypeAndFlagTest {}; +INSTANTIATE_TEST_CASE_P(IsPadded, + IsPaddedTest, + Combine(ValuesIn(ValidFrameTypes()), + Values(~Http2FrameFlag::FLAG_PADDED, 0xff))); +TEST_P(IsPaddedTest, IsPadded) { + const bool is_set = + (flags_ & Http2FrameFlag::FLAG_PADDED) == Http2FrameFlag::FLAG_PADDED; + LOG(INFO) << "is_set=" << is_set; + Http2FrameHeader v(0, type_, flags_, 0); + switch (type_) { + case Http2FrameType::DATA: + case Http2FrameType::HEADERS: + case Http2FrameType::PUSH_PROMISE: + EXPECT_EQ(is_set, v.IsPadded()) << v; + if (is_set) { + EXPECT_THAT(v.FlagsToString(), MatchesRegex(".*\\|?PADDED\\|.*")); + } else { + EXPECT_THAT(v.FlagsToString(), Not(HasSubstr("PADDED"))); + } + v.RetainFlags(Http2FrameFlag::FLAG_PADDED); + EXPECT_EQ(is_set, v.IsPadded()) << v; + { + std::stringstream s; + s << v; + EXPECT_EQ(v.ToString(), s.str()); + if (is_set) { + EXPECT_THAT(s.str(), HasSubstr("flags=PADDED,")); + } else { + EXPECT_THAT(s.str(), HasSubstr("flags=,")); + } + } + break; + default: + EXPECT_DEBUG_DEATH(v.IsPadded(), "DATA.*HEADERS.*PUSH_PROMISE") << v; + } +} + +class HasPriorityTest : public Http2FrameHeaderTypeAndFlagTest {}; +INSTANTIATE_TEST_CASE_P(HasPriority, + HasPriorityTest, + Combine(ValuesIn(ValidFrameTypes()), + Values(~Http2FrameFlag::FLAG_PRIORITY, 0xff))); +TEST_P(HasPriorityTest, HasPriority) { + const bool is_set = + (flags_ & Http2FrameFlag::FLAG_PRIORITY) == Http2FrameFlag::FLAG_PRIORITY; + LOG(INFO) << "is_set=" << is_set; + Http2FrameHeader v(0, type_, flags_, 0); + switch (type_) { + case Http2FrameType::HEADERS: + EXPECT_EQ(is_set, v.HasPriority()) << v; + if (is_set) { + EXPECT_THAT(v.FlagsToString(), MatchesRegex(".*\\|?PRIORITY\\|.*")); + } else { + EXPECT_THAT(v.FlagsToString(), Not(HasSubstr("PRIORITY"))); + } + v.RetainFlags(Http2FrameFlag::FLAG_PRIORITY); + EXPECT_EQ(is_set, v.HasPriority()) << v; + { + std::stringstream s; + s << v; + EXPECT_EQ(v.ToString(), s.str()); + if (is_set) { + EXPECT_THAT(s.str(), HasSubstr("flags=PRIORITY,")); + } else { + EXPECT_THAT(s.str(), HasSubstr("flags=,")); + } + } + break; + default: + EXPECT_DEBUG_DEATH(v.HasPriority(), "HEADERS") << v; + } +} + +TEST(Http2PriorityFieldsTest, Constructor) { + Http2Random random; + uint32_t stream_dependency = random.Rand32() & StreamIdMask(); + uint32_t weight = 1 + random.Rand8(); + bool is_exclusive = random.OneIn(2); + + Http2PriorityFields v(stream_dependency, weight, is_exclusive); + + EXPECT_EQ(stream_dependency, v.stream_dependency); + EXPECT_EQ(weight, v.weight); + EXPECT_EQ(is_exclusive, v.is_exclusive); + + // The high-bit must not be set on the stream id. + EXPECT_DEBUG_DEATH( + Http2PriorityFields(stream_dependency | 0x80000000, weight, is_exclusive), + "31-bit"); + + // The weight must be in the range 1-256. + EXPECT_DEBUG_DEATH(Http2PriorityFields(stream_dependency, 0, is_exclusive), + "too small"); + EXPECT_DEBUG_DEATH( + Http2PriorityFields(stream_dependency, weight + 256, is_exclusive), + "too large"); +} +#endif // GTEST_HAS_DEATH_TEST && !defined(NDEBUG) + +TEST(Http2RstStreamFieldsTest, IsSupported) { + Http2RstStreamFields v{Http2ErrorCode::HTTP2_NO_ERROR}; + EXPECT_TRUE(v.IsSupportedErrorCode()) << v; + + Http2RstStreamFields u{static_cast<Http2ErrorCode>(~0)}; + EXPECT_FALSE(u.IsSupportedErrorCode()) << v; +} + +TEST(Http2SettingFieldsTest, Misc) { + Http2Random random; + Http2SettingsParameter parameter = + static_cast<Http2SettingsParameter>(random.Rand16()); + uint32_t value = random.Rand32(); + + Http2SettingFields v(parameter, value); + + EXPECT_EQ(v, v); + EXPECT_EQ(parameter, v.parameter); + EXPECT_EQ(value, v.value); + + if (static_cast<uint16_t>(parameter) < 7) { + EXPECT_TRUE(v.IsSupportedParameter()) << v; + } else { + EXPECT_FALSE(v.IsSupportedParameter()) << v; + } + + Http2SettingFields u(parameter, ~value); + EXPECT_NE(v, u); + EXPECT_EQ(v.parameter, u.parameter); + EXPECT_NE(v.value, u.value); + + Http2SettingFields w(IncrementEnum(parameter), value); + EXPECT_NE(v, w); + EXPECT_NE(v.parameter, w.parameter); + EXPECT_EQ(v.value, w.value); + + Http2SettingFields x(Http2SettingsParameter::MAX_FRAME_SIZE, 123); + std::stringstream s; + s << x; + EXPECT_EQ("parameter=MAX_FRAME_SIZE, value=123", s.str()); +} + +TEST(Http2PushPromiseTest, Misc) { + Http2Random random; + uint32_t promised_stream_id = random.Rand32() & StreamIdMask(); + + Http2PushPromiseFields v{promised_stream_id}; + EXPECT_EQ(promised_stream_id, v.promised_stream_id); + EXPECT_EQ(v, v); + + std::stringstream s1; + s1 << "promised_stream_id=" << promised_stream_id; + std::stringstream s2; + s2 << v; + EXPECT_EQ(s1.str(), s2.str()); + + // High-bit is reserved, but not used, so we can set it. + promised_stream_id |= 0x80000000; + Http2PushPromiseFields w{promised_stream_id}; + EXPECT_EQ(w, w); + EXPECT_NE(v, w); + + v.promised_stream_id = promised_stream_id; + EXPECT_EQ(v, w); +} + +TEST(Http2GoAwayFieldsTest, Misc) { + Http2Random random; + uint32_t last_stream_id = random.Rand32() & StreamIdMask(); + Http2ErrorCode error_code = static_cast<Http2ErrorCode>(random.Rand32()); + + Http2GoAwayFields v(last_stream_id, error_code); + EXPECT_EQ(v, v); + EXPECT_EQ(last_stream_id, v.last_stream_id); + EXPECT_EQ(error_code, v.error_code); + + if (static_cast<uint32_t>(error_code) < 14) { + EXPECT_TRUE(v.IsSupportedErrorCode()) << v; + } else { + EXPECT_FALSE(v.IsSupportedErrorCode()) << v; + } + + Http2GoAwayFields u(~last_stream_id, error_code); + EXPECT_NE(v, u); + EXPECT_NE(v.last_stream_id, u.last_stream_id); + EXPECT_EQ(v.error_code, u.error_code); +} + +TEST(Http2WindowUpdateTest, Misc) { + Http2Random random; + uint32_t window_size_increment = random.Rand32() & UInt31Mask(); + + Http2WindowUpdateFields v{window_size_increment}; + EXPECT_EQ(window_size_increment, v.window_size_increment); + EXPECT_EQ(v, v); + + std::stringstream s1; + s1 << "window_size_increment=" << window_size_increment; + std::stringstream s2; + s2 << v; + EXPECT_EQ(s1.str(), s2.str()); + + // High-bit is reserved, but not used, so we can set it. + window_size_increment |= 0x80000000; + Http2WindowUpdateFields w{window_size_increment}; + EXPECT_EQ(w, w); + EXPECT_NE(v, w); + + v.window_size_increment = window_size_increment; + EXPECT_EQ(v, w); +} + +TEST(Http2AltSvcTest, Misc) { + Http2Random random; + uint16_t origin_length = random.Rand16(); + + Http2AltSvcFields v{origin_length}; + EXPECT_EQ(origin_length, v.origin_length); + EXPECT_EQ(v, v); + + std::stringstream s1; + s1 << "origin_length=" << origin_length; + std::stringstream s2; + s2 << v; + EXPECT_EQ(s1.str(), s2.str()); + + Http2AltSvcFields w{++origin_length}; + EXPECT_EQ(w, w); + EXPECT_NE(v, w); + + v.origin_length = w.origin_length; + EXPECT_EQ(v, w); +} + +} // namespace +} // namespace test +} // namespace net
diff --git a/net/http2/http2_structures_test_util.cc b/net/http2/http2_structures_test_util.cc new file mode 100644 index 0000000..1e6f429 --- /dev/null +++ b/net/http2/http2_structures_test_util.cc
@@ -0,0 +1,107 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/http2_structures_test_util.h" + +#include "net/http2/http2_constants.h" +#include "net/http2/http2_constants_test_util.h" +#include "net/http2/http2_structures.h" +#include "net/http2/tools/http2_random.h" + +namespace net { +namespace test { + +void Randomize(Http2FrameHeader* p, RandomBase* rng) { + p->payload_length = rng->Rand32() & 0xffffff; + p->type = static_cast<Http2FrameType>(rng->Rand8()); + p->flags = static_cast<Http2FrameFlag>(rng->Rand8()); + p->stream_id = rng->Rand32() & StreamIdMask(); +} +void Randomize(Http2PriorityFields* p, RandomBase* rng) { + p->stream_dependency = rng->Rand32() & StreamIdMask(); + p->weight = rng->Rand8() + 1; + p->is_exclusive = rng->OneIn(2); +} +void Randomize(Http2RstStreamFields* p, RandomBase* rng) { + p->error_code = static_cast<Http2ErrorCode>(rng->Rand32()); +} +void Randomize(Http2SettingFields* p, RandomBase* rng) { + p->parameter = static_cast<Http2SettingsParameter>(rng->Rand16()); + p->value = rng->Rand32(); +} +void Randomize(Http2PushPromiseFields* p, RandomBase* rng) { + p->promised_stream_id = rng->Rand32() & StreamIdMask(); +} +void Randomize(Http2PingFields* p, RandomBase* rng) { + for (int ndx = 0; ndx < 8; ++ndx) { + p->opaque_data[ndx] = rng->Rand8(); + } +} +void Randomize(Http2GoAwayFields* p, RandomBase* rng) { + p->last_stream_id = rng->Rand32() & StreamIdMask(); + p->error_code = static_cast<Http2ErrorCode>(rng->Rand32()); +} +void Randomize(Http2WindowUpdateFields* p, RandomBase* rng) { + p->window_size_increment = rng->Rand32() & 0x7fffffff; +} +void Randomize(Http2AltSvcFields* p, RandomBase* rng) { + p->origin_length = rng->Rand16(); +} + +void ScrubFlagsOfHeader(Http2FrameHeader* header) { + uint8_t invalid_mask = InvalidFlagMaskForFrameType(header->type); + uint8_t keep_mask = ~invalid_mask; + header->RetainFlags(keep_mask); +} + +bool FrameIsPadded(const Http2FrameHeader& header) { + switch (header.type) { + case Http2FrameType::DATA: + case Http2FrameType::HEADERS: + case Http2FrameType::PUSH_PROMISE: + return header.IsPadded(); + default: + return false; + } +} + +bool FrameHasPriority(const Http2FrameHeader& header) { + switch (header.type) { + case Http2FrameType::HEADERS: + return header.HasPriority(); + case Http2FrameType::PRIORITY: + return true; + default: + return false; + } +} + +bool FrameCanHavePayload(const Http2FrameHeader& header) { + switch (header.type) { + case Http2FrameType::DATA: + case Http2FrameType::HEADERS: + case Http2FrameType::PUSH_PROMISE: + case Http2FrameType::CONTINUATION: + case Http2FrameType::PING: + case Http2FrameType::GOAWAY: + case Http2FrameType::ALTSVC: + return true; + default: + return false; + } +} + +bool FrameCanHaveHpackPayload(const Http2FrameHeader& header) { + switch (header.type) { + case Http2FrameType::HEADERS: + case Http2FrameType::PUSH_PROMISE: + case Http2FrameType::CONTINUATION: + return true; + default: + return false; + } +} + +} // namespace test +} // namespace net
diff --git a/net/http2/http2_structures_test_util.h b/net/http2/http2_structures_test_util.h new file mode 100644 index 0000000..710a530 --- /dev/null +++ b/net/http2/http2_structures_test_util.h
@@ -0,0 +1,61 @@ +// Copyright 2016 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 NET_HTTP2_HTTP2_STRUCTURES_TEST_UTIL_H_ +#define NET_HTTP2_HTTP2_STRUCTURES_TEST_UTIL_H_ + +#include <string> + +#include "net/http2/http2_structures.h" +#include "net/http2/tools/http2_frame_builder.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace test { + +class RandomBase; + +template <class S> +std::string SerializeStructure(const S& s) { + Http2FrameBuilder fb; + fb.Append(s); + EXPECT_EQ(S::EncodedSize(), fb.size()); + return fb.buffer(); +} + +// Randomize the members of out, in a manner that yields encodeable contents +// (e.g. a "uint24" field has only the low 24 bits set). +void Randomize(Http2FrameHeader* out, RandomBase* rng); +void Randomize(Http2PriorityFields* out, RandomBase* rng); +void Randomize(Http2RstStreamFields* out, RandomBase* rng); +void Randomize(Http2SettingFields* out, RandomBase* rng); +void Randomize(Http2PushPromiseFields* out, RandomBase* rng); +void Randomize(Http2PingFields* out, RandomBase* rng); +void Randomize(Http2GoAwayFields* out, RandomBase* rng); +void Randomize(Http2WindowUpdateFields* out, RandomBase* rng); +void Randomize(Http2AltSvcFields* out, RandomBase* rng); + +// Clear bits of header->flags that are known to be invalid for the +// type. For unknown frame types, no change is made. +void ScrubFlagsOfHeader(Http2FrameHeader* header); + +// Is the frame with this header padded? Only true for known/supported frame +// types. +bool FrameIsPadded(const Http2FrameHeader& header); + +// Does the frame with this header have Http2PriorityFields? +bool FrameHasPriority(const Http2FrameHeader& header); + +// Does the frame with this header have a variable length payload (including +// empty) payload (e.g. DATA or HEADERS)? Really a test of the frame type. +bool FrameCanHavePayload(const Http2FrameHeader& header); + +// Does the frame with this header have a variable length HPACK payload +// (including empty) payload (e.g. HEADERS)? Really a test of the frame type. +bool FrameCanHaveHpackPayload(const Http2FrameHeader& header); + +} // namespace test +} // namespace net + +#endif // NET_HTTP2_HTTP2_STRUCTURES_TEST_UTIL_H_
diff --git a/net/http2/tools/failure.cc b/net/http2/tools/failure.cc new file mode 100644 index 0000000..b354be1e --- /dev/null +++ b/net/http2/tools/failure.cc
@@ -0,0 +1,29 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/tools/failure.h" + +namespace net { +namespace test { + +// This is a copy of the same named method in ::testing::internal. +// TODO(jamessynge): See about getting something like VERIFY_* adopted by +// gUnit (probably a very difficult task!). +std::string GetBoolAssertionFailureMessage( + const ::testing::AssertionResult& assertion_result, + const char* expression_text, + const char* actual_predicate_value, + const char* expected_predicate_value) { + const char* actual_message = assertion_result.message(); + ::testing::Message msg; + msg << "Value of: " << expression_text + << "\n Actual: " << actual_predicate_value; + if (actual_message[0] != '\0') + msg << " (" << actual_message << ")"; + msg << "\nExpected: " << expected_predicate_value; + return msg.GetString(); +} + +} // namespace test +} // namespace net
diff --git a/net/http2/tools/failure.h b/net/http2/tools/failure.h new file mode 100644 index 0000000..aa36d79 --- /dev/null +++ b/net/http2/tools/failure.h
@@ -0,0 +1,154 @@ +// Copyright 2016 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 NET_HTTP2_TOOLS_FAILURE_H_ +#define NET_HTTP2_TOOLS_FAILURE_H_ + +// Defines VERIFY_* macros, analogous to gUnit's EXPECT_* and ASSERT_* macros, +// but these return an appropriate AssertionResult if the condition is not +// satisfied. This enables one to create a function for verifying expectations +// that are needed by multiple callers or that rely on arguments not accessible +// to the main test method. Using VERIFY_SUCCESS allows one to annotate the +// a failing AssertionResult with more context. + +#include <iosfwd> +#include <sstream> +#include <string> + +#include "base/macros.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace test { + +template <typename T> +class VerifyThatHelper { + public: + VerifyThatHelper(const T& value, ::testing::Matcher<T> matcher) { + matches_ = matcher.Matches(value); + if (!matches_) { + printed_value_ = ::testing::PrintToString(value); + + std::ostringstream os; + matcher.DescribeTo(&os); + matcher_description_ = os.str(); + } + } + + operator bool() const { return matches_; } + + const std::string& printed_value() const { return printed_value_; } + const std::string& matcher_description() const { + return matcher_description_; + } + + private: + bool matches_; + std::string printed_value_; + std::string matcher_description_; + + DISALLOW_COPY_AND_ASSIGN(VerifyThatHelper); +}; + +// Constructs a failure message for Boolean assertions such as VERIFY_TRUE. +std::string GetBoolAssertionFailureMessage( + const ::testing::AssertionResult& assertion_result, + const char* expression_text, + const char* actual_predicate_value, + const char* expected_predicate_value); + +} // namespace test +} // namespace net + +// Macro for adding verification location to output stream or AssertionResult. +// Starts with a new-line because of the way that gUnit displays failures for +// EXPECT_TRUE(CallToFunctionThatFailsToVerify()). +#define VERIFY_FAILED_LOCATION_ \ + "\n" \ + << "(VERIFY failed in " << __func__ << "\n" \ + << " at " __FILE__ " : " << __LINE__ << ")\n" + +// Implements Boolean test verifications VERIFY_TRUE and VERIFY_FALSE. +// text is a textual represenation of expression as it was passed into +// VERIFY_TRUE or VERIFY_FALSE. +// clang-format off +#define VERIFY_TEST_BOOLEAN_(condition, text, actual, expected) \ + if (const ::testing::AssertionResult __assertion_result = \ + ::testing::AssertionResult((condition) ? expected : actual)) \ + ; \ + else \ + return ::testing::AssertionFailure() \ + << VERIFY_FAILED_LOCATION_ \ + << ::net::test::GetBoolAssertionFailureMessage( \ + __assertion_result, text, #actual, #expected) +// clang-format on + +// Boolean assertions. condition can be either a Boolean expression or an +// expression convertable to a boolean (such as a ::gtl::labs::optional). +#define VERIFY_TRUE(condition) \ + VERIFY_TEST_BOOLEAN_(condition, #condition, false, true) + +#define VERIFY_FALSE(condition) \ + VERIFY_TEST_BOOLEAN_(condition, #condition, true, false) + +// Convenient helper macro for writing methods that return an AssertionFailure +// that includes the tested condition in the message (in the manner of +// ASSERT_THAT and EXPECT_THAT). +// +// This macro avoids the do {} while(false) trick and putting braces around +// the if so you can do things like: +// VERIFY_THAT(foo, Lt(4)) << "foo too big in iteration " << i; +// (This parallels the implementation of CHECK().) +// +// We use an if statement with an empty true branch so that this doesn't eat +// a neighboring else when used in an unbraced if statement like: +// if (condition) +// VERIFY_THAT(foo, Eq(bar)); +// else +// FAIL(); +#define VERIFY_THAT(value, matcher) \ + if (const auto& _verify_that_helper = \ + ::net::test::VerifyThatHelper<decltype(value)>(value, matcher)) \ + ; \ + else \ + return ::testing::AssertionFailure() \ + << "Failed to verify that '" #value "' (" \ + << _verify_that_helper.printed_value() << ") " \ + << _verify_that_helper.matcher_description() \ + << " (on " __FILE__ ":" << __LINE__ << "). " + +// Useful variants of VERIFY_THAT, similar to the corresponding EXPECT_X or +// ASSERT_X defined by gUnit. +#define VERIFY_EQ(val1, val2) VERIFY_THAT(val1, ::testing::Eq(val2)) +#define VERIFY_NE(val1, val2) VERIFY_THAT(val1, ::testing::Ne(val2)) +#define VERIFY_GT(val1, val2) VERIFY_THAT(val1, ::testing::Gt(val2)) +#define VERIFY_LT(val1, val2) VERIFY_THAT(val1, ::testing::Lt(val2)) +#define VERIFY_GE(val1, val2) VERIFY_THAT(val1, ::testing::Ge(val2)) +#define VERIFY_LE(val1, val2) VERIFY_THAT(val1, ::testing::Le(val2)) + +// Convenience macro matching EXPECT_OK +#define VERIFY_OK(statement) VERIFY_EQ(::util::Status::OK, (statement)) + +// This version verifies that an expression of type AssertionResult is +// AssertionSuccess. If instead the value is an AssertionFailure, it appends +// info about the current code location to the failure's message and returns +// the failure to the caller of the current method. It permits the code site +// to append further messages to the failure message. For example: +// VERIFY_SUCCESS(SomeCall()) << "Some more context about SomeCall"; +// clang-format off +#define VERIFY_SUCCESS(expr) \ + if (::testing::AssertionResult __assertion_result = (expr)) \ + ; \ + else \ + return __assertion_result << VERIFY_FAILED_LOCATION_ +// clang-format on + +#define VERIFY_AND_RETURN_SUCCESS(expression) \ + { \ + VERIFY_SUCCESS(expression); \ + return ::testing::AssertionSuccess(); \ + } + +#endif // NET_HTTP2_TOOLS_FAILURE_H_
diff --git a/net/http2/tools/http2_bug_tracker.h b/net/http2/tools/http2_bug_tracker.h new file mode 100644 index 0000000..0e31bc2 --- /dev/null +++ b/net/http2/tools/http2_bug_tracker.h
@@ -0,0 +1,14 @@ +// Copyright 2016 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 NET_HTTP2_TOOLS_HTTP2_BUG_TRACKER_H_ +#define NET_HTTP2_TOOLS_HTTP2_BUG_TRACKER_H_ + +#include "base/logging.h" + +#define HTTP2_BUG LOG(DFATAL) +#define HTTP2_BUG_IF LOG_IF(DFATAL, (condition)) +#define FLAGS_http2_always_log_bugs_for_tests (true) + +#endif // NET_HTTP2_TOOLS_HTTP2_BUG_TRACKER_H_
diff --git a/net/http2/tools/http2_frame_builder.cc b/net/http2/tools/http2_frame_builder.cc new file mode 100644 index 0000000..c072525 --- /dev/null +++ b/net/http2/tools/http2_frame_builder.cc
@@ -0,0 +1,182 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/tools/http2_frame_builder.h" + +#ifdef WIN32 +#include <winsock2.h> // for htonl() functions +#else +#include <arpa/inet.h> +#include <netinet/in.h> // for htonl, htons +#endif + +#include "testing/gtest/include/gtest/gtest.h" + +using base::StringPiece; + +namespace net { +namespace test { + +Http2FrameBuilder::Http2FrameBuilder(Http2FrameType type, + uint8_t flags, + uint32_t stream_id) { + AppendUInt24(0); // Frame payload length, unknown so far. + Append(type); + AppendUInt8(flags); + AppendUInt31(stream_id); +} + +Http2FrameBuilder::Http2FrameBuilder(const Http2FrameHeader& v) { + Append(v); +} + +void Http2FrameBuilder::Append(StringPiece s) { + s.AppendToString(&buffer_); +} + +void Http2FrameBuilder::AppendBytes(const void* data, uint32_t num_bytes) { + Append(StringPiece(static_cast<const char*>(data), num_bytes)); +} + +void Http2FrameBuilder::AppendZeroes(size_t num_zero_bytes) { + char zero = 0; + buffer_.append(num_zero_bytes, zero); +} + +void Http2FrameBuilder::AppendUInt8(uint8_t value) { + AppendBytes(&value, 1); +} + +void Http2FrameBuilder::AppendUInt16(uint16_t value) { + value = htons(value); + AppendBytes(&value, 2); +} + +void Http2FrameBuilder::AppendUInt24(uint32_t value) { + // Doesn't make sense to try to append a larger value, as that doesn't + // simulate something an encoder could do (i.e. the other 8 bits simply aren't + // there to be occupied). + EXPECT_EQ(value, value & 0xffffff); + value = htonl(value); + AppendBytes(reinterpret_cast<char*>(&value) + 1, 3); +} + +void Http2FrameBuilder::AppendUInt31(uint32_t value) { + // If you want to test the high-bit being set, call AppendUInt32 instead. + uint32_t tmp = value & StreamIdMask(); + EXPECT_EQ(value, value & StreamIdMask()) + << "High-bit of uint32 should be clear."; + value = htonl(tmp); + AppendBytes(&value, 4); +} + +void Http2FrameBuilder::AppendUInt32(uint32_t value) { + value = htonl(value); + AppendBytes(&value, sizeof(value)); +} + +void Http2FrameBuilder::Append(Http2ErrorCode error_code) { + AppendUInt32(static_cast<uint32_t>(error_code)); +} + +void Http2FrameBuilder::Append(Http2FrameType type) { + AppendUInt8(static_cast<uint8_t>(type)); +} + +void Http2FrameBuilder::Append(Http2SettingsParameter parameter) { + AppendUInt16(static_cast<uint16_t>(parameter)); +} + +void Http2FrameBuilder::Append(const Http2FrameHeader& v) { + AppendUInt24(v.payload_length); + Append(v.type); + AppendUInt8(v.flags); + AppendUInt31(v.stream_id); +} + +void Http2FrameBuilder::Append(const Http2PriorityFields& v) { + // The EXCLUSIVE flag is the high-bit of the 32-bit stream dependency field. + uint32_t tmp = v.stream_dependency & StreamIdMask(); + EXPECT_EQ(tmp, v.stream_dependency); + if (v.is_exclusive) { + tmp |= 0x80000000; + } + AppendUInt32(tmp); + + // The PRIORITY frame's weight field is logically in the range [1, 256], + // but is encoded as a byte in the range [0, 255]. + ASSERT_LE(1u, v.weight); + ASSERT_LE(v.weight, 256u); + AppendUInt8(v.weight - 1); +} + +void Http2FrameBuilder::Append(const Http2RstStreamFields& v) { + Append(v.error_code); +} + +void Http2FrameBuilder::Append(const Http2SettingFields& v) { + Append(v.parameter); + AppendUInt32(v.value); +} + +void Http2FrameBuilder::Append(const Http2PushPromiseFields& v) { + AppendUInt31(v.promised_stream_id); +} + +void Http2FrameBuilder::Append(const Http2PingFields& v) { + AppendBytes(v.opaque_data, sizeof Http2PingFields::opaque_data); +} + +void Http2FrameBuilder::Append(const Http2GoAwayFields& v) { + AppendUInt31(v.last_stream_id); + Append(v.error_code); +} + +void Http2FrameBuilder::Append(const Http2WindowUpdateFields& v) { + EXPECT_NE(0u, v.window_size_increment) << "Increment must be non-zero."; + AppendUInt31(v.window_size_increment); +} + +void Http2FrameBuilder::Append(const Http2AltSvcFields& v) { + AppendUInt16(v.origin_length); +} + +// Methods for changing existing buffer contents. + +void Http2FrameBuilder::WriteAt(StringPiece s, size_t offset) { + ASSERT_LE(offset, buffer_.size()); + size_t len = offset + s.size(); + if (len > buffer_.size()) { + buffer_.resize(len); + } + for (size_t ndx = 0; ndx < s.size(); ++ndx) { + buffer_[offset + ndx] = s[ndx]; + } +} + +void Http2FrameBuilder::WriteBytesAt(const void* data, + uint32_t num_bytes, + size_t offset) { + WriteAt(StringPiece(static_cast<const char*>(data), num_bytes), offset); +} + +void Http2FrameBuilder::WriteUInt24At(uint32_t value, size_t offset) { + ASSERT_LT(value, static_cast<uint32_t>(1 << 24)); + value = htonl(value); + WriteBytesAt(reinterpret_cast<char*>(&value) + 1, sizeof(value) - 1, offset); +} + +void Http2FrameBuilder::SetPayloadLength(uint32_t payload_length) { + WriteUInt24At(payload_length, 0); +} + +size_t Http2FrameBuilder::SetPayloadLength() { + EXPECT_GE(size(), Http2FrameHeader::EncodedSize()); + uint32_t payload_length = size() - Http2FrameHeader::EncodedSize(); + SetPayloadLength(payload_length); + return payload_length; +} + +} // namespace test +} // namespace net
diff --git a/net/http2/tools/http2_frame_builder.h b/net/http2/tools/http2_frame_builder.h new file mode 100644 index 0000000..c665c759 --- /dev/null +++ b/net/http2/tools/http2_frame_builder.h
@@ -0,0 +1,100 @@ +// Copyright 2016 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 NET_HTTP2_TOOLS_HTTP2_FRAME_BUILDER_H_ +#define NET_HTTP2_TOOLS_HTTP2_FRAME_BUILDER_H_ + +// Http2FrameBuilder builds wire-format HTTP/2 frames (or fragments thereof) +// from components. +// +// For now, this is only intended for use in tests, and thus has EXPECT* in the +// code. If desired to use it in an encoder, it will need optimization work, +// especially w.r.t memory mgmt, and the EXPECT* will need to be removed or +// replaced with DCHECKs. + +#include <stddef.h> // for size_t + +#include <string> + +#include "base/strings/string_piece.h" +#include "net/http2/http2_constants.h" +#include "net/http2/http2_structures.h" + +namespace net { +namespace test { + +class Http2FrameBuilder { + public: + Http2FrameBuilder(Http2FrameType type, uint8_t flags, uint32_t stream_id); + explicit Http2FrameBuilder(const Http2FrameHeader& v); + Http2FrameBuilder() {} + ~Http2FrameBuilder() {} + + size_t size() const { return buffer_.size(); } + const std::string& buffer() const { return buffer_; } + + //---------------------------------------------------------------------------- + // Methods for appending to the end of the buffer. + + // Append a sequence of bytes from various sources. + void Append(base::StringPiece s); + void AppendBytes(const void* data, uint32_t num_bytes); + + // Append an array of type T[N] to the string. Intended for tests with arrays + // initialized from literals, such as: + // const char kData[] = {0x12, 0x23, ...}; + // builder.AppendBytes(kData); + template <typename T, size_t N> + void AppendBytes(T (&buf)[N]) { + AppendBytes(buf, N * sizeof(buf[0])); + } + + // Support for appending padding. Does not read or write the Pad Length field. + void AppendZeroes(size_t num_zero_bytes); + + // Append various sizes of unsigned integers. + void AppendUInt8(uint8_t value); + void AppendUInt16(uint16_t value); + void AppendUInt24(uint32_t value); + void AppendUInt31(uint32_t value); + void AppendUInt32(uint32_t value); + + // Append various enums. + void Append(Http2ErrorCode error_code); + void Append(Http2FrameType type); + void Append(Http2SettingsParameter parameter); + + // Append various structures. + void Append(const Http2FrameHeader& v); + void Append(const Http2PriorityFields& v); + void Append(const Http2RstStreamFields& v); + void Append(const Http2SettingFields& v); + void Append(const Http2PushPromiseFields& v); + void Append(const Http2PingFields& v); + void Append(const Http2GoAwayFields& v); + void Append(const Http2WindowUpdateFields& v); + void Append(const Http2AltSvcFields& v); + + // Methods for changing existing buffer contents (mostly focused on updating + // the payload length). + + void WriteAt(base::StringPiece s, size_t offset); + void WriteBytesAt(const void* data, uint32_t num_bytes, size_t offset); + void WriteUInt24At(uint32_t value, size_t offset); + + // Set the payload length to the specified size. + void SetPayloadLength(uint32_t payload_length); + + // Sets the payload length to the size of the buffer minus the size of + // the frame header. + size_t SetPayloadLength(); + + private: + std::string buffer_; +}; + +} // namespace test +} // namespace net + +#endif // NET_HTTP2_TOOLS_HTTP2_FRAME_BUILDER_H_
diff --git a/net/http2/tools/http2_random.cc b/net/http2/tools/http2_random.cc new file mode 100644 index 0000000..8732d6a8 --- /dev/null +++ b/net/http2/tools/http2_random.cc
@@ -0,0 +1,59 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/tools/http2_random.h" + +#include <limits> +#include <memory> + +#include "base/rand_util.h" + +namespace net { +namespace test { + +bool Http2Random::OneIn(int n) { + return base::RandGenerator(n) == 0; +} + +int32_t Http2Random::Uniform(int32_t n) { + return base::RandGenerator(n); +} + +uint8_t Http2Random::Rand8() { + return base::RandGenerator( + static_cast<uint64_t>(std::numeric_limits<uint8_t>::max()) + 1); +} + +uint16_t Http2Random::Rand16() { + return base::RandGenerator( + static_cast<uint64_t>(std::numeric_limits<uint16_t>::max()) + 1); +} + +uint32_t Http2Random::Rand32() { + return base::RandGenerator( + static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()) + 1); +} + +uint64_t Http2Random::Rand64() { + return base::RandUint64(); +} + +int32_t Http2Random::Next() { + return Rand32(); +} + +int32_t Http2Random::Skewed(int max_log) { + const uint32_t base = Rand32() % (max_log + 1); + const uint32_t mask = ((base < 32) ? (1u << base) : 0u) - 1u; + return Rand32() & mask; +} + +std::string Http2Random::RandString(int length) { + std::unique_ptr<char[]> buffer(new char[length]); + base::RandBytes(buffer.get(), length); + return std::string(buffer.get(), length); +} + +} // namespace test +} // namespace net
diff --git a/net/http2/tools/http2_random.h b/net/http2/tools/http2_random.h new file mode 100644 index 0000000..f7956cb8 --- /dev/null +++ b/net/http2/tools/http2_random.h
@@ -0,0 +1,46 @@ +// Copyright 2016 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 NET_HTTP2_TOOLS_HTTP2_RANDOM_H_ +#define NET_HTTP2_TOOLS_HTTP2_RANDOM_H_ + +#include <stdint.h> + +#include <string> + +namespace net { +namespace test { + +class RandomBase { + public: + virtual ~RandomBase() {} + virtual bool OneIn(int n) = 0; + virtual int32_t Uniform(int32_t n) = 0; + virtual uint8_t Rand8() = 0; + virtual uint16_t Rand16() = 0; + virtual uint32_t Rand32() = 0; + virtual uint64_t Rand64() = 0; + virtual int32_t Next() = 0; + virtual int32_t Skewed(int max_log) = 0; + virtual std::string RandString(int length) = 0; +}; + +class Http2Random : public RandomBase { + public: + ~Http2Random() override {} + bool OneIn(int n) override; + int32_t Uniform(int32_t n) override; + uint8_t Rand8() override; + uint16_t Rand16() override; + uint32_t Rand32() override; + uint64_t Rand64() override; + int32_t Next() override; + int32_t Skewed(int max_log) override; + std::string RandString(int length) override; +}; + +} // namespace test +} // namespace net + +#endif // NET_HTTP2_TOOLS_HTTP2_RANDOM_H_
diff --git a/net/http2/tools/random_decoder_test.cc b/net/http2/tools/random_decoder_test.cc new file mode 100644 index 0000000..8fd36743 --- /dev/null +++ b/net/http2/tools/random_decoder_test.cc
@@ -0,0 +1,179 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http2/tools/random_decoder_test.h" + +#include <stddef.h> + +#include <algorithm> +#include <memory> + +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/http2_constants.h" +#include "net/http2/tools/failure.h" +#include "testing/gtest/include/gtest/gtest.h" + +// It's rather time consuming to decode large buffers one at a time, +// especially with the log level cranked up. So, by default we don't do +// that unless explicitly requested. + +using ::testing::AssertionFailure; +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; +using base::StringPiece; + +namespace net { +namespace test { + +std::string HexEncode(StringPiece s) { + return base::HexEncode(s.data(), s.size()); +} + +RandomDecoderTest::RandomDecoderTest() {} + +bool RandomDecoderTest::StopDecodeOnDone() { + return stop_decode_on_done_; +} + +DecodeStatus RandomDecoderTest::DecodeSegments(DecodeBuffer* original, + const SelectSize& select_size) { + DecodeStatus status = DecodeStatus::kDecodeInProgress; + bool first = true; + VLOG(2) << "DecodeSegments: input size=" << original->Remaining(); + while (first || original->HasData()) { + size_t remaining = original->Remaining(); + size_t size = std::min( + remaining, select_size.Run(first, original->Offset(), remaining)); + DecodeBuffer db(original->cursor(), size); + VLOG(2) << "Decoding " << size << " bytes of " << remaining << " remaining"; + if (first) { + first = false; + status = StartDecoding(&db); + } else { + status = ResumeDecoding(&db); + } + // A decoder MUST consume some input (if any is available), else we could + // get stuck in infinite loops. + if (db.Offset() == 0 && db.HasData() && + status != DecodeStatus::kDecodeError) { + ADD_FAILURE() << "Decoder didn't make any progress; db.FullSize=" + << db.FullSize() + << " original.Offset=" << original->Offset(); + return DecodeStatus::kDecodeError; + } + original->AdvanceCursor(db.Offset()); + switch (status) { + case DecodeStatus::kDecodeDone: + if (original->Empty() || StopDecodeOnDone()) { + return DecodeStatus::kDecodeDone; + } + continue; + case DecodeStatus::kDecodeInProgress: + continue; + case DecodeStatus::kDecodeError: + return DecodeStatus::kDecodeError; + } + } + return status; +} + +// Decode |original| multiple times, with different segmentations, validating +// after each decode, returning on the first failure. +AssertionResult RandomDecoderTest::DecodeAndValidateSeveralWays( + DecodeBuffer* original, + bool return_non_zero_on_first, + const Validator& validator) { + const uint32_t original_remaining = original->Remaining(); + VLOG(1) << "DecodeAndValidateSeveralWays - Start, remaining = " + << original_remaining; + uint32_t first_consumed; + { + // Fast decode (no stopping unless decoder does so). + DecodeBuffer input(original->cursor(), original_remaining); + VLOG(2) << "DecodeSegmentsAndValidate with SelectRemaining"; + VERIFY_SUCCESS(DecodeSegmentsAndValidate( + &input, base::Bind(&SelectRemaining), validator)) + << "\nFailed with SelectRemaining; input.Offset=" << input.Offset() + << "; input.Remaining=" << input.Remaining(); + first_consumed = input.Offset(); + } + if (original_remaining <= 30) { + // Decode again, one byte at a time. + DecodeBuffer input(original->cursor(), original_remaining); + VLOG(2) << "DecodeSegmentsAndValidate with SelectOne"; + VERIFY_SUCCESS( + DecodeSegmentsAndValidate(&input, base::Bind(&SelectOne), validator)) + << "\nFailed with SelectOne; input.Offset=" << input.Offset() + << "; input.Remaining=" << input.Remaining(); + VERIFY_EQ(first_consumed, input.Offset()) << "\nFailed with SelectOne"; + } + if (original_remaining <= 20) { + // Decode again, one or zero bytes at a time. + DecodeBuffer input(original->cursor(), original_remaining); + VLOG(2) << "DecodeSegmentsAndValidate with SelectZeroAndOne"; + bool zero_next = !return_non_zero_on_first; + VERIFY_SUCCESS(DecodeSegmentsAndValidate( + &input, base::Bind(&SelectZeroAndOne, &zero_next), validator)) + << "\nFailed with SelectZeroAndOne"; + VERIFY_EQ(first_consumed, input.Offset()) + << "\nFailed with SelectZeroAndOne; input.Offset=" << input.Offset() + << "; input.Remaining=" << input.Remaining(); + } + { + // Decode again, with randomly selected segment sizes. + DecodeBuffer input(original->cursor(), original_remaining); + VLOG(2) << "DecodeSegmentsAndValidate with SelectRandom"; + VERIFY_SUCCESS(DecodeSegmentsAndValidate( + &input, base::Bind(&RandomDecoderTest::SelectRandom, + base::Unretained(this), return_non_zero_on_first), + validator)) + << "\nFailed with SelectRandom; input.Offset=" << input.Offset() + << "; input.Remaining=" << input.Remaining(); + VERIFY_EQ(first_consumed, input.Offset()) << "\nFailed with SelectRandom"; + } + VERIFY_EQ(original_remaining, original->Remaining()); + original->AdvanceCursor(first_consumed); + VLOG(1) << "DecodeAndValidateSeveralWays - SUCCESS"; + return ::testing::AssertionSuccess(); +} + +// static +size_t RandomDecoderTest::SelectZeroAndOne(bool* zero_next, + bool first, + size_t offset, + size_t remaining) { + if (*zero_next) { + *zero_next = false; + return 0; + } else { + *zero_next = true; + return 1; + } +} + +size_t RandomDecoderTest::SelectRandom(bool return_non_zero_on_first, + bool first, + size_t offset, + size_t remaining) { + uint32_t r = random_.Rand32(); + if (first && return_non_zero_on_first) { + CHECK_LT(0u, remaining); + if (remaining == 1) { + return 1; + } + return 1 + (r % remaining); // size in range [1, remaining). + } + return r % (remaining + 1); // size in range [0, remaining]. +} + +uint32_t RandomDecoderTest::RandStreamId() { + return random_.Rand32() & StreamIdMask(); +} + +} // namespace test +} // namespace net
diff --git a/net/http2/tools/random_decoder_test.h b/net/http2/tools/random_decoder_test.h new file mode 100644 index 0000000..f3ab829 --- /dev/null +++ b/net/http2/tools/random_decoder_test.h
@@ -0,0 +1,264 @@ +// Copyright 2016 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 NET_HTTP2_TOOLS_RANDOM_DECODER_TEST_H_ +#define NET_HTTP2_TOOLS_RANDOM_DECODER_TEST_H_ + +// RandomDecoderTest is a base class for tests of decoding various kinds +// of HTTP/2 and HPACK encodings. + +// TODO(jamessynge): Move more methods into .cc file. + +#include <stddef.h> + +#include <memory> +#include <string> +#include <type_traits> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/strings/string_piece.h" +#include "base/template_util.h" +#include "net/http2/decoder/decode_buffer.h" +#include "net/http2/decoder/decode_status.h" +#include "net/http2/tools/failure.h" +#include "net/http2/tools/http2_random.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace test { + +// Some helpers. + +template <typename T, size_t N> +base::StringPiece ToStringPiece(T (&data)[N]) { + return base::StringPiece(reinterpret_cast<const char*>(data), N * sizeof(T)); +} + +// strings/hex_ascii_dump.h doesn't support StringPiece args for this case. +std::string HexEncode(base::StringPiece s); + +// Overwrite the enum with some random value, probably not a valid value for +// the enum type, but which fits into its storage. +template <typename T, + typename E = typename std::enable_if<std::is_enum<T>::value>::type> +void CorruptEnum(T* out, RandomBase* rng) { + // Per cppreference.com, if the destination type of a static_cast is + // smaller than the source type (i.e. type of r and uint32 below), the + // resulting value is the smallest unsigned value equal to the source value + // modulo 2^n, where n is the number of bits used to represent the + // destination type unsigned U. + typedef typename base::underlying_type<T>::type underlying_type_T; + typedef typename std::make_unsigned<underlying_type_T>::type + unsigned_underlying_type_T; + auto r = static_cast<unsigned_underlying_type_T>(rng->Rand32()); + *out = static_cast<T>(r); +} + +// Base class for tests of the ability to decode a sequence of bytes with +// various boundaries between the DecodeBuffers provided to the decoder. +class RandomDecoderTest : public ::testing::Test { + public: + // SelectSize returns the size of the next DecodeBuffer to be passed to the + // decoder. Note that RandomDecoderTest allows that size to be zero, though + // some decoders can't deal with that on the first byte, hence the |first| + // parameter. + typedef base::Callback<size_t(bool first, size_t offset, size_t remaining)> + SelectSize; + + // Validator returns an AssertionResult so test can do: + // EXPECT_THAT(DecodeAndValidate(..., validator)); + typedef ::testing::AssertionResult AssertionResult; + typedef base::Callback<AssertionResult(const DecodeBuffer& input, + DecodeStatus status)> + Validator; + typedef base::Callback<AssertionResult()> NoArgValidator; + + RandomDecoderTest(); + + protected: + // TODO(jamessynge): Modify StartDecoding, etc. to (somehow) return + // AssertionResult so that the VERIFY_* methods exported from + // gunit_helpers.h can be widely used. + + // Start decoding; call allows sub-class to Reset the decoder, or deal with + // the first byte if that is done in a unique fashion. Might be called with + // a zero byte buffer. + virtual DecodeStatus StartDecoding(DecodeBuffer* db) = 0; + + // Resume decoding of the input after a prior call to StartDecoding, and + // possibly many calls to ResumeDecoding. + virtual DecodeStatus ResumeDecoding(DecodeBuffer* db) = 0; + + // Return true if a decode status of kDecodeDone indicates that + // decoding should stop. + virtual bool StopDecodeOnDone(); + + // Decode buffer |original| until we run out of input, or kDecodeDone is + // returned by the decoder AND StopDecodeOnDone() returns true. Segments + // (i.e. cuts up) the original DecodeBuffer into (potentially) smaller buffers + // by calling |select_size| to decide how large each buffer should be. + // We do this to test the ability to deal with arbitrary boundaries, as might + // happen in transport. + // Returns the final DecodeStatus. + DecodeStatus DecodeSegments(DecodeBuffer* original, + const SelectSize& select_size); + + // Decode buffer |original| until we run out of input, or kDecodeDone is + // returned by the decoder AND StopDecodeOnDone() returns true. Segments + // (i.e. cuts up) the original DecodeBuffer into (potentially) smaller buffers + // by calling |select_size| to decide how large each buffer should be. + // We do this to test the ability to deal with arbitrary boundaries, as might + // happen in transport. + // Invokes |validator| with the final decode status and the original decode + // buffer, with the cursor advanced as far as has been consumed by the decoder + // and returns validator's result. + ::testing::AssertionResult DecodeSegmentsAndValidate( + DecodeBuffer* original, + const SelectSize& select_size, + const Validator& validator) { + DecodeStatus status = DecodeSegments(original, select_size); + VERIFY_AND_RETURN_SUCCESS(validator.Run(*original, status)); + } + + // Returns a size for fast decoding, i.e. passing all that + // is available to the decoder. + static size_t SelectRemaining(bool first, size_t offset, size_t remaining) { + return remaining; + } + + // Returns a size for decoding a single byte at a time. + static size_t SelectOne(bool first, size_t offset, size_t remaining) { + return 1; + } + + // Returns a size for decoding a single byte at a time, where + // zero byte buffers are also allowed. Alternates between zero and one. + static size_t SelectZeroAndOne(bool* zero_next, + bool first, + size_t offset, + size_t remaining); + + // Returns a size for decoding random sized segments. + size_t SelectRandom(bool return_non_zero_on_first, + bool first, + size_t offset, + size_t remaining); + + // Decode |original| multiple times, with different segmentations of the + // decode buffer, validating after each decode, and confirming that they + // each decode the same amount. Returns on the first failure, else returns + // success. + AssertionResult DecodeAndValidateSeveralWays(DecodeBuffer* original, + bool return_non_zero_on_first, + const Validator& validator); + + static AssertionResult SucceedingValidator(const DecodeBuffer& input, + DecodeStatus status) { + return ::testing::AssertionSuccess(); + } + + static Validator ToValidator(const Validator& validator) { return validator; } + + static AssertionResult RunNoArgValidator(const NoArgValidator& validator, + const DecodeBuffer& input, + DecodeStatus status) { + return validator.Run(); + } + + static Validator ToValidator(const NoArgValidator& validator) { + return base::Bind(&RunNoArgValidator, validator); + } + + // Wraps a validator with another validator + // that first checks that the DecodeStatus is kDecodeDone and + // that the DecodeBuffer is empty. + // TODO(jamessynge): Replace this overload with the next, as using this method + // usually means that the wrapped function doesn't need to be passed the + // DecodeBuffer nor the DecodeStatus. + static AssertionResult ValidateDoneAndEmptyImpl(const Validator& wrapped, + const DecodeBuffer& input, + DecodeStatus status) { + VERIFY_EQ(status, DecodeStatus::kDecodeDone); + VERIFY_EQ(0u, input.Remaining()) << "\nOffset=" << input.Offset(); + return wrapped.Run(input, status); + } + static Validator ValidateDoneAndEmpty(const Validator& wrapped) { + return base::Bind(&ValidateDoneAndEmptyImpl, wrapped); + } + static AssertionResult ValidateDoneAndEmptyNoArgImpl( + const NoArgValidator& wrapped, + const DecodeBuffer& input, + DecodeStatus status) { + VERIFY_EQ(status, DecodeStatus::kDecodeDone); + VERIFY_EQ(0u, input.Remaining()) << "\nOffset=" << input.Offset(); + return wrapped.Run(); + } + static Validator ValidateDoneAndEmpty(const NoArgValidator& wrapped) { + return base::Bind(&ValidateDoneAndEmptyNoArgImpl, wrapped); + } + static Validator ValidateDoneAndEmpty() { + return ValidateDoneAndEmpty(base::Bind(&SucceedingValidator)); + } + + // Wraps a validator with another validator + // that first checks that the DecodeStatus is kDecodeDone and + // that the DecodeBuffer has the expected offset. + // TODO(jamessynge): Replace this overload with the next, as using this method + // usually means that the wrapped function doesn't need to be passed the + // DecodeBuffer nor the DecodeStatus. + static AssertionResult ValidateDoneAndOffsetImpl(uint32_t offset, + const Validator& wrapped, + const DecodeBuffer& input, + DecodeStatus status) { + VERIFY_EQ(status, DecodeStatus::kDecodeDone); + VERIFY_EQ(offset, input.Offset()) << "\nRemaining=" << input.Remaining(); + return wrapped.Run(input, status); + } + static Validator ValidateDoneAndOffset(uint32_t offset, + const Validator& wrapped) { + // Make a copy of |wrapped| (by not using base::ConstRef) to avoid lifetime + // issues if this method is called with a temporary Validator. + return base::Bind(&ValidateDoneAndOffsetImpl, offset, wrapped); + } + static AssertionResult ValidateDoneAndOffsetNoArgImpl( + uint32_t offset, + const NoArgValidator& wrapped, + const DecodeBuffer& input, + DecodeStatus status) { + VERIFY_EQ(status, DecodeStatus::kDecodeDone); + VERIFY_EQ(offset, input.Offset()) << "\nRemaining=" << input.Remaining(); + return wrapped.Run(); + } + static Validator ValidateDoneAndOffset(uint32_t offset, + const NoArgValidator& wrapped) { + // Make a copy of |wrapped| (by not using base::ConstRef) to avoid lifetime + // issues if this method is called with a temporary Validator. + return base::Bind(&ValidateDoneAndOffsetNoArgImpl, offset, wrapped); + } + static Validator ValidateDoneAndOffset(uint32_t offset) { + // Make a copy of |wrapped| (by not using base::ConstRef) to avoid lifetime + // issues if this method is called with a temporary Validator. + return ValidateDoneAndOffset(offset, base::Bind(&SucceedingValidator)); + } + + // Expose |random_| as RandomBase so callers do not have to care about which + // sub-class of RandomBase is used, nor can they rely on the specific + // sub-class that RandomDecoderTest uses. + RandomBase& Random() { return random_; } + RandomBase* RandomPtr() { return &random_; } + + uint32_t RandStreamId(); + + bool stop_decode_on_done_ = true; + + private: + Http2Random random_; +}; + +} // namespace test +} // namespace net + +#endif // NET_HTTP2_TOOLS_RANDOM_DECODER_TEST_H_
diff --git a/net/net.gypi b/net/net.gypi index 484e83c..7fe72a64 100644 --- a/net/net.gypi +++ b/net/net.gypi
@@ -784,6 +784,69 @@ 'http/url_security_manager.h', 'http/url_security_manager_posix.cc', 'http/url_security_manager_win.cc', + 'http2/decoder/decode_buffer.cc', + 'http2/decoder/decode_buffer.h', + 'http2/decoder/decode_http2_structures.cc', + 'http2/decoder/decode_http2_structures.h', + 'http2/decoder/decode_status.cc', + 'http2/decoder/decode_status.h', + 'http2/decoder/frame_decoder_state.cc', + 'http2/decoder/frame_decoder_state.h', + 'http2/decoder/http2_frame_decoder.cc', + 'http2/decoder/http2_frame_decoder.h', + 'http2/decoder/http2_frame_decoder_listener.cc', + 'http2/decoder/http2_frame_decoder_listener.h', + 'http2/decoder/http2_structure_decoder.cc', + 'http2/decoder/http2_structure_decoder.h', + 'http2/decoder/payload_decoders/altsvc_payload_decoder.cc', + 'http2/decoder/payload_decoders/altsvc_payload_decoder.h', + 'http2/decoder/payload_decoders/continuation_payload_decoder.cc', + 'http2/decoder/payload_decoders/continuation_payload_decoder.h', + 'http2/decoder/payload_decoders/data_payload_decoder.cc', + 'http2/decoder/payload_decoders/data_payload_decoder.h', + 'http2/decoder/payload_decoders/goaway_payload_decoder.cc', + 'http2/decoder/payload_decoders/goaway_payload_decoder.h', + 'http2/decoder/payload_decoders/headers_payload_decoder.cc', + 'http2/decoder/payload_decoders/headers_payload_decoder.h', + 'http2/decoder/payload_decoders/ping_payload_decoder.cc', + 'http2/decoder/payload_decoders/ping_payload_decoder.h', + 'http2/decoder/payload_decoders/priority_payload_decoder.cc', + 'http2/decoder/payload_decoders/priority_payload_decoder.h', + 'http2/decoder/payload_decoders/push_promise_payload_decoder.cc', + 'http2/decoder/payload_decoders/push_promise_payload_decoder.h', + 'http2/decoder/payload_decoders/rst_stream_payload_decoder.cc', + 'http2/decoder/payload_decoders/rst_stream_payload_decoder.h', + 'http2/decoder/payload_decoders/settings_payload_decoder.cc', + 'http2/decoder/payload_decoders/settings_payload_decoder.h', + 'http2/decoder/payload_decoders/unknown_payload_decoder.cc', + 'http2/decoder/payload_decoders/unknown_payload_decoder.h', + 'http2/decoder/payload_decoders/window_update_payload_decoder.cc', + 'http2/decoder/payload_decoders/window_update_payload_decoder.h', + 'http2/hpack/decoder/hpack_block_decoder.cc', + 'http2/hpack/decoder/hpack_block_decoder.h', + 'http2/hpack/decoder/hpack_decoder_string_buffer.cc', + 'http2/hpack/decoder/hpack_decoder_string_buffer.h', + 'http2/hpack/decoder/hpack_entry_decoder.cc', + 'http2/hpack/decoder/hpack_entry_decoder.h', + 'http2/hpack/decoder/hpack_entry_decoder_listener.cc', + 'http2/hpack/decoder/hpack_entry_decoder_listener.h', + 'http2/hpack/decoder/hpack_entry_type_decoder.cc', + 'http2/hpack/decoder/hpack_entry_type_decoder.h', + 'http2/hpack/decoder/hpack_string_decoder.cc', + 'http2/hpack/decoder/hpack_string_decoder.h', + 'http2/hpack/decoder/hpack_string_decoder_listener.cc', + 'http2/hpack/decoder/hpack_string_decoder_listener.h', + 'http2/hpack/decoder/hpack_varint_decoder.cc', + 'http2/hpack/decoder/hpack_varint_decoder.h', + 'http2/hpack/http2_hpack_constants.cc', + 'http2/hpack/http2_hpack_constants.h', + 'http2/hpack/huffman/http2_hpack_huffman_decoder.cc', + 'http2/hpack/huffman/http2_hpack_huffman_decoder.h', + 'http2/http2_constants.cc', + 'http2/http2_constants.h', + 'http2/http2_structures.cc', + 'http2/http2_structures.h', + 'http2/tools/http2_bug_tracker.h', 'nqe/cached_network_quality.cc', 'nqe/cached_network_quality.h', 'nqe/effective_connection_type.cc', @@ -1678,6 +1741,68 @@ 'http/transport_security_persister_unittest.cc', 'http/transport_security_state_unittest.cc', 'http/url_security_manager_unittest.cc', + 'http2/decoder/decode_buffer_test.cc', + 'http2/decoder/decode_http2_structures_test.cc', + 'http2/decoder/frame_decoder_state_test_util.cc', + 'http2/decoder/frame_decoder_state_test_util.h', + 'http2/decoder/frame_parts.cc', + 'http2/decoder/frame_parts.h', + 'http2/decoder/frame_parts_collector.cc', + 'http2/decoder/frame_parts_collector.h', + 'http2/decoder/frame_parts_collector_listener.cc', + 'http2/decoder/frame_parts_collector_listener.h', + 'http2/decoder/http2_frame_decoder_listener_test_util.cc', + 'http2/decoder/http2_frame_decoder_listener_test_util.h', + 'http2/decoder/http2_frame_decoder_test.cc', + 'http2/decoder/http2_structure_decoder_test.cc', + 'http2/decoder/http2_structure_decoder_test_util.h', + 'http2/decoder/payload_decoders/altsvc_payload_decoder_test.cc', + 'http2/decoder/payload_decoders/continuation_payload_decoder_test.cc', + 'http2/decoder/payload_decoders/data_payload_decoder_test.cc', + 'http2/decoder/payload_decoders/goaway_payload_decoder_test.cc', + 'http2/decoder/payload_decoders/headers_payload_decoder_test.cc', + 'http2/decoder/payload_decoders/payload_decoder_base_test_util.cc', + 'http2/decoder/payload_decoders/payload_decoder_base_test_util.h', + 'http2/decoder/payload_decoders/ping_payload_decoder_test.cc', + 'http2/decoder/payload_decoders/priority_payload_decoder_test.cc', + 'http2/decoder/payload_decoders/push_promise_payload_decoder_test.cc', + 'http2/decoder/payload_decoders/rst_stream_payload_decoder_test.cc', + 'http2/decoder/payload_decoders/settings_payload_decoder_test.cc', + 'http2/decoder/payload_decoders/unknown_payload_decoder_test.cc', + 'http2/decoder/payload_decoders/window_update_payload_decoder_test.cc', + 'http2/hpack/decoder/hpack_block_collector.cc', + 'http2/hpack/decoder/hpack_block_collector.h', + 'http2/hpack/decoder/hpack_block_decoder_test.cc', + 'http2/hpack/decoder/hpack_decoder_string_buffer_test.cc', + 'http2/hpack/decoder/hpack_entry_collector.cc', + 'http2/hpack/decoder/hpack_entry_collector.h', + 'http2/hpack/decoder/hpack_entry_decoder_test.cc', + 'http2/hpack/decoder/hpack_entry_type_decoder_test.cc', + 'http2/hpack/decoder/hpack_string_collector.cc', + 'http2/hpack/decoder/hpack_string_collector.h', + 'http2/hpack/decoder/hpack_string_decoder_test.cc', + 'http2/hpack/decoder/hpack_varint_decoder_test.cc', + 'http2/hpack/http2_hpack_constants_test.cc', + 'http2/hpack/huffman/http2_hpack_huffman_decoder_test.cc', + 'http2/hpack/tools/hpack_block_builder.cc', + 'http2/hpack/tools/hpack_block_builder.h', + 'http2/hpack/tools/hpack_block_builder_test.cc', + 'http2/hpack/tools/hpack_example.cc', + 'http2/hpack/tools/hpack_example.h', + 'http2/http2_constants_test.cc', + 'http2/http2_constants_test_util.cc', + 'http2/http2_constants_test_util.h', + 'http2/http2_structures_test.cc', + 'http2/http2_structures_test_util.cc', + 'http2/http2_structures_test_util.h', + 'http2/tools/failure.cc', + 'http2/tools/failure.h', + 'http2/tools/http2_frame_builder.cc', + 'http2/tools/http2_frame_builder.h', + 'http2/tools/http2_random.cc', + 'http2/tools/http2_random.h', + 'http2/tools/random_decoder_test.cc', + 'http2/tools/random_decoder_test.h', 'log/bounded_file_net_log_observer_unittest.cc', 'log/net_log_capture_mode_unittest.cc', 'log/net_log_unittest.cc',
diff --git a/remoting/test/chromoting_test_driver_environment.h b/remoting/test/chromoting_test_driver_environment.h index 369a4e7..df2cb21e 100644 --- a/remoting/test/chromoting_test_driver_environment.h +++ b/remoting/test/chromoting_test_driver_environment.h
@@ -40,7 +40,7 @@ std::string host_jid; std::string pin; base::FilePath refresh_token_file_path; - bool use_test_environment; + bool use_test_environment = false; }; explicit ChromotingTestDriverEnvironment(const EnvironmentOptions& options);
diff --git a/services/service_manager/service_manager.cc b/services/service_manager/service_manager.cc index 9a31773..dab2bd7 100644 --- a/services/service_manager/service_manager.cc +++ b/services/service_manager/service_manager.cc
@@ -10,7 +10,6 @@ #include "base/bind.h" #include "base/command_line.h" -#include "base/debug/alias.h" #include "base/guid.h" #include "base/logging.h" #include "base/macros.h" @@ -928,19 +927,8 @@ package_path = result->package_path; } - Identity source_instance_identity; - base::debug::Alias(&has_source_instance); - base::debug::Alias(&package_path); - base::debug::Alias(&source); - base::debug::Alias(&target); - if (source_instance) - source_instance_identity = source_instance->identity(); - base::debug::Alias(&source_instance_identity); -#if defined(GOOGLE_CHROME_BUILD) - // We do not currently want to hit this code path in production, but it's - // happening somehow. https://crbug.com/649673. - CHECK(false); -#endif + // TODO(rockot): Find a way to block this code path for content but allow + // it for chrome_mash. instance->StartWithFilePath(package_path); } }
diff --git a/services/ui/demo/BUILD.gn b/services/ui/demo/BUILD.gn index 5a4e2bb..85f03db 100644 --- a/services/ui/demo/BUILD.gn +++ b/services/ui/demo/BUILD.gn
@@ -8,8 +8,6 @@ source_set("lib") { sources = [ - "bitmap_uploader.cc", - "bitmap_uploader.h", "mus_demo.cc", "mus_demo.h", ] @@ -26,7 +24,10 @@ "//services/ui/public/cpp", "//services/ui/public/cpp:internal", "//services/ui/public/interfaces", + "//ui/aura", + "//ui/aura_extra", "//ui/gfx/geometry", + "//ui/wm", ] }
diff --git a/services/ui/demo/DEPS b/services/ui/demo/DEPS index a968818dd..e23d3e58 100644 --- a/services/ui/demo/DEPS +++ b/services/ui/demo/DEPS
@@ -1,5 +1,8 @@ include_rules = [ "+gpu/GLES2", "+services/ui/public/cpp", + "+ui/aura", + "+ui/aura_extra", + "+ui/wm", ]
diff --git a/services/ui/demo/bitmap_uploader.cc b/services/ui/demo/bitmap_uploader.cc deleted file mode 100644 index b208ed8..0000000 --- a/services/ui/demo/bitmap_uploader.cc +++ /dev/null
@@ -1,243 +0,0 @@ -// Copyright 2015 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "services/ui/demo/bitmap_uploader.h" - -#include <stddef.h> -#include <utility> - -#include "base/bind.h" -#include "base/callback.h" -#include "base/threading/thread_task_runner_handle.h" -#include "cc/ipc/compositor_frame.mojom.h" -#include "cc/quads/render_pass.h" -#include "cc/quads/solid_color_draw_quad.h" -#include "cc/quads/texture_draw_quad.h" -#include "services/ui/public/cpp/context_provider.h" -#include "services/ui/public/cpp/gles2_context.h" -#include "services/ui/public/cpp/gpu/gpu_service.h" -#include "services/ui/public/cpp/window.h" - -namespace ui { -namespace { - -const uint32_t g_transparent_color = 0x00000000; - -} // namespace - -const char kBitmapUploaderForAcceleratedWidget[] = - "__BITMAP_UPLOADER_ACCELERATED_WIDGET__"; - -BitmapUploader::BitmapUploader(Window* window) - : window_(window), - color_(g_transparent_color), - width_(0), - height_(0), - format_(BGRA), - next_resource_id_(1u), - weak_factory_(this) {} - -void BitmapUploader::Init(ui::GpuService* gpu_service) { - gpu_service->EstablishGpuChannel(base::Bind( - &BitmapUploader::OnGpuChannelEstablished, weak_factory_.GetWeakPtr(), - gpu_service->gpu_memory_buffer_manager())); -} - -BitmapUploader::~BitmapUploader() { - compositor_frame_sink_->DetachFromClient(); -} - -// Sets the color which is RGBA. -void BitmapUploader::SetColor(uint32_t color) { - if (color_ == color) - return; - color_ = color; - if (compositor_frame_sink_) - Upload(); -} - -// Sets a bitmap. -void BitmapUploader::SetBitmap(int width, - int height, - std::unique_ptr<std::vector<unsigned char>> data, - Format format) { - width_ = width; - height_ = height; - bitmap_ = std::move(data); - format_ = format; - if (compositor_frame_sink_) - Upload(); -} - -void BitmapUploader::Upload() { - if (!compositor_frame_sink_ || !compositor_frame_sink_->context_provider()) - return; - - const gfx::Rect bounds(window_->bounds().size()); - - cc::CompositorFrame frame; - // TODO(rjkroege): Support device scale factors other than 1. - frame.metadata.device_scale_factor = 1.0f; - frame.resource_list.resize(0u); - - const cc::RenderPassId render_pass_id(1, 1); - std::unique_ptr<cc::RenderPass> pass = cc::RenderPass::Create(); - pass->SetAll(render_pass_id, bounds, bounds, gfx::Transform(), - true /* has_transparent_background */); - - // The SharedQuadState is owned by the SharedQuadStateList - // shared_quad_state_list. - cc::SharedQuadState* sqs = pass->CreateAndAppendSharedQuadState(); - sqs->SetAll(gfx::Transform(), bounds.size(), bounds, bounds, - false /* is_clipped */, 1.f /* opacity */, SkBlendMode::kSrc, - 0 /* sorting_context_id */); - - if (bitmap_.get()) { - gpu::gles2::GLES2Interface* gl = - compositor_frame_sink_->context_provider()->ContextGL(); - gfx::Size bitmap_size(width_, height_); - GLuint texture_id = BindTextureForSize(bitmap_size); - gl->TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap_size.width(), - bitmap_size.height(), TextureFormat(), GL_UNSIGNED_BYTE, - &((*bitmap_)[0])); - - gpu::Mailbox mailbox; - gl->GenMailboxCHROMIUM(mailbox.name); - gl->ProduceTextureCHROMIUM(GL_TEXTURE_2D, mailbox.name); - - const GLuint64 fence_sync = gl->InsertFenceSyncCHROMIUM(); - gl->ShallowFlushCHROMIUM(); - - gpu::SyncToken sync_token; - gl->GenSyncTokenCHROMIUM(fence_sync, sync_token.GetData()); - - cc::TransferableResource resource; - resource.id = next_resource_id_++; - resource_to_texture_id_map_[resource.id] = texture_id; - resource.format = cc::ResourceFormat::RGBA_8888; - resource.filter = GL_LINEAR; - resource.size = bitmap_size; - resource.mailbox_holder = - gpu::MailboxHolder(mailbox, sync_token, GL_TEXTURE_2D); - resource.read_lock_fences_enabled = false; - resource.is_software = false; - resource.is_overlay_candidate = false; - frame.resource_list.push_back(std::move(resource)); - - cc::TextureDrawQuad* quad = - pass->CreateAndAppendDrawQuad<cc::TextureDrawQuad>(); - - gfx::Size rect_size; - if (width_ <= bounds.width() && height_ <= bounds.height()) { - rect_size.SetSize(width_, height_); - } else { - // The source bitmap is larger than the viewport. Resize it while - // maintaining the aspect ratio. - float width_ratio = static_cast<float>(width_) / bounds.width(); - float height_ratio = static_cast<float>(height_) / bounds.height(); - if (width_ratio > height_ratio) { - rect_size.SetSize(bounds.width(), height_ / width_ratio); - } else { - rect_size.SetSize(width_ / height_ratio, bounds.height()); - } - } - gfx::Rect rect(rect_size); - const bool needs_blending = true; - const bool premultiplied_alpha = true; - const gfx::PointF uv_top_left(0.f, 0.f); - const gfx::PointF uv_bottom_right(1.f, 1.f); - float vertex_opacity[4] = {1.f, 1.f, 1.f, 1.f}; - const bool y_flipped = false; - const bool nearest_neighbor = false; - quad->SetAll(sqs, rect, rect, rect, needs_blending, resource.id, - gfx::Size(), premultiplied_alpha, uv_top_left, uv_bottom_right, - g_transparent_color, vertex_opacity, y_flipped, - nearest_neighbor, false); - } - - if (color_ != g_transparent_color) { - cc::SolidColorDrawQuad* quad = - pass->CreateAndAppendDrawQuad<cc::SolidColorDrawQuad>(); - const bool force_antialiasing_off = false; - const gfx::Rect opaque_rect(0, 0, 0, 0); - const bool needs_blending = true; - quad->SetAll(sqs, bounds, opaque_rect, bounds, needs_blending, color_, - force_antialiasing_off); - } - - frame.render_pass_list.push_back(std::move(pass)); - - // TODO(rjkroege, fsamuel): We should throttle frames. - compositor_frame_sink_->SubmitCompositorFrame(std::move(frame)); -} - -void BitmapUploader::OnGpuChannelEstablished( - gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager, - scoped_refptr<gpu::GpuChannelHost> gpu_channel) { - compositor_frame_sink_ = window_->RequestCompositorFrameSink( - mojom::CompositorFrameSinkType::DEFAULT, - new ContextProvider(std::move(gpu_channel)), gpu_memory_buffer_manager); - compositor_frame_sink_->BindToClient(this); -} - -uint32_t BitmapUploader::BindTextureForSize(const gfx::Size& size) { - gpu::gles2::GLES2Interface* gl = - compositor_frame_sink_->context_provider()->ContextGL(); - // TODO(jamesr): Recycle textures. - GLuint texture = 0u; - gl->GenTextures(1, &texture); - gl->BindTexture(GL_TEXTURE_2D, texture); - gl->TexImage2D(GL_TEXTURE_2D, 0, TextureFormat(), size.width(), size.height(), - 0, TextureFormat(), GL_UNSIGNED_BYTE, 0); - gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - return texture; -} - -void BitmapUploader::SetBeginFrameSource(cc::BeginFrameSource* source) {} - -void BitmapUploader::ReclaimResources( - const cc::ReturnedResourceArray& resources) { - gpu::gles2::GLES2Interface* gl = - compositor_frame_sink_->context_provider()->ContextGL(); - // TODO(jamesr): Recycle. - for (size_t i = 0; i < resources.size(); ++i) { - cc::ReturnedResource resource = std::move(resources[i]); - DCHECK_EQ(1, resource.count); - gl->WaitSyncTokenCHROMIUM(resource.sync_token.GetConstData()); - uint32_t texture_id = resource_to_texture_id_map_[resource.id]; - DCHECK_NE(0u, texture_id); - resource_to_texture_id_map_.erase(resource.id); - gl->DeleteTextures(1, &texture_id); - } -} - -void BitmapUploader::SetTreeActivationCallback(const base::Closure& callback) { - // TODO(fsamuel): Implement this. -} - -void BitmapUploader::DidReceiveCompositorFrameAck() { - // TODO(fsamuel): Implement this. -} - -void BitmapUploader::DidLoseCompositorFrameSink() { - // TODO(fsamuel): Implement this. -} - -void BitmapUploader::OnDraw(const gfx::Transform& transform, - const gfx::Rect& viewport, - bool resourceless_software_draw) { - // TODO(fsamuel): Implement this. -} - -void BitmapUploader::SetMemoryPolicy(const cc::ManagedMemoryPolicy& policy) { - // TODO(fsamuel): Implement this. -} - -void BitmapUploader::SetExternalTilePriorityConstraints( - const gfx::Rect& viewport_rect, - const gfx::Transform& transform) { - // TODO(fsamuel): Implement this. -} - -} // namespace ui
diff --git a/services/ui/demo/bitmap_uploader.h b/services/ui/demo/bitmap_uploader.h deleted file mode 100644 index 7a22bb0..0000000 --- a/services/ui/demo/bitmap_uploader.h +++ /dev/null
@@ -1,101 +0,0 @@ -// Copyright 2015 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef SERVICES_UI_DEMO_BITMAP_UPLOADER_H_ -#define SERVICES_UI_DEMO_BITMAP_UPLOADER_H_ - -#include <stdint.h> - -#include <memory> -#include <unordered_map> - -#include "base/compiler_specific.h" -#include "base/macros.h" -#include "base/memory/weak_ptr.h" -#include "cc/output/compositor_frame_sink_client.h" -#include "gpu/GLES2/gl2chromium.h" -#include "gpu/GLES2/gl2extchromium.h" -#include "services/ui/public/cpp/window_compositor_frame_sink.h" - -namespace gpu { -class GpuChannelHost; -} - -namespace ui { - -class GpuService; -class Window; - -extern const char kBitmapUploaderForAcceleratedWidget[]; - -// BitmapUploader is useful if you want to draw a bitmap or color in a -// Window. -class BitmapUploader : public cc::CompositorFrameSinkClient { - public: - explicit BitmapUploader(Window* window); - ~BitmapUploader() override; - - void Init(GpuService* gpu_service); - // Sets the color which is RGBA. - void SetColor(uint32_t color); - - enum Format { - RGBA, // Pixel layout on Android. - BGRA, // Pixel layout everywhere else. - }; - - // Sets a bitmap. - void SetBitmap(int width, - int height, - std::unique_ptr<std::vector<unsigned char>> data, - Format format); - - private: - void Upload(); - - void OnGpuChannelEstablished( - gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager, - scoped_refptr<gpu::GpuChannelHost> gpu_channel); - - uint32_t BindTextureForSize(const gfx::Size& size); - - uint32_t TextureFormat() const { - return format_ == BGRA ? GL_BGRA_EXT : GL_RGBA; - } - - void SetIdNamespace(uint32_t id_namespace); - - // cc::CompositorFrameSinkClient implementation. - void SetBeginFrameSource(cc::BeginFrameSource* source) override; - void ReclaimResources(const cc::ReturnedResourceArray& resources) override; - void SetTreeActivationCallback(const base::Closure& callback) override; - void DidReceiveCompositorFrameAck() override; - void DidLoseCompositorFrameSink() override; - void OnDraw(const gfx::Transform& transform, - const gfx::Rect& viewport, - bool resourceless_software_draw) override; - void SetMemoryPolicy(const cc::ManagedMemoryPolicy& policy) override; - void SetExternalTilePriorityConstraints( - const gfx::Rect& viewport_rect, - const gfx::Transform& transform) override; - - Window* window_; - std::unique_ptr<WindowCompositorFrameSink> compositor_frame_sink_; - - uint32_t color_; - int width_; - int height_; - Format format_; - std::unique_ptr<std::vector<unsigned char>> bitmap_; - uint32_t next_resource_id_; - std::unordered_map<uint32_t, uint32_t> resource_to_texture_id_map_; - - base::WeakPtrFactory<BitmapUploader> weak_factory_; - - DISALLOW_COPY_AND_ASSIGN(BitmapUploader); -}; - -} // namespace ui - -#endif // SERVICES_UI_DEMO_BITMAP_UPLOADER_H_
diff --git a/services/ui/demo/mus_demo.cc b/services/ui/demo/mus_demo.cc index d0a90bb..9520f24 100644 --- a/services/ui/demo/mus_demo.cc +++ b/services/ui/demo/mus_demo.cc
@@ -8,16 +8,23 @@ #include "base/time/time.h" #include "services/service_manager/public/cpp/connector.h" #include "services/service_manager/public/cpp/service_context.h" -#include "services/ui/demo/bitmap_uploader.h" #include "services/ui/public/cpp/gpu/gpu_service.h" -#include "services/ui/public/cpp/window.h" -#include "services/ui/public/cpp/window_tree_client.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkColor.h" #include "third_party/skia/include/core/SkImageInfo.h" #include "third_party/skia/include/core/SkPaint.h" #include "third_party/skia/include/core/SkRect.h" +#include "ui/aura/client/default_capture_client.h" +#include "ui/aura/env.h" +#include "ui/aura/mus/mus_context_factory.h" +#include "ui/aura/mus/property_converter.h" +#include "ui/aura/mus/window_tree_client.h" +#include "ui/aura/mus/window_tree_host_mus.h" +#include "ui/aura/window.h" +#include "ui/aura_extra/image_window_delegate.h" #include "ui/gfx/geometry/rect.h" +#include "ui/gfx/image/image.h" +#include "ui/wm/core/wm_state.h" namespace ui { namespace demo { @@ -69,9 +76,22 @@ void MusDemo::OnStart() { screen_ = base::MakeUnique<display::ScreenBase>(); display::Screen::SetScreenInstance(screen_.get()); + + env_ = aura::Env::CreateInstance(aura::Env::Mode::MUS); + capture_client_ = base::MakeUnique<aura::client::DefaultCaptureClient>(); + property_converter_ = base::MakeUnique<aura::PropertyConverter>(); + wm_state_ = base::MakeUnique<::wm::WMState>(); + gpu_service_ = GpuService::Create(context()->connector()); - window_tree_client_ = base::MakeUnique<WindowTreeClient>(this, this); - window_tree_client_->ConnectAsWindowManager(context()->connector()); + context_factory_ = + base::MakeUnique<aura::MusContextFactory>(gpu_service_.get()); + env_->set_context_factory(context_factory_.get()); + + window_tree_client_ = base::MakeUnique<aura::WindowTreeClient>( + context()->connector(), this, this); + window_tree_client_->ConnectAsWindowManager(); + + env_->SetWindowTreeClient(window_tree_client_.get()); } bool MusDemo::OnConnect(const service_manager::ServiceInfo& remote_info, @@ -79,55 +99,88 @@ return true; } -void MusDemo::OnEmbed(Window* window) { +void MusDemo::OnEmbed( + std::unique_ptr<aura::WindowTreeHostMus> window_tree_host) { // Not called for the WindowManager. NOTREACHED(); } -void MusDemo::OnEmbedRootDestroyed(Window* root) { +void MusDemo::OnUnembed(aura::Window* root) { + NOTREACHED(); +} + +void MusDemo::OnEmbedRootDestroyed(aura::Window* root) { // Not called for the WindowManager. NOTREACHED(); } -void MusDemo::OnLostConnection(WindowTreeClient* client) { - window_ = nullptr; +void MusDemo::OnLostConnection(aura::WindowTreeClient* client) { + root_window_ = nullptr; window_tree_client_.reset(); timer_.Stop(); } void MusDemo::OnPointerEventObserved(const PointerEvent& event, - Window* target) {} + aura::Window* target) {} -void MusDemo::SetWindowManagerClient(WindowManagerClient* client) {} +aura::client::CaptureClient* MusDemo::GetCaptureClient() { + return capture_client_.get(); +} -bool MusDemo::OnWmSetBounds(Window* window, gfx::Rect* bounds) { +aura::PropertyConverter* MusDemo::GetPropertyConverter() { + return property_converter_.get(); +} + +void MusDemo::SetWindowManagerClient(aura::WindowManagerClient* client) {} + +bool MusDemo::OnWmSetBounds(aura::Window* window, gfx::Rect* bounds) { return true; } -bool MusDemo::OnWmSetProperty(Window* window, +bool MusDemo::OnWmSetProperty(aura::Window* window, const std::string& name, std::unique_ptr<std::vector<uint8_t>>* new_data) { return true; } -Window* MusDemo::OnWmCreateTopLevelWindow( +aura::Window* MusDemo::OnWmCreateTopLevelWindow( + mojom::WindowType window_type, std::map<std::string, std::vector<uint8_t>>* properties) { + NOTREACHED(); return nullptr; } void MusDemo::OnWmClientJankinessChanged( - const std::set<Window*>& client_windows, + const std::set<aura::Window*>& client_windows, bool janky) { // Don't care } -void MusDemo::OnWmNewDisplay(Window* window, const display::Display& display) { - DCHECK(!window_); // Only support one display. - window_ = window; +void MusDemo::OnWmWillCreateDisplay(const display::Display& display) { + screen_->display_list().AddDisplay(display, + display::DisplayList::Type::PRIMARY); +} - // Initialize bitmap uploader for sending frames to MUS. - uploader_.reset(new BitmapUploader(window_)); - uploader_->Init(gpu_service_.get()); +void MusDemo::OnWmNewDisplay( + std::unique_ptr<aura::WindowTreeHostMus> window_tree_host, + const display::Display& display) { + DCHECK(!root_window_); // Only support one display. + + window_tree_host->InitHost(); + window_tree_host->Show(); + root_window_ = window_tree_host->window(); + // Take ownership of the WTH. + window_tree_host_ = std::move(window_tree_host); + + // Initialize the window for the bitmap. + window_delegate_ = new aura_extra::ImageWindowDelegate(); + bitmap_window_ = base::MakeUnique<aura::Window>(window_delegate_); + bitmap_window_->Init(LAYER_TEXTURED); + bitmap_window_->SetBounds(root_window_->bounds()); + bitmap_window_->Show(); + bitmap_window_->SetName("Bitmap"); + + root_window_->AddChild(bitmap_window_.get()); // Draw initial frame and start the timer to regularly draw frames. DrawFrame(); @@ -135,30 +188,31 @@ base::Bind(&MusDemo::DrawFrame, base::Unretained(this))); } -void MusDemo::OnWmDisplayRemoved(ui::Window* window) { - window->Destroy(); +void MusDemo::OnWmDisplayRemoved(aura::WindowTreeHostMus* window_tree_host) { + timer_.Stop(); + root_window_->RemoveChild(bitmap_window_.get()); + bitmap_window_.reset(); } void MusDemo::OnWmDisplayModified(const display::Display& display) {} -void MusDemo::OnWmPerformMoveLoop(Window* window, +mojom::EventResult MusDemo::OnAccelerator(uint32_t id, const Event& event) { + return mojom::EventResult::UNHANDLED; +} + +void MusDemo::OnWmPerformMoveLoop(aura::Window* window, mojom::MoveLoopSource source, const gfx::Point& cursor_location, const base::Callback<void(bool)>& on_done) { // Don't care } -void MusDemo::OnWmCancelMoveLoop(Window* window) {} +void MusDemo::OnWmCancelMoveLoop(aura::Window* window) {} -void MusDemo::AllocBitmap() { - const gfx::Rect bounds = window_->GetBoundsInRoot(); - - // Allocate bitmap the same size as the window for drawing. - bitmap_.reset(); - SkImageInfo image_info = SkImageInfo::MakeN32(bounds.width(), bounds.height(), - kPremul_SkAlphaType); - bitmap_.allocPixels(image_info); -} +void MusDemo::OnWmSetClientArea( + aura::Window* window, + const gfx::Insets& insets, + const std::vector<gfx::Rect>& additional_client_areas) {} void MusDemo::DrawFrame() { base::TimeTicks now = base::TimeTicks::Now(); @@ -171,13 +225,12 @@ if (angle_ >= 360.0) angle_ = 0.0; - const gfx::Rect bounds = window_->GetBoundsInRoot(); - - // Check that bitmap and window sizes match, otherwise reallocate bitmap. - const SkImageInfo info = bitmap_.info(); - if (info.width() != bounds.width() || info.height() != bounds.height()) { - AllocBitmap(); - } + // Re-initialize the bitmap + bitmap_.reset(); + const gfx::Rect bounds = bitmap_window_->bounds(); + SkImageInfo image_info = SkImageInfo::MakeN32(bounds.width(), bounds.height(), + kPremul_SkAlphaType); + bitmap_.allocPixels(image_info); // Draw the rotated square on background in bitmap. SkCanvas canvas(bitmap_); @@ -186,28 +239,13 @@ DrawSquare(bounds, angle_, &canvas); canvas.flush(); - // Copy pixels data into vector that will be passed to BitmapUploader. - // TODO(rjkroege): Make a 1/0-copy bitmap uploader for the contents of a - // SkBitmap. - bitmap_.lockPixels(); - const unsigned char* addr = - static_cast<const unsigned char*>(bitmap_.getPixels()); - const int bytes = bounds.width() * bounds.height() * 4; - std::unique_ptr<std::vector<unsigned char>> data( - new std::vector<unsigned char>(addr, addr + bytes)); - bitmap_.unlockPixels(); + gfx::ImageSkiaRep image_skia_rep(bitmap_, 1); + gfx::ImageSkia image_skia(image_skia_rep); + gfx::Image image(image_skia); -#if defined(OS_ANDROID) - // TODO(jcivelli): find a way to not have an ifdef here. - BitmapUploader::Format bitmap_format = BitmapUploader::RGBA; -#else - BitmapUploader::Format bitmap_format = BitmapUploader::BGRA; -#endif - - // Send frame to MUS via BitmapUploader. - uploader_->SetBitmap(bounds.width(), bounds.height(), std::move(data), - bitmap_format); + window_delegate_->SetImage(image); + bitmap_window_->SchedulePaintInRect(bitmap_window_->bounds()); } } // namespace demo -} // namespace ui +} // namespace aura
diff --git a/services/ui/demo/mus_demo.h b/services/ui/demo/mus_demo.h index 941b238..e253509 100644 --- a/services/ui/demo/mus_demo.h +++ b/services/ui/demo/mus_demo.h
@@ -15,23 +15,41 @@ #include "base/macros.h" #include "base/timer/timer.h" #include "services/service_manager/public/cpp/service.h" -#include "services/ui/public/cpp/window_manager_delegate.h" -#include "services/ui/public/cpp/window_tree_client_delegate.h" #include "third_party/skia/include/core/SkBitmap.h" +#include "ui/aura/mus/window_manager_delegate.h" +#include "ui/aura/mus/window_tree_client_delegate.h" #include "ui/display/screen_base.h" +namespace aura { +class Env; +class PropertyConverter; + +namespace client { +class DefaultCaptureClient; +} +} // namespace aura + +namespace aura_extra { +class ImageWindowDelegate; +} + +namespace wm { +class WMState; +} + namespace ui { -class BitmapUploader; +class ContextFactory; class GpuService; namespace demo { -// A simple MUS Demo service. This service connects to the service:ui, creates a -// new window and draws a spinning square in the center of the window. Provides -// a simple way to demonstrate that the graphic stack works as intended. +// A simple MUS Demo service. This service connects to the service:ui, adds a +// new window to the root Window, and draws a spinning square in the center of +// the window. Provides a simple way to demonstrate that the graphic stack works +// as intended. class MusDemo : public service_manager::Service, - public WindowTreeClientDelegate, - public WindowManagerDelegate { + public aura::WindowTreeClientDelegate, + public aura::WindowManagerDelegate { public: MusDemo(); ~MusDemo() override; @@ -42,48 +60,66 @@ bool OnConnect(const service_manager::ServiceInfo& remote_info, service_manager::InterfaceRegistry* registry) override; - // WindowTreeClientDelegate: - void OnEmbed(Window* root) override; - void OnEmbedRootDestroyed(Window* root) override; - void OnLostConnection(WindowTreeClient* client) override; - void OnPointerEventObserved(const PointerEvent& event, - Window* target) override; + // aura::WindowTreeClientDelegate: + void OnEmbed( + std::unique_ptr<aura::WindowTreeHostMus> window_tree_host) override; + void OnUnembed(aura::Window* root) override; + void OnEmbedRootDestroyed(aura::Window* root) override; + void OnLostConnection(aura::WindowTreeClient* client) override; + void OnPointerEventObserved(const ui::PointerEvent& event, + aura::Window* target) override; + aura::client::CaptureClient* GetCaptureClient() override; + aura::PropertyConverter* GetPropertyConverter() override; - // WindowManagerDelegate: - void SetWindowManagerClient(WindowManagerClient* client) override; - bool OnWmSetBounds(Window* window, gfx::Rect* bounds) override; + // aura::WindowManagerDelegate: + void SetWindowManagerClient(aura::WindowManagerClient* client) override; + bool OnWmSetBounds(aura::Window* window, gfx::Rect* bounds) override; bool OnWmSetProperty( - Window* window, + aura::Window* window, const std::string& name, std::unique_ptr<std::vector<uint8_t>>* new_data) override; - Window* OnWmCreateTopLevelWindow( + aura::Window* OnWmCreateTopLevelWindow( + ui::mojom::WindowType window_type, std::map<std::string, std::vector<uint8_t>>* properties) override; - void OnWmClientJankinessChanged(const std::set<Window*>& client_windows, + void OnWmClientJankinessChanged(const std::set<aura::Window*>& client_windows, bool janky) override; - void OnWmNewDisplay(Window* window, const display::Display& display) override; - void OnWmDisplayRemoved(ui::Window* window) override; + void OnWmWillCreateDisplay(const display::Display& display) override; + void OnWmNewDisplay(std::unique_ptr<aura::WindowTreeHostMus> window_tree_host, + const display::Display& display) override; + void OnWmDisplayRemoved(aura::WindowTreeHostMus* window_tree_host) override; void OnWmDisplayModified(const display::Display& display) override; - void OnWmPerformMoveLoop(Window* window, - mojom::MoveLoopSource source, + ui::mojom::EventResult OnAccelerator(uint32_t id, + const ui::Event& event) override; + void OnWmPerformMoveLoop(aura::Window* window, + ui::mojom::MoveLoopSource source, const gfx::Point& cursor_location, const base::Callback<void(bool)>& on_done) override; - void OnWmCancelMoveLoop(Window* window) override; - - // Allocate a bitmap the same size as the window to draw into. - void AllocBitmap(); + void OnWmCancelMoveLoop(aura::Window* window) override; + void OnWmSetClientArea( + aura::Window* window, + const gfx::Insets& insets, + const std::vector<gfx::Rect>& additional_client_areas) override; // Draws one frame, incrementing the rotation angle. void DrawFrame(); - Window* window_ = nullptr; - std::unique_ptr<WindowTreeClient> window_tree_client_; - std::unique_ptr<GpuService> gpu_service_; - - // Dummy screen required to be the screen instance. + aura::Window* root_window_ = nullptr; + std::unique_ptr<aura::WindowTreeClient> window_tree_client_; + std::unique_ptr<aura::WindowTreeHostMus> window_tree_host_; + std::unique_ptr<ui::GpuService> gpu_service_; + std::unique_ptr<ui::ContextFactory> context_factory_; + std::unique_ptr<aura::Env> env_; std::unique_ptr<display::ScreenBase> screen_; - // Used to send frames to mus. - std::unique_ptr<BitmapUploader> uploader_; + std::unique_ptr<aura::client::DefaultCaptureClient> capture_client_; + std::unique_ptr<::wm::WMState> wm_state_; + std::unique_ptr<aura::PropertyConverter> property_converter_; + + // Window to which we draw the bitmap. + std::unique_ptr<aura::Window> bitmap_window_; + + // Destroys itself when the window gets destroyed. + aura_extra::ImageWindowDelegate* window_delegate_ = nullptr; // Bitmap that is the same size as our client window area. SkBitmap bitmap_; @@ -101,6 +137,6 @@ }; } // namespace demo -} // namespace ui +} // namespace aura #endif // SERVICES_UI_DEMO_MUS_DEMO_H_
diff --git a/services/ui/ime/test_ime_driver/manifest.json b/services/ui/ime/test_ime_driver/manifest.json index 3211013..7e980eb 100644 --- a/services/ui/ime/test_ime_driver/manifest.json +++ b/services/ui/ime/test_ime_driver/manifest.json
@@ -3,7 +3,7 @@ "display_name": "Test IME Driver", "interface_provider_specs": { "service_manager:connector": { - "provides": { "ime:test_ime_driver": [ "ui::mojom::IMEDriver" ] }, + "provides": { "ime:test_ime_driver": [] }, "requires": { "ui": [ "ui:ime_registrar" ] }
diff --git a/skia/config/SkUserConfig.h b/skia/config/SkUserConfig.h index c6e914a..b37dad0 100644 --- a/skia/config/SkUserConfig.h +++ b/skia/config/SkUserConfig.h
@@ -201,10 +201,6 @@ # define SK_SUPPORT_LEGACY_GETDEVICE #endif -#ifndef SK_SUPPORT_LEGACY_CLIP_REGIONOPS -#define SK_SUPPORT_LEGACY_CLIP_REGIONOPS -#endif - // Workaround for poor anisotropic mipmap quality, // pending Skia ripmap support. // (https://bugs.chromium.org/p/skia/issues/detail?id=4863)
diff --git a/skia/ext/analysis_canvas.cc b/skia/ext/analysis_canvas.cc index 35e76d0..bed72531 100644 --- a/skia/ext/analysis_canvas.cc +++ b/skia/ext/analysis_canvas.cc
@@ -391,13 +391,13 @@ } void AnalysisCanvas::onClipRect(const SkRect& rect, - SkRegion::Op op, + SkClipOp op, ClipEdgeStyle edge_style) { INHERITED::onClipRect(rect, op, edge_style); } void AnalysisCanvas::onClipPath(const SkPath& path, - SkRegion::Op op, + SkClipOp op, ClipEdgeStyle edge_style) { OnComplexClip(); INHERITED::onClipRect(path.getBounds(), op, edge_style); @@ -424,7 +424,7 @@ } void AnalysisCanvas::onClipRRect(const SkRRect& rrect, - SkRegion::Op op, + SkClipOp op, ClipEdgeStyle edge_style) { SkIRect clip_device_bounds; if (getClipDeviceBounds(&clip_device_bounds) && @@ -438,7 +438,7 @@ INHERITED::onClipRect(rrect.getBounds(), op, edge_style); } -void AnalysisCanvas::onClipRegion(const SkRegion& deviceRgn, SkRegion::Op op) { +void AnalysisCanvas::onClipRegion(const SkRegion& deviceRgn, SkClipOp op) { const ClipEdgeStyle edge_style = kHard_ClipEdgeStyle; if (deviceRgn.isRect()) { onClipRect(SkRect::MakeFromIRect(deviceRgn.getBounds()), op, edge_style);
diff --git a/skia/ext/analysis_canvas.h b/skia/ext/analysis_canvas.h index f13bfad4..a9aeb96 100644 --- a/skia/ext/analysis_canvas.h +++ b/skia/ext/analysis_canvas.h
@@ -86,15 +86,15 @@ void willRestore() override; void onClipRect(const SkRect& rect, - SkRegion::Op op, + SkClipOp op, ClipEdgeStyle edge_style) override; void onClipRRect(const SkRRect& rrect, - SkRegion::Op op, + SkClipOp op, ClipEdgeStyle edge_style) override; void onClipPath(const SkPath& path, - SkRegion::Op op, + SkClipOp op, ClipEdgeStyle edge_style) override; - void onClipRegion(const SkRegion& deviceRgn, SkRegion::Op op) override; + void onClipRegion(const SkRegion& deviceRgn, SkClipOp op) override; void onDrawText(const void* text, size_t byteLength,
diff --git a/skia/ext/benchmarking_canvas.cc b/skia/ext/benchmarking_canvas.cc index ff9c287a..e5adc0d 100644 --- a/skia/ext/benchmarking_canvas.cc +++ b/skia/ext/benchmarking_canvas.cc
@@ -264,7 +264,7 @@ return std::move(val); } -std::unique_ptr<base::Value> AsValue(SkRegion::Op op) { +std::unique_ptr<base::Value> AsValue(SkClipOp op) { static const char* gOpStrings[] = { "Difference", "Intersect", "Union", @@ -493,7 +493,7 @@ } void BenchmarkingCanvas::onClipRect(const SkRect& rect, - SkRegion::Op region_op, + SkClipOp region_op, SkCanvas::ClipEdgeStyle style) { AutoOp op(this, "ClipRect"); op.addParam("rect", AsValue(rect)); @@ -504,7 +504,7 @@ } void BenchmarkingCanvas::onClipRRect(const SkRRect& rrect, - SkRegion::Op region_op, + SkClipOp region_op, SkCanvas::ClipEdgeStyle style) { AutoOp op(this, "ClipRRect"); op.addParam("rrect", AsValue(rrect)); @@ -515,7 +515,7 @@ } void BenchmarkingCanvas::onClipPath(const SkPath& path, - SkRegion::Op region_op, + SkClipOp region_op, SkCanvas::ClipEdgeStyle style) { AutoOp op(this, "ClipPath"); op.addParam("path", AsValue(path)); @@ -526,7 +526,7 @@ } void BenchmarkingCanvas::onClipRegion(const SkRegion& region, - SkRegion::Op region_op) { + SkClipOp region_op) { AutoOp op(this, "ClipRegion"); op.addParam("region", AsValue(region)); op.addParam("op", AsValue(region_op));
diff --git a/skia/ext/benchmarking_canvas.h b/skia/ext/benchmarking_canvas.h index 1e6f5f6..b81da79 100644 --- a/skia/ext/benchmarking_canvas.h +++ b/skia/ext/benchmarking_canvas.h
@@ -35,10 +35,10 @@ void didConcat(const SkMatrix&) override; void didSetMatrix(const SkMatrix&) override; - void onClipRect(const SkRect&, SkRegion::Op, ClipEdgeStyle) override; - void onClipRRect(const SkRRect&, SkRegion::Op, ClipEdgeStyle) override; - void onClipPath(const SkPath&, SkRegion::Op, ClipEdgeStyle) override; - void onClipRegion(const SkRegion&, SkRegion::Op) override; + void onClipRect(const SkRect&, SkClipOp, ClipEdgeStyle) override; + void onClipRRect(const SkRRect&, SkClipOp, ClipEdgeStyle) override; + void onClipPath(const SkPath&, SkClipOp, ClipEdgeStyle) override; + void onClipRegion(const SkRegion&, SkClipOp) override; void onDrawPaint(const SkPaint&) override; void onDrawPoints(PointMode, size_t count, const SkPoint pts[],
diff --git a/storage/browser/blob/blob_entry.cc b/storage/browser/blob/blob_entry.cc index 8ccc037e..094eb365 100644 --- a/storage/browser/blob/blob_entry.cc +++ b/storage/browser/blob/blob_entry.cc
@@ -36,19 +36,7 @@ transport_allowed_callback(transport_allowed_callback), num_building_dependent_blobs(num_building_dependent_blobs) {} -BlobEntry::BuildingState::~BuildingState() { - DCHECK(!copy_quota_request); - DCHECK(!transport_quota_request); -} - -void BlobEntry::BuildingState::CancelRequests() { - if (copy_quota_request) { - copy_quota_request->Cancel(); - } - if (transport_quota_request) { - transport_quota_request->Cancel(); - } -} +BlobEntry::BuildingState::~BuildingState() {} BlobEntry::BlobEntry(const std::string& content_type, const std::string& content_disposition)
diff --git a/storage/browser/blob/blob_entry.h b/storage/browser/blob/blob_entry.h index 72d21fcb..3c3869e 100644 --- a/storage/browser/blob/blob_entry.h +++ b/storage/browser/blob/blob_entry.h
@@ -24,15 +24,16 @@ class ShareableBlobDataItem; class ViewBlobInternalsJob; -// Represents a blob in BlobStorageRegistry. Exported only for unit tests. +// This class represents a blob in BlobStorageRegistry. We export this only for +// unit tests. class STORAGE_EXPORT BlobEntry { public: using TransportAllowedCallback = base::Callback<void(BlobStatus, std::vector<BlobMemoryController::FileCreationInfo>)>; - // Records a copy from a referenced blob. Copies happen after referenced blobs - // are complete & quota for the copies is granted. + // This records a copy from a referenced blob. When we finish building our + // blob we perform all of these copies. struct STORAGE_EXPORT ItemCopyEntry { ItemCopyEntry(scoped_refptr<ShareableBlobDataItem> source_item, size_t source_item_offset, @@ -49,11 +50,11 @@ DISALLOW_COPY_AND_ASSIGN(ItemCopyEntry); }; - // Building state for pending blobs. State can include: - // 1. Waiting for quota to be granted for transport data (PENDING_QUOTA) + // This keeps track of our building state for our blob. While building, four + // things can be happening mostly simultaneously: + // 1. Waiting for quota to be reserved for memory needed (PENDING_QUOTA) // 2. Waiting for user population of data after quota (PENDING_TRANSPORT) - // 3. Waiting for blobs we reference to complete & quota granted for possible - // copies. (PENDING_INTERNALS) + // 3. Waiting for blobs we reference to complete (PENDING_INTERNALS) struct STORAGE_EXPORT BuildingState { // |transport_allowed_callback| is not null when data needs population. See // BlobStorageContext::BuildBlob for when the callback is called. @@ -62,9 +63,6 @@ size_t num_building_dependent_blobs); ~BuildingState(); - // Cancels pending memory or file requests. - void CancelRequests(); - const bool transport_items_present; // We can have trasnport data that's either populated or unpopulated. If we // need population, this is populated. @@ -77,10 +75,7 @@ size_t num_building_dependent_blobs; base::WeakPtr<BlobMemoryController::QuotaAllocationTask> - transport_quota_request; - - // Copy quota is always memory. - base::WeakPtr<BlobMemoryController::QuotaAllocationTask> copy_quota_request; + memory_quota_request; // These are copies from a referenced blob item to our blob items. Some of // these entries may have changed from bytes to files if they were paged. @@ -102,9 +97,7 @@ // Returns if we're a pending blob that can finish building. bool CanFinishBuilding() const { - // PENDING_INTERNALS means transport is finished. - return status_ == BlobStatus::PENDING_INTERNALS && building_state_ && - !building_state_->copy_quota_request && + return status_ == BlobStatus::PENDING_INTERNALS && building_state_->num_building_dependent_blobs == 0; }
diff --git a/storage/browser/blob/blob_memory_controller.cc b/storage/browser/blob/blob_memory_controller.cc index f4a002fc..6cc6c3f 100644 --- a/storage/browser/blob/blob_memory_controller.cc +++ b/storage/browser/blob/blob_memory_controller.cc
@@ -5,7 +5,6 @@ #include "storage/browser/blob/blob_memory_controller.h" #include <algorithm> -#include <numeric> #include "base/callback.h" #include "base/callback_helpers.h" @@ -46,8 +45,6 @@ base::CreateDirectoryAndGetError(blob_storage_dir, &error); UMA_HISTOGRAM_ENUMERATION("Storage.Blob.CreateDirectoryResult", -error, -File::FILE_ERROR_MAX); - DLOG_IF(ERROR, error != File::FILE_OK) - << "Error creating blob storage directory: " << error; return error; } @@ -77,6 +74,15 @@ return std::make_pair(std::vector<FileCreationInfo>(), creation_info.error); } + + // Grab the file info to get the "last modified" time and store the file. + File::Info file_info; + bool success = file.GetInfo(&file_info); + creation_info.error = success ? File::FILE_OK : File::FILE_ERROR_FAILED; + if (!success) { + return std::make_pair(std::vector<FileCreationInfo>(), + creation_info.error); + } creation_info.file = std::move(file); result.push_back(std::move(creation_info)); @@ -128,10 +134,6 @@ if (bytes_written < 0) break; } - if (!file.Flush()) { - creation_info.error = File::FILE_ERROR_FAILED; - return creation_info; - } File::Info info; bool success = file.GetInfo(&info); @@ -160,10 +162,6 @@ for (const auto& size_pair : file_id_to_sizes) { file_sizes_output->push_back(size_pair.second); } - DCHECK_EQ(std::accumulate(file_sizes_output->begin(), - file_sizes_output->end(), 0ull), - total_size_output) - << "Illegal builder configuration, temporary files must be totally used."; return total_size_output; } @@ -300,7 +298,7 @@ } ~FileQuotaAllocationTask() override {} - void RunDoneCallback(std::vector<FileCreationInfo> file_info, bool success) { + void RunDoneCallback(bool success, std::vector<FileCreationInfo> file_info) { // Make sure we clear the weak pointers we gave to the caller beforehand. weak_factory_.InvalidateWeakPtrs(); @@ -315,7 +313,7 @@ controller_->pending_file_quota_tasks_.erase(my_list_position_); } - done_callback_.Run(std::move(file_info), success); + done_callback_.Run(success, std::move(file_info)); } base::WeakPtr<QuotaAllocationTask> GetWeakPtr() { @@ -343,7 +341,7 @@ for (size_t i = 0; i < files.size(); i++) { files[i].file_reference = std::move(references[i]); } - RunDoneCallback(std::move(files), true); + RunDoneCallback(true, std::move(files)); } // The my_list_position_ iterator is stored so that we can remove ourself @@ -381,7 +379,6 @@ void BlobMemoryController::DisableFilePaging(base::File::Error reason) { UMA_HISTOGRAM_ENUMERATION("Storage.Blob.PagingDisabled", -reason, -File::FILE_ERROR_MAX); - DLOG(ERROR) << "Blob storage paging disabled, reason: " << reason; file_paging_enabled_ = false; in_flight_memory_used_ = 0; items_paging_to_file_.clear(); @@ -401,7 +398,7 @@ memory_request->RunDoneCallback(false); } for (auto& file_request : old_file_tasks) { - file_request->RunDoneCallback(std::vector<FileCreationInfo>(), false); + file_request->RunDoneCallback(false, std::vector<FileCreationInfo>()); } }
diff --git a/storage/browser/blob/blob_memory_controller.h b/storage/browser/blob/blob_memory_controller.h index 865d3e0..068e22b 100644 --- a/storage/browser/blob/blob_memory_controller.h +++ b/storage/browser/blob/blob_memory_controller.h
@@ -103,7 +103,7 @@ // The bool argument is true if we successfully received file quota, and the // vector argument provides the file info. using FileQuotaRequestCallback = - base::Callback<void(std::vector<FileCreationInfo>, bool)>; + base::Callback<void(bool, std::vector<FileCreationInfo>)>; // We enable file paging if |file_runner| isn't a nullptr. BlobMemoryController(const base::FilePath& storage_directory,
diff --git a/storage/browser/blob/blob_reader.cc b/storage/browser/blob/blob_reader.cc index 5584036..1ec3002 100644 --- a/storage/browser/blob/blob_reader.cc +++ b/storage/browser/blob/blob_reader.cc
@@ -14,7 +14,6 @@ #include "base/bind.h" #include "base/callback_helpers.h" -#include "base/debug/stack_trace.h" #include "base/sequenced_task_runner.h" #include "base/time/time.h" #include "base/trace_event/trace_event.h"
diff --git a/storage/browser/blob/blob_storage_context.cc b/storage/browser/blob/blob_storage_context.cc index acc086e..1fecda8 100644 --- a/storage/browser/blob/blob_storage_context.cc +++ b/storage/browser/blob/blob_storage_context.cc
@@ -92,13 +92,9 @@ size_t num_files_with_unknown_size = 0; size_t num_building_dependent_blobs = 0; - bool found_memory_transport = false; - bool found_file_transport = false; - base::CheckedNumeric<uint64_t> checked_total_size = 0; base::CheckedNumeric<uint64_t> checked_total_memory_size = 0; - base::CheckedNumeric<uint64_t> checked_transport_quota_needed = 0; - base::CheckedNumeric<uint64_t> checked_copy_quota_needed = 0; + base::CheckedNumeric<uint64_t> checked_memory_quota_needed = 0; for (scoped_refptr<BlobDataItem> input_item : input_builder.items_) { const DataElement& input_element = input_item->data_element(); @@ -109,19 +105,11 @@ if (IsBytes(type)) { DCHECK_NE(0 + DataElement::kUnknownSize, input_element.length()); - found_memory_transport = true; - if (found_file_transport) { - // We cannot have both memory and file transport items. - status = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; - return; - } - contains_unpopulated_transport_items |= - (type == DataElement::TYPE_BYTES_DESCRIPTION); - checked_transport_quota_needed += length; + checked_memory_quota_needed += length; checked_total_size += length; scoped_refptr<ShareableBlobDataItem> item = new ShareableBlobDataItem( std::move(input_item), ShareableBlobDataItem::QUOTA_NEEDED); - pending_transport_items.push_back(item); + pending_memory_items.push_back(item); transport_items.push_back(item.get()); output_blob->AppendSharedBlobItem(std::move(item)); continue; @@ -186,14 +174,14 @@ copies.push_back(ItemCopyEntry(slice.first_source_item, slice.first_item_slice_offset, slice.dest_items.front())); - pending_copy_items.push_back(slice.dest_items.front()); + pending_memory_items.push_back(slice.dest_items.front()); } if (slice.last_source_item) { copies.push_back( ItemCopyEntry(slice.last_source_item, 0, slice.dest_items.back())); - pending_copy_items.push_back(slice.dest_items.back()); + pending_memory_items.push_back(slice.dest_items.back()); } - checked_copy_quota_needed += slice.copying_memory_size; + checked_memory_quota_needed += slice.copying_memory_size; for (auto& shareable_item : slice.dest_items) { output_blob->AppendSharedBlobItem(std::move(shareable_item)); @@ -201,30 +189,14 @@ continue; } - // If the source item is a temporary file item, then we need to keep track - // of that and mark it as needing quota. - scoped_refptr<ShareableBlobDataItem> item; - if (BlobDataBuilder::IsFutureFileItem(input_element)) { - found_file_transport = true; - if (found_memory_transport) { - // We cannot have both memory and file transport items. - status = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; - return; - } - contains_unpopulated_transport_items = true; - item = new ShareableBlobDataItem(std::move(input_item), - ShareableBlobDataItem::QUOTA_NEEDED); - pending_transport_items.push_back(item); - transport_items.push_back(item.get()); - checked_transport_quota_needed += length; - } else { - item = new ShareableBlobDataItem( - std::move(input_item), - ShareableBlobDataItem::POPULATED_WITHOUT_QUOTA); - } + DCHECK(!BlobDataBuilder::IsFutureFileItem(input_element)) + << "File allocation not implemented."; if (length == DataElement::kUnknownSize) num_files_with_unknown_size++; + scoped_refptr<ShareableBlobDataItem> item = new ShareableBlobDataItem( + std::move(input_item), ShareableBlobDataItem::POPULATED_WITHOUT_QUOTA); + checked_total_size += length; output_blob->AppendSharedBlobItem(std::move(item)); } @@ -234,18 +206,14 @@ return; } if (!checked_total_size.IsValid() || !checked_total_memory_size.IsValid() || - !checked_transport_quota_needed.IsValid() || - !checked_copy_quota_needed.IsValid()) { + !checked_memory_quota_needed.IsValid()) { status = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; return; } total_size = checked_total_size.ValueOrDie(); total_memory_size = checked_total_memory_size.ValueOrDie(); - transport_quota_needed = checked_transport_quota_needed.ValueOrDie(); - copy_quota_needed = checked_copy_quota_needed.ValueOrDie(); - transport_quota_type = found_file_transport ? TransportQuotaType::FILE - : TransportQuotaType::MEMORY; - if (transport_quota_needed) { + memory_quota_needed = checked_memory_quota_needed.ValueOrDie(); + if (memory_quota_needed) { status = BlobStatus::PENDING_QUOTA; } else { status = BlobStatus::PENDING_INTERNALS; @@ -330,16 +298,8 @@ data_item = new BlobDataItem(std::move(element), source_item->data_handle_); - if (BlobDataBuilder::IsFutureFileItem(source_item->data_element())) { - // The source file isn't a real file yet (path is fake), so store the - // items we need to copy from later. - if (item_index == first_item_index) { - first_item_slice_offset = item_offset; - first_source_item = source_items[item_index]; - } else { - last_source_item = source_items[item_index]; - } - } + DCHECK(!BlobDataBuilder::IsFutureFileItem(source_item->data_element())) + << "File allocation unimplemented."; break; } case DataElement::TYPE_FILE_FILESYSTEM: { @@ -381,13 +341,8 @@ : memory_controller_(base::FilePath(), scoped_refptr<base::TaskRunner>()), ptr_factory_(this) {} -BlobStorageContext::BlobStorageContext( - base::FilePath storage_directory, - scoped_refptr<base::TaskRunner> file_runner) - : memory_controller_(std::move(storage_directory), std::move(file_runner)), - ptr_factory_(this) {} - -BlobStorageContext::~BlobStorageContext() {} +BlobStorageContext::~BlobStorageContext() { +} std::unique_ptr<BlobDataHandle> BlobStorageContext::GetBlobDataFromUUID( const std::string& uuid) { @@ -459,9 +414,14 @@ // stores the complete item representation in the internal data. BlobFlattener flattener(content, entry, ®istry_); - DCHECK(!flattener.contains_unpopulated_transport_items || + DCHECK(flattener.status != BlobStatus::PENDING_TRANSPORT || + !transport_allowed_callback) + << "There is no pending content for the user to populate, so the " + "callback should be null."; + DCHECK(flattener.status != BlobStatus::PENDING_TRANSPORT || transport_allowed_callback) - << "If we have pending unpopulated content then a callback is required"; + << "If we have pending content then there needs to be a callback " + "present."; entry->set_size(flattener.total_size); entry->set_status(flattener.status); @@ -470,14 +430,8 @@ UMA_HISTOGRAM_COUNTS("Storage.Blob.ItemCount", entry->items().size()); UMA_HISTOGRAM_COUNTS("Storage.Blob.TotalSize", flattener.total_memory_size / 1024); - - uint64_t total_memory_needed = - flattener.copy_quota_needed + - (flattener.transport_quota_type == TransportQuotaType::MEMORY - ? flattener.transport_quota_needed - : 0); UMA_HISTOGRAM_COUNTS("Storage.Blob.TotalUnsharedSize", - total_memory_needed / 1024); + flattener.memory_quota_needed / 1024); size_t num_building_dependent_blobs = 0; std::vector<std::unique_ptr<BlobDataHandle>> dependent_blobs; @@ -500,7 +454,7 @@ } entry->set_building_state(base::MakeUnique<BlobEntry::BuildingState>( - !flattener.pending_transport_items.empty(), transport_allowed_callback, + !flattener.transport_items.empty(), transport_allowed_callback, num_building_dependent_blobs)); BlobEntry::BuildingState* building_state = entry->building_state_.get(); std::swap(building_state->copies, flattener.copies); @@ -514,52 +468,21 @@ return handle; } - // Avoid the state where we might grant only one quota. - if (!memory_controller_.CanReserveQuota(flattener.copy_quota_needed + - flattener.transport_quota_needed)) { + if (!memory_controller_.CanReserveQuota(flattener.memory_quota_needed)) { CancelBuildingBlobInternal(entry, BlobStatus::ERR_OUT_OF_MEMORY); return handle; } - if (flattener.copy_quota_needed > 0) { - // The blob can complete during the execution of |ReserveMemoryQuota|. + if (flattener.memory_quota_needed > 0) { + // The blob can complete during the execution of this line. base::WeakPtr<QuotaAllocationTask> pending_request = memory_controller_.ReserveMemoryQuota( - std::move(flattener.pending_copy_items), - base::Bind(&BlobStorageContext::OnEnoughSpaceForCopies, + std::move(flattener.pending_memory_items), + base::Bind(&BlobStorageContext::OnEnoughSizeForMemory, ptr_factory_.GetWeakPtr(), content.uuid_)); // Building state will be null if the blob is already finished. if (entry->building_state_) - entry->building_state_->copy_quota_request = std::move(pending_request); - } - - if (flattener.transport_quota_needed > 0) { - base::WeakPtr<QuotaAllocationTask> pending_request; - - switch (flattener.transport_quota_type) { - case TransportQuotaType::MEMORY: { - // The blob can complete during the execution of |ReserveMemoryQuota|. - std::vector<BlobMemoryController::FileCreationInfo> empty_files; - pending_request = memory_controller_.ReserveMemoryQuota( - std::move(flattener.pending_transport_items), - base::Bind(&BlobStorageContext::OnEnoughSpaceForTransport, - ptr_factory_.GetWeakPtr(), content.uuid_, - base::Passed(&empty_files))); - break; - } - case TransportQuotaType::FILE: - pending_request = memory_controller_.ReserveFileQuota( - std::move(flattener.pending_transport_items), - base::Bind(&BlobStorageContext::OnEnoughSpaceForTransport, - ptr_factory_.GetWeakPtr(), content.uuid_)); - break; - } - - // Building state will be null if the blob is already finished. - if (entry->building_state_) { - entry->building_state_->transport_quota_request = - std::move(pending_request); - } + entry->building_state_->memory_quota_request = std::move(pending_request); } if (entry->CanFinishBuilding()) @@ -699,26 +622,8 @@ const char* src_data = copy.source_item->item()->bytes() + copy.source_item_offset; copy.dest_item->item()->item_->SetToBytes(src_data, dest_size); - break; - } - case DataElement::TYPE_FILE: { - // If we expected a memory item (and our source was paged to disk) we - // free that memory. - if (dest_type == DataElement::TYPE_BYTES_DESCRIPTION) - copy.dest_item->set_memory_allocation(nullptr); - - const DataElement& source_element = - copy.source_item->item()->data_element(); - std::unique_ptr<DataElement> new_element(new DataElement()); - new_element->SetToFilePathRange( - source_element.path(), - source_element.offset() + copy.source_item_offset, dest_size, - source_element.expected_modification_time()); - scoped_refptr<BlobDataItem> new_item(new BlobDataItem( - std::move(new_element), copy.source_item->item()->data_handle_)); - copy.dest_item->set_item(std::move(new_item)); - break; - } + } break; + case DataElement::TYPE_FILE: case DataElement::TYPE_UNKNOWN: case DataElement::TYPE_BLOB: case DataElement::TYPE_BYTES_DESCRIPTION: @@ -765,10 +670,8 @@ NotifyTransportCompleteInternal(entry); } -void BlobStorageContext::OnEnoughSpaceForTransport( - const std::string& uuid, - std::vector<BlobMemoryController::FileCreationInfo> files, - bool success) { +void BlobStorageContext::OnEnoughSizeForMemory(const std::string& uuid, + bool success) { if (!success) { CancelBuildingBlob(uuid, BlobStatus::ERR_OUT_OF_MEMORY); return; @@ -777,25 +680,15 @@ if (!entry || !entry->building_state_.get()) return; BlobEntry::BuildingState& building_state = *entry->building_state_; - DCHECK(!building_state.transport_quota_request); - DCHECK(building_state.transport_items_present); + DCHECK(!building_state.memory_quota_request); - entry->set_status(BlobStatus::PENDING_TRANSPORT); - RequestTransport(entry, std::move(files)); - - if (entry->CanFinishBuilding()) - FinishBuilding(entry); -} - -void BlobStorageContext::OnEnoughSpaceForCopies(const std::string& uuid, - bool success) { - if (!success) { - CancelBuildingBlob(uuid, BlobStatus::ERR_OUT_OF_MEMORY); - return; + if (building_state.transport_items_present) { + entry->set_status(BlobStatus::PENDING_TRANSPORT); + RequestTransport(entry, + std::vector<BlobMemoryController::FileCreationInfo>()); + } else { + entry->set_status(BlobStatus::PENDING_INTERNALS); } - BlobEntry* entry = registry_.GetEntry(uuid); - if (!entry) - return; if (entry->CanFinishBuilding()) FinishBuilding(entry); @@ -823,8 +716,12 @@ } void BlobStorageContext::ClearAndFreeMemory(BlobEntry* entry) { - if (entry->building_state_) - entry->building_state_->CancelRequests(); + if (entry->building_state_) { + BlobEntry::BuildingState* building_state = entry->building_state_.get(); + if (building_state->memory_quota_request) { + building_state->memory_quota_request->Cancel(); + } + } entry->ClearItems(); entry->ClearOffsets(); entry->set_size(0);
diff --git a/storage/browser/blob/blob_storage_context.h b/storage/browser/blob/blob_storage_context.h index 9e068a7..c64581e9 100644 --- a/storage/browser/blob/blob_storage_context.h +++ b/storage/browser/blob/blob_storage_context.h
@@ -36,7 +36,6 @@ namespace content { class BlobDispatcherHost; class BlobDispatcherHostTest; -class BlobStorageBrowserTest; } namespace storage { @@ -47,17 +46,14 @@ class ShareableBlobDataItem; // This class handles the logistics of blob storage within the browser process. -// This class is not threadsafe, access on IO thread. In Chromium there is one -// instance per profile. +// We are single threaded and should only be used on the IO thread. In Chromium +// there is one instance per profile. class STORAGE_EXPORT BlobStorageContext { public: using TransportAllowedCallback = BlobEntry::TransportAllowedCallback; // Initializes the context without disk support. BlobStorageContext(); - // Disk support is enabled if |file_runner| isn't null. - BlobStorageContext(base::FilePath storage_directory, - scoped_refptr<base::TaskRunner> file_runner); ~BlobStorageContext(); std::unique_ptr<BlobDataHandle> GetBlobDataFromUUID(const std::string& uuid); @@ -129,7 +125,6 @@ protected: friend class content::BlobDispatcherHost; friend class content::BlobDispatcherHostTest; - friend class content::BlobStorageBrowserTest; friend class BlobTransportHost; friend class BlobTransportHostTest; friend class BlobDataHandle; @@ -138,8 +133,6 @@ friend class BlobSliceTest; friend class BlobStorageContextTest; - enum class TransportQuotaType { MEMORY, FILE }; - // Transforms a BlobDataBuilder into a BlobEntry with no blob references. // BlobSlice is used to flatten out these references. Records the total size // and items for memory and file quota requests. @@ -159,8 +152,6 @@ // reference ourself. BlobStatus status = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS; - bool contains_unpopulated_transport_items = false; - // This is the total size of the blob, including all memory, files, etc. uint64_t total_size = 0; // Total memory size of the blob (not including files, etc). @@ -168,19 +159,14 @@ std::vector<std::pair<std::string, BlobEntry*>> dependent_blobs; - TransportQuotaType transport_quota_type = TransportQuotaType::MEMORY; - uint64_t transport_quota_needed = 0; - std::vector<scoped_refptr<ShareableBlobDataItem>> pending_transport_items; - // Hold a separate vector of pointers to declare them as populated. + uint64_t memory_quota_needed = 0; + std::vector<scoped_refptr<ShareableBlobDataItem>> pending_memory_items; + std::vector<ShareableBlobDataItem*> transport_items; - // Copy quota is always memory quota. - uint64_t copy_quota_needed = 0; - std::vector<scoped_refptr<ShareableBlobDataItem>> pending_copy_items; - // These record all future copies we'll need to do from referenced blobs. - // This happens when we do a partial slice from a pending data or file - // item. + // This + // happens when we do a partial slice from a pending data or file item. std::vector<BlobEntry::ItemCopyEntry> copies; private: @@ -249,13 +235,7 @@ BlobEntry* entry, std::vector<BlobMemoryController::FileCreationInfo> files); - // The files array is empty for memory quota request responses. - void OnEnoughSpaceForTransport( - const std::string& uuid, - std::vector<BlobMemoryController::FileCreationInfo> files, - bool can_fit); - - void OnEnoughSpaceForCopies(const std::string& uuid, bool can_fit); + void OnEnoughSizeForMemory(const std::string& uuid, bool can_fit); void OnDependentBlobFinished(const std::string& owning_blob_uuid, BlobStatus reason);
diff --git a/storage/common/blob_storage/blob_storage_constants.h b/storage/common/blob_storage/blob_storage_constants.h index b94e886..f54d20a 100644 --- a/storage/common/blob_storage/blob_storage_constants.h +++ b/storage/common/blob_storage/blob_storage_constants.h
@@ -28,8 +28,8 @@ size_t max_blob_in_memory_space = 500 * 1024 * 1024; // This is the maximum amount of disk space we can use. - // TODO(dmurph): Determine initial heuristic based on disk usage & arch. - uint64_t max_blob_disk_space = 0ull; + // TODO(dmurph): Consider storage size of the device. + uint64_t max_blob_disk_space = 5ull * 1024 * 1024 * 1024; // This is the minimum file size we can use when paging blob items to disk. // We combine items until we reach at least this size. @@ -73,13 +73,12 @@ // Blob state section: // The blob has finished. DONE = 200, - // Waiting for memory or file quota for the to-be transported data. + // The system is pending on quota being granted, the transport layer + // populating pending data, and/or copying data from dependent blobs. See + // BlobEntry::BuildingState determine which of these are happening, as they + // all can happen concurrently. PENDING_QUOTA = 201, - // Waiting for data to be transported (quota has been granted). PENDING_TRANSPORT = 202, - // Waiting for any operations involving dependent blobs after transport data - // has been populated. See BlobEntry::BuildingState for more info. - // TODO(dmurph): Change to PENDING_REFERENCED_BLOBS (crbug.com/670398). PENDING_INTERNALS = 203, LAST = PENDING_INTERNALS };
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json index 1dbeb4e..38c79aa2 100644 --- a/testing/buildbot/chromium.fyi.json +++ b/testing/buildbot/chromium.fyi.json
@@ -12720,7 +12720,7 @@ "swarming": { "can_use_on_swarming_builders": false }, - "test": "media_mojo_shell_unittests" + "test": "media_service_unittests" }, { "swarming": { @@ -12756,7 +12756,7 @@ "swarming": { "can_use_on_swarming_builders": false }, - "test": "media_mojo_shell_unittests" + "test": "media_service_unittests" }, { "swarming": {
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json index 797d1d5..fe982fa0 100644 --- a/testing/buildbot/chromium.gpu.fyi.json +++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -9971,6 +9971,29 @@ "--show-stdout", "--browser=release", "-v", + "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d11 --use-passthrough-cmd-decoder" + ], + "isolate_name": "telemetry_gpu_integration_test", + "name": "webgl_conformance_d3d11_passthrough_tests", + "override_compile_targets": [ + "telemetry_gpu_integration_test_run" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "gpu": "1002:6613", + "os": "Windows-2008ServerR2-SP1" + } + ] + } + }, + { + "args": [ + "webgl_conformance", + "--show-stdout", + "--browser=release", + "-v", "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d9" ], "isolate_name": "telemetry_gpu_integration_test", @@ -10127,6 +10150,29 @@ "--show-stdout", "--browser=release", "-v", + "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d11 --use-passthrough-cmd-decoder" + ], + "isolate_name": "telemetry_gpu_integration_test", + "name": "webgl_conformance_d3d11_passthrough_tests", + "override_compile_targets": [ + "telemetry_gpu_integration_test_run" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "gpu": "10de:104a", + "os": "Windows-2008ServerR2-SP1" + } + ] + } + }, + { + "args": [ + "webgl_conformance", + "--show-stdout", + "--browser=release", + "-v", "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d9" ], "isolate_name": "telemetry_gpu_integration_test", @@ -10541,6 +10587,29 @@ "--show-stdout", "--browser=debug", "-v", + "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d11 --use-passthrough-cmd-decoder" + ], + "isolate_name": "telemetry_gpu_integration_test", + "name": "webgl_conformance_d3d11_passthrough_tests", + "override_compile_targets": [ + "telemetry_gpu_integration_test_run" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "gpu": "8086:0412", + "os": "Windows-10-10586" + } + ] + } + }, + { + "args": [ + "webgl_conformance", + "--show-stdout", + "--browser=debug", + "-v", "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d9" ], "isolate_name": "telemetry_gpu_integration_test", @@ -11017,6 +11086,29 @@ "--show-stdout", "--browser=release", "-v", + "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d11 --use-passthrough-cmd-decoder" + ], + "isolate_name": "telemetry_gpu_integration_test", + "name": "webgl_conformance_d3d11_passthrough_tests", + "override_compile_targets": [ + "telemetry_gpu_integration_test_run" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "gpu": "8086:0412", + "os": "Windows-10-10586" + } + ] + } + }, + { + "args": [ + "webgl_conformance", + "--show-stdout", + "--browser=release", + "-v", "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d9" ], "isolate_name": "telemetry_gpu_integration_test", @@ -11454,6 +11546,29 @@ "--show-stdout", "--browser=debug", "-v", + "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d11 --use-passthrough-cmd-decoder" + ], + "isolate_name": "telemetry_gpu_integration_test", + "name": "webgl_conformance_d3d11_passthrough_tests", + "override_compile_targets": [ + "telemetry_gpu_integration_test_run" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "gpu": "1002:6613", + "os": "Windows-2008ServerR2-SP1" + } + ] + } + }, + { + "args": [ + "webgl_conformance", + "--show-stdout", + "--browser=debug", + "-v", "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d9" ], "isolate_name": "telemetry_gpu_integration_test", @@ -11868,6 +11983,29 @@ "--show-stdout", "--browser=debug", "-v", + "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d11 --use-passthrough-cmd-decoder" + ], + "isolate_name": "telemetry_gpu_integration_test", + "name": "webgl_conformance_d3d11_passthrough_tests", + "override_compile_targets": [ + "telemetry_gpu_integration_test_run" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "gpu": "10de:104a", + "os": "Windows-2008ServerR2-SP1" + } + ] + } + }, + { + "args": [ + "webgl_conformance", + "--show-stdout", + "--browser=debug", + "-v", "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d9" ], "isolate_name": "telemetry_gpu_integration_test", @@ -12305,6 +12443,29 @@ "--show-stdout", "--browser=debug", "-v", + "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d11 --use-passthrough-cmd-decoder" + ], + "isolate_name": "telemetry_gpu_integration_test", + "name": "webgl_conformance_d3d11_passthrough_tests", + "override_compile_targets": [ + "telemetry_gpu_integration_test_run" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "gpu": "8086:0412", + "os": "Windows-2008ServerR2-SP1" + } + ] + } + }, + { + "args": [ + "webgl_conformance", + "--show-stdout", + "--browser=debug", + "-v", "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d9" ], "isolate_name": "telemetry_gpu_integration_test", @@ -12758,6 +12919,29 @@ "--show-stdout", "--browser=release", "-v", + "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d11 --use-passthrough-cmd-decoder" + ], + "isolate_name": "telemetry_gpu_integration_test", + "name": "webgl_conformance_d3d11_passthrough_tests", + "override_compile_targets": [ + "telemetry_gpu_integration_test_run" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "gpu": "1002:6779", + "os": "Windows-2008ServerR2-SP1" + } + ] + } + }, + { + "args": [ + "webgl_conformance", + "--show-stdout", + "--browser=release", + "-v", "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d9" ], "isolate_name": "telemetry_gpu_integration_test", @@ -13232,6 +13416,29 @@ "--show-stdout", "--browser=release", "-v", + "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d11 --use-passthrough-cmd-decoder" + ], + "isolate_name": "telemetry_gpu_integration_test", + "name": "webgl_conformance_d3d11_passthrough_tests", + "override_compile_targets": [ + "telemetry_gpu_integration_test_run" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "gpu": "1002:6613", + "os": "Windows-2008ServerR2-SP1" + } + ] + } + }, + { + "args": [ + "webgl_conformance", + "--show-stdout", + "--browser=release", + "-v", "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d9" ], "isolate_name": "telemetry_gpu_integration_test", @@ -13685,6 +13892,29 @@ "--show-stdout", "--browser=release", "-v", + "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d11 --use-passthrough-cmd-decoder" + ], + "isolate_name": "telemetry_gpu_integration_test", + "name": "webgl_conformance_d3d11_passthrough_tests", + "override_compile_targets": [ + "telemetry_gpu_integration_test_run" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "gpu": "8086:041a", + "os": "Windows-2008ServerR2-SP1" + } + ] + } + }, + { + "args": [ + "webgl_conformance", + "--show-stdout", + "--browser=release", + "-v", "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d9" ], "isolate_name": "telemetry_gpu_integration_test", @@ -14138,6 +14368,29 @@ "--show-stdout", "--browser=release", "-v", + "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d11 --use-passthrough-cmd-decoder" + ], + "isolate_name": "telemetry_gpu_integration_test", + "name": "webgl_conformance_d3d11_passthrough_tests", + "override_compile_targets": [ + "telemetry_gpu_integration_test_run" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "gpu": "10de:0f02", + "os": "Windows-2008ServerR2-SP1" + } + ] + } + }, + { + "args": [ + "webgl_conformance", + "--show-stdout", + "--browser=release", + "-v", "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d9" ], "isolate_name": "telemetry_gpu_integration_test", @@ -14684,6 +14937,29 @@ "--show-stdout", "--browser=release", "-v", + "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d11 --use-passthrough-cmd-decoder" + ], + "isolate_name": "telemetry_gpu_integration_test", + "name": "webgl_conformance_d3d11_passthrough_tests", + "override_compile_targets": [ + "telemetry_gpu_integration_test_run" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "gpu": "10de:104a", + "os": "Windows-2008ServerR2-SP1" + } + ] + } + }, + { + "args": [ + "webgl_conformance", + "--show-stdout", + "--browser=release", + "-v", "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d9" ], "isolate_name": "telemetry_gpu_integration_test", @@ -15160,6 +15436,29 @@ "--show-stdout", "--browser=release", "-v", + "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d11 --use-passthrough-cmd-decoder" + ], + "isolate_name": "telemetry_gpu_integration_test", + "name": "webgl_conformance_d3d11_passthrough_tests", + "override_compile_targets": [ + "telemetry_gpu_integration_test_run" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "gpu": "8086:0412", + "os": "Windows-2008ServerR2-SP1" + } + ] + } + }, + { + "args": [ + "webgl_conformance", + "--show-stdout", + "--browser=release", + "-v", "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d9" ], "isolate_name": "telemetry_gpu_integration_test", @@ -15574,6 +15873,29 @@ "--show-stdout", "--browser=debug_x64", "-v", + "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d11 --use-passthrough-cmd-decoder" + ], + "isolate_name": "telemetry_gpu_integration_test", + "name": "webgl_conformance_d3d11_passthrough_tests", + "override_compile_targets": [ + "telemetry_gpu_integration_test_run" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "gpu": "10de:104a", + "os": "Windows-2008ServerR2-SP1" + } + ] + } + }, + { + "args": [ + "webgl_conformance", + "--show-stdout", + "--browser=debug_x64", + "-v", "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d9" ], "isolate_name": "telemetry_gpu_integration_test", @@ -16084,6 +16406,29 @@ "--show-stdout", "--browser=release_x64", "-v", + "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d11 --use-passthrough-cmd-decoder" + ], + "isolate_name": "telemetry_gpu_integration_test", + "name": "webgl_conformance_d3d11_passthrough_tests", + "override_compile_targets": [ + "telemetry_gpu_integration_test_run" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "gpu": "10de:104a", + "os": "Windows-2008ServerR2-SP1" + } + ] + } + }, + { + "args": [ + "webgl_conformance", + "--show-stdout", + "--browser=release_x64", + "-v", "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d9" ], "isolate_name": "telemetry_gpu_integration_test", @@ -16521,6 +16866,29 @@ "--show-stdout", "--browser=debug", "-v", + "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d11 --use-passthrough-cmd-decoder" + ], + "isolate_name": "telemetry_gpu_integration_test", + "name": "webgl_conformance_d3d11_passthrough_tests", + "override_compile_targets": [ + "telemetry_gpu_integration_test_run" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "gpu": "10de:104a", + "os": "Windows-2012ServerR2-SP0" + } + ] + } + }, + { + "args": [ + "webgl_conformance", + "--show-stdout", + "--browser=debug", + "-v", "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d9" ], "isolate_name": "telemetry_gpu_integration_test", @@ -17000,6 +17368,29 @@ "--show-stdout", "--browser=release", "-v", + "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d11 --use-passthrough-cmd-decoder" + ], + "isolate_name": "telemetry_gpu_integration_test", + "name": "webgl_conformance_d3d11_passthrough_tests", + "override_compile_targets": [ + "telemetry_gpu_integration_test_run" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "gpu": "10de:104a", + "os": "Windows-2012ServerR2-SP0" + } + ] + } + }, + { + "args": [ + "webgl_conformance", + "--show-stdout", + "--browser=release", + "-v", "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --use-angle=d3d9" ], "isolate_name": "telemetry_gpu_integration_test",
diff --git a/testing/buildbot/chromium.linux.json b/testing/buildbot/chromium.linux.json index be6f07ee..6ab065d 100644 --- a/testing/buildbot/chromium.linux.json +++ b/testing/buildbot/chromium.linux.json
@@ -3176,7 +3176,7 @@ "swarming": { "can_use_on_swarming_builders": true }, - "test": "media_mojo_shell_unittests" + "test": "media_service_unittests" }, { "swarming": {
diff --git a/testing/buildbot/filters/browser-side-navigation.linux.browser_tests.filter b/testing/buildbot/filters/browser-side-navigation.linux.browser_tests.filter index 7bf3459..998192dc 100644 --- a/testing/buildbot/filters/browser-side-navigation.linux.browser_tests.filter +++ b/testing/buildbot/filters/browser-side-navigation.linux.browser_tests.filter
@@ -8,8 +8,6 @@ # TabManagerTest.TabManagerBasics crashes sometimes -TabManagerTest.TabManagerBasics -ThreatDOMDetailsTest.Everything --WebNavigationApiTest.RequestOpenTab --WebNavigationApiTest.UserAction # https://crbug.com/652767: NavigationHandle::GetResponseHeaders sometimes # returns null for browser-side navigations
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl index c6d44074..1d57587 100644 --- a/testing/buildbot/gn_isolate_map.pyl +++ b/testing/buildbot/gn_isolate_map.pyl
@@ -571,14 +571,27 @@ "label": "//media:media_unittests", "type": "windowed_test_launcher", }, - "media_mojo_shell_unittests": { - "label": "//media/mojo/services:media_mojo_shell_unittests", + "media_service_unittests": { + "label": "//media/mojo/services:media_service_unittests", "type": "console_test_launcher", }, "media_blink_unittests": { "label": "//media/blink:media_blink_unittests", "type": "windowed_test_launcher", }, + "media_router_perf_tests": { + "label": "//chrome/test/media_router:media_router_perf_tests", + "type": "script", + "script": "//chrome/test/media_router/telemetry/run_benchmark", + "args": [ + "--browser=release", + "--also-run-disabled-tests", + "-v", + "--use-live-sites", + "--output-format=chartjson", + "--output-dir=${ISOLATED_OUTDIR}", + ], + }, "media_router_tests": { "label": "//chrome/test/media_router:media_router_tests", "type": "script",
diff --git a/testing/buildbot/manage.py b/testing/buildbot/manage.py index 3715285..c3e2a95 100755 --- a/testing/buildbot/manage.py +++ b/testing/buildbot/manage.py
@@ -167,6 +167,7 @@ 'content_junit_tests', 'content_junit_tests', 'junit_unit_tests', + 'media_router_perf_tests', 'media_router_tests', 'net_junit_tests', 'net_junit_tests',
diff --git a/third_party/WebKit/LayoutTests/LeakExpectations b/third_party/WebKit/LayoutTests/LeakExpectations index 365b239..c667847 100644 --- a/third_party/WebKit/LayoutTests/LeakExpectations +++ b/third_party/WebKit/LayoutTests/LeakExpectations
@@ -54,6 +54,7 @@ # ----------------------------------------------------------------- crbug.com/364417 paint/invalidation/japanese-rl-selection-clear.html [ Leak ] +crbug.com/364417 virtual/spinvalidation/paint/invalidation/japanese-rl-selection-clear.html [ Leak ] crbug.com/455369 fast/html/marquee-destroyed-without-removed-from-crash.html [ Leak Pass ]
diff --git a/third_party/WebKit/LayoutTests/fast/text/selection-with-inline-padding-expected.html b/third_party/WebKit/LayoutTests/fast/text/selection-with-inline-padding-expected.html deleted file mode 100644 index b4c4bd2..0000000 --- a/third_party/WebKit/LayoutTests/fast/text/selection-with-inline-padding-expected.html +++ /dev/null
@@ -1,27 +0,0 @@ -<!DOCTYPE html> -<html> - <head> - <title>Test paddings and selection for inline elements</title> - <style> - p > b, pre > b { padding: 0 10px; } - </style> - </head> - <body> - <p> - Lorem ipsum dolor sit amet, consectetur adipiscing elit.<br> - <b id="test">Sed dictum erat sit amet pharetra pretium.</b><br> - Nullam a est vitae orci tempus tincidunt nec at dolor. - </p> - <p> - Tests that selections do not include padding in the Y direction for - inline elements where it should be ignored. - </p> - <script> - var node = document.getElementById('test').firstChild; - var range = document.createRange(); - range.setStart(node, 5); - range.setEnd(node, node.length - 5); - window.getSelection().addRange(range); - </script> - </body> -</html>
diff --git a/third_party/WebKit/LayoutTests/fast/text/selection-with-inline-padding.html b/third_party/WebKit/LayoutTests/fast/text/selection-with-inline-padding.html index 56c2a6c..7992c4b 100644 --- a/third_party/WebKit/LayoutTests/fast/text/selection-with-inline-padding.html +++ b/third_party/WebKit/LayoutTests/fast/text/selection-with-inline-padding.html
@@ -1,27 +1,25 @@ <!DOCTYPE html> -<html> - <head> - <title>Test paddings and selection for inline elements</title> - <style> - p > b, pre > b { padding: 10px; } - </style> - </head> - <body> - <p> - Lorem ipsum dolor sit amet, consectetur adipiscing elit.<br> - <b id="test">Sed dictum erat sit amet pharetra pretium.</b><br> - Nullam a est vitae orci tempus tincidunt nec at dolor. - </p> - <p> - Tests that selections do not include padding in the Y direction for - inline elements where it should be ignored. - </p> - <script> - var node = document.getElementById('test').firstChild; - var range = document.createRange(); - range.setStart(node, 5); - range.setEnd(node, node.length - 5); - window.getSelection().addRange(range); - </script> - </body> -</html> +<style> +p > b { + padding: 10px; +} +</style> +<p> + Lorem ipsum dolor sit amet, consectetur adipiscing elit.<br> + <b id="test">Sed dictum erat sit amet pharetra pretium.</b><br> + Nullam a est vitae orci tempus tincidunt nec at dolor. +</p> +<p> + Tests whether selection includes padding in the Y direction for inline + elements. This padding would ideally not be included. http://crbug.com/657325#c13 +</p> +<script src="../../resources/run-after-layout-and-paint.js"></script> +<script> +runAfterLayoutAndPaint(function() { + var node = document.getElementById('test').firstChild; + var range = document.createRange(); + range.setStart(node, 5); + range.setEnd(node, node.length - 5); + window.getSelection().addRange(range); +}, true); +</script>
diff --git a/third_party/WebKit/LayoutTests/images/resources/png-simple.png b/third_party/WebKit/LayoutTests/images/resources/png-simple.png new file mode 100644 index 0000000..0ddfdad9 --- /dev/null +++ b/third_party/WebKit/LayoutTests/images/resources/png-simple.png Binary files differ
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/resize-scrollable-iframe-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/resize-scrollable-iframe-expected.txt index b55dc80..aa5b644 100644 --- a/third_party/WebKit/LayoutTests/paint/invalidation/resize-scrollable-iframe-expected.txt +++ b/third_party/WebKit/LayoutTests/paint/invalidation/resize-scrollable-iframe-expected.txt
@@ -18,11 +18,21 @@ }, { "object": "LayoutView #document", + "rect": [8, 193, 285, 200], + "reason": "incremental" + }, + { + "object": "LayoutView #document", "rect": [8, 393, 285, 15], "reason": "scroll" }, { "object": "LayoutView #document", + "rect": [93, 108, 200, 285], + "reason": "incremental" + }, + { + "object": "LayoutView #document", "rect": [8, 193, 85, 15], "reason": "scroll" }, @@ -77,6 +87,10 @@ { "object": "VerticalScrollbar", "reason": "scroll" + }, + { + "object": "LayoutView #document", + "reason": "incremental" } ] }
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table-cell-collapsed-border-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/table-cell-collapsed-border-expected.txt index cd078c63..c8b10b4 100644 --- a/third_party/WebKit/LayoutTests/paint/invalidation/table-cell-collapsed-border-expected.txt +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table-cell-collapsed-border-expected.txt
@@ -7,6 +7,11 @@ "drawsContent": true, "paintInvalidations": [ { + "object": "LayoutTable TABLE", + "rect": [3, 60, 441, 405], + "reason": "invalidate paint rectangle" + }, + { "object": "LayoutTableCell TD id='t3'", "rect": [220, 260, 122, 110], "reason": "style change" @@ -26,48 +31,8 @@ ], "objectPaintInvalidations": [ { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD id='t1'", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD id='t2'", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD id='t3'", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" + "object": "LayoutTable TABLE", + "reason": "invalidate paint rectangle" }, { "object": "LayoutTableCell TD id='t1'",
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table-outer-border-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/table-outer-border-expected.txt index b06d1cb..dd21182 100644 --- a/third_party/WebKit/LayoutTests/paint/invalidation/table-outer-border-expected.txt +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table-outer-border-expected.txt
@@ -95,14 +95,6 @@ "reason": "layoutObject removal" }, { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { "object": "LayoutTable TABLE id='table' class='green'", "reason": "layoutObject insertion" },
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table-section-repaint-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/table-section-repaint-expected.txt index 34142075..ca5147f 100644 --- a/third_party/WebKit/LayoutTests/paint/invalidation/table-section-repaint-expected.txt +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table-section-repaint-expected.txt
@@ -7,6 +7,11 @@ "drawsContent": true, "paintInvalidations": [ { + "object": "LayoutTable TABLE", + "rect": [8, 308, 60, 83], + "reason": "forced by layout" + }, + { "object": "LayoutTableCell TD class='half'", "rect": [8, 353, 60, 38], "reason": "bounds change" @@ -100,11 +105,6 @@ "object": "LayoutTableCell TD class='red half'", "rect": [8, 8, 60, 30], "reason": "bounds change" - }, - { - "object": "LayoutTable TABLE", - "rect": [8, 372, 60, 19], - "reason": "incremental" } ] } @@ -172,7 +172,7 @@ }, { "object": "LayoutTable TABLE", - "reason": "incremental" + "reason": "forced by layout" }, { "object": "LayoutTableRow TR",
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/border-collapse-change-separate-to-collapse-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/table/border-collapse-change-separate-to-collapse-expected.txt index 18cc801..ba69534 100644 --- a/third_party/WebKit/LayoutTests/paint/invalidation/table/border-collapse-change-separate-to-collapse-expected.txt +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/border-collapse-change-separate-to-collapse-expected.txt
@@ -51,14 +51,6 @@ ], "objectPaintInvalidations": [ { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { "object": "LayoutTable TABLE id='table'", "reason": "style change" },
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-cell-append-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-cell-append-expected.txt index 59ef756..e9f5d7b 100644 --- a/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-cell-append-expected.txt +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-cell-append-expected.txt
@@ -8,8 +8,8 @@ "paintInvalidations": [ { "object": "LayoutTable TABLE", - "rect": [8, 59, 120, 5], - "reason": "incremental" + "rect": [8, 8, 120, 56], + "reason": "forced by layout" }, { "object": "LayoutTableCell TD", @@ -17,11 +17,6 @@ "reason": "layoutObject insertion" }, { - "object": "LayoutTable TABLE", - "rect": [65, 8, 63, 56], - "reason": "incremental" - }, - { "object": "LayoutTableCell TD", "rect": [8, 8, 62, 56], "reason": "location change" @@ -31,16 +26,8 @@ ], "objectPaintInvalidations": [ { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { "object": "LayoutTable TABLE", - "reason": "incremental" + "reason": "forced by layout" }, { "object": "LayoutTableRow TR id='row'",
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-cell-border-color-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-cell-border-color-expected.txt index 76964c5..89451a0 100644 --- a/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-cell-border-color-expected.txt +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-cell-border-color-expected.txt
@@ -7,6 +7,11 @@ "drawsContent": true, "paintInvalidations": [ { + "object": "LayoutTable TABLE", + "rect": [8, 8, 114, 54], + "reason": "invalidate paint rectangle" + }, + { "object": "LayoutTableCell TD id='foo'", "rect": [8, 8, 60, 54], "reason": "style change" @@ -16,12 +21,8 @@ ], "objectPaintInvalidations": [ { - "object": "LayoutTableCell TD id='foo'", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" + "object": "LayoutTable TABLE", + "reason": "invalidate paint rectangle" }, { "object": "LayoutTableCell TD id='foo'",
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-cell-border-width-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-cell-border-width-expected.txt index 6a5f6dc0..7b946e8 100644 --- a/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-cell-border-width-expected.txt +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-cell-border-width-expected.txt
@@ -8,8 +8,8 @@ "paintInvalidations": [ { "object": "LayoutTable TABLE", - "rect": [8, 58, 114, 4], - "reason": "incremental" + "rect": [8, 8, 114, 54], + "reason": "forced by layout" }, { "object": "LayoutTableCell TD id='foo'", @@ -25,27 +25,14 @@ "object": "LayoutTableCell TD", "rect": [62, 8, 55, 52], "reason": "bounds change" - }, - { - "object": "LayoutTable TABLE", - "rect": [116, 8, 6, 54], - "reason": "incremental" } ] } ], "objectPaintInvalidations": [ { - "object": "LayoutTableCell TD id='foo'", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { "object": "LayoutTable TABLE", - "reason": "incremental" + "reason": "forced by layout" }, { "object": "LayoutTableRow TR",
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-col-border-color-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-col-border-color-expected.txt index 27c93b4..09774d8 100644 --- a/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-col-border-color-expected.txt +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-col-border-color-expected.txt
@@ -7,6 +7,11 @@ "drawsContent": true, "paintInvalidations": [ { + "object": "LayoutTable TABLE", + "rect": [8, 8, 113, 104], + "reason": "invalidate paint rectangle" + }, + { "object": "LayoutTableCol COL id='col'", "rect": [8, 8, 113, 104], "reason": "style change" @@ -16,20 +21,8 @@ ], "objectPaintInvalidations": [ { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" + "object": "LayoutTable TABLE", + "reason": "invalidate paint rectangle" }, { "object": "LayoutTableCol COL id='col'",
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-col-border-width-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-col-border-width-expected.txt index 5ba2070..e54cf9ba 100644 --- a/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-col-border-width-expected.txt +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-col-border-width-expected.txt
@@ -7,16 +7,16 @@ "drawsContent": true, "paintInvalidations": [ { + "object": "LayoutTable TABLE", + "rect": [8, 8, 113, 104], + "reason": "forced by layout" + }, + { "object": "LayoutTableCol COL id='col'", "rect": [8, 8, 113, 104], "reason": "style change" }, { - "object": "LayoutTable TABLE", - "rect": [8, 108, 113, 4], - "reason": "incremental" - }, - { "object": "LayoutTableCell TD", "rect": [8, 59, 60, 53], "reason": "bounds change" @@ -50,35 +50,14 @@ "object": "LayoutTableCell TD", "rect": [63, 59, 54, 51], "reason": "bounds change" - }, - { - "object": "LayoutTable TABLE", - "rect": [116, 8, 5, 104], - "reason": "incremental" } ] } ], "objectPaintInvalidations": [ { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { "object": "LayoutTable TABLE", - "reason": "incremental" + "reason": "forced by layout" }, { "object": "LayoutTableCol COL id='col'",
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-colgroup-border-color-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-colgroup-border-color-expected.txt index 71449ce..5629d33 100644 --- a/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-colgroup-border-color-expected.txt +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-colgroup-border-color-expected.txt
@@ -7,6 +7,11 @@ "drawsContent": true, "paintInvalidations": [ { + "object": "LayoutTable TABLE", + "rect": [7, 7, 167, 104], + "reason": "invalidate paint rectangle" + }, + { "object": "LayoutTableCol COLGROUP id='colgroup'", "rect": [8, 8, 166, 102], "reason": "style change" @@ -16,28 +21,8 @@ ], "objectPaintInvalidations": [ { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" + "object": "LayoutTable TABLE", + "reason": "invalidate paint rectangle" }, { "object": "LayoutTableCol COLGROUP id='colgroup'",
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-colgroup-border-width-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-colgroup-border-width-expected.txt index 7f1d98f..8f3e28c 100644 --- a/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-colgroup-border-width-expected.txt +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-colgroup-border-width-expected.txt
@@ -7,6 +7,11 @@ "drawsContent": true, "paintInvalidations": [ { + "object": "LayoutTable TABLE", + "rect": [8, 8, 166, 102], + "reason": "forced by layout" + }, + { "object": "LayoutTableCol COLGROUP id='colgroup'", "rect": [8, 8, 166, 102], "reason": "style change" @@ -60,43 +65,14 @@ "object": "LayoutTableCell TD", "rect": [116, 9, 54, 50], "reason": "bounds change" - }, - { - "object": "LayoutTable TABLE", - "rect": [169, 8, 5, 102], - "reason": "incremental" } ] } ], "objectPaintInvalidations": [ { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { "object": "LayoutTable TABLE", - "reason": "incremental" + "reason": "forced by layout" }, { "object": "LayoutTableCol COLGROUP id='colgroup'",
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-row-border-width-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-row-border-width-expected.txt index 4013197..42b0a3b9 100644 --- a/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-row-border-width-expected.txt +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-row-border-width-expected.txt
@@ -7,6 +7,11 @@ "drawsContent": true, "paintInvalidations": [ { + "object": "LayoutTable TABLE", + "rect": [8, 8, 60, 103], + "reason": "forced by layout" + }, + { "object": "LayoutTableCell TD", "rect": [8, 8, 60, 54], "reason": "forced by layout" @@ -17,11 +22,6 @@ "reason": "location change" }, { - "object": "LayoutTable TABLE", - "rect": [8, 109, 60, 2], - "reason": "incremental" - }, - { "object": "LayoutTableRow TR id='row'", "rect": [10, 10, 56, 50], "reason": "style change" @@ -30,27 +30,14 @@ "object": "LayoutTableRow TR id='row'", "rect": [9, 9, 54, 50], "reason": "style change" - }, - { - "object": "LayoutTable TABLE", - "rect": [62, 8, 6, 103], - "reason": "incremental" } ] } ], "objectPaintInvalidations": [ { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { "object": "LayoutTable TABLE", - "reason": "incremental" + "reason": "forced by layout" }, { "object": "LayoutTableRow TR id='row'",
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-table-border-color-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-table-border-color-expected.txt index 5a9447d..e9ea09b 100644 --- a/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-table-border-color-expected.txt +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-table-border-color-expected.txt
@@ -16,10 +16,6 @@ ], "objectPaintInvalidations": [ { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { "object": "LayoutTable TABLE id='tbl'", "reason": "style change" }
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-table-border-width-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-table-border-width-expected.txt index 7ebee422..c2f06f30 100644 --- a/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-table-border-width-expected.txt +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-table-border-width-expected.txt
@@ -21,10 +21,6 @@ ], "objectPaintInvalidations": [ { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { "object": "LayoutTable TABLE id='tbl'", "reason": "style change" },
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-tbody-border-width-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-tbody-border-width-expected.txt index 2b0dc2a..87ac20939 100644 --- a/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-tbody-border-width-expected.txt +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/cached-change-tbody-border-width-expected.txt
@@ -8,8 +8,8 @@ "paintInvalidations": [ { "object": "LayoutTable TABLE", - "rect": [8, 159, 114, 2], - "reason": "incremental" + "rect": [8, 8, 114, 153], + "reason": "forced by layout" }, { "object": "LayoutTableSection TBODY id='tbody'", @@ -70,43 +70,14 @@ "object": "LayoutTableCell TD", "rect": [63, 59, 54, 51], "reason": "bounds change" - }, - { - "object": "LayoutTable TABLE", - "rect": [115, 8, 7, 153], - "reason": "incremental" } ] } ], "objectPaintInvalidations": [ { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { "object": "LayoutTable TABLE", - "reason": "incremental" + "reason": "forced by layout" }, { "object": "LayoutTableSection TBODY id='tbody'",
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/collapsed-border-cell-resize-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/table/collapsed-border-cell-resize-expected.txt index b34767d..1ef83120 100644 --- a/third_party/WebKit/LayoutTests/paint/invalidation/table/collapsed-border-cell-resize-expected.txt +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/collapsed-border-cell-resize-expected.txt
@@ -7,6 +7,11 @@ "drawsContent": true, "paintInvalidations": [ { + "object": "LayoutTable TABLE", + "rect": [8, 8, 104, 204], + "reason": "forced by layout" + }, + { "object": "LayoutTableCell TD id='target'", "rect": [8, 8, 104, 204], "reason": "border box change" @@ -16,6 +21,10 @@ ], "objectPaintInvalidations": [ { + "object": "LayoutTable TABLE", + "reason": "forced by layout" + }, + { "object": "LayoutTableCell TD id='target'", "reason": "border box change" }
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-cell-change-collapsed-border-color-expected.html b/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-cell-change-collapsed-border-color-expected.html new file mode 100644 index 0000000..cf6608bf --- /dev/null +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-cell-change-collapsed-border-color-expected.html
@@ -0,0 +1,7 @@ +<!DOCTYPE html> +<p style="height: 50px">Passes if green borders are visible surrounding A's cell and no red. +<table style="border-collapse: collapse;"> + <tr> + <td id="a" style="border: 5px solid green; width: 100px; height: 100px">A</td> + </tr> +</table>
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-cell-change-collapsed-border-color-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-cell-change-collapsed-border-color-expected.txt new file mode 100644 index 0000000..bf4df5b --- /dev/null +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-cell-change-collapsed-border-color-expected.txt
@@ -0,0 +1,49 @@ +{ + "layers": [ + { + "name": "LayoutView #document", + "bounds": [800, 600], + "contentsOpaque": true, + "drawsContent": true, + "paintInvalidations": [ + { + "object": "LayoutTable TABLE", + "rect": [8, 82, 112, 112], + "reason": "invalidate paint rectangle" + } + ] + }, + { + "name": "LayoutTableCell TD id='a'", + "position": [10, 84], + "bounds": [107, 107], + "drawsContent": true, + "paintInvalidations": [ + { + "object": "LayoutTableCell TD id='a'", + "rect": [-2, -2, 112, 112], + "reason": "style change" + } + ] + } + ], + "objectPaintInvalidations": [ + { + "object": "LayoutTable TABLE", + "reason": "invalidate paint rectangle" + }, + { + "object": "LayoutTableCell TD id='a'", + "reason": "style change" + }, + { + "object": "RootInlineBox", + "reason": "style change" + }, + { + "object": "RowBackground", + "reason": "style change" + } + ] +} +
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-cell-change-collapsed-border-color.html b/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-cell-change-collapsed-border-color.html new file mode 100644 index 0000000..96981e21 --- /dev/null +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-cell-change-collapsed-border-color.html
@@ -0,0 +1,14 @@ +<!DOCTYPE html> +<p style="height: 50px">Passes if green borders are visible surrounding A's cell and no red. +<table style="border-collapse: collapse;"> + <tr> + <td id="a" style="border: 5px solid red; width: 100px; height: 100px; will-change: transform">A</td> + </tr> +</table> +<script src="../resources/text-based-repaint.js"></script> +<script> +function repaintTest() { + a.style.borderColor = 'green'; +} +onload = runRepaintAndPixelTest; +</script>
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-cell-collapsed-border-add-anonymous-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-cell-collapsed-border-add-anonymous-expected.txt new file mode 100644 index 0000000..c2f1a9a --- /dev/null +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-cell-collapsed-border-add-anonymous-expected.txt
@@ -0,0 +1,2 @@ +Passes if no crash. +A
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-cell-collapsed-border-add-anonymous.html b/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-cell-collapsed-border-add-anonymous.html new file mode 100644 index 0000000..680d574 --- /dev/null +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-cell-collapsed-border-add-anonymous.html
@@ -0,0 +1,19 @@ +<!DOCTYPE html> +<style> +td { will-change: transform; } +</style> +Passes if no crash. +<table id="table" style="border-collapse: collapse"> + <tr> + <td style="border: 4px solid pink"></td> + <td></td> + </tr> +</table> +<script src="../../../resources/run-after-layout-and-paint.js"></script> +<script> +if (window.testRunner) + testRunner.dumpAsText(); +runAfterLayoutAndPaint(function() { + table.appendChild(document.createTextNode("A")); +}, true); +</script>
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-row-change-collapsed-border-color-expected.html b/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-row-change-collapsed-border-color-expected.html new file mode 100644 index 0000000..dc1f099 --- /dev/null +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-row-change-collapsed-border-color-expected.html
@@ -0,0 +1,7 @@ +<!DOCTYPE html> +<p style="height: 50px">Passes if green borders are visible surrounding A's cell and no red. +<table style="border-collapse: collapse;"> + <tr id="a" style="border: 5px solid green"> + <td style="width: 100px; height: 100px">A</td> + </tr> +</table>
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-row-change-collapsed-border-color-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-row-change-collapsed-border-color-expected.txt new file mode 100644 index 0000000..c26169aa2 --- /dev/null +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-row-change-collapsed-border-color-expected.txt
@@ -0,0 +1,41 @@ +{ + "layers": [ + { + "name": "LayoutView #document", + "bounds": [800, 600], + "contentsOpaque": true, + "drawsContent": true, + "paintInvalidations": [ + { + "object": "LayoutTable TABLE", + "rect": [8, 82, 112, 112], + "reason": "invalidate paint rectangle" + } + ] + }, + { + "name": "LayoutTableRow TR id='a'", + "position": [10, 84], + "bounds": [107, 107], + "drawsContent": true, + "paintInvalidations": [ + { + "object": "LayoutTableRow TR id='a'", + "rect": [0, 0, 107, 107], + "reason": "style change" + } + ] + } + ], + "objectPaintInvalidations": [ + { + "object": "LayoutTable TABLE", + "reason": "invalidate paint rectangle" + }, + { + "object": "LayoutTableRow TR id='a'", + "reason": "style change" + } + ] +} +
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-row-change-collapsed-border-color.html b/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-row-change-collapsed-border-color.html new file mode 100644 index 0000000..b3f51b3 --- /dev/null +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-row-change-collapsed-border-color.html
@@ -0,0 +1,14 @@ +<!DOCTYPE html> +<p style="height: 50px">Passes if green borders are visible surrounding A's cell and no red. +<table style="border-collapse: collapse;"> + <tr id="a" style="border: 5px solid red; will-change: transform"> + <td style="width: 100px; height: 100px">A</td> + </tr> +</table> +<script src="../resources/text-based-repaint.js"></script> +<script> +function repaintTest() { + a.style.borderColor = 'green'; +} +onload = runRepaintAndPixelTest; +</script>
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-section-change-collapsed-border-color-expected.html b/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-section-change-collapsed-border-color-expected.html new file mode 100644 index 0000000..b76f9bf --- /dev/null +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-section-change-collapsed-border-color-expected.html
@@ -0,0 +1,9 @@ +<!DOCTYPE html> +<p style="height: 50px">Passes if green borders are visible surrounding A's cell and no red. +<table style="border-collapse: collapse;"> + <thead id="a" style="border: 5px solid green"> + <tr> + <td style="width: 100px; height: 100px">A</td> + </tr> + </head> +</table>
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-section-change-collapsed-border-color-expected.txt b/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-section-change-collapsed-border-color-expected.txt new file mode 100644 index 0000000..8505bd02 --- /dev/null +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-section-change-collapsed-border-color-expected.txt
@@ -0,0 +1,41 @@ +{ + "layers": [ + { + "name": "LayoutView #document", + "bounds": [800, 600], + "contentsOpaque": true, + "drawsContent": true, + "paintInvalidations": [ + { + "object": "LayoutTable TABLE", + "rect": [8, 82, 112, 112], + "reason": "invalidate paint rectangle" + } + ] + }, + { + "name": "LayoutTableSection THEAD id='a'", + "position": [10, 84], + "bounds": [107, 107], + "drawsContent": true, + "paintInvalidations": [ + { + "object": "LayoutTableSection THEAD id='a'", + "rect": [0, 0, 107, 107], + "reason": "style change" + } + ] + } + ], + "objectPaintInvalidations": [ + { + "object": "LayoutTable TABLE", + "reason": "invalidate paint rectangle" + }, + { + "object": "LayoutTableSection THEAD id='a'", + "reason": "style change" + } + ] +} +
diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-section-change-collapsed-border-color.html b/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-section-change-collapsed-border-color.html new file mode 100644 index 0000000..c7b9932 --- /dev/null +++ b/third_party/WebKit/LayoutTests/paint/invalidation/table/composited-section-change-collapsed-border-color.html
@@ -0,0 +1,16 @@ +<!DOCTYPE html> +<p style="height: 50px">Passes if green borders are visible surrounding A's cell and no red. +<table style="border-collapse: collapse;"> + <thead id="a" style="border: 5px solid red; will-change: transform"> + <tr> + <td style="width: 100px; height: 100px">A</td> + </tr> + </head> +</table> +<script src="../resources/text-based-repaint.js"></script> +<script> +function repaintTest() { + a.style.borderColor = 'green'; +} +onload = runRepaintAndPixelTest; +</script>
diff --git a/third_party/WebKit/LayoutTests/paint/text/selection-no-clip-text.html b/third_party/WebKit/LayoutTests/paint/text/selection-no-clip-text.html new file mode 100644 index 0000000..477fbd3 --- /dev/null +++ b/third_party/WebKit/LayoutTests/paint/text/selection-no-clip-text.html
@@ -0,0 +1,8 @@ +<!DOCTYPE html> +Passes if top of all text in second row is visible when fully selected. +<div style="line-height: 0.7">X<br>YYYY</div> +<script> +var range = document.createRange(); +range.selectNode(document.body); +document.getSelection().addRange(range); +</script>
diff --git a/third_party/WebKit/LayoutTests/platform/linux/fast/text/selection-with-inline-padding-expected.png b/third_party/WebKit/LayoutTests/platform/linux/fast/text/selection-with-inline-padding-expected.png new file mode 100644 index 0000000..ab249ea --- /dev/null +++ b/third_party/WebKit/LayoutTests/platform/linux/fast/text/selection-with-inline-padding-expected.png Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/fast/text/selection-with-inline-padding-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/fast/text/selection-with-inline-padding-expected.txt new file mode 100644 index 0000000..ecf50154 --- /dev/null +++ b/third_party/WebKit/LayoutTests/platform/linux/fast/text/selection-with-inline-padding-expected.txt
@@ -0,0 +1,22 @@ +layer at (0,0) size 800x600 + LayoutView at (0,0) size 800x600 +layer at (0,0) size 800x148 + LayoutBlockFlow {HTML} at (0,0) size 800x148 + LayoutBlockFlow {BODY} at (8,16) size 784x116 + LayoutBlockFlow {P} at (0,0) size 784x60 + LayoutText {#text} at (0,0) size 330x19 + text run at (0,0) width 330: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + LayoutBR {BR} at (330,0) size 0x19 + LayoutInline {B} at (0,0) size 301x39 + LayoutText {#text} at (10,20) size 281x19 + text run at (10,20) width 281: "Sed dictum erat sit amet pharetra pretium." + LayoutBR {BR} at (300,20) size 1x19 + LayoutText {#text} at (0,40) size 315x19 + text run at (0,40) width 315: "Nullam a est vitae orci tempus tincidunt nec at dolor." + LayoutBlockFlow {P} at (0,76) size 784x40 + LayoutText {#text} at (0,0) size 729x39 + text run at (0,0) width 409: "Tests whether selection includes padding in the Y direction for inline " + text run at (408,0) width 321: "elements. This padding would ideally not be included." + text run at (0,20) width 181: "http://crbug.com/657325#c13" +selection start: position 5 of child 0 {#text} of child 3 {B} of child 0 {P} of body +selection end: position 37 of child 0 {#text} of child 3 {B} of child 0 {P} of body
diff --git a/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/resize-scrollable-iframe-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/resize-scrollable-iframe-expected.txt index 61b869b..bab242a 100644 --- a/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/resize-scrollable-iframe-expected.txt +++ b/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/resize-scrollable-iframe-expected.txt
@@ -18,11 +18,21 @@ }, { "object": "LayoutView #document", + "rect": [8, 193, 285, 200], + "reason": "incremental" + }, + { + "object": "LayoutView #document", "rect": [8, 393, 285, 15], "reason": "scroll" }, { "object": "LayoutView #document", + "rect": [93, 108, 200, 285], + "reason": "incremental" + }, + { + "object": "LayoutView #document", "rect": [8, 193, 85, 15], "reason": "scroll" }, @@ -77,6 +87,10 @@ { "object": "VerticalScrollbar", "reason": "scroll" + }, + { + "object": "LayoutView #document", + "reason": "incremental" } ] }
diff --git a/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/table-cell-collapsed-border-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/table-cell-collapsed-border-expected.txt index e452e1f..b1b1bfb 100644 --- a/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/table-cell-collapsed-border-expected.txt +++ b/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/table-cell-collapsed-border-expected.txt
@@ -7,6 +7,11 @@ "drawsContent": true, "paintInvalidations": [ { + "object": "LayoutTable TABLE", + "rect": [3, 64, 441, 405], + "reason": "invalidate paint rectangle" + }, + { "object": "LayoutTableCell TD id='t3'", "rect": [220, 264, 122, 110], "reason": "style change" @@ -26,48 +31,8 @@ ], "objectPaintInvalidations": [ { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD id='t1'", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD id='t2'", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD id='t3'", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" + "object": "LayoutTable TABLE", + "reason": "invalidate paint rectangle" }, { "object": "LayoutTableCell TD id='t1'",
diff --git a/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/table-collapsed-border-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/table-collapsed-border-expected.txt index 71fa8da..bf2ed89b 100644 --- a/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/table-collapsed-border-expected.txt +++ b/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/table-collapsed-border-expected.txt
@@ -13,6 +13,11 @@ }, { "object": "LayoutTable TABLE", + "rect": [8, 286, 95, 82], + "reason": "forced by layout" + }, + { + "object": "LayoutTable TABLE", "rect": [8, 102, 95, 82], "reason": "full" }, @@ -102,11 +107,6 @@ "reason": "layoutObject removal" }, { - "object": "LayoutTable TABLE", - "rect": [74, 286, 29, 82], - "reason": "incremental" - }, - { "object": "LayoutTableCell TD", "rect": [8, 224, 14, 22], "reason": "forced by layout" @@ -147,30 +147,6 @@ "reason": "layoutObject removal" }, { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { "object": "LayoutTable TABLE id='t'", "reason": "style change" }, @@ -256,7 +232,7 @@ }, { "object": "LayoutTable TABLE", - "reason": "incremental" + "reason": "forced by layout" }, { "object": "LayoutTableCell TD",
diff --git a/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/table/cached-change-cell-sl-border-color-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/table/cached-change-cell-sl-border-color-expected.txt index 6bb9255..2882c0c 100644 --- a/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/table/cached-change-cell-sl-border-color-expected.txt +++ b/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/table/cached-change-cell-sl-border-color-expected.txt
@@ -7,6 +7,11 @@ "drawsContent": true, "paintInvalidations": [ { + "object": "LayoutTable (relative positioned) TABLE", + "rect": [8, 8, 114, 54], + "reason": "invalidate paint rectangle" + }, + { "object": "LayoutTableCell TD id='foo'", "rect": [8, 8, 60, 54], "reason": "style change" @@ -30,12 +35,8 @@ "reason": "layoutObject removal" }, { - "object": "LayoutTableCell TD id='foo'", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" + "object": "LayoutTable (relative positioned) TABLE", + "reason": "invalidate paint rectangle" }, { "object": "LayoutTableCell TD id='foo'",
diff --git a/third_party/WebKit/LayoutTests/platform/linux/paint/text/selection-no-clip-text-expected.png b/third_party/WebKit/LayoutTests/platform/linux/paint/text/selection-no-clip-text-expected.png new file mode 100644 index 0000000..52d9f71b --- /dev/null +++ b/third_party/WebKit/LayoutTests/platform/linux/paint/text/selection-no-clip-text-expected.png Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/linux/paint/text/selection-no-clip-text-expected.txt b/third_party/WebKit/LayoutTests/platform/linux/paint/text/selection-no-clip-text-expected.txt new file mode 100644 index 0000000..9efa6f0b --- /dev/null +++ b/third_party/WebKit/LayoutTests/platform/linux/paint/text/selection-no-clip-text-expected.txt
@@ -0,0 +1,16 @@ +layer at (0,0) size 800x600 + LayoutView at (0,0) size 800x600 +layer at (0,0) size 800x58 + LayoutBlockFlow {HTML} at (0,0) size 800x58 + LayoutBlockFlow {BODY} at (8,8) size 784x42 + LayoutBlockFlow (anonymous) at (0,0) size 784x20 + LayoutText {#text} at (0,0) size 399x19 + text run at (0,0) width 399: "Passes if top of all text in second row is visible when fully selected." + LayoutBlockFlow {DIV} at (0,20) size 784x22 + LayoutText {#text} at (0,-4) size 11x19 + text run at (0,-4) width 11: "X" + LayoutBR {BR} at (11,-4) size 0x19 + LayoutText {#text} at (0,7) size 44x19 + text run at (0,7) width 44: "YYYY" +selection start: position 0 of child 0 {#text} of body +selection end: position 4 of child 2 {#text} of child 1 {DIV} of body
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/fast/text/selection-with-inline-padding-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/fast/text/selection-with-inline-padding-expected.png new file mode 100644 index 0000000..2040ce8 --- /dev/null +++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/fast/text/selection-with-inline-padding-expected.png Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/fast/text/selection-with-inline-padding-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/fast/text/selection-with-inline-padding-expected.png new file mode 100644 index 0000000..85fbe4e --- /dev/null +++ b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/fast/text/selection-with-inline-padding-expected.png Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/fast/text/selection-with-inline-padding-expected.png b/third_party/WebKit/LayoutTests/platform/mac/fast/text/selection-with-inline-padding-expected.png new file mode 100644 index 0000000..c83872da --- /dev/null +++ b/third_party/WebKit/LayoutTests/platform/mac/fast/text/selection-with-inline-padding-expected.png Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/fast/text/selection-with-inline-padding-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/fast/text/selection-with-inline-padding-expected.txt new file mode 100644 index 0000000..408ed81f --- /dev/null +++ b/third_party/WebKit/LayoutTests/platform/mac/fast/text/selection-with-inline-padding-expected.txt
@@ -0,0 +1,22 @@ +layer at (0,0) size 800x600 + LayoutView at (0,0) size 800x600 +layer at (0,0) size 800x138 + LayoutBlockFlow {HTML} at (0,0) size 800x138 + LayoutBlockFlow {BODY} at (8,16) size 784x106 + LayoutBlockFlow {P} at (0,0) size 784x54 + LayoutText {#text} at (0,0) size 357x18 + text run at (0,0) width 357: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + LayoutBR {BR} at (356,0) size 1x18 + LayoutInline {B} at (0,0) size 314x38 + LayoutText {#text} at (10,18) size 294x18 + text run at (10,18) width 294: "Sed dictum erat sit amet pharetra pretium." + LayoutBR {BR} at (313,18) size 1x18 + LayoutText {#text} at (0,36) size 339x18 + text run at (0,36) width 339: "Nullam a est vitae orci tempus tincidunt nec at dolor." + LayoutBlockFlow {P} at (0,70) size 784x36 + LayoutText {#text} at (0,0) size 730x36 + text run at (0,0) width 446: "Tests whether selection includes padding in the Y direction for inline " + text run at (445,0) width 285: "elements. This padding would ideally not be" + text run at (0,18) width 253: "included. http://crbug.com/657325#c13" +selection start: position 5 of child 0 {#text} of child 3 {B} of child 0 {P} of body +selection end: position 37 of child 0 {#text} of child 3 {B} of child 0 {P} of body
diff --git a/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/line-flow-with-floats-9-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/line-flow-with-floats-9-expected.txt index e4a9ce9..f5e66e64 100644 --- a/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/line-flow-with-floats-9-expected.txt +++ b/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/line-flow-with-floats-9-expected.txt
@@ -94,6 +94,11 @@ { "object": "LayoutView #document", "rect": [485, 0, 15, 600], + "reason": "incremental" + }, + { + "object": "LayoutView #document", + "rect": [485, 0, 15, 600], "reason": "scroll" } ] @@ -109,6 +114,10 @@ "reason": "scroll" }, { + "object": "LayoutView #document", + "reason": "incremental" + }, + { "object": "LayoutBlockFlow P", "reason": "incremental" },
diff --git a/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/table-collapsed-border-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/table-collapsed-border-expected.txt index d64d6549..4aabc19 100644 --- a/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/table-collapsed-border-expected.txt +++ b/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/table-collapsed-border-expected.txt
@@ -18,6 +18,11 @@ }, { "object": "LayoutTable TABLE", + "rect": [8, 268, 101, 76], + "reason": "forced by layout" + }, + { + "object": "LayoutTable TABLE", "rect": [8, 96, 101, 76], "reason": "full" }, @@ -102,11 +107,6 @@ "reason": "layoutObject removal" }, { - "object": "LayoutTable TABLE", - "rect": [79, 268, 30, 76], - "reason": "incremental" - }, - { "object": "LayoutTableCell TD", "rect": [8, 210, 14, 20], "reason": "forced by layout" @@ -147,30 +147,6 @@ "reason": "layoutObject removal" }, { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { "object": "LayoutTable TABLE id='t'", "reason": "style change" }, @@ -256,7 +232,7 @@ }, { "object": "LayoutTable TABLE", - "reason": "incremental" + "reason": "forced by layout" }, { "object": "LayoutTableCell TD",
diff --git a/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/table/cached-change-cell-sl-border-color-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/table/cached-change-cell-sl-border-color-expected.txt index 1804202..d682150 100644 --- a/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/table/cached-change-cell-sl-border-color-expected.txt +++ b/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/table/cached-change-cell-sl-border-color-expected.txt
@@ -7,6 +7,11 @@ "drawsContent": true, "paintInvalidations": [ { + "object": "LayoutTable (relative positioned) TABLE", + "rect": [8, 8, 114, 54], + "reason": "invalidate paint rectangle" + }, + { "object": "LayoutTableCell TD id='foo'", "rect": [8, 8, 60, 54], "reason": "style change" @@ -30,12 +35,8 @@ "reason": "layoutObject removal" }, { - "object": "LayoutTableCell TD id='foo'", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" + "object": "LayoutTable (relative positioned) TABLE", + "reason": "invalidate paint rectangle" }, { "object": "LayoutTableCell TD id='foo'",
diff --git a/third_party/WebKit/LayoutTests/platform/mac/paint/text/selection-no-clip-text-expected.png b/third_party/WebKit/LayoutTests/platform/mac/paint/text/selection-no-clip-text-expected.png new file mode 100644 index 0000000..50ebdd3 --- /dev/null +++ b/third_party/WebKit/LayoutTests/platform/mac/paint/text/selection-no-clip-text-expected.png Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/mac/paint/text/selection-no-clip-text-expected.txt b/third_party/WebKit/LayoutTests/platform/mac/paint/text/selection-no-clip-text-expected.txt new file mode 100644 index 0000000..885d2026 --- /dev/null +++ b/third_party/WebKit/LayoutTests/platform/mac/paint/text/selection-no-clip-text-expected.txt
@@ -0,0 +1,16 @@ +layer at (0,0) size 800x600 + LayoutView at (0,0) size 800x600 +layer at (0,0) size 800x56 + LayoutBlockFlow {HTML} at (0,0) size 800x56 + LayoutBlockFlow {BODY} at (8,8) size 784x40 + LayoutBlockFlow (anonymous) at (0,0) size 784x18 + LayoutText {#text} at (0,0) size 433x18 + text run at (0,0) width 433: "Passes if top of all text in second row is visible when fully selected." + LayoutBlockFlow {DIV} at (0,18) size 784x22 + LayoutText {#text} at (0,-4) size 12x18 + text run at (0,-4) width 12: "X" + LayoutBR {BR} at (11,-4) size 1x18 + LayoutText {#text} at (0,7) size 47x18 + text run at (0,7) width 47: "YYYY" +selection start: position 0 of child 0 {#text} of body +selection end: position 4 of child 2 {#text} of child 1 {DIV} of body
diff --git a/third_party/WebKit/LayoutTests/platform/win/fast/text/selection-with-inline-padding-expected.png b/third_party/WebKit/LayoutTests/platform/win/fast/text/selection-with-inline-padding-expected.png new file mode 100644 index 0000000..1a452c58 --- /dev/null +++ b/third_party/WebKit/LayoutTests/platform/win/fast/text/selection-with-inline-padding-expected.png Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/fast/text/selection-with-inline-padding-expected.txt b/third_party/WebKit/LayoutTests/platform/win/fast/text/selection-with-inline-padding-expected.txt new file mode 100644 index 0000000..8da17d12 --- /dev/null +++ b/third_party/WebKit/LayoutTests/platform/win/fast/text/selection-with-inline-padding-expected.txt
@@ -0,0 +1,22 @@ +layer at (0,0) size 800x600 + LayoutView at (0,0) size 800x600 +layer at (0,0) size 800x138 + LayoutBlockFlow {HTML} at (0,0) size 800x138 + LayoutBlockFlow {BODY} at (8,16) size 784x106 + LayoutBlockFlow {P} at (0,0) size 784x54 + LayoutText {#text} at (0,0) size 357x17 + text run at (0,0) width 357: "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + LayoutBR {BR} at (356,0) size 1x17 + LayoutInline {B} at (0,0) size 314x37 + LayoutText {#text} at (10,18) size 294x17 + text run at (10,18) width 294: "Sed dictum erat sit amet pharetra pretium." + LayoutBR {BR} at (313,18) size 1x17 + LayoutText {#text} at (0,36) size 339x17 + text run at (0,36) width 339: "Nullam a est vitae orci tempus tincidunt nec at dolor." + LayoutBlockFlow {P} at (0,70) size 784x36 + LayoutText {#text} at (0,0) size 730x35 + text run at (0,0) width 446: "Tests whether selection includes padding in the Y direction for inline " + text run at (445,0) width 285: "elements. This padding would ideally not be" + text run at (0,18) width 253: "included. http://crbug.com/657325#c13" +selection start: position 5 of child 0 {#text} of child 3 {B} of child 0 {P} of body +selection end: position 37 of child 0 {#text} of child 3 {B} of child 0 {P} of body
diff --git a/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/line-flow-with-floats-9-expected.txt b/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/line-flow-with-floats-9-expected.txt index bc46f17..8a565500 100644 --- a/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/line-flow-with-floats-9-expected.txt +++ b/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/line-flow-with-floats-9-expected.txt
@@ -94,6 +94,11 @@ { "object": "LayoutView #document", "rect": [485, 0, 15, 600], + "reason": "incremental" + }, + { + "object": "LayoutView #document", + "rect": [485, 0, 15, 600], "reason": "scroll" } ] @@ -109,6 +114,10 @@ "reason": "scroll" }, { + "object": "LayoutView #document", + "reason": "incremental" + }, + { "object": "LayoutBlockFlow P", "reason": "incremental" },
diff --git a/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/table-collapsed-border-expected.txt b/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/table-collapsed-border-expected.txt index 5eba99d..ce4853e 100644 --- a/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/table-collapsed-border-expected.txt +++ b/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/table-collapsed-border-expected.txt
@@ -18,6 +18,11 @@ }, { "object": "LayoutTable TABLE", + "rect": [8, 268, 101, 76], + "reason": "forced by layout" + }, + { + "object": "LayoutTable TABLE", "rect": [8, 96, 101, 76], "reason": "full" }, @@ -102,11 +107,6 @@ "reason": "layoutObject removal" }, { - "object": "LayoutTable TABLE", - "rect": [79, 268, 30, 76], - "reason": "incremental" - }, - { "object": "LayoutTableCell TD", "rect": [8, 210, 14, 20], "reason": "forced by layout" @@ -147,30 +147,6 @@ "reason": "layoutObject removal" }, { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" - }, - { "object": "LayoutTable TABLE id='t'", "reason": "style change" }, @@ -256,7 +232,7 @@ }, { "object": "LayoutTable TABLE", - "reason": "incremental" + "reason": "forced by layout" }, { "object": "LayoutTableCell TD",
diff --git a/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/table/cached-change-cell-sl-border-color-expected.txt b/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/table/cached-change-cell-sl-border-color-expected.txt index 560cab9..1904492 100644 --- a/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/table/cached-change-cell-sl-border-color-expected.txt +++ b/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/table/cached-change-cell-sl-border-color-expected.txt
@@ -7,6 +7,11 @@ "drawsContent": true, "paintInvalidations": [ { + "object": "LayoutTable (relative positioned) TABLE", + "rect": [8, 8, 114, 54], + "reason": "invalidate paint rectangle" + }, + { "object": "LayoutTableCell TD id='foo'", "rect": [8, 8, 60, 54], "reason": "style change" @@ -30,12 +35,8 @@ "reason": "layoutObject removal" }, { - "object": "LayoutTableCell TD id='foo'", - "reason": "style change" - }, - { - "object": "LayoutTableCell TD", - "reason": "style change" + "object": "LayoutTable (relative positioned) TABLE", + "reason": "invalidate paint rectangle" }, { "object": "LayoutTableCell TD id='foo'",
diff --git a/third_party/WebKit/LayoutTests/platform/win/paint/text/selection-no-clip-text-expected.png b/third_party/WebKit/LayoutTests/platform/win/paint/text/selection-no-clip-text-expected.png new file mode 100644 index 0000000..47783e8 --- /dev/null +++ b/third_party/WebKit/LayoutTests/platform/win/paint/text/selection-no-clip-text-expected.png Binary files differ
diff --git a/third_party/WebKit/LayoutTests/platform/win/paint/text/selection-no-clip-text-expected.txt b/third_party/WebKit/LayoutTests/platform/win/paint/text/selection-no-clip-text-expected.txt new file mode 100644 index 0000000..d32756f --- /dev/null +++ b/third_party/WebKit/LayoutTests/platform/win/paint/text/selection-no-clip-text-expected.txt
@@ -0,0 +1,16 @@ +layer at (0,0) size 800x600 + LayoutView at (0,0) size 800x600 +layer at (0,0) size 800x56 + LayoutBlockFlow {HTML} at (0,0) size 800x56 + LayoutBlockFlow {BODY} at (8,8) size 784x40 + LayoutBlockFlow (anonymous) at (0,0) size 784x18 + LayoutText {#text} at (0,0) size 433x17 + text run at (0,0) width 433: "Passes if top of all text in second row is visible when fully selected." + LayoutBlockFlow {DIV} at (0,18) size 784x22 + LayoutText {#text} at (0,-3) size 12x17 + text run at (0,-3) width 12: "X" + LayoutBR {BR} at (11,-3) size 1x17 + LayoutText {#text} at (0,8) size 47x17 + text run at (0,8) width 47: "YYYY" +selection start: position 0 of child 0 {#text} of body +selection end: position 4 of child 2 {#text} of child 1 {DIV} of body
diff --git a/third_party/WebKit/LayoutTests/platform/win7/fast/text/selection-with-inline-padding-expected.png b/third_party/WebKit/LayoutTests/platform/win7/fast/text/selection-with-inline-padding-expected.png new file mode 100644 index 0000000..8e5829f --- /dev/null +++ b/third_party/WebKit/LayoutTests/platform/win7/fast/text/selection-with-inline-padding-expected.png Binary files differ
diff --git a/third_party/WebKit/LayoutTests/webaudio/codec-tests/flac/flac-decode-expected.wav b/third_party/WebKit/LayoutTests/webaudio/codec-tests/flac/flac-decode-expected.wav new file mode 100644 index 0000000..4dde09129 --- /dev/null +++ b/third_party/WebKit/LayoutTests/webaudio/codec-tests/flac/flac-decode-expected.wav Binary files differ
diff --git a/third_party/WebKit/LayoutTests/webaudio/codec-tests/flac/flac-decode.html b/third_party/WebKit/LayoutTests/webaudio/codec-tests/flac/flac-decode.html new file mode 100644 index 0000000..5d2eaa7e --- /dev/null +++ b/third_party/WebKit/LayoutTests/webaudio/codec-tests/flac/flac-decode.html
@@ -0,0 +1,17 @@ +<!doctype html> +<html> + <head> + <title>Test FLAC Decoding</title> + <script src="../../resources/audio-codec-test.js"></script> + <script src="../../resources/audio-testing.js"></script> + <script src="../../resources/buffer-loader.js"></script> + </head> + + <body> + <script> + var url = "flac-test.flac"; + + window.onload = function () { runDecodingTest(url) }; + </script> + </body> +</html>
diff --git a/third_party/WebKit/LayoutTests/webaudio/codec-tests/flac/flac-test.flac b/third_party/WebKit/LayoutTests/webaudio/codec-tests/flac/flac-test.flac new file mode 100644 index 0000000..471157a --- /dev/null +++ b/third_party/WebKit/LayoutTests/webaudio/codec-tests/flac/flac-test.flac Binary files differ
diff --git a/third_party/WebKit/LayoutTests/webaudio/codec-tests/opus/opus-decode-expected.wav b/third_party/WebKit/LayoutTests/webaudio/codec-tests/opus/opus-decode-expected.wav new file mode 100644 index 0000000..1d36b67 --- /dev/null +++ b/third_party/WebKit/LayoutTests/webaudio/codec-tests/opus/opus-decode-expected.wav Binary files differ
diff --git a/third_party/WebKit/LayoutTests/webaudio/codec-tests/opus/opus-decode.html b/third_party/WebKit/LayoutTests/webaudio/codec-tests/opus/opus-decode.html new file mode 100644 index 0000000..03596deb1 --- /dev/null +++ b/third_party/WebKit/LayoutTests/webaudio/codec-tests/opus/opus-decode.html
@@ -0,0 +1,17 @@ +<!doctype html> +<html> + <head> + <title>Test OPUS Decoding</title> + <script src="../../resources/audio-codec-test.js"></script> + <script src="../../resources/audio-testing.js"></script> + <script src="../../resources/buffer-loader.js"></script> + </head> + + <body> + <script> + var url = "opus-test.opus"; + + window.onload = function () { runDecodingTest(url, 48000) }; + </script> + </body> +</html>
diff --git a/third_party/WebKit/LayoutTests/webaudio/codec-tests/opus/opus-test.opus b/third_party/WebKit/LayoutTests/webaudio/codec-tests/opus/opus-test.opus new file mode 100644 index 0000000..a1f787f --- /dev/null +++ b/third_party/WebKit/LayoutTests/webaudio/codec-tests/opus/opus-test.opus Binary files differ
diff --git a/third_party/WebKit/Source/bindings/core/v8/ScriptController.cpp b/third_party/WebKit/Source/bindings/core/v8/ScriptController.cpp index c32e0ed..949601e 100644 --- a/third_party/WebKit/Source/bindings/core/v8/ScriptController.cpp +++ b/third_party/WebKit/Source/bindings/core/v8/ScriptController.cpp
@@ -32,7 +32,6 @@ #include "bindings/core/v8/ScriptController.h" -#include "bindings/core/v8/BindingSecurity.h" #include "bindings/core/v8/ScriptSourceCode.h" #include "bindings/core/v8/ScriptValue.h" #include "bindings/core/v8/V8Binding.h" @@ -78,16 +77,6 @@ namespace blink { -bool ScriptController::canAccessFromCurrentOrigin(v8::Isolate* isolate, - Frame* frame) { - if (!frame) - return false; - return !isolate->InContext() || - BindingSecurity::shouldAllowAccessToFrame( - currentDOMWindow(isolate), frame, - BindingSecurity::ErrorReportOption::Report); -} - ScriptController::ScriptController(LocalFrame* frame) : m_windowProxyManager(WindowProxyManager::create(*frame)) {}
diff --git a/third_party/WebKit/Source/bindings/core/v8/ScriptController.h b/third_party/WebKit/Source/bindings/core/v8/ScriptController.h index 301ace9b..eb78263f 100644 --- a/third_party/WebKit/Source/bindings/core/v8/ScriptController.h +++ b/third_party/WebKit/Source/bindings/core/v8/ScriptController.h
@@ -124,8 +124,6 @@ void enableEval(); void disableEval(const String& errorMessage); - static bool canAccessFromCurrentOrigin(v8::Isolate*, Frame*); - void collectIsolatedContexts( Vector<std::pair<ScriptState*, SecurityOrigin*>>&);
diff --git a/third_party/WebKit/Source/core/BUILD.gn b/third_party/WebKit/Source/core/BUILD.gn index 969ac81..b20f845a 100644 --- a/third_party/WebKit/Source/core/BUILD.gn +++ b/third_party/WebKit/Source/core/BUILD.gn
@@ -52,8 +52,7 @@ config("blink_core_pch") { if (enable_precompiled_headers) { - # TODO(thakis): Enable for clang once it works, https://crbug.com/667891 - if (is_win && !is_clang) { + if (is_win) { # This is a string rather than a file GN knows about. It has to match # exactly what's in the /FI flag below, and what might appear in the # source code in quotes for an #include directive. @@ -1296,6 +1295,7 @@ "paint/SVGInlineTextBoxPainterTest.cpp", "paint/StubChromeClientForSPv2.h", "paint/TableCellPainterTest.cpp", + "paint/TablePainterTest.cpp", "paint/TextPainterTest.cpp", "paint/VideoPainterTest.cpp", "streams/ReadableStreamOperationsTest.cpp",
diff --git a/third_party/WebKit/Source/core/css/StyleSheetContents.cpp b/third_party/WebKit/Source/core/css/StyleSheetContents.cpp index be7d917d..1403ca2 100644 --- a/third_party/WebKit/Source/core/css/StyleSheetContents.cpp +++ b/third_party/WebKit/Source/core/css/StyleSheetContents.cpp
@@ -491,6 +491,10 @@ return root->clientSingleOwnerDocument(); } +Document* StyleSheetContents::anyOwnerDocument() const { + return rootStyleSheet()->clientAnyOwnerDocument(); +} + static bool childRulesHaveFailedOrCanceledSubresources( const HeapVector<Member<StyleRuleBase>>& rules) { for (unsigned i = 0; i < rules.size(); ++i) { @@ -531,15 +535,18 @@ return childRulesHaveFailedOrCanceledSubresources(m_childRules); } -Document* StyleSheetContents::clientSingleOwnerDocument() const { - if (!m_hasSingleOwnerDocument || clientSize() <= 0) +Document* StyleSheetContents::clientAnyOwnerDocument() const { + if (clientSize() <= 0) return nullptr; - if (m_loadingClients.size()) return (*m_loadingClients.begin())->ownerDocument(); return (*m_completedClients.begin())->ownerDocument(); } +Document* StyleSheetContents::clientSingleOwnerDocument() const { + return m_hasSingleOwnerDocument ? clientAnyOwnerDocument() : nullptr; +} + StyleSheetContents* StyleSheetContents::parentStyleSheet() const { return m_ownerRule ? m_ownerRule->parentStyleSheet() : nullptr; }
diff --git a/third_party/WebKit/Source/core/css/StyleSheetContents.h b/third_party/WebKit/Source/core/css/StyleSheetContents.h index 954dd098..5fbb04c 100644 --- a/third_party/WebKit/Source/core/css/StyleSheetContents.h +++ b/third_party/WebKit/Source/core/css/StyleSheetContents.h
@@ -86,6 +86,10 @@ Document* singleOwnerDocument() const; bool hasSingleOwnerDocument() const { return m_hasSingleOwnerDocument; } + // Gets the first owner document in the list of registered clients, or nullptr + // if there are none. + Document* anyOwnerDocument() const; + const String& charset() const { return m_parserContext.charset(); } bool loadCompleted() const; @@ -187,6 +191,7 @@ void notifyRemoveFontFaceRule(const StyleRuleFontFace*); Document* clientSingleOwnerDocument() const; + Document* clientAnyOwnerDocument() const; Member<StyleRuleImport> m_ownerRule;
diff --git a/third_party/WebKit/Source/core/css/parser/CSSLazyParsingState.cpp b/third_party/WebKit/Source/core/css/parser/CSSLazyParsingState.cpp index 1a231c6..b3d2ec5 100644 --- a/third_party/WebKit/Source/core/css/parser/CSSLazyParsingState.cpp +++ b/third_party/WebKit/Source/core/css/parser/CSSLazyParsingState.cpp
@@ -5,6 +5,7 @@ #include "core/css/parser/CSSLazyParsingState.h" #include "core/css/parser/CSSLazyPropertyParserImpl.h" #include "core/css/parser/CSSParserTokenRange.h" +#include "core/dom/Document.h" #include "core/frame/UseCounter.h" #include "platform/Histogram.h" @@ -21,7 +22,8 @@ m_parsedStyleRules(0), m_totalStyleRules(0), m_styleRulesNeededForNextMilestone(0), - m_usage(UsageGe0) { + m_usage(UsageGe0), + m_shouldUseCount(!!m_context.useCounter()) { recordUsageMetrics(); } @@ -33,9 +35,21 @@ const CSSParserContext& CSSLazyParsingState::context() { DCHECK(m_owningContents); - UseCounter* sheetCounter = UseCounter::getFrom(m_owningContents); - if (sheetCounter != m_context.useCounter()) - m_context = CSSParserContext(m_context, sheetCounter); + if (!m_shouldUseCount) { + DCHECK(!m_context.useCounter()); + return m_context; + } + + // Try as best as possible to grab a valid UseCounter if the underlying + // document has gone away. + if (!m_document) + m_document = m_owningContents->anyOwnerDocument(); + + // Always refresh the UseCounter, as the Document can outlive its + // underlying frame host causing a use-after-free of m_context's counter. + UseCounter* useCounter = UseCounter::getFrom(m_document); + if (useCounter != m_context.useCounter()) + m_context = CSSParserContext(m_context, useCounter); return m_context; } @@ -106,4 +120,9 @@ usageHistogram.count(m_usage); } +DEFINE_TRACE(CSSLazyParsingState) { + visitor->trace(m_owningContents); + visitor->trace(m_document); +} + } // namespace blink
diff --git a/third_party/WebKit/Source/core/css/parser/CSSLazyParsingState.h b/third_party/WebKit/Source/core/css/parser/CSSLazyParsingState.h index 8c77cb7..80c846f 100644 --- a/third_party/WebKit/Source/core/css/parser/CSSLazyParsingState.h +++ b/third_party/WebKit/Source/core/css/parser/CSSLazyParsingState.h
@@ -37,7 +37,7 @@ bool shouldLazilyParseProperties(const CSSSelectorList&, const CSSParserTokenRange& block) const; - DEFINE_INLINE_TRACE() { visitor->trace(m_owningContents); } + DECLARE_TRACE(); // Exposed for tests. This enum is used to back a histogram, so new values // must be appended to the end, before UsageLastValue. @@ -66,6 +66,10 @@ // it should (we DCHECK this fact). WeakMember<StyleSheetContents> m_owningContents; + // Cache the document as a proxy for caching the UseCounter. Grabbing the + // UseCounter per every property parse is a bit more expensive. + WeakMember<Document> m_document; + // Used for calculating the % of rules that ended up being parsed. int m_parsedStyleRules; int m_totalStyleRules; @@ -73,6 +77,11 @@ int m_styleRulesNeededForNextMilestone; int m_usage; + + // Whether or not use counting is enabled for parsing. This will usually be + // true, except for when stylesheets with @imports are removed from the page. + // See StyleRuleImport::setCSSStyleSheet. + const bool m_shouldUseCount; }; } // namespace blink
diff --git a/third_party/WebKit/Source/core/css/parser/CSSLazyParsingTest.cpp b/third_party/WebKit/Source/core/css/parser/CSSLazyParsingTest.cpp index 8adfe18..0efd224 100644 --- a/third_party/WebKit/Source/core/css/parser/CSSLazyParsingTest.cpp +++ b/third_party/WebKit/Source/core/css/parser/CSSLazyParsingTest.cpp
@@ -29,6 +29,7 @@ protected: HistogramTester m_histogramTester; + Persistent<StyleSheetContents> m_cachedContents; }; TEST_F(CSSLazyParsingTest, Simple) { @@ -119,42 +120,52 @@ DummyPageHolder::create(IntSize(500, 500)); CSSParserContext context(HTMLStandardMode, UseCounter::getFrom(&dummyHolder->document())); - StyleSheetContents* styleSheet = StyleSheetContents::create(context); - CSSStyleSheet* sheet = - CSSStyleSheet::create(styleSheet, dummyHolder->document()); - DCHECK(sheet); + m_cachedContents = StyleSheetContents::create(context); + { + CSSStyleSheet* sheet = + CSSStyleSheet::create(m_cachedContents, dummyHolder->document()); + DCHECK(sheet); - String sheetText = "body { background-color: red; } p { color: orange; }"; - CSSParser::parseSheet(context, styleSheet, sheetText, true /* lazy parse */); + String sheetText = "body { background-color: red; } p { color: orange; }"; + CSSParser::parseSheet(context, m_cachedContents, sheetText, + true /* lazy parse */); - // Parse the first property set with the first document as owner. - StyleRule* rule = ruleAt(styleSheet, 0); - EXPECT_FALSE(hasParsedProperties(rule)); - rule->properties(); - EXPECT_TRUE(hasParsedProperties(rule)); + // Parse the first property set with the first document as owner. + StyleRule* rule = ruleAt(m_cachedContents, 0); + EXPECT_FALSE(hasParsedProperties(rule)); + rule->properties(); + EXPECT_TRUE(hasParsedProperties(rule)); - EXPECT_EQ(&dummyHolder->document(), styleSheet->singleOwnerDocument()); + EXPECT_EQ(&dummyHolder->document(), + m_cachedContents->singleOwnerDocument()); + UseCounter& useCounter1 = dummyHolder->document().frameHost()->useCounter(); + EXPECT_TRUE(useCounter1.isCounted(CSSPropertyBackgroundColor)); + EXPECT_FALSE(useCounter1.isCounted(CSSPropertyColor)); - // Change owner document. - styleSheet->unregisterClient(sheet); + // Change owner document. + m_cachedContents->unregisterClient(sheet); + dummyHolder.reset(); + } + // Ensure no stack references to oilpan objects. + ThreadState::current()->collectAllGarbage(); + std::unique_ptr<DummyPageHolder> dummyHolder2 = DummyPageHolder::create(IntSize(500, 500)); - CSSStyleSheet::create(styleSheet, dummyHolder2->document()); + CSSStyleSheet* sheet2 = + CSSStyleSheet::create(m_cachedContents, dummyHolder2->document()); - EXPECT_EQ(&dummyHolder2->document(), styleSheet->singleOwnerDocument()); + EXPECT_EQ(&dummyHolder2->document(), m_cachedContents->singleOwnerDocument()); // Parse the second property set with the second document as owner. - StyleRule* rule2 = ruleAt(styleSheet, 1); + StyleRule* rule2 = ruleAt(m_cachedContents, 1); EXPECT_FALSE(hasParsedProperties(rule2)); rule2->properties(); EXPECT_TRUE(hasParsedProperties(rule2)); - UseCounter& useCounter1 = dummyHolder->document().frameHost()->useCounter(); UseCounter& useCounter2 = dummyHolder2->document().frameHost()->useCounter(); - EXPECT_TRUE(useCounter1.isCounted(CSSPropertyBackgroundColor)); + EXPECT_TRUE(sheet2); EXPECT_TRUE(useCounter2.isCounted(CSSPropertyColor)); EXPECT_FALSE(useCounter2.isCounted(CSSPropertyBackgroundColor)); - EXPECT_FALSE(useCounter1.isCounted(CSSPropertyColor)); } TEST_F(CSSLazyParsingTest, SimpleRuleUsagePercent) {
diff --git a/third_party/WebKit/Source/core/dom/SecurityContext.cpp b/third_party/WebKit/Source/core/dom/SecurityContext.cpp index 4a6c560..dfce655 100644 --- a/third_party/WebKit/Source/core/dom/SecurityContext.cpp +++ b/third_party/WebKit/Source/core/dom/SecurityContext.cpp
@@ -100,13 +100,12 @@ } void SecurityContext::setFeaturePolicyFromHeader( - const String& headerValue, - FeaturePolicy* parentFeaturePolicy, - Vector<String>* messages) { + const WebParsedFeaturePolicy& parsedHeader, + FeaturePolicy* parentFeaturePolicy) { DCHECK(!m_featurePolicy); m_featurePolicy = FeaturePolicy::createFromParentPolicy(parentFeaturePolicy, m_securityOrigin); - m_featurePolicy->setHeaderPolicy(headerValue, messages); + m_featurePolicy->setHeaderPolicy(parsedHeader); } } // namespace blink
diff --git a/third_party/WebKit/Source/core/dom/SecurityContext.h b/third_party/WebKit/Source/core/dom/SecurityContext.h index f625d9d..b58abaa 100644 --- a/third_party/WebKit/Source/core/dom/SecurityContext.h +++ b/third_party/WebKit/Source/core/dom/SecurityContext.h
@@ -33,6 +33,7 @@ #include "platform/heap/Handle.h" #include "platform/weborigin/Suborigin.h" #include "public/platform/WebAddressSpace.h" +#include "public/platform/WebFeaturePolicy.h" #include "public/platform/WebInsecureRequestPolicy.h" #include "public/platform/WebURLRequest.h" #include "wtf/HashSet.h" @@ -96,9 +97,8 @@ void setFeaturePolicyForTesting(std::unique_ptr<FeaturePolicy> newPolicy) { m_featurePolicy = std::move(newPolicy); } - void setFeaturePolicyFromHeader(const String& headerValue, - FeaturePolicy* parentFeaturePolicy, - Vector<String>* messages = nullptr); + void setFeaturePolicyFromHeader(const WebParsedFeaturePolicy& parsedHeader, + FeaturePolicy* parentFeaturePolicy); protected: SecurityContext();
diff --git a/third_party/WebKit/Source/core/frame/FeaturePolicyInFrameTest.cpp b/third_party/WebKit/Source/core/frame/FeaturePolicyInFrameTest.cpp index f937554..b2b45c12 100644 --- a/third_party/WebKit/Source/core/frame/FeaturePolicyInFrameTest.cpp +++ b/third_party/WebKit/Source/core/frame/FeaturePolicyInFrameTest.cpp
@@ -98,8 +98,9 @@ Vector<String> messages; std::unique_ptr<FeaturePolicy> policy1 = createFromParentPolicy(nullptr, m_originA); - policy1->setHeaderPolicy("{\"default-off\": [\"self\"], \"default-on\": []}", - &messages); + policy1->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-off\": [\"self\"], \"default-on\": []}", m_originA.get(), + &messages)); EXPECT_EQ(0UL, messages.size()); document().setFeaturePolicyForTesting(std::move(policy1)); EXPECT_TRUE(isFeatureEnabledInFrame(kDefaultOffFeature, frame())); @@ -122,7 +123,8 @@ Vector<String> messages; std::unique_ptr<FeaturePolicy> policy1 = createFromParentPolicy(nullptr, m_originA); - policy1->setHeaderPolicy("{\"default-on\": []}", &messages); + policy1->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-on\": []}", m_originA.get(), &messages)); EXPECT_EQ(0UL, messages.size()); document().setFeaturePolicyForTesting(std::move(policy1)); EXPECT_TRUE(isFeatureEnabledInFrame(kDefaultOnFeature, frame()));
diff --git a/third_party/WebKit/Source/core/frame/PerformanceMonitor.cpp b/third_party/WebKit/Source/core/frame/PerformanceMonitor.cpp index 24a1ed10..7b29652a 100644 --- a/third_party/WebKit/Source/core/frame/PerformanceMonitor.cpp +++ b/third_party/WebKit/Source/core/frame/PerformanceMonitor.cpp
@@ -175,7 +175,6 @@ : m_localRoot(localRoot) { std::fill(std::begin(m_thresholds), std::end(m_thresholds), 0); Platform::current()->currentThread()->addTaskTimeObserver(this); - Platform::current()->currentThread()->addTaskObserver(this); } PerformanceMonitor::~PerformanceMonitor() { @@ -205,7 +204,6 @@ m_subscriptions.clear(); updateInstrumentation(); Platform::current()->currentThread()->removeTaskTimeObserver(this); - Platform::current()->currentThread()->removeTaskObserver(this); } void PerformanceMonitor::updateInstrumentation() { @@ -301,7 +299,8 @@ } } -void PerformanceMonitor::willProcessTask() { +void PerformanceMonitor::willProcessTask(scheduler::TaskQueue*, + double startTime) { // Reset m_taskExecutionContext. We don't clear this in didProcessTask // as it is needed in ReportTaskTime which occurs after didProcessTask. m_taskExecutionContext = nullptr; @@ -319,17 +318,32 @@ m_handlerDepth = 0; } -void PerformanceMonitor::didProcessTask() { +void PerformanceMonitor::didProcessTask(scheduler::TaskQueue*, + double startTime, + double endTime) { if (!m_enabled) return; double layoutThreshold = m_thresholds[kLongLayout]; - if (!layoutThreshold || m_perTaskStyleAndLayoutTime < layoutThreshold) - return; - ClientThresholds* clientThresholds = m_subscriptions.get(kLongLayout); - DCHECK(clientThresholds); - for (const auto& it : *clientThresholds) { - if (it.value < m_perTaskStyleAndLayoutTime) - it.key->reportLongLayout(m_perTaskStyleAndLayoutTime); + if (layoutThreshold && m_perTaskStyleAndLayoutTime > layoutThreshold) { + ClientThresholds* clientThresholds = m_subscriptions.get(kLongLayout); + DCHECK(clientThresholds); + for (const auto& it : *clientThresholds) { + if (it.value < m_perTaskStyleAndLayoutTime) + it.key->reportLongLayout(m_perTaskStyleAndLayoutTime); + } + } + + double taskTime = endTime - startTime; + if (m_thresholds[kLongTask] && taskTime > m_thresholds[kLongTask]) { + ClientThresholds* clientThresholds = m_subscriptions.get(kLongTask); + for (const auto& it : *clientThresholds) { + if (it.value < taskTime) { + it.key->reportLongTask( + startTime, endTime, + m_taskHasMultipleContexts ? nullptr : m_taskExecutionContext, + m_taskHasMultipleContexts); + } + } } } @@ -346,26 +360,6 @@ } } -void PerformanceMonitor::ReportTaskTime(scheduler::TaskQueue*, - double startTime, - double endTime) { - if (!m_enabled) - return; - double taskTime = endTime - startTime; - if (!m_thresholds[kLongTask] || taskTime < m_thresholds[kLongTask]) - return; - - ClientThresholds* clientThresholds = m_subscriptions.get(kLongTask); - for (const auto& it : *clientThresholds) { - if (it.value < taskTime) { - it.key->reportLongTask( - startTime, endTime, - m_taskHasMultipleContexts ? nullptr : m_taskExecutionContext, - m_taskHasMultipleContexts); - } - } -} - DEFINE_TRACE(PerformanceMonitor) { visitor->trace(m_localRoot); visitor->trace(m_taskExecutionContext);
diff --git a/third_party/WebKit/Source/core/frame/PerformanceMonitor.h b/third_party/WebKit/Source/core/frame/PerformanceMonitor.h index 1c705d4d..b6801278 100644 --- a/third_party/WebKit/Source/core/frame/PerformanceMonitor.h +++ b/third_party/WebKit/Source/core/frame/PerformanceMonitor.h
@@ -28,7 +28,6 @@ // (in the local frame tree) in m_webPerformanceObservers. class CORE_EXPORT PerformanceMonitor final : public GarbageCollectedFinalized<PerformanceMonitor>, - public WebThread::TaskObserver, public scheduler::TaskTimeObserver { WTF_MAKE_NONCOPYABLE(PerformanceMonitor); @@ -118,12 +117,9 @@ double time, SourceLocation*); - // WebThread::TaskObserver implementation. - void willProcessTask() override; - void didProcessTask() override; - // scheduler::TaskTimeObserver implementation - void ReportTaskTime(scheduler::TaskQueue*, + void willProcessTask(scheduler::TaskQueue*, double startTime) override; + void didProcessTask(scheduler::TaskQueue*, double startTime, double endTime) override;
diff --git a/third_party/WebKit/Source/core/frame/PerformanceMonitorTest.cpp b/third_party/WebKit/Source/core/frame/PerformanceMonitorTest.cpp index 17c4f828..a784878 100644 --- a/third_party/WebKit/Source/core/frame/PerformanceMonitorTest.cpp +++ b/third_party/WebKit/Source/core/frame/PerformanceMonitorTest.cpp
@@ -32,15 +32,15 @@ m_monitor->alwaysWillExecuteScript(executionContext); } - void willProcessTask() { m_monitor->willProcessTask(); } - - void didProcessTask() { m_monitor->didProcessTask(); } - // scheduler::TaskTimeObserver implementation - void ReportTaskTime(scheduler::TaskQueue* queue, + void willProcessTask(scheduler::TaskQueue* queue, double startTime) { + m_monitor->willProcessTask(queue, startTime); + } + + void didProcessTask(scheduler::TaskQueue* queue, double startTime, double endTime) { - m_monitor->ReportTaskTime(queue, startTime, endTime); + m_monitor->didProcessTask(queue, startTime, endTime); } String frameContextURL(); @@ -79,18 +79,17 @@ } TEST_F(PerformanceMonitorTest, SingleScriptInTask) { - willProcessTask(); + willProcessTask(nullptr, 3719349.445172); EXPECT_EQ(0, numUniqueFrameContextsSeen()); willExecuteScript(executionContext()); EXPECT_EQ(1, numUniqueFrameContextsSeen()); - didProcessTask(); - ReportTaskTime(nullptr, 3719349.445172, 3719349.5561923); // Long task + didProcessTask(nullptr, 3719349.445172, 3719349.5561923); // Long task EXPECT_EQ(1, numUniqueFrameContextsSeen()); EXPECT_EQ("https://example.com/foo", frameContextURL()); } TEST_F(PerformanceMonitorTest, MultipleScriptsInTask_SingleContext) { - willProcessTask(); + willProcessTask(nullptr, 3719349.445172); EXPECT_EQ(0, numUniqueFrameContextsSeen()); willExecuteScript(executionContext()); EXPECT_EQ(1, numUniqueFrameContextsSeen()); @@ -98,14 +97,13 @@ willExecuteScript(executionContext()); EXPECT_EQ(1, numUniqueFrameContextsSeen()); - didProcessTask(); - ReportTaskTime(nullptr, 3719349.445172, 3719349.5561923); // Long task + didProcessTask(nullptr, 3719349.445172, 3719349.5561923); // Long task EXPECT_EQ(1, numUniqueFrameContextsSeen()); EXPECT_EQ("https://example.com/foo", frameContextURL()); } TEST_F(PerformanceMonitorTest, MultipleScriptsInTask_MultipleContexts) { - willProcessTask(); + willProcessTask(nullptr, 3719349.445172); EXPECT_EQ(0, numUniqueFrameContextsSeen()); willExecuteScript(executionContext()); EXPECT_EQ(1, numUniqueFrameContextsSeen()); @@ -113,20 +111,18 @@ willExecuteScript(anotherExecutionContext()); EXPECT_EQ(2, numUniqueFrameContextsSeen()); - didProcessTask(); - ReportTaskTime(nullptr, 3719349.445172, 3719349.5561923); // Long task + didProcessTask(nullptr, 3719349.445172, 3719349.5561923); // Long task EXPECT_EQ(2, numUniqueFrameContextsSeen()); EXPECT_EQ("", frameContextURL()); } TEST_F(PerformanceMonitorTest, NoScriptInLongTask) { - willProcessTask(); + willProcessTask(nullptr, 3719349.445172); willExecuteScript(executionContext()); - didProcessTask(); - ReportTaskTime(nullptr, 3719349.445172, 3719349.445182); - willProcessTask(); - didProcessTask(); - ReportTaskTime(nullptr, 3719349.445172, 3719349.5561923); // Long task + didProcessTask(nullptr, 3719349.445172, 3719349.445182); + + willProcessTask(nullptr, 3719349.445172); + didProcessTask(nullptr, 3719349.445172, 3719349.5561923); // Long task // Without presence of Script, FrameContext URL is not available EXPECT_EQ(0, numUniqueFrameContextsSeen()); }
diff --git a/third_party/WebKit/Source/core/html/HTMLFrameElementBase.cpp b/third_party/WebKit/Source/core/html/HTMLFrameElementBase.cpp index 2eb31fc9..05e28a7f 100644 --- a/third_party/WebKit/Source/core/html/HTMLFrameElementBase.cpp +++ b/third_party/WebKit/Source/core/html/HTMLFrameElementBase.cpp
@@ -23,6 +23,7 @@ #include "core/html/HTMLFrameElementBase.h" +#include "bindings/core/v8/BindingSecurity.h" #include "bindings/core/v8/ScriptController.h" #include "bindings/core/v8/ScriptEventListener.h" #include "core/HTMLNames.h" @@ -56,10 +57,18 @@ const KURL& completeURL = document().completeURL(m_URL); - if (protocolIsJavaScript(completeURL)) { - if (contentFrame() && - !ScriptController::canAccessFromCurrentOrigin(toIsolate(&document()), - contentFrame())) + if (contentFrame() && protocolIsJavaScript(completeURL)) { + // Check if the caller can execute script in the context of the content + // frame. NB: This check can be invoked without any JS on the stack for some + // parser operations. In such case, we use the origin of the frame element's + // containing document as the caller context. + v8::Isolate* isolate = toIsolate(&document()); + LocalDOMWindow* accessingWindow = isolate->InContext() + ? currentDOMWindow(isolate) + : document().domWindow(); + if (!BindingSecurity::shouldAllowAccessToFrame( + accessingWindow, contentFrame(), + BindingSecurity::ErrorReportOption::Report)) return false; }
diff --git a/third_party/WebKit/Source/core/layout/LayoutDetailsMarker.h b/third_party/WebKit/Source/core/layout/LayoutDetailsMarker.h index ad9e858..a118552 100644 --- a/third_party/WebKit/Source/core/layout/LayoutDetailsMarker.h +++ b/third_party/WebKit/Source/core/layout/LayoutDetailsMarker.h
@@ -40,6 +40,9 @@ return type == LayoutObjectDetailsMarker || LayoutBlockFlow::isOfType(type); } void paint(const PaintInfo&, const LayoutPoint&) const override; + bool paintedOutputOfObjectHasNoEffectRegardlessOfSize() const override { + return false; + } bool isOpen() const; };
diff --git a/third_party/WebKit/Source/core/layout/LayoutObject.h b/third_party/WebKit/Source/core/layout/LayoutObject.h index a52f630..0528c06b 100644 --- a/third_party/WebKit/Source/core/layout/LayoutObject.h +++ b/third_party/WebKit/Source/core/layout/LayoutObject.h
@@ -1309,18 +1309,6 @@ void setShouldDoFullPaintInvalidationIncludingNonCompositingDescendants(); - // Returns true if the object itself will not generate any effective painted - // output no matter what size the object is. For example, this function can - // return false for an object whose size is currently 0x0 but would have - // effective painted output if it was set a non-empty size. - // It's used to skip unforced paint invalidation (which is when - // shouldDoFullPaintInvalidation is false, but mayNeedPaintInvalidation or - // childShouldCheckForPaintInvalidation is true) to avoid unnecessary paint - // invalidations of empty areas covered by such objects. - virtual bool paintedOutputOfObjectHasNoEffectRegardlessOfSize() const { - return false; - } - // Returns the rect that should have paint invalidated whenever this object // changes. The rect is in the view's coordinate space. This method deals with // outlines and overflow.
diff --git a/third_party/WebKit/Source/core/layout/LayoutTable.cpp b/third_party/WebKit/Source/core/layout/LayoutTable.cpp index cb63f2c..5542392 100644 --- a/third_party/WebKit/Source/core/layout/LayoutTable.cpp +++ b/third_party/WebKit/Source/core/layout/LayoutTable.cpp
@@ -99,7 +99,8 @@ // If border was changed, invalidate collapsed borders cache. if (!needsLayout() && oldStyle && oldStyle->border() != style()->border()) - invalidateCollapsedBorders(); + invalidateCollapsedBorders(PaintInvalidationStyleChange); + if (LayoutTableBoxComponent::doCellsHaveDirtyWidth(*this, *this, diff, *oldStyle)) markAllCellsWidthsDirtyAndOrNeedsLayout(MarkDirtyAndNeedsLayout); @@ -732,7 +733,7 @@ updateLayerTransformAfterLayout(); // Layout was changed, so probably borders too. - invalidateCollapsedBorders(); + invalidateCollapsedBorders(PaintInvalidationForcedByLayout); computeOverflow(clientLogicalBottom()); updateAfterLayout(); @@ -751,13 +752,19 @@ clearNeedsLayout(); } -void LayoutTable::invalidateCollapsedBorders() { - m_collapsedBorders.clear(); +void LayoutTable::invalidateCollapsedBorders(PaintInvalidationReason reason) { + DCHECK(reason == PaintInvalidationStyleChange || + reason == PaintInvalidationForcedByLayout); + + m_collapsedBordersInfo = nullptr; if (!collapseBorders()) return; m_collapsedBordersValid = false; - setMayNeedPaintInvalidation(); + if (reason == PaintInvalidationForcedByLayout) + setShouldDoFullPaintInvalidation(reason); + else + setMayNeedPaintInvalidation(); } // Collect all the unique border values that we want to paint in a sorted list. @@ -765,10 +772,15 @@ // cache of its containing section, and invalidates itself if any border // changes. This method doesn't affect layout. void LayoutTable::recalcCollapsedBordersIfNeeded() { - if (m_collapsedBordersValid || !collapseBorders()) + if (m_collapsedBordersValid) return; m_collapsedBordersValid = true; - m_collapsedBorders.clear(); + m_collapsedBordersInfo = nullptr; + if (!collapseBorders()) + return; + + LayoutRect boundsOfChangedCells; + Vector<CollapsedBorderValue> values; for (LayoutObject* section = firstChild(); section; section = section->nextSibling()) { if (!section->isTableSection()) @@ -777,12 +789,23 @@ row = row->nextRow()) { for (LayoutTableCell* cell = row->firstCell(); cell; cell = cell->nextCell()) { - ASSERT(cell->table() == this); - cell->collectBorderValues(m_collapsedBorders); + DCHECK(cell->table() == this); + bool cellChanged = cell->collectBorderValues(values); + if (cellChanged && !shouldDoFullPaintInvalidation()) { + LayoutRect cellRect = cell->localVisualRect(); + cell->mapToVisualRectInAncestorSpace(this, cellRect); + boundsOfChangedCells.unite(cellRect); + } } } } - LayoutTableCell::sortBorderValues(m_collapsedBorders); + if (!values.isEmpty()) { + LayoutTableCell::sortBorderValues(values); + m_collapsedBordersInfo = + wrapUnique(new CollapsedBordersInfo(std::move(values))); + } + + invalidatePaintRectangle(boundsOfChangedCells); } void LayoutTable::addOverflowFromChildren() { @@ -1662,10 +1685,10 @@ PaintInvalidationReason LayoutTable::invalidatePaintIfNeeded( const PaintInvalidationState& paintInvalidationState) { - if (collapseBorders() && !m_collapsedBorders.isEmpty()) + if (hasCollapsedBorders()) { paintInvalidationState.paintingLayer() .setNeedsPaintPhaseDescendantBlockBackgrounds(); - + } return LayoutBlock::invalidatePaintIfNeeded(paintInvalidationState); }
diff --git a/third_party/WebKit/Source/core/layout/LayoutTable.h b/third_party/WebKit/Source/core/layout/LayoutTable.h index 09ac5911..2148c78 100644 --- a/third_party/WebKit/Source/core/layout/LayoutTable.h +++ b/third_party/WebKit/Source/core/layout/LayoutTable.h
@@ -29,7 +29,9 @@ #include "core/CSSPropertyNames.h" #include "core/CoreExport.h" #include "core/layout/LayoutBlock.h" +#include "core/paint/PaintResult.h" #include "core/style/CollapsedBorderValue.h" +#include "platform/graphics/paint/CullRect.h" #include "wtf/Vector.h" #include <memory> @@ -377,8 +379,7 @@ LayoutTableCell* cellBefore(const LayoutTableCell*) const; LayoutTableCell* cellAfter(const LayoutTableCell*) const; - typedef Vector<CollapsedBorderValue> CollapsedBorderValues; - void invalidateCollapsedBorders(); + void invalidateCollapsedBorders(PaintInvalidationReason); bool hasSections() const { return m_head || m_foot || m_firstBody; } @@ -407,9 +408,23 @@ void paintMask(const PaintInfo&, const LayoutPoint&) const final; - const CollapsedBorderValues& collapsedBorders() const { - ASSERT(m_collapsedBordersValid); - return m_collapsedBorders; + struct CollapsedBordersInfo { + explicit CollapsedBordersInfo(const Vector<CollapsedBorderValue>& values) + : values(std::move(values)) {} + + PaintResult lastPaintResult = FullyPainted; + CullRect lastPaintRect; + const Vector<CollapsedBorderValue> values; + }; + + bool hasCollapsedBorders() const { + DCHECK(m_collapsedBordersValid); + DCHECK(!m_collapsedBordersInfo || collapseBorders()); + return !!m_collapsedBordersInfo; + } + CollapsedBordersInfo& getCollapsedBordersInfo() const { + DCHECK(hasCollapsedBorders()); + return *m_collapsedBordersInfo; } void subtractCaptionRect(LayoutRect&) const; @@ -552,7 +567,7 @@ // need to compare a cells border against all the adjoining cells, rows, // row groups, column, column groups and table. Thus we cache them in this // field. - CollapsedBorderValues m_collapsedBorders; + std::unique_ptr<CollapsedBordersInfo> m_collapsedBordersInfo; bool m_collapsedBordersValid : 1; mutable bool m_hasColElements : 1;
diff --git a/third_party/WebKit/Source/core/layout/LayoutTableCell.cpp b/third_party/WebKit/Source/core/layout/LayoutTableCell.cpp index e8195e9..24d98a15 100644 --- a/third_party/WebKit/Source/core/layout/LayoutTableCell.cpp +++ b/third_party/WebKit/Source/core/layout/LayoutTableCell.cpp
@@ -67,34 +67,6 @@ updateColAndRowSpanFlags(); } -LayoutTableCell::CollapsedBorderValues::CollapsedBorderValues( - const LayoutTable& layoutTable, - const CollapsedBorderValue& startBorder, - const CollapsedBorderValue& endBorder, - const CollapsedBorderValue& beforeBorder, - const CollapsedBorderValue& afterBorder) - : m_layoutTable(layoutTable), - m_startBorder(startBorder), - m_endBorder(endBorder), - m_beforeBorder(beforeBorder), - m_afterBorder(afterBorder) {} - -void LayoutTableCell::CollapsedBorderValues::setCollapsedBorderValues( - const CollapsedBorderValues& other) { - m_startBorder = other.startBorder(); - m_endBorder = other.endBorder(); - m_beforeBorder = other.beforeBorder(); - m_afterBorder = other.afterBorder(); -} - -String LayoutTableCell::CollapsedBorderValues::debugName() const { - return "CollapsedBorderValues"; -} - -LayoutRect LayoutTableCell::CollapsedBorderValues::visualRect() const { - return m_layoutTable.visualRect(); -} - void LayoutTableCell::willBeRemovedFromTree() { LayoutBlockFlow::willBeRemovedFromTree(); @@ -500,7 +472,7 @@ return; if (!table->selfNeedsLayout() && !table->normalChildNeedsLayout() && oldStyle && oldStyle->border() != style()->border()) - table->invalidateCollapsedBorders(); + table->invalidateCollapsedBorders(PaintInvalidationStyleChange); if (LayoutTableBoxComponent::doCellsHaveDirtyWidth(*this, *table, diff, *oldStyle)) { @@ -1303,7 +1275,7 @@ TableCellPainter(*this).paint(paintInfo, paintOffset); } -static void addBorderStyle(LayoutTable::CollapsedBorderValues& borderValues, +static void addBorderStyle(Vector<CollapsedBorderValue>& borderValues, CollapsedBorderValue borderValue) { if (!borderValue.isVisible()) return; @@ -1315,56 +1287,34 @@ borderValues.append(borderValue); } -void LayoutTableCell::collectBorderValues( - LayoutTable::CollapsedBorderValues& borderValues) { - CollapsedBorderValues newValues( - *table(), computeCollapsedStartBorder(), computeCollapsedEndBorder(), - computeCollapsedBeforeBorder(), computeCollapsedAfterBorder()); +bool LayoutTableCell::collectBorderValues( + Vector<CollapsedBorderValue>& borderValues) { + CollapsedBorderValues newValues = { + computeCollapsedStartBorder(), computeCollapsedEndBorder(), + computeCollapsedBeforeBorder(), computeCollapsedAfterBorder()}; bool changed = false; - if (!newValues.startBorder().isVisible() && - !newValues.endBorder().isVisible() && - !newValues.beforeBorder().isVisible() && - !newValues.afterBorder().isVisible()) { + if (newValues.allBordersAreInvisible()) { changed = !!m_collapsedBorderValues; m_collapsedBorderValues = nullptr; } else if (!m_collapsedBorderValues) { changed = true; - m_collapsedBorderValues = wrapUnique(new CollapsedBorderValues( - *table(), newValues.startBorder(), newValues.endBorder(), - newValues.beforeBorder(), newValues.afterBorder())); + m_collapsedBorderValues = wrapUnique(new CollapsedBorderValues(newValues)); } else { - // We check visuallyEquals so that the table cell is invalidated only if a - // changed collapsed border is visible in the first place. - changed = !m_collapsedBorderValues->startBorder().visuallyEquals( - newValues.startBorder()) || - !m_collapsedBorderValues->endBorder().visuallyEquals( - newValues.endBorder()) || - !m_collapsedBorderValues->beforeBorder().visuallyEquals( - newValues.beforeBorder()) || - !m_collapsedBorderValues->afterBorder().visuallyEquals( - newValues.afterBorder()); + changed = !m_collapsedBorderValues->bordersVisuallyEqual(newValues); if (changed) - m_collapsedBorderValues->setCollapsedBorderValues(newValues); + *m_collapsedBorderValues = newValues; } - // If collapsed borders changed, invalidate the cell's display item client on - // the table's backing. - // TODO(crbug.com/451090#c5): Need a way to invalidate/repaint the borders - // only. - if (changed) - ObjectPaintInvalidator(*table()) - .slowSetPaintingLayerNeedsRepaintAndInvalidateDisplayItemClient( - *this, PaintInvalidationStyleChange); - - addBorderStyle(borderValues, newValues.startBorder()); - addBorderStyle(borderValues, newValues.endBorder()); - addBorderStyle(borderValues, newValues.beforeBorder()); - addBorderStyle(borderValues, newValues.afterBorder()); + addBorderStyle(borderValues, newValues.startBorder); + addBorderStyle(borderValues, newValues.endBorder); + addBorderStyle(borderValues, newValues.beforeBorder); + addBorderStyle(borderValues, newValues.afterBorder); + return changed; } void LayoutTableCell::sortBorderValues( - LayoutTable::CollapsedBorderValues& borderValues) { + Vector<CollapsedBorderValue>& borderValues) { std::sort(borderValues.begin(), borderValues.end(), compareBorders); } @@ -1459,8 +1409,6 @@ return; ObjectPaintInvalidator invalidator(*this); - if (m_collapsedBorderValues) - invalidator.invalidateDisplayItemClient(*m_collapsedBorderValues, reason); if (m_rowBackgroundDisplayItemClient) { invalidator.invalidateDisplayItemClient(*m_rowBackgroundDisplayItemClient, reason);
diff --git a/third_party/WebKit/Source/core/layout/LayoutTableCell.h b/third_party/WebKit/Source/core/layout/LayoutTableCell.h index 12f5210..e9a9716 100644 --- a/third_party/WebKit/Source/core/layout/LayoutTableCell.h +++ b/third_party/WebKit/Source/core/layout/LayoutTableCell.h
@@ -185,8 +185,9 @@ int borderBefore() const override; int borderAfter() const override; - void collectBorderValues(LayoutTable::CollapsedBorderValues&); - static void sortBorderValues(LayoutTable::CollapsedBorderValues&); + // Returns true if any collapsed borders related to this cell changed. + bool collectBorderValues(Vector<CollapsedBorderValue>&); + static void sortBorderValues(Vector<CollapsedBorderValue>&); void layout() override; @@ -289,33 +290,22 @@ bool backgroundIsKnownToBeOpaqueInRect(const LayoutRect&) const override; void invalidateDisplayItemClients(PaintInvalidationReason) const override; - // TODO(wkorman): Consider renaming to more clearly differentiate from - // CollapsedBorderValue. - class CollapsedBorderValues : public DisplayItemClient { - public: - CollapsedBorderValues(const LayoutTable&, - const CollapsedBorderValue& startBorder, - const CollapsedBorderValue& endBorder, - const CollapsedBorderValue& beforeBorder, - const CollapsedBorderValue& afterBorder); + struct CollapsedBorderValues { + CollapsedBorderValue startBorder; + CollapsedBorderValue endBorder; + CollapsedBorderValue beforeBorder; + CollapsedBorderValue afterBorder; - const CollapsedBorderValue& startBorder() const { return m_startBorder; } - const CollapsedBorderValue& endBorder() const { return m_endBorder; } - const CollapsedBorderValue& beforeBorder() const { return m_beforeBorder; } - const CollapsedBorderValue& afterBorder() const { return m_afterBorder; } - - void setCollapsedBorderValues(const CollapsedBorderValues& other); - - // DisplayItemClient methods. - String debugName() const; - LayoutRect visualRect() const; - - private: - const LayoutTable& m_layoutTable; - CollapsedBorderValue m_startBorder; - CollapsedBorderValue m_endBorder; - CollapsedBorderValue m_beforeBorder; - CollapsedBorderValue m_afterBorder; + bool allBordersAreInvisible() const { + return !startBorder.isVisible() && !endBorder.isVisible() && + !beforeBorder.isVisible() && !afterBorder.isVisible(); + } + bool bordersVisuallyEqual(const CollapsedBorderValues& other) const { + return startBorder.visuallyEquals(other.startBorder) && + endBorder.visuallyEquals(other.endBorder) && + beforeBorder.visuallyEquals(other.beforeBorder) && + afterBorder.visuallyEquals(other.afterBorder); + } }; class RowBackgroundDisplayItemClient : public DisplayItemClient { @@ -331,6 +321,7 @@ }; bool usesCompositedCellDisplayItemClients() const; + const CollapsedBorderValues* collapsedBorderValues() const { return m_collapsedBorderValues.get(); } @@ -350,6 +341,8 @@ void ensureIsReadyForPaintInvalidation() override; + LayoutRect localVisualRect() const override; + protected: void styleDidChange(StyleDifference, const ComputedStyle* oldStyle) override; void computePreferredLogicalWidths() override; @@ -373,7 +366,6 @@ void paintMask(const PaintInfo&, const LayoutPoint&) const override; LayoutSize offsetFromContainer(const LayoutObject*) const override; - LayoutRect localVisualRect() const override; int borderHalfLeft(bool outer) const; int borderHalfRight(bool outer) const; @@ -411,8 +403,8 @@ // See also https://code.google.com/p/chromium/issues/detail?id=128227 for // some history. // - // Those functions are called when the cache (m_collapsedBorders) is - // invalidated on LayoutTable. + // Those functions are called before paint invalidation if the collapsed + // borders cache is invalidated on LayoutTable. CollapsedBorderValue computeCollapsedStartBorder( IncludeBorderColorOrNot = IncludeBorderColor) const; CollapsedBorderValue computeCollapsedEndBorder(
diff --git a/third_party/WebKit/Source/core/layout/LayoutTableCol.cpp b/third_party/WebKit/Source/core/layout/LayoutTableCol.cpp index ba923455..b136ac8 100644 --- a/third_party/WebKit/Source/core/layout/LayoutTableCol.cpp +++ b/third_party/WebKit/Source/core/layout/LayoutTableCol.cpp
@@ -60,7 +60,7 @@ // the next one can't be, so don't even check its condition. if (!table->selfNeedsLayout() && !table->normalChildNeedsLayout() && oldStyle->border() != style()->border()) { - table->invalidateCollapsedBorders(); + table->invalidateCollapsedBorders(PaintInvalidationStyleChange); } else if ((oldStyle->logicalWidth() != style()->logicalWidth()) || LayoutTableBoxComponent::doCellsHaveDirtyWidth(*this, *table, diff, *oldStyle)) {
diff --git a/third_party/WebKit/Source/core/layout/LayoutTableRow.cpp b/third_party/WebKit/Source/core/layout/LayoutTableRow.cpp index 03d700d..41d43d6 100644 --- a/third_party/WebKit/Source/core/layout/LayoutTableRow.cpp +++ b/third_party/WebKit/Source/core/layout/LayoutTableRow.cpp
@@ -74,7 +74,7 @@ if (!table->selfNeedsLayout() && !table->normalChildNeedsLayout() && oldStyle->border() != style()->border()) - table->invalidateCollapsedBorders(); + table->invalidateCollapsedBorders(PaintInvalidationStyleChange); if (LayoutTableBoxComponent::doCellsHaveDirtyWidth(*this, *table, diff, *oldStyle)) {
diff --git a/third_party/WebKit/Source/core/layout/LayoutTableSection.cpp b/third_party/WebKit/Source/core/layout/LayoutTableSection.cpp index 572ded7..79f4c10 100644 --- a/third_party/WebKit/Source/core/layout/LayoutTableSection.cpp +++ b/third_party/WebKit/Source/core/layout/LayoutTableSection.cpp
@@ -132,7 +132,7 @@ if (!table->selfNeedsLayout() && !table->normalChildNeedsLayout() && oldStyle->border() != style()->border()) - table->invalidateCollapsedBorders(); + table->invalidateCollapsedBorders(PaintInvalidationStyleChange); if (LayoutTableBoxComponent::doCellsHaveDirtyWidth(*this, *table, diff, *oldStyle))
diff --git a/third_party/WebKit/Source/core/layout/LayoutTableSection.h b/third_party/WebKit/Source/core/layout/LayoutTableSection.h index 5c0b9f70..6c82c3ad 100644 --- a/third_party/WebKit/Source/core/layout/LayoutTableSection.h +++ b/third_party/WebKit/Source/core/layout/LayoutTableSection.h
@@ -57,6 +57,13 @@ unsigned m_end; }; +inline bool operator==(const CellSpan& a, const CellSpan& b) { + return a.start() == b.start() && a.end() == b.end(); +} +inline bool operator!=(const CellSpan& a, const CellSpan& b) { + return !(a == b); +} + class LayoutTableCell; class LayoutTableRow; @@ -296,8 +303,13 @@ // columnPos vectors. LayoutRect logicalRectForWritingModeAndDirection(const LayoutRect&) const; + CellSpan fullTableRowSpan() const { return CellSpan(0, m_grid.size()); } + CellSpan fullTableEffectiveColumnSpan() const { + return CellSpan(0, table()->numEffectiveColumns()); + } CellSpan dirtiedRows(const LayoutRect& visualRect) const; CellSpan dirtiedEffectiveColumns(const LayoutRect& visualRect) const; + const HashSet<LayoutTableCell*>& overflowingCells() const { return m_overflowingCells; } @@ -401,11 +413,6 @@ void computeOverflowFromCells(unsigned totalRows, unsigned nEffCols); - CellSpan fullTableRowSpan() const { return CellSpan(0, m_grid.size()); } - CellSpan fullTableEffectiveColumnSpan() const { - return CellSpan(0, table()->numEffectiveColumns()); - } - // These two functions take a rectangle as input that has been flipped by // logicalRectForWritingModeAndDirection. // The returned span of rows or columns is end-exclusive, and empty if
diff --git a/third_party/WebKit/Source/core/layout/LayoutView.cpp b/third_party/WebKit/Source/core/layout/LayoutView.cpp index ef2a285..00192c6 100644 --- a/third_party/WebKit/Source/core/layout/LayoutView.cpp +++ b/third_party/WebKit/Source/core/layout/LayoutView.cpp
@@ -1026,4 +1026,13 @@ return rect; } +bool LayoutView::paintedOutputOfObjectHasNoEffectRegardlessOfSize() const { + // Frame scroll corner is painted using LayoutView as the display item client. + if (!RuntimeEnabledFeatures::rootLayerScrollingEnabled() && + (frameView()->horizontalScrollbar() || frameView()->verticalScrollbar())) + return false; + + return LayoutBlockFlow::paintedOutputOfObjectHasNoEffectRegardlessOfSize(); +} + } // namespace blink
diff --git a/third_party/WebKit/Source/core/layout/LayoutView.h b/third_party/WebKit/Source/core/layout/LayoutView.h index c2adf61..d2036212 100644 --- a/third_party/WebKit/Source/core/layout/LayoutView.h +++ b/third_party/WebKit/Source/core/layout/LayoutView.h
@@ -265,6 +265,8 @@ int viewLogicalWidthForBoxSizing() const; int viewLogicalHeightForBoxSizing() const; + bool paintedOutputOfObjectHasNoEffectRegardlessOfSize() const override; + UntracedMember<FrameView> m_frameView; // The current selection represented as 2 boundaries.
diff --git a/third_party/WebKit/Source/core/layout/line/RootInlineBox.cpp b/third_party/WebKit/Source/core/layout/line/RootInlineBox.cpp index cf52c32..811f819 100644 --- a/third_party/WebKit/Source/core/layout/line/RootInlineBox.cpp +++ b/third_party/WebKit/Source/core/layout/line/RootInlineBox.cpp
@@ -389,7 +389,6 @@ LayoutUnit RootInlineBox::selectionTop() const { LayoutUnit selectionTop = m_lineTop; - if (m_hasAnnotationsBefore) selectionTop -= !getLineLayoutItem().style()->isFlippedLinesWritingMode() ? computeOverAnnotationAdjustment(m_lineTop) @@ -399,25 +398,7 @@ !prevRootBox()) return selectionTop; - LayoutUnit prevBottom = prevRootBox()->selectionBottom(); - if (prevBottom < selectionTop && block().containsFloats()) { - // This line has actually been moved further down, probably from a large - // line-height, but possibly because the line was forced to clear floats. - // If so, let's check the offsets, and only be willing to use the previous - // line's bottom if the offsets are greater on both sides. - LayoutUnit prevLeft = - block().logicalLeftOffsetForLine(prevBottom, DoNotIndentText); - LayoutUnit prevRight = - block().logicalRightOffsetForLine(prevBottom, DoNotIndentText); - LayoutUnit newLeft = - block().logicalLeftOffsetForLine(selectionTop, DoNotIndentText); - LayoutUnit newRight = - block().logicalRightOffsetForLine(selectionTop, DoNotIndentText); - if (prevLeft > newLeft || prevRight < newRight) - return selectionTop; - } - - return prevBottom; + return std::min(selectionTop, prevRootBox()->selectionBottom()); } LayoutUnit RootInlineBox::selectionBottom() const { @@ -434,25 +415,7 @@ !nextRootBox()) return selectionBottom; - LayoutUnit nextTop = nextRootBox()->selectionTop(); - if (nextTop > selectionBottom && block().containsFloats()) { - // The next line has actually been moved further over, probably from a large - // line-height, but possibly because the line was forced to clear floats. - // If so, let's check the offsets, and only be willing to use the next - // line's top if the offsets are greater on both sides. - LayoutUnit nextLeft = - block().logicalLeftOffsetForLine(nextTop, DoNotIndentText); - LayoutUnit nextRight = - block().logicalRightOffsetForLine(nextTop, DoNotIndentText); - LayoutUnit newLeft = - block().logicalLeftOffsetForLine(selectionBottom, DoNotIndentText); - LayoutUnit newRight = - block().logicalRightOffsetForLine(selectionBottom, DoNotIndentText); - if (nextLeft > newLeft || nextRight < newRight) - return selectionBottom; - } - - return nextTop; + return std::max(selectionBottom, nextRootBox()->selectionTop()); } LayoutUnit RootInlineBox::blockDirectionPointInLine() const {
diff --git a/third_party/WebKit/Source/core/layout/svg/LayoutSVGBlock.h b/third_party/WebKit/Source/core/layout/svg/LayoutSVGBlock.h index cfc5c091..56077911 100644 --- a/third_party/WebKit/Source/core/layout/svg/LayoutSVGBlock.h +++ b/third_party/WebKit/Source/core/layout/svg/LayoutSVGBlock.h
@@ -80,6 +80,11 @@ const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction) override; + + // The inherited version doesn't check for SVG effects. + bool paintedOutputOfObjectHasNoEffectRegardlessOfSize() const override { + return false; + } }; } // namespace blink
diff --git a/third_party/WebKit/Source/core/loader/FrameLoader.cpp b/third_party/WebKit/Source/core/loader/FrameLoader.cpp index a577dbe..213982d 100644 --- a/third_party/WebKit/Source/core/loader/FrameLoader.cpp +++ b/third_party/WebKit/Source/core/loader/FrameLoader.cpp
@@ -97,6 +97,7 @@ #include "platform/weborigin/SecurityPolicy.h" #include "platform/weborigin/Suborigin.h" #include "public/platform/WebCachePolicy.h" +#include "public/platform/WebFeaturePolicy.h" #include "public/platform/WebURLRequest.h" #include "wtf/AutoReset.h" #include "wtf/text/CString.h" @@ -584,15 +585,19 @@ m_documentLoader->response().httpHeaderField( HTTPNames::Feature_Policy); Vector<String> messages; + const WebParsedFeaturePolicy& parsedHeader = + FeaturePolicy::parseFeaturePolicy( + featurePolicyHeader, + m_frame->securityContext()->getSecurityOrigin(), &messages); m_frame->securityContext()->setFeaturePolicyFromHeader( - featurePolicyHeader, parentFeaturePolicy, &messages); + parsedHeader, parentFeaturePolicy); for (auto& message : messages) { m_frame->document()->addConsoleMessage(ConsoleMessage::create( OtherMessageSource, ErrorMessageLevel, "Error with Feature-Policy header: " + message)); } - if (!featurePolicyHeader.isEmpty()) - client()->didSetFeaturePolicyHeader(featurePolicyHeader); + if (!parsedHeader.isEmpty()) + client()->didSetFeaturePolicyHeader(parsedHeader); } }
diff --git a/third_party/WebKit/Source/core/loader/FrameLoaderClient.h b/third_party/WebKit/Source/core/loader/FrameLoaderClient.h index 56ad147d..79694244 100644 --- a/third_party/WebKit/Source/core/loader/FrameLoaderClient.h +++ b/third_party/WebKit/Source/core/loader/FrameLoaderClient.h
@@ -46,6 +46,7 @@ #include "platform/network/ResourceLoadPriority.h" #include "platform/weborigin/Referrer.h" #include "public/platform/WebEffectiveConnectionType.h" +#include "public/platform/WebFeaturePolicy.h" #include "public/platform/WebInsecureRequestPolicy.h" #include "public/platform/WebLoadingBehaviorFlag.h" #include "wtf/Forward.h" @@ -275,7 +276,8 @@ virtual void didChangeSandboxFlags(Frame* childFrame, SandboxFlags) {} - virtual void didSetFeaturePolicyHeader(const String& headerValue) {} + virtual void didSetFeaturePolicyHeader( + const WebParsedFeaturePolicy& parsedHeader) {} // Called when a new Content Security Policy is added to the frame's document. // This can be triggered by handling of HTTP headers, handling of <meta>
diff --git a/third_party/WebKit/Source/core/paint/PaintInvalidator.cpp b/third_party/WebKit/Source/core/paint/PaintInvalidator.cpp index 9982e8a0..8756306e 100644 --- a/third_party/WebKit/Source/core/paint/PaintInvalidator.cpp +++ b/third_party/WebKit/Source/core/paint/PaintInvalidator.cpp
@@ -214,11 +214,8 @@ context.paintingLayer->setNeedsPaintPhaseDescendantBlockBackgrounds(); } - if (object.isTable()) { - const LayoutTable& table = toLayoutTable(object); - if (table.collapseBorders() && !table.collapsedBorders().isEmpty()) - context.paintingLayer->setNeedsPaintPhaseDescendantBlockBackgrounds(); - } + if (object.isTable() && toLayoutTable(object).hasCollapsedBorders()) + context.paintingLayer->setNeedsPaintPhaseDescendantBlockBackgrounds(); } namespace {
diff --git a/third_party/WebKit/Source/core/paint/TableCellPainter.cpp b/third_party/WebKit/Source/core/paint/TableCellPainter.cpp index 856d8ecc..f3102f6 100644 --- a/third_party/WebKit/Source/core/paint/TableCellPainter.cpp +++ b/third_party/WebKit/Source/core/paint/TableCellPainter.cpp
@@ -19,40 +19,40 @@ const ComputedStyle& styleForCellFlow, const LayoutTableCell::CollapsedBorderValues& values) { if (styleForCellFlow.isHorizontalWritingMode()) { - return styleForCellFlow.isLeftToRightDirection() ? values.startBorder() - : values.endBorder(); + return styleForCellFlow.isLeftToRightDirection() ? values.startBorder + : values.endBorder; } - return styleForCellFlow.isFlippedBlocksWritingMode() ? values.afterBorder() - : values.beforeBorder(); + return styleForCellFlow.isFlippedBlocksWritingMode() ? values.afterBorder + : values.beforeBorder; } static const CollapsedBorderValue& collapsedRightBorder( const ComputedStyle& styleForCellFlow, const LayoutTableCell::CollapsedBorderValues& values) { if (styleForCellFlow.isHorizontalWritingMode()) { - return styleForCellFlow.isLeftToRightDirection() ? values.endBorder() - : values.startBorder(); + return styleForCellFlow.isLeftToRightDirection() ? values.endBorder + : values.startBorder; } - return styleForCellFlow.isFlippedBlocksWritingMode() ? values.beforeBorder() - : values.afterBorder(); + return styleForCellFlow.isFlippedBlocksWritingMode() ? values.beforeBorder + : values.afterBorder; } static const CollapsedBorderValue& collapsedTopBorder( const ComputedStyle& styleForCellFlow, const LayoutTableCell::CollapsedBorderValues& values) { if (styleForCellFlow.isHorizontalWritingMode()) - return values.beforeBorder(); - return styleForCellFlow.isLeftToRightDirection() ? values.startBorder() - : values.endBorder(); + return values.beforeBorder; + return styleForCellFlow.isLeftToRightDirection() ? values.startBorder + : values.endBorder; } static const CollapsedBorderValue& collapsedBottomBorder( const ComputedStyle& styleForCellFlow, const LayoutTableCell::CollapsedBorderValues& values) { if (styleForCellFlow.isHorizontalWritingMode()) - return values.afterBorder(); - return styleForCellFlow.isLeftToRightDirection() ? values.endBorder() - : values.startBorder(); + return values.afterBorder; + return styleForCellFlow.isLeftToRightDirection() ? values.endBorder + : values.startBorder; } void TableCellPainter::paint(const PaintInfo& paintInfo, @@ -68,15 +68,6 @@ return style; } -const DisplayItemClient& TableCellPainter::displayItemClientForBorders() const { - // TODO(wkorman): We may need to handle PaintInvalidationDelayedFull. - // http://crbug.com/657186 - return m_layoutTableCell.usesCompositedCellDisplayItemClients() - ? static_cast<const DisplayItemClient&>( - *m_layoutTableCell.collapsedBorderValues()) - : m_layoutTableCell; -} - void TableCellPainter::paintCollapsedBorders( const PaintInfo& paintInfo, const LayoutPoint& paintOffset, @@ -104,18 +95,6 @@ const CollapsedBorderValue& bottomBorderValue = collapsedBottomBorder(styleForCellFlow, *values); - int displayItemType = DisplayItem::kTableCollapsedBorderBase; - if (topBorderValue.shouldPaint(currentBorderValue)) - displayItemType |= DisplayItem::TableCollapsedBorderTop; - if (bottomBorderValue.shouldPaint(currentBorderValue)) - displayItemType |= DisplayItem::TableCollapsedBorderBottom; - if (leftBorderValue.shouldPaint(currentBorderValue)) - displayItemType |= DisplayItem::TableCollapsedBorderLeft; - if (rightBorderValue.shouldPaint(currentBorderValue)) - displayItemType |= DisplayItem::TableCollapsedBorderRight; - if (displayItemType == DisplayItem::kTableCollapsedBorderBase) - return; - int topWidth = topBorderValue.width(); int bottomWidth = bottomBorderValue.width(); int leftWidth = leftBorderValue.width(); @@ -131,41 +110,32 @@ paintRect.height() + topWidth / 2 + (bottomWidth + 1) / 2); GraphicsContext& graphicsContext = paintInfo.context; - const DisplayItemClient& client = displayItemClientForBorders(); - if (DrawingRecorder::useCachedDrawingIfPossible( - graphicsContext, client, - static_cast<DisplayItem::Type>(displayItemType))) - return; - - DrawingRecorder recorder(graphicsContext, client, - static_cast<DisplayItem::Type>(displayItemType), - borderRect); Color cellColor = m_layoutTableCell.resolveColor(CSSPropertyColor); // We never paint diagonals at the joins. We simply let the border with the // highest precedence paint on top of borders with lower precedence. - if (displayItemType & DisplayItem::TableCollapsedBorderTop) { + if (topBorderValue.shouldPaint(currentBorderValue)) { ObjectPainter::drawLineForBoxSide( graphicsContext, borderRect.x(), borderRect.y(), borderRect.maxX(), borderRect.y() + topWidth, BSTop, topBorderValue.color().resolve(cellColor), collapsedBorderStyle(topBorderValue.style()), 0, 0, true); } - if (displayItemType & DisplayItem::TableCollapsedBorderBottom) { + if (bottomBorderValue.shouldPaint(currentBorderValue)) { ObjectPainter::drawLineForBoxSide( graphicsContext, borderRect.x(), borderRect.maxY() - bottomWidth, borderRect.maxX(), borderRect.maxY(), BSBottom, bottomBorderValue.color().resolve(cellColor), collapsedBorderStyle(bottomBorderValue.style()), 0, 0, true); } - if (displayItemType & DisplayItem::TableCollapsedBorderLeft) { + if (leftBorderValue.shouldPaint(currentBorderValue)) { ObjectPainter::drawLineForBoxSide( graphicsContext, borderRect.x(), borderRect.y(), borderRect.x() + leftWidth, borderRect.maxY(), BSLeft, leftBorderValue.color().resolve(cellColor), collapsedBorderStyle(leftBorderValue.style()), 0, 0, true); } - if (displayItemType & DisplayItem::TableCollapsedBorderRight) { + if (rightBorderValue.shouldPaint(currentBorderValue)) { ObjectPainter::drawLineForBoxSide( graphicsContext, borderRect.maxX() - rightWidth, borderRect.y(), borderRect.maxX(), borderRect.maxY(), BSRight,
diff --git a/third_party/WebKit/Source/core/paint/TableCellPainter.h b/third_party/WebKit/Source/core/paint/TableCellPainter.h index a87c4b59..749915c 100644 --- a/third_party/WebKit/Source/core/paint/TableCellPainter.h +++ b/third_party/WebKit/Source/core/paint/TableCellPainter.h
@@ -39,7 +39,6 @@ void paintMask(const PaintInfo&, const LayoutPoint& paintOffset); private: - const DisplayItemClient& displayItemClientForBorders() const; LayoutRect paintRectNotIncludingVisualOverflow( const LayoutPoint& paintOffset); void paintBackground(const PaintInfo&,
diff --git a/third_party/WebKit/Source/core/paint/TableCellPainterTest.cpp b/third_party/WebKit/Source/core/paint/TableCellPainterTest.cpp index 1cd5830..adce65d 100644 --- a/third_party/WebKit/Source/core/paint/TableCellPainterTest.cpp +++ b/third_party/WebKit/Source/core/paint/TableCellPainterTest.cpp
@@ -170,11 +170,12 @@ "100px solid yellow; background: green; }" " table { margin: 100px; border-collapse: collapse; }" "</style>" - "<table>" + "<table id='table'>" " <tr><td id='cell'></td></tr>" "</table>"); LayoutView& layoutView = *document().layoutView(); + LayoutObject& table = *getLayoutObjectByElementId("table"); LayoutObject& cell = *getLayoutObjectByElementId("cell"); rootPaintController().invalidateAll(); @@ -188,7 +189,7 @@ rootPaintController().getDisplayItemList(), 4, TestDisplayItem(layoutView, DisplayItem::kDocumentBackground), TestDisplayItem(cell, DisplayItem::kBoxDecorationBackground), - TestDisplayItem(cell, DisplayItem::kTableCollapsedBorderLast), + TestDisplayItem(table, DisplayItem::kTableCollapsedBorders), TestDisplayItem(cell, DisplayItem::paintPhaseToDrawingType( PaintPhaseSelfOutlineOnly))); }
diff --git a/third_party/WebKit/Source/core/paint/TablePainter.cpp b/third_party/WebKit/Source/core/paint/TablePainter.cpp index 16f910484..65a069c 100644 --- a/third_party/WebKit/Source/core/paint/TablePainter.cpp +++ b/third_party/WebKit/Source/core/paint/TablePainter.cpp
@@ -44,31 +44,61 @@ } } - if (m_layoutTable.collapseBorders() && - shouldPaintDescendantBlockBackgrounds(paintPhase) && - m_layoutTable.style()->visibility() == EVisibility::Visible) { - // Using our cached sorted styles, we then do individual passes, - // painting each style of border from lowest precedence to highest - // precedence. - LayoutTable::CollapsedBorderValues collapsedBorders = - m_layoutTable.collapsedBorders(); - size_t count = collapsedBorders.size(); - for (size_t i = 0; i < count; ++i) { - for (LayoutTableSection* section = m_layoutTable.bottomSection(); - section; section = m_layoutTable.sectionAbove(section)) { - LayoutPoint childPoint = - m_layoutTable.flipForWritingModeForChild(section, paintOffset); - TableSectionPainter(*section).paintCollapsedBorders( - paintInfoForDescendants, childPoint, collapsedBorders[i]); - } - } - } + if (shouldPaintDescendantBlockBackgrounds(paintPhase)) + paintCollapsedBorders(paintInfoForDescendants, paintOffset); } if (shouldPaintSelfOutline(paintPhase)) ObjectPainter(m_layoutTable).paintOutline(paintInfo, paintOffset); } +void TablePainter::paintCollapsedBorders(const PaintInfo& paintInfo, + const LayoutPoint& paintOffset) { + if (!m_layoutTable.hasCollapsedBorders() || + m_layoutTable.style()->visibility() != EVisibility::Visible) + return; + + LayoutTable::CollapsedBordersInfo& collapsedBorders = + m_layoutTable.getCollapsedBordersInfo(); + + // Normally we don't clip individual display items by paint dirty rect + // (aka interest rect), to keep display items independent with paint dirty + // rect so we can just depend on paint invalidation status to repaint them. + // However, the collapsed border display item may be too big to contain all + // collapsed borders in a huge table, so we clip it to paint dirty rect. + // We need to invalidate the display item if the previous paint is clipped + // and the paint dirty rect changed. + if (collapsedBorders.lastPaintResult != FullyPainted && + collapsedBorders.lastPaintRect != paintInfo.cullRect()) + m_layoutTable.setDisplayItemsUncached(); + + if (DrawingRecorder::useCachedDrawingIfPossible( + paintInfo.context, m_layoutTable, + DisplayItem::kTableCollapsedBorders)) + return; + + DrawingRecorder recorder( + paintInfo.context, m_layoutTable, DisplayItem::kTableCollapsedBorders, + FloatRect(LayoutRect(paintOffset, m_layoutTable.size()))); + + // Using our cached sorted styles, we then do individual passes, painting + // each style of border from lowest precedence to highest precedence. + PaintResult paintResult = FullyPainted; + for (const auto& borderValue : collapsedBorders.values) { + for (LayoutTableSection* section = m_layoutTable.bottomSection(); section; + section = m_layoutTable.sectionAbove(section)) { + LayoutPoint childPoint = + m_layoutTable.flipForWritingModeForChild(section, paintOffset); + if (TableSectionPainter(*section).paintCollapsedBorders( + paintInfo, childPoint, borderValue) == + MayBeClippedByPaintDirtyRect) + paintResult = MayBeClippedByPaintDirtyRect; + } + } + collapsedBorders.lastPaintResult = paintResult; + collapsedBorders.lastPaintRect = paintInfo.cullRect(); +} + void TablePainter::paintBoxDecorationBackground( const PaintInfo& paintInfo, const LayoutPoint& paintOffset) {
diff --git a/third_party/WebKit/Source/core/paint/TablePainter.h b/third_party/WebKit/Source/core/paint/TablePainter.h index d3de8e0e..ac57899 100644 --- a/third_party/WebKit/Source/core/paint/TablePainter.h +++ b/third_party/WebKit/Source/core/paint/TablePainter.h
@@ -24,6 +24,8 @@ void paintMask(const PaintInfo&, const LayoutPoint&); private: + void paintCollapsedBorders(const PaintInfo&, const LayoutPoint&); + const LayoutTable& m_layoutTable; };
diff --git a/third_party/WebKit/Source/core/paint/TablePainterTest.cpp b/third_party/WebKit/Source/core/paint/TablePainterTest.cpp new file mode 100644 index 0000000..7ebcdff1 --- /dev/null +++ b/third_party/WebKit/Source/core/paint/TablePainterTest.cpp
@@ -0,0 +1,75 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "core/paint/PaintControllerPaintTest.h" +#include "platform/graphics/paint/DrawingDisplayItem.h" + +namespace blink { + +using TablePainterTest = PaintControllerPaintTest; + +TEST_F(TablePainterTest, CollapsedBorderInterestRectChange) { + setBodyInnerHTML( + "<style>" + " table { border-collapse: collapse; position: absolute; }" + " td { width: 100px; height: 100px; border: 2px solid blue; }" + "</style>" + "<table id='table'>" + " <tr><td></td><td></td><td></td><td></td></tr>" + " <tr><td></td><td></td><td></td><td></td></tr>" + " <tr><td></td><td></td><td></td><td></td></tr>" + " <tr><td></td><td></td><td></td><td></td></tr>" + "</table>"); + + PaintLayer& htmlLayer = + *toLayoutBoxModelObject(document().documentElement()->layoutObject()) + ->layer(); + LayoutObject& table = *getLayoutObjectByElementId("table"); + + rootPaintController().invalidateAll(); + document().view()->updateAllLifecyclePhasesExceptPaint(); + IntRect interestRect(300, 300, 300, 300); + paint(&interestRect); + + EXPECT_DISPLAY_LIST( + rootPaintController().getDisplayItemList(), 4, + TestDisplayItem(layoutView(), documentBackgroundType), + TestDisplayItem(htmlLayer, DisplayItem::kSubsequence), + TestDisplayItem(table, DisplayItem::kTableCollapsedBorders), + TestDisplayItem(htmlLayer, DisplayItem::kEndSubsequence)); + // Painted collapsed borders of the central 4 cells, each 4 operations. + EXPECT_EQ(16, static_cast<const DrawingDisplayItem&>( + rootPaintController().getDisplayItemList()[2]) + .picture() + ->approximateOpCount()); + + // Should repaint collapsed borders if the previous paint didn't fully paint + // and interest rect changes. + document().view()->updateAllLifecyclePhasesExceptPaint(); + interestRect = IntRect(0, 0, 1000, 1000); + EXPECT_TRUE(paintWithoutCommit(&interestRect)); + EXPECT_EQ(1, numCachedNewItems()); + commit(); + EXPECT_DISPLAY_LIST( + rootPaintController().getDisplayItemList(), 4, + TestDisplayItem(layoutView(), documentBackgroundType), + TestDisplayItem(htmlLayer, DisplayItem::kSubsequence), + TestDisplayItem(table, DisplayItem::kTableCollapsedBorders), + TestDisplayItem(htmlLayer, DisplayItem::kEndSubsequence)); + // Painted collapsed borders of all 16 cells, each 4 operations. + EXPECT_EQ(64, static_cast<const DrawingDisplayItem&>( + rootPaintController().getDisplayItemList()[2]) + .picture() + ->approximateOpCount()); + + // Should not repaint collapsed borders if the previous paint fully painted + // and interest rect changes. + document().view()->updateAllLifecyclePhasesExceptPaint(); + interestRect = IntRect(0, 0, 400, 400); + EXPECT_TRUE(paintWithoutCommit(&interestRect)); + EXPECT_EQ(4, numCachedNewItems()); + commit(); +} + +} // namespace blink
diff --git a/third_party/WebKit/Source/core/paint/TableSectionPainter.cpp b/third_party/WebKit/Source/core/paint/TableSectionPainter.cpp index 685ba682..44e1d61 100644 --- a/third_party/WebKit/Source/core/paint/TableSectionPainter.cpp +++ b/third_party/WebKit/Source/core/paint/TableSectionPainter.cpp
@@ -149,24 +149,27 @@ return elem1->absoluteColumnIndex() < elem2->absoluteColumnIndex(); } -void TableSectionPainter::paintCollapsedBorders( +PaintResult TableSectionPainter::paintCollapsedBorders( const PaintInfo& paintInfo, const LayoutPoint& paintOffset, const CollapsedBorderValue& currentBorderValue) { - paintCollapsedSectionBorders(paintInfo, paintOffset, currentBorderValue); + PaintResult result = + paintCollapsedSectionBorders(paintInfo, paintOffset, currentBorderValue); LayoutTable* table = m_layoutTableSection.table(); - if (table->header() == m_layoutTableSection) + if (table->header() == m_layoutTableSection) { paintRepeatingHeaderGroup(paintInfo, paintOffset, currentBorderValue, PaintCollapsedBorders); + } + return result; } -void TableSectionPainter::paintCollapsedSectionBorders( +PaintResult TableSectionPainter::paintCollapsedSectionBorders( const PaintInfo& paintInfo, const LayoutPoint& paintOffset, const CollapsedBorderValue& currentBorderValue) { if (!m_layoutTableSection.numRows() || !m_layoutTableSection.table()->effectiveColumns().size()) - return; + return FullyPainted; LayoutPoint adjustedPaintOffset = paintOffset + m_layoutTableSection.location(); @@ -185,7 +188,7 @@ m_layoutTableSection.dirtiedEffectiveColumns(tableAlignedRect); if (dirtiedColumns.start() >= dirtiedColumns.end()) - return; + return MayBeClippedByPaintDirtyRect; // Collapsed borders are painted from the bottom right to the top left so that // precedence due to cell position is respected. @@ -207,6 +210,11 @@ currentBorderValue); } } + + if (dirtiedRows == m_layoutTableSection.fullTableRowSpan() && + dirtiedColumns == m_layoutTableSection.fullTableEffectiveColumnSpan()) + return FullyPainted; + return MayBeClippedByPaintDirtyRect; } void TableSectionPainter::paintObject(const PaintInfo& paintInfo,
diff --git a/third_party/WebKit/Source/core/paint/TableSectionPainter.h b/third_party/WebKit/Source/core/paint/TableSectionPainter.h index d6ff986f..9e33c022 100644 --- a/third_party/WebKit/Source/core/paint/TableSectionPainter.h +++ b/third_party/WebKit/Source/core/paint/TableSectionPainter.h
@@ -6,6 +6,7 @@ #define TableSectionPainter_h #include "core/paint/PaintPhase.h" +#include "core/paint/PaintResult.h" #include "core/style/ShadowData.h" #include "wtf/Allocator.h" @@ -26,9 +27,10 @@ : m_layoutTableSection(layoutTableSection) {} void paint(const PaintInfo&, const LayoutPoint&); - void paintCollapsedBorders(const PaintInfo&, - const LayoutPoint&, - const CollapsedBorderValue&); + + PaintResult paintCollapsedBorders(const PaintInfo&, + const LayoutPoint&, + const CollapsedBorderValue&); private: void paintObject(const PaintInfo&, const LayoutPoint&); @@ -56,9 +58,9 @@ const CollapsedBorderValue& currentBorderValue, ItemToPaint); void paintSection(const PaintInfo&, const LayoutPoint&); - void paintCollapsedSectionBorders(const PaintInfo&, - const LayoutPoint&, - const CollapsedBorderValue&); + PaintResult paintCollapsedSectionBorders(const PaintInfo&, + const LayoutPoint&, + const CollapsedBorderValue&); const LayoutTableSection& m_layoutTableSection; };
diff --git a/third_party/WebKit/Source/devtools/front_end/elements/StylesSidebarPane.js b/third_party/WebKit/Source/devtools/front_end/elements/StylesSidebarPane.js index badb3a6..77571ef 100644 --- a/third_party/WebKit/Source/devtools/front_end/elements/StylesSidebarPane.js +++ b/third_party/WebKit/Source/devtools/front_end/elements/StylesSidebarPane.js
@@ -1115,6 +1115,9 @@ for (var matchingIndex of matchingSelectorIndexes) matchingSelectors[matchingIndex] = true; + if (this._parentPane._isEditingStyle) + return; + var fragment = this._hoverableSelectorsMode ? this._renderHoverableSelectors(selectorTexts, matchingSelectors) : this._renderSimplifiedSelectors(selectorTexts, matchingSelectors); this._selectorElement.removeChildren();
diff --git a/third_party/WebKit/Source/devtools/front_end/sources/sourcesView.css b/third_party/WebKit/Source/devtools/front_end/sources/sourcesView.css index f347560..62bef36 100644 --- a/third_party/WebKit/Source/devtools/front_end/sources/sourcesView.css +++ b/third_party/WebKit/Source/devtools/front_end/sources/sourcesView.css
@@ -39,6 +39,7 @@ background-color: #f3f3f3; border-top: 1px solid #dadada; overflow: hidden; + z-index: 0; } .sources-toolbar .toolbar {
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/searchableView.css b/third_party/WebKit/Source/devtools/front_end/ui/searchableView.css index 726b9aa..cf02aaf 100644 --- a/third_party/WebKit/Source/devtools/front_end/ui/searchableView.css +++ b/third_party/WebKit/Source/devtools/front_end/ui/searchableView.css
@@ -10,6 +10,7 @@ border-top: 1px solid #ccc; display: flex; overflow: hidden; + z-index: 0; } .search-bar.replaceable {
diff --git a/third_party/WebKit/Source/devtools/scripts/build/rjsmin.py b/third_party/WebKit/Source/devtools/scripts/build/rjsmin.py index 2d15e03..54e20ec1 100755 --- a/third_party/WebKit/Source/devtools/scripts/build/rjsmin.py +++ b/third_party/WebKit/Source/devtools/scripts/build/rjsmin.py
@@ -1,19 +1,5 @@ #!/usr/bin/env python -# -# Copyright 2011 - 2013 -# Andr\xe9 Malo or his licensors, as applicable -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. +# -*- coding: ascii -*- r""" ===================== Javascript Minifier @@ -21,7 +7,26 @@ rJSmin is a javascript minifier written in python. -The minifier is based on the semantics of `jsmin.c by Douglas Crockford`_\. +The minifier is based on the semantics of `jsmin.c by Douglas Crockford`_\\. + +:Copyright: + + Copyright 2011 - 2015 + Andr\xe9 Malo or his licensors, as applicable + +:License: + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. The module is a re-implementation aiming for speed, so it can be used at runtime (rather than during a preprocessing step). Usually it produces the @@ -30,22 +35,24 @@ - there is no error detection: unterminated string, regex and comment literals are treated as regular javascript code and minified as such. - Control characters inside string and regex literals are left untouched; they - are not converted to spaces (nor to \n) + are not converted to spaces (nor to \\n) - Newline characters are not allowed inside string and regex literals, except for line continuations in string literals (ECMA-5). - "return /regex/" is recognized correctly. +- Line terminators after regex literals are handled more sensibly - "+ +" and "- -" sequences are not collapsed to '++' or '--' - Newlines before ! operators are removed more sensibly +- Comments starting with an exclamation mark (``!``) can be kept optionally - rJSmin does not handle streams, but only complete strings. (However, the module provides a "streamy" interface). -Since most parts of the logic are handled by the regex engine it's way -faster than the original python port of ``jsmin.c`` by Baruch Even. The speed -factor varies between about 6 and 55 depending on input and python version -(it gets faster the more compressed the input already is). Compared to the +Since most parts of the logic are handled by the regex engine it's way faster +than the original python port of ``jsmin.c`` by Baruch Even. The speed factor +varies between about 6 and 55 depending on input and python version (it gets +faster the more compressed the input already is). Compared to the speed-refactored python port by Dave St.Germain the performance gain is less -dramatic but still between 1.2 and 7. See the docs/BENCHMARKS file for -details. +dramatic but still between 3 and 50 (for huge inputs). See the docs/BENCHMARKS +file for details. rjsmin.c is a reimplementation of rjsmin.py in C and speeds it up even more. @@ -54,11 +61,13 @@ .. _jsmin.c by Douglas Crockford: http://www.crockford.com/javascript/jsmin.c """ -__author__ = "Andr\xe9 Malo" -__author__ = getattr(__author__, 'decode', lambda x: __author__)('latin-1') +if __doc__: + # pylint: disable = redefined-builtin + __doc__ = __doc__.encode('ascii').decode('unicode_escape') +__author__ = r"Andr\xe9 Malo".encode('ascii').decode('unicode_escape') __docformat__ = "restructuredtext en" __license__ = "Apache License, Version 2.0" -__version__ = '1.0.7' +__version__ = '1.0.12' __all__ = ['jsmin'] import re as _re @@ -79,7 +88,9 @@ :Return: Minifier :Rtype: ``callable`` """ - # pylint: disable = R0912, R0914, W0612 + # pylint: disable = unused-variable + # pylint: disable = too-many-locals + if not python_only: try: import _rjsmin @@ -90,12 +101,15 @@ try: xrange except NameError: - xrange = range # pylint: disable = W0622 + xrange = range # pylint: disable = redefined-builtin space_chars = r'[\000-\011\013\014\016-\040]' line_comment = r'(?://[^\r\n]*)' space_comment = r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)' + space_comment_nobang = r'(?:/\*(?!!)[^*]*\*+(?:[^/*][^*]*\*+)*/)' + bang_comment = r'(?:/\*![^*]*\*+(?:[^/*][^*]*\*+)*/)' + string1 = \ r'(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^\047\\\r\n]*)*\047)' string2 = r'(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^"\\\r\n]*)*")' @@ -105,7 +119,8 @@ charclass = r'(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\])' nospecial = r'[^/\\\[\r\n]' regex = r'(?:/(?![\r\n/*])%s*(?:(?:\\[^\r\n]|%s)%s*)*/)' % ( - nospecial, charclass, nospecial) + nospecial, charclass, nospecial + ) space = r'(?:%s|%s)' % (space_chars, space_comment) newline = r'(?:%s?[\r\n])' % line_comment @@ -135,24 +150,33 @@ return ''.join(['%s%s%s' % ( chr(first), last > first + 1 and '-' or '', - last != first and chr(last) or '') for first, last in result]) + last != first and chr(last) or '' + ) for first, last in result]) # noqa - return _re.sub(r'([\000-\040\047])', # for better portability - lambda m: '\\%03o' % ord(m.group(1)), (sequentize(result) + return _re.sub( + r'([\000-\040\047])', # \047 for better portability + lambda m: '\\%03o' % ord(m.group(1)), ( + sequentize(result) .replace('\\', '\\\\') .replace('[', '\\[') - .replace(']', '\\]'))) + .replace(']', '\\]') + ) + ) def id_literal_(what): """ Make id_literal like char class """ match = _re.compile(what).match - result = ''.join([chr(c) for c in xrange(127) if not match(chr(c))]) + result = ''.join([ + chr(c) for c in xrange(127) if not match(chr(c)) + ]) return '[^%s]' % fix_charclass(result) def not_id_literal_(keep): """ Make negated id_literal like char class """ match = _re.compile(id_literal_(keep)).match - result = ''.join([chr(c) for c in xrange(127) if not match(chr(c))]) + result = ''.join([ + chr(c) for c in xrange(127) if not match(chr(c)) + ]) return r'[%s]' % fix_charclass(result) not_id_literal = not_id_literal_(r'[a-zA-Z0-9_$]') @@ -162,48 +186,130 @@ id_literal = id_literal_(r'[a-zA-Z0-9_$]') id_literal_open = id_literal_(r'[a-zA-Z0-9_${\[(!+-]') id_literal_close = id_literal_(r'[a-zA-Z0-9_$}\])"\047+-]') + post_regex_off = id_literal_(r'[^\000-\040}\])?:|,;.&=+-]') dull = r'[^\047"`/\000-\040]' - space_sub = _re.compile(( - r'(%(dull)s+)' - r'|(%(strings)s%(dull)s*)' + space_sub_simple = _re.compile(( + # noqa pylint: disable = bad-continuation + + r'(%(dull)s+)' # 0 + r'|(%(strings)s%(dull)s*)' # 1 r'|(?<=%(preregex1)s)' r'%(space)s*(?:%(newline)s%(space)s*)*' - r'(%(regex)s%(dull)s*)' + r'(%(regex)s)' # 2 + r'(%(space)s*(?:%(newline)s%(space)s*)+' # 3 + r'(?=%(post_regex_off)s))?' r'|(?<=%(preregex2)s)' - r'%(space)s*(?:%(newline)s%(space)s)*' - r'(%(regex)s%(dull)s*)' + r'%(space)s*(?:(%(newline)s)%(space)s*)*' # 4 + r'(%(regex)s)' # 5 + r'(%(space)s*(?:%(newline)s%(space)s*)+' # 6 + r'(?=%(post_regex_off)s))?' r'|(?<=%(id_literal_close)s)' - r'%(space)s*(?:(%(newline)s)%(space)s*)+' + r'%(space)s*(?:(%(newline)s)%(space)s*)+' # 7 r'(?=%(id_literal_open)s)' - r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)' - r'|(?<=\+)(%(space)s)+(?=\+)' - r'|(?<=-)(%(space)s)+(?=-)' + r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)' # 8 + r'|(?<=\+)(%(space)s)+(?=\+)' # 9 + r'|(?<=-)(%(space)s)+(?=-)' # 10 r'|%(space)s+' - r'|(?:%(newline)s%(space)s*)+') % locals()).sub - #print space_sub.__self__.pattern + r'|(?:%(newline)s%(space)s*)+' + ) % locals()).sub - def space_subber(match): + # print space_sub_simple.__self__.pattern + + def space_subber_simple(match): """ Substitution callback """ - # pylint: disable = C0321, R0911 + # pylint: disable = too-many-return-statements + groups = match.groups() if groups[0]: return groups[0] elif groups[1]: return groups[1] elif groups[2]: + if groups[3]: + return groups[2] + '\n' return groups[2] - elif groups[3]: - return groups[3] - elif groups[4]: + elif groups[5]: + return "%s%s%s" % ( + groups[4] and '\n' or '', + groups[5], + groups[6] and '\n' or '', + ) + elif groups[7]: return '\n' - elif groups[5] or groups[6] or groups[7]: + elif groups[8] or groups[9] or groups[10]: return ' ' else: return '' - def jsmin(script): # pylint: disable = W0621 + space_sub_banged = _re.compile(( + # noqa pylint: disable = bad-continuation + + r'(%(dull)s+)' # 0 + r'|(%(strings)s%(dull)s*)' # 1 + r'|(?<=%(preregex1)s)' + r'(%(space)s*(?:%(newline)s%(space)s*)*)' # 2 + r'(%(regex)s)' # 3 + r'(%(space)s*(?:%(newline)s%(space)s*)+' # 4 + r'(?=%(post_regex_off)s))?' + r'|(?<=%(preregex2)s)' + r'(%(space)s*(?:(%(newline)s)%(space)s*)*)' # 5, 6 + r'(%(regex)s)' # 7 + r'(%(space)s*(?:%(newline)s%(space)s*)+' # 8 + r'(?=%(post_regex_off)s))?' + r'|(?<=%(id_literal_close)s)' + r'(%(space)s*(?:%(newline)s%(space)s*)+)' # 9 + r'(?=%(id_literal_open)s)' + r'|(?<=%(id_literal)s)(%(space)s+)(?=%(id_literal)s)' # 10 + r'|(?<=\+)(%(space)s+)(?=\+)' # 11 + r'|(?<=-)(%(space)s+)(?=-)' # 12 + r'|(%(space)s+)' # 13 + r'|((?:%(newline)s%(space)s*)+)' # 14 + ) % locals()).sub + + # print space_sub_banged.__self__.pattern + + keep = _re.compile(( + r'%(space_chars)s+|%(space_comment_nobang)s+|%(newline)s+' + r'|(%(bang_comment)s+)' + ) % locals()).sub + keeper = lambda m: m.groups()[0] or '' + + # print keep.__self__.pattern + + def space_subber_banged(match): + """ Substitution callback """ + # pylint: disable = too-many-return-statements + + groups = match.groups() + if groups[0]: + return groups[0] + elif groups[1]: + return groups[1] + elif groups[3]: + return "%s%s%s%s" % ( + keep(keeper, groups[2]), + groups[3], + keep(keeper, groups[4] or ''), + groups[4] and '\n' or '', + ) + elif groups[7]: + return "%s%s%s%s%s" % ( + keep(keeper, groups[5]), + groups[6] and '\n' or '', + groups[7], + keep(keeper, groups[8] or ''), + groups[8] and '\n' or '', + ) + elif groups[9]: + return keep(keeper, groups[9]) + '\n' + elif groups[10] or groups[11] or groups[12]: + return keep(keeper, groups[10] or groups[11] or groups[12]) or ' ' + else: + return keep(keeper, groups[13] or groups[14]) + + def jsmin(script, keep_bang_comments=False): r""" Minify javascript based on `jsmin.c by Douglas Crockford`_\. @@ -218,17 +324,29 @@ `script` : ``str`` Script to minify + `keep_bang_comments` : ``bool`` + Keep comments starting with an exclamation mark? (``/*!...*/``) + :Return: Minified script :Rtype: ``str`` """ - return space_sub(space_subber, '\n%s\n' % script).strip() + # pylint: disable = redefined-outer-name + + if keep_bang_comments: + return space_sub_banged( + space_subber_banged, '\n%s\n' % script + ).strip() + else: + return space_sub_simple( + space_subber_simple, '\n%s\n' % script + ).strip() return jsmin jsmin = _make_jsmin() -def jsmin_for_posers(script): +def jsmin_for_posers(script, keep_bang_comments=False): r""" Minify javascript based on `jsmin.c by Douglas Crockford`_\. @@ -240,57 +358,158 @@ http://www.crockford.com/javascript/jsmin.c :Warning: This function is the digest of a _make_jsmin() call. It just - utilizes the resulting regex. It's just for fun here and may + utilizes the resulting regexes. It's here for fun and may vanish any time. Use the `jsmin` function instead. :Parameters: `script` : ``str`` Script to minify + `keep_bang_comments` : ``bool`` + Keep comments starting with an exclamation mark? (``/*!...*/``) + :Return: Minified script :Rtype: ``str`` """ - def subber(match): - """ Substitution callback """ - groups = match.groups() - return ( - groups[0] or - groups[1] or - groups[2] or - groups[3] or - (groups[4] and '\n') or - (groups[5] and ' ') or - (groups[6] and ' ') or - (groups[7] and ' ') or - '') + if not keep_bang_comments: + rex = ( + r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]' + r'|\r?\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]' + r'|\r?\n|\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?<=[(,=:\[!&|?' + r'{};\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*' + r'][^*]*\*+)*/))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\0' + r'14\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)*((?:/(?![\r' + r'\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r' + r'\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/))((?:[\000-\011\013\014' + r'\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:(?:(?://[^\r' + r'\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:' + r'[^/*][^*]*\*+)*/))*)+(?=[^\000-\040&)+,.:;=?\]|}-]))?|(?<=[\00' + r'0-#%-,./:-@\[-^`{-~-]return)(?:[\000-\011\013\014\016-\040]|(?' + r':/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:((?:(?://[^\r\n]*)?[\r\n]' + r'))(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*' + r'\*+)*/))*)*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[' + r'[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/))((' + r'?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)' + r'*/))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\04' + r'0]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040&)+,.:;' + r'=?\]|}-]))?|(?<=[^\000-!#%&(*,./:-@\[\\^`{|~])(?:[\000-\011\01' + r'3\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:((?:(?:' + r'//[^\r\n]*)?[\r\n]))(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]' + r'*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040"#%-\047)*,./:-@\\-^' + r'`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-\011\013\014\0' + r'16-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^\000-#%-,./' + r':-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]|(?:/\*[' + r'^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=\+)|(?<=-)((?:[\000-\011\013' + r'\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=-)|(?:[' + r'\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)' + r')+|(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]' + r'|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+' + ) - return _re.sub( - r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?' - r'\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|' - r'\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?<=[(,=:\[!&|?{};\r\n])(?' - r':[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*' - r'(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*' - r'[^*]*\*+(?:[^/*][^*]*\*+)*/))*)*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(' - r'?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[' - r'\r\n]*)*/)[^\047"/\000-\040]*)|(?<=[\000-#%-,./:-@\[-^`{-~-]return' - r')(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/' - r'))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:' - r'/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?' - r':(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/' - r'\\\[\r\n]*)*/)[^\047"/\000-\040]*)|(?<=[^\000-!#%&(*,./:-@\[\\^`{|' - r'~])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)' - r'*/))*(?:((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011\013\014\016-\040]' - r'|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040"#%-\047)*,./' - r':-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-\011\013\01' - r'4\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^\000-#%-,./:' - r'-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*' - r'\*+(?:[^/*][^*]*\*+)*/)))+(?=\+)|(?<=-)((?:[\000-\011\013\014\016-' - r'\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=-)|(?:[\000-\011\013' - r'\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+|(?:(?:(?://[^' - r'\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^' - r'/*][^*]*\*+)*/))*)+', subber, '\n%s\n' % script).strip() + def subber(match): + """ Substitution callback """ + groups = match.groups() + return ( + groups[0] or + groups[1] or + (groups[3] and (groups[2] + '\n')) or + groups[2] or + (groups[5] and "%s%s%s" % ( + groups[4] and '\n' or '', + groups[5], + groups[6] and '\n' or '', + )) or + (groups[7] and '\n') or + (groups[8] and ' ') or + (groups[9] and ' ') or + (groups[10] and ' ') or + '' + ) + else: + rex = ( + r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]' + r'|\r?\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]' + r'|\r?\n|\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?<=[(,=:\[!&|?' + r'{};\r\n])((?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/' + r'*][^*]*\*+)*/))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013' + r'\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)*)((?:/(?!' + r'[\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^' + r'\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/))((?:[\000-\011\013\01' + r'4\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:(?:(?://[^' + r'\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(' + r'?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040&)+,.:;=?\]|}-]))?|(?<=[' + r'\000-#%-,./:-@\[-^`{-~-]return)((?:[\000-\011\013\014\016-\040' + r']|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:((?:(?://[^\r\n]*)?[' + r'\r\n]))(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][' + r'^*]*\*+)*/))*)*)((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|' + r'(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*' + r'/))((?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]' + r'*\*+)*/))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\01' + r'6-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040&)' + r'+,.:;=?\]|}-]))?|(?<=[^\000-!#%&(*,./:-@\[\\^`{|~])((?:[\000-' + r'\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:' + r'(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/' + r'\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+)(?=[^\000-\040"#%-\047)*,./' + r':-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-\011\01' + r'3\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+)(?=[^\000' + r'-#%-,./:-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]|' + r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+)(?=\+)|(?<=-)((?:[\000-\0' + r'11\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+)(?=-' + r')|((?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*' + r'\*+)*/))+)|((?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014' + r'\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+)' + ) + + keep = _re.compile(( + r'[\000-\011\013\014\016-\040]+|(?:/\*(?!!)[^*]*\*+(?:[^/*][^*]*' + r'\*+)*/)+|(?:(?://[^\r\n]*)?[\r\n])+|((?:/\*![^*]*\*+(?:[^/*][^' + r'*]*\*+)*/)+)' + ) % locals()).sub + keeper = lambda m: m.groups()[0] or '' + + def subber(match): + """ Substitution callback """ + groups = match.groups() + return ( + groups[0] or + groups[1] or + (groups[3] and "%s%s%s%s" % ( + keep(keeper, groups[2]), + groups[3], + keep(keeper, groups[4] or ''), + groups[4] and '\n' or '', + )) or + (groups[7] and "%s%s%s%s%s" % ( + keep(keeper, groups[5]), + groups[6] and '\n' or '', + groups[7], + keep(keeper, groups[8] or ''), + groups[8] and '\n' or '', + )) or + (groups[9] and keep(keeper, groups[9] + '\n')) or + (groups[10] and keep(keeper, groups[10]) or ' ') or + (groups[11] and keep(keeper, groups[11]) or ' ') or + (groups[12] and keep(keeper, groups[12]) or ' ') or + keep(keeper, groups[13] or groups[14]) + ) + + return _re.sub(rex, subber, '\n%s\n' % script).strip() if __name__ == '__main__': - import sys as _sys - _sys.stdout.write(jsmin(_sys.stdin.read())) + def main(): + """ Main """ + import sys as _sys + + argv = _sys.argv[1:] + keep_bang_comments = '-b' in argv or '-bp' in argv or '-pb' in argv + if '-p' in argv or '-bp' in argv or '-pb' in argv: + xjsmin = _make_jsmin(python_only=True) + else: + xjsmin = jsmin + + _sys.stdout.write(xjsmin( + _sys.stdin.read(), keep_bang_comments=keep_bang_comments + )) + + main()
diff --git a/third_party/WebKit/Source/modules/canvas2d/BaseRenderingContext2D.cpp b/third_party/WebKit/Source/modules/canvas2d/BaseRenderingContext2D.cpp index 7efea9d..d3fb0e2 100644 --- a/third_party/WebKit/Source/modules/canvas2d/BaseRenderingContext2D.cpp +++ b/third_party/WebKit/Source/modules/canvas2d/BaseRenderingContext2D.cpp
@@ -739,8 +739,7 @@ SkPath skPath = path.getSkPath(); skPath.setFillType(parseWinding(windingRuleString)); modifiableState().clipPath(skPath, m_clipAntialiasing); - c->clipPath(skPath, SkRegion::kIntersect_Op, - m_clipAntialiasing == AntiAliased); + c->clipPath(skPath, kIntersect_SkClipOp, m_clipAntialiasing == AntiAliased); if (ExpensiveCanvasHeuristicParameters::ComplexClipsAreExpensive && !skPath.isRect(0) && hasImageBuffer()) { imageBuffer()->setHasExpensiveOp();
diff --git a/third_party/WebKit/Source/modules/canvas2d/ClipList.cpp b/third_party/WebKit/Source/modules/canvas2d/ClipList.cpp index 49f26839..506fd5da 100644 --- a/third_party/WebKit/Source/modules/canvas2d/ClipList.cpp +++ b/third_party/WebKit/Source/modules/canvas2d/ClipList.cpp
@@ -29,7 +29,7 @@ void ClipList::playback(SkCanvas* canvas) const { for (const ClipOp* it = m_clipList.begin(); it < m_clipList.end(); it++) { - canvas->clipPath(it->m_path, SkRegion::kIntersect_Op, + canvas->clipPath(it->m_path, kIntersect_SkClipOp, it->m_antiAliasingMode == AntiAliased); } }
diff --git a/third_party/WebKit/Source/modules/filesystem/FileWriterSync.cpp b/third_party/WebKit/Source/modules/filesystem/FileWriterSync.cpp index 0bb0fa4..b8529b6 100644 --- a/third_party/WebKit/Source/modules/filesystem/FileWriterSync.cpp +++ b/third_party/WebKit/Source/modules/filesystem/FileWriterSync.cpp
@@ -41,11 +41,11 @@ void FileWriterSync::write(Blob* data, ExceptionState& exceptionState) { ASSERT(data); ASSERT(writer()); - ASSERT(m_complete); + DCHECK(m_complete); prepareForWrite(); writer()->write(position(), data->uuid()); - ASSERT(m_complete); + DCHECK(m_complete); if (m_error) { FileError::throwDOMException(exceptionState, m_error); return; @@ -57,14 +57,14 @@ void FileWriterSync::seek(long long position, ExceptionState& exceptionState) { ASSERT(writer()); - ASSERT(m_complete); + DCHECK(m_complete); seekInternal(position); } void FileWriterSync::truncate(long long offset, ExceptionState& exceptionState) { ASSERT(writer()); - ASSERT(m_complete); + DCHECK(m_complete); if (offset < 0) { exceptionState.throwDOMException(InvalidStateError, FileError::invalidStateErrorMessage); @@ -72,7 +72,7 @@ } prepareForWrite(); writer()->truncate(offset); - ASSERT(m_complete); + DCHECK(m_complete); if (m_error) { FileError::throwDOMException(exceptionState, m_error); return; @@ -84,46 +84,29 @@ void FileWriterSync::didWrite(long long bytes, bool complete) { DCHECK_EQ(FileError::kOK, m_error); -#if DCHECK_IS_ON() DCHECK(!m_complete); m_complete = complete; -#endif } void FileWriterSync::didTruncate() { DCHECK_EQ(FileError::kOK, m_error); -#if DCHECK_IS_ON() DCHECK(!m_complete); m_complete = true; -#endif } void FileWriterSync::didFail(WebFileError error) { DCHECK_EQ(FileError::kOK, m_error); m_error = static_cast<FileError::ErrorCode>(error); -#if DCHECK_IS_ON() DCHECK(!m_complete); m_complete = true; -#endif } -FileWriterSync::FileWriterSync() - : m_error(FileError::kOK) -#if DCHECK_IS_ON() - , - m_complete(true) -#endif -{ -} +FileWriterSync::FileWriterSync() : m_error(FileError::kOK), m_complete(true) {} void FileWriterSync::prepareForWrite() { -#if DCHECK_IS_ON() DCHECK(m_complete); -#endif m_error = FileError::kOK; -#if DCHECK_IS_ON() m_complete = false; -#endif } FileWriterSync::~FileWriterSync() {}
diff --git a/third_party/WebKit/Source/modules/filesystem/FileWriterSync.h b/third_party/WebKit/Source/modules/filesystem/FileWriterSync.h index 5156897..91d49e0 100644 --- a/third_party/WebKit/Source/modules/filesystem/FileWriterSync.h +++ b/third_party/WebKit/Source/modules/filesystem/FileWriterSync.h
@@ -69,9 +69,7 @@ void prepareForWrite(); FileError::ErrorCode m_error; -#if DCHECK_IS_ON() bool m_complete; -#endif }; } // namespace blink
diff --git a/third_party/WebKit/Source/platform/BUILD.gn b/third_party/WebKit/Source/platform/BUILD.gn index b562a1cf..ec4be49 100644 --- a/third_party/WebKit/Source/platform/BUILD.gn +++ b/third_party/WebKit/Source/platform/BUILD.gn
@@ -1746,6 +1746,7 @@ "image-decoders/gif/GIFImageDecoderTest.cpp", "image-decoders/ico/ICOImageDecoderTest.cpp", "image-decoders/jpeg/JPEGImageDecoderTest.cpp", + "image-decoders/png/PNGImageDecoderTest.cpp", "image-decoders/webp/WEBPImageDecoderTest.cpp", "json/JSONParserTest.cpp", "json/JSONValuesTest.cpp",
diff --git a/third_party/WebKit/Source/platform/feature_policy/FeaturePolicy.cpp b/third_party/WebKit/Source/platform/feature_policy/FeaturePolicy.cpp index 51cabe3..be49368 100644 --- a/third_party/WebKit/Source/platform/feature_policy/FeaturePolicy.cpp +++ b/third_party/WebKit/Source/platform/feature_policy/FeaturePolicy.cpp
@@ -27,69 +27,6 @@ return nullptr; } -// Converts a list of JSON feature policy items into a mapping of features to -// whitelists. For future compatibility, unrecognized features are simply -// ignored, as are unparseable origins. If |messages| is not null, then any -// errors in the input will cause an error message to be appended to it. -HashMap<const FeaturePolicy::Feature*, - std::unique_ptr<FeaturePolicy::Whitelist>> -parseFeaturePolicyFromJson(std::unique_ptr<JSONArray> policyItems, - RefPtr<SecurityOrigin> origin, - FeaturePolicy::FeatureList& features, - Vector<String>* messages) { - HashMap<const FeaturePolicy::Feature*, - std::unique_ptr<FeaturePolicy::Whitelist>> - whitelists; - - for (size_t i = 0; i < policyItems->size(); ++i) { - JSONObject* item = JSONObject::cast(policyItems->at(i)); - if (!item) { - if (messages) - messages->append("Policy is not an object"); - continue; // Array element is not an object; skip - } - - for (size_t j = 0; j < item->size(); ++j) { - JSONObject::Entry entry = item->at(j); - String featureName = entry.first; - JSONArray* targets = JSONArray::cast(entry.second); - if (!targets) { - if (messages) - messages->append("Whitelist is not an array of strings."); - continue; - } - - const FeaturePolicy::Feature* feature = - featureForName(featureName, features); - if (!feature) - continue; // Feature is not recognized; skip - - std::unique_ptr<FeaturePolicy::Whitelist> whitelist( - new FeaturePolicy::Whitelist); - String targetString; - for (size_t j = 0; j < targets->size(); ++j) { - if (targets->at(j)->asString(&targetString)) { - if (equalIgnoringCase(targetString, "self")) { - whitelist->add(origin); - } else if (targetString == "*") { - whitelist->addAll(); - } else { - KURL originUrl = KURL(KURL(), targetString); - if (originUrl.isValid()) { - whitelist->add(SecurityOrigin::create(originUrl)); - } - } - } else { - if (messages) - messages->append("Whitelist is not an array of strings."); - } - } - whitelists.set(feature, std::move(whitelist)); - } - } - return whitelists; -} - } // namespace // Definitions of all features controlled by Feature Policy should appear here. @@ -120,6 +57,19 @@ const FeaturePolicy::Feature kWebRTC{ "webrtc", FeaturePolicy::FeatureDefault::EnableForAll}; +// static +std::unique_ptr<FeaturePolicy::Whitelist> FeaturePolicy::Whitelist::from( + const WebFeaturePolicy::ParsedWhitelist& parsedWhitelist) { + std::unique_ptr<Whitelist> whitelist(new FeaturePolicy::Whitelist); + if (parsedWhitelist.matchesAllOrigins) { + whitelist->addAll(); + } else { + for (const WebSecurityOrigin& origin : parsedWhitelist.origins) + whitelist->add(static_cast<WTF::PassRefPtr<SecurityOrigin>>(origin)); + } + return whitelist; +} + FeaturePolicy::Whitelist::Whitelist() : m_matchesAllOrigins(false) {} void FeaturePolicy::Whitelist::addAll() { @@ -195,20 +145,79 @@ getDefaultFeatureList()); } -void FeaturePolicy::setHeaderPolicy(const String& policy, - Vector<String>* messages) { - DCHECK(m_headerWhitelists.isEmpty()); +// static +WebParsedFeaturePolicy FeaturePolicy::parseFeaturePolicy( + const String& policy, + RefPtr<SecurityOrigin> origin, + Vector<String>* messages) { + Vector<WebFeaturePolicy::ParsedWhitelist> whitelists; + // Use a reasonable parse depth limit; the actual maximum depth is only going // to be 4 for a valid policy, but we'll give the featurePolicyParser a chance // to report more specific errors, unless the string is really invalid. - std::unique_ptr<JSONArray> policyJSON = parseJSONHeader(policy, 50); - if (!policyJSON) { + std::unique_ptr<JSONArray> policyItems = parseJSONHeader(policy, 50); + if (!policyItems) { if (messages) messages->append("Unable to parse header"); - return; + return whitelists; } - m_headerWhitelists = parseFeaturePolicyFromJson( - std::move(policyJSON), m_origin, m_features, messages); + + for (size_t i = 0; i < policyItems->size(); ++i) { + JSONObject* item = JSONObject::cast(policyItems->at(i)); + if (!item) { + if (messages) + messages->append("Policy is not an object"); + continue; // Array element is not an object; skip + } + + for (size_t j = 0; j < item->size(); ++j) { + JSONObject::Entry entry = item->at(j); + String featureName = entry.first; + JSONArray* targets = JSONArray::cast(entry.second); + if (!targets) { + if (messages) + messages->append("Whitelist is not an array of strings."); + continue; + } + + WebFeaturePolicy::ParsedWhitelist whitelist; + whitelist.featureName = featureName; + Vector<WebSecurityOrigin> origins; + String targetString; + for (size_t j = 0; j < targets->size(); ++j) { + if (targets->at(j)->asString(&targetString)) { + if (equalIgnoringCase(targetString, "self")) { + if (!origin->isUnique()) + origins.append(origin); + } else if (targetString == "*") { + whitelist.matchesAllOrigins = true; + } else { + WebSecurityOrigin targetOrigin = + WebSecurityOrigin::createFromString(targetString); + if (!targetOrigin.isNull() && !targetOrigin.isUnique()) + origins.append(targetOrigin); + } + } else { + if (messages) + messages->append("Whitelist is not an array of strings."); + } + } + whitelist.origins = origins; + whitelists.append(whitelist); + } + } + return whitelists; +} + +void FeaturePolicy::setHeaderPolicy(const WebParsedFeaturePolicy& policy) { + DCHECK(m_headerWhitelists.isEmpty()); + for (const WebFeaturePolicy::ParsedWhitelist& parsedWhitelist : policy) { + const FeaturePolicy::Feature* feature = + featureForName(parsedWhitelist.featureName, m_features); + if (!feature) + continue; + m_headerWhitelists.set(feature, Whitelist::from(parsedWhitelist)); + } } bool FeaturePolicy::isFeatureEnabledForOrigin(
diff --git a/third_party/WebKit/Source/platform/feature_policy/FeaturePolicy.h b/third_party/WebKit/Source/platform/feature_policy/FeaturePolicy.h index b73ee05..062d6e5 100644 --- a/third_party/WebKit/Source/platform/feature_policy/FeaturePolicy.h +++ b/third_party/WebKit/Source/platform/feature_policy/FeaturePolicy.h
@@ -7,6 +7,7 @@ #include "platform/PlatformExport.h" #include "platform/weborigin/SecurityOrigin.h" +#include "public/platform/WebFeaturePolicy.h" #include "wtf/RefPtr.h" #include "wtf/Vector.h" #include "wtf/text/WTFString.h" @@ -73,6 +74,9 @@ // will always return true. class Whitelist final { public: + static std::unique_ptr<Whitelist> from( + const WebFeaturePolicy::ParsedWhitelist&); + Whitelist(); // Adds a single origin to the whitelist. @@ -123,14 +127,22 @@ using FeatureList = const Vector<const FeaturePolicy::Feature*>; + // Converts a JSON feature policy string into a vector of whitelists, one for + // each feature specified. Unrecognized features are parsed and included + // but will be filtered out when the policy is constructed. If |messages| is + // not null, then any errors in the input will cause an error message to be + // appended to it. + static WebParsedFeaturePolicy parseFeaturePolicy(const String& policy, + RefPtr<SecurityOrigin>, + Vector<String>* messages); + static std::unique_ptr<FeaturePolicy> createFromParentPolicy( const FeaturePolicy* parent, RefPtr<SecurityOrigin>); - // Sets the declared policy from the Feature-Policy HTTP header. If the header - // cannot be parsed, errors will be appended to the |messages| vector, if not - // null. - void setHeaderPolicy(const String&, Vector<String>* messages); + // Sets the declared policy from the parsed Feature-Policy HTTP header. + // Unrecognized features will be ignored. + void setHeaderPolicy(const WebParsedFeaturePolicy&); // Returns whether or not the given feature is enabled by this policy. bool isFeatureEnabledForOrigin(const Feature&, const SecurityOrigin&) const;
diff --git a/third_party/WebKit/Source/platform/feature_policy/FeaturePolicyFuzzer.cpp b/third_party/WebKit/Source/platform/feature_policy/FeaturePolicyFuzzer.cpp index e641895..041a414 100644 --- a/third_party/WebKit/Source/platform/feature_policy/FeaturePolicyFuzzer.cpp +++ b/third_party/WebKit/Source/platform/feature_policy/FeaturePolicyFuzzer.cpp
@@ -17,9 +17,8 @@ WTF::Vector<WTF::String> messages; RefPtr<blink::SecurityOrigin> origin = blink::SecurityOrigin::createFromString("https://example.com/"); - std::unique_ptr<blink::FeaturePolicy> policy = - blink::FeaturePolicy::createFromParentPolicy(nullptr, origin); - policy->setHeaderPolicy(WTF::String(data, size), &messages); + blink::FeaturePolicy::parseFeaturePolicy(WTF::String(data, size), + origin.get(), &messages); return 0; }
diff --git a/third_party/WebKit/Source/platform/feature_policy/FeaturePolicyTest.cpp b/third_party/WebKit/Source/platform/feature_policy/FeaturePolicyTest.cpp index 54ba7bb..b5ec7f2 100644 --- a/third_party/WebKit/Source/platform/feature_policy/FeaturePolicyTest.cpp +++ b/third_party/WebKit/Source/platform/feature_policy/FeaturePolicyTest.cpp
@@ -87,9 +87,7 @@ Vector<String> messages; for (const char* policyString : kValidPolicies) { messages.clear(); - std::unique_ptr<FeaturePolicy> policy = - createFromParentPolicy(nullptr, m_originA); - policy->setHeaderPolicy(policyString, &messages); + FeaturePolicy::parseFeaturePolicy(policyString, m_originA.get(), &messages); EXPECT_EQ(0UL, messages.size()); } } @@ -98,13 +96,61 @@ Vector<String> messages; for (const char* policyString : kInvalidPolicies) { messages.clear(); - std::unique_ptr<FeaturePolicy> policy = - createFromParentPolicy(nullptr, m_originA); - policy->setHeaderPolicy(policyString, &messages); + FeaturePolicy::parseFeaturePolicy(policyString, m_originA.get(), &messages); EXPECT_NE(0UL, messages.size()); } } +TEST_F(FeaturePolicyTest, PolicyParsedCorrectly) { + Vector<String> messages; + + // Empty policy. + WebParsedFeaturePolicy parsedPolicy = + FeaturePolicy::parseFeaturePolicy("{}", m_originA.get(), &messages); + EXPECT_EQ(0UL, parsedPolicy.size()); + + // Simple policy with "self". + parsedPolicy = FeaturePolicy::parseFeaturePolicy( + "{\"default-self\": [\"self\"]}", m_originA.get(), &messages); + EXPECT_EQ(1UL, parsedPolicy.size()); + EXPECT_EQ("default-self", parsedPolicy[0].featureName); + EXPECT_FALSE(parsedPolicy[0].matchesAllOrigins); + EXPECT_EQ(1UL, parsedPolicy[0].origins.size()); + EXPECT_TRUE(m_originA->isSameSchemeHostPortAndSuborigin( + parsedPolicy[0].origins[0].get())); + + // Simple policy with *. + parsedPolicy = FeaturePolicy::parseFeaturePolicy( + "{\"default-self\": [\"*\"]}", m_originA.get(), &messages); + EXPECT_EQ(1UL, parsedPolicy.size()); + EXPECT_EQ("default-self", parsedPolicy[0].featureName); + EXPECT_TRUE(parsedPolicy[0].matchesAllOrigins); + EXPECT_EQ(0UL, parsedPolicy[0].origins.size()); + + // Complicated policy. + parsedPolicy = FeaturePolicy::parseFeaturePolicy( + "{\"default-self\": [\"*\"], " + "\"default-on\": [\"https://example.net\", \"https://example.org\"], " + "\"default-off\": [\"self\"]}", + m_originA.get(), &messages); + EXPECT_EQ(3UL, parsedPolicy.size()); + EXPECT_EQ("default-self", parsedPolicy[0].featureName); + EXPECT_TRUE(parsedPolicy[0].matchesAllOrigins); + EXPECT_EQ(0UL, parsedPolicy[0].origins.size()); + EXPECT_EQ("default-on", parsedPolicy[1].featureName); + EXPECT_FALSE(parsedPolicy[1].matchesAllOrigins); + EXPECT_EQ(2UL, parsedPolicy[1].origins.size()); + EXPECT_TRUE(m_originB->isSameSchemeHostPortAndSuborigin( + parsedPolicy[1].origins[0].get())); + EXPECT_TRUE(m_originC->isSameSchemeHostPortAndSuborigin( + parsedPolicy[1].origins[1].get())); + EXPECT_EQ("default-off", parsedPolicy[2].featureName); + EXPECT_FALSE(parsedPolicy[2].matchesAllOrigins); + EXPECT_EQ(1UL, parsedPolicy[2].origins.size()); + EXPECT_TRUE(m_originA->isSameSchemeHostPortAndSuborigin( + parsedPolicy[2].origins[0].get())); +} + TEST_F(FeaturePolicyTest, TestInitialPolicy) { // +-------------+ // |(1)Origin A | @@ -175,7 +221,8 @@ createFromParentPolicy(nullptr, m_originA); std::unique_ptr<FeaturePolicy> policy2 = createFromParentPolicy(policy1.get(), m_originB); - policy2->setHeaderPolicy("{\"default-self\": [\"self\"]}", &messages); + policy2->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-self\": [\"self\"]}", m_originB.get(), &messages)); EXPECT_EQ(0UL, messages.size()); EXPECT_FALSE(policy2->isFeatureEnabled(kDefaultSelfFeature)); } @@ -199,7 +246,8 @@ Vector<String> messages; std::unique_ptr<FeaturePolicy> policy1 = createFromParentPolicy(nullptr, m_originA); - policy1->setHeaderPolicy("{\"default-self\": [\"self\"]}", &messages); + policy1->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-self\": [\"self\"]}", m_originA.get(), &messages)); EXPECT_EQ(0UL, messages.size()); std::unique_ptr<FeaturePolicy> policy2 = createFromParentPolicy(policy1.get(), m_originA); @@ -233,7 +281,8 @@ Vector<String> messages; std::unique_ptr<FeaturePolicy> policy1 = createFromParentPolicy(nullptr, m_originA); - policy1->setHeaderPolicy("{\"default-self\": [\"self\"]}", &messages); + policy1->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-self\": [\"self\"]}", m_originA.get(), &messages)); EXPECT_EQ(0UL, messages.size()); std::unique_ptr<FeaturePolicy> policy2 = createFromParentPolicy(policy1.get(), m_originB); @@ -262,7 +311,8 @@ Vector<String> messages; std::unique_ptr<FeaturePolicy> policy1 = createFromParentPolicy(nullptr, m_originA); - policy1->setHeaderPolicy("{\"default-self\": [\"" ORIGIN_B "\"]}", &messages); + policy1->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-self\": [\"" ORIGIN_B "\"]}", m_originA.get(), &messages)); EXPECT_EQ(0UL, messages.size()); std::unique_ptr<FeaturePolicy> policy2 = createFromParentPolicy(policy1.get(), m_originB); @@ -284,7 +334,8 @@ Vector<String> messages; std::unique_ptr<FeaturePolicy> policy1 = createFromParentPolicy(nullptr, m_originA); - policy1->setHeaderPolicy("{\"default-on\": []}", &messages); + policy1->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-on\": []}", m_originA.get(), &messages)); EXPECT_EQ(0UL, messages.size()); EXPECT_FALSE(policy1->isFeatureEnabled(kDefaultOnFeature)); } @@ -302,7 +353,8 @@ Vector<String> messages; std::unique_ptr<FeaturePolicy> policy1 = createFromParentPolicy(nullptr, m_originA); - policy1->setHeaderPolicy("{\"default-on\": []}", &messages); + policy1->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-on\": []}", m_originA.get(), &messages)); EXPECT_EQ(0UL, messages.size()); std::unique_ptr<FeaturePolicy> policy2 = createFromParentPolicy(policy1.get(), m_originA); @@ -324,7 +376,8 @@ createFromParentPolicy(nullptr, m_originA); std::unique_ptr<FeaturePolicy> policy2 = createFromParentPolicy(policy1.get(), m_originB); - policy2->setHeaderPolicy("{\"default-on\": []}", &messages); + policy2->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-on\": []}", m_originB.get(), &messages)); EXPECT_EQ(0UL, messages.size()); EXPECT_FALSE(policy2->isFeatureEnabled(kDefaultOnFeature)); } @@ -349,7 +402,8 @@ createFromParentPolicy(nullptr, m_originA); std::unique_ptr<FeaturePolicy> policy2 = createFromParentPolicy(policy1.get(), m_originB); - policy2->setHeaderPolicy("{\"default-on\": [\"self\"]}", &messages); + policy2->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-on\": [\"self\"]}", m_originB.get(), &messages)); EXPECT_EQ(0UL, messages.size()); std::unique_ptr<FeaturePolicy> policy3 = createFromParentPolicy(policy2.get(), m_originC); @@ -370,7 +424,8 @@ Vector<String> messages; std::unique_ptr<FeaturePolicy> policy1 = createFromParentPolicy(nullptr, m_originA); - policy1->setHeaderPolicy("{\"default-on\": []}", &messages); + policy1->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-on\": []}", m_originA.get(), &messages)); EXPECT_EQ(0UL, messages.size()); std::unique_ptr<FeaturePolicy> policy2 = createFromParentPolicy(policy1.get(), m_originB); @@ -394,7 +449,8 @@ Vector<String> messages; std::unique_ptr<FeaturePolicy> policy1 = createFromParentPolicy(nullptr, m_originA); - policy1->setHeaderPolicy("{\"default-self\": [\"*\"]}", &messages); + policy1->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-self\": [\"*\"]}", m_originA.get(), &messages)); EXPECT_EQ(0UL, messages.size()); std::unique_ptr<FeaturePolicy> policy2 = createFromParentPolicy(policy1.get(), m_originB); @@ -422,7 +478,8 @@ Vector<String> messages; std::unique_ptr<FeaturePolicy> policy1 = createFromParentPolicy(nullptr, m_originA); - policy1->setHeaderPolicy("{\"default-on\": [\"" ORIGIN_B "\"]}", &messages); + policy1->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-on\": [\"" ORIGIN_B "\"]}", m_originA.get(), &messages)); EXPECT_EQ(0UL, messages.size()); std::unique_ptr<FeaturePolicy> policy2 = createFromParentPolicy(policy1.get(), m_originB); @@ -453,7 +510,8 @@ Vector<String> messages; std::unique_ptr<FeaturePolicy> policy1 = createFromParentPolicy(nullptr, m_originA); - policy1->setHeaderPolicy("{\"default-self\": [\"" ORIGIN_B "\"]}", &messages); + policy1->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-self\": [\"" ORIGIN_B "\"]}", m_originA.get(), &messages)); EXPECT_EQ(0UL, messages.size()); std::unique_ptr<FeaturePolicy> policy2 = createFromParentPolicy(policy1.get(), m_originB); @@ -484,16 +542,19 @@ Vector<String> messages; std::unique_ptr<FeaturePolicy> policy1 = createFromParentPolicy(nullptr, m_originA); - policy1->setHeaderPolicy("{\"default-off\": [\"" ORIGIN_B "\"]}", &messages); + policy1->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-off\": [\"" ORIGIN_B "\"]}", m_originA.get(), &messages)); EXPECT_EQ(0UL, messages.size()); std::unique_ptr<FeaturePolicy> policy2 = createFromParentPolicy(policy1.get(), m_originB); - policy2->setHeaderPolicy("{\"default-off\": [\"self\"]}", &messages); + policy2->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-off\": [\"self\"]}", m_originB.get(), &messages)); std::unique_ptr<FeaturePolicy> policy3 = createFromParentPolicy(policy2.get(), m_originB); std::unique_ptr<FeaturePolicy> policy4 = createFromParentPolicy(policy2.get(), m_originC); - policy4->setHeaderPolicy("{\"default-off\": [\"self\"]}", &messages); + policy4->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-off\": [\"self\"]}", m_originC.get(), &messages)); EXPECT_FALSE(policy1->isFeatureEnabled(kDefaultOffFeature)); EXPECT_TRUE(policy2->isFeatureEnabled(kDefaultOffFeature)); EXPECT_FALSE(policy3->isFeatureEnabled(kDefaultOffFeature)); @@ -517,11 +578,13 @@ Vector<String> messages; std::unique_ptr<FeaturePolicy> policy1 = createFromParentPolicy(nullptr, m_originA); - policy1->setHeaderPolicy("{\"default-self\": [\"*\"]}", &messages); + policy1->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-self\": [\"*\"]}", m_originA.get(), &messages)); EXPECT_EQ(0UL, messages.size()); std::unique_ptr<FeaturePolicy> policy2 = createFromParentPolicy(policy1.get(), m_originB); - policy2->setHeaderPolicy("{\"default-self\": [\"*\"]}", &messages); + policy2->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-self\": [\"*\"]}", m_originB.get(), &messages)); EXPECT_EQ(0UL, messages.size()); std::unique_ptr<FeaturePolicy> policy3 = createFromParentPolicy(policy2.get(), m_originA); @@ -547,11 +610,13 @@ Vector<String> messages; std::unique_ptr<FeaturePolicy> policy1 = createFromParentPolicy(nullptr, m_originA); - policy1->setHeaderPolicy("{\"default-self\": [\"self\"]}", &messages); + policy1->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-self\": [\"self\"]}", m_originA.get(), &messages)); EXPECT_EQ(0UL, messages.size()); std::unique_ptr<FeaturePolicy> policy2 = createFromParentPolicy(policy1.get(), m_originB); - policy2->setHeaderPolicy("{\"default-self\": [\"*\"]}", &messages); + policy2->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-self\": [\"*\"]}", m_originB.get(), &messages)); EXPECT_EQ(0UL, messages.size()); std::unique_ptr<FeaturePolicy> policy3 = createFromParentPolicy(policy2.get(), m_originA); @@ -580,13 +645,15 @@ Vector<String> messages; std::unique_ptr<FeaturePolicy> policy1 = createFromParentPolicy(nullptr, m_originA); - policy1->setHeaderPolicy("{\"default-self\": [\"self\", \"" ORIGIN_B "\"]}", - &messages); + policy1->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-self\": [\"self\", \"" ORIGIN_B "\"]}", m_originA.get(), + &messages)); EXPECT_EQ(0UL, messages.size()); std::unique_ptr<FeaturePolicy> policy2 = createFromParentPolicy(policy1.get(), m_originB); - policy2->setHeaderPolicy("{\"default-self\": [\"self\", \"" ORIGIN_C "\"]}", - &messages); + policy2->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-self\": [\"self\", \"" ORIGIN_C "\"]}", m_originB.get(), + &messages)); EXPECT_EQ(0UL, messages.size()); std::unique_ptr<FeaturePolicy> policy3 = createFromParentPolicy(policy2.get(), m_originC); @@ -612,8 +679,9 @@ Vector<String> messages; std::unique_ptr<FeaturePolicy> policy1 = createFromParentPolicy(nullptr, m_originA); - policy1->setHeaderPolicy("{\"default-on\": [\"self\", \"" ORIGIN_B "\"]}", - &messages); + policy1->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-on\": [\"self\", \"" ORIGIN_B "\"]}", m_originA.get(), + &messages)); EXPECT_EQ(0UL, messages.size()); std::unique_ptr<FeaturePolicy> policy2 = createFromParentPolicy(policy1.get(), m_originB); @@ -645,8 +713,9 @@ Vector<String> messages; std::unique_ptr<FeaturePolicy> policy1 = createFromParentPolicy(nullptr, m_originA); - policy1->setHeaderPolicy("{\"default-self\": [\"self\", \"" ORIGIN_B "\"]}", - &messages); + policy1->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-self\": [\"self\", \"" ORIGIN_B "\"]}", m_originA.get(), + &messages)); EXPECT_EQ(0UL, messages.size()); std::unique_ptr<FeaturePolicy> policy2 = createFromParentPolicy(policy1.get(), m_originB); @@ -680,14 +749,16 @@ Vector<String> messages; std::unique_ptr<FeaturePolicy> policy1 = createFromParentPolicy(nullptr, m_originA); - policy1->setHeaderPolicy("{\"default-self\": [\"self\", \"" ORIGIN_B - "\"], \"default-on\": [\"self\"]}", - &messages); + policy1->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-self\": [\"self\", \"" ORIGIN_B + "\"], \"default-on\": [\"self\"]}", + m_originA.get(), &messages)); EXPECT_EQ(0UL, messages.size()); std::unique_ptr<FeaturePolicy> policy2 = createFromParentPolicy(policy1.get(), m_originB); - policy2->setHeaderPolicy( - "{\"default-self\": [\"*\"], \"default-on\": [\"*\"]}", &messages); + policy2->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-self\": [\"*\"], \"default-on\": [\"*\"]}", m_originB.get(), + &messages)); EXPECT_EQ(0UL, messages.size()); std::unique_ptr<FeaturePolicy> policy3 = createFromParentPolicy(policy2.get(), m_originC); @@ -709,8 +780,9 @@ Vector<String> messages; std::unique_ptr<FeaturePolicy> policy1 = createFromParentPolicy(nullptr, m_originA); - policy1->setHeaderPolicy("{\"default-off\": [\"self\", \"" ORIGIN_B "\"]}", - &messages); + policy1->setHeaderPolicy(FeaturePolicy::parseFeaturePolicy( + "{\"default-off\": [\"self\", \"" ORIGIN_B "\"]}", m_originA.get(), + &messages)); EXPECT_EQ(0UL, messages.size()); EXPECT_TRUE( policy1->isFeatureEnabledForOrigin(kDefaultOffFeature, *m_originA));
diff --git a/third_party/WebKit/Source/platform/graphics/GraphicsContext.cpp b/third_party/WebKit/Source/platform/graphics/GraphicsContext.cpp index cb5f3ad..3e2ef2d 100644 --- a/third_party/WebKit/Source/platform/graphics/GraphicsContext.cpp +++ b/third_party/WebKit/Source/platform/graphics/GraphicsContext.cpp
@@ -854,8 +854,7 @@ } else { // Clip-based fallback. SkAutoCanvasRestore autoRestore(m_canvas, true); - m_canvas->clipRRect(dest, SkRegion::kIntersect_Op, - imagePaint.isAntiAlias()); + m_canvas->clipRRect(dest, imagePaint.isAntiAlias()); image->draw(m_canvas, imagePaint, dest.rect(), srcRect, respectOrientation, Image::ClampImageToSourceRect); } @@ -1138,17 +1137,17 @@ } void GraphicsContext::clipRoundedRect(const FloatRoundedRect& rrect, - SkRegion::Op regionOp, + SkClipOp clipOp, AntiAliasingMode shouldAntialias) { if (contextDisabled()) return; if (!rrect.isRounded()) { - clipRect(rrect.rect(), shouldAntialias, regionOp); + clipRect(rrect.rect(), shouldAntialias, clipOp); return; } - clipRRect(rrect, shouldAntialias, regionOp); + clipRRect(rrect, shouldAntialias, clipOp); } void GraphicsContext::clipOut(const Path& pathToClip) { @@ -1167,12 +1166,12 @@ if (contextDisabled()) return; - clipRoundedRect(rect, SkRegion::kDifference_Op); + clipRoundedRect(rect, kDifference_SkClipOp); } void GraphicsContext::clipRect(const SkRect& rect, AntiAliasingMode aa, - SkRegion::Op op) { + SkClipOp op) { if (contextDisabled()) return; ASSERT(m_canvas); @@ -1182,7 +1181,7 @@ void GraphicsContext::clipPath(const SkPath& path, AntiAliasingMode aa, - SkRegion::Op op) { + SkClipOp op) { if (contextDisabled()) return; ASSERT(m_canvas); @@ -1192,7 +1191,7 @@ void GraphicsContext::clipRRect(const SkRRect& rect, AntiAliasingMode aa, - SkRegion::Op op) { + SkClipOp op) { if (contextDisabled()) return; ASSERT(m_canvas);
diff --git a/third_party/WebKit/Source/platform/graphics/GraphicsContext.h b/third_party/WebKit/Source/platform/graphics/GraphicsContext.h index 7aa52e1b..f4a56f7 100644 --- a/third_party/WebKit/Source/platform/graphics/GraphicsContext.h +++ b/third_party/WebKit/Source/platform/graphics/GraphicsContext.h
@@ -35,10 +35,10 @@ #include "platform/graphics/GraphicsContextState.h" #include "platform/graphics/ImageOrientation.h" #include "platform/graphics/skia/SkiaUtils.h" +#include "third_party/skia/include/core/SkClipOp.h" #include "third_party/skia/include/core/SkMetaData.h" #include "third_party/skia/include/core/SkPictureRecorder.h" #include "third_party/skia/include/core/SkRefCnt.h" -#include "third_party/skia/include/core/SkRegion.h" #include "wtf/Allocator.h" #include "wtf/Forward.h" #include "wtf/Noncopyable.h" @@ -220,22 +220,22 @@ void clip(const IntRect& rect) { clipRect(rect); } void clip(const FloatRect& rect) { clipRect(rect); } void clipRoundedRect(const FloatRoundedRect&, - SkRegion::Op = SkRegion::kIntersect_Op, + SkClipOp = kIntersect_SkClipOp, AntiAliasingMode = AntiAliased); void clipOut(const IntRect& rect) { - clipRect(rect, NotAntiAliased, SkRegion::kDifference_Op); + clipRect(rect, NotAntiAliased, kDifference_SkClipOp); } void clipOut(const FloatRect& rect) { - clipRect(rect, NotAntiAliased, SkRegion::kDifference_Op); + clipRect(rect, NotAntiAliased, kDifference_SkClipOp); } void clipOut(const Path&); void clipOutRoundedRect(const FloatRoundedRect&); void clipPath(const SkPath&, AntiAliasingMode = NotAntiAliased, - SkRegion::Op = SkRegion::kIntersect_Op); + SkClipOp = kIntersect_SkClipOp); void clipRect(const SkRect&, AntiAliasingMode = NotAntiAliased, - SkRegion::Op = SkRegion::kIntersect_Op); + SkClipOp = kIntersect_SkClipOp); void drawText(const Font&, const TextRunPaintInfo&, const FloatPoint&); void drawText(const Font&, @@ -397,7 +397,7 @@ // SkCanvas wrappers. void clipRRect(const SkRRect&, AntiAliasingMode = NotAntiAliased, - SkRegion::Op = SkRegion::kIntersect_Op); + SkClipOp = kIntersect_SkClipOp); void concat(const SkMatrix&); // Apply deferred paint state saves
diff --git a/third_party/WebKit/Source/platform/graphics/InterceptingCanvas.h b/third_party/WebKit/Source/platform/graphics/InterceptingCanvas.h index fe143b4..316561d6 100644 --- a/third_party/WebKit/Source/platform/graphics/InterceptingCanvas.h +++ b/third_party/WebKit/Source/platform/graphics/InterceptingCanvas.h
@@ -144,10 +144,10 @@ SkScalar x, SkScalar y, const SkPaint&) override = 0; - void onClipRect(const SkRect&, SkRegion::Op, ClipEdgeStyle) override = 0; - void onClipRRect(const SkRRect&, SkRegion::Op, ClipEdgeStyle) override = 0; - void onClipPath(const SkPath&, SkRegion::Op, ClipEdgeStyle) override = 0; - void onClipRegion(const SkRegion&, SkRegion::Op) override = 0; + void onClipRect(const SkRect&, SkClipOp, ClipEdgeStyle) override = 0; + void onClipRRect(const SkRRect&, SkClipOp, ClipEdgeStyle) override = 0; + void onClipPath(const SkPath&, SkClipOp, ClipEdgeStyle) override = 0; + void onClipRegion(const SkRegion&, SkClipOp) override = 0; void onDrawPicture(const SkPicture*, const SkMatrix*, const SkPaint*) override = 0; @@ -317,27 +317,27 @@ } void onClipRect(const SkRect& rect, - SkRegion::Op op, + SkClipOp op, ClipEdgeStyle edgeStyle) override { Interceptor interceptor(this); this->SkCanvas::onClipRect(rect, op, edgeStyle); } void onClipRRect(const SkRRect& rrect, - SkRegion::Op op, + SkClipOp op, ClipEdgeStyle edgeStyle) override { Interceptor interceptor(this); this->SkCanvas::onClipRRect(rrect, op, edgeStyle); } void onClipPath(const SkPath& path, - SkRegion::Op op, + SkClipOp op, ClipEdgeStyle edgeStyle) override { Interceptor interceptor(this); this->SkCanvas::onClipPath(path, op, edgeStyle); } - void onClipRegion(const SkRegion& region, SkRegion::Op op) override { + void onClipRegion(const SkRegion& region, SkClipOp op) override { Interceptor interceptor(this); this->SkCanvas::onClipRegion(region, op); }
diff --git a/third_party/WebKit/Source/platform/graphics/LoggingCanvas.cpp b/third_party/WebKit/Source/platform/graphics/LoggingCanvas.cpp index 51618e1..d9f3b3e 100644 --- a/third_party/WebKit/Source/platform/graphics/LoggingCanvas.cpp +++ b/third_party/WebKit/Source/platform/graphics/LoggingCanvas.cpp
@@ -493,19 +493,19 @@ return scalarsArray; } -String regionOpName(SkRegion::Op op) { +String clipOpName(SkClipOp op) { switch (op) { - case SkRegion::kDifference_Op: + case kDifference_SkClipOp: return "kDifference_Op"; - case SkRegion::kIntersect_Op: + case kIntersect_SkClipOp: return "kIntersect_Op"; - case SkRegion::kUnion_Op: + case kUnion_SkClipOp: return "kUnion_Op"; - case SkRegion::kXOR_Op: + case kXOR_SkClipOp: return "kXOR_Op"; - case SkRegion::kReverseDifference_Op: + case kReverseDifference_SkClipOp: return "kReverseDifference_Op"; - case SkRegion::kReplace_Op: + case kReplace_SkClipOp: return "kReplace_Op"; default: return "Unknown type"; @@ -818,42 +818,42 @@ } void LoggingCanvas::onClipRect(const SkRect& rect, - SkRegion::Op op, + SkClipOp op, ClipEdgeStyle style) { AutoLogger logger(this); JSONObject* params = logger.logItemWithParams("clipRect"); params->setObject("rect", objectForSkRect(rect)); - params->setString("SkRegion::Op", regionOpName(op)); + params->setString("SkRegion::Op", clipOpName(op)); params->setBoolean("softClipEdgeStyle", kSoft_ClipEdgeStyle == style); this->SkCanvas::onClipRect(rect, op, style); } void LoggingCanvas::onClipRRect(const SkRRect& rrect, - SkRegion::Op op, + SkClipOp op, ClipEdgeStyle style) { AutoLogger logger(this); JSONObject* params = logger.logItemWithParams("clipRRect"); params->setObject("rrect", objectForSkRRect(rrect)); - params->setString("SkRegion::Op", regionOpName(op)); + params->setString("SkRegion::Op", clipOpName(op)); params->setBoolean("softClipEdgeStyle", kSoft_ClipEdgeStyle == style); this->SkCanvas::onClipRRect(rrect, op, style); } void LoggingCanvas::onClipPath(const SkPath& path, - SkRegion::Op op, + SkClipOp op, ClipEdgeStyle style) { AutoLogger logger(this); JSONObject* params = logger.logItemWithParams("clipPath"); params->setObject("path", objectForSkPath(path)); - params->setString("SkRegion::Op", regionOpName(op)); + params->setString("SkRegion::Op", clipOpName(op)); params->setBoolean("softClipEdgeStyle", kSoft_ClipEdgeStyle == style); this->SkCanvas::onClipPath(path, op, style); } -void LoggingCanvas::onClipRegion(const SkRegion& region, SkRegion::Op op) { +void LoggingCanvas::onClipRegion(const SkRegion& region, SkClipOp op) { AutoLogger logger(this); JSONObject* params = logger.logItemWithParams("clipRegion"); - params->setString("op", regionOpName(op)); + params->setString("op", clipOpName(op)); this->SkCanvas::onClipRegion(region, op); }
diff --git a/third_party/WebKit/Source/platform/graphics/LoggingCanvas.h b/third_party/WebKit/Source/platform/graphics/LoggingCanvas.h index 54e52a9..5a44103 100644 --- a/third_party/WebKit/Source/platform/graphics/LoggingCanvas.h +++ b/third_party/WebKit/Source/platform/graphics/LoggingCanvas.h
@@ -108,10 +108,10 @@ SkScalar x, SkScalar y, const SkPaint&) override; - void onClipRect(const SkRect&, SkRegion::Op, ClipEdgeStyle) override; - void onClipRRect(const SkRRect&, SkRegion::Op, ClipEdgeStyle) override; - void onClipPath(const SkPath&, SkRegion::Op, ClipEdgeStyle) override; - void onClipRegion(const SkRegion&, SkRegion::Op) override; + void onClipRect(const SkRect&, SkClipOp, ClipEdgeStyle) override; + void onClipRRect(const SkRRect&, SkClipOp, ClipEdgeStyle) override; + void onClipPath(const SkPath&, SkClipOp, ClipEdgeStyle) override; + void onClipRegion(const SkRegion&, SkClipOp) override; virtual void onDrawPicture(const SkPicture*, const SkMatrix*, const SkPaint*); void didSetMatrix(const SkMatrix&) override; void didConcat(const SkMatrix&) override;
diff --git a/third_party/WebKit/Source/platform/graphics/paint/ClipPathDisplayItem.cpp b/third_party/WebKit/Source/platform/graphics/paint/ClipPathDisplayItem.cpp index 4b2a3c4b..d208879 100644 --- a/third_party/WebKit/Source/platform/graphics/paint/ClipPathDisplayItem.cpp +++ b/third_party/WebKit/Source/platform/graphics/paint/ClipPathDisplayItem.cpp
@@ -20,7 +20,7 @@ void BeginClipPathDisplayItem::appendToWebDisplayItemList( const IntRect& visualRect, WebDisplayItemList* list) const { - list->appendClipPathItem(m_clipPath, SkRegion::kIntersect_Op, true); + list->appendClipPathItem(m_clipPath, kIntersect_SkClipOp, true); } void BeginClipPathDisplayItem::analyzeForGpuRasterization(
diff --git a/third_party/WebKit/Source/platform/graphics/paint/DisplayItem.cpp b/third_party/WebKit/Source/platform/graphics/paint/DisplayItem.cpp index ecaa5b61..f37ca39 100644 --- a/third_party/WebKit/Source/platform/graphics/paint/DisplayItem.cpp +++ b/third_party/WebKit/Source/platform/graphics/paint/DisplayItem.cpp
@@ -68,23 +68,6 @@ return "Unknown" static WTF::String specialDrawingTypeAsDebugString(DisplayItem::Type type) { - if (type >= DisplayItem::kTableCollapsedBorderUnalignedBase) { - if (type <= DisplayItem::kTableCollapsedBorderBase) - return "TableCollapsedBorderAlignment"; - if (type <= DisplayItem::kTableCollapsedBorderLast) { - StringBuilder sb; - sb.append("TableCollapsedBorder"); - if (type & DisplayItem::TableCollapsedBorderTop) - sb.append("Top"); - if (type & DisplayItem::TableCollapsedBorderRight) - sb.append("Right"); - if (type & DisplayItem::TableCollapsedBorderBottom) - sb.append("Bottom"); - if (type & DisplayItem::TableCollapsedBorderLeft) - sb.append("Left"); - return sb.toString(); - } - } switch (type) { DEBUG_STRING_CASE(BoxDecorationBackground); DEBUG_STRING_CASE(Caret); @@ -126,6 +109,7 @@ DEBUG_STRING_CASE(TableCellBackgroundFromColumn); DEBUG_STRING_CASE(TableCellBackgroundFromSection); DEBUG_STRING_CASE(TableCellBackgroundFromRow); + DEBUG_STRING_CASE(TableCollapsedBorders); DEBUG_STRING_CASE(TableSectionBoxShadowInset); DEBUG_STRING_CASE(TableSectionBoxShadowNormal); DEBUG_STRING_CASE(TableRowBoxShadowInset);
diff --git a/third_party/WebKit/Source/platform/graphics/paint/DisplayItem.h b/third_party/WebKit/Source/platform/graphics/paint/DisplayItem.h index 90d9449..efdf25d 100644 --- a/third_party/WebKit/Source/platform/graphics/paint/DisplayItem.h +++ b/third_party/WebKit/Source/platform/graphics/paint/DisplayItem.h
@@ -110,15 +110,7 @@ kTableCellBackgroundFromColumn, kTableCellBackgroundFromSection, kTableCellBackgroundFromRow, - // Table collapsed borders can be painted together (e.g., left & top) but - // there are at most 4 phases of collapsed border painting for a single - // cell. To disambiguate these phases of collapsed border painting, a mask - // is used. TableCollapsedBorderBase can be larger than - // TableCollapsedBorderUnalignedBase to ensure the base lower bits are 0's. - kTableCollapsedBorderUnalignedBase, - kTableCollapsedBorderBase = - (((kTableCollapsedBorderUnalignedBase - 1) >> 4) + 1) << 4, - kTableCollapsedBorderLast = kTableCollapsedBorderBase + 0x0f, + kTableCollapsedBorders, kTableSectionBoxShadowInset, kTableSectionBoxShadowNormal, kTableRowBoxShadowInset, @@ -202,19 +194,6 @@ kTypeLast = kUninitializedType }; - static_assert(kTableCollapsedBorderBase >= kTableCollapsedBorderUnalignedBase, - "TableCollapsedBorder types overlap with other types"); - static_assert((kTableCollapsedBorderBase & 0xf) == 0, - "The lowest 4 bits of TableCollapsedBorderBase should be zero"); - // Bits or'ed onto TableCollapsedBorderBase to generate a real table collapsed - // border type. - enum TableCollapsedBorderSides { - TableCollapsedBorderTop = 1 << 0, - TableCollapsedBorderRight = 1 << 1, - TableCollapsedBorderBottom = 1 << 2, - TableCollapsedBorderLeft = 1 << 3, - }; - DisplayItem(const DisplayItemClient& client, Type type, size_t derivedSize) : m_client(&client), m_type(type),
diff --git a/third_party/WebKit/Source/platform/graphics/paint/DisplayItemClient.h b/third_party/WebKit/Source/platform/graphics/paint/DisplayItemClient.h index 25a5bcd9f..fb47d824 100644 --- a/third_party/WebKit/Source/platform/graphics/paint/DisplayItemClient.h +++ b/third_party/WebKit/Source/platform/graphics/paint/DisplayItemClient.h
@@ -55,6 +55,20 @@ // offsetFromLayoutObjectWithSubpixelAccumulation(). virtual LayoutRect visualRect() const = 0; + // This is declared here instead of in LayoutObject for verifying the + // condition in DrawingRecorder. + // Returns true if the object itself will not generate any effective painted + // output no matter what size the object is. For example, this function can + // return false for an object whose size is currently 0x0 but would have + // effective painted output if it was set a non-empty size. It's used to skip + // unforced paint invalidation of LayoutObjects (which is when + // shouldDoFullPaintInvalidation is false, but mayNeedPaintInvalidation or + // childShouldCheckForPaintInvalidation is true) to avoid unnecessary paint + // invalidations of empty areas covered by such objects. + virtual bool paintedOutputOfObjectHasNoEffectRegardlessOfSize() const { + return false; + } + void setDisplayItemsUncached( PaintInvalidationReason reason = PaintInvalidationFull) const { m_cacheGenerationOrInvalidationReason.invalidate(reason);
diff --git a/third_party/WebKit/Source/platform/graphics/paint/DrawingRecorder.cpp b/third_party/WebKit/Source/platform/graphics/paint/DrawingRecorder.cpp index 3516816..a6011c9 100644 --- a/third_party/WebKit/Source/platform/graphics/paint/DrawingRecorder.cpp +++ b/third_party/WebKit/Source/platform/graphics/paint/DrawingRecorder.cpp
@@ -61,7 +61,7 @@ // expand by one pixel in device (pixel) space, but to do that we would need // to add the verification mode to Skia. cullRect.inflate(1); - context.clipRect(cullRect, NotAntiAliased, SkRegion::kIntersect_Op); + context.clipRect(cullRect, NotAntiAliased, kIntersect_SkClipOp); } #endif } @@ -79,9 +79,19 @@ m_context.getPaintController().newDisplayItemList().size()); #endif + sk_sp<const SkPicture> picture = m_context.endRecording(); + +#if DCHECK_IS_ON() + if (!RuntimeEnabledFeatures::slimmingPaintStrictCullRectClippingEnabled() && + !m_context.getPaintController().isForSkPictureBuilder() && + m_displayItemClient.paintedOutputOfObjectHasNoEffectRegardlessOfSize()) { + DCHECK_EQ(0, picture->approximateOpCount()) + << m_displayItemClient.debugName(); + } +#endif + m_context.getPaintController().createAndAppend<DrawingDisplayItem>( - m_displayItemClient, m_displayItemType, m_context.endRecording(), - m_knownToBeOpaque); + m_displayItemClient, m_displayItemType, picture, m_knownToBeOpaque); } } // namespace blink
diff --git a/third_party/WebKit/Source/platform/graphics/paint/PaintController.h b/third_party/WebKit/Source/platform/graphics/paint/PaintController.h index 8aaebc3..cde4c2d 100644 --- a/third_party/WebKit/Source/platform/graphics/paint/PaintController.h +++ b/third_party/WebKit/Source/platform/graphics/paint/PaintController.h
@@ -187,6 +187,10 @@ #if DCHECK_IS_ON() void assertDisplayItemClientsAreLive(); + + enum Usage { ForNormalUsage, ForSkPictureBuilder }; + void setUsage(Usage usage) { m_usage = usage; } + bool isForSkPictureBuilder() const { return m_usage == ForSkPictureBuilder; } #endif void setTracksRasterInvalidations(bool value); @@ -351,6 +355,8 @@ #if DCHECK_IS_ON() // This is used to check duplicated ids during createAndAppend(). IndicesByClientMap m_newDisplayItemIndicesByClient; + + Usage m_usage; #endif // These are set in useCachedDrawingIfPossible() and
diff --git a/third_party/WebKit/Source/platform/graphics/paint/SkPictureBuilder.cpp b/third_party/WebKit/Source/platform/graphics/paint/SkPictureBuilder.cpp index cdc23e9b..3905add 100644 --- a/third_party/WebKit/Source/platform/graphics/paint/SkPictureBuilder.cpp +++ b/third_party/WebKit/Source/platform/graphics/paint/SkPictureBuilder.cpp
@@ -26,6 +26,10 @@ m_paintControllerPtr = PaintController::create(); m_paintController = m_paintControllerPtr.get(); } +#if DCHECK_IS_ON() + m_paintController->setUsage(PaintController::ForSkPictureBuilder); +#endif + m_context = wrapUnique( new GraphicsContext(*m_paintController, disabledMode, metaData)); @@ -35,7 +39,11 @@ } } -SkPictureBuilder::~SkPictureBuilder() {} +SkPictureBuilder::~SkPictureBuilder() { +#if DCHECK_IS_ON() + m_paintController->setUsage(PaintController::ForNormalUsage); +#endif +} sk_sp<SkPicture> SkPictureBuilder::endRecording() { m_context->beginRecording(m_bounds);
diff --git a/third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp b/third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp new file mode 100644 index 0000000..f03406d7 --- /dev/null +++ b/third_party/WebKit/Source/platform/image-decoders/png/PNGImageDecoderTest.cpp
@@ -0,0 +1,179 @@ +// Copyright 2016 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 "platform/image-decoders/png/PNGImageDecoder.h" + +#include "platform/image-decoders/ImageDecoderTestHelpers.h" +#include "testing/gtest/include/gtest/gtest.h" +#include <memory> + +namespace blink { + +namespace { + +std::unique_ptr<ImageDecoder> createDecoder( + ImageDecoder::AlphaOption alphaOption) { + return wrapUnique( + new PNGImageDecoder(alphaOption, ImageDecoder::ColorSpaceTransformed, + ImageDecoder::targetColorSpaceForTesting(), + ImageDecoder::noDecodedImageByteLimit)); +} + +std::unique_ptr<ImageDecoder> createDecoder() { + return createDecoder(ImageDecoder::AlphaNotPremultiplied); +} + +std::unique_ptr<ImageDecoder> createDecoderWithPngData(const char* pngFile) { + auto decoder = createDecoder(); + auto data = readFile(pngFile); + EXPECT_FALSE(data->isEmpty()); + decoder->setData(data.get(), true); + return decoder; +} + +void testSize(const char* pngFile, IntSize expectedSize) { + auto decoder = createDecoderWithPngData(pngFile); + EXPECT_TRUE(decoder->isSizeAvailable()); + EXPECT_EQ(expectedSize, decoder->size()); +} + +void testRepetitionCount(const char* pngFile, int expectedRepetitionCount) { + auto decoder = createDecoderWithPngData(pngFile); + // Decoding the frame count sets the repetition count as well. + decoder->frameCount(); + EXPECT_FALSE(decoder->failed()); + EXPECT_EQ(expectedRepetitionCount, decoder->repetitionCount()); +} + +// Verify that the decoder can successfully decode the first frame when +// initially only half of the frame data is received, resulting in a partially +// decoded image, and then the rest of the image data is received. Verify that +// the bitmap hashes of the two stages are different. Also verify that the final +// bitmap hash is equivalent to the hash when all data is provided at once. +// +// This verifies that decoder correctly keeps track of where it stopped +// decoding when the image was not yet fully received. +void testProgressiveDecodingContinuesAfterFullData(const char* pngFile, + size_t offsetMidFirstFrame) { + auto fullData = readFile(pngFile); + ASSERT_FALSE(fullData->isEmpty()); + + auto decoderUpfront = createDecoder(); + decoderUpfront->setData(fullData.get(), true); + EXPECT_GE(1u, decoderUpfront->frameCount()); + const ImageFrame* const frameUpfront = decoderUpfront->frameBufferAtIndex(0); + ASSERT_EQ(ImageFrame::FrameComplete, frameUpfront->getStatus()); + const unsigned hashUpfront = hashBitmap(frameUpfront->bitmap()); + + auto decoder = createDecoder(); + RefPtr<SharedBuffer> partialData = + SharedBuffer::create(fullData->data(), offsetMidFirstFrame); + decoder->setData(partialData, false); + + EXPECT_EQ(1u, decoder->frameCount()); + const ImageFrame* frame = decoder->frameBufferAtIndex(0); + EXPECT_EQ(frame->getStatus(), ImageFrame::FramePartial); + const unsigned hashPartial = hashBitmap(frame->bitmap()); + + decoder->setData(fullData.get(), true); + frame = decoder->frameBufferAtIndex(0); + EXPECT_EQ(frame->getStatus(), ImageFrame::FrameComplete); + const unsigned hashFull = hashBitmap(frame->bitmap()); + + EXPECT_FALSE(decoder->failed()); + EXPECT_NE(hashFull, hashPartial); + EXPECT_EQ(hashFull, hashUpfront); +} + +// Modify the frame data bytes for frame |frameIndex| so that decoding fails. +// Parsing should work fine, and is checked with |expectedFrameCountBefore|. If +// the failure should invalidate the decoder, |expectFailure| should be set to +// true. If not, |expectedFrameCountAfter| should indicate the new frame count +// after the failure. +void testFailureDuringDecode(const char* file, + size_t idatOffset, + size_t frameIndex, + bool expectFailure, + size_t expectedFrameCountBefore, + size_t expectedFrameCountAfter = 0u) { + RefPtr<SharedBuffer> fullData = readFile(file); + ASSERT_FALSE(fullData->isEmpty()); + + // This is the offset where the frame data chunk frame |frameIndex| starts. + RefPtr<SharedBuffer> data = + SharedBuffer::create(fullData->data(), idatOffset + 8u); + // Repeat the first 8 bytes of the frame data. This should result in a + // successful parse, since frame data is not analyzed in that step, but + // should give an error during decoding. + data->append(fullData->data() + idatOffset, 8u); + data->append(fullData->data() + idatOffset + 16u, + fullData->size() - idatOffset - 16u); + + auto decoder = createDecoder(); + decoder->setData(data.get(), true); + + EXPECT_EQ(expectedFrameCountBefore, decoder->frameCount()); + + const ImageFrame* const frame = decoder->frameBufferAtIndex(frameIndex); + EXPECT_EQ(expectFailure, decoder->failed()); + if (!expectFailure) { + EXPECT_EQ(expectedFrameCountAfter, decoder->frameCount()); + EXPECT_EQ(ImageFrame::FrameEmpty, frame->getStatus()); + } +} + +} // Anonymous namespace + +// Static PNG tests + +TEST(StaticPNGTests, repetitionCountTest) { + testRepetitionCount("/LayoutTests/images/resources/png-simple.png", + cAnimationNone); +} + +TEST(StaticPNGTests, sizeTest) { + testSize("/LayoutTests/images/resources/png-simple.png", IntSize(111, 29)); +} + +TEST(StaticPNGTests, MetaDataTest) { + const size_t expectedFrameCount = 1; + const size_t expectedDuration = 0; + auto decoder = + createDecoderWithPngData("/LayoutTests/images/resources/png-simple.png"); + EXPECT_EQ(expectedFrameCount, decoder->frameCount()); + EXPECT_EQ(expectedDuration, decoder->frameDurationAtIndex(0)); +} + +TEST(StaticPNGTests, ProgressiveDecoding) { + testProgressiveDecoding(&createDecoder, + "/LayoutTests/images/resources/png-simple.png", 11u); +} + +TEST(StaticPNGTests, ProgressiveDecodingContinuesAfterFullData) { + testProgressiveDecodingContinuesAfterFullData( + "/LayoutTests/images/resources/png-simple.png", 1000u); +} + +TEST(StaticPNGTests, FailureDuringDecodingInvalidatesDecoder) { + testFailureDuringDecode( + "/LayoutTests/images/resources/png-simple.png", + 85u, // idat offset for frame index 0 + 0u, // try to decode frame index 0 + true, // expect the decoder to be invalidated after the failure + 1u); // expected frame count before failure +} + +// For static images, frameIsCompleteAtIndex(0) should return true if and only +// if the frame is successfully decoded, not when it is fully received. +TEST(StaticPNGTests, VerifyFrameCompleteBehavior) { + auto decoder = + createDecoderWithPngData("/LayoutTests/images/resources/png-simple.png"); + EXPECT_EQ(1u, decoder->frameCount()); + EXPECT_FALSE(decoder->frameIsCompleteAtIndex(0)); + EXPECT_EQ(ImageFrame::FrameComplete, + decoder->frameBufferAtIndex(0)->getStatus()); + EXPECT_TRUE(decoder->frameIsCompleteAtIndex(0)); +} + +}; // namespace blink
diff --git a/third_party/WebKit/Source/platform/scheduler/base/task_queue_manager.cc b/third_party/WebKit/Source/platform/scheduler/base/task_queue_manager.cc index a72e1459..74a311b1 100644 --- a/third_party/WebKit/Source/platform/scheduler/base/task_queue_manager.cc +++ b/third_party/WebKit/Source/platform/scheduler/base/task_queue_manager.cc
@@ -232,11 +232,6 @@ queues_to_delete_.clear(); LazyNow lazy_now(real_time_domain()->CreateLazyNow()); - base::TimeTicks task_start_time; - - if (!delegate_->IsNested() && task_time_observers_.might_have_observers()) - task_start_time = lazy_now.Now(); - UpdateWorkQueues(lazy_now); for (int i = 0; i < work_batch_size_; i++) { @@ -244,11 +239,7 @@ if (!SelectWorkQueueToService(&work_queue)) break; - // TaskQueueManager guarantees that task queue will not be deleted - // when we are in DoWork (but WorkQueue may be deleted). - internal::TaskQueueImpl* task_queue = work_queue->task_queue(); - - switch (ProcessTaskFromWorkQueue(work_queue)) { + switch (ProcessTaskFromWorkQueue(work_queue, &lazy_now)) { case ProcessTaskResult::DEFERRED: // If a task was deferred, try again with another task. continue; @@ -258,18 +249,6 @@ return; // The TaskQueueManager got deleted, we must bail out. } - lazy_now = real_time_domain()->CreateLazyNow(); - if (!delegate_->IsNested() && task_start_time != base::TimeTicks()) { - // Only report top level task durations. - base::TimeTicks task_end_time = lazy_now.Now(); - for (auto& observer : task_time_observers_) { - observer.ReportTaskTime(task_queue, - MonotonicTimeInSeconds(task_start_time), - MonotonicTimeInSeconds(task_end_time)); - } - task_start_time = task_end_time; - } - work_queue = nullptr; // The queue may have been unregistered. UpdateWorkQueues(lazy_now); @@ -312,7 +291,8 @@ } TaskQueueManager::ProcessTaskResult TaskQueueManager::ProcessTaskFromWorkQueue( - internal::WorkQueue* work_queue) { + internal::WorkQueue* work_queue, + LazyNow* lazy_now) { DCHECK(main_thread_checker_.CalledOnValidThread()); scoped_refptr<DeletionSentinel> protect(deletion_sentinel_); internal::TaskQueueImpl::Task pending_task = @@ -341,13 +321,23 @@ MaybeRecordTaskDelayHistograms(pending_task, queue); + double task_start_time = 0; TRACE_TASK_EXECUTION("TaskQueueManager::ProcessTaskFromWorkQueue", pending_task); if (queue->GetShouldNotifyObservers()) { for (auto& observer : task_observers_) observer.WillProcessTask(pending_task); queue->NotifyWillProcessTask(pending_task); + + bool notify_time_observers = + !delegate_->IsNested() && task_time_observers_.might_have_observers(); + if (notify_time_observers) { + task_start_time = MonotonicTimeInSeconds(lazy_now->Now()); + for (auto& observer : task_time_observers_) + observer.willProcessTask(queue, task_start_time); + } } + TRACE_EVENT1(tracing_category_, "TaskQueueManager::RunTask", "queue", queue->GetName()); // NOTE when TaskQueues get unregistered a reference ends up getting retained @@ -364,7 +354,15 @@ currently_executing_task_queue_ = prev_executing_task_queue; + *lazy_now = real_time_domain()->CreateLazyNow(); + if (queue->GetShouldNotifyObservers()) { + if (task_start_time) { + double task_end_time = MonotonicTimeInSeconds(lazy_now->Now()); + for (auto& observer : task_time_observers_) + observer.didProcessTask(queue, task_start_time, task_end_time); + } + for (auto& observer : task_observers_) observer.DidProcessTask(pending_task); queue->NotifyDidProcessTask(pending_task);
diff --git a/third_party/WebKit/Source/platform/scheduler/base/task_queue_manager.h b/third_party/WebKit/Source/platform/scheduler/base/task_queue_manager.h index 5509bd9..0fbfa7e 100644 --- a/third_party/WebKit/Source/platform/scheduler/base/task_queue_manager.h +++ b/third_party/WebKit/Source/platform/scheduler/base/task_queue_manager.h
@@ -188,7 +188,8 @@ EXECUTED, TASK_QUEUE_MANAGER_DELETED }; - ProcessTaskResult ProcessTaskFromWorkQueue(internal::WorkQueue* work_queue); + ProcessTaskResult ProcessTaskFromWorkQueue(internal::WorkQueue* work_queue, + LazyNow*); bool RunsTasksOnCurrentThread() const; bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here,
diff --git a/third_party/WebKit/Source/platform/scheduler/base/test_task_time_observer.h b/third_party/WebKit/Source/platform/scheduler/base/test_task_time_observer.h index 7d2e64d..f448af7 100644 --- a/third_party/WebKit/Source/platform/scheduler/base/test_task_time_observer.h +++ b/third_party/WebKit/Source/platform/scheduler/base/test_task_time_observer.h
@@ -13,7 +13,8 @@ class TestTaskTimeObserver : public TaskTimeObserver { public: - void ReportTaskTime(TaskQueue* task_queue, + void willProcessTask(TaskQueue* task_queue, double start_time) override {} + void didProcessTask(TaskQueue* task_queue, double start_time, double end_time) override {} };
diff --git a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.cc b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.cc index 0beb608e..752e9a1 100644 --- a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.cc +++ b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.cc
@@ -1661,7 +1661,7 @@ } } -void RendererSchedulerImpl::ReportTaskTime(TaskQueue* task_queue, +void RendererSchedulerImpl::didProcessTask(TaskQueue* task_queue, double start_time, double end_time) { // TODO(scheduler-dev): Remove conversions when Blink starts using
diff --git a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.h b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.h index 2d06c3ca..a9286e04 100644 --- a/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.h +++ b/third_party/WebKit/Source/platform/scheduler/renderer/renderer_scheduler_impl.h
@@ -136,7 +136,8 @@ const base::PendingTask& task) override; // TaskTimeObserver implementation: - void ReportTaskTime(TaskQueue* task_queue, + void willProcessTask(TaskQueue* task_queue, double start_time) override{}; + void didProcessTask(TaskQueue* task_queue, double start_time, double end_time) override;
diff --git a/third_party/WebKit/Source/web/FrameLoaderClientImpl.cpp b/third_party/WebKit/Source/web/FrameLoaderClientImpl.cpp index 1fae82c..362cd93 100644 --- a/third_party/WebKit/Source/web/FrameLoaderClientImpl.cpp +++ b/third_party/WebKit/Source/web/FrameLoaderClientImpl.cpp
@@ -872,9 +872,9 @@ } void FrameLoaderClientImpl::didSetFeaturePolicyHeader( - const String& headerValue) { + const WebParsedFeaturePolicy& parsedHeader) { if (m_webFrame->client()) - m_webFrame->client()->didSetFeaturePolicyHeader(headerValue); + m_webFrame->client()->didSetFeaturePolicyHeader(parsedHeader); } void FrameLoaderClientImpl::didAddContentSecurityPolicy(
diff --git a/third_party/WebKit/Source/web/FrameLoaderClientImpl.h b/third_party/WebKit/Source/web/FrameLoaderClientImpl.h index 3ddbeaaa..2a8ba08 100644 --- a/third_party/WebKit/Source/web/FrameLoaderClientImpl.h +++ b/third_party/WebKit/Source/web/FrameLoaderClientImpl.h
@@ -186,7 +186,8 @@ void didEnforceInsecureRequestPolicy(WebInsecureRequestPolicy) override; void didUpdateToUniqueOrigin() override; void didChangeSandboxFlags(Frame* childFrame, SandboxFlags) override; - void didSetFeaturePolicyHeader(const String& headerValue) override; + void didSetFeaturePolicyHeader( + const WebParsedFeaturePolicy& parsedHeader) override; void didAddContentSecurityPolicy(const String& headerValue, ContentSecurityPolicyHeaderType, ContentSecurityPolicyHeaderSource) override;
diff --git a/third_party/WebKit/Source/web/WebRemoteFrameImpl.cpp b/third_party/WebKit/Source/web/WebRemoteFrameImpl.cpp index 0d8c7ccb..73c09aa 100644 --- a/third_party/WebKit/Source/web/WebRemoteFrameImpl.cpp +++ b/third_party/WebKit/Source/web/WebRemoteFrameImpl.cpp
@@ -433,14 +433,14 @@ } void WebRemoteFrameImpl::setReplicatedFeaturePolicyHeader( - const WebString& headerValue) const { + const WebParsedFeaturePolicy& parsedHeader) const { if (RuntimeEnabledFeatures::featurePolicyEnabled()) { FeaturePolicy* parentFeaturePolicy = nullptr; if (parent()) { Frame* parentFrame = frame()->client()->parent(); parentFeaturePolicy = parentFrame->securityContext()->getFeaturePolicy(); } - frame()->securityContext()->setFeaturePolicyFromHeader(headerValue, + frame()->securityContext()->setFeaturePolicyFromHeader(parsedHeader, parentFeaturePolicy); } }
diff --git a/third_party/WebKit/Source/web/WebRemoteFrameImpl.h b/third_party/WebKit/Source/web/WebRemoteFrameImpl.h index 9489567..4cf55f6 100644 --- a/third_party/WebKit/Source/web/WebRemoteFrameImpl.h +++ b/third_party/WebKit/Source/web/WebRemoteFrameImpl.h
@@ -147,7 +147,8 @@ void setReplicatedSandboxFlags(WebSandboxFlags) const override; void setReplicatedName(const WebString& name, const WebString& uniqueName) const override; - void setReplicatedFeaturePolicyHeader(const WebString&) const override; + void setReplicatedFeaturePolicyHeader( + const WebParsedFeaturePolicy& parsedHeader) const override; void addReplicatedContentSecurityPolicyHeader( const WebString& headerValue, WebContentSecurityPolicyType,
diff --git a/third_party/WebKit/public/platform/WebDisplayItemList.h b/third_party/WebKit/public/platform/WebDisplayItemList.h index 7bce0e8..0a46f5edd 100644 --- a/third_party/WebKit/public/platform/WebDisplayItemList.h +++ b/third_party/WebKit/public/platform/WebDisplayItemList.h
@@ -13,11 +13,12 @@ #include "WebVector.h" #include "third_party/skia/include/core/SkBlendMode.h" +#include "third_party/skia/include/core/SkClipOp.h" #include "third_party/skia/include/core/SkRefCnt.h" -#include "third_party/skia/include/core/SkRegion.h" class SkColorFilter; class SkMatrix44; +class SkPath; class SkPicture; struct SkRect; class SkRRect; @@ -46,8 +47,7 @@ virtual void appendClipItem(const WebRect& clipRect, const WebVector<SkRRect>& roundedClipRects) {} virtual void appendEndClipItem() {} - virtual void appendClipPathItem(const SkPath&, SkRegion::Op, bool antialias) { - } + virtual void appendClipPathItem(const SkPath&, SkClipOp, bool antialias) {} virtual void appendEndClipPathItem() {} virtual void appendFloatClipItem(const WebFloatRect& clipRect) {} virtual void appendEndFloatClipItem() {}
diff --git a/third_party/WebKit/public/platform/WebFeaturePolicy.h b/third_party/WebKit/public/platform/WebFeaturePolicy.h new file mode 100644 index 0000000..a2c03f7 --- /dev/null +++ b/third_party/WebKit/public/platform/WebFeaturePolicy.h
@@ -0,0 +1,27 @@ +// Copyright 2016 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 WebFeaturePolicy_h +#define WebFeaturePolicy_h + +#include "WebSecurityOrigin.h" +#include "WebString.h" +#include "WebVector.h" + +namespace blink { + +struct WebFeaturePolicy { + struct ParsedWhitelist { + ParsedWhitelist() : matchesAllOrigins(false) {} + WebString featureName; + bool matchesAllOrigins; + WebVector<WebSecurityOrigin> origins; + }; +}; + +using WebParsedFeaturePolicy = WebVector<WebFeaturePolicy::ParsedWhitelist>; + +} // namespace blink + +#endif // WebFeaturePolicy_h
diff --git a/third_party/WebKit/public/platform/scheduler/base/task_time_observer.h b/third_party/WebKit/public/platform/scheduler/base/task_time_observer.h index 81714dd..6f8c89c 100644 --- a/third_party/WebKit/public/platform/scheduler/base/task_time_observer.h +++ b/third_party/WebKit/public/platform/scheduler/base/task_time_observer.h
@@ -19,11 +19,16 @@ TaskTimeObserver() {} virtual ~TaskTimeObserver() {} + // Callback to be called when task is about to start. + // |task_queue| - TaskQueue on which this task will run, + // |start_time| - time in seconds when task started to run, + virtual void willProcessTask(TaskQueue* task_queue, double start_time) = 0; + // Callback to be called when task is completed. // |task_queue| - TaskQueue on which this task was run, // |start_time| - time in seconds when task started to run, // |end_time| - time in seconds when task was completed. - virtual void ReportTaskTime(TaskQueue* task_queue, + virtual void didProcessTask(TaskQueue* task_queue, double start_time, double end_time) = 0;
diff --git a/third_party/WebKit/public/web/WebFrameClient.h b/third_party/WebKit/public/web/WebFrameClient.h index 2593df06f..1dc6db0 100644 --- a/third_party/WebKit/public/web/WebFrameClient.h +++ b/third_party/WebKit/public/web/WebFrameClient.h
@@ -50,6 +50,7 @@ #include "public/platform/BlameContext.h" #include "public/platform/WebCommon.h" #include "public/platform/WebEffectiveConnectionType.h" +#include "public/platform/WebFeaturePolicy.h" #include "public/platform/WebFileSystem.h" #include "public/platform/WebFileSystemType.h" #include "public/platform/WebInsecureRequestPolicy.h" @@ -229,7 +230,8 @@ // Called when a Feature-Policy HTTP header is encountered while loading the // frame's document. - virtual void didSetFeaturePolicyHeader(const WebString& headerValue) {} + virtual void didSetFeaturePolicyHeader( + const WebParsedFeaturePolicy& parsedHeader) {} // Called when a new Content Security Policy is added to the frame's // document. This can be triggered by handling of HTTP headers, handling
diff --git a/third_party/WebKit/public/web/WebRemoteFrame.h b/third_party/WebKit/public/web/WebRemoteFrame.h index b8307b9..28f973f 100644 --- a/third_party/WebKit/public/web/WebRemoteFrame.h +++ b/third_party/WebKit/public/web/WebRemoteFrame.h
@@ -5,6 +5,7 @@ #ifndef WebRemoteFrame_h #define WebRemoteFrame_h +#include "public/platform/WebFeaturePolicy.h" #include "public/platform/WebInsecureRequestPolicy.h" #include "public/web/WebContentSecurityPolicy.h" #include "public/web/WebFrame.h" @@ -59,7 +60,7 @@ const WebString& uniqueName) const = 0; virtual void setReplicatedFeaturePolicyHeader( - const WebString& headerValue) const = 0; + const WebParsedFeaturePolicy& parsedHeader) const = 0; // Adds |header| to the set of replicated CSP headers. virtual void addReplicatedContentSecurityPolicyHeader(
diff --git a/tools/determinism/deterministic_build_whitelist.pyl b/tools/determinism/deterministic_build_whitelist.pyl index cccd9f9..3a0c96f1 100644 --- a/tools/determinism/deterministic_build_whitelist.pyl +++ b/tools/determinism/deterministic_build_whitelist.pyl
@@ -110,7 +110,7 @@ 'mac_installer_unittests', 'macviews_interactive_ui_tests', 'media_blink_unittests', - 'media_mojo_shell_unittests', + 'media_service_unittests', 'media_mojo_unittests', 'media_perftests', 'media_pipeline_integration_unittests', @@ -268,7 +268,7 @@ 'mash_init_library.dll', 'media_blink_unittests.exe', 'media_library.dll', - 'media_mojo_shell_unittests.exe', + 'media_service_unittests.exe', 'media_mojo_unittests.exe', 'media_perftests.exe', 'media_pipeline_integration_unittests.exe',
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index 8b5e656..be82630 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml
@@ -95984,6 +95984,7 @@ <int value="8" label="RequestCoordinator timed-out"/> <int value="9" label="Prerendering not started"/> <int value="10" label="Prerendering failed (non-retryable)"/> + <int value="11" label="Prerendering failed (don't start next request)"/> </enum> <enum name="OfflinePagesCctApiPrerenderAllowedStatus" type="int">
diff --git a/ui/aura/mus/window_manager_delegate.h b/ui/aura/mus/window_manager_delegate.h index 4d135397..0dc8d618 100644 --- a/ui/aura/mus/window_manager_delegate.h +++ b/ui/aura/mus/window_manager_delegate.h
@@ -14,7 +14,7 @@ #include "base/callback_forward.h" #include "services/ui/public/interfaces/cursor.mojom.h" -#include "services/ui/public/interfaces/event_matcher.mojom.h" +#include "services/ui/public/interfaces/window_manager.mojom.h" #include "services/ui/public/interfaces/window_manager_constants.mojom.h" #include "services/ui/public/interfaces/window_tree_constants.mojom.h" #include "ui/aura/aura_export.h" @@ -48,9 +48,9 @@ virtual void SetNonClientCursor(Window* window, ui::mojom::Cursor non_client_cursor) = 0; - virtual void AddAccelerator(uint32_t id, - ui::mojom::EventMatcherPtr event_matcher, - const base::Callback<void(bool)>& callback) = 0; + virtual void AddAccelerators( + std::vector<ui::mojom::AcceleratorPtr> accelerators, + const base::Callback<void(bool)>& callback) = 0; virtual void RemoveAccelerator(uint32_t id) = 0; virtual void AddActivationParent(Window* window) = 0; virtual void RemoveActivationParent(Window* window) = 0;
diff --git a/ui/aura/mus/window_tree_client.cc b/ui/aura/mus/window_tree_client.cc index 042d000..d318eab 100644 --- a/ui/aura/mus/window_tree_client.cc +++ b/ui/aura/mus/window_tree_client.cc
@@ -1438,13 +1438,12 @@ WindowMus::Get(window)->server_id(), cursor_id); } -void WindowTreeClient::AddAccelerator( - uint32_t id, - ui::mojom::EventMatcherPtr event_matcher, +void WindowTreeClient::AddAccelerators( + std::vector<ui::mojom::AcceleratorPtr> accelerators, const base::Callback<void(bool)>& callback) { if (window_manager_internal_client_) { - window_manager_internal_client_->AddAccelerators( - ui::CreateAcceleratorVector(id, std::move(event_matcher)), callback); + window_manager_internal_client_->AddAccelerators(std::move(accelerators), + callback); } }
diff --git a/ui/aura/mus/window_tree_client.h b/ui/aura/mus/window_tree_client.h index 6178a69..0945366 100644 --- a/ui/aura/mus/window_tree_client.h +++ b/ui/aura/mus/window_tree_client.h
@@ -398,9 +398,8 @@ void SetFrameDecorationValues( ui::mojom::FrameDecorationValuesPtr values) override; void SetNonClientCursor(Window* window, ui::mojom::Cursor cursor_id) override; - void AddAccelerator(uint32_t id, - ui::mojom::EventMatcherPtr event_matcher, - const base::Callback<void(bool)>& callback) override; + void AddAccelerators(std::vector<ui::mojom::AcceleratorPtr> accelerators, + const base::Callback<void(bool)>& callback) override; void RemoveAccelerator(uint32_t id) override; void AddActivationParent(Window* window) override; void RemoveActivationParent(Window* window) override;
diff --git a/ui/compositor/clip_recorder.cc b/ui/compositor/clip_recorder.cc index 1c0eb55..14650df 100644 --- a/ui/compositor/clip_recorder.cc +++ b/ui/compositor/clip_recorder.cc
@@ -49,14 +49,14 @@ void ClipRecorder::ClipPath(const gfx::Path& clip_path) { bool antialias = false; context_.list_->CreateAndAppendPairedBeginItem<cc::ClipPathDisplayItem>( - clip_path, SkRegion::kIntersect_Op, antialias); + clip_path, kIntersect_SkClipOp, antialias); RecordCloser(CLIP_PATH); } void ClipRecorder::ClipPathWithAntiAliasing(const gfx::Path& clip_path) { bool antialias = true; context_.list_->CreateAndAppendPairedBeginItem<cc::ClipPathDisplayItem>( - clip_path, SkRegion::kIntersect_Op, antialias); + clip_path, kIntersect_SkClipOp, antialias); RecordCloser(CLIP_PATH); }
diff --git a/ui/display/manager/display_manager.cc b/ui/display/manager/display_manager.cc index 9d74e34..6d3e03ab0 100644 --- a/ui/display/manager/display_manager.cc +++ b/ui/display/manager/display_manager.cc
@@ -394,7 +394,8 @@ const gfx::Insets* overscan_insets, const gfx::Size& resolution_in_pixels, float device_scale_factor, - ui::ColorCalibrationProfile color_profile) { + ui::ColorCalibrationProfile color_profile, + const TouchCalibrationData* touch_calibration_data) { if (display_info_.find(display_id) == display_info_.end()) display_info_[display_id] = ManagedDisplayInfo(display_id, std::string(), false); @@ -415,6 +416,8 @@ display_info_[display_id].set_configured_ui_scale(ui_scale); if (overscan_insets) display_info_[display_id].SetOverscanInsets(*overscan_insets); + if (touch_calibration_data) + display_info_[display_id].SetTouchCalibrationData(*touch_calibration_data); if (!resolution_in_pixels.IsEmpty()) { DCHECK(!Display::IsInternalDisplayId(display_id)); // Default refresh rate, until OnNativeDisplaysChanged() updates us with the @@ -984,6 +987,44 @@ bool DisplayManager::SoftwareMirroringEnabled() const { return software_mirroring_enabled(); } + +void DisplayManager::SetTouchCalibrationData( + int64_t display_id, + const TouchCalibrationData::CalibrationPointPairQuad& point_pair_quad, + const gfx::Size& display_bounds) { + bool update = false; + TouchCalibrationData calibration_data(point_pair_quad, display_bounds); + DisplayInfoList display_info_list; + for (const auto& display : active_display_list_) { + display::ManagedDisplayInfo info = GetDisplayInfo(display.id()); + if (info.id() == display_id) { + info.SetTouchCalibrationData(calibration_data); + update = true; + } + display_info_list.push_back(info); + } + if (update) + UpdateDisplaysWith(display_info_list); + else + display_info_[display_id].SetTouchCalibrationData(calibration_data); +} + +void DisplayManager::ClearTouchCalibrationData(int64_t display_id) { + bool update = false; + DisplayInfoList display_info_list; + for (const auto& display : active_display_list_) { + display::ManagedDisplayInfo info = GetDisplayInfo(display.id()); + if (info.id() == display_id) { + info.clear_touch_calibration_data(); + update = true; + } + display_info_list.push_back(info); + } + if (update) + UpdateDisplaysWith(display_info_list); + else + display_info_[display_id].clear_touch_calibration_data(); +} #endif void DisplayManager::SetDefaultMultiDisplayModeForCurrentDisplays(
diff --git a/ui/display/manager/display_manager.h b/ui/display/manager/display_manager.h index 6b58000c1..8437a3f1 100644 --- a/ui/display/manager/display_manager.h +++ b/ui/display/manager/display_manager.h
@@ -164,15 +164,19 @@ bool SetDisplayMode(int64_t display_id, const scoped_refptr<ManagedDisplayMode>& display_mode); - // Register per display properties. |overscan_insets| is null if the display - // has no custom overscan insets. - void RegisterDisplayProperty(int64_t display_id, - Display::Rotation rotation, - float ui_scale, - const gfx::Insets* overscan_insets, - const gfx::Size& resolution_in_pixels, - float device_scale_factor, - ui::ColorCalibrationProfile color_profile); + // Register per display properties. + // |overscan_insets| is null if the display has no custom overscan insets. + // |touch_calibration_data| is null if the display has no touch calibration + // associated data. + void RegisterDisplayProperty( + int64_t display_id, + Display::Rotation rotation, + float ui_scale, + const gfx::Insets* overscan_insets, + const gfx::Size& resolution_in_pixels, + float device_scale_factor, + ui::ColorCalibrationProfile color_profile, + const TouchCalibrationData* touch_calibration_data); // Register stored rotation properties for the internal display. void RegisterDisplayRotationProperties(bool rotation_lock, @@ -295,6 +299,11 @@ #if defined(OS_CHROMEOS) void SetSoftwareMirroring(bool enabled) override; bool SoftwareMirroringEnabled() const override; + void SetTouchCalibrationData( + int64_t display_id, + const TouchCalibrationData::CalibrationPointPairQuad& point_pair_quad, + const gfx::Size& display_bounds); + void ClearTouchCalibrationData(int64_t display_id); #endif // Sets/gets default multi display mode.
diff --git a/ui/display/manager/managed_display_info.cc b/ui/display/manager/managed_display_info.cc index 73b33084..1dd52ba 100644 --- a/ui/display/manager/managed_display_info.cc +++ b/ui/display/manager/managed_display_info.cc
@@ -87,6 +87,19 @@ : point_pairs(calibration_data.point_pairs), bounds(calibration_data.bounds) {} +bool TouchCalibrationData::operator==(TouchCalibrationData other) const { + if (bounds != other.bounds) + return false; + CalibrationPointPairQuad quad_1 = point_pairs; + CalibrationPointPairQuad& quad_2 = other.point_pairs; + + // Make sure the point pairs are in the correct order. + std::sort(quad_1.begin(), quad_1.end(), CalibrationPointPairCompare); + std::sort(quad_2.begin(), quad_2.end(), CalibrationPointPairCompare); + + return quad_1 == quad_2; +} + ManagedDisplayMode::ManagedDisplayMode() : refresh_rate_(0.0f), is_interlaced_(false),
diff --git a/ui/display/manager/managed_display_info.h b/ui/display/manager/managed_display_info.h index 57c259b..ff530b0 100644 --- a/ui/display/manager/managed_display_info.h +++ b/ui/display/manager/managed_display_info.h
@@ -6,7 +6,7 @@ #define UI_DISPLAY_MANAGER_MANAGED_DISPLAY_INFO_H_ #include <stdint.h> - +#include <algorithm> #include <array> #include <map> #include <string> @@ -34,6 +34,15 @@ const gfx::Size& bounds); TouchCalibrationData(const TouchCalibrationData& calibration_data); + static bool CalibrationPointPairCompare(const CalibrationPointPair& pair_1, + const CalibrationPointPair& pair_2) { + return pair_1.first.y() < pair_2.first.y() + ? true + : pair_1.first.x() < pair_2.first.x(); + } + + bool operator==(TouchCalibrationData other) const; + // Calibration point pairs used during calibration. Each point pair contains a // display point and the corresponding touch point. CalibrationPointPairQuad point_pairs;
diff --git a/ui/display/manager/managed_display_info_unittest.cc b/ui/display/manager/managed_display_info_unittest.cc index b27dcc4..069c761 100644 --- a/ui/display/manager/managed_display_info_unittest.cc +++ b/ui/display/manager/managed_display_info_unittest.cc
@@ -168,15 +168,7 @@ info.SetTouchCalibrationData(expected_data); EXPECT_TRUE(info.has_touch_calibration_data()); - TouchCalibrationData actual_data = info.GetTouchCalibrationData(); - EXPECT_EQ(actual_data.bounds, size); - for (size_t i = 0; i < expected_data.point_pairs.size(); i++) { - EXPECT_EQ(actual_data.point_pairs[i].first, - expected_data.point_pairs[i].first); - - EXPECT_EQ(actual_data.point_pairs[i].second, - expected_data.point_pairs[i].second); - } + EXPECT_EQ(expected_data, info.GetTouchCalibrationData()); // Clear all touch calibration data for the display. info.clear_touch_calibration_data();
diff --git a/ui/gfx/canvas.cc b/ui/gfx/canvas.cc index 5dd5774..9dd7aea 100644 --- a/ui/gfx/canvas.cc +++ b/ui/gfx/canvas.cc
@@ -187,16 +187,16 @@ canvas_->restore(); } -void Canvas::ClipRect(const Rect& rect, SkRegion::Op op) { +void Canvas::ClipRect(const Rect& rect, SkClipOp op) { canvas_->clipRect(RectToSkRect(rect), op); } -void Canvas::ClipRect(const RectF& rect, SkRegion::Op op) { +void Canvas::ClipRect(const RectF& rect, SkClipOp op) { canvas_->clipRect(RectFToSkRect(rect), op); } void Canvas::ClipPath(const SkPath& path, bool do_anti_alias) { - canvas_->clipPath(path, SkRegion::kIntersect_Op, do_anti_alias); + canvas_->clipPath(path, kIntersect_SkClipOp, do_anti_alias); } bool Canvas::IsClipEmpty() const {
diff --git a/ui/gfx/canvas.h b/ui/gfx/canvas.h index 66a5598..24d6dbe 100644 --- a/ui/gfx/canvas.h +++ b/ui/gfx/canvas.h
@@ -196,8 +196,8 @@ void Restore(); // Applies |rect| to the current clip using the specified region |op|. - void ClipRect(const Rect& rect, SkRegion::Op op = SkRegion::kIntersect_Op); - void ClipRect(const RectF& rect, SkRegion::Op op = SkRegion::kIntersect_Op); + void ClipRect(const Rect& rect, SkClipOp op = kIntersect_SkClipOp); + void ClipRect(const RectF& rect, SkClipOp op = kIntersect_SkClipOp); // Adds |path| to the current clip. |do_anti_alias| is true if the clip // should be antialiased.
diff --git a/ui/gl/BUILD.gn b/ui/gl/BUILD.gn index e7705f6..5050c891 100644 --- a/ui/gl/BUILD.gn +++ b/ui/gl/BUILD.gn
@@ -154,8 +154,6 @@ if (use_egl) { sources += [ - "angle_platform_impl.cc", - "angle_platform_impl.h", "egl_util.cc", "egl_util.h", "gl_bindings_autogen_egl.cc", @@ -221,6 +219,8 @@ } if (is_win) { sources += [ + "angle_platform_impl.cc", + "angle_platform_impl.h", "gl_bindings_autogen_wgl.cc", "gl_bindings_autogen_wgl.h", "gl_context_wgl.cc",
diff --git a/ui/gl/egl_api_unittest.cc b/ui/gl/egl_api_unittest.cc index 772cf06..b9dbd15a 100644 --- a/ui/gl/egl_api_unittest.cc +++ b/ui/gl/egl_api_unittest.cc
@@ -25,7 +25,6 @@ g_driver_egl.fn.eglGetCurrentDisplayFn = &FakeGetCurrentDisplay; g_driver_egl.fn.eglGetDisplayFn = &FakeGetDisplay; g_driver_egl.fn.eglGetErrorFn = &FakeGetError; - g_driver_egl.fn.eglGetProcAddressFn = &FakeGetProcAddress; } void TearDown() override { @@ -83,11 +82,6 @@ return EGL_SUCCESS; } - static __eglMustCastToProperFunctionPointerType GL_BINDING_CALL - FakeGetProcAddress(const char* procname) { - return nullptr; - } - std::pair<const char*, const char*> GetExtensions() { return std::make_pair( api_->eglQueryStringFn(EGL_NO_DISPLAY, EGL_EXTENSIONS),
diff --git a/ui/gl/gl_surface_egl.cc b/ui/gl/gl_surface_egl.cc index 56032ca9..54ffb7dd 100644 --- a/ui/gl/gl_surface_egl.cc +++ b/ui/gl/gl_surface_egl.cc
@@ -12,7 +12,6 @@ #include <vector> #include "base/command_line.h" -#include "base/lazy_instance.h" #include "base/logging.h" #include "base/macros.h" #include "base/message_loop/message_loop.h" @@ -22,7 +21,6 @@ #include "base/trace_event/trace_event.h" #include "build/build_config.h" #include "ui/gfx/geometry/rect.h" -#include "ui/gl/angle_platform_impl.h" #include "ui/gl/egl_util.h" #include "ui/gl/gl_context.h" #include "ui/gl/gl_context_egl.h" @@ -136,10 +134,6 @@ bool g_egl_surface_orientation_supported = false; bool g_use_direct_composition = false; -base::LazyInstance<ANGLEPlatformImpl> g_angle_platform_impl = - LAZY_INSTANCE_INITIALIZER; -ANGLEPlatformShutdownFunc g_angle_platform_shutdown = nullptr; - EGLDisplay GetPlatformANGLEDisplay(EGLNativeDisplayType native_display, EGLenum platform_type, bool warpDevice) { @@ -539,11 +533,7 @@ } // static -void GLSurfaceEGL::ShutdownOneOff() { - if (g_angle_platform_shutdown) { - g_angle_platform_shutdown(); - } - +void GLSurfaceEGL::ResetForTesting() { if (g_display != EGL_NO_DISPLAY) eglTerminate(g_display); g_display = EGL_NO_DISPLAY; @@ -617,17 +607,6 @@ g_native_display = native_display; - // Init ANGLE platform here, before we call GetPlatformDisplay(). - ANGLEPlatformInitializeFunc angle_platform_init = - reinterpret_cast<ANGLEPlatformInitializeFunc>( - eglGetProcAddress("ANGLEPlatformInitialize")); - if (angle_platform_init) { - angle_platform_init(&g_angle_platform_impl.Get()); - - g_angle_platform_shutdown = reinterpret_cast<ANGLEPlatformShutdownFunc>( - eglGetProcAddress("ANGLEPlatformShutdown")); - } - // If EGL_EXT_client_extensions not supported this call to eglQueryString // will return NULL. const char* client_extensions =
diff --git a/ui/gl/gl_surface_egl.h b/ui/gl/gl_surface_egl.h index dc000eba..1a0e7055 100644 --- a/ui/gl/gl_surface_egl.h +++ b/ui/gl/gl_surface_egl.h
@@ -78,7 +78,7 @@ GLSurface::Format GetFormat() override; static bool InitializeOneOff(EGLNativeDisplayType native_display); - static void ShutdownOneOff(); + static void ResetForTesting(); static EGLDisplay GetHardwareDisplay(); static EGLDisplay InitializeDisplay(EGLNativeDisplayType native_display); static EGLNativeDisplayType GetNativeDisplay();
diff --git a/ui/gl/init/gl_initializer_android.cc b/ui/gl/init/gl_initializer_android.cc index f338cc0..d528ad4 100644 --- a/ui/gl/init/gl_initializer_android.cc +++ b/ui/gl/init/gl_initializer_android.cc
@@ -97,7 +97,6 @@ } void ShutdownGLPlatform() { - GLSurfaceEGL::ShutdownOneOff(); ClearBindingsEGL(); ClearBindingsGL(); ClearBindingsOSMESA();
diff --git a/ui/gl/init/gl_initializer_ozone.cc b/ui/gl/init/gl_initializer_ozone.cc index 213ef8b..624e43b 100644 --- a/ui/gl/init/gl_initializer_ozone.cc +++ b/ui/gl/init/gl_initializer_ozone.cc
@@ -92,7 +92,6 @@ } void ShutdownGLPlatform() { - GLSurfaceEGL::ShutdownOneOff(); if (HasGLOzone()) { GetGLOzone()->ShutdownGL(); return;
diff --git a/ui/gl/init/gl_initializer_win.cc b/ui/gl/init/gl_initializer_win.cc index 89a3586..5b0fc28 100644 --- a/ui/gl/init/gl_initializer_win.cc +++ b/ui/gl/init/gl_initializer_win.cc
@@ -11,6 +11,7 @@ #include "base/bind.h" #include "base/command_line.h" #include "base/files/file_path.h" +#include "base/lazy_instance.h" #include "base/logging.h" #include "base/native_library.h" #include "base/path_service.h" @@ -18,6 +19,8 @@ #include "base/threading/thread_restrictions.h" #include "base/trace_event/trace_event.h" #include "base/win/windows_version.h" +// TODO(jmadill): Apply to all platforms eventually +#include "ui/gl/angle_platform_impl.h" #include "ui/gl/gl_bindings.h" #include "ui/gl/gl_egl_api_implementation.h" #include "ui/gl/gl_features.h" @@ -35,6 +38,12 @@ const wchar_t kD3DCompiler[] = L"D3DCompiler_47.dll"; +// TODO(jmadill): Apply to all platforms eventually +base::LazyInstance<ANGLEPlatformImpl> g_angle_platform_impl = + LAZY_INSTANCE_INITIALIZER; + +ANGLEPlatformShutdownFunc g_angle_platform_shutdown = nullptr; + bool LoadD3DXLibrary(const base::FilePath& module_path, const base::FilePath::StringType& name) { base::NativeLibrary library = @@ -143,6 +152,22 @@ } #endif + if (!using_swift_shader) { + // Init ANGLE platform here, before we call GetPlatformDisplay(). + // TODO(jmadill): Apply to all platforms eventually + ANGLEPlatformInitializeFunc angle_platform_init = + reinterpret_cast<ANGLEPlatformInitializeFunc>( + base::GetFunctionPointerFromNativeLibrary( + gles_library, "ANGLEPlatformInitialize")); + if (angle_platform_init) { + angle_platform_init(&g_angle_platform_impl.Get()); + + g_angle_platform_shutdown = reinterpret_cast<ANGLEPlatformShutdownFunc>( + base::GetFunctionPointerFromNativeLibrary(gles_library, + "ANGLEPlatformShutdown")); + } + } + GLGetProcAddressProc get_proc_address = reinterpret_cast<GLGetProcAddressProc>( base::GetFunctionPointerFromNativeLibrary(egl_library, @@ -295,7 +320,11 @@ } void ShutdownGLPlatform() { - GLSurfaceEGL::ShutdownOneOff(); + // TODO(jmadill): Apply to all platforms eventually + if (g_angle_platform_shutdown) { + g_angle_platform_shutdown(); + } + ClearBindingsEGL(); ClearBindingsGL(); ClearBindingsOSMESA();
diff --git a/ui/gl/init/gl_initializer_x11.cc b/ui/gl/init/gl_initializer_x11.cc index 27fca039..a41e5ce 100644 --- a/ui/gl/init/gl_initializer_x11.cc +++ b/ui/gl/init/gl_initializer_x11.cc
@@ -186,7 +186,6 @@ } void ShutdownGLPlatform() { - GLSurfaceEGL::ShutdownOneOff(); ClearBindingsEGL(); ClearBindingsGL(); ClearBindingsGLX();
diff --git a/ui/ozone/common/gl_ozone_egl.cc b/ui/ozone/common/gl_ozone_egl.cc index 7abf320..d63b305 100644 --- a/ui/ozone/common/gl_ozone_egl.cc +++ b/ui/ozone/common/gl_ozone_egl.cc
@@ -41,7 +41,7 @@ } void GLOzoneEGL::ShutdownGL() { - gl::GLSurfaceEGL::ShutdownOneOff(); + gl::GLSurfaceEGL::ResetForTesting(); gl::ClearBindingsGL(); gl::ClearBindingsEGL(); }
diff --git a/ui/views/animation/ink_drop_painted_layer_delegates.cc b/ui/views/animation/ink_drop_painted_layer_delegates.cc index eec57980..a5de90c6b 100644 --- a/ui/views/animation/ink_drop_painted_layer_delegates.cc +++ b/ui/views/animation/ink_drop_painted_layer_delegates.cc
@@ -167,8 +167,7 @@ // Now the shadow. paint.setLooper(gfx::CreateShadowDrawLooperCorrectBlur(shadows_)); - recorder.canvas()->sk_canvas()->clipRRect(r_rect, SkRegion::kDifference_Op, - true); + recorder.canvas()->sk_canvas()->clipRRect(r_rect, kDifference_SkClipOp, true); recorder.canvas()->sk_canvas()->drawRRect(r_rect, paint); }
diff --git a/ui/views/bubble/bubble_border.cc b/ui/views/bubble/bubble_border.cc index b151db0..8b282fa 100644 --- a/ui/views/bubble/bubble_border.cc +++ b/ui/views/bubble/bubble_border.cc
@@ -358,7 +358,7 @@ // Clip the arrow bounds out to avoid painting the overlapping edge area. canvas->Save(); - canvas->ClipRect(arrow_bounds, SkRegion::kDifference_Op); + canvas->ClipRect(arrow_bounds, kDifference_SkClipOp); Painter::PaintPainterAt(canvas, images_->border_painter.get(), bounds); canvas->Restore(); @@ -538,7 +538,7 @@ paint.setAntiAlias(true); SkRRect r_rect = GetClientRect(view); - canvas->sk_canvas()->clipRRect(r_rect, SkRegion::kDifference_Op, + canvas->sk_canvas()->clipRRect(r_rect, kDifference_SkClipOp, true /*doAntiAlias*/); // The border is drawn outside the content area. @@ -550,7 +550,7 @@ void BubbleBorder::PaintNoAssets(const View& view, gfx::Canvas* canvas) { gfx::ScopedCanvas scoped(canvas); - canvas->sk_canvas()->clipRRect(GetClientRect(view), SkRegion::kDifference_Op, + canvas->sk_canvas()->clipRRect(GetClientRect(view), kDifference_SkClipOp, true /*doAntiAlias*/); canvas->sk_canvas()->drawColor(SK_ColorTRANSPARENT, SkBlendMode::kSrc); }