diff --git a/.gitignore b/.gitignore index 3a385b67..45e8e27 100644 --- a/.gitignore +++ b/.gitignore
@@ -231,7 +231,7 @@ /net/net_unittests_run.xml /net/Release /net/testserver.log -/net/third_party/quiche +/net/third_party/quiche/src /orderfiles /out*/ /ppapi/native_client/nacl_irt.xml
diff --git a/DEPS b/DEPS index de86aab..00884e4 100644 --- a/DEPS +++ b/DEPS
@@ -133,27 +133,27 @@ # 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': '09db9d21756e0bcc37114224c433d01a99ec377e', + 'skia_revision': '7e603db010683f5123f267cefefabc33f33024d4', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling V8 # and whatever else without interference from each other. - 'v8_revision': '8482f4517758bf9c5c6e45e0aca44577defb8fa3', + 'v8_revision': 'ed14aba38a9580529707e04a9b06a74f542e3f90', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling swarming_client # and whatever else without interference from each other. - 'swarming_revision': 'aa60736aded9fc32a0e21a81f5fc51f6009d01f3', + 'swarming_revision': '1b65f4e862045f3ba430a4cbd0643f5b1b66c230', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling ANGLE # and whatever else without interference from each other. - 'angle_revision': 'e421c05c6b8c921dca21a29f92f7255f3140c12b', + 'angle_revision': 'e25ff9d82c06d1fba7c31a4a2c2678cc68e10fd6', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling SwiftShader # and whatever else without interference from each other. - 'swiftshader_revision': '4fe8eb31e4a495b02af24d1b7b38adfd21f0800d', + 'swiftshader_revision': 'dcc9fd7958e79ed3eb1fcc7e7f06a5545988f64e', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling PDFium # and whatever else without interference from each other. - 'pdfium_revision': 'a3097da6c7df969dfc7ee2d93a8072d61486ac34', + 'pdfium_revision': 'b33a01115c28d6ec31c3bbd5df0444f56a0ce824', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling openmax_dl # and whatever else without interference from each other. @@ -184,7 +184,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling NaCl # and whatever else without interference from each other. - 'nacl_revision': '6abc006f6760ec49350cd45e8bccbff4809725ac', + 'nacl_revision': 'ca374f5112fca6b36fc5d73e117a60b82f394993', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling freetype # and whatever else without interference from each other. @@ -276,7 +276,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling feed # and whatever else without interference from each other. - 'quiche_revision': '3f28356e440b0acac3030ad0e686b1d2c8c92542', + 'quiche_revision': '2c2444e99d33294db4e067b4e8d42d123a460ba2', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling ios_webkit # and whatever else without interference from each other. @@ -768,7 +768,7 @@ }, 'src/third_party/breakpad/breakpad': - Var('chromium_git') + '/breakpad/breakpad.git' + '@' + '21b48a72aa50dde84149267f6b7402522b846b24', + Var('chromium_git') + '/breakpad/breakpad.git' + '@' + '1fc9cc0d0e1dfafb8d29dba8d01f09587d870026', 'src/third_party/byte_buddy': { 'packages': [ @@ -806,7 +806,7 @@ # Build tools for Chrome OS. Note: This depends on third_party/pyelftools. 'src/third_party/chromite': { - 'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '14fc5d16ccf425fd6082f4e993cbf2e2f0c72be8', + 'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'ed8036a40a27a1ec37e686b782a0066f69392935', 'condition': 'checkout_linux', }, @@ -831,7 +831,7 @@ }, 'src/third_party/depot_tools': - Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '5b1f4aaf31d8f26b41db73ba1d3cde638649c92c', + Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '5f6b911ad015b2660547353753bcfe50ae932fe2', 'src/third_party/devtools-node-modules': Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'), @@ -900,7 +900,7 @@ }, 'src/third_party/glslang/src': - Var('chromium_git') + '/external/github.com/KhronosGroup/glslang.git' + '@' + '86c72c9486a97da534f97e56b8f7ae06cd1b580b', + Var('chromium_git') + '/external/github.com/KhronosGroup/glslang.git' + '@' + 'c0640dabfddd7d6aa2fbcb58ab669326da8b93f0', 'src/third_party/google_toolbox_for_mac/src': { 'url': Var('chromium_git') + '/external/github.com/google/google-toolbox-for-mac.git' + '@' + Var('google_toolbox_for_mac_revision'), @@ -1075,7 +1075,7 @@ }, 'src/third_party/libvpx/source/libvpx': - Var('chromium_git') + '/webm/libvpx.git' + '@' + 'e50f4e4112a0e031dfc9df7a115fb0f8931bd4e1', + Var('chromium_git') + '/webm/libvpx.git' + '@' + '3fd96f7d7d848ce8fc3ff27cb689207d191996ed', 'src/third_party/libwebm/source': Var('chromium_git') + '/webm/libwebm.git' + '@' + '51ca718c3adf0ddedacd7df25fe45f67dc5a9ce1', @@ -1184,7 +1184,7 @@ }, 'src/third_party/perfetto': - Var('android_git') + '/platform/external/perfetto.git' + '@' + 'ad7d5341ef84b3f0e2204d56482ef6f1cd5de9d6', + Var('android_git') + '/platform/external/perfetto.git' + '@' + 'caf3ef834ff80712738fc18c17c96468a8d89969', 'src/third_party/perl': { 'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + 'ac0d98b5cee6c024b0cffeb4f8f45b6fc5ccdb78', @@ -1396,7 +1396,7 @@ Var('chromium_git') + '/v8/v8.git' + '@' + Var('v8_revision'), 'src-internal': { - 'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@caf7bdf789eb4e1ab8559327ca995aa43b4df9b7', + 'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@1499f5591e06f8ed8e04af29f929d10cb4c4b83b', 'condition': 'checkout_src_internal', },
diff --git a/android_webview/BUILD.gn b/android_webview/BUILD.gn index 9e4e6d5..a35c754 100644 --- a/android_webview/BUILD.gn +++ b/android_webview/BUILD.gn
@@ -5,6 +5,7 @@ import("//android_webview/system_webview_apk_tmpl.gni") import("//android_webview/variables.gni") import("//android_webview/webview_repack_locales.gni") +import("//build/android/resource_sizes.gni") import("//build/config/android/config.gni") import("//build/config/android/rules.gni") import("//build/config/locales.gni") @@ -1111,6 +1112,13 @@ apk_name = "SystemWebView" } + android_resource_sizes_test("resource_sizes_system_webview_apk") { + apk_name = "SystemWebView" + data_deps = [ + ":system_webview_apk", + ] + } + system_webview_apk_tmpl("trichrome_webview_apk") { android_manifest = trichrome_webview_android_manifest android_manifest_dep = ":trichrome_webview_manifest"
diff --git a/ash/DEPS b/ash/DEPS index d5fca440..deae74a 100644 --- a/ash/DEPS +++ b/ash/DEPS
@@ -47,9 +47,6 @@ # header required to launch the app. "-ash/components", - # Ash can talk to public interfaces for mini-apps. - "+ash/components/shortcut_viewer/public", - # Ash sits above content. Exceptions live in //ash/content. "-content",
diff --git a/ash/app_list/app_list_controller_impl.cc b/ash/app_list/app_list_controller_impl.cc index 40fdaba..06fe98c 100644 --- a/ash/app_list/app_list_controller_impl.cc +++ b/ash/app_list/app_list_controller_impl.cc
@@ -47,6 +47,7 @@ #include "ui/base/ui_base_features.h" #include "ui/display/manager/display_manager.h" #include "ui/display/screen.h" +#include "ui/views/controls/textfield/textfield.h" #include "ui/wm/public/activation_client.h" namespace ash { @@ -734,6 +735,26 @@ presenter_.GetView()->Back(); } +void AppListControllerImpl::SetKeyboardTraversalMode(bool engaged) { + if (keyboard_traversal_engaged_ == engaged) + return; + + keyboard_traversal_engaged_ = engaged; + + views::View* focused_view = + presenter_.GetView()->GetFocusManager()->GetFocusedView(); + + if (!focused_view) + return; + + // When the search box has focus, it is actually the textfield that has focus. + // As such, the |SearchBoxView| must be told to repaint directly. + if (focused_view == presenter_.GetView()->search_box_view()->search_box()) + presenter_.GetView()->search_box_view()->SchedulePaint(); + else + focused_view->SchedulePaint(); +} + ash::ShelfAction AppListControllerImpl::OnAppListButtonPressed( int64_t display_id, app_list::AppListShowSource show_source, @@ -951,6 +972,9 @@ UpdateAssistantVisibility(); if (client_) client_->ViewShown(display_id); + + // Ensure search box starts fresh with no ring each time it opens. + keyboard_traversal_engaged_ = false; } void AppListControllerImpl::ViewClosing() { @@ -1044,6 +1068,10 @@ return false; } +bool AppListControllerImpl::KeyboardTraversalEngaged() { + return keyboard_traversal_engaged_; +} + bool AppListControllerImpl::CanProcessEventsOnApplistViews() { // Do not allow processing events during overview or while overview is // finished but still animating out. Note in clamshell mode, if overview and
diff --git a/ash/app_list/app_list_controller_impl.h b/ash/app_list/app_list_controller_impl.h index 02a9ac2..88fc6e6 100644 --- a/ash/app_list/app_list_controller_impl.h +++ b/ash/app_list/app_list_controller_impl.h
@@ -198,6 +198,7 @@ ui::MenuSourceType source_type) override; bool ProcessHomeLauncherGesture(ui::GestureEvent* event, const gfx::Point& screen_location) override; + bool KeyboardTraversalEngaged() override; bool CanProcessEventsOnApplistViews() override; void GetNavigableContentsFactory( mojo::PendingReceiver<content::mojom::NavigableContentsFactory> receiver) @@ -276,6 +277,8 @@ // Performs the 'back' action for the active page. void Back(); + void SetKeyboardTraversalMode(bool engaged); + // Handles app list button press event. (Search key should trigger the same // behavior.) All three parameters are only used in clamshell mode. // |display_id| is the id of display where app list should toggle. @@ -350,9 +353,12 @@ // Bindings for the AppListController interface. mojo::BindingSet<mojom::AppListController> bindings_; - // Whether the on-screen keyboard is shown. + // True if the on-screen keyboard is shown. bool onscreen_keyboard_shown_ = false; + // True if the most recent event handled by |presenter_| was a key event. + bool keyboard_traversal_engaged_ = false; + // True if Shutdown() has been called. bool is_shutdown_ = false;
diff --git a/ash/app_list/app_list_presenter_delegate_impl.cc b/ash/app_list/app_list_presenter_delegate_impl.cc index 67ad066..d0aecd9a 100644 --- a/ash/app_list/app_list_presenter_delegate_impl.cc +++ b/ash/app_list/app_list_presenter_delegate_impl.cc
@@ -5,6 +5,7 @@ #include "ash/app_list/app_list_presenter_delegate_impl.h" #include "ash/app_list/app_list_controller_impl.h" +#include "ash/app_list/app_list_util.h" #include "ash/app_list/presenter/app_list_presenter_impl.h" #include "ash/app_list/views/app_list_main_view.h" #include "ash/app_list/views/app_list_view.h" @@ -29,6 +30,7 @@ #include "ui/aura/window.h" #include "ui/display/manager/display_manager.h" #include "ui/events/event.h" +#include "ui/events/keycodes/keyboard_codes_posix.h" #include "ui/keyboard/keyboard_controller.h" #include "ui/views/widget/widget.h" #include "ui/wm/core/coordinate_conversion.h" @@ -268,11 +270,17 @@ // AppListPresenterDelegateImpl, aura::EventFilter implementation: void AppListPresenterDelegateImpl::OnMouseEvent(ui::MouseEvent* event) { + // Moving the mouse shouldn't hide focus rings. + if (event->IsAnyButton()) + controller_->SetKeyboardTraversalMode(false); + if (event->type() == ui::ET_MOUSE_PRESSED) ProcessLocatedEvent(event); } void AppListPresenterDelegateImpl::OnGestureEvent(ui::GestureEvent* event) { + controller_->SetKeyboardTraversalMode(false); + if (event->type() == ui::ET_GESTURE_TAP || event->type() == ui::ET_GESTURE_TWO_FINGER_TAP || event->type() == ui::ET_GESTURE_LONG_PRESS) { @@ -280,6 +288,21 @@ } } +void AppListPresenterDelegateImpl::OnKeyEvent(ui::KeyEvent* event) { + if (controller_->KeyboardTraversalEngaged()) + return; + + // When in tablet mode, all events hit this function, so we must ensure that + // the home launcher is visible before setting an event handled. + if ((app_list::IsUnhandledArrowKeyEvent(*event) || + event->key_code() == ui::VKEY_TAB) && + (!IsTabletMode() || presenter_->home_launcher_shown())) { + // Handle the first arrow key event to just show the focus rings. + event->SetHandled(); + controller_->SetKeyboardTraversalMode(true); + } +} + void AppListPresenterDelegateImpl::SnapAppListBoundsToDisplayEdge() { CHECK(view_ && view_->GetWidget()); aura::Window* window = view_->GetWidget()->GetNativeView();
diff --git a/ash/app_list/app_list_presenter_delegate_impl.h b/ash/app_list/app_list_presenter_delegate_impl.h index f1acd599..b7e2cc6 100644 --- a/ash/app_list/app_list_presenter_delegate_impl.h +++ b/ash/app_list/app_list_presenter_delegate_impl.h
@@ -74,6 +74,7 @@ // ui::EventHandler overrides: void OnMouseEvent(ui::MouseEvent* event) override; void OnGestureEvent(ui::GestureEvent* event) override; + void OnKeyEvent(ui::KeyEvent* event) override; // Snaps the app list window bounds to fit the screen size. (See // https://crbug.com/884889).
diff --git a/ash/app_list/app_list_view_delegate.h b/ash/app_list/app_list_view_delegate.h index 2a834a7..83adf4c 100644 --- a/ash/app_list/app_list_view_delegate.h +++ b/ash/app_list/app_list_view_delegate.h
@@ -153,6 +153,10 @@ ui::GestureEvent* event, const gfx::Point& screen_location) = 0; + // Returns True if the last event passing through app list was a key event. + // This is stored in the controller and managed by the presenter. + virtual bool KeyboardTraversalEngaged() = 0; + // Checks if we are allowed to process events on the app list main view and // its descendants. virtual bool CanProcessEventsOnApplistViews() = 0;
diff --git a/ash/app_list/test/app_list_test_view_delegate.cc b/ash/app_list/test/app_list_test_view_delegate.cc index 5d29935..09b83e7b 100644 --- a/ash/app_list/test/app_list_test_view_delegate.cc +++ b/ash/app_list/test/app_list_test_view_delegate.cc
@@ -35,6 +35,10 @@ return search_model_.get(); } +bool AppListTestViewDelegate::KeyboardTraversalEngaged() { + return true; +} + void AppListTestViewDelegate::OpenSearchResult( const std::string& result_id, int event_flags,
diff --git a/ash/app_list/test/app_list_test_view_delegate.h b/ash/app_list/test/app_list_test_view_delegate.h index a26cbf7..a2b2788 100644 --- a/ash/app_list/test/app_list_test_view_delegate.h +++ b/ash/app_list/test/app_list_test_view_delegate.h
@@ -58,6 +58,7 @@ // AppListViewDelegate overrides: AppListModel* GetModel() override; SearchModel* GetSearchModel() override; + bool KeyboardTraversalEngaged() override; void StartAssistant() override {} void StartSearch(const base::string16& raw_query) override {} void OpenSearchResult(const std::string& result_id,
diff --git a/ash/app_list/views/app_list_item_view.cc b/ash/app_list/views/app_list_item_view.cc index aacc8d9..f368820 100644 --- a/ash/app_list/views/app_list_item_view.cc +++ b/ash/app_list/views/app_list_item_view.cc
@@ -20,6 +20,7 @@ #include "base/bind.h" #include "base/strings/utf_string_conversions.h" #include "build/build_config.h" +#include "cc/paint/paint_flags.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" #include "ui/compositor/layer.h" @@ -530,14 +531,23 @@ if (apps_grid_view_->IsDraggedView(this)) return; - if (apps_grid_view_->IsSelectedView(this)) { + // TODO(ginko) focus and selection should be unified. + if ((apps_grid_view_->IsSelectedView(this) || HasFocus()) && + (delegate_->KeyboardTraversalEngaged() || + (context_menu_ && context_menu_->IsShowingMenu()))) { cc::PaintFlags flags; flags.setAntiAlias(true); - flags.setColor(apps_grid_view_->is_in_folder() - ? kFolderGridFocusRingColor - : AppListConfig::instance().grid_selected_color()); - flags.setStyle(cc::PaintFlags::kStroke_Style); - flags.setStrokeWidth(kFocusRingWidth); + if (delegate_->KeyboardTraversalEngaged()) { + flags.setColor(apps_grid_view_->is_in_folder() + ? kFolderGridFocusRingColor + : AppListConfig::instance().grid_selected_color()); + flags.setStyle(cc::PaintFlags::kStroke_Style); + flags.setStrokeWidth(kFocusRingWidth); + } else { + // If a context menu is open, we should instead use a grey selection. + flags.setColor(SkColorSetA(gfx::kGoogleGrey100, 31)); + flags.setStyle(cc::PaintFlags::kFill_Style); + } gfx::Rect selection_highlight_bounds = GetContentsBounds(); AdaptBoundsForSelectionHighlight(&selection_highlight_bounds); canvas->DrawRoundRect(gfx::RectF(selection_highlight_bounds), @@ -658,6 +668,7 @@ } void AppListItemView::OnBlur() { + SchedulePaint(); apps_grid_view_->ClearSelectedView(this); }
diff --git a/ash/app_list/views/apps_grid_view.cc b/ash/app_list/views/apps_grid_view.cc index 7812596..ed1b276 100644 --- a/ash/app_list/views/apps_grid_view.cc +++ b/ash/app_list/views/apps_grid_view.cc
@@ -480,7 +480,6 @@ void AppsGridView::ClearSelectedView(AppListItemView* view) { if (view && IsSelectedView(view)) { - selected_view_->SchedulePaint(); selected_view_ = nullptr; } }
diff --git a/ash/app_list/views/search_box_view.cc b/ash/app_list/views/search_box_view.cc index 437c5645..29777f4 100644 --- a/ash/app_list/views/search_box_view.cc +++ b/ash/app_list/views/search_box_view.cc
@@ -193,7 +193,7 @@ void SearchBoxView::OnPaintBackground(gfx::Canvas* canvas) { // Paints the focus ring if the search box is focused. if (search_box()->HasFocus() && !is_search_box_active() && - !is_tablet_mode()) { + view_delegate_->KeyboardTraversalEngaged()) { gfx::Rect bounds = GetContentsBounds(); bounds.Inset(-kSearchBoxFocusRingPadding, -kSearchBoxFocusRingPadding); cc::PaintFlags flags;
diff --git a/ash/app_list/views/search_result_suggestion_chip_view.cc b/ash/app_list/views/search_result_suggestion_chip_view.cc index b4a8ca5..23f81332 100644 --- a/ash/app_list/views/search_result_suggestion_chip_view.cc +++ b/ash/app_list/views/search_result_suggestion_chip_view.cc
@@ -142,6 +142,12 @@ // Background. flags.setColor(kBackgroundColor); canvas->DrawRoundRect(bounds, height() / 2, flags); + + // Focus Ring should only be visible when keyboard traversal is occurring. + if (view_delegate_->KeyboardTraversalEngaged()) + focus_ring()->SetColor(kFocusRingColor); + else + focus_ring()->SetColor(SkColorSetA(kFocusRingColor, 0)); } void SearchResultSuggestionChipView::OnFocus() {
diff --git a/ash/autoclick/autoclick_controller.cc b/ash/autoclick/autoclick_controller.cc index 84373da..d98e8cfe 100644 --- a/ash/autoclick/autoclick_controller.cc +++ b/ash/autoclick/autoclick_controller.cc
@@ -96,7 +96,7 @@ enabled_ = enabled; if (enabled_) { - Shell::Get()->AddPreTargetHandler(this); + Shell::Get()->AddPreTargetHandler(this, ui::EventTarget::Priority::kSystem); // Only create the bubble controller when needed. Most users will not enable // automatic clicks, so there's no need to use these unless the feature @@ -409,6 +409,8 @@ void AutoclickController::OnMouseEvent(ui::MouseEvent* event) { DCHECK(event->target()); + if (event->type() == ui::ET_MOUSE_CAPTURE_CHANGED) + return; gfx::Point point_in_screen = event->target()->GetScreenLocation(*event); if (!(event->flags() & ui::EF_IS_SYNTHESIZED) && (event->type() == ui::ET_MOUSE_MOVED ||
diff --git a/ash/components/shortcut_viewer/BUILD.gn b/ash/components/shortcut_viewer/BUILD.gn index 08e4450..71aadd7 100644 --- a/ash/components/shortcut_viewer/BUILD.gn +++ b/ash/components/shortcut_viewer/BUILD.gn
@@ -10,8 +10,6 @@ "keyboard_shortcut_viewer_metadata.cc", "keyboard_shortcut_viewer_metadata.h", "ksv_export.h", - "shortcut_viewer.cc", - "shortcut_viewer.h", "views/bubble_view.cc", "views/bubble_view.h", "views/keyboard_shortcut_item_list_view.cc",
diff --git a/ash/components/shortcut_viewer/shortcut_viewer.cc b/ash/components/shortcut_viewer/shortcut_viewer.cc deleted file mode 100644 index 2884388..0000000 --- a/ash/components/shortcut_viewer/shortcut_viewer.cc +++ /dev/null
@@ -1,15 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "ash/components/shortcut_viewer/shortcut_viewer.h" - -#include "ash/components/shortcut_viewer/views/keyboard_shortcut_view.h" - -namespace keyboard_shortcut_viewer { - -void Toggle(base::TimeTicks user_gesture_time) { - KeyboardShortcutView::Toggle(user_gesture_time, nullptr); -} - -} // namespace keyboard_shortcut_viewer
diff --git a/ash/components/shortcut_viewer/shortcut_viewer.h b/ash/components/shortcut_viewer/shortcut_viewer.h deleted file mode 100644 index 75c6c8f..0000000 --- a/ash/components/shortcut_viewer/shortcut_viewer.h +++ /dev/null
@@ -1,18 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef ASH_COMPONENTS_SHORTCUT_VIEWER_SHORTCUT_VIEWER_H_ -#define ASH_COMPONENTS_SHORTCUT_VIEWER_SHORTCUT_VIEWER_H_ - -#include "base/time/time.h" - -namespace keyboard_shortcut_viewer { - -// Toggle the Keyboard Shortcut Viewer window. |user_gesture_time| is the time -// of the user gesture that caused the window to show. Used for metrics. -void Toggle(base::TimeTicks user_gesture_time); - -} // namespace keyboard_shortcut_viewer - -#endif // ASH_COMPONENTS_SHORTCUT_VIEWER_SHORTCUT_VIEWER_H_
diff --git a/ash/components/shortcut_viewer/views/keyboard_shortcut_view.cc b/ash/components/shortcut_viewer/views/keyboard_shortcut_view.cc index 2ad49a7..cef4806b 100644 --- a/ash/components/shortcut_viewer/views/keyboard_shortcut_view.cc +++ b/ash/components/shortcut_viewer/views/keyboard_shortcut_view.cc
@@ -138,14 +138,14 @@ } // static -views::Widget* KeyboardShortcutView::Toggle(base::TimeTicks start_time, - aura::Window* context) { +views::Widget* KeyboardShortcutView::Toggle(aura::Window* context) { if (g_ksv_view) { if (g_ksv_view->GetWidget()->IsActive()) g_ksv_view->GetWidget()->Close(); else g_ksv_view->GetWidget()->Activate(); } else { + const base::TimeTicks start_time = base::TimeTicks::Now(); TRACE_EVENT0("shortcut_viewer", "CreateWidget"); base::RecordAction( base::UserMetricsAction("KeyboardShortcutViewer.CreateWindow")); @@ -608,3 +608,11 @@ } } // namespace keyboard_shortcut_viewer + +namespace ash { + +void ToggleKeyboardShortcutViewer() { + keyboard_shortcut_viewer::KeyboardShortcutView::Toggle(nullptr); +} + +} // namespace ash
diff --git a/ash/components/shortcut_viewer/views/keyboard_shortcut_view.h b/ash/components/shortcut_viewer/views/keyboard_shortcut_view.h index 208dc94e..bcd14be 100644 --- a/ash/components/shortcut_viewer/views/keyboard_shortcut_view.h +++ b/ash/components/shortcut_viewer/views/keyboard_shortcut_view.h
@@ -19,10 +19,6 @@ class Window; } -namespace base { -class TimeTicks; -} - namespace views { class TabbedPane; class Widget; @@ -44,14 +40,8 @@ // 1. Show the window if it is not open. // 2. Activate the window if it is open but not active. // 3. Close the window if it is open and active. - // |start_time| is the time of the user gesture that caused the window to - // show. Used for metrics. // |context| is used to determine which display to place the Window on. - // |context| is only necessary when called from within Chrome. - // TODO: remove |context|, it's not needed once KeyboardShortcutView is only - // launched as an app. - static views::Widget* Toggle(base::TimeTicks start_time, - aura::Window* context); + static views::Widget* Toggle(aura::Window* context); // views::View: const char* GetClassName() const override;
diff --git a/ash/components/shortcut_viewer/views/keyboard_shortcut_view_unittest.cc b/ash/components/shortcut_viewer/views/keyboard_shortcut_view_unittest.cc index 1cd4675..3a2b7955 100644 --- a/ash/components/shortcut_viewer/views/keyboard_shortcut_view_unittest.cc +++ b/ash/components/shortcut_viewer/views/keyboard_shortcut_view_unittest.cc
@@ -30,7 +30,7 @@ ~KeyboardShortcutViewTest() override = default; views::Widget* Toggle() { - return KeyboardShortcutView::Toggle(base::TimeTicks(), CurrentContext()); + return KeyboardShortcutView::Toggle(CurrentContext()); } // ash::AshTestBase:
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn index 182a6ae..e1dc915 100644 --- a/ash/public/cpp/BUILD.gn +++ b/ash/public/cpp/BUILD.gn
@@ -76,6 +76,7 @@ "immersive/immersive_fullscreen_controller_delegate.h", "immersive/immersive_revealed_lock.cc", "immersive/immersive_revealed_lock.h", + "keyboard_shortcut_viewer.h", "lock_screen_widget_factory.cc", "lock_screen_widget_factory.h", "login_constants.h",
diff --git a/ash/public/cpp/keyboard_shortcut_viewer.h b/ash/public/cpp/keyboard_shortcut_viewer.h new file mode 100644 index 0000000..e524b82 --- /dev/null +++ b/ash/public/cpp/keyboard_shortcut_viewer.h
@@ -0,0 +1,20 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ASH_PUBLIC_CPP_KEYBOARD_SHORTCUT_VIEWER_H_ +#define ASH_PUBLIC_CPP_KEYBOARD_SHORTCUT_VIEWER_H_ + +#include "ash/public/cpp/ash_public_export.h" + +namespace ash { + +// Toggle the Keyboard Shortcut Viewer window. +// 1. Show the window if it is not open. +// 2. Activate the window if it is open but not active. +// 3. Close the window if it is open and active. +void ASH_PUBLIC_EXPORT ToggleKeyboardShortcutViewer(); + +} // namespace ash + +#endif // ASH_PUBLIC_CPP_KEYBOARD_SHORTCUT_VIEWER_H_
diff --git a/base/BUILD.gn b/base/BUILD.gn index 00f3b10b4..748c652 100644 --- a/base/BUILD.gn +++ b/base/BUILD.gn
@@ -214,13 +214,6 @@ "cpu.h", "critical_closure.h", "critical_closure_internal_ios.mm", - "observer_list_internal.cc", - "observer_list_internal.h", - "observer_list_types.cc", - "observer_list_types.h", - - # This file depends on files from the "debug/allocator" target, - # but this target does not depend on "debug/allocator". "debug/activity_analyzer.cc", "debug/activity_analyzer.h", "debug/activity_tracker.cc", @@ -315,6 +308,8 @@ "hash/hash.h", "hash/md5.cc", "hash/md5.h", + "hash/sha1.cc", + "hash/sha1.h", "ios/block_types.h", "ios/crb_protocol_observers.h", "ios/crb_protocol_observers.mm", @@ -547,8 +542,12 @@ "native_library_win.cc", "no_destructor.h", "observer_list.h", + "observer_list_internal.cc", + "observer_list_internal.h", "observer_list_threadsafe.cc", "observer_list_threadsafe.h", + "observer_list_types.cc", + "observer_list_types.h", "one_shot_event.cc", "one_shot_event.h", "optional.h", @@ -594,37 +593,23 @@ "process/process.h", "process/process_handle.cc", "process/process_handle.h", - - #"process/process_handle_freebsd.cc", # Unused in Chromium build. "process/process_handle_linux.cc", "process/process_handle_mac.cc", - - #"process/process_handle_openbsd.cc", # Unused in Chromium build. "process/process_handle_win.cc", "process/process_info.h", "process/process_info_win.cc", "process/process_iterator.cc", "process/process_iterator.h", - - #"process/process_iterator_freebsd.cc", # Unused in Chromium build. "process/process_iterator_linux.cc", "process/process_iterator_mac.cc", - - #"process/process_iterator_openbsd.cc", # Unused in Chromium build. "process/process_iterator_win.cc", "process/process_linux.cc", "process/process_mac.cc", "process/process_metrics.cc", "process/process_metrics.h", - - #"process/process_metrics_freebsd.cc", # Unused in Chromium build. "process/process_metrics_ios.cc", "process/process_metrics_linux.cc", "process/process_metrics_mac.cc", - - #"process/process_metrics_openbsd.cc", # Unused in Chromium build. - "hash/sha1.cc", - "hash/sha1.h", "process/process_metrics_win.cc", "process/process_win.cc", "profiler/frame.cc", @@ -743,14 +728,11 @@ "synchronization/waitable_event_watcher_win.cc", "synchronization/waitable_event_win.cc", "sys_byteorder.h", + "syslog_logging.cc", + "syslog_logging.h", "system/sys_info.cc", "system/sys_info.h", "system/sys_info_internal.h", - - #"system/sys_info_freebsd.cc", # Unused in Chromium build. - #"system/sys_info_openbsd.cc", # Unused in Chromium build. - "syslog_logging.cc", - "syslog_logging.h", "system/system_monitor.cc", "system/system_monitor.h", "task/cancelable_task_tracker.cc", @@ -1127,6 +1109,20 @@ "win/wrapped_window_proc.h", ] + # Various files that are unused in the Chromium build, but presumably here to + # make downstream's life easier. They are not included in the main sources + # list to avoid breaking GN formatting's auto-sorting. + sources += [ + #"process/process_handle_freebsd.cc", + #"process/process_iterator_freebsd.cc", + #"process/process_metrics_freebsd.cc", + #"system/sys_info_freebsd.cc", + #"process/process_iterator_openbsd.cc", + #"process/process_handle_openbsd.cc", + #"process/process_metrics_openbsd.cc", + #"system/sys_info_openbsd.cc", + ] + if (is_posix) { sources += [ "debug/debugger_posix.cc",
diff --git a/base/allocator/partition_allocator/partition_alloc.cc b/base/allocator/partition_allocator/partition_alloc.cc index f2d8d28..80b97a63 100644 --- a/base/allocator/partition_allocator/partition_alloc.cc +++ b/base/allocator/partition_allocator/partition_alloc.cc
@@ -64,11 +64,110 @@ static bool g_initialized = false; void (*internal::PartitionRootBase::gOomHandlingFunction)() = nullptr; +std::atomic<bool> PartitionAllocHooks::hooks_enabled_(false); subtle::SpinLock PartitionAllocHooks::set_hooks_lock_; std::atomic<PartitionAllocHooks::AllocationObserverHook*> PartitionAllocHooks::allocation_observer_hook_(nullptr); std::atomic<PartitionAllocHooks::FreeObserverHook*> PartitionAllocHooks::free_observer_hook_(nullptr); +std::atomic<PartitionAllocHooks::AllocationOverrideHook*> + PartitionAllocHooks::allocation_override_hook_(nullptr); +std::atomic<PartitionAllocHooks::FreeOverrideHook*> + PartitionAllocHooks::free_override_hook_(nullptr); +std::atomic<PartitionAllocHooks::ReallocOverrideHook*> + PartitionAllocHooks::realloc_override_hook_(nullptr); + +void PartitionAllocHooks::SetObserverHooks(AllocationObserverHook* alloc_hook, + FreeObserverHook* free_hook) { + subtle::SpinLock::Guard guard(set_hooks_lock_); + + // Chained hooks are not supported. Registering a non-null hook when a + // non-null hook is already registered indicates somebody is trying to + // overwrite a hook. + CHECK((!allocation_observer_hook_ && !free_observer_hook_) || + (!alloc_hook && !free_hook)) + << "Overwriting already set observer hooks"; + allocation_observer_hook_ = alloc_hook; + free_observer_hook_ = free_hook; + + hooks_enabled_ = allocation_observer_hook_ || allocation_override_hook_; +} + +void PartitionAllocHooks::SetOverrideHooks(AllocationOverrideHook* alloc_hook, + FreeOverrideHook* free_hook, + ReallocOverrideHook realloc_hook) { + subtle::SpinLock::Guard guard(set_hooks_lock_); + + CHECK((!allocation_override_hook_ && !free_override_hook_ && + !realloc_override_hook_) || + (!alloc_hook && !free_hook && !realloc_hook)) + << "Overwriting already set override hooks"; + allocation_override_hook_ = alloc_hook; + free_override_hook_ = free_hook; + realloc_override_hook_ = realloc_hook; + + hooks_enabled_ = allocation_observer_hook_ || allocation_override_hook_; +} + +void PartitionAllocHooks::AllocationObserverHookIfEnabled( + void* address, + size_t size, + const char* type_name) { + if (AllocationObserverHook* hook = + allocation_observer_hook_.load(std::memory_order_relaxed)) { + hook(address, size, type_name); + } +} + +bool PartitionAllocHooks::AllocationOverrideHookIfEnabled( + void** out, + int flags, + size_t size, + const char* type_name) { + if (AllocationOverrideHook* hook = + allocation_override_hook_.load(std::memory_order_relaxed)) { + return hook(out, flags, size, type_name); + } + return false; +} + +void PartitionAllocHooks::FreeObserverHookIfEnabled(void* address) { + if (FreeObserverHook* hook = + free_observer_hook_.load(std::memory_order_relaxed)) { + hook(address); + } +} + +bool PartitionAllocHooks::FreeOverrideHookIfEnabled(void* address) { + if (FreeOverrideHook* hook = + free_override_hook_.load(std::memory_order_relaxed)) { + return hook(address); + } + return false; +} + +void PartitionAllocHooks::ReallocObserverHookIfEnabled(void* old_address, + void* new_address, + size_t size, + const char* type_name) { + // Report a reallocation as a free followed by an allocation. + AllocationObserverHook* allocation_hook = + allocation_observer_hook_.load(std::memory_order_relaxed); + FreeObserverHook* free_hook = + free_observer_hook_.load(std::memory_order_relaxed); + if (allocation_hook && free_hook) { + free_hook(old_address); + allocation_hook(new_address, size, type_name); + } +} +bool PartitionAllocHooks::ReallocOverrideHookIfEnabled(size_t* out, + void* address) { + if (ReallocOverrideHook* hook = + realloc_override_hook_.load(std::memory_order_relaxed)) { + return hook(out, address); + } + return false; +} static void PartitionAllocBaseInit(internal::PartitionRootBase* root) { DCHECK(!root->initialized); @@ -282,42 +381,53 @@ internal::PartitionExcessiveAllocationSize(); } - internal::PartitionPage* page = internal::PartitionPage::FromPointer( - internal::PartitionCookieFreePointerAdjust(ptr)); - // TODO(palmer): See if we can afford to make this a CHECK. - DCHECK(root->IsValidPage(page)); + const bool hooks_enabled = PartitionAllocHooks::AreHooksEnabled(); + bool overridden = false; + size_t actual_old_size; + if (UNLIKELY(hooks_enabled)) { + overridden = PartitionAllocHooks::ReallocOverrideHookIfEnabled( + &actual_old_size, ptr); + } + if (LIKELY(!overridden)) { + internal::PartitionPage* page = internal::PartitionPage::FromPointer( + internal::PartitionCookieFreePointerAdjust(ptr)); + // TODO(palmer): See if we can afford to make this a CHECK. + DCHECK(root->IsValidPage(page)); - if (UNLIKELY(page->bucket->is_direct_mapped())) { - // We may be able to perform the realloc in place by changing the - // accessibility of memory pages and, if reducing the size, decommitting - // them. - if (PartitionReallocDirectMappedInPlace(root, page, new_size)) { - PartitionAllocHooks::ReallocObserverHookIfEnabled(ptr, ptr, new_size, - type_name); + if (UNLIKELY(page->bucket->is_direct_mapped())) { + // We may be able to perform the realloc in place by changing the + // accessibility of memory pages and, if reducing the size, decommitting + // them. + if (PartitionReallocDirectMappedInPlace(root, page, new_size)) { + if (UNLIKELY(hooks_enabled)) { + PartitionAllocHooks::ReallocObserverHookIfEnabled(ptr, ptr, new_size, + type_name); + } + return ptr; + } + } + + const size_t actual_new_size = root->ActualSize(new_size); + actual_old_size = PartitionAllocGetSize(ptr); + + // TODO: note that tcmalloc will "ignore" a downsizing realloc() unless the + // new size is a significant percentage smaller. We could do the same if we + // determine it is a win. + if (actual_new_size == actual_old_size) { + // Trying to allocate a block of size |new_size| would give us a block of + // the same size as the one we've already got, so re-use the allocation + // after updating statistics (and cookies, if present). + page->set_raw_size(internal::PartitionCookieSizeAdjustAdd(new_size)); +#if DCHECK_IS_ON() + // Write a new trailing cookie when it is possible to keep track of + // |new_size| via the raw size pointer. + if (page->get_raw_size_ptr()) + internal::PartitionCookieWriteValue(static_cast<char*>(ptr) + new_size); +#endif return ptr; } } - size_t actual_new_size = root->ActualSize(new_size); - size_t actual_old_size = PartitionAllocGetSize(ptr); - - // TODO: note that tcmalloc will "ignore" a downsizing realloc() unless the - // new size is a significant percentage smaller. We could do the same if we - // determine it is a win. - if (actual_new_size == actual_old_size) { - // Trying to allocate a block of size |new_size| would give us a block of - // the same size as the one we've already got, so re-use the allocation - // after updating statistics (and cookies, if present). - page->set_raw_size(internal::PartitionCookieSizeAdjustAdd(new_size)); -#if DCHECK_IS_ON() - // Write a new trailing cookie when it is possible to keep track of - // |new_size| via the raw size pointer. - if (page->get_raw_size_ptr()) - internal::PartitionCookieWriteValue(static_cast<char*>(ptr) + new_size); -#endif - return ptr; - } - // This realloc cannot be resized in-place. Sadness. void* ret = PartitionAllocGenericFlags(root, flags, new_size, type_name); if (!ret) {
diff --git a/base/allocator/partition_allocator/partition_alloc.h b/base/allocator/partition_allocator/partition_alloc.h index ab7b185b..43e4ca2 100644 --- a/base/allocator/partition_allocator/partition_alloc.h +++ b/base/allocator/partition_allocator/partition_alloc.h
@@ -211,6 +211,8 @@ BASE_EXPORT void PartitionAllocGlobalInit(void (*oom_handling_function)()); +// PartitionAlloc supports setting hooks to observe allocations/frees as they +// occur as well as 'override' hooks that allow overriding those operations. class BASE_EXPORT PartitionAllocHooks { public: // Log allocation and free events. @@ -219,56 +221,64 @@ const char* type_name); typedef void FreeObserverHook(void* address); - // To unhook, call SetObserverHooks with nullptrs. - static void SetObserverHooks(AllocationObserverHook* alloc_hook, - FreeObserverHook* free_hook) { - subtle::SpinLock::Guard guard(set_hooks_lock_); + // If it returns true, the allocation has been overridden with the pointer in + // *out. + typedef bool AllocationOverrideHook(void** out, + int flags, + size_t size, + const char* type_name); + // If it returns true, then the allocation was overridden and has been freed. + typedef bool FreeOverrideHook(void* address); + // If it returns true, the underlying allocation is overridden and *out holds + // the size of the underlying allocation. + typedef bool ReallocOverrideHook(size_t* out, void* address); - // Chained hooks are not supported. Registering a non-null hook when a - // non-null hook is already registered indicates somebody is trying to - // overwrite a hook. - CHECK((!allocation_observer_hook_ && !free_observer_hook_) || - (!alloc_hook && !free_hook)) - << "Overwriting already set observer hooks"; - allocation_observer_hook_ = alloc_hook; - free_observer_hook_ = free_hook; + // To unhook, call Set*Hooks with nullptrs. + static void SetObserverHooks(AllocationObserverHook* alloc_hook, + FreeObserverHook* free_hook); + static void SetOverrideHooks(AllocationOverrideHook* alloc_hook, + FreeOverrideHook* free_hook, + ReallocOverrideHook realloc_hook); + + // Helper method to check whether hooks are enabled. This is an optimization + // so that if a function needs to call observer and override hooks in two + // different places this value can be cached and only loaded once. + static bool AreHooksEnabled() { + return hooks_enabled_.load(std::memory_order_relaxed); } static void AllocationObserverHookIfEnabled(void* address, size_t size, - const char* type_name) { - if (AllocationObserverHook* hook = - allocation_observer_hook_.load(std::memory_order_relaxed)) - hook(address, size, type_name); - } + const char* type_name); + static bool AllocationOverrideHookIfEnabled(void** out, + int flags, + size_t size, + const char* type_name); - static void FreeObserverHookIfEnabled(void* address) { - if (FreeObserverHook* hook = - free_observer_hook_.load(std::memory_order_relaxed)) - hook(address); - } + static void FreeObserverHookIfEnabled(void* address); + static bool FreeOverrideHookIfEnabled(void* address); static void ReallocObserverHookIfEnabled(void* old_address, void* new_address, size_t size, - const char* type_name) { - // Report a reallocation as a free followed by an allocation. - AllocationObserverHook* allocation_hook = - allocation_observer_hook_.load(std::memory_order_relaxed); - FreeObserverHook* free_hook = - free_observer_hook_.load(std::memory_order_relaxed); - if (allocation_hook && free_hook) { - free_hook(old_address); - allocation_hook(new_address, size, type_name); - } - } + const char* type_name); + static bool ReallocOverrideHookIfEnabled(size_t* out, void* address); private: - // Lock used to synchronize SetObserverHooks calls. + // Single bool that is used to indicate whether observer or allocation hooks + // are set to reduce the numbers of loads required to check whether hooking is + // enabled. + static std::atomic<bool> hooks_enabled_; + + // Lock used to synchronize Set*Hooks calls. static subtle::SpinLock set_hooks_lock_; static std::atomic<AllocationObserverHook*> allocation_observer_hook_; static std::atomic<FreeObserverHook*> free_observer_hook_; + + static std::atomic<AllocationOverrideHook*> allocation_override_hook_; + static std::atomic<FreeOverrideHook*> free_override_hook_; + static std::atomic<ReallocOverrideHook*> realloc_override_hook_; }; ALWAYS_INLINE void* PartitionRoot::Alloc(size_t size, const char* type_name) { @@ -287,6 +297,16 @@ CHECK(result); return result; #else + void* result; + const bool hooks_enabled = PartitionAllocHooks::AreHooksEnabled(); + if (UNLIKELY(hooks_enabled)) { + if (PartitionAllocHooks::AllocationOverrideHookIfEnabled(&result, flags, + size, type_name)) { + PartitionAllocHooks::AllocationObserverHookIfEnabled(result, size, + type_name); + return result; + } + } size_t requested_size = size; size = internal::PartitionCookieSizeAdjustAdd(size); DCHECK(this->initialized); @@ -294,9 +314,11 @@ DCHECK(index < this->num_buckets); DCHECK(size == index << kBucketShift); internal::PartitionBucket* bucket = &this->buckets()[index]; - void* result = AllocFromBucket(bucket, flags, size); - PartitionAllocHooks::AllocationObserverHookIfEnabled(result, requested_size, - type_name); + result = AllocFromBucket(bucket, flags, size); + if (UNLIKELY(hooks_enabled)) { + PartitionAllocHooks::AllocationObserverHookIfEnabled(result, requested_size, + type_name); + } return result; #endif // defined(MEMORY_TOOL_REPLACES_ALLOCATOR) } @@ -325,10 +347,14 @@ #if defined(MEMORY_TOOL_REPLACES_ALLOCATOR) free(ptr); #else - void* original_ptr = ptr; // TODO(palmer): Check ptr alignment before continuing. Shall we do the check // inside PartitionCookieFreePointerAdjust? - PartitionAllocHooks::FreeObserverHookIfEnabled(original_ptr); + if (PartitionAllocHooks::AreHooksEnabled()) { + PartitionAllocHooks::FreeObserverHookIfEnabled(ptr); + if (PartitionAllocHooks::FreeOverrideHookIfEnabled(ptr)) + return; + } + ptr = internal::PartitionCookieFreePointerAdjust(ptr); internal::PartitionPage* page = internal::PartitionPage::FromPointer(ptr); // TODO(palmer): See if we can afford to make this a CHECK. @@ -372,18 +398,29 @@ return result; #else DCHECK(root->initialized); + void* result; + const bool hooks_enabled = PartitionAllocHooks::AreHooksEnabled(); + if (UNLIKELY(hooks_enabled)) { + if (PartitionAllocHooks::AllocationOverrideHookIfEnabled(&result, flags, + size, type_name)) { + PartitionAllocHooks::AllocationObserverHookIfEnabled(result, size, + type_name); + return result; + } + } size_t requested_size = size; size = internal::PartitionCookieSizeAdjustAdd(size); internal::PartitionBucket* bucket = PartitionGenericSizeToBucket(root, size); - void* ret = nullptr; { subtle::SpinLock::Guard guard(root->lock); - ret = root->AllocFromBucket(bucket, flags, size); + result = root->AllocFromBucket(bucket, flags, size); } - PartitionAllocHooks::AllocationObserverHookIfEnabled(ret, requested_size, - type_name); + if (UNLIKELY(hooks_enabled)) { + PartitionAllocHooks::AllocationObserverHookIfEnabled(result, requested_size, + type_name); + } - return ret; + return result; #endif } @@ -407,7 +444,12 @@ if (UNLIKELY(!ptr)) return; - PartitionAllocHooks::FreeObserverHookIfEnabled(ptr); + if (PartitionAllocHooks::AreHooksEnabled()) { + PartitionAllocHooks::FreeObserverHookIfEnabled(ptr); + if (PartitionAllocHooks::FreeOverrideHookIfEnabled(ptr)) + return; + } + ptr = internal::PartitionCookieFreePointerAdjust(ptr); internal::PartitionPage* page = internal::PartitionPage::FromPointer(ptr); // TODO(palmer): See if we can afford to make this a CHECK.
diff --git a/base/allocator/partition_allocator/partition_alloc_unittest.cc b/base/allocator/partition_allocator/partition_alloc_unittest.cc index 2d402355..5ebe066 100644 --- a/base/allocator/partition_allocator/partition_alloc_unittest.cc +++ b/base/allocator/partition_allocator/partition_alloc_unittest.cc
@@ -2226,6 +2226,63 @@ PartitionFree(ptr); } +TEST_F(PartitionAllocTest, OverrideHooks) { + constexpr size_t kOverriddenSize = 1234; + constexpr const char* kOverriddenType = "Overridden type"; + constexpr unsigned char kOverriddenChar = 'A'; + + // Marked static so that we can use them in non-capturing lambdas below. + // (Non-capturing lambdas convert directly to function pointers.) + static volatile bool free_called = false; + static void* overridden_allocation = malloc(kOverriddenSize); + memset(overridden_allocation, kOverriddenChar, kOverriddenSize); + + PartitionAllocHooks::SetOverrideHooks( + [](void** out, int flags, size_t size, const char* type_name) -> bool { + if (size == kOverriddenSize && type_name == kOverriddenType) { + *out = overridden_allocation; + return true; + } + return false; + }, + [](void* address) -> bool { + if (address == overridden_allocation) { + free_called = true; + return true; + } + return false; + }, + [](size_t* out, void* address) -> bool { + if (address == overridden_allocation) { + *out = kOverriddenSize; + return true; + } + return false; + }); + + void* ptr = PartitionAllocGenericFlags(generic_allocator.root(), + PartitionAllocReturnNull, + kOverriddenSize, kOverriddenType); + ASSERT_EQ(ptr, overridden_allocation); + + PartitionFree(ptr); + EXPECT_TRUE(free_called); + + // overridden_allocation has not actually been freed so we can now immediately + // realloc it. + free_called = false; + ptr = PartitionReallocGenericFlags(generic_allocator.root(), + PartitionAllocReturnNull, ptr, 1, nullptr); + ASSERT_NE(ptr, nullptr); + EXPECT_NE(ptr, overridden_allocation); + EXPECT_TRUE(free_called); + EXPECT_EQ(*(char*)ptr, kOverriddenChar); + PartitionFree(ptr); + + PartitionAllocHooks::SetOverrideHooks(nullptr, nullptr, nullptr); + free(overridden_allocation); +} + } // namespace internal } // namespace base
diff --git a/base/command_line.cc b/base/command_line.cc index 0bd071bb..3f13db20 100644 --- a/base/command_line.cc +++ b/base/command_line.cc
@@ -438,7 +438,23 @@ int num_args = 0; wchar_t** args = NULL; - args = ::CommandLineToArgvW(as_wcstr(command_line), &num_args); + // When calling CommandLineToArgvW, use the apiset if available. + // Doing so will bypass loading shell32.dll on Win8+. + HMODULE downlevel_shell32_dll = + ::LoadLibraryEx(L"api-ms-win-downlevel-shell32-l1-1-0.dll", nullptr, + LOAD_LIBRARY_SEARCH_SYSTEM32); + if (downlevel_shell32_dll) { + auto command_line_to_argv_w_proc = + reinterpret_cast<decltype(::CommandLineToArgvW)*>( + ::GetProcAddress(downlevel_shell32_dll, "CommandLineToArgvW")); + if (command_line_to_argv_w_proc) + args = command_line_to_argv_w_proc(as_wcstr(command_line), &num_args); + ::FreeLibrary(downlevel_shell32_dll); + } else { + // Since the apiset is not available, allow the delayload of shell32.dll + // to take place. + args = ::CommandLineToArgvW(as_wcstr(command_line), &num_args); + } DPLOG_IF(FATAL, !args) << "CommandLineToArgvW failed on command line: " << UTF16ToUTF8(command_line);
diff --git a/base/files/file_path.cc b/base/files/file_path.cc index bfc1122..45f78a2 100644 --- a/base/files/file_path.cc +++ b/base/files/file_path.cc
@@ -23,6 +23,7 @@ #if defined(OS_WIN) #include <windows.h> +#include "base/win/win_util.h" #elif defined(OS_MACOSX) #include <CoreFoundation/CoreFoundation.h> #endif @@ -707,10 +708,11 @@ int FilePath::CompareIgnoreCase(StringPieceType string1, StringPieceType string2) { - static decltype(::CharUpperW)* const char_upper_api = - reinterpret_cast<decltype(::CharUpperW)*>( - ::GetProcAddress(::GetModuleHandle(L"user32.dll"), "CharUpperW")); - CHECK(char_upper_api); + // CharUpperW within user32 is used here because it will provide unicode + // conversions regardless of locale. The STL alternative, towupper, has a + // locale consideration that prevents it from converting all characters by + // default. + CHECK(win::IsUser32AndGdi32Available()); // Perform character-wise upper case comparison rather than using the // fully Unicode-aware CompareString(). For details see: // http://blogs.msdn.com/michkap/archive/2005/10/17/481600.aspx @@ -720,9 +722,9 @@ StringPieceType::const_iterator string2end = string2.end(); for ( ; i1 != string1end && i2 != string2end; ++i1, ++i2) { wchar_t c1 = - (wchar_t)LOWORD(char_upper_api((LPWSTR)(DWORD_PTR)MAKELONG(*i1, 0))); + (wchar_t)LOWORD(::CharUpperW((LPWSTR)(DWORD_PTR)MAKELONG(*i1, 0))); wchar_t c2 = - (wchar_t)LOWORD(char_upper_api((LPWSTR)(DWORD_PTR)MAKELONG(*i2, 0))); + (wchar_t)LOWORD(::CharUpperW((LPWSTR)(DWORD_PTR)MAKELONG(*i2, 0))); if (c1 < c2) return -1; if (c1 > c2)
diff --git a/base/logging.cc b/base/logging.cc index 79afe25b..bd9e2d8 100644 --- a/base/logging.cc +++ b/base/logging.cc
@@ -107,6 +107,10 @@ #include "base/threading/platform_thread.h" #include "base/vlog.h" +#if defined(OS_WIN) +#include "base/win/win_util.h" +#endif + #if defined(OS_POSIX) || defined(OS_FUCHSIA) #include "base/posix/safe_strerror.h" #endif @@ -550,8 +554,12 @@ #if defined(OS_WIN) // We intentionally don't implement a dialog on other platforms. // You can just look at stderr. - MessageBoxW(nullptr, base::UTF8ToUTF16(str).c_str(), L"Fatal error", - MB_OK | MB_ICONHAND | MB_TOPMOST); + if (base::win::IsUser32AndGdi32Available()) { + MessageBoxW(nullptr, base::as_wcstr(base::UTF8ToUTF16(str)), L"Fatal error", + MB_OK | MB_ICONHAND | MB_TOPMOST); + } else { + OutputDebugStringW(base::as_wcstr(base::UTF8ToUTF16(str))); + } #endif // defined(OS_WIN) } #endif // !defined(NDEBUG)
diff --git a/base/metrics/ukm_source_id.h b/base/metrics/ukm_source_id.h index fd3ad2f..6b259c7 100644 --- a/base/metrics/ukm_source_id.h +++ b/base/metrics/ukm_source_id.h
@@ -20,6 +20,9 @@ UKM = 0, NAVIGATION_ID = 1, APP_ID = 2, + // Source ID for background events that don't have an open tab but the + // associated URL is still present in the browser's history. + HISTORY_ID = 3, }; // Default constructor has the invalid value.
diff --git a/base/native_library.h b/base/native_library.h index 4a65ffe..ec47c14 100644 --- a/base/native_library.h +++ b/base/native_library.h
@@ -85,10 +85,20 @@ #if defined(OS_WIN) // Loads a native library from the system directory using the appropriate flags. // The function first checks to see if the library is already loaded and will -// get a handle if so. Blocking may occur if the library is not loaded and -// LoadLibrary must be called. -BASE_EXPORT NativeLibrary LoadSystemLibrary(FilePath::StringPieceType name, - NativeLibraryLoadError* error); +// get a handle if so. This method results in a lock that may block the calling +// thread. +BASE_EXPORT NativeLibrary +LoadSystemLibrary(FilePath::StringPieceType name, + NativeLibraryLoadError* error = nullptr); + +// Gets the module handle for the specified system library and pins it to +// ensure it never gets unloaded. If the module is not loaded, it will first +// call LoadSystemLibrary to load it. If the module cannot be pinned, this +// method returns null and includes the error. This method results in a lock +// that may block the calling thread. +BASE_EXPORT NativeLibrary +PinSystemLibrary(FilePath::StringPieceType name, + NativeLibraryLoadError* error = nullptr); #endif // Loads a native library from disk. Release it with UnloadNativeLibrary when
diff --git a/base/native_library_win.cc b/base/native_library_win.cc index 47814eeed..08d520d0 100644 --- a/base/native_library_win.cc +++ b/base/native_library_win.cc
@@ -112,7 +112,7 @@ // GetLastError() needs to be called immediately after // LoadLibraryExW call. if (error) - error->code = GetLastError(); + error->code = ::GetLastError(); } // If LoadLibraryExW API/flags are unavailable or API call fails, try @@ -133,7 +133,7 @@ // GetLastError() needs to be called immediately after LoadLibraryW call. if (!module && error) - error->code = GetLastError(); + error->code = ::GetLastError(); if (restore_directory) SetCurrentDirectory(current_directory); @@ -147,15 +147,15 @@ NativeLibrary LoadSystemLibraryHelper(const FilePath& library_path, NativeLibraryLoadError* error) { + // GetModuleHandleEx and subsequently LoadLibraryEx acquire the LoaderLock, + // hence must not be called from Dllmain. + ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK); NativeLibrary module; BOOL module_found = - ::GetModuleHandleEx(0, as_wcstr(library_path.value()), &module); + ::GetModuleHandleExW(0, as_wcstr(library_path.value()), &module); if (!module_found) { - // LoadLibrary() opens the file off disk and acquires the LoaderLock, hence - // must not be called from DllMain. - ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK); bool are_search_flags_available = AreSearchFlagsAvailable(); - // prefer LOAD_LIBRARY_SEARCH_SYSTEM32 to avoid DLL preloading attacks + // Prefer LOAD_LIBRARY_SEARCH_SYSTEM32 to avoid DLL preloading attacks. DWORD flags = are_search_flags_available ? LOAD_LIBRARY_SEARCH_SYSTEM32 : LOAD_WITH_ALTERED_SEARCH_PATH; module = ::LoadLibraryExW(as_wcstr(library_path.value()), nullptr, flags); @@ -218,4 +218,38 @@ return nullptr; } +NativeLibrary PinSystemLibrary(FilePath::StringPieceType name, + NativeLibraryLoadError* error) { + Optional<FilePath> library_path = GetSystemLibraryName(name); + if (!library_path) { + if (error) + error->code = ERROR_NOT_FOUND; + return nullptr; + } + + // GetModuleHandleEx acquires the LoaderLock, hence must not be called from + // Dllmain. + ScopedBlockingCall scoped_blocking_call(FROM_HERE, BlockingType::MAY_BLOCK); + ScopedNativeLibrary module; + if (!::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, + as_wcstr(library_path.value().value()), + ScopedNativeLibrary::Receiver(module).get())) { + // Load and pin the library since it wasn't already loaded. + module = ScopedNativeLibrary( + LoadSystemLibraryHelper(library_path.value(), error)); + if (module.is_valid()) { + ScopedNativeLibrary temp; + if (!::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN, + as_wcstr(library_path.value().value()), + ScopedNativeLibrary::Receiver(temp).get())) { + if (error) + error->code = ::GetLastError(); + // Return nullptr since we failed to pin the module. + return nullptr; + } + } + } + return module.release(); +} + } // namespace base
diff --git a/base/test/scoped_task_environment.cc b/base/test/scoped_task_environment.cc index 720cee2..7e51e08 100644 --- a/base/test/scoped_task_environment.cc +++ b/base/test/scoped_task_environment.cc
@@ -416,7 +416,8 @@ void ScopedTaskEnvironment::CompleteInitialization() { #if defined(OS_POSIX) || defined(OS_FUCHSIA) - if (main_thread_type() == MainThreadType::IO) { + if (main_thread_type() == MainThreadType::IO || + main_thread_type() == MainThreadType::IO_MOCK_TIME) { file_descriptor_watcher_ = std::make_unique<FileDescriptorWatcher>(GetMainThreadTaskRunner()); }
diff --git a/base/test/scoped_task_environment_unittest.cc b/base/test/scoped_task_environment_unittest.cc index 96d4ff2e..6d77fde 100644 --- a/base/test/scoped_task_environment_unittest.cc +++ b/base/test/scoped_task_environment_unittest.cc
@@ -287,8 +287,7 @@ #if defined(OS_POSIX) TEST_F(ScopedTaskEnvironmentTest, SupportsFileDescriptorWatcherOnIOMainThread) { ScopedTaskEnvironment scoped_task_environment( - ScopedTaskEnvironment::MainThreadType::IO, - ScopedTaskEnvironment::ThreadPoolExecutionMode::ASYNC); + ScopedTaskEnvironment::MainThreadType::IO); int pipe_fds_[2]; ASSERT_EQ(0, pipe(pipe_fds_)); @@ -302,6 +301,32 @@ // This will hang if the notification doesn't occur as expected. run_loop.Run(); } + +TEST_F(ScopedTaskEnvironmentTest, + SupportsFileDescriptorWatcherOnIOMockTimeMainThread) { + ScopedTaskEnvironment scoped_task_environment( + ScopedTaskEnvironment::MainThreadType::IO_MOCK_TIME); + + int pipe_fds_[2]; + ASSERT_EQ(0, pipe(pipe_fds_)); + + RunLoop run_loop; + + ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, BindLambdaForTesting([&]() { + int64_t x = 1; + auto ret = write(pipe_fds_[1], &x, sizeof(x)); + ASSERT_EQ(static_cast<size_t>(ret), sizeof(x)); + }), + TimeDelta::FromHours(1)); + + auto controller = FileDescriptorWatcher::WatchReadable( + pipe_fds_[0], run_loop.QuitClosure()); + + // This will hang if the notification doesn't occur as expected (Run() should + // fast-forward-time when idle). + run_loop.Run(); +} #endif // defined(OS_POSIX) // Verify that the TickClock returned by
diff --git a/base/threading/thread_restrictions.h b/base/threading/thread_restrictions.h index 6ad6729..f80d144a3 100644 --- a/base/threading/thread_restrictions.h +++ b/base/threading/thread_restrictions.h
@@ -185,6 +185,9 @@ class AudioOutputDevice; class BlockingUrlProtocol; } +namespace memory_instrumentation { +class OSMetrics; +} namespace midi { class TaskService; // https://crbug.com/796830 } @@ -338,6 +341,7 @@ friend class content::WebContentsViewMac; friend class cronet::CronetPrefsManager; friend class cronet::CronetURLRequestContext; + friend class memory_instrumentation::OSMetrics; friend class mojo::CoreLibraryInitializer; friend class resource_coordinator::TabManagerDelegate; // crbug.com/778703 friend class ui::MaterialDesignController;
diff --git a/base/win/win_util.cc b/base/win/win_util.cc index 8bfe84d9..94e70637 100644 --- a/base/win/win_util.cc +++ b/base/win/win_util.cc
@@ -99,16 +99,19 @@ // Windows versions GetProcAddress will return null and report failure so that // callers can fall back on the deprecated SetProcessDPIAware. bool SetProcessDpiAwarenessWrapper(PROCESS_DPI_AWARENESS value) { - decltype(&::SetProcessDpiAwareness) set_process_dpi_awareness_func = - reinterpret_cast<decltype(&::SetProcessDpiAwareness)>(GetProcAddress( - GetModuleHandle(L"user32.dll"), "SetProcessDpiAwarenessInternal")); + if (!IsUser32AndGdi32Available()) + return false; + + static const auto set_process_dpi_awareness_func = + reinterpret_cast<decltype(&::SetProcessDpiAwareness)>( + GetUser32FunctionPointer("SetProcessDpiAwarenessInternal")); if (set_process_dpi_awareness_func) { HRESULT hr = set_process_dpi_awareness_func(value); if (SUCCEEDED(hr)) return true; DLOG_IF(ERROR, hr == E_ACCESSDENIED) - << "Access denied error from SetProcessDpiAwarenessInternal. Function " - "called twice, or manifest was used."; + << "Access denied error from SetProcessDpiAwarenessInternal. " + "Function called twice, or manifest was used."; NOTREACHED() << "SetProcessDpiAwarenessInternal failed with unexpected error: " << hr; @@ -127,11 +130,12 @@ // available (i.e., prior to Windows 10 1703) or fails, returns false. // https://docs.microsoft.com/en-us/windows/desktop/hidpi/dpi-awareness-context bool EnablePerMonitorV2() { - decltype( - &::SetProcessDpiAwarenessContext) set_process_dpi_awareness_context_func = + if (!IsUser32AndGdi32Available()) + return false; + + static const auto set_process_dpi_awareness_context_func = reinterpret_cast<decltype(&::SetProcessDpiAwarenessContext)>( - ::GetProcAddress(::GetModuleHandle(L"user32.dll"), - "SetProcessDpiAwarenessContext")); + GetUser32FunctionPointer("SetProcessDpiAwarenessContext")); if (set_process_dpi_awareness_context_func) { return set_process_dpi_awareness_context_func( DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); @@ -174,6 +178,15 @@ return &state; } +NativeLibrary PinUser32Internal(NativeLibraryLoadError* error) { + static NativeLibraryLoadError load_error; + static const NativeLibrary user32_module = + PinSystemLibrary(FILE_PATH_LITERAL("user32.dll"), &load_error); + if (!user32_module && error) + error->code = load_error.code; + return user32_module; +} + } // namespace // Uses the Windows 10 WRL API's to query the current system state. The API's @@ -273,11 +286,8 @@ // if we find ACPI\* or HID\VID* keyboards. typedef BOOL (WINAPI* GetAutoRotationState)(PAR_STATE state); - - GetAutoRotationState get_rotation_state = - reinterpret_cast<GetAutoRotationState>(::GetProcAddress( - GetModuleHandle(L"user32.dll"), "GetAutoRotationState")); - + static const auto get_rotation_state = reinterpret_cast<GetAutoRotationState>( + GetUser32FunctionPointer("GetAutoRotationState")); if (get_rotation_state) { AR_STATE auto_rotation_state = AR_ENABLED; get_rotation_state(&auto_rotation_state); @@ -287,8 +297,8 @@ // the current configuration, then we can assume that this is a desktop // or a traditional laptop. if (reason) { - *reason += (auto_rotation_state & AR_NOSENSOR) ? "AR_NOSENSOR\n" : - "AR_NOT_SUPPORTED\n"; + *reason += (auto_rotation_state & AR_NOSENSOR) ? "AR_NOSENSOR\n" + : "AR_NOT_SUPPORTED\n"; result = true; } else { return true; @@ -552,10 +562,9 @@ // See // https://msdn.microsoft.com/en-us/library/windows/desktop/dn629263(v=vs.85).aspx typedef decltype(GetAutoRotationState)* GetAutoRotationStateType; - GetAutoRotationStateType get_auto_rotation_state_func = - reinterpret_cast<GetAutoRotationStateType>(GetProcAddress( - GetModuleHandle(L"user32.dll"), "GetAutoRotationState")); - + static const auto get_auto_rotation_state_func = + reinterpret_cast<GetAutoRotationStateType>( + GetUser32FunctionPointer("GetAutoRotationState")); if (get_auto_rotation_state_func) { AR_STATE rotation_state = AR_ENABLED; if (get_auto_rotation_state_func(&rotation_state) && @@ -703,13 +712,16 @@ } void EnableHighDPISupport() { + if (!IsUser32AndGdi32Available()) + return; + // Enable per-monitor V2 if it is available (Win10 1703 or later). if (EnablePerMonitorV2()) return; - // Fall back to per-monitor DPI for older versions of Win10 instead of Win8.1 - // since Win8.1 does not have EnableChildWindowDpiMessage, necessary for - // correct non-client area scaling across monitors. + // Fall back to per-monitor DPI for older versions of Win10 instead of + // Win8.1 since Win8.1 does not have EnableChildWindowDpiMessage, + // necessary for correct non-client area scaling across monitors. PROCESS_DPI_AWARENESS process_dpi_awareness = GetVersion() >= Version::WIN10 ? PROCESS_PER_MONITOR_DPI_AWARE : PROCESS_SYSTEM_DPI_AWARE; @@ -736,6 +748,18 @@ return string16(as_u16cstr(guid_string), kGuidStringCharacters - 1); } +bool PinUser32(NativeLibraryLoadError* error) { + return PinUser32Internal(error) != nullptr; +} + +void* GetUser32FunctionPointer(const char* function_name, + NativeLibraryLoadError* error) { + NativeLibrary user32_module = PinUser32Internal(error); + if (user32_module) + return GetFunctionPointerFromNativeLibrary(user32_module, function_name); + return nullptr; +} + ScopedDomainStateForTesting::ScopedDomainStateForTesting(bool state) : initial_state_(IsEnrolledToDomain()) { *GetDomainEnrollmentStateStorage() = state;
diff --git a/base/win/win_util.h b/base/win/win_util.h index fcbca51..e53f6d6 100644 --- a/base/win/win_util.h +++ b/base/win/win_util.h
@@ -37,6 +37,9 @@ typedef _tagpropertykey PROPERTYKEY; namespace base { + +struct NativeLibraryLoadError; + namespace win { inline uint32_t HandleToUint32(HANDLE h) { @@ -160,9 +163,17 @@ // Returns true if the current process can make USER32 or GDI32 calls such as // CreateWindow and CreateDC. Windows 8 and above allow the kernel component -// of these calls to be disabled which can cause undefined behaviour such as -// crashes. This function can be used to guard areas of code using these calls -// and provide a fallback path if necessary. +// of these calls to be disabled (also known as win32k lockdown) which can +// cause undefined behaviour such as crashes. This function can be used to +// guard areas of code using these calls and provide a fallback path if +// necessary. +// Because they are not always needed (and not needed at all in processes that +// have the win32k lockdown), USER32 and GDI32 are delayloaded. Attempts to +// load them in those processes will cause a crash. Any code which uses USER32 +// or GDI32 and may run in a locked-down process MUST be guarded using this +// method. Before the dlls were delayloaded, method calls into USER32 and GDI32 +// did not work, so adding calls to this method to guard them simply avoids +// unnecessary method calls. BASE_EXPORT bool IsUser32AndGdi32Available(); // Takes a snapshot of the modules loaded in the |process|. The returned @@ -187,6 +198,21 @@ // Returns a string representation of |rguid|. BASE_EXPORT string16 String16FromGUID(REFGUID rguid); +// Attempts to pin user32.dll to ensure it remains loaded. If it isn't loaded +// yet, the module will first be loaded and then the pin will be attempted. If +// pinning is successful, returns true. If the module cannot be loaded and/or +// pinned, |error| is set and the method returns false. +BASE_EXPORT bool PinUser32(NativeLibraryLoadError* error = nullptr); + +// Gets a pointer to a function within user32.dll, if available. If user32.dll +// cannot be loaded or the function cannot be found, this function returns +// nullptr and sets |error|. Once loaded, user32.dll is pinned, and therefore +// the function pointer returned by this function will never change and can be +// cached. +BASE_EXPORT void* GetUser32FunctionPointer( + const char* function_name, + NativeLibraryLoadError* error = nullptr); + // Allows changing the domain enrolled state for the life time of the object. // The original state is restored upon destruction. class BASE_EXPORT ScopedDomainStateForTesting {
diff --git a/build/config/android/internal_rules.gni b/build/config/android/internal_rules.gni index 42d2ccd..5c12c8a3 100644 --- a/build/config/android/internal_rules.gni +++ b/build/config/android/internal_rules.gni
@@ -2147,6 +2147,10 @@ # found in the AndroidManifest.xml. Does not affect the package name # used in AndroidManifest.xml. # + # resource_ids_provider_dep: (optional) + # Use resource IDs provided by another APK target when compiling resources + # (via. "aapt2 link --stable-ids") + # # Output variables: # arsc_output: Path to output .ap_ file (optional). # @@ -3654,11 +3658,7 @@ java_sources_file = _java_sources_file } output_jar_path = _final_jar_path - if (_has_sources) { - deps = _accumulated_public_deps # compile & build_config - } else { - deps = _accumulated_deps + _accumulated_public_deps - } + deps = _accumulated_deps + _accumulated_public_deps } _accumulated_public_deps += [ ":$_process_prebuilt_target_name" ]
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni index a0b88163..b4abdc2 100644 --- a/build/config/android/rules.gni +++ b/build/config/android/rules.gni
@@ -2045,8 +2045,13 @@ # resources.arsc file in the apk or module. # resources_config_path: Path to the aapt2 optimize config file that tags # resources with acceptable/non-acceptable optimizations. - # verify_android_configuration: Enables verification of expected merged - # manifest and proguard flags based on a golden file. + # verify_manifest: Enables verification of expected merged manifest based + # on a golden file. + # verify_proguard_flags: Enables verification of expected merged proguard + # flags based on a golden file. + # resource_ids_provider_dep: If passed, this target will use the resource + # IDs generated by {resource_ids_provider_dep}__compile_res during + # resource compilation. # static_library_dependent_targets: A list of scopes describing targets that # use this target as a static library. Common Java code from the targets # listed in static_library_dependent_targets will be moved into this @@ -2292,6 +2297,8 @@ } } _resource_ids_provider_dep = _resource_ids_provider_deps[0] + } else if (defined(invoker.resource_ids_provider_dep)) { + _resource_ids_provider_dep = invoker.resource_ids_provider_dep } _uses_static_library = defined(invoker.static_library_provider) @@ -2310,13 +2317,6 @@ _incremental_install_json_path = "$root_out_dir/gen.runtime/$_target_dir_name/$target_name.incremental.json" } - _verify_android_configuration = - defined(invoker.verify_android_configuration) && - invoker.verify_android_configuration && !is_java_debug - if (_verify_android_configuration) { - _target_src_dir = get_label_info(":$target_name", "dir") - } - _android_manifest = "$target_gen_dir/${_template_name}_manifest/AndroidManifest.xml" _merge_manifest_target = "${_template_name}__merge_manifests" @@ -2324,7 +2324,9 @@ input_manifest = _android_root_manifest output_manifest = _android_manifest build_config = _build_config - if (_verify_android_configuration) { + if (defined(invoker.verify_manifest) && invoker.verify_manifest && + !is_java_debug) { + _target_src_dir = get_label_info(":$target_name", "dir") expected_manifest = "$_target_src_dir/java/$_template_name.AndroidManifest.expected" } @@ -2809,11 +2811,6 @@ forward_variables_from(invoker, [ "proguard_jar_path" ]) deps += _deps + [ ":$_compile_resources_target" ] proguard_mapping_path = _proguard_mapping_path - if (!defined(invoker.proguard_jar_path) && - _verify_android_configuration) { - proguard_expectations_file = - "$_target_src_dir/java/$_template_name.proguard_flags.expected" - } } else { input_jars = [ _lib_dex_path ] input_dex_classpath = @@ -3266,10 +3263,10 @@ "product_version_resources_dep", "proguard_configs", "proguard_enabled", - "verify_android_configuration", "proguard_jar_path", "resource_blacklist_regex", "resource_blacklist_exceptions", + "resource_ids_provider_dep", "resources_config_path", "secondary_abi_loadable_modules", "secondary_abi_shared_libraries", @@ -3384,6 +3381,7 @@ "proguard_jar_path", "resource_blacklist_exceptions", "resource_blacklist_regex", + "resource_ids_provider_dep", "resources_config_path", "secondary_abi_shared_libraries", "shared_libraries", @@ -3396,7 +3394,7 @@ "testonly", "uncompress_shared_libraries", "use_chromium_linker", - "verify_android_configuration", + "verify_manifest", "version_code", "version_name", "write_asset_list", @@ -4432,6 +4430,13 @@ not_needed(invoker, [ "proguard_jar_path" ]) } } else { + _verify_proguard_flags = defined(invoker.verify_proguard_flags) && + invoker.verify_proguard_flags + if (_verify_proguard_flags) { + _proguard_expectations_file = + get_label_info(":$target_name", "dir") + + "/java/$target_name.proguard_flags.expected" + } dex(_sync_dex_target) { enable_multidex = _enable_multidex proguard_enabled = true @@ -4443,6 +4448,10 @@ ]) build_config = _build_config + if (_verify_proguard_flags) { + proguard_expectations_file = _proguard_expectations_file + } + deps = _sync_module_java_targets + [ ":$_build_config_target" ] output = _unsplit_dex_zip }
diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn index 5c1f067..ef1dd234 100644 --- a/chrome/BUILD.gn +++ b/chrome/BUILD.gn
@@ -236,12 +236,26 @@ } ldflags = [ + "/DELAYLOAD:advapi32.dll", + "/DELAYLOAD:comdlg32.dll", "/DELAYLOAD:dbghelp.dll", "/DELAYLOAD:dwmapi.dll", - "/DELAYLOAD:uxtheme.dll", + "/DELAYLOAD:imm32.dll", + "/DELAYLOAD:iphlpapi.dll", "/DELAYLOAD:ole32.dll", "/DELAYLOAD:oleaut32.dll", + "/DELAYLOAD:propsys.dll", + "/DELAYLOAD:psapi.dll", + "/DELAYLOAD:shell32.dll", + "/DELAYLOAD:shlwapi.dll", + "/DELAYLOAD:user32.dll", + "/DELAYLOAD:uxtheme.dll", + "/DELAYLOAD:wer.dll", + "/DELAYLOAD:wevtapi.dll", "/DELAYLOAD:winhttp.dll", + "/DELAYLOAD:wininet.dll", + "/DELAYLOAD:winmm.dll", + "/DELAYLOAD:wintrust.dll", ] if (current_cpu == "x64") { @@ -421,22 +435,53 @@ ] ldflags = [ + "/DELAYLOAD:advapi32.dll", + "/DELAYLOAD:comctl32.dll", "/DELAYLOAD:comdlg32.dll", + "/DELAYLOAD:credui.dll", "/DELAYLOAD:crypt32.dll", "/DELAYLOAD:cryptui.dll", + "/DELAYLOAD:d3d11.dll", + "/DELAYLOAD:d3d9.dll", "/DELAYLOAD:dbghelp.dll", "/DELAYLOAD:dhcpcsvc.dll", "/DELAYLOAD:dwmapi.dll", + "/DELAYLOAD:dwrite.dll", + "/DELAYLOAD:dxgi.dll", + "/DELAYLOAD:esent.dll", + "/DELAYLOAD:gdi32.dll", + "/DELAYLOAD:hid.dll", "/DELAYLOAD:imagehlp.dll", "/DELAYLOAD:imm32.dll", "/DELAYLOAD:iphlpapi.dll", + "/DELAYLOAD:netapi32.dll", + "/DELAYLOAD:ole32.dll", + "/DELAYLOAD:oleacc.dll", + "/DELAYLOAD:oleaut32.dll", + "/DELAYLOAD:ncrypt.dll", + "/DELAYLOAD:propsys.dll", + "/DELAYLOAD:psapi.dll", + "/DELAYLOAD:secur32.dll", "/DELAYLOAD:setupapi.dll", + "/DELAYLOAD:shell32.dll", + "/DELAYLOAD:shlwapi.dll", + "/DELAYLOAD:uiautomationcore.dll", "/DELAYLOAD:urlmon.dll", + "/DELAYLOAD:user32.dll", + "/DELAYLOAD:userenv.dll", + "/DELAYLOAD:usp10.dll", + "/DELAYLOAD:wer.dll", + "/DELAYLOAD:wevtapi.dll", "/DELAYLOAD:winhttp.dll", "/DELAYLOAD:wininet.dll", + "/DELAYLOAD:winmm.dll", "/DELAYLOAD:winspool.drv", + "/DELAYLOAD:wintrust.dll", + "/DELAYLOAD:winusb.dll", "/DELAYLOAD:ws2_32.dll", "/DELAYLOAD:wsock32.dll", + "/DELAYLOAD:wtsapi32.dll", + "/DELAYLOAD:api-ms-win-core-winrt-string-l1-1-0.dll", ] if (!is_component_build) { @@ -520,11 +565,25 @@ ] ldflags = [ + "/DELAYLOAD:comctl32.dll", + "/DELAYLOAD:comdlg32.dll", "/DELAYLOAD:d3d11.dll", "/DELAYLOAD:d3d9.dll", "/DELAYLOAD:dwmapi.dll", + "/DELAYLOAD:dxgi.dll", "/DELAYLOAD:dxva2.dll", "/DELAYLOAD:esent.dll", + "/DELAYLOAD:gdi32.dll", + "/DELAYLOAD:imm32.dll", + "/DELAYLOAD:ole32.dll", + "/DELAYLOAD:oleacc.dll", + "/DELAYLOAD:propsys.dll", + "/DELAYLOAD:rstrtmgr.dll", + "/DELAYLOAD:shell32.dll", + "/DELAYLOAD:shlwapi.dll", + "/DELAYLOAD:urlmon.dll", + "/DELAYLOAD:user32.dll", + "/DELAYLOAD:usp10.dll", "/DELAYLOAD:wininet.dll", ]
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn index a2595e3..885bac0a 100644 --- a/chrome/android/BUILD.gn +++ b/chrome/android/BUILD.gn
@@ -1782,11 +1782,12 @@ "is_64_bit_browser", "is_base_module", "module_name", - "verify_android_configuration", "proguard_jar_path", + "resource_ids_provider_dep", "static_library_provider", "target_type", "use_trichrome_library", + "verify_manifest", "version_code", "version_name", ]) @@ -1891,6 +1892,7 @@ use_trichrome_library = true if (trichrome_synchronized_proguard) { static_library_provider = ":trichrome_library_apk" + resource_ids_provider_dep = "//android_webview:trichrome_webview_apk" } } @@ -2194,13 +2196,14 @@ version_code = _version_code version_name = _version_name - # Having //clank present causes different flags because of how play services - # is wired up. Also, only check for the 32 bit browser as this is the - # default. - if (!enable_chrome_android_internal && - (!defined(invoker.is_64_bit_browser) || !invoker.is_64_bit_browser) && - !_is_trichrome) { - verify_android_configuration = true + if (defined(invoker.verify_android_configuration) && + invoker.verify_android_configuration) { + verify_manifest = true + } + + if (_is_trichrome && trichrome_synchronized_proguard) { + resource_ids_provider_dep = + "//android_webview:trichrome_webview_for_bundle_apk" } } @@ -2264,6 +2267,10 @@ if (!is_java_debug) { proguard_enabled = true proguard_android_sdk_dep = webview_framework_dep + if (defined(invoker.verify_android_configuration) && + invoker.verify_android_configuration) { + verify_proguard_flags = true + } } enable_language_splits = enable_chrome_language_splits min_sdk_version = _min_sdk_version @@ -2296,6 +2303,12 @@ monochrome_or_trichrome_public_bundle_tmpl("monochrome_public_bundle") { bundle_suffix = "" + + # Having //clank present causes different flags because of how play services + # is wired up. + if (!enable_chrome_android_internal) { + verify_android_configuration = true + } } if (is_official_build) {
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni index e634fb8..dedea7b 100644 --- a/chrome/android/chrome_java_sources.gni +++ b/chrome/android/chrome_java_sources.gni
@@ -614,7 +614,6 @@ "java/src/org/chromium/chrome/browser/engagement/SiteEngagementService.java", "java/src/org/chromium/chrome/browser/explore_sites/CategoryCardAdapter.java", "java/src/org/chromium/chrome/browser/explore_sites/CategoryCardViewHolderFactory.java", - "java/src/org/chromium/chrome/browser/explore_sites/CondensedCategoryCardViewHolderFactory.java", "java/src/org/chromium/chrome/browser/explore_sites/ExperimentalExploreSitesCategoryTileView.java", "java/src/org/chromium/chrome/browser/explore_sites/ExperimentalExploreSitesSection.java", "java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBackgroundTask.java",
diff --git a/chrome/android/chrome_public_apk_tmpl.gni b/chrome/android/chrome_public_apk_tmpl.gni index 3fd6e9fc..dbf8869e 100644 --- a/chrome/android/chrome_public_apk_tmpl.gni +++ b/chrome/android/chrome_public_apk_tmpl.gni
@@ -297,7 +297,7 @@ forward_variables_from(invoker, [ "version_code", - "verify_android_configuration", + "verify_manifest", ]) is_trichrome = defined(invoker.use_trichrome_library) && invoker.use_trichrome_library
diff --git a/chrome/android/java/README.md b/chrome/android/java/README.md index 94205510..74c2d08 100644 --- a/chrome/android/java/README.md +++ b/chrome/android/java/README.md
@@ -19,8 +19,8 @@ ### What are `*.proguard_flags.expected` files? -[monochrome_public_apk.proguard_flags.expected](monochrome_public_apk.proguard_flags.expected) -contains all proguard configs used when building MonochromePublic.apk, and is +[monochrome_public_bundle.proguard_flags.expected](monochrome_public_bundle.proguard_flags.expected) +contains all proguard configs used when building MonochromePublic.aab, and is generated by the `proguard()` build step. ### Why do we care about Proguard flag discrepancies? @@ -97,7 +97,7 @@ For Proguard flags failures: ``` - autoninja -C $CHROMIUM_OUTPUT_DIR monochrome_public_apk + autoninja -C $CHROMIUM_OUTPUT_DIR monochrome_public_bundle ``` 3. Run the command suggested in the error message to copy the contents of the @@ -129,7 +129,7 @@ Updating the file doesn't fix the error -* Make sure you're building `monochrome_public_apk` +* Make sure you're building `monochrome_public_bundle` Otherwise, please file a bug at [crbug.com/new](https://crbug.com/new) and/or message estevenson@.
diff --git a/chrome/android/java/monochrome_public_apk.proguard_flags.expected b/chrome/android/java/monochrome_public_bundle.proguard_flags.expected similarity index 96% rename from chrome/android/java/monochrome_public_apk.proguard_flags.expected rename to chrome/android/java/monochrome_public_bundle.proguard_flags.expected index 59be4b9..cdb0675 100644 --- a/chrome/android/java/monochrome_public_apk.proguard_flags.expected +++ b/chrome/android/java/monochrome_public_bundle.proguard_flags.expected
@@ -121,7 +121,9 @@ # If we annotated all Parcelables that get put into Bundles other than # for saveInstanceState (e.g. PendingIntents), then we could actually keep the # names of just those ones. For now, we'll just keep them all. --keepnames class * implements android.os.Parcelable +-keepnames class * implements android.os.Parcelable { + <init>(...); +} # Keep all enum values and valueOf methods. See # http://proguard.sourceforge.net/index.html#manual/examples.html @@ -130,10 +132,6 @@ public static **[] values(); } -# Keep classes implementing ParameterProvider -- these will be instantiated -# via reflection. --keep class * implements org.chromium.base.test.params.ParameterProvider - # Allows Proguard freedom in removing these log related calls. We ask for debug # and verbose logs to be stripped out in base.Log, so we are just ensuring we # get rid of all other debug/verbose logs. @@ -191,25 +189,25 @@ -keep @interface org.chromium.base.annotations.UsedByReflection # Keeps for class level annotations. --keep @org.chromium.base.annotations.UsedByReflection class * {} +-keep @org.chromium.base.annotations.UsedByReflection class ** {} # Keeps for method level annotations. --keepclasseswithmembers class * { +-keepclasseswithmembers class ** { @org.chromium.base.annotations.AccessedByNative <fields>; } --keepclasseswithmembers,includedescriptorclasses class * { +-keepclasseswithmembers,includedescriptorclasses class ** { @org.chromium.base.annotations.CalledByNative <methods>; } --keepclasseswithmembers,includedescriptorclasses class * { +-keepclasseswithmembers,includedescriptorclasses class ** { @org.chromium.base.annotations.CalledByNativeUnchecked <methods>; } --keepclasseswithmembers class * { +-keepclasseswithmembers class ** { @org.chromium.base.annotations.UsedByReflection <methods>; } --keepclasseswithmembers class * { +-keepclasseswithmembers class ** { @org.chromium.base.annotations.UsedByReflection <fields>; } --keepclasseswithmembers,includedescriptorclasses class * { +-keepclasseswithmembers,includedescriptorclasses class ** { native <methods>; } @@ -220,10 +218,10 @@ # Never inline classes or methods with this annotation, but allow shrinking and # obfuscation. --keepnames,allowobfuscation @org.chromium.base.annotations.DoNotInline class * { +-keepnames,allowobfuscation @org.chromium.base.annotations.DoNotInline class ** { *; } --keepclassmembernames,allowobfuscation class * { +-keepclassmembernames,allowobfuscation class ** { @org.chromium.base.annotations.DoNotInline <methods>; } @@ -236,7 +234,9 @@ # If we annotated all Parcelables that get put into Bundles other than # for saveInstanceState (e.g. PendingIntents), then we could actually keep the # names of just those ones. For now, we'll just keep them all. --keepnames class org.chromium.** implements android.os.Parcelable +-keepnames class org.chromium.** implements android.os.Parcelable { + <init>(...); +} # Keep all enum values and valueOf methods. See # http://proguard.sourceforge.net/index.html#manual/examples.html @@ -279,6 +279,18 @@ -keep class org.chromium.build.BuildHooksAndroidImpl ################################################################################ +# ../../build/android/multidex.flags +################################################################################ +# Copyright 2017 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# When multidex is enabled, need to keep the @MainDex annotation so that it +# can be used to create the main dex list. +-keepattributes *Annotations* +-keep @interface org.chromium.base.annotations.MainDex + +################################################################################ # ../../chrome/android/java/proguard.flags ################################################################################ # Copyright 2016 The Chromium Authors. All rights reserved.
diff --git a/chrome/android/java/res/values-v17/styles.xml b/chrome/android/java/res/values-v17/styles.xml index bf50d15..469e69c 100644 --- a/chrome/android/java/res/values-v17/styles.xml +++ b/chrome/android/java/res/values-v17/styles.xml
@@ -549,12 +549,7 @@ <item name="android:includeFontPadding">false</item> <item name="android:requiresFadingEdge">horizontal</item> <item name="android:singleLine">true</item> - <item name="android:textAppearance"> - @style/TextAppearance.ContextualSearchContextTextView</item> - </style> - <style name="TextAppearance.ContextualSearchContextTextView"> - <item name="android:textColor">#CCC</item> - <item name="android:textSize">@dimen/text_size_large</item> + <item name="android:textAppearance">@style/TextAppearance.BlackHint1</item> </style> <style name="ContextualSearchCaptionTextView"> <item name="android:layout_width">match_parent</item>
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml index fa90ac3d..4d0f3a3d 100644 --- a/chrome/android/java/res/values/dimens.xml +++ b/chrome/android/java/res/values/dimens.xml
@@ -377,18 +377,6 @@ <dimen name="explore_sites_loading_spinny_size">36dp</dimen> <dimen name="explore_sites_loading_spinny_padding">24dp</dimen> - <dimen name="explore_sites_category_padding_vertical_condensed">12dp</dimen> - <dimen name="explore_sites_category_padding_horizontal_condensed">4dp</dimen> - <dimen name="explore_sites_category_grid_minimum_spacing">8dp</dimen> - <dimen name="explore_sites_category_title_spacing_condensed">10dp</dimen> - <dimen name="explore_sites_tile_view_icon_background_size_condensed">44dp</dimen> - <dimen name="explore_sites_tile_view_icon_size_condensed">22dp</dimen> - <dimen name="explore_sites_tile_view_icon_margin_top_condensed">18dp</dimen> - <dimen name="explore_sites_tile_view_title_margin_top_condensed">51dp</dimen> - <dimen name="explore_sites_tile_view_width_condensed">72dp</dimen> - <dimen name="explore_sites_tile_view_icon_padding_horizontal_condensed">4dp</dimen> - <dimen name="explore_sites_tile_view_text_margin_bottom_condensed">4dp</dimen> - <!-- Recent tabs page --> <dimen name="recent_tabs_visible_separator_padding">8dp</dimen> <dimen name="recent_tabs_group_view_vertical_padding">8dp</dimen>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java index 3a5213b6..8f3e186 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
@@ -304,7 +304,6 @@ public static final String SERVICE_WORKER_PAYMENT_APPS = "ServiceWorkerPaymentApps"; public static final String SHOPPING_ASSIST = "ShoppingAssist"; public static final String SHOW_TRUSTED_PUBLISHER_URL = "ShowTrustedPublisherURL"; - public static final String SOLE_INTEGRATION = "SoleIntegration"; public static final String SSL_COMMITTED_INTERSTITIALS = "SSLCommittedInterstitials"; public static final String SPANNABLE_INLINE_AUTOCOMPLETE = "SpannableInlineAutocomplete"; public static final String SUBRESOURCE_FILTER = "SubresourceFilter";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ShortcutHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/ShortcutHelper.java index eecb0b8..819d6108 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/ShortcutHelper.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ShortcutHelper.java
@@ -232,7 +232,7 @@ */ @SuppressWarnings("unused") @CalledByNative - private static void addShortcut(String id, String url, String userTitle, Bitmap icon, + public static void addShortcut(String id, String url, String userTitle, Bitmap icon, boolean isIconAdaptive, int source) { Context context = ContextUtils.getApplicationContext(); final Intent shortcutIntent = createShortcutIntent(url);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabPanel.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabPanel.java index 58f83d4..83653174 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabPanel.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabPanel.java
@@ -9,13 +9,12 @@ import android.view.MotionEvent; import org.chromium.base.SysUtils; +import org.chromium.base.TimeUtils; import org.chromium.base.metrics.RecordHistogram; import org.chromium.chrome.browser.ChromeFeatureList; import org.chromium.chrome.browser.compositor.LayerTitleCache; import org.chromium.chrome.browser.compositor.bottombar.OverlayContentDelegate; import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel; -import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.PanelState; -import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.StateChangeReason; import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelContent; import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelManager; import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelManager.PanelPriority; @@ -44,8 +43,17 @@ /** The compositor layer used for drawing the panel. */ private EphemeralTabSceneLayer mSceneLayer; + /** Remembers whether the panel was opened to the peeking state. */ + private boolean mDidRecordFirstPeek; + + /** The timestamp when the panel entered the peeking state for the first time. */ + private long mPanelPeekedNanoseconds; + /** Remembers whether the panel was opened beyond the peeking state. */ - private boolean mWasPanelOpened; + private boolean mDidRecordFirstOpen; + + /** The timestamp when the panel entered the opened state for the first time. */ + private long mPanelOpenedNanoseconds; /** True if the Tab from which the panel is opened is in incognito mode. */ private boolean mIsIncognito; @@ -172,13 +180,12 @@ @Override public void setPanelState(@PanelState int toState, @StateChangeReason int reason) { super.setPanelState(toState, reason); - if (toState == PanelState.CLOSED) { - RecordHistogram.recordBooleanHistogram("EphemeralTab.Ctr", mWasPanelOpened); - RecordHistogram.recordEnumeratedHistogram( - "EphemeralTab.CloseReason", reason, StateChangeReason.MAX_VALUE + 1); - mWasPanelOpened = false; + if (toState == PanelState.PEEKED) { + recordMetricsForPeeked(); + } else if (toState == PanelState.CLOSED) { + recordMetricsForClosed(reason); } else if (toState == PanelState.EXPANDED || toState == PanelState.MAXIMIZED) { - mWasPanelOpened = true; + recordMetricsForOpened(); } } @@ -307,4 +314,70 @@ mEphemeralTabBarControl = null; } } + + //-------- + // METRICS + //-------- + /** Records metrics for the peeked panel state. */ + private void recordMetricsForPeeked() { + startPeekTimer(); + // Could be returning to Peek from Open. + finishOpenTimer(); + } + + /** Records metrics when the panel has been fully opened. */ + private void recordMetricsForOpened() { + startOpenTimer(); + finishPeekTimer(); + } + + /** Records metrics when the panel has been closed. */ + private void recordMetricsForClosed(@StateChangeReason int stateChangeReason) { + finishPeekTimer(); + finishOpenTimer(); + RecordHistogram.recordBooleanHistogram("EphemeralTab.Ctr", mDidRecordFirstOpen); + RecordHistogram.recordEnumeratedHistogram( + "EphemeralTab.CloseReason", stateChangeReason, StateChangeReason.MAX_VALUE + 1); + resetTimers(); + } + + /** Resets the metrics used by the timers. */ + private void resetTimers() { + mDidRecordFirstPeek = false; + mPanelPeekedNanoseconds = 0; + mDidRecordFirstOpen = false; + mPanelOpenedNanoseconds = 0; + } + + /** Starts timing the peek state if it's not already been started. */ + private void startPeekTimer() { + if (mPanelPeekedNanoseconds == 0) mPanelPeekedNanoseconds = System.nanoTime(); + } + + /** Finishes timing metrics for the first peek state, unless that has already been done. */ + private void finishPeekTimer() { + if (!mDidRecordFirstPeek && mPanelPeekedNanoseconds != 0) { + mDidRecordFirstPeek = true; + int durationPeeking = (int) ((System.nanoTime() - mPanelPeekedNanoseconds) + / TimeUtils.NANOSECONDS_PER_MILLISECOND); + RecordHistogram.recordMediumTimesHistogram( + "EphemeralTab.DurationPeeked", durationPeeking); + } + } + + /** Starts timing the open state if it's not already been started. */ + private void startOpenTimer() { + if (mPanelOpenedNanoseconds == 0) mPanelOpenedNanoseconds = System.nanoTime(); + } + + /** Finishes timing metrics for the first open state, unless that has already been done. */ + private void finishOpenTimer() { + if (!mDidRecordFirstOpen && mPanelOpenedNanoseconds != 0) { + mDidRecordFirstOpen = true; + int durationOpened = (int) ((System.nanoTime() - mPanelOpenedNanoseconds) + / TimeUtils.NANOSECONDS_PER_MILLISECOND); + RecordHistogram.recordMediumTimesHistogram( + "EphemeralTab.DurationOpened", durationOpened); + } + } }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/ToolbarSwipeLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/ToolbarSwipeLayout.java index e53bd926..89505c1b 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/ToolbarSwipeLayout.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/ToolbarSwipeLayout.java
@@ -154,22 +154,26 @@ : fromIndex + 1; int leftIndex = dragFromLeftEdge ? toIndex : fromIndex; int rightIndex = !dragFromLeftEdge ? toIndex : fromIndex; + int leftTabId = Tab.INVALID_TAB_ID; + int rightTabId = Tab.INVALID_TAB_ID; - List<Integer> visibleTabs = new ArrayList<Integer>(); if (0 <= leftIndex && leftIndex < model.getCount()) { - int leftTabId = model.getTabAt(leftIndex).getId(); + leftTabId = model.getTabAt(leftIndex).getId(); mLeftTab = createLayoutTab(leftTabId, model.isIncognito(), NO_CLOSE_BUTTON, NEED_TITLE); prepareLayoutTabForSwipe(mLeftTab, leftIndex != fromIndex); - visibleTabs.add(leftTabId); } if (0 <= rightIndex && rightIndex < model.getCount()) { - int rightTabId = model.getTabAt(rightIndex).getId(); + rightTabId = model.getTabAt(rightIndex).getId(); mRightTab = createLayoutTab(rightTabId, model.isIncognito(), NO_CLOSE_BUTTON, NEED_TITLE); prepareLayoutTabForSwipe(mRightTab, rightIndex != fromIndex); - visibleTabs.add(rightTabId); } - + // Prioritize toTabId because fromTabId likely has a live layer. + int fromTabId = dragFromLeftEdge ? rightTabId : leftTabId; + int toTabId = !dragFromLeftEdge ? rightTabId : leftTabId; + List<Integer> visibleTabs = new ArrayList<Integer>(); + if (toTabId != Tab.INVALID_TAB_ID) visibleTabs.add(toTabId); + if (fromTabId != Tab.INVALID_TAB_ID) visibleTabs.add(fromTabId); updateCacheVisibleIds(visibleTabs); mToTab = null;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CategoryCardViewHolderFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CategoryCardViewHolderFactory.java index c6b4ce3..c55570f 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CategoryCardViewHolderFactory.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CategoryCardViewHolderFactory.java
@@ -13,7 +13,7 @@ import org.chromium.ui.modelutil.RecyclerViewAdapter; /** Factory to create CategoryCardViewHolder objects. */ -class CategoryCardViewHolderFactory implements RecyclerViewAdapter.ViewHolderFactory< +public class CategoryCardViewHolderFactory implements RecyclerViewAdapter.ViewHolderFactory< CategoryCardViewHolderFactory.CategoryCardViewHolder> { /** View holder for the recycler view. */ public static class CategoryCardViewHolder extends RecyclerView.ViewHolder {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CondensedCategoryCardViewHolderFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CondensedCategoryCardViewHolderFactory.java deleted file mode 100644 index d0e524ec..0000000 --- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/CondensedCategoryCardViewHolderFactory.java +++ /dev/null
@@ -1,23 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package org.chromium.chrome.browser.explore_sites; - -import org.chromium.chrome.R; - -class CondensedCategoryCardViewHolderFactory extends CategoryCardViewHolderFactory { - public CondensedCategoryCardViewHolderFactory() { - super(); - } - - @Override - protected int getCategoryCardViewResource() { - return R.layout.explore_sites_category_card_view_condensed; - } - - @Override - protected int getTileViewResource() { - return R.layout.explore_sites_tile_view_condensed; - } -}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryCardView.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryCardView.java index 08f74e4..48f2dd8 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryCardView.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryCardView.java
@@ -4,6 +4,7 @@ package org.chromium.chrome.browser.explore_sites; +import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Rect; @@ -70,13 +71,13 @@ mTileViewLayout = tileResource; } - private class CategoryCardInteractionDelegate + protected class CategoryCardInteractionDelegate implements ContextMenuManager.Delegate, OnClickListener, OnCreateContextMenuListener, OnFocusChangeListener { private String mSiteUrl; private int mTileIndex; - CategoryCardInteractionDelegate(String siteUrl, int tileIndex) { + public CategoryCardInteractionDelegate(String siteUrl, int tileIndex) { mSiteUrl = siteUrl; mTileIndex = tileIndex; } @@ -146,7 +147,7 @@ // paradigm for the category card view itself since it is mismatched to the needs of the // recycler view that we use for category cards. The controller for MVC is actually here, the // bind code inside the view class. - private class ExploreSitesSiteViewBinder + protected class ExploreSitesSiteViewBinder implements PropertyModelChangeProcessor .ViewBinder<PropertyModel, ExploreSitesTileView, PropertyKey> { @Override @@ -159,14 +160,18 @@ } else if (key == ExploreSitesSite.URL_KEY) { // Attach click handlers. CategoryCardInteractionDelegate interactionDelegate = - new CategoryCardInteractionDelegate(model.get(ExploreSitesSite.URL_KEY), - model.get(ExploreSitesSite.TILE_INDEX_KEY)); + createInteractionDelegate(model); view.setOnClickListener(interactionDelegate); view.setOnCreateContextMenuListener(interactionDelegate); ContextMenuManager.registerViewForTouchlessContextMenu(view, interactionDelegate); view.setOnFocusChangeListener(interactionDelegate); } } + + protected CategoryCardInteractionDelegate createInteractionDelegate(PropertyModel model) { + return new CategoryCardInteractionDelegate(model.get(ExploreSitesSite.URL_KEY), + model.get(ExploreSitesSite.TILE_INDEX_KEY)); + } } public ExploreSitesCategoryCardView(Context context, AttributeSet attrs) { @@ -245,7 +250,7 @@ siteModel.set(ExploreSitesSite.TILE_INDEX_KEY, tileIndex); mModelChangeProcessors.add(PropertyModelChangeProcessor.create( - siteModel, tileView, new ExploreSitesSiteViewBinder())); + siteModel, tileView, createViewBinder((Activity) getContext()))); // Fetch icon if not present already. if (siteModel.get(ExploreSitesSite.ICON_KEY) == null) { @@ -276,4 +281,8 @@ RecordHistogram.recordLinearCountHistogram("ExploreSites.SiteTilesClickIndex", cardIndex * MAX_TILE_COUNT + tileIndex, 1, 100, 100); } + + protected ExploreSitesSiteViewBinder createViewBinder(Activity activity) { + return new ExploreSitesSiteViewBinder(); + } }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesPage.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesPage.java index c125d1d..1a0c995 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesPage.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesPage.java
@@ -31,7 +31,6 @@ import org.chromium.chrome.browser.tab.EmptyTabObserver; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.TabObserver; -import org.chromium.chrome.browser.util.FeatureUtilities; import org.chromium.chrome.browser.widget.RoundedIconGenerator; import org.chromium.content_public.browser.NavigationController; import org.chromium.content_public.browser.NavigationEntry; @@ -162,9 +161,7 @@ mView.setNavigationDelegate(host.createHistoryNavigationDelegate()); mRecyclerView = mView.findViewById(R.id.explore_sites_category_recycler); - CategoryCardViewHolderFactory factory = FeatureUtilities.isNoTouchModeEnabled() - ? new CondensedCategoryCardViewHolderFactory() - : new CategoryCardViewHolderFactory(); + CategoryCardViewHolderFactory factory = createCategoryCardViewHolderFactory(); RecyclerViewAdapter<CategoryCardViewHolderFactory.CategoryCardViewHolder, Void> adapter = new RecyclerViewAdapter<>(adapterDelegate, factory); @@ -375,4 +372,8 @@ return RecyclerView.NO_POSITION; } + + protected CategoryCardViewHolderFactory createCategoryCardViewHolderFactory() { + return new CategoryCardViewHolderFactory(); + } }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesSite.java b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesSite.java index 2f740ef..5bebe4a 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesSite.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesSite.java
@@ -16,13 +16,13 @@ static final int DEFAULT_TILE_INDEX = -1; static final PropertyModel.ReadableIntPropertyKey ID_KEY = new PropertyModel.ReadableIntPropertyKey(); - static final PropertyModel.WritableIntPropertyKey TILE_INDEX_KEY = + public static final PropertyModel.WritableIntPropertyKey TILE_INDEX_KEY = new PropertyModel.WritableIntPropertyKey(); - static final PropertyModel.ReadableObjectPropertyKey<String> TITLE_KEY = + public static final PropertyModel.ReadableObjectPropertyKey<String> TITLE_KEY = new PropertyModel.ReadableObjectPropertyKey<>(); - static final PropertyModel.ReadableObjectPropertyKey<String> URL_KEY = + public static final PropertyModel.ReadableObjectPropertyKey<String> URL_KEY = new PropertyModel.ReadableObjectPropertyKey<>(); - static final PropertyModel.WritableObjectPropertyKey<Bitmap> ICON_KEY = + public static final PropertyModel.WritableObjectPropertyKey<Bitmap> ICON_KEY = new PropertyModel.WritableObjectPropertyKey<>(); static final PropertyModel.WritableBooleanPropertyKey BLACKLISTED_KEY = new PropertyModel.WritableBooleanPropertyKey();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/native_page/ContextMenuManager.java b/chrome/android/java/src/org/chromium/chrome/browser/native_page/ContextMenuManager.java index 61ce2cd7..c888c75 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/native_page/ContextMenuManager.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/native_page/ContextMenuManager.java
@@ -33,7 +33,8 @@ public class ContextMenuManager implements OnCloseContextMenuListener { @IntDef({ContextMenuItemId.OPEN_IN_NEW_WINDOW, ContextMenuItemId.OPEN_IN_NEW_TAB, ContextMenuItemId.OPEN_IN_INCOGNITO_TAB, ContextMenuItemId.SAVE_FOR_OFFLINE, - ContextMenuItemId.REMOVE, ContextMenuItemId.LEARN_MORE}) + ContextMenuItemId.ADD_TO_MY_APPS, ContextMenuItemId.REMOVE, + ContextMenuItemId.LEARN_MORE}) @Retention(RetentionPolicy.SOURCE) public @interface ContextMenuItemId { // The order of the items will be based on the value of their ID. So if new items are added, @@ -43,10 +44,11 @@ int OPEN_IN_NEW_TAB = 1; int OPEN_IN_INCOGNITO_TAB = 2; int SAVE_FOR_OFFLINE = 3; - int REMOVE = 4; - int LEARN_MORE = 5; + int ADD_TO_MY_APPS = 4; + int REMOVE = 5; + int LEARN_MORE = 6; - int NUM_ENTRIES = 6; + int NUM_ENTRIES = 7; } private final NativePageNavigationDelegate mNavigationDelegate; @@ -206,6 +208,8 @@ return true; case ContextMenuItemId.LEARN_MORE: return true; + case ContextMenuItemId.ADD_TO_MY_APPS: + return false; default: assert false; return false;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceManager.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceManager.java index 0313ce6..c1c97bfc 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceManager.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceManager.java
@@ -249,9 +249,9 @@ private static final String FAILURE_UPLOAD_SUFFIX = "_crash_failure_upload"; /** - * Whether or not Sole integration is enabled. - * Default value is true. + * Deprecated in M76. This value may still exist in the shared preferences file. Do not reuse. */ + @Deprecated public static final String SOLE_INTEGRATION_ENABLED_KEY = "sole_integration_enabled"; /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java b/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java index 67e707d..b435fca2 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java
@@ -72,7 +72,6 @@ private static Boolean sHasGoogleAccountAuthenticator; private static Boolean sHasRecognitionIntentHandler; - private static Boolean sIsSoleEnabled; private static Boolean sIsHomePageButtonForceEnabled; private static Boolean sIsHomepageTileEnabled; private static Boolean sIsNewTabPageButtonEnabled; @@ -194,7 +193,6 @@ * Caches flags that must take effect on startup but are set via native code. */ public static void cacheNativeFlags() { - cacheSoleEnabled(); cacheCommandLineOnNonRootedEnabled(); FirstRunUtils.cacheFirstRunPrefs(); cacheHomePageButtonForceEnabled(); @@ -540,33 +538,6 @@ } /** - * Cache whether or not Sole integration is enabled. - */ - public static void cacheSoleEnabled() { - boolean featureEnabled = ChromeFeatureList.isEnabled(ChromeFeatureList.SOLE_INTEGRATION); - ChromePreferenceManager prefManager = ChromePreferenceManager.getInstance(); - boolean prefEnabled = - prefManager.readBoolean(ChromePreferenceManager.SOLE_INTEGRATION_ENABLED_KEY, true); - if (featureEnabled == prefEnabled) return; - - prefManager.writeBoolean( - ChromePreferenceManager.SOLE_INTEGRATION_ENABLED_KEY, featureEnabled); - } - - /** - * @return Whether or not Sole integration is enabled. - */ - public static boolean isSoleEnabled() { - if (sIsSoleEnabled == null) { - ChromePreferenceManager prefManager = ChromePreferenceManager.getInstance(); - - sIsSoleEnabled = prefManager.readBoolean( - ChromePreferenceManager.SOLE_INTEGRATION_ENABLED_KEY, true); - } - return sIsSoleEnabled; - } - - /** * @param activityContext The context for the containing activity. * @return Whether contextual suggestions are enabled. */
diff --git a/chrome/android/touchless/java/res/drawable/ic_add_circle_outline_24dp.xml b/chrome/android/touchless/java/res/drawable/ic_add_circle_outline_24dp.xml new file mode 100644 index 0000000..305a233 --- /dev/null +++ b/chrome/android/touchless/java/res/drawable/ic_add_circle_outline_24dp.xml
@@ -0,0 +1,14 @@ +<!-- Copyright 2019 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + tools:targetApi="21"> + <path + android:fillColor="@color/default_icon_color" + android:pathData="M13,7h-2v4L7,11v2h4v4h2v-4h4v-2h-4L13,7zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/> +</vector>
diff --git a/chrome/android/java/res/layout/explore_sites_category_card_view_condensed.xml b/chrome/android/touchless/java/res/layout/touchless_explore_sites_category_card_view.xml similarity index 90% rename from chrome/android/java/res/layout/explore_sites_category_card_view_condensed.xml rename to chrome/android/touchless/java/res/layout/touchless_explore_sites_category_card_view.xml index a990cc9..45803dd 100644 --- a/chrome/android/java/res/layout/explore_sites_category_card_view_condensed.xml +++ b/chrome/android/touchless/java/res/layout/touchless_explore_sites_category_card_view.xml
@@ -3,7 +3,7 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> -<org.chromium.chrome.browser.explore_sites.ExploreSitesCategoryCardView +<org.chromium.chrome.browser.touchless.TouchlessExploreSitesCategoryCardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" @@ -35,4 +35,4 @@ app:minHorizontalSpacing="@dimen/explore_sites_category_grid_minimum_spacing" android:paddingTop="@dimen/explore_sites_category_title_spacing_condensed" /> -</org.chromium.chrome.browser.explore_sites.ExploreSitesCategoryCardView> +</org.chromium.chrome.browser.touchless.TouchlessExploreSitesCategoryCardView>
diff --git a/chrome/android/java/res/layout/explore_sites_tile_view_condensed.xml b/chrome/android/touchless/java/res/layout/touchless_explore_sites_tile_view_condensed.xml similarity index 100% rename from chrome/android/java/res/layout/explore_sites_tile_view_condensed.xml rename to chrome/android/touchless/java/res/layout/touchless_explore_sites_tile_view_condensed.xml
diff --git a/chrome/android/touchless/java/res/values-v17/dimens.xml b/chrome/android/touchless/java/res/values-v17/dimens.xml index 4095a91..d80f8f6 100644 --- a/chrome/android/touchless/java/res/values-v17/dimens.xml +++ b/chrome/android/touchless/java/res/values-v17/dimens.xml
@@ -54,4 +54,16 @@ the mocks don't take into account --> <dimen name="touchless_snippets_age_margin_bottom">10dp</dimen> + <dimen name="explore_sites_category_padding_vertical_condensed">12dp</dimen> + <dimen name="explore_sites_category_padding_horizontal_condensed">4dp</dimen> + <dimen name="explore_sites_category_grid_minimum_spacing">8dp</dimen> + <dimen name="explore_sites_category_title_spacing_condensed">10dp</dimen> + <dimen name="explore_sites_tile_view_icon_background_size_condensed">44dp</dimen> + <dimen name="explore_sites_tile_view_icon_size_condensed">22dp</dimen> + <dimen name="explore_sites_tile_view_icon_margin_top_condensed">18dp</dimen> + <dimen name="explore_sites_tile_view_title_margin_top_condensed">51dp</dimen> + <dimen name="explore_sites_tile_view_width_condensed">72dp</dimen> + <dimen name="explore_sites_tile_view_icon_padding_horizontal_condensed">4dp</dimen> + <dimen name="explore_sites_tile_view_text_margin_bottom_condensed">4dp</dimen> + </resources>
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessAddToHomescreenManager.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessAddToHomescreenManager.java new file mode 100644 index 0000000..6cd2923 --- /dev/null +++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessAddToHomescreenManager.java
@@ -0,0 +1,54 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.touchless; + +import android.app.Activity; +import android.graphics.Bitmap; + +import org.chromium.chrome.browser.ShortcutHelper; +import org.chromium.chrome.browser.webapps.AddToHomescreenDialog; + +/** + * Add to homescreen manager specifically for touchless devices. + */ +class TouchlessAddToHomescreenManager implements AddToHomescreenDialog.Delegate { + protected final Activity mActivity; + private final String mUrl; + private final String mTitle; + private final Bitmap mIconBitmap; + + protected AddToHomescreenDialog mDialog; + + public TouchlessAddToHomescreenManager( + Activity activity, String url, String title, Bitmap iconBitmap) { + mActivity = activity; + mUrl = url; + mTitle = title; + mIconBitmap = iconBitmap; + } + + // Starts the process of showing the dialog and adding the shortcut. + public void start() { + mDialog = new AddToHomescreenDialog(mActivity, this); + mDialog.show(); + mDialog.onUserTitleAvailable(mTitle, mUrl, false); + mDialog.onIconAvailable(mIconBitmap); + } + + @Override + public void addToHomescreen(String title) { + ShortcutHelper.addShortcut(mUrl, mUrl, title, mIconBitmap, false, 0); + } + + @Override + public void onNativeAppDetailsRequested() { + return; + } + + @Override + public void onDialogDismissed() { + mDialog = null; + } +}
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessCategoryCardViewHolderFactory.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessCategoryCardViewHolderFactory.java new file mode 100644 index 0000000..9eb387d --- /dev/null +++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessCategoryCardViewHolderFactory.java
@@ -0,0 +1,23 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.touchless; + +import org.chromium.chrome.browser.explore_sites.CategoryCardViewHolderFactory; +import org.chromium.chrome.touchless.R; + +/** + * Factory to provide resources for touchless devices. + */ +class TouchlessCategoryCardViewHolderFactory extends CategoryCardViewHolderFactory { + @Override + protected int getCategoryCardViewResource() { + return R.layout.touchless_explore_sites_category_card_view; + } + + @Override + protected int getTileViewResource() { + return R.layout.touchless_explore_sites_tile_view_condensed; + } +}
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessContextMenuManager.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessContextMenuManager.java index 8a205ebd..75f6ddd9 100644 --- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessContextMenuManager.java +++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessContextMenuManager.java
@@ -4,8 +4,11 @@ package org.chromium.chrome.browser.touchless; +import android.app.Activity; import android.content.Context; +import android.graphics.Bitmap; import android.support.annotation.DrawableRes; +import android.support.annotation.StringRes; import android.view.View; import android.view.View.OnClickListener; @@ -26,6 +29,26 @@ * ContextMenuManager and Delegate to select items to show and lookup their labels. */ public class TouchlessContextMenuManager extends ContextMenuManager { + /** + * Delegate for touchless-specific context menu items. + */ + public interface Delegate extends ContextMenuManager.Delegate { + /** + * @return Activity associated with this delegate. Used to display prompts. + */ + Activity getActivity(); + + /** + * @return Title associated with this delegate. + */ + String getTitle(); + + /** + * @return Icon associated with this delegate. Used to populate touchless shortcuts. + */ + Bitmap getIconBitmap(); + } + private PropertyModel mTouchlessMenuModel; private ModalDialogManager mModalDialogManager; @@ -43,8 +66,8 @@ * @param delegate Delegate defines filter for displayed menu items and behavior for selects * item. */ - public void showTouchlessContextMenu( - ModalDialogManager modalDialogManager, Context context, Delegate delegate) { + public void showTouchlessContextMenu(ModalDialogManager modalDialogManager, Context context, + ContextMenuManager.Delegate delegate) { ArrayList<PropertyModel> menuItems = new ArrayList(); for (@ContextMenuItemId int itemId = 0; itemId < ContextMenuItemId.NUM_ENTRIES; itemId++) { if (!shouldShowItem(itemId, delegate)) continue; @@ -68,7 +91,32 @@ } @Override - protected boolean shouldShowItem(@ContextMenuItemId int itemId, Delegate delegate) { + protected @StringRes int getResourceIdForMenuItem(@ContextMenuItemId int id) { + if (id == ContextMenuItemId.ADD_TO_MY_APPS) { + return R.string.menu_add_to_apps; + } + + return super.getResourceIdForMenuItem(id); + } + + @Override + protected boolean handleMenuItemClick( + @ContextMenuItemId int itemId, ContextMenuManager.Delegate delegate) { + if (itemId == ContextMenuItemId.ADD_TO_MY_APPS) { + Delegate touchlessDelegate = (Delegate) delegate; + TouchlessAddToHomescreenManager touchlessAddToHomescreenManager = + new TouchlessAddToHomescreenManager(touchlessDelegate.getActivity(), + touchlessDelegate.getUrl(), touchlessDelegate.getTitle(), + touchlessDelegate.getIconBitmap()); + touchlessAddToHomescreenManager.start(); + return false; + } + return super.handleMenuItemClick(itemId, delegate); + } + + @Override + protected boolean shouldShowItem( + @ContextMenuItemId int itemId, ContextMenuManager.Delegate delegate) { // Here we filter out any item IDs that don't make sense in touchless. switch (itemId) { case ContextMenuItemId.REMOVE: @@ -83,6 +131,8 @@ // fall through case ContextMenuItemId.OPEN_IN_NEW_WINDOW: return false; + case ContextMenuItemId.ADD_TO_MY_APPS: + return delegate.isItemSupported(itemId); } assert false : "Encountered unexpected touchless context menu item type"; @@ -139,6 +189,8 @@ return R.drawable.ic_remove_circle_outline_24dp; case ContextMenuItemId.LEARN_MORE: return R.drawable.ic_help_outline_24dp; + case ContextMenuItemId.ADD_TO_MY_APPS: + return R.drawable.ic_add_circle_outline_24dp; default: return 0; } @@ -149,10 +201,11 @@ } private class TouchlessItemClickListener implements OnClickListener { - private final Delegate mDelegate; + private final ContextMenuManager.Delegate mDelegate; private final @ContextMenuItemId int mItemId; - public TouchlessItemClickListener(Delegate delegate, @ContextMenuItemId int itemId) { + public TouchlessItemClickListener( + ContextMenuManager.Delegate delegate, @ContextMenuItemId int itemId) { mDelegate = delegate; mItemId = itemId; }
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessExploreSitesCategoryCardView.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessExploreSitesCategoryCardView.java new file mode 100644 index 0000000..a07af8ee --- /dev/null +++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessExploreSitesCategoryCardView.java
@@ -0,0 +1,78 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.chrome.browser.touchless; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Bitmap; +import android.util.AttributeSet; + +import org.chromium.chrome.browser.ChromeActivity; +import org.chromium.chrome.browser.explore_sites.ExploreSitesCategoryCardView; +import org.chromium.chrome.browser.explore_sites.ExploreSitesSite; +import org.chromium.chrome.browser.native_page.ContextMenuManager; +import org.chromium.ui.modelutil.PropertyModel; + +class TouchlessExploreSitesCategoryCardView extends ExploreSitesCategoryCardView { + public TouchlessExploreSitesCategoryCardView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + protected class TouchlessExploreSitesSiteViewBinder + extends ExploreSitesCategoryCardView.ExploreSitesSiteViewBinder { + Activity mActivity; + public TouchlessExploreSitesSiteViewBinder(Activity activity) { + mActivity = activity; + } + + @Override + protected ExploreSitesCategoryCardView.CategoryCardInteractionDelegate + createInteractionDelegate(PropertyModel model) { + return new TouchlessCategoryCardInteractionDelegate((ChromeActivity) mActivity, model); + } + } + + /** + * Delegate that's aware of fields necessary for the touchless context menu. + */ + protected class TouchlessCategoryCardInteractionDelegate + extends ExploreSitesCategoryCardView.CategoryCardInteractionDelegate + implements TouchlessContextMenuManager.Delegate { + private ChromeActivity mActivity; + private PropertyModel mModel; + + TouchlessCategoryCardInteractionDelegate(ChromeActivity activity, PropertyModel model) { + super(model.get(ExploreSitesSite.URL_KEY), model.get(ExploreSitesSite.TILE_INDEX_KEY)); + mActivity = activity; + mModel = model; + } + + @Override + public Activity getActivity() { + return mActivity; + } + + @Override + public String getTitle() { + return mModel.get(ExploreSitesSite.TITLE_KEY); + } + + @Override + public Bitmap getIconBitmap() { + return mModel.get(ExploreSitesSite.ICON_KEY); + } + + @Override + public boolean isItemSupported(int menuItemId) { + return menuItemId == ContextMenuManager.ContextMenuItemId.ADD_TO_MY_APPS + || menuItemId == ContextMenuManager.ContextMenuItemId.REMOVE; + } + } + + @Override + protected ExploreSitesSiteViewBinder createViewBinder(Activity activity) { + return new TouchlessExploreSitesSiteViewBinder(activity); + } +}
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessExploreSitesPage.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessExploreSitesPage.java index 4205fad..23d398ba 100644 --- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessExploreSitesPage.java +++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessExploreSitesPage.java
@@ -8,6 +8,7 @@ import android.view.View; import org.chromium.chrome.browser.ChromeActivity; +import org.chromium.chrome.browser.explore_sites.CategoryCardViewHolderFactory; import org.chromium.chrome.browser.explore_sites.ExploreSitesPage; import org.chromium.chrome.browser.native_page.ContextMenuManager; import org.chromium.chrome.browser.native_page.NativePageHost; @@ -49,4 +50,9 @@ mTouchlessContextMenuManager.showTouchlessContextMenu( mModalDialogManager, mContext, delegate); } + + @Override + protected CategoryCardViewHolderFactory createCategoryCardViewHolderFactory() { + return new TouchlessCategoryCardViewHolderFactory(); + } }
diff --git a/chrome/android/touchless/java/strings/touchless_strings.grd b/chrome/android/touchless/java/strings/touchless_strings.grd index 1048e0c..6e25b9f 100644 --- a/chrome/android/touchless/java/strings/touchless_strings.grd +++ b/chrome/android/touchless/java/strings/touchless_strings.grd
@@ -131,6 +131,10 @@ <message name="IDS_MORE_ARTICLES" desc="Message at the bottom of a list of news items prompting the user to load more."> More articles </message> + <!-- TODO(crbug.com/957789): Remove this from downstream. --> + <message name="IDS_MENU_ADD_TO_APPS" desc="Text to accompany icon that will navigate to a page showing a categorized view of different applications or sites"> + Add to My apps + </message> </messages> </release> </grit>
diff --git a/chrome/android/touchless/touchless_java_sources.gni b/chrome/android/touchless/touchless_java_sources.gni index 6d92469..55a84d4 100644 --- a/chrome/android/touchless/touchless_java_sources.gni +++ b/chrome/android/touchless/touchless_java_sources.gni
@@ -24,10 +24,13 @@ "touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsRecyclerView.java", "touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsTileView.java", "touchless/java/src/org/chromium/chrome/browser/touchless/SiteSuggestionsViewHolderFactory.java", + "touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessAddToHomescreenManager.java", "touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessActionItemViewHolder.java", "touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessArticleViewHolder.java", + "touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessCategoryCardViewHolderFactory.java", "touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessContextMenuManager.java", "touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessDelegate.java", + "touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessExploreSitesCategoryCardView.java", "touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessExploreSitesPage.java", "touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessLayoutManager.java", "touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessNewTabPage.java",
diff --git a/chrome/app/DEPS b/chrome/app/DEPS index 5d04067..d15df98 100644 --- a/chrome/app/DEPS +++ b/chrome/app/DEPS
@@ -44,7 +44,6 @@ specific_include_rules = { "chrome_content_browser_overlay_manifest\.cc": [ - "+ash/components/shortcut_viewer/public", "+chrome/services/app_service", "+chromeos/assistant", "+chromeos/services/assistant", @@ -83,7 +82,6 @@ "+third_party/blink/public/mojom", ], "chrome_packaged_service_manifests\.cc": [ - "+ash/components/shortcut_viewer/public", "+chrome/services/cups_ipp_parser/public", "+chrome/services/cups_proxy/public", "+chrome/services/file_util/public",
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp index 4f9c504..f0ebb635 100644 --- a/chrome/app/settings_strings.grdp +++ b/chrome/app/settings_strings.grdp
@@ -2150,9 +2150,6 @@ <message name="IDS_ONC_CELLULAR_APN_AUTHENTICATION" desc="ONC Property label for APN-Authentication"> Authentication </message> - <message name="IDS_ONC_CELLULAR_CARRIER" desc="ONC Property label for Cellular.Carrier"> - Carrier - </message> <message name="IDS_ONC_CELLULAR_FAMILY" desc="ONC Property label for Cellular.Family"> Family </message>
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn index 7bb4368..c142049a 100644 --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn
@@ -840,6 +840,8 @@ "metrics/thread_watcher_android.h", "metrics/thread_watcher_report_hang.cc", "metrics/thread_watcher_report_hang.h", + "metrics/ukm_background_recorder_service.cc", + "metrics/ukm_background_recorder_service.h", "metrics/variations/chrome_variations_service_client.cc", "metrics/variations/chrome_variations_service_client.h", "native_window_notification_source.h", @@ -1106,10 +1108,12 @@ "performance_manager/performance_manager_tab_helper.cc", "performance_manager/performance_manager_tab_helper.h", "performance_manager/public/graph/node_type.h", + "performance_manager/public/web_contents_proxy.h", "performance_manager/render_process_user_data.cc", "performance_manager/render_process_user_data.h", "performance_manager/web_contents_proxy.cc", - "performance_manager/web_contents_proxy.h", + "performance_manager/web_contents_proxy_impl.cc", + "performance_manager/web_contents_proxy_impl.h", "performance_manager/webui_graph_dump_impl.cc", "performance_manager/webui_graph_dump_impl.h", "performance_monitor/metric_evaluator_helper_win.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index b22bdc0..9e02d30 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc
@@ -1658,19 +1658,9 @@ flag_descriptions::kEnableDataReductionProxyServerExperimentDescription, kOsAll, MULTI_VALUE_TYPE(kDataReductionProxyServerExperiment)}, #if defined(OS_ANDROID) - {"enable-data-reduction-proxy-savings-promo", - flag_descriptions::kEnableDataReductionProxySavingsPromoName, - flag_descriptions::kEnableDataReductionProxySavingsPromoDescription, - kOsAndroid, - SINGLE_VALUE_TYPE(data_reduction_proxy::switches:: - kEnableDataReductionProxySavingsPromo)}, {"enable-offline-previews", flag_descriptions::kEnableOfflinePreviewsName, flag_descriptions::kEnableOfflinePreviewsDescription, kOsAndroid, FEATURE_VALUE_TYPE(previews::features::kOfflinePreviews)}, - {"enable-previews-android-omnibox-ui", - flag_descriptions::kEnablePreviewsAndroidOmniboxUIName, - flag_descriptions::kEnablePreviewsAndroidOmniboxUIDescription, kOsAndroid, - FEATURE_VALUE_TYPE(previews::features::kAndroidOmniboxPreviewsBadge)}, {"enable-lite-page-server-previews", flag_descriptions::kEnableLitePageServerPreviewsName, flag_descriptions::kEnableLitePageServerPreviewsDescription, kOsAndroid, @@ -1681,19 +1671,12 @@ FEATURE_VALUE_TYPE( previews::features::kHTTPSServerPreviewsUsingURLLoader)}, #endif // OS_ANDROID - {"enable-lite-mode-rebrand", - flag_descriptions::kEnableDataSaverLiteModeRebrandName, - flag_descriptions::kEnableDataSaverLiteModeRebrandDescription, kOsAll, - FEATURE_VALUE_TYPE(previews::features::kDataSaverLiteModeRebranding)}, #if defined(OS_CHROMEOS) || defined(OS_LINUX) {"enable-save-data", flag_descriptions::kEnableSaveDataName, flag_descriptions::kEnableSaveDataDescription, kOsCrOS, SINGLE_VALUE_TYPE( data_reduction_proxy::switches::kEnableDataReductionProxy)}, #endif // OS_CHROMEOS - {"enable-client-lo-fi", flag_descriptions::kEnableClientLoFiName, - flag_descriptions::kEnableClientLoFiDescription, kOsAll, - FEATURE_VALUE_TYPE(previews::features::kClientLoFi)}, {"enable-noscript-previews", flag_descriptions::kEnableNoScriptPreviewsName, flag_descriptions::kEnableNoScriptPreviewsDescription, kOsAll, FEATURE_VALUE_TYPE(previews::features::kNoScriptPreviews)}, @@ -1701,10 +1684,6 @@ flag_descriptions::kEnableResourceLoadingHintsName, flag_descriptions::kEnableResourceLoadingHintsDescription, kOsAll, FEATURE_VALUE_TYPE(previews::features::kResourceLoadingHints)}, - {"enable-optimization-hints", - flag_descriptions::kEnableOptimizationHintsName, - flag_descriptions::kEnableOptimizationHintsDescription, kOsAll, - FEATURE_VALUE_TYPE(previews::features::kOptimizationHints)}, {"allow-insecure-localhost", flag_descriptions::kAllowInsecureLocalhostName, flag_descriptions::kAllowInsecureLocalhostDescription, kOsAll, SINGLE_VALUE_TYPE(switches::kAllowInsecureLocalhost)}, @@ -2919,12 +2898,6 @@ kOsDesktop, FEATURE_VALUE_TYPE(device::kWebAuthPINSupport)}, #endif // !defined(OS_ANDROID) -#if defined(OS_ANDROID) - {"enable-sole-integration", flag_descriptions::kSoleIntegrationName, - flag_descriptions::kSoleIntegrationDescription, kOsAndroid, - FEATURE_VALUE_TYPE(chrome::android::kSoleIntegration)}, -#endif // defined(OS_ANDROID) - {"enable-viz-display-compositor", flag_descriptions::kVizDisplayCompositorName, flag_descriptions::kVizDisplayCompositorDescription, kOsAndroid | kOsCrOS,
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc index e822721..0c182e4 100644 --- a/chrome/browser/android/chrome_feature_list.cc +++ b/chrome/browser/android/chrome_feature_list.cc
@@ -164,7 +164,6 @@ &kServiceManagerForBackgroundPrefetch, &kServiceManagerForDownload, &kShoppingAssist, - &kSoleIntegration, &kSpannableInlineAutocomplete, &kSpecialLocaleWrapper, &kSpecialUserDecision, @@ -481,9 +480,6 @@ const base::Feature kShoppingAssist{"ShoppingAssist", base::FEATURE_DISABLED_BY_DEFAULT}; -const base::Feature kSoleIntegration{"SoleIntegration", - base::FEATURE_ENABLED_BY_DEFAULT}; - const base::Feature kSpannableInlineAutocomplete{ "SpannableInlineAutocomplete", base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/chrome/browser/android/chrome_feature_list.h b/chrome/browser/android/chrome_feature_list.h index 0d6c4d1..d7bcad4 100644 --- a/chrome/browser/android/chrome_feature_list.h +++ b/chrome/browser/android/chrome_feature_list.h
@@ -95,7 +95,6 @@ extern const base::Feature kServiceManagerForBackgroundPrefetch; extern const base::Feature kServiceManagerForDownload; extern const base::Feature kShoppingAssist; -extern const base::Feature kSoleIntegration; extern const base::Feature kSpannableInlineAutocomplete; extern const base::Feature kSpecialLocaleWrapper; extern const base::Feature kSpecialUserDecision;
diff --git a/chrome/browser/android/vr/arcore_device/arcore_gl.cc b/chrome/browser/android/vr/arcore_device/arcore_gl.cc index df4160a..c6359f19 100644 --- a/chrome/browser/android/vr/arcore_device/arcore_gl.cc +++ b/chrome/browser/android/vr/arcore_device/arcore_gl.cc
@@ -291,7 +291,9 @@ } // Check if the frame_size and display_rotation updated last frame. If yes, - // apply the update for this frame. + // apply the update for this frame. In the current implementation, this should + // only happen once per session since we don't support mid-session rotation or + // resize. if (should_recalculate_uvs_) { // Get the UV transform matrix from ArCore's UV transform. std::vector<float> uvs_transformed = @@ -310,6 +312,11 @@ float bottom = depth_near * (m.get(2, 1) - 1.f) / m.get(1, 1); float top = depth_near * (m.get(2, 1) + 1.f) / m.get(1, 1); + // Also calculate the inverse projection which is needed for converting + // screen touches to world rays. + bool has_inverse = projection_.GetInverse(&inverse_projection_); + DCHECK(has_inverse); + // VRFieldOfView wants positive angles. mojom::VRFieldOfViewPtr field_of_view = mojom::VRFieldOfView::New(); field_of_view->leftDegrees = gfx::RadToDeg(atanf(-left / depth_near)); @@ -653,8 +660,57 @@ // Controller doesn't have a measured position. state->description->emulated_position = true; - // TODO(klausw): fill in state->grip and state->description->pointer_offset - // by unprojecting the screen coordinates into a world space ray. + // The Renderer code ignores state->grip for TAPPING (screen-based) target ray + // mode, so we don't bother filling it in here. If this does get used at + // some point in the future, this should be set to the inverse of the + // pose rigid transform. + + // Get a viewer-space ray from screen-space coordinates by applying the + // inverse of the projection matrix. Z coordinate of -1 means the point will + // be projected onto the projection matrix near plane. See also + // third_party/blink/renderer/modules/xr/xr_view.cc's UnprojectPointer. + gfx::Point3F touch_point( + screen_last_touch_.x() / transfer_size_.width() * 2.f - 1.f, + (1.f - screen_last_touch_.y() / transfer_size_.height()) * 2.f - 1.f, + -1.f); + DVLOG(3) << __func__ << ": touch_point=" << touch_point.ToString(); + inverse_projection_.TransformPoint(&touch_point); + DVLOG(3) << __func__ << ": unprojected=" << touch_point.ToString(); + + // Ray points along -Z in ray space, so we need to flip it to get + // the +Z axis unit vector. + gfx::Vector3dF ray_backwards(-touch_point.x(), -touch_point.y(), + -touch_point.z()); + gfx::Vector3dF new_z; + bool can_normalize = ray_backwards.GetNormalized(&new_z); + DCHECK(can_normalize); + + // Complete the ray-space basis by adding X and Y unit + // vectors based on cross products. + const gfx::Vector3dF kUp(0.f, 1.f, 0.f); + gfx::Vector3dF new_x(kUp); + new_x.Cross(new_z); + new_x.GetNormalized(&new_x); + gfx::Vector3dF new_y(new_z); + new_y.Cross(new_x); + new_y.GetNormalized(&new_y); + + // Fill in the transform matrix in column-major order. The first three columns + // contain the basis vectors, the fourth column the position offset. + gfx::Transform from_ray_space( + new_x.x(), new_x.y(), new_x.z(), 0, // X basis vector + new_y.x(), new_y.y(), new_y.z(), 0, // Y basis vector + new_z.x(), new_z.y(), new_z.z(), 0, // Z basis vector + touch_point.x(), touch_point.y(), touch_point.z(), 1); + DVLOG(3) << __func__ << ": from_ray_space=" << from_ray_space.ToString(); + + // We now have a transform from ray space to viewer space, but the mojo + // matrices go in the opposite direction, in this case it expects a transform + // from grip matrix (== viewer space) to ray space, so we need to invert it. + gfx::Transform to_ray_space; + bool can_invert = from_ray_space.GetInverse(&to_ray_space); + state->description->pointer_offset = to_ray_space; + DCHECK(can_invert); return state; }
diff --git a/chrome/browser/android/vr/arcore_device/arcore_gl.h b/chrome/browser/android/vr/arcore_device/arcore_gl.h index 5a3756c1..1735b49 100644 --- a/chrome/browser/android/vr/arcore_device/arcore_gl.h +++ b/chrome/browser/android/vr/arcore_device/arcore_gl.h
@@ -165,6 +165,7 @@ gfx::Transform uv_transform_; gfx::Transform webxr_transform_; gfx::Transform projection_; + gfx::Transform inverse_projection_; // The first run of ProduceFrame should set uv_transform_ and projection_ // using the default settings in ArCore. bool should_recalculate_uvs_ = true;
diff --git a/chrome/browser/chrome_browser_main_mac.mm b/chrome/browser/chrome_browser_main_mac.mm index c78343d0..e1c6ba5 100644 --- a/chrome/browser/chrome_browser_main_mac.mm +++ b/chrome/browser/chrome_browser_main_mac.mm
@@ -18,7 +18,7 @@ #include "base/mac/mac_util.h" #include "base/mac/scoped_nsobject.h" #include "base/mac/sdk_forward_declarations.h" -#include "base/metrics/histogram_macros.h" +#include "base/metrics/histogram_functions.h" #include "base/optional.h" #include "base/path_service.h" #include "base/task/post_task.h" @@ -36,6 +36,7 @@ #include "chrome/browser/ui/cocoa/main_menu_builder.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_switches.h" +#include "chrome/common/mac/staging_watcher.h" #include "chrome/grit/chromium_strings.h" #include "components/crash/content/app/crashpad.h" #include "components/metrics/metrics_service.h" @@ -71,7 +72,8 @@ base::BindOnce(&EnsureMetadataNeverIndexFileOnFileThread, user_data_dir)); } -// Used for UMA; never alter existing values. +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. enum class FilesystemType { kUnknown, kOther, @@ -157,20 +159,21 @@ if (success) filesystem_type = FilesystemStringToType(is_ro_dmg, filesystem_type_string); - UMA_HISTOGRAM_ENUMERATION("OSX.InstallationFilesystem", filesystem_type); + base::UmaHistogramEnumeration("OSX.InstallationFilesystem", filesystem_type); } void RecordInstanceStats() { upgrade_util::ThisAndOtherUserCounts counts = upgrade_util::GetCountOfOtherInstancesOfThisBinary(); - UMA_HISTOGRAM_COUNTS_100("OSX.OtherInstances.ThisUser", - counts.this_user_count); - UMA_HISTOGRAM_COUNTS_100("OSX.OtherInstances.OtherUser", - counts.other_user_count); + base::UmaHistogramCounts100("OSX.OtherInstances.ThisUser", + counts.this_user_count); + base::UmaHistogramCounts100("OSX.OtherInstances.OtherUser", + counts.other_user_count); } -// Used for UMA; never alter existing values. +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. enum class FastUserSwitchEvent { kUserDidBecomeActiveEvent, kUserDidBecomeInactiveEvent, @@ -178,7 +181,7 @@ }; void LogFastUserSwitchStat(FastUserSwitchEvent event) { - UMA_HISTOGRAM_ENUMERATION("OSX.FastUserSwitch", event); + base::UmaHistogramEnumeration("OSX.FastUserSwitch", event); } void InstallFastUserSwitchStatRecorder() { @@ -234,7 +237,8 @@ cr_fsid->val[1] == buf.f_fsid.val[1]; } -// Used for UMA; never alter existing values. +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. enum class StagingDirectoryStep { kFailedToFindDirectory, kItemReplacementDirectory, @@ -247,7 +251,7 @@ }; void LogStagingDirectoryLocation(StagingDirectoryStep step) { - UMA_HISTOGRAM_ENUMERATION("OSX.StagingDirectoryLocation2", step); + base::UmaHistogramEnumeration("OSX.StagingDirectoryLocation2", step); } void RecordStagingDirectoryStats() { @@ -357,6 +361,31 @@ RecordStagingDirectoryStats(); } +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. +enum class StartupUpdateState { + kUpdateKeyNotSet, + kUpdateKeySetAndStagedCopyPresent, + kUpdateKeySetAndStagedCopyNotPresent, + kMaxValue = kUpdateKeySetAndStagedCopyNotPresent, +}; + +// Records about the state of Chrome updates. This is pre-emptory data +// gathering to make sure that a situation that the team thinks will be OK is +// actually OK in the field. +void RecordUpdateState() { + StartupUpdateState state = StartupUpdateState::kUpdateKeyNotSet; + NSString* staging_location = [CrStagingKeyWatcher stagingLocation]; + if (staging_location) { + if ([[NSFileManager defaultManager] fileExistsAtPath:staging_location]) + state = StartupUpdateState::kUpdateKeySetAndStagedCopyPresent; + else + state = StartupUpdateState::kUpdateKeySetAndStagedCopyNotPresent; + } + + base::UmaHistogramEnumeration("OSX.StartupUpdateState", state); +} + } // namespace // ChromeBrowserMainPartsMac --------------------------------------------------- @@ -442,6 +471,8 @@ ChromeBrowserMainPartsPosix::PostMainMessageLoopStart(); RecordInstallationStats(); + + RecordUpdateState(); } void ChromeBrowserMainPartsMac::PreProfileInit() {
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc index c3dacf4..a32b40f 100644 --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc
@@ -5700,6 +5700,7 @@ DCHECK(!previews_user_data->cache_control_no_transform_directive() || !previews::HasEnabledPreviews(committed_state)); + // TODO(robertogden): Consider moving this to after the holdback logic. previews_user_data->set_committed_previews_state(committed_state); previews::PreviewsType committed_type =
diff --git a/chrome/browser/chromeos/accessibility/spoken_feedback_app_list_browsertest.cc b/chrome/browser/chromeos/accessibility/spoken_feedback_app_list_browsertest.cc index 74e73dc..a076b49 100644 --- a/chrome/browser/chromeos/accessibility/spoken_feedback_app_list_browsertest.cc +++ b/chrome/browser/chromeos/accessibility/spoken_feedback_app_list_browsertest.cc
@@ -129,6 +129,9 @@ // Check that Launcher, partial view state is announced. EXPECT_EQ("Launcher, partial view", speech_monitor_.GetNextUtterance()); + // Send a key press to enable keyboard traversal + SendKeyPressWithSearchAndShift(ui::VKEY_TAB); + // Move focus to expand all apps button; SendKeyPressWithSearchAndShift(ui::VKEY_TAB); EXPECT_EQ("Expand to all apps", speech_monitor_.GetNextUtterance()); @@ -164,6 +167,9 @@ while (speech_monitor_.GetNextUtterance() != "Launcher, partial view") { } + // Send a key press to enable keyboard traversal + SendKeyPressWithSearchAndShift(ui::VKEY_TAB); + // Move focus to expand all apps button. SendKeyPressWithSearchAndShift(ui::VKEY_TAB); while (speech_monitor_.GetNextUtterance() != @@ -263,6 +269,9 @@ while (speech_monitor_.GetNextUtterance() != "Launcher, partial view") { } + // Send a key press to enable keyboard traversal + SendKeyPressWithSearchAndShift(ui::VKEY_TAB); + // Move focus to expand all apps button. SendKeyPressWithSearchAndShift(ui::VKEY_TAB); while (speech_monitor_.GetNextUtterance() !=
diff --git a/chrome/browser/chromeos/input_method/input_method_engine_browsertests.cc b/chrome/browser/chromeos/input_method/input_method_engine_browsertests.cc index 37fa4c4..8f5c73f5 100644 --- a/chrome/browser/chromeos/input_method/input_method_engine_browsertests.cc +++ b/chrome/browser/chromeos/input_method/input_method_engine_browsertests.cc
@@ -10,7 +10,6 @@ #include "base/stl_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" -#include "base/test/scoped_feature_list.h" #include "chrome/browser/extensions/extension_browsertest.h" #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h" #include "chrome/browser/ui/browser_window.h" @@ -35,7 +34,6 @@ #include "ui/base/ime/ime_engine_handler_interface.h" #include "ui/base/ime/mock_ime_input_context_handler.h" #include "ui/base/ime/text_input_flags.h" -#include "ui/base/ui_base_features.h" #include "ui/chromeos/ime/input_method_menu_item.h" #include "ui/chromeos/ime/input_method_menu_manager.h" #include "ui/events/event.h" @@ -59,7 +57,6 @@ kTestTypeNormal = 0, kTestTypeIncognito = 1, kTestTypeComponent = 2, - kTestTypeMojoImf = 3, }; class InputMethodEngineBrowserTest @@ -69,14 +66,6 @@ InputMethodEngineBrowserTest() : extensions::ExtensionBrowserTest() {} virtual ~InputMethodEngineBrowserTest() {} - void SetUpInProcessBrowserTestFixture() override { - if (GetParam() == kTestTypeMojoImf) { - scoped_feature_list_.InitWithFeatures( - {features::kSingleProcessMash, features::kMojoIMF}, {}); - } - extensions::ExtensionBrowserTest::SetUpInProcessBrowserTestFixture(); - } - void TearDownInProcessBrowserTestFixture() override { extension_ = NULL; } ui::IMEEngineHandlerInterface::InputContext CreateInputContextWithInputType( @@ -122,7 +111,6 @@ const std::string& extension_name, TestType type) { switch (type) { case kTestTypeNormal: - case kTestTypeMojoImf: return LoadExtension(test_data_dir_.AppendASCII(extension_name)); case kTestTypeIncognito: return LoadExtensionIncognito( @@ -136,7 +124,6 @@ } const extensions::Extension* extension_; - base::test::ScopedFeatureList scoped_feature_list_; private: DISALLOW_COPY_AND_ASSIGN(InputMethodEngineBrowserTest); @@ -195,9 +182,6 @@ INSTANTIATE_TEST_SUITE_P(InputMethodEngineComponentExtensionBrowserTest, InputMethodEngineBrowserTest, ::testing::Values(kTestTypeComponent)); -INSTANTIATE_TEST_SUITE_P(MojoImfBrowserTest, - InputMethodEngineBrowserTest, - ::testing::Values(kTestTypeMojoImf)); IN_PROC_BROWSER_TEST_P(InputMethodEngineBrowserTest, BasicScenarioTest) { @@ -284,14 +268,10 @@ IN_PROC_BROWSER_TEST_P(InputMethodEngineBrowserTest, APIArgumentTest) { - // This test doesn't support Mojo-based IMF. // TODO(crbug.com/956825): Makes real end to end test without mocking the // input context handler. The test should mock the TextInputClient instance // hooked up with InputMethodChromeOS, or even using the real TextInputClient // if possible. - if (features::IsMojoImfEnabled()) - return; - LoadTestInputMethod(); InputMethodManager::Get()->GetActiveIMEState()->ChangeInputMethod(
diff --git a/chrome/browser/chromeos/login/existing_user_controller_browsertest.cc b/chrome/browser/chromeos/login/existing_user_controller_browsertest.cc index cb099cf..67e2a9e 100644 --- a/chrome/browser/chromeos/login/existing_user_controller_browsertest.cc +++ b/chrome/browser/chromeos/login/existing_user_controller_browsertest.cc
@@ -41,6 +41,7 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h" #include "chrome/browser/ui/webui/chromeos/login/terms_of_service_screen_handler.h" #include "chrome/grit/generated_resources.h" #include "chrome/test/base/testing_browser_process.h" @@ -270,7 +271,7 @@ session_manager_test_api.InjectStubUserContext(user_context); EXPECT_CALL(*mock_login_display_, SetUIEnabled(true)).Times(1); EXPECT_CALL(*mock_login_display_host_, - StartWizard(OobeScreenId(TermsOfServiceScreenView::kScreenId))) + StartWizard(TermsOfServiceScreenView::kScreenId.AsId())) .Times(0); content::WindowedNotificationObserver profile_prepared_observer( @@ -1145,7 +1146,7 @@ CryptohomeMissing) { SetUpStubAuthenticatorAndAttemptLogin(AuthFailure::MISSING_CRYPTOHOME); - OobeScreenWaiter(OobeScreen::SCREEN_GAIA_SIGNIN).Wait(); + OobeScreenWaiter(GaiaView::kScreenId).Wait(); const user_manager::User* user = user_manager::UserManager::Get()->FindUser(test_user_.account_id); ASSERT_TRUE(user);
diff --git a/chrome/browser/chromeos/login/screens/welcome_screen.h b/chrome/browser/chromeos/login/screens/welcome_screen.h index d682b57..bec3e72 100644 --- a/chrome/browser/chromeos/login/screens/welcome_screen.h +++ b/chrome/browser/chromeos/login/screens/welcome_screen.h
@@ -70,15 +70,16 @@ void AddObserver(Observer* observer); void RemoveObserver(Observer* observer); + // BaseScreen implementation: + void Show() override; + void Hide() override; + void OnUserAction(const std::string& action_id) override; + protected: // Exposes exit callback to test overrides. base::RepeatingClosure* exit_callback() { return &exit_callback_; } private: - // BaseScreen implementation: - void Show() override; - void Hide() override; - void OnUserAction(const std::string& action_id) override; // InputMethodManager::Observer implementation: void InputMethodChanged(input_method::InputMethodManager* manager,
diff --git a/chrome/browser/chromeos/login/screens/welcome_screen_browsertest.cc b/chrome/browser/chromeos/login/screens/welcome_screen_browsertest.cc new file mode 100644 index 0000000..73e06de --- /dev/null +++ b/chrome/browser/chromeos/login/screens/welcome_screen_browsertest.cc
@@ -0,0 +1,281 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/browser_process.h" +#include "chrome/browser/chromeos/accessibility/accessibility_manager.h" +#include "chrome/browser/chromeos/accessibility/magnification_manager.h" +#include "chrome/browser/chromeos/login/login_wizard.h" +#include "chrome/browser/chromeos/login/screens/welcome_screen.h" +#include "chrome/browser/chromeos/login/test/js_checker.h" +#include "chrome/browser/chromeos/login/test/oobe_base_test.h" +#include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h" +#include "chrome/browser/chromeos/login/ui/login_display_host.h" +#include "chrome/browser/chromeos/login/wizard_controller.h" +#include "chrome/browser/ui/webui/chromeos/login/oobe_ui.h" +#include "chrome/browser/ui/webui/chromeos/login/welcome_screen_handler.h" + +namespace chromeos { + +namespace { + +chromeos::OobeUI* GetOobeUI() { + auto* host = chromeos::LoginDisplayHost::default_host(); + return host ? host->GetOobeUI() : nullptr; +} + +void ToggleAccessibilityFeature(const std::string& feature_name, + bool new_value) { + test::JSChecker js = test::OobeJS(); + std::string feature_toggle = + test::GetOobeElementPath({"connect", feature_name, "button"}) + + ".checked"; + js.set_polymer_ui(false); + + if (!new_value) + feature_toggle = "!" + feature_toggle; + + js.ExpectVisiblePath({"connect", feature_name, "button"}); + EXPECT_FALSE(js.GetBool(feature_toggle)); + js.TapOnPath({"connect", feature_name, "button"}); + js.CreateWaiter(feature_toggle); +} + +} // namespace + +class WelcomeScreenBrowserTest : public InProcessBrowserTest { + public: + WelcomeScreenBrowserTest() = default; + ~WelcomeScreenBrowserTest() override = default; + + // InProcessBrowserTest: + void SetUpOnMainThread() override { + ShowLoginWizard(OobeScreen::SCREEN_TEST_NO_WINDOW); + + WizardController::default_controller() + ->screen_manager() + ->DeleteScreenForTesting(WelcomeView::kScreenId); + auto welcome_screen = std::make_unique<WelcomeScreen>( + GetOobeUI()->GetView<WelcomeScreenHandler>(), + base::BindRepeating(&WelcomeScreenBrowserTest::OnWelcomeScreenExit, + base::Unretained(this))); + welcome_screen_ = welcome_screen.get(); + WizardController::default_controller() + ->screen_manager() + ->SetScreenForTesting(std::move(welcome_screen)); + InProcessBrowserTest::SetUpOnMainThread(); + } + + void WaitForScreenExit() { + if (screen_exit_) + return; + base::RunLoop run_loop; + screen_exit_callback_ = run_loop.QuitClosure(); + run_loop.Run(); + } + + void OnWelcomeScreenExit() { + screen_exit_ = true; + if (screen_exit_callback_) { + std::move(screen_exit_callback_).Run(); + } + } + + WelcomeScreen* welcome_screen_ = nullptr; + + private: + bool screen_exit_ = false; + + base::OnceClosure screen_exit_callback_; +}; + +IN_PROC_BROWSER_TEST_F(WelcomeScreenBrowserTest, WelcomeScreenElements) { + welcome_screen_->Show(); + OobeScreenWaiter(WelcomeView::kScreenId).Wait(); + + test::OobeJS().ExpectVisiblePath({"connect", "welcomeScreen"}); + test::OobeJS().ExpectHiddenPath({"connect", "accessibilityScreen"}); + test::OobeJS().ExpectHiddenPath({"connect", "languageScreen"}); + test::OobeJS().ExpectHiddenPath({"connect", "timezoneScreen"}); + test::OobeJS().ExpectVisiblePath( + {"connect", "welcomeScreen", "welcomeNextButton"}); + test::OobeJS().ExpectVisiblePath( + {"connect", "welcomeScreen", "languageSelectionButton"}); + test::OobeJS().ExpectVisiblePath( + {"connect", "welcomeScreen", "accessibilitySettingsButton"}); +} + +IN_PROC_BROWSER_TEST_F(WelcomeScreenBrowserTest, WelcomeScreenNext) { + welcome_screen_->Show(); + OobeScreenWaiter(WelcomeView::kScreenId).Wait(); + test::OobeJS().TapOnPath({"connect", "welcomeScreen", "welcomeNextButton"}); + WaitForScreenExit(); +} + +// Set of browser tests for Welcome Screen Language options. +IN_PROC_BROWSER_TEST_F(WelcomeScreenBrowserTest, WelcomeScreenLanguageFlow) { + welcome_screen_->Show(); + OobeScreenWaiter(WelcomeView::kScreenId).Wait(); + test::OobeJS().TapOnPath( + {"connect", "welcomeScreen", "languageSelectionButton"}); + + test::OobeJS().TapOnPath({"connect", "ok-button-language"}); + test::OobeJS().TapOnPath({"connect", "welcomeScreen", "welcomeNextButton"}); + WaitForScreenExit(); +} + +IN_PROC_BROWSER_TEST_F(WelcomeScreenBrowserTest, + WelcomeScreenLanguageElements) { + welcome_screen_->Show(); + OobeScreenWaiter(WelcomeView::kScreenId).Wait(); + test::OobeJS().TapOnPath( + {"connect", "welcomeScreen", "languageSelectionButton"}); + + test::OobeJS().ExpectVisiblePath({"connect", "languageDropdownContainer"}); + test::OobeJS().ExpectVisiblePath({"connect", "keyboardDropdownContainer"}); + test::OobeJS().ExpectVisiblePath({"connect", "languageSelect"}); + test::OobeJS().ExpectVisiblePath({"connect", "keyboardSelect"}); +} + +IN_PROC_BROWSER_TEST_F(WelcomeScreenBrowserTest, + WelcomeScreenLanguageSelection) { + welcome_screen_->Show(); + OobeScreenWaiter(WelcomeView::kScreenId).Wait(); + + test::OobeJS().TapOnPath( + {"connect", "welcomeScreen", "languageSelectionButton"}); + ASSERT_TRUE(g_browser_process->GetApplicationLocale() == "en-US"); + test::OobeJS().GetBool( + "document.getElementById('connect').$.welcomeScreen.currentLanguage == " + "'English (United States)'"); + + test::OobeJS().SelectElementInPath("fr", + {"connect", "languageSelect", "select"}); + test::OobeJS().GetBool( + "document.getElementById('connect').$.welcomeScreen.currentLanguage == " + "'français'"); + ASSERT_TRUE(g_browser_process->GetApplicationLocale() == "fr"); + + test::OobeJS().SelectElementInPath("en-US", + {"connect", "languageSelect", "select"}); + test::OobeJS().GetBool( + "document.getElementById('connect').$.welcomeScreen.currentLanguage == " + "'English (United States)'"); + ASSERT_TRUE(g_browser_process->GetApplicationLocale() == "en-US"); +} + +IN_PROC_BROWSER_TEST_F(WelcomeScreenBrowserTest, + WelcomeScreenKeyboardSelection) { + welcome_screen_->Show(); + OobeScreenWaiter(WelcomeView::kScreenId).Wait(); + test::OobeJS().TapOnPath( + {"connect", "welcomeScreen", "languageSelectionButton"}); + + test::OobeJS().SelectElementInPath( + "_comp_ime_fgoepimhcoialccpbmpnnblemnepkkaoxkb:us:intl:eng", + {"connect", "keyboardSelect", "select"}); + test::OobeJS().GetBool( + "document.getElementById('connect').$.welcomeScreen.currentKeyboard==" + "'US'"); + ASSERT_TRUE(welcome_screen_->GetInputMethod() == + "_comp_ime_fgoepimhcoialccpbmpnnblemnepkkaoxkb:us:intl:eng"); + + test::OobeJS().SelectElementInPath( + "_comp_ime_fgoepimhcoialccpbmpnnblemnepkkaoxkb:us:workman:eng", + {"connect", "keyboardSelect", "select"}); + test::OobeJS().GetBool( + "document.getElementById('connect').$.welcomeScreen.currentKeyboard==" + "'_comp_ime_fgoepimhcoialccpbmpnnblemnepkkaoxkb:us:workman:eng'"); + ASSERT_TRUE(welcome_screen_->GetInputMethod() == + "_comp_ime_fgoepimhcoialccpbmpnnblemnepkkaoxkb:us:workman:eng"); +} + +// Set of browser tests for Welcome Screen Accessibility options. +IN_PROC_BROWSER_TEST_F(WelcomeScreenBrowserTest, + WelcomeScreenAccessibilityFlow) { + welcome_screen_->Show(); + OobeScreenWaiter(WelcomeView::kScreenId).Wait(); + test::OobeJS().TapOnPath( + {"connect", "welcomeScreen", "accessibilitySettingsButton"}); + + test::OobeJS().TapOnPath({"connect", "ok-button-accessibility"}); + test::OobeJS().TapOnPath({"connect", "welcomeScreen", "welcomeNextButton"}); + WaitForScreenExit(); +} + +IN_PROC_BROWSER_TEST_F(WelcomeScreenBrowserTest, + WelcomeScreenAccessibilitySpokenFeedback) { + welcome_screen_->Show(); + OobeScreenWaiter(WelcomeView::kScreenId).Wait(); + test::OobeJS().TapOnPath( + {"connect", "welcomeScreen", "accessibilitySettingsButton"}); + + ASSERT_FALSE(AccessibilityManager::Get()->IsSpokenFeedbackEnabled()); + ToggleAccessibilityFeature("accessibility-spoken-feedback", true); + ASSERT_TRUE(AccessibilityManager::Get()->IsSpokenFeedbackEnabled()); + + ToggleAccessibilityFeature("accessibility-spoken-feedback", false); + ASSERT_FALSE(AccessibilityManager::Get()->IsSpokenFeedbackEnabled()); +} + +IN_PROC_BROWSER_TEST_F(WelcomeScreenBrowserTest, + WelcomeScreenAccessibilityLargeCursor) { + welcome_screen_->Show(); + OobeScreenWaiter(WelcomeView::kScreenId).Wait(); + test::OobeJS().TapOnPath( + {"connect", "welcomeScreen", "accessibilitySettingsButton"}); + + ASSERT_FALSE(AccessibilityManager::Get()->IsLargeCursorEnabled()); + ToggleAccessibilityFeature("accessibility-large-cursor", true); + ASSERT_TRUE(AccessibilityManager::Get()->IsLargeCursorEnabled()); + + ToggleAccessibilityFeature("accessibility-large-cursor", false); + ASSERT_FALSE(AccessibilityManager::Get()->IsLargeCursorEnabled()); +} + +IN_PROC_BROWSER_TEST_F(WelcomeScreenBrowserTest, + WelcomeScreenAccessibilityHighContrast) { + welcome_screen_->Show(); + OobeScreenWaiter(WelcomeView::kScreenId).Wait(); + test::OobeJS().TapOnPath( + {"connect", "welcomeScreen", "accessibilitySettingsButton"}); + + ASSERT_FALSE(AccessibilityManager::Get()->IsHighContrastEnabled()); + ToggleAccessibilityFeature("accessibility-high-contrast", true); + ASSERT_TRUE(AccessibilityManager::Get()->IsHighContrastEnabled()); + + ToggleAccessibilityFeature("accessibility-high-contrast", false); + ASSERT_FALSE(AccessibilityManager::Get()->IsHighContrastEnabled()); +} + +IN_PROC_BROWSER_TEST_F(WelcomeScreenBrowserTest, + WelcomeScreenAccessibilitySelectToSpeak) { + welcome_screen_->Show(); + OobeScreenWaiter(WelcomeView::kScreenId).Wait(); + test::OobeJS().TapOnPath( + {"connect", "welcomeScreen", "accessibilitySettingsButton"}); + + ASSERT_FALSE(AccessibilityManager::Get()->IsSelectToSpeakEnabled()); + ToggleAccessibilityFeature("accessibility-select-to-speak", true); + ASSERT_TRUE(AccessibilityManager::Get()->IsSelectToSpeakEnabled()); + + ToggleAccessibilityFeature("accessibility-select-to-speak", false); + ASSERT_FALSE(AccessibilityManager::Get()->IsSelectToSpeakEnabled()); +} + +IN_PROC_BROWSER_TEST_F(WelcomeScreenBrowserTest, + WelcomeScreenAccessibilityScreenMagnifier) { + welcome_screen_->Show(); + OobeScreenWaiter(WelcomeView::kScreenId).Wait(); + test::OobeJS().TapOnPath( + {"connect", "welcomeScreen", "accessibilitySettingsButton"}); + + ASSERT_FALSE(MagnificationManager::Get()->IsMagnifierEnabled()); + ToggleAccessibilityFeature("accessibility-screen-magnifier", true); + ASSERT_TRUE(MagnificationManager::Get()->IsMagnifierEnabled()); + + ToggleAccessibilityFeature("accessibility-screen-magnifier", false); + ASSERT_FALSE(MagnificationManager::Get()->IsMagnifierEnabled()); +} + +} // namespace chromeos
diff --git a/chrome/browser/content_settings/content_settings_browsertest.cc b/chrome/browser/content_settings/content_settings_browsertest.cc index 30e13e1d..7660271 100644 --- a/chrome/browser/content_settings/content_settings_browsertest.cc +++ b/chrome/browser/content_settings/content_settings_browsertest.cc
@@ -106,19 +106,21 @@ net::EmbeddedTestServer https_server_; }; -// Test the combination of different ways of accessing cookies. -// Cookies can be read by JS or http request headers. -// Cookies can be written by JS or http response headers. +// Test the combination of different ways of accessing cookies --- JS, HTML, +// or the new async cookie-store API. enum class CookieMode { - kJSReadJSWrite, - kJSReadHttpWrite, - kHttpReadJSWrite, - kHttpReadHttpWrite + kJS, + kHttp, + kJSAsync, }; -class CookieSettingsTest : public ContentSettingsTest, - public testing::WithParamInterface<CookieMode> { +class CookieSettingsTest + : public ContentSettingsTest, + public testing::WithParamInterface<std::pair<CookieMode, CookieMode>> { public: + CookieMode ReadMode() const { return GetParam().first; } + CookieMode WriteMode() const { return GetParam().second; } + void SetUpOnMainThread() override { embedded_test_server()->RegisterRequestMonitor(base::BindRepeating( &CookieSettingsTest::MonitorRequest, base::Unretained(this))); @@ -126,26 +128,41 @@ &CookieSettingsTest::MonitorRequest, base::Unretained(this))); ASSERT_TRUE(embedded_test_server()->Start()); ASSERT_TRUE(https_server_.Start()); + + // Async API is for https only. + if (ReadMode() == CookieMode::kJSAsync || + WriteMode() == CookieMode::kJSAsync) + set_secure_scheme(); + } + + void SetUpCommandLine(base::CommandLine* cmd) override { + // Get access to CookieStore API. + cmd->AppendSwitch(switches::kEnableExperimentalWebPlatformFeatures); + ContentSettingsTest::SetUpCommandLine(cmd); } void set_secure_scheme() { secure_scheme_ = true; } std::string ReadCookie(Browser* browser) { - if (GetParam() == CookieMode::kJSReadJSWrite || - GetParam() == CookieMode::kJSReadHttpWrite) { - return JSReadCookie(browser); + switch (ReadMode()) { + case CookieMode::kJS: + return JSReadCookie(browser); + case CookieMode::kHttp: + return HttpReadCookie(browser); + case CookieMode::kJSAsync: + return JSAsyncReadCookie(browser); } - - return HttpReadCookie(browser); } void WriteCookie(Browser* browser) { - if (GetParam() == CookieMode::kJSReadJSWrite || - GetParam() == CookieMode::kHttpReadJSWrite) { - return JSWriteCookie(browser); + switch (WriteMode()) { + case CookieMode::kJS: + return JSWriteCookie(browser); + case CookieMode::kHttp: + return HttpWriteCookie(browser); + case CookieMode::kJSAsync: + return JSAsyncWriteCookie(browser); } - - return HttpWriteCookie(browser); } // Check the cookie in an incognito window. @@ -191,6 +208,10 @@ return GetServer()->GetURL("/set_cookie_header.html"); } + net::EmbeddedTestServer* GetOtherServer() { + return secure_scheme_ ? embedded_test_server() : &https_server_; + } + private: net::EmbeddedTestServer* GetServer() { return secure_scheme_ ? &https_server_ : embedded_test_server(); @@ -206,6 +227,21 @@ return cookies; } + // Read a cookie with JavaScript cookie-store API + std::string JSAsyncReadCookie(Browser* browser) { + return content::EvalJsWithManualReply( + browser->tab_strip_model()->GetActiveWebContents(), + "async function doGet() {" + " const cookies = await window.cookieStore.getAll();" + " let cookie_str = '';" + " for (const cookie of cookies)" + " cookie_str += `${cookie.name}=${cookie.value};`;" + " window.domAutomationController.send(cookie_str);" + "}" + "doGet()") + .ExtractString(); + } + // Read a cookie by fetching a url on the appropriate test server and checking // what Cookie header (if any) it saw. std::string HttpReadCookie(Browser* browser) { @@ -235,6 +271,22 @@ CHECK(rv); } + // Set a cookie with JavaScript cookie-store api. + void JSAsyncWriteCookie(Browser* browser) { + content::EvalJsResult result = content::EvalJsWithManualReply( + browser->tab_strip_model()->GetActiveWebContents(), + "async function doSet() {" + " await window.cookieStore.set(" + " 'name', 'Good', " + " { expires: Date.now() + 3600*1000," + " sameSite: 'unrestricted' });" + " window.domAutomationController.send(true);" + "}" + "doSet()"); + // Failure ignored here since some tests purposefully try to set disallowed + // cookies. + } + // Set a cookie by visiting a page that has a Set-Cookie header. void HttpWriteCookie(Browser* browser) { auto* network_context = @@ -320,7 +372,7 @@ WriteCookie(browser()); ASSERT_TRUE(ReadCookie(browser()).empty()); - GURL unblocked_url = https_server_.GetURL("/cookie1.html"); + GURL unblocked_url = GetOtherServer()->GetURL("/cookie1.html"); ui_test_utils::NavigateToURL(browser(), unblocked_url); ASSERT_FALSE(GetCookies(browser()->profile(), unblocked_url).empty()); @@ -368,10 +420,12 @@ INSTANTIATE_TEST_SUITE_P( /* no prefix */, CookieSettingsTest, - ::testing::Values(CookieMode::kJSReadJSWrite, - CookieMode::kJSReadHttpWrite, - CookieMode::kHttpReadJSWrite, - CookieMode::kHttpReadHttpWrite)); + ::testing::Values(std::make_pair(CookieMode::kJS, CookieMode::kJS), + std::make_pair(CookieMode::kJS, CookieMode::kHttp), + std::make_pair(CookieMode::kHttp, CookieMode::kJS), + std::make_pair(CookieMode::kHttp, CookieMode::kHttp), + std::make_pair(CookieMode::kHttp, CookieMode::kJSAsync), + std::make_pair(CookieMode::kJSAsync, CookieMode::kJS))); // This fails on ChromeOS because kRestoreOnStartup is ignored and the startup // preference is always "continue where I left off.
diff --git a/chrome/browser/extensions/api/feedback_private/chrome_feedback_private_delegate.cc b/chrome/browser/extensions/api/feedback_private/chrome_feedback_private_delegate.cc index b4891f1..831b2dd 100644 --- a/chrome/browser/extensions/api/feedback_private/chrome_feedback_private_delegate.cc +++ b/chrome/browser/extensions/api/feedback_private/chrome_feedback_private_delegate.cc
@@ -196,14 +196,12 @@ constexpr bool scrub = true; if (system_logs::ContainsIwlwifiLogs(feedback_data->sys_info())) { - VLOG(1) << "Fetching WiFi dump logs."; system_logs::SystemLogsFetcher* fetcher = new system_logs::SystemLogsFetcher(scrub); fetcher->AddSource(std::make_unique<system_logs::IwlwifiDumpLogSource>()); fetcher->Fetch(base::BindOnce(&OnFetchedExtraLogs, feedback_data, std::move(callback))); } else { - VLOG(1) << "WiFi dump logs are not present."; std::move(callback).Run(feedback_data); } }
diff --git a/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc b/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc index 8553531..14cd2345 100644 --- a/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc +++ b/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc
@@ -243,8 +243,6 @@ // Add a Cellular GSM Device. device_test_->AddDevice(kCellularDevicePath, shill::kTypeCellular, "stub_cellular_device1"); - SetDeviceProperty(kCellularDevicePath, shill::kCarrierProperty, - base::Value("Cellular1_Carrier")); base::DictionaryValue home_provider; home_provider.SetString("name", "Cellular1_Provider"); home_provider.SetString("code", "000000");
diff --git a/chrome/browser/first_run/upgrade_util_mac.mm b/chrome/browser/first_run/upgrade_util_mac.mm index 375418e..f24c53e 100644 --- a/chrome/browser/first_run/upgrade_util_mac.mm +++ b/chrome/browser/first_run/upgrade_util_mac.mm
@@ -181,10 +181,7 @@ // If this isn't a new-style update, and the staging key isn't set, don't // block at all. - const NSTimeInterval kPollingTime = 60 * 60; // 1 hour - base::scoped_nsobject<CrStagingKeyWatcher> staging_watcher( - [[CrStagingKeyWatcher alloc] initWithPollingTime:kPollingTime]); - if (![staging_watcher isStagingKeySet]) { + if (![CrStagingKeyWatcher isStagingKeySet]) { LogInstanceCheckResult(OtherInstancesCheckResult::kOldStyleUpdate); return true; }
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json index d5d9373..7d2f5a17 100644 --- a/chrome/browser/flag-metadata.json +++ b/chrome/browser/flag-metadata.json
@@ -908,11 +908,6 @@ "expiry_milestone": 76 }, { - "name": "enable-client-lo-fi", - "owners": [ "//components/data_reduction_proxy/OWNERS" ], - "expiry_milestone": 76 - }, - { "name": "enable-cloud-print-xps", "owners": [ "//printing/OWNERS" ], "expiry_milestone": 76 @@ -965,11 +960,6 @@ "expiry_milestone": 76 }, { - "name": "enable-data-reduction-proxy-savings-promo", - "owners": [ "//components/data_reduction_proxy/OWNERS" ], - "expiry_milestone": 76 - }, - { "name": "enable-data-reduction-proxy-server-experiment", "owners": [ "//components/data_reduction_proxy/OWNERS" ], "expiry_milestone": 76 @@ -1033,7 +1023,7 @@ { "name": "enable-ephemeral-tab", "owners": [ "donnd", "jinsukkim" ], - "expiry_milestone": 76 + "expiry_milestone": 80 }, { "name": "enable-experimental-accessibility-features", @@ -1240,11 +1230,6 @@ "expiry_milestone": 76 }, { - "name": "enable-lite-mode-rebrand", - "owners": [ "//components/data_reduction_proxy/OWNERS" ], - "expiry_milestone": 76 - }, - { "name": "enable-lite-page-server-previews", "owners": [ "//components/data_reduction_proxy/OWNERS" ], "expiry_milestone": 76 @@ -1415,11 +1400,6 @@ "expiry_milestone": 76 }, { - "name": "enable-optimization-hints", - "owners": [ "//components/data_reduction_proxy/OWNERS" ], - "expiry_milestone": 76 - }, - { "name": "enable-osk-overscroll", // "owners": [ "your-team" ], "expiry_milestone": 76 @@ -1452,11 +1432,6 @@ "expiry_milestone": 80 }, { - "name": "enable-previews-android-omnibox-ui", - "owners": [ "//components/data_reduction_proxy/OWNERS" ], - "expiry_milestone": 76 - }, - { "name": "enable-process-sharing-with-strict-site-instances", "owners": [ "japhet" ], "expiry_milestone": 80
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc index 82d496e7..05cf00c 100644 --- a/chrome/browser/flag_descriptions.cc +++ b/chrome/browser/flag_descriptions.cc
@@ -496,21 +496,11 @@ const char kEnableBrotliDescription[] = "Enable Brotli Content-Encoding support."; -const char kEnableDataSaverLiteModeRebrandName[] = - "Data Saver Lite Mode Rebranding"; -const char kEnableDataSaverLiteModeRebrandDescription[] = - "Enable the Data Saver rebranding to Lite Mode."; - const char kEnableSaveDataName[] = "Enables save data feature"; const char kEnableSaveDataDescription[] = "Enables save data feature. May cause user's traffic to be proxied via " "Google's data reduction proxy."; -const char kEnableClientLoFiName[] = "Client-side Lo-Fi previews"; - -const char kEnableClientLoFiDescription[] = - "Enable showing low fidelity images on some pages on slow networks."; - const char kEnableFilesystemInIncognitoName[] = "Filesystem API in Incognito"; const char kEnableFilesystemInIncognitoDescription[] = "Enable Filesystem API in incognito mode."; @@ -541,13 +531,6 @@ "Enable a different approach to saving data by configuring the back end " "server"; -const char kEnableDataReductionProxySavingsPromoName[] = - "Data Saver 1 MB Savings Promo"; -const char kEnableDataReductionProxySavingsPromoDescription[] = - "Enable a Data Saver promo for 1 MB of savings. If Data Saver has already " - "saved 1 MB of data, then the promo will not be shown. Data Saver must be " - "enabled for the promo to be shown."; - const char kEnableDesktopPWAsName[] = "Desktop PWAs"; const char kEnableDesktopPWAsDescription[] = "Experimental windowing and install banner treatment for Progressive Web " @@ -623,12 +606,6 @@ "scroller'. i.e. The one that gets special features like URL bar movement, " "overscroll glow, rotation anchoring, etc."; -const char kEnablePreviewsAndroidOmniboxUIName[] = - "Previews Android Omnibox UI"; -const char kEnablePreviewsAndroidOmniboxUIDescription[] = - "Enable showing the Previews UI in the Omnibox on Android instead of an " - "InfoBar. This has no effect on other platforms."; - const char kEnableLitePageServerPreviewsName[] = "Lite Page Server Previews"; const char kEnableLitePageServerPreviewsDescription[] = "Enable showing Lite Page Previews served from a Previews Server." @@ -738,12 +715,6 @@ const char kEnableNotificationExpansionAnimationDescription[] = "Enable notification animations whenever the expansion state is toggled."; -const char kEnableOptimizationHintsName[] = "Optimization Hints"; -const char kEnableOptimizationHintsDescription[] = - "Enable the Optimization Hints feature which incorporates server hints " - "into decisions for what optimizations to perform on some pages on slow " - "networks."; - const char kEnableOutOfBlinkCorsName[] = "Out of blink CORS"; const char kEnableOutOfBlinkCorsDescription[] = "CORS handling logic is moved out of blink."; @@ -1761,11 +1732,6 @@ const char kSmoothScrollingDescription[] = "Animate smoothly when scrolling page content."; -const char kSoleIntegrationName[] = "Sole integration"; -const char kSoleIntegrationDescription[] = - "Enable Sole integration for browser customization. You must restart " - "the browser twice for changes to take effect."; - const char kSpeculativeServiceWorkerStartOnQueryInputName[] = "Enable speculative start of a service worker when a search is predicted."; const char kSpeculativeServiceWorkerStartOnQueryInputDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h index 28e51a8..33d7873 100644 --- a/chrome/browser/flag_descriptions.h +++ b/chrome/browser/flag_descriptions.h
@@ -320,15 +320,9 @@ extern const char kEnableBrotliName[]; extern const char kEnableBrotliDescription[]; -extern const char kEnableDataSaverLiteModeRebrandName[]; -extern const char kEnableDataSaverLiteModeRebrandDescription[]; - extern const char kEnableSaveDataName[]; extern const char kEnableSaveDataDescription[]; -extern const char kEnableClientLoFiName[]; -extern const char kEnableClientLoFiDescription[]; - extern const char kEnableFilesystemInIncognitoName[]; extern const char kEnableFilesystemInIncognitoDescription[]; @@ -350,9 +344,6 @@ extern const char kEnableDataReductionProxyServerExperimentName[]; extern const char kEnableDataReductionProxyServerExperimentDescription[]; -extern const char kEnableDataReductionProxySavingsPromoName[]; -extern const char kEnableDataReductionProxySavingsPromoDescription[]; - extern const char kEnableDesktopPWAsName[]; extern const char kEnableDesktopPWAsDescription[]; @@ -392,9 +383,6 @@ extern const char kEnableImplicitRootScrollerName[]; extern const char kEnableImplicitRootScrollerDescription[]; -extern const char kEnablePreviewsAndroidOmniboxUIName[]; -extern const char kEnablePreviewsAndroidOmniboxUIDescription[]; - extern const char kEnableLitePageServerPreviewsName[]; extern const char kEnableLitePageServerPreviewsDescription[]; @@ -449,9 +437,6 @@ extern const char kEnableNotificationExpansionAnimationName[]; extern const char kEnableNotificationExpansionAnimationDescription[]; -extern const char kEnableOptimizationHintsName[]; -extern const char kEnableOptimizationHintsDescription[]; - extern const char kEnableOutOfBlinkCorsName[]; extern const char kEnableOutOfBlinkCorsDescription[]; @@ -1054,9 +1039,6 @@ extern const char kSmoothScrollingName[]; extern const char kSmoothScrollingDescription[]; -extern const char kSoleIntegrationName[]; -extern const char kSoleIntegrationDescription[]; - extern const char kSpeculativeServiceWorkerStartOnQueryInputName[]; extern const char kSpeculativeServiceWorkerStartOnQueryInputDescription[];
diff --git a/chrome/browser/media/router/providers/cast/cast_activity_manager.cc b/chrome/browser/media/router/providers/cast/cast_activity_manager.cc index 6ab8b5b..f2cde5fa 100644 --- a/chrome/browser/media/router/providers/cast/cast_activity_manager.cc +++ b/chrome/browser/media/router/providers/cast/cast_activity_manager.cc
@@ -14,6 +14,7 @@ #include "chrome/browser/media/router/providers/cast/cast_activity_record.h" #include "chrome/browser/media/router/providers/cast/cast_session_client.h" #include "chrome/common/media_router/media_source_helper.h" +#include "chrome/common/media_router/mojo/media_router.mojom.h" #include "url/origin.h" using blink::mojom::PresentationConnectionCloseReason; @@ -359,14 +360,9 @@ const MediaSinkInternal* sink = media_sink_service_->GetSinkByRoute(route); CHECK(sink); - for (auto& client : activity->connected_clients()) { - client.second->SendMessageToClient( - CreateReceiverActionStopMessage(client.first, *sink, hash_token_)); - } - activity->SendStopSessionMessageToReceiver( base::nullopt, // TODO(jrw): Get the real client ID. - std::move(callback)); + hash_token_, std::move(callback)); } CastActivityManager::ActivityMap::iterator @@ -394,8 +390,7 @@ const std::string& app_id) { std::unique_ptr<CastActivityRecordBase> activity; if (activity_record_factory_) { - activity.reset( - activity_record_factory_->MakeCastActivityRecord(route, app_id)); + activity = activity_record_factory_->MakeCastActivityRecord(route, app_id); } else { activity.reset(new CastActivityRecord(route, app_id, media_sink_service_, message_handler_, session_tracker_, @@ -499,8 +494,7 @@ base::Optional<int> request_id) { auto it = FindActivityBySink(sink); if (it != activities_.end()) { - for (auto& client : it->second->connected_clients()) - client.second->SendMediaStatusToClient(media_status, request_id); + it->second->SendMediaStatusToClients(media_status, request_id); } }
diff --git a/chrome/browser/media/router/providers/cast/cast_activity_manager.h b/chrome/browser/media/router/providers/cast/cast_activity_manager.h index 0a2058f..87a7a60 100644 --- a/chrome/browser/media/router/providers/cast/cast_activity_manager.h +++ b/chrome/browser/media/router/providers/cast/cast_activity_manager.h
@@ -19,7 +19,6 @@ #include "chrome/browser/media/router/providers/cast/cast_session_tracker.h" #include "chrome/common/media_router/mojo/media_router.mojom.h" #include "chrome/common/media_router/providers/cast/cast_media_source.h" -#include "mojo/public/cpp/bindings/binding.h" #include "url/origin.h" namespace cast_channel {
diff --git a/chrome/browser/media/router/providers/cast/cast_activity_record.cc b/chrome/browser/media/router/providers/cast/cast_activity_record.cc index 8aa3e208..3016f316 100644 --- a/chrome/browser/media/router/providers/cast/cast_activity_record.cc +++ b/chrome/browser/media/router/providers/cast/cast_activity_record.cc
@@ -32,14 +32,13 @@ int tab_id) { const std::string& client_id = source.client_id(); DCHECK(!base::ContainsKey(connected_clients_, client_id)); - std::unique_ptr<CastSessionClientBase> client; - if (client_factory_) { - client = client_factory_->MakeClient(client_id, origin, tab_id); - } else { - client = std::make_unique<CastSessionClient>(client_id, origin, tab_id, - source.auto_join_policy(), - data_decoder_, this); - } + std::unique_ptr<CastSessionClientBase> client = + client_factory_for_test_ + ? client_factory_for_test_->MakeClientForTest(client_id, origin, + tab_id) + : std::make_unique<CastSessionClient>(client_id, origin, tab_id, + source.auto_join_policy(), + data_decoder_, this); auto presentation_connection = client->Init(); connected_clients_.emplace(client_id, std::move(client)); @@ -123,14 +122,22 @@ cast_message.client_id(), std::move(callback)); } +// TODO(jrw): Revise the name of this method. void CastActivityRecord::SendStopSessionMessageToReceiver( const base::Optional<std::string>& client_id, + const std::string& hash_token, mojom::MediaRouteProvider::TerminateRouteCallback callback) { const std::string& sink_id = route_.media_sink_id(); const MediaSinkInternal* sink = media_sink_service_->GetSinkById(sink_id); DCHECK(sink); DCHECK(session_id_); + // TODO(jrw): Add test for this loop. + for (const auto& client : connected_clients()) { + client.second->SendMessageToClient( + CreateReceiverActionStopMessage(client.first, *sink, hash_token)); + } + message_handler_->StopSession( sink->cast_data().cast_channel_id, *session_id_, client_id, activity_manager_->MakeResultCallbackForRoute(route_.media_route_id(), @@ -168,6 +175,13 @@ it->second->SendMessageToClient(std::move(message)); } +void CastActivityRecord::SendMediaStatusToClients( + const base::Value& media_status, + base::Optional<int> request_id) { + for (auto& client : connected_clients()) + client.second->SendMediaStatusToClient(media_status, request_id); +} + void CastActivityRecord::ClosePresentationConnections( PresentationConnectionCloseReason close_reason) { for (auto& client : connected_clients_) @@ -216,6 +230,7 @@ return sink->cast_data().cast_channel_id; } -CastSessionClientFactory* CastActivityRecord::client_factory_ = nullptr; +CastSessionClientFactoryForTest* CastActivityRecord::client_factory_for_test_ = + nullptr; } // namespace media_router
diff --git a/chrome/browser/media/router/providers/cast/cast_activity_record.h b/chrome/browser/media/router/providers/cast/cast_activity_record.h index afd87f9..359e8c8f 100644 --- a/chrome/browser/media/router/providers/cast/cast_activity_record.h +++ b/chrome/browser/media/router/providers/cast/cast_activity_record.h
@@ -20,14 +20,14 @@ class CastActivityManagerBase; class CastActivityRecord; -class CastSessionClientFactory; class CastInternalMessage; class CastSession; class CastSessionClientBase; -class CastSessionClientFactory; +class CastSessionClientFactoryForTest; class CastSessionTracker; class DataDecoder; class MediaSinkServiceBase; +class MediaRoute; // TODO(jrw): Rename // CastActivityRecordBase -> CastActivityRecord @@ -65,6 +65,7 @@ virtual void SendStopSessionMessageToReceiver( const base::Optional<std::string>& client_id, + const std::string& hash_token, mojom::MediaRouteProvider::TerminateRouteCallback callback) = 0; // Called when the client given by |client_id| requests to leave the session. @@ -98,6 +99,8 @@ virtual void SendMessageToClient( const std::string& client_id, blink::mojom::PresentationConnectionMessagePtr message) = 0; + virtual void SendMediaStatusToClients(const base::Value& media_status, + base::Optional<int> request_id) = 0; // Closes / Terminates the PresentationConnections of all clients connected // to this activity. @@ -116,7 +119,7 @@ class CastActivityRecordFactory { public: - virtual CastActivityRecordBase* MakeCastActivityRecord( + virtual std::unique_ptr<CastActivityRecordBase> MakeCastActivityRecord( const MediaRoute& route, const std::string& app_id) = 0; }; @@ -141,6 +144,7 @@ cast_channel::ResultCallback callback) override; void SendStopSessionMessageToReceiver( const base::Optional<std::string>& client_id, + const std::string& hash_token, mojom::MediaRouteProvider::TerminateRouteCallback callback) override; void HandleLeaveSession(const std::string& client_id) override; mojom::RoutePresentationConnectionPtr AddClient(const CastMediaSource& source, @@ -153,12 +157,19 @@ void SendMessageToClient( const std::string& client_id, blink::mojom::PresentationConnectionMessagePtr message) override; + + void SendMediaStatusToClients(const base::Value& media_status, + base::Optional<int> request_id) override; + + // Closes / Terminates the PresentationConnections of all clients connected + // to this activity. void ClosePresentationConnections( blink::mojom::PresentationConnectionCloseReason close_reason) override; void TerminatePresentationConnections() override; - static void SetClientFactoryForTest(CastSessionClientFactory* factory) { - client_factory_ = factory; + static void SetClientFactoryForTest( + CastSessionClientFactoryForTest* factory) { + client_factory_for_test_ = factory; } private: @@ -183,7 +194,7 @@ return it == connected_clients_.end() ? nullptr : it->second.get(); } - static CastSessionClientFactory* client_factory_; + static CastSessionClientFactoryForTest* client_factory_for_test_; MediaSinkServiceBase* const media_sink_service_; // TODO(https://crbug.com/809249): Consider wrapping CastMessageHandler with
diff --git a/chrome/browser/media/router/providers/cast/cast_activity_record_unittest.cc b/chrome/browser/media/router/providers/cast/cast_activity_record_unittest.cc index ee3efb9..1875301 100644 --- a/chrome/browser/media/router/providers/cast/cast_activity_record_unittest.cc +++ b/chrome/browser/media/router/providers/cast/cast_activity_record_unittest.cc
@@ -5,7 +5,6 @@ #include "chrome/browser/media/router/providers/cast/cast_activity_record.h" #include <memory> -#include <sstream> #include <string> #include <tuple> #include <utility> @@ -81,8 +80,6 @@ void(blink::mojom::PresentationConnectionCloseReason reason)); }; -using NewClientCallback = base::RepeatingCallback<void(MockCastSessionClient&)>; - class MockCastActivityManager : public CastActivityManagerBase { public: MOCK_METHOD2(MakeResultCallbackForRoute, @@ -93,7 +90,8 @@ } // namespace -class CastActivityRecordTest : public testing::Test, CastSessionClientFactory { +class CastActivityRecordTest : public testing::Test, + public CastSessionClientFactoryForTest { public: CastActivityRecordTest() {} @@ -140,7 +138,7 @@ CastActivityRecord::SetClientFactoryForTest(nullptr); } - std::unique_ptr<CastSessionClientBase> MakeClient( + std::unique_ptr<CastSessionClientBase> MakeClientForTest( const std::string& client_id, const url::Origin& origin, int tab_id) override { @@ -317,7 +315,8 @@ RouteRequestResult::INCOGNITO_MISMATCH)); SetUpSession(); - record_->SendStopSessionMessageToReceiver(client_id, callback.Get()); + record_->SendStopSessionMessageToReceiver(client_id, "dummyHashToken", + callback.Get()); } TEST_F(CastActivityRecordTest, HandleLeaveSession) {
diff --git a/chrome/browser/media/router/providers/cast/cast_internal_message_util.cc b/chrome/browser/media/router/providers/cast/cast_internal_message_util.cc index 4af4556..189883d 100644 --- a/chrome/browser/media/router/providers/cast/cast_internal_message_util.cc +++ b/chrome/browser/media/router/providers/cast/cast_internal_message_util.cc
@@ -16,12 +16,11 @@ #include "chrome/common/media_router/providers/cast/cast_media_source.h" #include "components/cast_channel/cast_socket.h" #include "components/cast_channel/enum_table.h" -#include "components/cast_channel/proto/cast_channel.pb.h" #include "net/base/escape.h" namespace cast_util { -using namespace media_router; +using media_router::CastInternalMessage; template <> const EnumTable<CastInternalMessage::Type>
diff --git a/chrome/browser/media/router/providers/cast/cast_session_client.cc b/chrome/browser/media/router/providers/cast/cast_session_client.cc index 9d70a9a..dcf7f3ce 100644 --- a/chrome/browser/media/router/providers/cast/cast_session_client.cc +++ b/chrome/browser/media/router/providers/cast/cast_session_client.cc
@@ -5,6 +5,7 @@ #include "chrome/browser/media/router/providers/cast/cast_session_client.h" #include "base/bind.h" +#include "base/logging.h" #include "chrome/browser/media/router/data_decoder_util.h" #include "chrome/browser/media/router/providers/cast/cast_activity_record.h" #include "chrome/browser/media/router/providers/cast/cast_internal_message_util.h" @@ -22,9 +23,9 @@ void ReportClientMessageParseError(const MediaRoute::Id& route_id, const std::string& error) { - // TODO(crbug.com/808720): Record UMA metric for parse result. - DVLOG(2) << "Failed to parse Cast client message for " << route_id << ": " - << error; + // TODO(crbug.com/905002): Record UMA metric for parse result. + LOG(ERROR) << "Failed to parse Cast client message for " << route_id << ": " + << error; } } // namespace @@ -41,7 +42,7 @@ int tab_id, AutoJoinPolicy auto_join_policy, DataDecoder* data_decoder, - CastActivityRecord* activity) + CastActivityRecordBase* activity) : CastSessionClientBase(client_id, origin, tab_id), auto_join_policy_(auto_join_policy), data_decoder_(data_decoder), @@ -141,7 +142,6 @@ if (!cast_message) { ReportClientMessageParseError(activity_->route().media_route_id(), "Not a Cast message"); - DLOG(ERROR) << "Received non-Cast message from client"; return; }
diff --git a/chrome/browser/media/router/providers/cast/cast_session_client.h b/chrome/browser/media/router/providers/cast/cast_session_client.h index df116c2..43c115f6 100644 --- a/chrome/browser/media/router/providers/cast/cast_session_client.h +++ b/chrome/browser/media/router/providers/cast/cast_session_client.h
@@ -23,7 +23,7 @@ namespace media_router { -class CastActivityRecord; +class CastActivityRecordBase; class DataDecoder; // TODO(jrw): Rename @@ -98,9 +98,9 @@ int tab_id_; }; -class CastSessionClientFactory { +class CastSessionClientFactoryForTest { public: - virtual std::unique_ptr<CastSessionClientBase> MakeClient( + virtual std::unique_ptr<CastSessionClientBase> MakeClientForTest( const std::string& client_id, const url::Origin& origin, int tab_id) = 0; @@ -117,7 +117,7 @@ int tab_id, AutoJoinPolicy auto_join_policy, DataDecoder* data_decoder, - CastActivityRecord* activity); + CastActivityRecordBase* activity); ~CastSessionClient() override; // CastSessionClientBase implementation @@ -161,7 +161,7 @@ const AutoJoinPolicy auto_join_policy_; DataDecoder* const data_decoder_; - CastActivityRecord* const activity_; + CastActivityRecordBase* const activity_; // The maximum number of pending media requests, used to prevent memory leaks. // Normally the number of pending requests should be fairly small, but each
diff --git a/chrome/browser/media/router/providers/cast/cast_session_client_unittest.cc b/chrome/browser/media/router/providers/cast/cast_session_client_unittest.cc new file mode 100644 index 0000000..73828e4 --- /dev/null +++ b/chrome/browser/media/router/providers/cast/cast_session_client_unittest.cc
@@ -0,0 +1,252 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/media/router/providers/cast/cast_session_client.h" + +// #include <iostream> +#include <memory> +#include <tuple> +#include <utility> + +#include "base/bind.h" +#include "base/logging.h" +#include "base/run_loop.h" +#include "base/task/post_task.h" +#include "base/test/mock_log.h" +#include "base/test/values_test_util.h" +#include "chrome/browser/media/router/data_decoder_util.h" +#include "chrome/browser/media/router/providers/cast/cast_activity_manager.h" +#include "chrome/browser/media/router/providers/cast/cast_internal_message_util.h" +#include "chrome/browser/media/router/providers/cast/mock_cast_activity_record.h" +#include "chrome/browser/media/router/providers/cast/test_util.h" +#include "chrome/browser/media/router/providers/common/buffered_message_sender.h" +#include "chrome/browser/media/router/test/mock_mojo_media_router.h" +#include "chrome/browser/media/router/test/test_helper.h" +#include "chrome/common/media_router/test/test_helper.h" +#include "components/cast_channel/cast_test_util.h" +#include "content/public/browser/browser_task_traits.h" +#include "content/public/test/test_browser_thread_bundle.h" +// #include "services/data_decoder/data_decoder_service.h" +#include "services/data_decoder/public/cpp/testing_json_parser.h" +// #include "services/data_decoder/public/mojom/constants.mojom.h" +#include "services/service_manager/public/cpp/test/test_connector_factory.h" +// #include "testing/gmock/include/gmock/gmock.h" +// #include "testing/gtest/include/gtest/gtest.h" + +using base::test::IsJson; +using base::test::ParseJson; +using testing::_; +using testing::AllOf; +using testing::AnyNumber; +using testing::HasSubstr; +using testing::IsEmpty; +using testing::Not; +using testing::Return; +using testing::WithArg; + +namespace media_router { + +namespace { +constexpr int kTabId = 213; + +class MockPresentationConnection : public blink::mojom::PresentationConnection { + public: + explicit MockPresentationConnection( + mojom::RoutePresentationConnectionPtr connections) + : binding_(this, std::move(connections->connection_request)) {} + + ~MockPresentationConnection() override = default; + + MOCK_METHOD1(OnMessage, void(blink::mojom::PresentationConnectionMessagePtr)); + MOCK_METHOD1(DidChangeState, + void(blink::mojom::PresentationConnectionState state)); + MOCK_METHOD1(DidClose, void(blink::mojom::PresentationConnectionCloseReason)); + + // NOTE: This member doesn't look like it's used for anything, but it needs to + // exist in order for Mojo magic to work correctly. + mojo::Binding<blink::mojom::PresentationConnection> binding_; + + DISALLOW_COPY_AND_ASSIGN(MockPresentationConnection); +}; + +} // namespace + +class CastSessionClientTest : public testing::Test { + public: + CastSessionClientTest() { activity_.set_session_id("theSessionId"); } + + ~CastSessionClientTest() override { RunUntilIdle(); } + + protected: + void RunUntilIdle() { thread_bundle_.RunUntilIdle(); } + + content::TestBrowserThreadBundle thread_bundle_; + data_decoder::TestingJsonParser::ScopedFactoryOverride parser_override_; + service_manager::TestConnectorFactory connector_factory_; + cast_channel::MockCastSocketService socket_service_{ + base::CreateSingleThreadTaskRunnerWithTraits( + {content::BrowserThread::UI})}; + cast_channel::MockCastMessageHandler message_handler_{&socket_service_}; + DataDecoder decoder_{connector_factory_.GetDefaultConnector()}; + url::Origin origin_; + MediaRoute route_; + MockCastActivityRecord activity_{route_, "theAppId"}; + std::unique_ptr<CastSessionClient> client_ = + std::make_unique<CastSessionClient>("theClientId", + origin_, + kTabId, + AutoJoinPolicy::kPageScoped, + &decoder_, + &activity_); + std::unique_ptr<MockPresentationConnection> mock_connection_ = + std::make_unique<MockPresentationConnection>(client_->Init()); + base::test::MockLog log_; +}; + +TEST_F(CastSessionClientTest, OnInvalidJson) { + // TODO(crbug.com/905002): Check UMA calls instead of logging (here and + // below). + EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, _, _, + HasSubstr("Failed to parse Cast client message"))) + .WillOnce(Return(true)); // suppress logging + + log_.StartCapturingLogs(); + client_->OnMessage( + blink::mojom::PresentationConnectionMessage::NewMessage("invalid js")); +} + +TEST_F(CastSessionClientTest, OnInvalidMessage) { + EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, _, _, + AllOf(HasSubstr("Failed to parse Cast client message"), + HasSubstr("Not a Cast message")))) + .WillOnce(Return(true)); // suppress logging + + log_.StartCapturingLogs(); + client_->OnMessage( + blink::mojom::PresentationConnectionMessage::NewMessage("{}")); +} + +TEST_F(CastSessionClientTest, OnMessageWrongClientId) { + EXPECT_CALL( + log_, Log(logging::LOG_ERROR, _, _, _, + AllOf(HasSubstr("Client ID mismatch"), HasSubstr("theClientId"), + HasSubstr("theWrongClientId")))) + .WillOnce(Return(true)); // suppress logging + + log_.StartCapturingLogs(); + client_->OnMessage( + blink::mojom::PresentationConnectionMessage::NewMessage(R"({ + "type": "v2_message", + "clientId": "theWrongClientId", + "message": { + "sessionId": "theSessionId", + "type": "MEDIA_GET_STATUS" + } + })")); +} + +TEST_F(CastSessionClientTest, OnMessageWrongSessionId) { + EXPECT_CALL(log_, Log(logging::LOG_ERROR, _, _, _, + AllOf(HasSubstr("Session ID mismatch"), + HasSubstr("theSessionId"), + HasSubstr("theWrongSessionId")))) + .WillOnce(Return(true)); // suppress logging + + log_.StartCapturingLogs(); + client_->OnMessage( + blink::mojom::PresentationConnectionMessage::NewMessage(R"({ + "type": "v2_message", + "clientId": "theClientId", + "message": { + "sessionId": "theWrongSessionId", + "type": "MEDIA_GET_STATUS" + } + })")); +} + +TEST_F(CastSessionClientTest, AppMessageFromClient) { + EXPECT_CALL(activity_, SendAppMessageToReceiver) + .WillOnce(Return(cast_channel::Result::kOk)); + + client_->OnMessage( + blink::mojom::PresentationConnectionMessage::NewMessage(R"({ + "type": "app_message", + "clientId": "theClientId", + "message": { + "namespaceName": "urn:x-cast:com.google.foo", + "sessionId": "theSessionId", + "message": {} + }, + "sequenceNumber": 123 + })")); +} + +TEST_F(CastSessionClientTest, OnMediaStatusUpdatedWithPendingRequest) { + EXPECT_CALL(activity_, SendMediaRequestToReceiver(IsCastInternalMessage(R"({ + "type": "v2_message", + "clientId": "theClientId", + "sequenceNumber": 123, + "message": { + "sessionId": "theSessionId", + "type": "MEDIA_GET_STATUS" + } + })"))) + .WillOnce(Return(345)); + client_->OnMessage( + blink::mojom::PresentationConnectionMessage::NewMessage(R"({ + "type": "v2_message", + "clientId": "theClientId", + "message": { + "sessionId": "theSessionId", + "type": "MEDIA_GET_STATUS" + }, + "sequenceNumber": 123 + })")); + RunUntilIdle(); + + EXPECT_CALL(*mock_connection_, OnMessage(IsCastMessage(R"({ + "clientId": "theClientId", + "message": {"foo": "bar"}, + "timeoutMillis": 0, + "type": "v2_message" + })"))); + client_->SendMediaStatusToClient(ParseJson(R"({"foo": "bar"})"), 123); +} + +TEST_F(CastSessionClientTest, SendSetVolumeCommandToReceiver) { + EXPECT_CALL(activity_, + SendSetVolumeRequestToReceiver(IsCastInternalMessage(R"({ + "type": "v2_message", + "clientId": "theClientId", + "sequenceNumber": 123, + "message": { + "sessionId": "theSessionId", + "type": "SET_VOLUME" + } + })"), + _)) + .WillOnce(WithArg<1>([](auto callback) { + std::move(callback).Run(cast_channel::Result::kOk); + })); + EXPECT_CALL(*mock_connection_, OnMessage(IsCastMessage(R"({ + "clientId": "theClientId", + "message": null, + "sequenceNumber": 123, + "timeoutMillis": 0, + "type": "v2_message" + })"))); + + client_->OnMessage( + blink::mojom::PresentationConnectionMessage::NewMessage(R"({ + "type": "v2_message", + "clientId": "theClientId", + "sequenceNumber": 123, + "message": { + "sessionId": "theSessionId", + "type": "SET_VOLUME" + } + })")); +} + +} // namespace media_router
diff --git a/chrome/browser/media/router/providers/cast/mock_cast_activity_record.h b/chrome/browser/media/router/providers/cast/mock_cast_activity_record.h index c4bd02d..a72a33f 100644 --- a/chrome/browser/media/router/providers/cast/mock_cast_activity_record.h +++ b/chrome/browser/media/router/providers/cast/mock_cast_activity_record.h
@@ -5,7 +5,10 @@ #ifndef CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_CAST_MOCK_CAST_ACTIVITY_RECORD_H_ #define CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_CAST_MOCK_CAST_ACTIVITY_RECORD_H_ +#include <string> + #include "chrome/browser/media/router/providers/cast/cast_activity_record.h" +#include "chrome/browser/media/router/providers/cast/cast_internal_message_util.h" #include "chrome/browser/media/router/providers/cast/cast_session_client.h" #include "testing/gmock/include/gmock/gmock.h" @@ -33,9 +36,10 @@ MOCK_METHOD2(SendSetVolumeRequestToReceiver, void(const CastInternalMessage& cast_message, cast_channel::ResultCallback callback)); - MOCK_METHOD2( + MOCK_METHOD3( SendStopSessionMessageToReceiver, void(const base::Optional<std::string>& client_id, + const std::string& hash_token, mojom::MediaRouteProvider::TerminateRouteCallback callback)); MOCK_METHOD1(HandleLeaveSession, void(const std::string& client_id)); MOCK_METHOD3(
diff --git a/chrome/browser/media/router/providers/cast/test_util.cc b/chrome/browser/media/router/providers/cast/test_util.cc new file mode 100644 index 0000000..6c75cdc --- /dev/null +++ b/chrome/browser/media/router/providers/cast/test_util.cc
@@ -0,0 +1,42 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/media/router/providers/cast/test_util.h" + +#include "components/cast_channel/enum_table.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace media_router { + +std::ostream& operator<<(std::ostream& out, CastInternalMessage::Type type) { + return out << cast_util::EnumToString(type).value_or("<invalid>"); +} + +// TODO(jrw): Why won't gtest call this operator? +std::ostream& operator<<(std::ostream& out, + const CastInternalMessage& message) { + out << "{type=" << message.type() << ", client_id=" << message.client_id(); + if (message.sequence_number()) { + out << ", sequence_number=" << *message.sequence_number(); + } + if (message.has_session_id()) { + out << ", session_id=" << message.session_id(); + } + + switch (message.type()) { + case CastInternalMessage::Type::kAppMessage: + out << ", app_message_namespace=" << message.app_message_namespace() + << ", app_message_body=" << message.app_message_body(); + break; + case CastInternalMessage::Type::kV2Message: + out << ", v2_message_type=" << message.v2_message_type() + << ", v2_message_body=" << message.v2_message_body(); + break; + default: + break; + } + return out << "}"; +} + +} // namespace media_router
diff --git a/chrome/browser/media/router/providers/cast/test_util.h b/chrome/browser/media/router/providers/cast/test_util.h new file mode 100644 index 0000000..b50f4d15 --- /dev/null +++ b/chrome/browser/media/router/providers/cast/test_util.h
@@ -0,0 +1,46 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_CAST_TEST_UTIL_H_ +#define CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_CAST_TEST_UTIL_H_ + +#include <iosfwd> + +#include "base/test/values_test_util.h" +#include "chrome/browser/media/router/providers/cast/cast_internal_message_util.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace media_router { + +std::ostream& operator<<(std::ostream&, CastInternalMessage::Type); +std::ostream& operator<<(std::ostream&, const CastInternalMessage&); + +// Matcher for CastInternalMessage arguments. +MATCHER_P(IsCastInternalMessage, json, "") { + auto message = CastInternalMessage::From(base::test::ParseJson(json)); + DCHECK(message); + if (arg.type() != message->type() || + arg.client_id() != message->client_id() || + arg.sequence_number() != message->sequence_number()) + return false; + + if (arg.has_session_id() && arg.session_id() != message->session_id()) + return false; + + switch (arg.type()) { + case CastInternalMessage::Type::kAppMessage: + return arg.app_message_namespace() == message->app_message_namespace() && + arg.app_message_body() == message->app_message_body(); + case CastInternalMessage::Type::kV2Message: + return arg.v2_message_type() == message->v2_message_type() && + testing::Matches(base::test::IsJson(arg.v2_message_body()))( + message->v2_message_body()); + default: + return true; + } +} + +} // namespace media_router + +#endif // CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_CAST_TEST_UTIL_H_
diff --git a/chrome/browser/media/router/test/test_helper.h b/chrome/browser/media/router/test/test_helper.h index 7873af0..677edd0 100644 --- a/chrome/browser/media/router/test/test_helper.h +++ b/chrome/browser/media/router/test/test_helper.h
@@ -1,3 +1,4 @@ + // Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -5,20 +6,15 @@ #ifndef CHROME_BROWSER_MEDIA_ROUTER_TEST_TEST_HELPER_H_ #define CHROME_BROWSER_MEDIA_ROUTER_TEST_TEST_HELPER_H_ -#include <stddef.h> -#include <stdint.h> - #include <string> #include <vector> #include "base/macros.h" #include "base/test/values_test_util.h" -#include "build/build_config.h" #include "chrome/browser/media/router/issue_manager.h" #include "chrome/browser/media/router/issues_observer.h" #include "chrome/browser/media/router/media_routes_observer.h" #include "chrome/browser/media/router/media_sinks_observer.h" -#include "content/public/browser/presentation_service_delegate.h" #include "testing/gmock/include/gmock/gmock.h" #include "third_party/blink/public/mojom/presentation/presentation.mojom.h" @@ -230,6 +226,8 @@ #endif // !defined(OS_ANDROID) +// Matcher for blink::mojom::PresentationConnectionMessagePtr arguments. +// TODO(jrw): Rename to something like IsPresentationConnectionMessage. MATCHER_P(IsCastMessage, json, "") { return arg->is_message() && base::test::IsJsonMatcher(json).MatchAndExplain( arg->get_message(), result_listener);
diff --git a/chrome/browser/metrics/process_memory_metrics_emitter_unittest.cc b/chrome/browser/metrics/process_memory_metrics_emitter_unittest.cc index 8923d92..aaa8372 100644 --- a/chrome/browser/metrics/process_memory_metrics_emitter_unittest.cc +++ b/chrome/browser/metrics/process_memory_metrics_emitter_unittest.cc
@@ -118,12 +118,13 @@ using memory_instrumentation::mojom::VmRegion; return memory_instrumentation::mojom::OSMemDump::New( - resident_set_kb, private_footprint_kb, shared_footprint_kb + resident_set_kb, resident_set_kb /* peak_resident_set_kb */, + private_footprint_kb, shared_footprint_kb #if defined(OS_LINUX) || defined(OS_ANDROID) , private_swap_footprint_kb #endif - ); + ); } void PopulateBrowserMetrics(GlobalMemoryDumpPtr& global_dump,
diff --git a/chrome/browser/metrics/ukm_background_recorder_browsertest.cc b/chrome/browser/metrics/ukm_background_recorder_browsertest.cc new file mode 100644 index 0000000..270a38e2 --- /dev/null +++ b/chrome/browser/metrics/ukm_background_recorder_browsertest.cc
@@ -0,0 +1,84 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/metrics/ukm_background_recorder_service.h" + +#include "base/optional.h" +#include "base/run_loop.h" +#include "base/time/time.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "components/history/core/browser/history_service.h" +#include "services/metrics/public/cpp/ukm_builders.h" +#include "services/metrics/public/cpp/ukm_source_id.h" +#include "url/gurl.h" +#include "url/origin.h" + +namespace { + +const url::Origin kVisitedOrigin = + url::Origin::Create(GURL("https://foobar.com")); + +void DidGetRecordResult(base::OnceClosure quit_closure, + base::Optional<ukm::SourceId>* out_result, + base::Optional<ukm::SourceId> result) { + *out_result = std::move(result); + std::move(quit_closure).Run(); +} + +} // namespace + +class UkmBackgroundRecorderBrowserTest : public InProcessBrowserTest { + public: + UkmBackgroundRecorderBrowserTest() = default; + ~UkmBackgroundRecorderBrowserTest() override = default; + + void SetUpOnMainThread() override { + background_recorder_service_ = + ukm::UkmBackgroundRecorderFactory::GetForProfile(browser()->profile()); + DCHECK(background_recorder_service_); + + // Adds the URL to the history so that UKM events for this origin are + // recorded. + background_recorder_service_->history_service_->AddPage( + GURL(kVisitedOrigin.GetURL().spec() + "baz"), base::Time::Now(), + history::SOURCE_BROWSED); + } + + protected: + base::Optional<ukm::SourceId> GetSourceId(const url::Origin& origin) { + base::Optional<ukm::SourceId> result; + + base::RunLoop run_loop; + background_recorder_service_->GetBackgroundSourceIdIfAllowed( + origin, + base::BindOnce(&DidGetRecordResult, run_loop.QuitClosure(), &result)); + run_loop.Run(); + + return result; + } + + private: + ukm::UkmBackgroundRecorderService* background_recorder_service_; + + DISALLOW_COPY_AND_ASSIGN(UkmBackgroundRecorderBrowserTest); +}; + +IN_PROC_BROWSER_TEST_F(UkmBackgroundRecorderBrowserTest, + SourceIdReturnedWhenOriginInHistory) { + // Check visited origin. + { + auto source_id = GetSourceId(kVisitedOrigin); + ASSERT_TRUE(source_id); + EXPECT_NE(*source_id, ukm::kInvalidSourceId); + EXPECT_EQ(ukm::GetSourceIdType(*source_id), ukm::SourceIdType::HISTORY_ID); + } + + // Check unvisited origin. + { + auto origin = url::Origin::Create(GURL("https://notvisited.com")); + auto source_id = GetSourceId(origin); + EXPECT_FALSE(source_id); + } +}
diff --git a/chrome/browser/metrics/ukm_background_recorder_service.cc b/chrome/browser/metrics/ukm_background_recorder_service.cc new file mode 100644 index 0000000..69c4172 --- /dev/null +++ b/chrome/browser/metrics/ukm_background_recorder_service.cc
@@ -0,0 +1,98 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/metrics/ukm_background_recorder_service.h" + +#include "base/callback.h" +#include "base/callback_helpers.h" +#include "chrome/browser/history/history_service_factory.h" +#include "chrome/browser/profiles/incognito_helpers.h" +#include "chrome/browser/profiles/profile.h" +#include "components/history/core/browser/history_service.h" +#include "components/keyed_service/content/browser_context_dependency_manager.h" +#include "services/metrics/public/cpp/ukm_builders.h" +#include "services/metrics/public/cpp/ukm_recorder.h" +#include "url/origin.h" + +namespace ukm { + +UkmBackgroundRecorderService::UkmBackgroundRecorderService(Profile* profile) + : history_service_(HistoryServiceFactory::GetForProfile( + profile, + ServiceAccessType::EXPLICIT_ACCESS)), + weak_ptr_factory_(this) { + DCHECK(history_service_); +} + +UkmBackgroundRecorderService::~UkmBackgroundRecorderService() = default; + +void UkmBackgroundRecorderService::Shutdown() { + task_tracker_.TryCancelAll(); +} + +void UkmBackgroundRecorderService::GetBackgroundSourceIdIfAllowed( + const url::Origin& origin, + GetBackgroundSourceIdCallback callback) { + auto visit_callback = base::BindOnce( + &UkmBackgroundRecorderService::DidGetVisibleVisitCount, + weak_ptr_factory_.GetWeakPtr(), origin, std::move(callback)); + + history_service_->GetVisibleVisitCountToHost( + origin.GetURL(), + base::AdaptCallbackForRepeating(std::move(visit_callback)), + &task_tracker_); +} + +void UkmBackgroundRecorderService::DidGetVisibleVisitCount( + const url::Origin& origin, + GetBackgroundSourceIdCallback callback, + bool did_determine, + int num_visits, + base::Time first_visit_time) { + if (!did_determine || !num_visits) { + std::move(callback).Run(base::nullopt); + return; + } + + ukm::SourceId source_id = ukm::ConvertToSourceId( + ukm::AssignNewSourceId(), ukm::SourceIdType::HISTORY_ID); + ukm::UkmRecorder* recorder = ukm::UkmRecorder::Get(); + DCHECK(recorder); + recorder->UpdateSourceURL(source_id, origin.GetURL()); + + std::move(callback).Run(source_id); +} + +// static +UkmBackgroundRecorderFactory* UkmBackgroundRecorderFactory::GetInstance() { + return base::Singleton<UkmBackgroundRecorderFactory>::get(); +} + +// static +UkmBackgroundRecorderService* UkmBackgroundRecorderFactory::GetForProfile( + Profile* profile) { + return static_cast<UkmBackgroundRecorderService*>( + GetInstance()->GetServiceForBrowserContext(profile, /* create= */ true)); +} + +UkmBackgroundRecorderFactory::UkmBackgroundRecorderFactory() + : BrowserContextKeyedServiceFactory( + "UkmBackgroundRecorderService", + BrowserContextDependencyManager::GetInstance()) { + DependsOn(HistoryServiceFactory::GetInstance()); +} + +UkmBackgroundRecorderFactory::~UkmBackgroundRecorderFactory() = default; + +KeyedService* UkmBackgroundRecorderFactory::BuildServiceInstanceFor( + content::BrowserContext* context) const { + return new UkmBackgroundRecorderService(Profile::FromBrowserContext(context)); +} + +content::BrowserContext* UkmBackgroundRecorderFactory::GetBrowserContextToUse( + content::BrowserContext* context) const { + return chrome::GetBrowserContextOwnInstanceInIncognito(context); +} + +} // namespace ukm
diff --git a/chrome/browser/metrics/ukm_background_recorder_service.h b/chrome/browser/metrics/ukm_background_recorder_service.h new file mode 100644 index 0000000..55b89731 --- /dev/null +++ b/chrome/browser/metrics/ukm_background_recorder_service.h
@@ -0,0 +1,101 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_METRICS_UKM_BACKGROUND_RECORDER_SERVICE_H_ +#define CHROME_BROWSER_METRICS_UKM_BACKGROUND_RECORDER_SERVICE_H_ + +#include "base/callback_forward.h" +#include "base/macros.h" +#include "base/memory/singleton.h" +#include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "base/task/cancelable_task_tracker.h" +#include "components/keyed_service/content/browser_context_keyed_service_factory.h" +#include "components/keyed_service/core/keyed_service.h" +#include "services/metrics/public/cpp/ukm_source_id.h" + +class Profile; +class UkmBackgroundRecorderBrowserTest; + +namespace content { +class BrowserContext; +} // namespace content + +namespace history { +class HistoryService; +} // namespace history + +namespace url { +class Origin; +} // namespace url + +namespace ukm { + +// Service for background features that want to record UKM events. This service +// relies on the HistoryService, so that it can check whether the URL of the +// event is present in the Browser's history, and accordingly decide whether to +// generate a new SourceId or not. +class UkmBackgroundRecorderService : public KeyedService { + public: + using GetBackgroundSourceIdCallback = + base::OnceCallback<void(base::Optional<ukm::SourceId>)>; + + // |profile| is needed to access the appropriate services |this| depends on. + explicit UkmBackgroundRecorderService(Profile* profile); + ~UkmBackgroundRecorderService() override; + + void Shutdown() override; + + // Checks whether events related to |origin| can be recorded with UKM, by + // checking if |origin| is present in the profile's history. This + // should be used with background features if there will not be an open + // window at the time of the recording. |callback| will be run with a valid + // source ID if recording is allowed, or a nullopt otherwise. + void GetBackgroundSourceIdIfAllowed(const url::Origin& origin, + GetBackgroundSourceIdCallback callback); + + private: + friend UkmBackgroundRecorderBrowserTest; + + // Callback for querying the history service. |did_determine| is whether the + // history service was able to complete the query, and |num_visits| is the + // number of visits for the provided |origin|. + void DidGetVisibleVisitCount( + const url::Origin& origin, + UkmBackgroundRecorderService::GetBackgroundSourceIdCallback callback, + bool did_determine, + int num_visits, + base::Time first_visit_time); + + history::HistoryService* history_service_; + + // Task tracker used for querying URLs in the history service. + base::CancelableTaskTracker task_tracker_; + + base::WeakPtrFactory<UkmBackgroundRecorderService> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(UkmBackgroundRecorderService); +}; + +class UkmBackgroundRecorderFactory : public BrowserContextKeyedServiceFactory { + public: + static UkmBackgroundRecorderFactory* GetInstance(); + static UkmBackgroundRecorderService* GetForProfile(Profile* profile); + + private: + friend struct base::DefaultSingletonTraits<UkmBackgroundRecorderFactory>; + + UkmBackgroundRecorderFactory(); + ~UkmBackgroundRecorderFactory() override; + + KeyedService* BuildServiceInstanceFor( + content::BrowserContext* context) const override; + + content::BrowserContext* GetBrowserContextToUse( + content::BrowserContext* context) const override; +}; + +} // namespace ukm + +#endif // CHROME_BROWSER_METRICS_UKM_BACKGROUND_RECORDER_SERVICE_H_
diff --git a/chrome/browser/page_load_metrics/observers/previews_ukm_observer.cc b/chrome/browser/page_load_metrics/observers/previews_ukm_observer.cc index 9e3ee38..7abd5da8 100644 --- a/chrome/browser/page_load_metrics/observers/previews_ukm_observer.cc +++ b/chrome/browser/page_load_metrics/observers/previews_ukm_observer.cc
@@ -78,8 +78,8 @@ content::PreviewsState previews_state = previews_user_data->committed_previews_state(); - if (coin_flip_result_ != CoinFlipHoldbackResult::kNotSet) - DCHECK(previews_likely_); + DCHECK(coin_flip_result_ == CoinFlipHoldbackResult::kNotSet || + previews_likely_); if (navigation_handle->GetWebContents()->GetContentsMimeType() == kOfflinePreviewsMimeType) {
diff --git a/chrome/browser/performance_manager/decorators/frozen_frame_aggregator_unittest.cc b/chrome/browser/performance_manager/decorators/frozen_frame_aggregator_unittest.cc index 44efe502..3207e1c 100644 --- a/chrome/browser/performance_manager/decorators/frozen_frame_aggregator_unittest.cc +++ b/chrome/browser/performance_manager/decorators/frozen_frame_aggregator_unittest.cc
@@ -46,7 +46,7 @@ ffa_ = std::make_unique<FrozenFrameAggregator>(); graph()->RegisterObserver(ffa_.get()); process_node_ = CreateNode<ProcessNodeImpl>(); - page_node_ = CreateNode<PageNodeImpl>(nullptr); + page_node_ = CreateNode<PageNodeImpl>(); } void TearDown() override { graph()->UnregisterObserver(ffa_.get()); } @@ -116,7 +116,7 @@ // Create another process and another page. auto proc2 = CreateNode<ProcessNodeImpl>(); - auto page2 = CreateNode<PageNodeImpl>(nullptr); + auto page2 = CreateNode<PageNodeImpl>(); ExpectProcessData(1, 1); // Create a child frame for the first page hosted in the second process.
diff --git a/chrome/browser/performance_manager/graph/frame_node_impl_unittest.cc b/chrome/browser/performance_manager/graph/frame_node_impl_unittest.cc index 4bc6b27..203dd66 100644 --- a/chrome/browser/performance_manager/graph/frame_node_impl_unittest.cc +++ b/chrome/browser/performance_manager/graph/frame_node_impl_unittest.cc
@@ -38,7 +38,7 @@ TEST_F(FrameNodeImplTest, AddFrameHierarchyBasic) { auto process = CreateNode<ProcessNodeImpl>(); - auto page = CreateNode<PageNodeImpl>(nullptr); + auto page = CreateNode<PageNodeImpl>(); auto parent_node = CreateNode<FrameNodeImpl>(process.get(), page.get()); auto child2_node = CreateNode<FrameNodeImpl>(process.get(), page.get(), parent_node.get(), 1); @@ -53,7 +53,7 @@ TEST_F(FrameNodeImplTest, Url) { auto process = CreateNode<ProcessNodeImpl>(); - auto page = CreateNode<PageNodeImpl>(nullptr); + auto page = CreateNode<PageNodeImpl>(); auto frame_node = CreateNode<FrameNodeImpl>(process.get(), page.get()); EXPECT_TRUE(frame_node->url().is_empty()); const GURL url("http://www.foo.com/"); @@ -63,7 +63,7 @@ TEST_F(FrameNodeImplTest, RemoveChildFrame) { auto process = CreateNode<ProcessNodeImpl>(); - auto page = CreateNode<PageNodeImpl>(nullptr); + auto page = CreateNode<PageNodeImpl>(); auto parent_frame_node = CreateNode<FrameNodeImpl>(process.get(), page.get()); auto child_frame_node = CreateNode<FrameNodeImpl>(process.get(), page.get(), parent_frame_node.get(), 1); @@ -83,7 +83,7 @@ TEST_F(FrameNodeImplTest, IsAdFrame) { auto process = CreateNode<ProcessNodeImpl>(); - auto page = CreateNode<PageNodeImpl>(nullptr); + auto page = CreateNode<PageNodeImpl>(); auto frame_node = CreateNode<FrameNodeImpl>(process.get(), page.get()); EXPECT_FALSE(frame_node->is_ad_frame()); frame_node->SetIsAdFrame();
diff --git a/chrome/browser/performance_manager/graph/graph_test_harness.h b/chrome/browser/performance_manager/graph/graph_test_harness.h index 6a408d14..6b233986 100644 --- a/chrome/browser/performance_manager/graph/graph_test_harness.h +++ b/chrome/browser/performance_manager/graph/graph_test_harness.h
@@ -83,6 +83,17 @@ } }; +// A specialized factory function for page nodes that helps fill out some +// common values. +template <> +struct TestNodeWrapper<PageNodeImpl>::Factory { + static std::unique_ptr<PageNodeImpl> Create( + GraphImpl* graph, + const WebContentsProxy& wc_proxy = WebContentsProxy()) { + return std::make_unique<PageNodeImpl>(graph, wc_proxy); + } +}; + // static template <typename NodeClass> template <typename... Args>
diff --git a/chrome/browser/performance_manager/graph/mock_graphs.cc b/chrome/browser/performance_manager/graph/mock_graphs.cc index 83a6a09..de742e9c 100644 --- a/chrome/browser/performance_manager/graph/mock_graphs.cc +++ b/chrome/browser/performance_manager/graph/mock_graphs.cc
@@ -29,7 +29,7 @@ GraphImpl* graph) : system(TestNodeWrapper<SystemNodeImpl>::Create(graph)), process(TestNodeWrapper<TestProcessNodeImpl>::Create(graph)), - page(TestNodeWrapper<PageNodeImpl>::Create(graph, nullptr)), + page(TestNodeWrapper<PageNodeImpl>::Create(graph)), frame(TestNodeWrapper<FrameNodeImpl>::Create(graph, process.get(), page.get())) { @@ -47,7 +47,7 @@ MockMultiplePagesInSingleProcessGraph::MockMultiplePagesInSingleProcessGraph( GraphImpl* graph) : MockSinglePageInSingleProcessGraph(graph), - other_page(TestNodeWrapper<PageNodeImpl>::Create(graph, nullptr)), + other_page(TestNodeWrapper<PageNodeImpl>::Create(graph)), other_frame(TestNodeWrapper<FrameNodeImpl>::Create(graph, process.get(), other_page.get(),
diff --git a/chrome/browser/performance_manager/graph/page_node_impl.cc b/chrome/browser/performance_manager/graph/page_node_impl.cc index cc61c5e..350b82a 100644 --- a/chrome/browser/performance_manager/graph/page_node_impl.cc +++ b/chrome/browser/performance_manager/graph/page_node_impl.cc
@@ -44,9 +44,9 @@ } // namespace PageNodeImpl::PageNodeImpl(GraphImpl* graph, - const base::WeakPtr<WebContentsProxy>& weak_contents) + const WebContentsProxy& contents_proxy) : TypedNodeBase(graph), - contents_proxy_(weak_contents), + contents_proxy_(contents_proxy), visibility_change_time_(PerformanceManagerClock::NowTicks()) { DETACH_FROM_SEQUENCE(sequence_checker_); } @@ -55,7 +55,7 @@ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); } -const base::WeakPtr<WebContentsProxy>& PageNodeImpl::contents_proxy() const { +const WebContentsProxy& PageNodeImpl::contents_proxy() const { return contents_proxy_; }
diff --git a/chrome/browser/performance_manager/graph/page_node_impl.h b/chrome/browser/performance_manager/graph/page_node_impl.h index f027137a..12b060b8 100644 --- a/chrome/browser/performance_manager/graph/page_node_impl.h +++ b/chrome/browser/performance_manager/graph/page_node_impl.h
@@ -14,7 +14,7 @@ #include "chrome/browser/performance_manager/graph/node_attached_data.h" #include "chrome/browser/performance_manager/graph/node_base.h" #include "chrome/browser/performance_manager/observers/graph_observer.h" -#include "chrome/browser/performance_manager/web_contents_proxy.h" +#include "chrome/browser/performance_manager/public/web_contents_proxy.h" #include "services/metrics/public/cpp/ukm_source_id.h" #include "url/gurl.h" @@ -30,13 +30,13 @@ static constexpr NodeTypeEnum Type() { return NodeTypeEnum::kPage; } explicit PageNodeImpl(GraphImpl* graph, - const base::WeakPtr<WebContentsProxy>& contents_proxy); + const WebContentsProxy& contents_proxy); ~PageNodeImpl() override; // Returns the web contents associated with this page node. It is valid to // call this function on any thread but the weak pointer must only be // dereferenced on the UI thread. - const base::WeakPtr<WebContentsProxy>& contents_proxy() const; + const WebContentsProxy& contents_proxy() const; void SetIsLoading(bool is_loading); void SetIsVisible(bool is_visible); @@ -160,8 +160,8 @@ template <typename MapFunction> void ForAllFrameNodes(MapFunction map_function) const; - // A weak pointer to the WebContentsProxy associated with this page. - const base::WeakPtr<WebContentsProxy> contents_proxy_; + // The WebContentsProxy associated with this page. + const WebContentsProxy contents_proxy_; // The main frame nodes of this page. There can be more than one main frame // in a page, among other reasons because during main frame navigation, the
diff --git a/chrome/browser/performance_manager/graph/page_node_impl_unittest.cc b/chrome/browser/performance_manager/graph/page_node_impl_unittest.cc index b955961..b80a180 100644 --- a/chrome/browser/performance_manager/graph/page_node_impl_unittest.cc +++ b/chrome/browser/performance_manager/graph/page_node_impl_unittest.cc
@@ -40,7 +40,7 @@ TEST_F(PageNodeImplTest, AddFrameBasic) { auto process_node = CreateNode<ProcessNodeImpl>(); - auto page_node = CreateNode<PageNodeImpl>(nullptr); + auto page_node = CreateNode<PageNodeImpl>(); auto parent_frame = CreateNode<FrameNodeImpl>(process_node.get(), page_node.get()); auto child1_frame = CreateNode<FrameNodeImpl>( @@ -54,7 +54,7 @@ TEST_F(PageNodeImplTest, RemoveFrame) { auto process_node = CreateNode<ProcessNodeImpl>(); - auto page_node = CreateNode<PageNodeImpl>(nullptr); + auto page_node = CreateNode<PageNodeImpl>(); auto frame_node = CreateNode<FrameNodeImpl>(process_node.get(), page_node.get(), nullptr, 0); @@ -202,7 +202,7 @@ TestNodeWrapper<ProcessNodeImpl> process = TestNodeWrapper<ProcessNodeImpl>::Create(mock_graph); TestNodeWrapper<PageNodeImpl> page = - TestNodeWrapper<PageNodeImpl>::Create(mock_graph, nullptr); + TestNodeWrapper<PageNodeImpl>::Create(mock_graph); // Check the initial values before any frames are added. EXPECT_EQ(0u, page->GetInterventionPolicyFramesReportedForTesting()); @@ -343,7 +343,7 @@ TestNodeWrapper<ProcessNodeImpl> process = TestNodeWrapper<ProcessNodeImpl>::Create(mock_graph); TestNodeWrapper<PageNodeImpl> page = - TestNodeWrapper<PageNodeImpl>::Create(mock_graph, nullptr); + TestNodeWrapper<PageNodeImpl>::Create(mock_graph); // Create two frames and immediately attach them to the page. TestNodeWrapper<FrameNodeImpl> f0 = TestNodeWrapper<FrameNodeImpl>::Create(
diff --git a/chrome/browser/performance_manager/observers/graph_observer_unittest.cc b/chrome/browser/performance_manager/observers/graph_observer_unittest.cc index 81dbe513..a5221b1 100644 --- a/chrome/browser/performance_manager/observers/graph_observer_unittest.cc +++ b/chrome/browser/performance_manager/observers/graph_observer_unittest.cc
@@ -59,7 +59,7 @@ { auto process_node = CreateNode<ProcessNodeImpl>(); - auto page_node = CreateNode<PageNodeImpl>(nullptr); + auto page_node = CreateNode<PageNodeImpl>(); auto root_frame_node = CreateNode<FrameNodeImpl>(process_node.get(), page_node.get()); auto frame_node = CreateNode<FrameNodeImpl>(
diff --git a/chrome/browser/performance_manager/observers/metrics_collector_unittest.cc b/chrome/browser/performance_manager/observers/metrics_collector_unittest.cc index 68a1071..efde6cd8 100644 --- a/chrome/browser/performance_manager/observers/metrics_collector_unittest.cc +++ b/chrome/browser/performance_manager/observers/metrics_collector_unittest.cc
@@ -62,7 +62,7 @@ }; TEST_F(MAYBE_MetricsCollectorTest, FromBackgroundedToFirstTitleUpdatedUMA) { - auto page_node = CreateNode<PageNodeImpl>(nullptr); + auto page_node = CreateNode<PageNodeImpl>(); page_node->OnMainFrameNavigationCommitted(PerformanceManagerClock::NowTicks(), kDummyID, kDummyUrl); @@ -95,7 +95,7 @@ TEST_F(MAYBE_MetricsCollectorTest, FromBackgroundedToFirstTitleUpdatedUMA5MinutesTimeout) { - auto page_node = CreateNode<PageNodeImpl>(nullptr); + auto page_node = CreateNode<PageNodeImpl>(); page_node->OnMainFrameNavigationCommitted(PerformanceManagerClock::NowTicks(), kDummyID, kDummyUrl); @@ -114,7 +114,7 @@ TEST_F(MAYBE_MetricsCollectorTest, FromBackgroundedToFirstNonPersistentNotificationCreatedUMA) { auto process_node = CreateNode<ProcessNodeImpl>(); - auto page_node = CreateNode<PageNodeImpl>(nullptr); + auto page_node = CreateNode<PageNodeImpl>(); auto frame_node = CreateNode<FrameNodeImpl>(process_node.get(), page_node.get()); @@ -151,7 +151,7 @@ MAYBE_MetricsCollectorTest, FromBackgroundedToFirstNonPersistentNotificationCreatedUMA5MinutesTimeout) { auto process_node = CreateNode<ProcessNodeImpl>(); - auto page_node = CreateNode<PageNodeImpl>(nullptr); + auto page_node = CreateNode<PageNodeImpl>(); auto frame_node = CreateNode<FrameNodeImpl>(process_node.get(), page_node.get()); @@ -170,7 +170,7 @@ } TEST_F(MAYBE_MetricsCollectorTest, FromBackgroundedToFirstFaviconUpdatedUMA) { - auto page_node = CreateNode<PageNodeImpl>(nullptr); + auto page_node = CreateNode<PageNodeImpl>(); page_node->OnMainFrameNavigationCommitted(PerformanceManagerClock::NowTicks(), kDummyID, kDummyUrl); @@ -203,7 +203,7 @@ TEST_F(MAYBE_MetricsCollectorTest, FromBackgroundedToFirstFaviconUpdatedUMA5MinutesTimeout) { - auto page_node = CreateNode<PageNodeImpl>(nullptr); + auto page_node = CreateNode<PageNodeImpl>(); page_node->OnMainFrameNavigationCommitted(PerformanceManagerClock::NowTicks(), kDummyID, kDummyUrl); @@ -222,7 +222,7 @@ // Flaky test: https://crbug.com/833028 TEST_F(MAYBE_MetricsCollectorTest, ResponsivenessMetric) { auto process_node = CreateNode<ProcessNodeImpl>(); - auto page_node = CreateNode<PageNodeImpl>(nullptr); + auto page_node = CreateNode<PageNodeImpl>(); auto frame_node = CreateNode<FrameNodeImpl>(process_node.get(), page_node.get());
diff --git a/chrome/browser/performance_manager/performance_manager.cc b/chrome/browser/performance_manager/performance_manager.cc index 100ae26..9445cb25 100644 --- a/chrome/browser/performance_manager/performance_manager.cc +++ b/chrome/browser/performance_manager/performance_manager.cc
@@ -104,7 +104,7 @@ } std::unique_ptr<PageNodeImpl> PerformanceManager::CreatePageNode( - const base::WeakPtr<WebContentsProxy>& contents_proxy) { + const WebContentsProxy& contents_proxy) { return CreateNodeImpl<PageNodeImpl>(base::OnceCallback<void(PageNodeImpl*)>(), contents_proxy); }
diff --git a/chrome/browser/performance_manager/performance_manager.h b/chrome/browser/performance_manager/performance_manager.h index 883ffff..7121d3f 100644 --- a/chrome/browser/performance_manager/performance_manager.h +++ b/chrome/browser/performance_manager/performance_manager.h
@@ -16,7 +16,7 @@ #include "base/sequenced_task_runner.h" #include "chrome/browser/performance_manager/graph/graph_impl.h" #include "chrome/browser/performance_manager/performance_manager.h" -#include "chrome/browser/performance_manager/web_contents_proxy.h" +#include "chrome/browser/performance_manager/public/web_contents_proxy.h" #include "chrome/browser/performance_manager/webui_graph_dump_impl.h" #include "services/resource_coordinator/public/mojom/coordination_unit.mojom.h" #include "services/service_manager/public/cpp/bind_source_info.h" @@ -82,7 +82,7 @@ const base::UnguessableToken& dev_tools_token, FrameNodeCreationCallback creation_callback); std::unique_ptr<PageNodeImpl> CreatePageNode( - const base::WeakPtr<WebContentsProxy>& contents_proxy); + const WebContentsProxy& contents_proxy); std::unique_ptr<ProcessNodeImpl> CreateProcessNode(); // Destroys a node returned from the creation functions above.
diff --git a/chrome/browser/performance_manager/performance_manager_tab_helper.cc b/chrome/browser/performance_manager/performance_manager_tab_helper.cc index 52c8ab2..4e0b16c2 100644 --- a/chrome/browser/performance_manager/performance_manager_tab_helper.cc +++ b/chrome/browser/performance_manager/performance_manager_tab_helper.cc
@@ -33,7 +33,8 @@ : content::WebContentsObserver(web_contents), performance_manager_(PerformanceManager::GetInstance()), weak_factory_(this) { - page_node_ = performance_manager_->CreatePageNode(weak_factory_.GetWeakPtr()); + page_node_ = performance_manager_->CreatePageNode( + WebContentsProxy(weak_factory_.GetWeakPtr())); // Make sure to set the visibility property when we create // |page_resource_coordinator_|.
diff --git a/chrome/browser/performance_manager/performance_manager_tab_helper.h b/chrome/browser/performance_manager/performance_manager_tab_helper.h index 927cdb45..32087fc8 100644 --- a/chrome/browser/performance_manager/performance_manager_tab_helper.h +++ b/chrome/browser/performance_manager/performance_manager_tab_helper.h
@@ -12,7 +12,7 @@ #include "base/macros.h" #include "base/memory/weak_ptr.h" -#include "chrome/browser/performance_manager/web_contents_proxy.h" +#include "chrome/browser/performance_manager/web_contents_proxy_impl.h" #include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_user_data.h" #include "services/metrics/public/cpp/ukm_source_id.h" @@ -31,7 +31,7 @@ class PerformanceManagerTabHelper : public content::WebContentsObserver, public content::WebContentsUserData<PerformanceManagerTabHelper>, - public WebContentsProxy { + public WebContentsProxyImpl { public: // Detaches all instances from their WebContents and destroys them. static void DetachAndDestroyAll(); @@ -58,7 +58,7 @@ const std::string& interface_name, mojo::ScopedMessagePipeHandle* interface_pipe) override; - // WebContentsProxy overrides. + // WebContentsProxyImpl overrides. content::WebContents* GetWebContents() const override; int64_t LastNavigationId() const override; @@ -66,7 +66,7 @@ private: friend class content::WebContentsUserData<PerformanceManagerTabHelper>; - friend class WebContentsProxy; + friend class WebContentsProxyImpl; explicit PerformanceManagerTabHelper(content::WebContents* web_contents);
diff --git a/chrome/browser/performance_manager/performance_manager_unittest.cc b/chrome/browser/performance_manager/performance_manager_unittest.cc index fe9ef22..7376dd9 100644 --- a/chrome/browser/performance_manager/performance_manager_unittest.cc +++ b/chrome/browser/performance_manager/performance_manager_unittest.cc
@@ -57,7 +57,7 @@ performance_manager()->CreateProcessNode(); EXPECT_NE(nullptr, process_node.get()); std::unique_ptr<PageNodeImpl> page_node = - performance_manager()->CreatePageNode(nullptr); + performance_manager()->CreatePageNode(WebContentsProxy()); EXPECT_NE(nullptr, page_node.get()); // Create a node of each type. @@ -77,7 +77,7 @@ std::unique_ptr<ProcessNodeImpl> process_node = performance_manager()->CreateProcessNode(); std::unique_ptr<PageNodeImpl> page_node = - performance_manager()->CreatePageNode(nullptr); + performance_manager()->CreatePageNode(WebContentsProxy()); std::unique_ptr<FrameNodeImpl> parent1_frame = performance_manager()->CreateFrameNode(process_node.get(), @@ -120,7 +120,7 @@ TEST_F(PerformanceManagerTest, CallOnGraph) { // Create a page node for something to target. std::unique_ptr<PageNodeImpl> page_node = - performance_manager()->CreatePageNode(nullptr); + performance_manager()->CreatePageNode(WebContentsProxy()); PerformanceManager::GraphCallback graph_callback = base::BindLambdaForTesting( [&page_node](GraphImpl* graph) { EXPECT_EQ(page_node->graph(), graph); });
diff --git a/chrome/browser/performance_manager/public/web_contents_proxy.h b/chrome/browser/performance_manager/public/web_contents_proxy.h new file mode 100644 index 0000000..a7fb633c --- /dev/null +++ b/chrome/browser/performance_manager/public/web_contents_proxy.h
@@ -0,0 +1,56 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_PUBLIC_WEB_CONTENTS_PROXY_H_ +#define CHROME_BROWSER_PERFORMANCE_MANAGER_PUBLIC_WEB_CONTENTS_PROXY_H_ + +#include "base/memory/weak_ptr.h" + +namespace content { +class WebContents; +} // namespace content + +namespace performance_manager { + +class PerformanceManagerTabHelper; +class WebContentsProxyImpl; + +// A WebContentsProxy is used to post messages out of the performance +// manager sequence that are bound for a WebContents running on the UI thread. +// The object is bound to the UI thread. A WebContentsProxy is conceputally +// equivalent to a WeakPtr<WebContents>. Copy and assignment are explicitly +// allowed for this object. +class WebContentsProxy { + public: + WebContentsProxy(); + WebContentsProxy(const WebContentsProxy& other); + WebContentsProxy(WebContentsProxy&& other); + ~WebContentsProxy(); + + WebContentsProxy& operator=(const WebContentsProxy& other); + WebContentsProxy& operator=(WebContentsProxy&& other); + + // Allows resolving this proxy to the underlying WebContents. This must only + // be called on the UI thread. + content::WebContents* Get() const; + + // Returns the ID of the last committed navigation in the main frame of the + // web contents. The return value of this is only meaningful if Get() + // returns a non-null value, otherwise it will always return the sentinel + // value of 0 to indicate an invalid navigation ID. This must only be called + // on the UI thread. + int64_t LastNavigationId() const; + + protected: + friend class PerformanceManagerTabHelper; + + explicit WebContentsProxy(const base::WeakPtr<WebContentsProxyImpl>& impl); + + private: + base::WeakPtr<WebContentsProxyImpl> impl_; +}; + +} // namespace performance_manager + +#endif // CHROME_BROWSER_PERFORMANCE_MANAGER_PUBLIC_WEB_CONTENTS_PROXY_H_
diff --git a/chrome/browser/performance_manager/web_contents_proxy.cc b/chrome/browser/performance_manager/web_contents_proxy.cc index ec1adda..da115f7 100644 --- a/chrome/browser/performance_manager/web_contents_proxy.cc +++ b/chrome/browser/performance_manager/web_contents_proxy.cc
@@ -2,11 +2,48 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/performance_manager/web_contents_proxy.h" +#include "chrome/browser/performance_manager/public/web_contents_proxy.h" + +#include "chrome/browser/performance_manager/web_contents_proxy_impl.h" namespace performance_manager { WebContentsProxy::WebContentsProxy() = default; + +WebContentsProxy::WebContentsProxy(const WebContentsProxy& other) + : impl_(other.impl_) {} + +WebContentsProxy::WebContentsProxy(WebContentsProxy&& other) + : impl_(std::move(other.impl_)) {} + WebContentsProxy::~WebContentsProxy() = default; +WebContentsProxy& WebContentsProxy::operator=(const WebContentsProxy& other) { + impl_ = other.impl_; + return *this; +} + +WebContentsProxy& WebContentsProxy::operator=(WebContentsProxy&& other) { + impl_ = std::move(other.impl_); + return *this; +} + +content::WebContents* WebContentsProxy::Get() const { + auto* proxy = impl_.get(); + if (!proxy) + return nullptr; + return proxy->GetWebContents(); +} + +int64_t WebContentsProxy::LastNavigationId() const { + auto* proxy = impl_.get(); + if (!proxy) + return 0; + return proxy->LastNavigationId(); +} + +WebContentsProxy::WebContentsProxy( + const base::WeakPtr<WebContentsProxyImpl>& impl) + : impl_(impl) {} + } // namespace performance_manager
diff --git a/chrome/browser/performance_manager/web_contents_proxy.h b/chrome/browser/performance_manager/web_contents_proxy.h deleted file mode 100644 index c6032d5..0000000 --- a/chrome/browser/performance_manager/web_contents_proxy.h +++ /dev/null
@@ -1,41 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_WEB_CONTENTS_PROXY_H_ -#define CHROME_BROWSER_PERFORMANCE_MANAGER_WEB_CONTENTS_PROXY_H_ - -#include <cstdint> - -#include "base/macros.h" - -namespace content { -class WebContents; -} // namespace content - -namespace performance_manager { - -// A WebContentsProxy is used to post messages out of the performance manager -// sequence that are bound for a WebContents running on the UI thread. The -// object is bound to the UI thread. A WeakPtr<WebContentsProxy> is effectively -// equivalent to a WeakPtr<WebContents>. -class WebContentsProxy { - public: - WebContentsProxy(); - virtual ~WebContentsProxy(); - - // Allows resolving this proxy to the underlying WebContents. This must only - // be called on the UI thread. - virtual content::WebContents* GetWebContents() const = 0; - - // Returns the ID of the last committed navigation in the main frame of the - // web contents. This must only be called on the UI thread. - virtual int64_t LastNavigationId() const = 0; - - private: - DISALLOW_COPY_AND_ASSIGN(WebContentsProxy); -}; - -} // namespace performance_manager - -#endif // CHROME_BROWSER_PERFORMANCE_MANAGER_WEB_CONTENTS_PROXY_H_
diff --git a/chrome/browser/performance_manager/web_contents_proxy_impl.cc b/chrome/browser/performance_manager/web_contents_proxy_impl.cc new file mode 100644 index 0000000..1d11c1f --- /dev/null +++ b/chrome/browser/performance_manager/web_contents_proxy_impl.cc
@@ -0,0 +1,12 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/performance_manager/web_contents_proxy_impl.h" + +namespace performance_manager { + +WebContentsProxyImpl::WebContentsProxyImpl() = default; +WebContentsProxyImpl::~WebContentsProxyImpl() = default; + +} // namespace performance_manager
diff --git a/chrome/browser/performance_manager/web_contents_proxy_impl.h b/chrome/browser/performance_manager/web_contents_proxy_impl.h new file mode 100644 index 0000000..e8c8cf2 --- /dev/null +++ b/chrome/browser/performance_manager/web_contents_proxy_impl.h
@@ -0,0 +1,44 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_WEB_CONTENTS_PROXY_IMPL_H_ +#define CHROME_BROWSER_PERFORMANCE_MANAGER_WEB_CONTENTS_PROXY_IMPL_H_ + +#include <cstdint> + +#include "base/macros.h" + +namespace content { +class WebContents; +} // namespace content + +namespace performance_manager { + +// A WebContentsProxyImpl is used to post messages out of the performance +// manager sequence that are bound for a WebContents running on the UI thread. +// The object is bound to the UI thread. A WeakPtr<WebContentsProxyImpl> is +// effectively equivalent to a WeakPtr<WebContents>. +// +// This class is opaquely embedded in a WebContentsProxy, which hides the fact +// that a weak pointer is being used under the hood. +class WebContentsProxyImpl { + public: + WebContentsProxyImpl(); + virtual ~WebContentsProxyImpl(); + + // Allows resolving this proxy to the underlying WebContents. This must only + // be called on the UI thread. + virtual content::WebContents* GetWebContents() const = 0; + + // Returns the ID of the last committed navigation in the main frame of the + // web contents. This must only be called on the UI thread. + virtual int64_t LastNavigationId() const = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(WebContentsProxyImpl); +}; + +} // namespace performance_manager + +#endif // CHROME_BROWSER_PERFORMANCE_MANAGER_WEB_CONTENTS_PROXY_IMPL_H_
diff --git a/chrome/browser/performance_manager/web_contents_proxy_unittest.cc b/chrome/browser/performance_manager/web_contents_proxy_unittest.cc index fd640f9..3d6076c 100644 --- a/chrome/browser/performance_manager/web_contents_proxy_unittest.cc +++ b/chrome/browser/performance_manager/web_contents_proxy_unittest.cc
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/performance_manager/web_contents_proxy.h" +#include "chrome/browser/performance_manager/public/web_contents_proxy.h" #include "base/run_loop.h" #include "base/task/post_task.h" @@ -34,11 +34,9 @@ content::WebContents* proxy_contents = nullptr; auto deref_proxy = base::BindLambdaForTesting( - [&proxy_contents](const base::WeakPtr<WebContentsProxy>& proxy, + [&proxy_contents](const WebContentsProxy& proxy, base::RepeatingClosure quit_loop) { - proxy_contents = nullptr; - if (proxy) - proxy_contents = proxy.get()->GetWebContents(); + proxy_contents = proxy.Get(); quit_loop.Run(); });
diff --git a/chrome/browser/previews/previews_content_util.cc b/chrome/browser/previews/previews_content_util.cc index 1261c97b..4477e31 100644 --- a/chrome/browser/previews/previews_content_util.cc +++ b/chrome/browser/previews/previews_content_util.cc
@@ -455,8 +455,7 @@ if (!HasEnabledPreviews(initial_state & kPreCommitPreviews)) return initial_state; - if (previews_data->random_coin_flip_for_navigation() || - params::ShouldOverrideCoinFlipHoldbackResult()) { + if (previews_data->CoinFlipForNavigation()) { // Holdback all previews. It is possible that some number of client previews // will also be held back here. However, since a before-commit preview was // likely, we turn off all of them to make analysis simpler and this code @@ -487,12 +486,10 @@ if (!previews_data) return initial_state; - if (!HasEnabledPreviews(initial_state)) { + if (!HasEnabledPreviews(initial_state)) return initial_state; - } - if (previews_data->random_coin_flip_for_navigation() || - params::ShouldOverrideCoinFlipHoldbackResult()) { + if (previews_data->CoinFlipForNavigation()) { // No pre-commit previews should be set, since such a preview would have // already committed and we don't want to incorrectly clear that state. If // it did, at least make everything functionally correct.
diff --git a/chrome/browser/previews/previews_content_util_unittest.cc b/chrome/browser/previews/previews_content_util_unittest.cc index 894bde2..2618c546 100644 --- a/chrome/browser/previews/previews_content_util_unittest.cc +++ b/chrome/browser/previews/previews_content_util_unittest.cc
@@ -590,7 +590,6 @@ bool enable_feature; // True maps to previews::CoinFlipHoldbackResult::kHoldback. bool set_random_coin_flip_for_navigation; - bool set_coin_flip_override; previews::CoinFlipHoldbackResult want_coin_flip_result; content::PreviewsState initial_state; content::PreviewsState want_returned; @@ -600,7 +599,6 @@ .msg = "Feature disabled, no affect, heads", .enable_feature = false, .set_random_coin_flip_for_navigation = true, - .set_coin_flip_override = false, .want_coin_flip_result = previews::CoinFlipHoldbackResult::kNotSet, .initial_state = content::CLIENT_LOFI_ON, .want_returned = content::CLIENT_LOFI_ON, @@ -609,16 +607,6 @@ .msg = "Feature disabled, no affect, tails", .enable_feature = false, .set_random_coin_flip_for_navigation = false, - .set_coin_flip_override = false, - .want_coin_flip_result = previews::CoinFlipHoldbackResult::kNotSet, - .initial_state = content::CLIENT_LOFI_ON, - .want_returned = content::CLIENT_LOFI_ON, - }, - { - .msg = "Feature disabled, no affect, forced override", - .enable_feature = false, - .set_random_coin_flip_for_navigation = false, - .set_coin_flip_override = true, .want_coin_flip_result = previews::CoinFlipHoldbackResult::kNotSet, .initial_state = content::CLIENT_LOFI_ON, .want_returned = content::CLIENT_LOFI_ON, @@ -628,17 +616,6 @@ "on true coin flip", .enable_feature = true, .set_random_coin_flip_for_navigation = true, - .set_coin_flip_override = false, - .want_coin_flip_result = previews::CoinFlipHoldbackResult::kNotSet, - .initial_state = content::CLIENT_LOFI_ON, - .want_returned = content::CLIENT_LOFI_ON, - }, - { - .msg = "After-commit decided previews are not affected before commit " - "on forced override", - .enable_feature = true, - .set_random_coin_flip_for_navigation = false, - .set_coin_flip_override = true, .want_coin_flip_result = previews::CoinFlipHoldbackResult::kNotSet, .initial_state = content::CLIENT_LOFI_ON, .want_returned = content::CLIENT_LOFI_ON, @@ -648,7 +625,6 @@ "on false coin flip", .enable_feature = true, .set_random_coin_flip_for_navigation = false, - .set_coin_flip_override = false, .want_coin_flip_result = previews::CoinFlipHoldbackResult::kNotSet, .initial_state = content::CLIENT_LOFI_ON, .want_returned = content::CLIENT_LOFI_ON, @@ -658,17 +634,6 @@ "Before-commit decided previews are affected on true coin flip", .enable_feature = true, .set_random_coin_flip_for_navigation = true, - .set_coin_flip_override = false, - .want_coin_flip_result = previews::CoinFlipHoldbackResult::kHoldback, - .initial_state = content::OFFLINE_PAGE_ON, - .want_returned = content::PREVIEWS_OFF, - }, - { - .msg = - "Before-commit decided previews are affected on forced override", - .enable_feature = true, - .set_random_coin_flip_for_navigation = false, - .set_coin_flip_override = true, .want_coin_flip_result = previews::CoinFlipHoldbackResult::kHoldback, .initial_state = content::OFFLINE_PAGE_ON, .want_returned = content::PREVIEWS_OFF, @@ -677,7 +642,6 @@ .msg = "Before-commit decided previews are logged on false coin flip", .enable_feature = true, .set_random_coin_flip_for_navigation = false, - .set_coin_flip_override = false, .want_coin_flip_result = previews::CoinFlipHoldbackResult::kAllowed, .initial_state = content::OFFLINE_PAGE_ON, .want_returned = content::OFFLINE_PAGE_ON, @@ -688,18 +652,6 @@ "both exist", .enable_feature = true, .set_random_coin_flip_for_navigation = true, - .set_coin_flip_override = false, - .want_coin_flip_result = previews::CoinFlipHoldbackResult::kHoldback, - .initial_state = content::OFFLINE_PAGE_ON | content::CLIENT_LOFI_ON, - .want_returned = content::PREVIEWS_OFF, - }, - { - .msg = - "Forced override impacts both pre and post commit previews when " - "both exist", - .enable_feature = true, - .set_random_coin_flip_for_navigation = false, - .set_coin_flip_override = true, .want_coin_flip_result = previews::CoinFlipHoldbackResult::kHoldback, .initial_state = content::OFFLINE_PAGE_ON | content::CLIENT_LOFI_ON, .want_returned = content::PREVIEWS_OFF, @@ -709,7 +661,6 @@ "both exist", .enable_feature = true, .set_random_coin_flip_for_navigation = false, - .set_coin_flip_override = false, .want_coin_flip_result = previews::CoinFlipHoldbackResult::kAllowed, .initial_state = content::OFFLINE_PAGE_ON | content::CLIENT_LOFI_ON, .want_returned = content::OFFLINE_PAGE_ON | content::CLIENT_LOFI_ON, @@ -724,15 +675,15 @@ // So don't enable the feature until afterwards. content::NavigationHandle* handle = StartNavigation(); - GetPreviewsUserData(handle)->SetRandomCoinFlipForNavigationForTesting( - test_case.set_random_coin_flip_for_navigation); - base::test::ScopedFeatureList scoped_feature_list; if (test_case.enable_feature) { scoped_feature_list.InitAndEnableFeatureWithParameters( previews::features::kCoinFlipHoldback, {{"force_coin_flip_always_holdback", - test_case.set_coin_flip_override ? "true" : "false"}}); + test_case.set_random_coin_flip_for_navigation ? "true" : "false"}, + {"force_coin_flip_always_allow", + !test_case.set_random_coin_flip_for_navigation ? "true" + : "false"}}); } else { scoped_feature_list.InitAndDisableFeature( previews::features::kCoinFlipHoldback); @@ -752,7 +703,6 @@ std::string msg; bool enable_feature; bool set_random_coin_flip_for_navigation; - bool set_coin_flip_override; previews::CoinFlipHoldbackResult want_coin_flip_result; content::PreviewsState initial_state; content::PreviewsState want_returned; @@ -762,7 +712,6 @@ .msg = "Feature disabled, no affect, heads", .enable_feature = false, .set_random_coin_flip_for_navigation = true, - .set_coin_flip_override = false, .want_coin_flip_result = previews::CoinFlipHoldbackResult::kNotSet, .initial_state = content::CLIENT_LOFI_ON, .want_returned = content::CLIENT_LOFI_ON, @@ -771,16 +720,6 @@ .msg = "Feature disabled, no affect, tails", .enable_feature = false, .set_random_coin_flip_for_navigation = false, - .set_coin_flip_override = false, - .want_coin_flip_result = previews::CoinFlipHoldbackResult::kNotSet, - .initial_state = content::CLIENT_LOFI_ON, - .want_returned = content::CLIENT_LOFI_ON, - }, - { - .msg = "Feature disabled, no affect, forced override", - .enable_feature = false, - .set_random_coin_flip_for_navigation = false, - .set_coin_flip_override = true, .want_coin_flip_result = previews::CoinFlipHoldbackResult::kNotSet, .initial_state = content::CLIENT_LOFI_ON, .want_returned = content::CLIENT_LOFI_ON, @@ -789,16 +728,6 @@ .msg = "Holdback enabled previews", .enable_feature = true, .set_random_coin_flip_for_navigation = true, - .set_coin_flip_override = false, - .want_coin_flip_result = previews::CoinFlipHoldbackResult::kHoldback, - .initial_state = content::CLIENT_LOFI_ON, - .want_returned = content::PREVIEWS_OFF, - }, - { - .msg = "Holdback enabled previews via override", - .enable_feature = true, - .set_random_coin_flip_for_navigation = false, - .set_coin_flip_override = true, .want_coin_flip_result = previews::CoinFlipHoldbackResult::kHoldback, .initial_state = content::CLIENT_LOFI_ON, .want_returned = content::PREVIEWS_OFF, @@ -807,7 +736,6 @@ .msg = "Log enabled previews", .enable_feature = true, .set_random_coin_flip_for_navigation = false, - .set_coin_flip_override = false, .want_coin_flip_result = previews::CoinFlipHoldbackResult::kAllowed, .initial_state = content::CLIENT_LOFI_ON, .want_returned = content::CLIENT_LOFI_ON, @@ -822,15 +750,15 @@ // So don't enable the feature until afterwards. content::NavigationHandle* handle = StartNavigationAndReadyCommit(); - GetPreviewsUserData(handle)->SetRandomCoinFlipForNavigationForTesting( - test_case.set_random_coin_flip_for_navigation); - base::test::ScopedFeatureList scoped_feature_list; if (test_case.enable_feature) { scoped_feature_list.InitAndEnableFeatureWithParameters( previews::features::kCoinFlipHoldback, {{"force_coin_flip_always_holdback", - test_case.set_coin_flip_override ? "true" : "false"}}); + test_case.set_random_coin_flip_for_navigation ? "true" : "false"}, + {"force_coin_flip_always_allow", + !test_case.set_random_coin_flip_for_navigation ? "true" + : "false"}}); } else { scoped_feature_list.InitAndDisableFeature( previews::features::kCoinFlipHoldback);
diff --git a/chrome/browser/previews/previews_lite_page_browsertest.cc b/chrome/browser/previews/previews_lite_page_browsertest.cc index b41b8bf..6652d2d 100644 --- a/chrome/browser/previews/previews_lite_page_browsertest.cc +++ b/chrome/browser/previews/previews_lite_page_browsertest.cc
@@ -70,6 +70,8 @@ #include "components/previews/core/previews_features.h" #include "components/previews/core/previews_lite_page_redirect.h" #include "components/previews/core/previews_switches.h" +#include "components/ukm/content/source_url_recorder.h" +#include "components/ukm/test_ukm_recorder.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/web_contents.h" #include "content/public/common/page_type.h" @@ -79,6 +81,8 @@ #include "net/nqe/effective_connection_type.h" #include "net/test/embedded_test_server/http_request.h" #include "net/test/embedded_test_server/http_response.h" +#include "services/metrics/public/cpp/ukm_builders.h" +#include "services/metrics/public/cpp/ukm_source.h" #include "services/network/public/cpp/features.h" #include "services/network/test/test_network_quality_tracker.h" #include "ui/base/l10n/l10n_util.h" @@ -93,7 +97,11 @@ // This should match the value in //components/google/core/common/google_util.cc // so that the X-Client-Data header is sent for subresources. const char kPreviewsHost[] = "litepages.googlezip.net"; -} + +// A host that is blacklisted for Lite Page Redirect previews and won't trigger +// on it. +const char kBlacklistedHost[] = "blacklisted.com"; +} // namespace class PreviewsLitePageServerBrowserTest : public InProcessBrowserTest, @@ -120,9 +128,9 @@ // Previews server will respond with HTTP 403. kAuthFailure = 4, - // Previews server will respond with HTTP 307 to a non-preview page and set - // the host-blacklist header value. - kHostBlacklist = 5, + // Previews server will respond with HTTP 307 bypass to a non-preview page + // and set the host-blacklist header value. + kBypassAndBlacklistOriginHost = 5, // Previews server will respond with HTTP 200 and a content body that loads // a subresource. When the subresource is loaded, |subresources_requested|_ @@ -170,6 +178,10 @@ https_server_->GetURL(kOriginHost, "/previews/noscript_test.html"); ASSERT_TRUE(https_url_.SchemeIs(url::kHttpsScheme)); + blacklisted_https_url_ = + https_server_->GetURL(kBlacklistedHost, "/previews/noscript_test.html"); + ASSERT_TRUE(blacklisted_https_url_.SchemeIs(url::kHttpsScheme)); + https_to_https_redirect_url_ = https_server_->GetURL(kOriginHost, "/previews/to_https_redirect.html"); ASSERT_TRUE(https_to_https_redirect_url_.SchemeIs(url::kHttpsScheme)); @@ -293,6 +305,9 @@ PreviewsLitePageDecider* decider = previews_service->previews_lite_page_decider(); decider->SetUserHasSeenUINotification(); + + decider->BlacklistBypassedHost(kBlacklistedHost, + base::TimeDelta::FromHours(1)); } void InitializeOptimizationHints() { @@ -534,6 +549,8 @@ virtual GURL previews_server_url() const { return previews_server_url_; } + const GURL& blacklisted_https_url() const { return blacklisted_https_url_; } + const GURL& https_url() const { return https_url_; } const GURL& base_https_lite_page_url() const { return base_https_lite_page_url_; @@ -741,7 +758,7 @@ break; case kRedirectNonPreview: response->set_code(net::HTTP_TEMPORARY_REDIRECT); - response->AddCustomHeader("Location", HttpLitePageURL(kSuccess).spec()); + response->AddCustomHeader("Location", blacklisted_https_url().spec()); break; case kRedirectPreview: response->set_code(net::HTTP_TEMPORARY_REDIRECT); @@ -759,13 +776,13 @@ // test server will respond with HTTP 400. response->AddCustomHeader("Location", HttpsLitePageURL(kBypass).spec()); break; - case kHostBlacklist: + case kBypassAndBlacklistOriginHost: response->set_code(net::HTTP_TEMPORARY_REDIRECT); // This will not cause a redirect loop because on following this // redirect, the URL will no longer be a preview URL and the embedded // test server will respond with HTTP 400. - response->AddCustomHeader("Location", - HttpsLitePageURL(kHostBlacklist).spec()); + response->AddCustomHeader( + "Location", HttpsLitePageURL(kBypassAndBlacklistOriginHost).spec()); response->AddCustomHeader("chrome-proxy", "host-blacklisted"); break; case kAuthFailure: @@ -803,6 +820,7 @@ std::unique_ptr<net::EmbeddedTestServer> slow_http_server_; std::unique_ptr<net::EmbeddedTestServer> pingback_server_; GURL https_url_; + GURL blacklisted_https_url_; GURL base_https_lite_page_url_; GURL https_media_url_; GURL http_url_; @@ -1205,7 +1223,8 @@ // Verify the preview is not triggered when the server responds with bypass // 307 and host-blacklist. base::HistogramTester histogram_tester; - ui_test_utils::NavigateToURL(browser(), HttpsLitePageURL(kHostBlacklist)); + ui_test_utils::NavigateToURL( + browser(), HttpsLitePageURL(kBypassAndBlacklistOriginHost)); VerifyPreviewNotLoaded(); VerifyInfoStatus(&histogram_tester, previews::ServerLitePageStatus::kBypass); @@ -1936,3 +1955,474 @@ ClearDeciderState(); } + +class CoinFlipHoldbackExperimentBrowserTest + : public PreviewsLitePageAndPageHintsBrowserTest { + public: + CoinFlipHoldbackExperimentBrowserTest() = default; + + ~CoinFlipHoldbackExperimentBrowserTest() override = default; + + void SetUp() override { + ukm_feature_list_.InitAndEnableFeature(ukm::kUkmFeature); + PreviewsLitePageAndPageHintsBrowserTest::SetUp(); + } + + void SetUpCommandLine(base::CommandLine* cmd) override { + PreviewsLitePageAndPageHintsBrowserTest::SetUpCommandLine(cmd); + cmd->AppendSwitch("litepage_redirect_overrides_page_hints"); + } + + void PreRunTestOnMainThread() override { + InProcessBrowserTest::PreRunTestOnMainThread(); + ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>(); + } + + // |coin_flip_holdback_enabled|: Whether the coin flip holdback feature flag + // should be enabled. + // + // |redirect_navigation|: Whether a preview should only be eligible on a + // redirect. + // + // |allow_lite_page_redirect|: Whether a lite page redirect preview should be + // eligible. + // + // |allow_resource_loading|: Whether a resource loading preview should be + // eligible. + // + // |set_random_navigation_coin_flip|: What the random coin flip should be. + // True is holdback, false is allowed. + void RunTest(bool coin_flip_holdback_enabled, + bool redirect_navigation, + bool allow_lite_page_redirect, + bool allow_resource_loading, + bool set_random_navigation_coin_flip) { + ukm::InitializeSourceUrlRecorderForWebContents(GetWebContents()); + + if (coin_flip_holdback_enabled) { + scoped_feature_list_.InitAndEnableFeatureWithParameters( + previews::features::kCoinFlipHoldback, + {{"force_coin_flip_always_holdback", + set_random_navigation_coin_flip ? "true" : "false"}, + {"force_coin_flip_always_allow", + !set_random_navigation_coin_flip ? "true" : "false"}}); + } else { + scoped_feature_list_.InitAndDisableFeature( + previews::features::kCoinFlipHoldback); + } + + GURL final_url = blacklisted_https_url(); + GURL starting_url = redirect_navigation + ? HttpsLitePageURL(kRedirectNonPreview) + : blacklisted_https_url(); + + if (allow_lite_page_redirect) { + final_url = HttpsLitePageURL(kSuccess); + starting_url = redirect_navigation ? http_to_https_redirect_url() + : HttpsLitePageURL(kSuccess); + } + + if (allow_resource_loading) + SetResourceLoadingHints({final_url.host()}); + + ASSERT_EQ(redirect_navigation, starting_url != final_url); + + ui_test_utils::NavigateToURL(browser(), starting_url); + base::RunLoop().RunUntilIdle(); + content::WaitForLoadStop(GetWebContents()); + } + + void ValidateResult(bool want_lite_page_redirect_committed, + bool want_resource_loading_committed, + int want_ukm_coin_flip_holdback_result, + bool want_ukm_previews_likely) { + PreviewsUITabHelper* ui_tab_helper = + PreviewsUITabHelper::FromWebContents(GetWebContents()); + EXPECT_EQ( + want_lite_page_redirect_committed || want_resource_loading_committed, + ui_tab_helper->displayed_preview_ui()); + previews::PreviewsUserData* previews_data = + ui_tab_helper->previews_user_data(); + + if (want_lite_page_redirect_committed) { + VerifyPreviewLoaded(); + EXPECT_NE(previews_data->coin_flip_holdback_result(), + previews::CoinFlipHoldbackResult::kHoldback); + } + + if (want_resource_loading_committed) { + EXPECT_EQ(previews_data->committed_previews_type(), + previews::PreviewsType::RESOURCE_LOADING_HINTS); + EXPECT_NE(previews_data->coin_flip_holdback_result(), + previews::CoinFlipHoldbackResult::kHoldback); + } + + EXPECT_EQ(want_ukm_coin_flip_holdback_result, + static_cast<int>(previews_data->coin_flip_holdback_result())); + + if (want_lite_page_redirect_committed || want_resource_loading_committed) { + // Navigate to a non-preview page to trigger the UKM PLM Observer to + // record. + ui_test_utils::NavigateToURL(browser(), GURL("http://nopreviews")); + base::RunLoop().RunUntilIdle(); + content::WaitForLoadStop(GetWebContents()); + + using UkmEntry = ukm::builders::Previews; + auto entries = ukm_recorder_->GetEntriesByName(UkmEntry::kEntryName); + ASSERT_EQ(1u, entries.size()); + auto* entry = entries.at(0); + + ukm_recorder_->ExpectEntryMetric(entry, UkmEntry::kcoin_flip_resultName, + want_ukm_coin_flip_holdback_result); + ukm_recorder_->ExpectEntryMetric(entry, UkmEntry::kpreviews_likelyName, + want_ukm_previews_likely ? 1 : 0); + } + } + + private: + std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_; + base::test::ScopedFeatureList scoped_feature_list_; + base::test::ScopedFeatureList ukm_feature_list_; +}; + +// True if testing using the URLLoader Interceptor implementation. +INSTANTIATE_TEST_SUITE_P(URLLoaderImplementation, + CoinFlipHoldbackExperimentBrowserTest, + testing::Bool()); + +IN_PROC_BROWSER_TEST_P(CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS(NoPreviews_NoCoinFlip)) { + // Set ECT so that we are sure to not trigger any preview. + g_browser_process->network_quality_tracker() + ->ReportEffectiveConnectionTypeForTesting( + net::EFFECTIVE_CONNECTION_TYPE_4G); + + RunTest(false /* coin_flip_holdback_enabled */, + false /* redirect_navigation*/, false /* allow_lite_page_redirect*/, + false /* allow_resource_loading*/, + false /* set_random_navigation_coin_flip*/); + + ValidateResult(false /* want_lite_page_redirect_committed */, + false /* want_resource_loading_committed */, + 0 /* want_ukm_coin_flip_holdback_result */, + false /* want_ukm_previews_likely */); +} + +IN_PROC_BROWSER_TEST_P( + CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS(BothPreviewsAllowedWantLPR_NoCoinFlip)) { + RunTest(false /* coin_flip_holdback_enabled */, + false /* redirect_navigation*/, true /* allow_lite_page_redirect*/, + true /* allow_resource_loading*/, + false /* set_random_navigation_coin_flip*/); + + ValidateResult(true /* want_lite_page_redirect_committed */, + false /* want_resource_loading_committed */, + 0 /* want_ukm_coin_flip_holdback_result */, + true /* want_ukm_previews_likely */); +} + +IN_PROC_BROWSER_TEST_P(CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS(LPRAllowed_NoCoinFlip)) { + RunTest(false /* coin_flip_holdback_enabled */, + false /* redirect_navigation*/, true /* allow_lite_page_redirect*/, + false /* allow_resource_loading*/, + false /* set_random_navigation_coin_flip*/); + + ValidateResult(true /* want_lite_page_redirect_committed */, + false /* want_resource_loading_committed */, + 0 /* want_ukm_coin_flip_holdback_result */, + true /* want_ukm_previews_likely */); +} + +IN_PROC_BROWSER_TEST_P(CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS(RLHAllowed_NoCoinFlip)) { + RunTest(false /* coin_flip_holdback_enabled */, + false /* redirect_navigation*/, false /* allow_lite_page_redirect*/, + true /* allow_resource_loading*/, + false /* set_random_navigation_coin_flip*/); + + ValidateResult(false /* want_lite_page_redirect_committed */, + true /* want_resource_loading_committed */, + 0 /* want_ukm_coin_flip_holdback_result */, + true /* want_ukm_previews_likely */); +} + +IN_PROC_BROWSER_TEST_P( + CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS(NoPreviews_WithRedirect_NoCoinFlip)) { + // Set ECT so that we are sure to not trigger any preview. + g_browser_process->network_quality_tracker() + ->ReportEffectiveConnectionTypeForTesting( + net::EFFECTIVE_CONNECTION_TYPE_4G); + + RunTest(false /* coin_flip_holdback_enabled */, true /* redirect_navigation*/, + false /* allow_lite_page_redirect*/, + false /* allow_resource_loading*/, + false /* set_random_navigation_coin_flip*/); + + ValidateResult(false /* want_lite_page_redirect_committed */, + false /* want_resource_loading_committed */, + 0 /* want_ukm_coin_flip_holdback_result */, + false /* want_ukm_previews_likely */); +} + +IN_PROC_BROWSER_TEST_P( + CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS( + BothPreviewsAllowedWantLPR_WithRedirect_NoCoinFlip)) { + RunTest(false /* coin_flip_holdback_enabled */, true /* redirect_navigation*/, + true /* allow_lite_page_redirect*/, true /* allow_resource_loading*/, + false /* set_random_navigation_coin_flip*/); + + ValidateResult(true /* want_lite_page_redirect_committed */, + false /* want_resource_loading_committed */, + 0 /* want_ukm_coin_flip_holdback_result */, + true /* want_ukm_previews_likely */); +} + +IN_PROC_BROWSER_TEST_P( + CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS(LPRAllowed_WithRedirect_NoCoinFlip)) { + RunTest(false /* coin_flip_holdback_enabled */, true /* redirect_navigation*/, + true /* allow_lite_page_redirect*/, + false /* allow_resource_loading */, + false /* set_random_navigation_coin_flip*/); + + ValidateResult(true /* want_lite_page_redirect_committed */, + false /* want_resource_loading_committed */, + 0 /* want_ukm_coin_flip_holdback_result */, + true /* want_ukm_previews_likely */); +} + +IN_PROC_BROWSER_TEST_P( + CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS(RLHAllowed_WithRedirect_NoCoinFlip)) { + RunTest(false /* coin_flip_holdback_enabled */, true /* redirect_navigation*/, + false /* allow_lite_page_redirect*/, true /* allow_resource_loading*/, + false /* set_random_navigation_coin_flip*/); + + ValidateResult(false /* want_lite_page_redirect_committed */, + true /* want_resource_loading_committed */, + 0 /* want_ukm_coin_flip_holdback_result */, + true /* want_ukm_previews_likely */); +} + +IN_PROC_BROWSER_TEST_P( + CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS(NoPreviews_CoinFlipEnabled_Allowed)) { + // Set ECT so that we are sure to not trigger any preview. + g_browser_process->network_quality_tracker() + ->ReportEffectiveConnectionTypeForTesting( + net::EFFECTIVE_CONNECTION_TYPE_4G); + + RunTest(true /* coin_flip_holdback_enabled */, false /* redirect_navigation*/, + false /* allow_lite_page_redirect*/, + false /* allow_resource_loading*/, + false /* set_random_navigation_coin_flip*/); + + ValidateResult(false /* want_lite_page_redirect_committed */, + false /* want_resource_loading_committed */, + 0 /* want_ukm_coin_flip_holdback_result */, + false /* want_ukm_previews_likely */); +} + +IN_PROC_BROWSER_TEST_P( + CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS( + BothPreviewsAllowedWantLPR_CoinFlipEnabled_Allowed)) { + RunTest(true /* coin_flip_holdback_enabled */, false /* redirect_navigation*/, + true /* allow_lite_page_redirect*/, true /* allow_resource_loading*/, + false /* set_random_navigation_coin_flip*/); + + ValidateResult(true /* want_lite_page_redirect_committed */, + false /* want_resource_loading_committed */, + 1 /* want_ukm_coin_flip_holdback_result */, + true /* want_ukm_previews_likely */); +} + +IN_PROC_BROWSER_TEST_P( + CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS(LPRAllowed_CoinFlipEnabled_Allowed)) { + RunTest(true /* coin_flip_holdback_enabled */, false /* redirect_navigation*/, + true /* allow_lite_page_redirect*/, false /* allow_resource_loading*/, + false /* set_random_navigation_coin_flip*/); + + ValidateResult(true /* want_lite_page_redirect_committed */, + false /* want_resource_loading_committed */, + 1 /* want_ukm_coin_flip_holdback_result */, + true /* want_ukm_previews_likely */); +} + +IN_PROC_BROWSER_TEST_P( + CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS(RLHAllowed_CoinFlipEnabled_Allowed)) { + RunTest(true /* coin_flip_holdback_enabled */, false /* redirect_navigation*/, + false /* allow_lite_page_redirect*/, true /* allow_resource_loading*/, + false /* set_random_navigation_coin_flip*/); + + ValidateResult(false /* want_lite_page_redirect_committed */, + true /* want_resource_loading_committed */, + 1 /* want_ukm_coin_flip_holdback_result */, + true /* want_ukm_previews_likely */); +} + +IN_PROC_BROWSER_TEST_P(CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS( + NoPreviews_WithRedirect_CoinFlipEnabled_Allowed)) { + // Set ECT so that we are sure to not trigger any preview. + g_browser_process->network_quality_tracker() + ->ReportEffectiveConnectionTypeForTesting( + net::EFFECTIVE_CONNECTION_TYPE_4G); + + RunTest(true /* coin_flip_holdback_enabled */, true /* redirect_navigation*/, + false /* allow_lite_page_redirect*/, + false /* allow_resource_loading*/, + false /* set_random_navigation_coin_flip*/); + + ValidateResult(false /* want_lite_page_redirect_committed */, + false /* want_resource_loading_committed */, + 0 /* want_ukm_coin_flip_holdback_result */, + false /* want_ukm_previews_likely */); +} + +IN_PROC_BROWSER_TEST_P( + CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS( + BothPreviewsAllowedWantLPR_WithRedirect_CoinFlipEnabled_Allowed)) { + RunTest(true /* coin_flip_holdback_enabled */, true /* redirect_navigation*/, + true /* allow_lite_page_redirect*/, true /* allow_resource_loading*/, + false /* set_random_navigation_coin_flip*/); + + ValidateResult(true /* want_lite_page_redirect_committed */, + false /* want_resource_loading_committed */, + 1 /* want_ukm_coin_flip_holdback_result */, + true /* want_ukm_previews_likely */); +} + +IN_PROC_BROWSER_TEST_P(CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS( + LPRAllowed_WithRedirect_CoinFlipEnabled_Allowed)) { + RunTest(true /* coin_flip_holdback_enabled */, true /* redirect_navigation*/, + true /* allow_lite_page_redirect*/, + false /* allow_resource_loading */, + false /* set_random_navigation_coin_flip*/); + + ValidateResult(true /* want_lite_page_redirect_committed */, + false /* want_resource_loading_committed */, + 1 /* want_ukm_coin_flip_holdback_result */, + true /* want_ukm_previews_likely */); +} + +IN_PROC_BROWSER_TEST_P(CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS( + RLHAllowed_WithRedirect_CoinFlipEnabled_Allowed)) { + RunTest(true /* coin_flip_holdback_enabled */, true /* redirect_navigation*/, + false /* allow_lite_page_redirect*/, true /* allow_resource_loading*/, + false /* set_random_navigation_coin_flip*/); + + ValidateResult(false /* want_lite_page_redirect_committed */, + true /* want_resource_loading_committed */, + 1 /* want_ukm_coin_flip_holdback_result */, + true /* want_ukm_previews_likely */); +} + +IN_PROC_BROWSER_TEST_P( + CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS(NoPreviews_CoinFlipEnabled_Holdback)) { + // Set ECT so that we are sure to not trigger any preview. + g_browser_process->network_quality_tracker() + ->ReportEffectiveConnectionTypeForTesting( + net::EFFECTIVE_CONNECTION_TYPE_4G); + + RunTest(true /* coin_flip_holdback_enabled */, false /* redirect_navigation*/, + false /* allow_lite_page_redirect*/, + false /* allow_resource_loading*/, + true /* set_random_navigation_coin_flip*/); + + ValidateResult(false /* want_lite_page_redirect_committed */, + false /* want_resource_loading_committed */, + 0 /* want_ukm_coin_flip_holdback_result */, + false /* want_ukm_previews_likely */); +} + +IN_PROC_BROWSER_TEST_P( + CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS( + BothPreviewsAllowedWantLPR_CoinFlipEnabled_Holdback)) { + RunTest(true /* coin_flip_holdback_enabled */, false /* redirect_navigation*/, + true /* allow_lite_page_redirect*/, true /* allow_resource_loading*/, + true /* set_random_navigation_coin_flip*/); + + ValidateResult(false /* want_lite_page_redirect_committed */, + false /* want_resource_loading_committed */, + 2 /* want_ukm_coin_flip_holdback_result */, + true /* want_ukm_previews_likely */); +} + +IN_PROC_BROWSER_TEST_P( + CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS(LPRAllowed_CoinFlipEnabled_Holdback)) { + RunTest(true /* coin_flip_holdback_enabled */, false /* redirect_navigation*/, + true /* allow_lite_page_redirect*/, false /* allow_resource_loading*/, + true /* set_random_navigation_coin_flip*/); + + ValidateResult(false /* want_lite_page_redirect_committed */, + false /* want_resource_loading_committed */, + 2 /* want_ukm_coin_flip_holdback_result */, + true /* want_ukm_previews_likely */); +} + +IN_PROC_BROWSER_TEST_P( + CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS(RLHAllowed_CoinFlipEnabled_Holdback)) { + RunTest(true /* coin_flip_holdback_enabled */, false /* redirect_navigation*/, + false /* allow_lite_page_redirect*/, true /* allow_resource_loading*/, + true /* set_random_navigation_coin_flip*/); + + ValidateResult(false /* want_lite_page_redirect_committed */, + false /* want_resource_loading_committed */, + 2 /* want_ukm_coin_flip_holdback_result */, + true /* want_ukm_previews_likely */); +} + +IN_PROC_BROWSER_TEST_P( + CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS( + BothPreviewsAllowedWantLPR_WithRedirect_CoinFlipEnabled_Holdback)) { + RunTest(true /* coin_flip_holdback_enabled */, true /* redirect_navigation*/, + true /* allow_lite_page_redirect*/, true /* allow_resource_loading*/, + true /* set_random_navigation_coin_flip*/); + + ValidateResult(false /* want_lite_page_redirect_committed */, + false /* want_resource_loading_committed */, + 2 /* want_ukm_coin_flip_holdback_result */, + true /* want_ukm_previews_likely */); +} + +IN_PROC_BROWSER_TEST_P(CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS( + LPRAllowed_WithRedirect_CoinFlipEnabled_Holdback)) { + RunTest(true /* coin_flip_holdback_enabled */, true /* redirect_navigation*/, + true /* allow_lite_page_redirect*/, + false /* allow_resource_loading */, + true /* set_random_navigation_coin_flip*/); + + ValidateResult(false /* want_lite_page_redirect_committed */, + false /* want_resource_loading_committed */, + 2 /* want_ukm_coin_flip_holdback_result */, + true /* want_ukm_previews_likely */); +} + +IN_PROC_BROWSER_TEST_P(CoinFlipHoldbackExperimentBrowserTest, + DISABLE_ON_WIN_MAC_CHROMESOS( + RLHAllowed_WithRedirect_CoinFlipEnabled_Holdback)) { + RunTest(true /* coin_flip_holdback_enabled */, true /* redirect_navigation*/, + false /* allow_lite_page_redirect*/, true /* allow_resource_loading*/, + true /* set_random_navigation_coin_flip*/); + + ValidateResult(false /* want_lite_page_redirect_committed */, + false /* want_resource_loading_committed */, + 2 /* want_ukm_coin_flip_holdback_result */, + true /* want_ukm_previews_likely */); +}
diff --git a/chrome/browser/previews/previews_ui_tab_helper.cc b/chrome/browser/previews/previews_ui_tab_helper.cc index 8840ee00..34ce735 100644 --- a/chrome/browser/previews/previews_ui_tab_helper.cc +++ b/chrome/browser/previews/previews_ui_tab_helper.cc
@@ -372,7 +372,9 @@ #endif // BUILDFLAG(ENABLE_OFFLINE_PAGES) // Check for committed main frame preview. - if (previews_user_data_ && previews_user_data_->HasCommittedPreviewsType()) { + if (previews_user_data_ && previews_user_data_->HasCommittedPreviewsType() && + previews_user_data_->coin_flip_holdback_result() != + previews::CoinFlipHoldbackResult::kHoldback) { previews::PreviewsType main_frame_preview = previews_user_data_->committed_previews_type(); if (ShouldShowUIForPreviewsType(main_frame_preview)) {
diff --git a/chrome/browser/profile_resetter/profile_resetter.cc b/chrome/browser/profile_resetter/profile_resetter.cc index 5efe37b..89d00b7 100644 --- a/chrome/browser/profile_resetter/profile_resetter.cc +++ b/chrome/browser/profile_resetter/profile_resetter.cc
@@ -24,6 +24,7 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/search/instant_service_factory.h" #include "chrome/browser/search_engines/template_url_service_factory.h" +#include "chrome/browser/translate/chrome_translate_client.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" @@ -33,6 +34,7 @@ #include "components/content_settings/core/browser/host_content_settings_map.h" #include "components/content_settings/core/browser/website_settings_info.h" #include "components/content_settings/core/browser/website_settings_registry.h" +#include "components/language/core/browser/language_prefs.h" #include "components/prefs/pref_service.h" #include "components/prefs/scoped_user_pref_update.h" #include "components/search_engines/search_engines_pref_names.h" @@ -131,6 +133,7 @@ {PINNED_TABS, &ProfileResetter::ResetPinnedTabs}, {SHORTCUTS, &ProfileResetter::ResetShortcuts}, {NTP_CUSTOMIZATIONS, &ProfileResetter::ResetNtpCustomizations}, + {LANGUAGES, &ProfileResetter::ResetLanguages}, }; ResettableFlags reset_triggered_for_flags = 0; @@ -342,6 +345,19 @@ MarkAsDone(NTP_CUSTOMIZATIONS); } +void ProfileResetter::ResetLanguages() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + PrefService* prefs = profile_->GetPrefs(); + DCHECK(prefs); + + auto translate_prefs = ChromeTranslateClient::CreateTranslatePrefs(prefs); + translate_prefs->ResetToDefaults(); + + language::ResetLanguagePrefs(prefs); + + MarkAsDone(LANGUAGES); +} + void ProfileResetter::OnTemplateURLServiceLoaded() { // TemplateURLService has loaded. If we need to clean search engines, it's // time to go on.
diff --git a/chrome/browser/profile_resetter/profile_resetter.h b/chrome/browser/profile_resetter/profile_resetter.h index efba184..43d5901 100644 --- a/chrome/browser/profile_resetter/profile_resetter.h +++ b/chrome/browser/profile_resetter/profile_resetter.h
@@ -49,11 +49,12 @@ PINNED_TABS = 1 << 6, SHORTCUTS = 1 << 7, NTP_CUSTOMIZATIONS = 1 << 8, + LANGUAGES = 1 << 9, // Update ALL if you add new values and check whether the type of // ResettableFlags needs to be enlarged. ALL = DEFAULT_SEARCH_ENGINE | HOMEPAGE | CONTENT_SETTINGS | COOKIES_AND_SITE_DATA | EXTENSIONS | STARTUP_PAGES | PINNED_TABS | - SHORTCUTS | NTP_CUSTOMIZATIONS + SHORTCUTS | NTP_CUSTOMIZATIONS | LANGUAGES }; // Bit vector for Resettable enum. @@ -90,6 +91,7 @@ void ResetPinnedTabs(); void ResetShortcuts(); void ResetNtpCustomizations(); + void ResetLanguages(); // BrowsingDataRemover::Observer: void OnBrowsingDataRemoverDone() override;
diff --git a/chrome/browser/profiling_host/background_profiling_triggers_unittest.cc b/chrome/browser/profiling_host/background_profiling_triggers_unittest.cc index 333a25a..ee166a9 100644 --- a/chrome/browser/profiling_host/background_profiling_triggers_unittest.cc +++ b/chrome/browser/profiling_host/background_profiling_triggers_unittest.cc
@@ -37,12 +37,13 @@ uint32_t private_footprint_kb, uint32_t shared_footprint_kb) { return memory_instrumentation::mojom::OSMemDump::New( - resident_set_kb, private_footprint_kb, shared_footprint_kb + resident_set_kb, resident_set_kb /* peak_resident_set_kb */, + private_footprint_kb, shared_footprint_kb #if defined(OS_LINUX) || defined(OS_ANDROID) , 0 #endif - ); + ); } void PopulateMetrics(GlobalMemoryDumpPtr* global_dump,
diff --git a/chrome/browser/resource_coordinator/local_site_characteristics_webcontents_observer.cc b/chrome/browser/resource_coordinator/local_site_characteristics_webcontents_observer.cc index 1a7ff7c6..f487c83 100644 --- a/chrome/browser/resource_coordinator/local_site_characteristics_webcontents_observer.cc +++ b/chrome/browser/resource_coordinator/local_site_characteristics_webcontents_observer.cc
@@ -11,7 +11,7 @@ #include "chrome/browser/performance_manager/graph/page_node_impl.h" #include "chrome/browser/performance_manager/observers/graph_observer.h" #include "chrome/browser/performance_manager/performance_manager.h" -#include "chrome/browser/performance_manager/web_contents_proxy.h" +#include "chrome/browser/performance_manager/public/web_contents_proxy.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/resource_coordinator/local_site_characteristics_data_store_factory.h" #include "chrome/browser/resource_coordinator/tab_helper.h" @@ -50,7 +50,7 @@ private: static void DispatchNonPersistentNotificationCreated( - base::WeakPtr<WebContentsProxy> contents_proxy, + WebContentsProxy contents_proxy, int64_t navigation_id); // Binds to the task runner where the object is constructed. @@ -336,13 +336,15 @@ // Return the WCO if notification is not late, and it's available. LocalSiteCharacteristicsWebContentsObserver* MaybeGetWCO( - base::WeakPtr<performance_manager::WebContentsProxy> contents_proxy, + performance_manager::WebContentsProxy contents_proxy, int64_t navigation_id) { // Bail if this is a late notification. - if (!contents_proxy || contents_proxy->LastNavigationId() != navigation_id) + if (contents_proxy.LastNavigationId() != navigation_id) return nullptr; - content::WebContents* web_contents = contents_proxy->GetWebContents(); + // The proxy is guaranteed to dereference if the navigation ID above was + // valid. + content::WebContents* web_contents = contents_proxy.Get(); DCHECK(web_contents); // The L41r is not itself WebContentsUserData, but rather stored on @@ -359,9 +361,8 @@ // static void LocalSiteCharacteristicsWebContentsObserver::GraphObserver:: - DispatchNonPersistentNotificationCreated( - base::WeakPtr<WebContentsProxy> contents_proxy, - int64_t navigation_id) { + DispatchNonPersistentNotificationCreated(WebContentsProxy contents_proxy, + int64_t navigation_id) { if (auto* wco = MaybeGetWCO(contents_proxy, navigation_id)) wco->OnNonPersistentNotificationCreated(); }
diff --git a/chrome/browser/resource_coordinator/tab_lifecycle_unit_source.cc b/chrome/browser/resource_coordinator/tab_lifecycle_unit_source.cc index 5dc7025..267bea3 100644 --- a/chrome/browser/resource_coordinator/tab_lifecycle_unit_source.cc +++ b/chrome/browser/resource_coordinator/tab_lifecycle_unit_source.cc
@@ -12,7 +12,7 @@ #include "chrome/browser/performance_manager/graph/graph_impl.h" #include "chrome/browser/performance_manager/graph/page_node_impl.h" #include "chrome/browser/performance_manager/performance_manager.h" -#include "chrome/browser/performance_manager/web_contents_proxy.h" +#include "chrome/browser/performance_manager/public/web_contents_proxy.h" #include "chrome/browser/resource_coordinator/discard_metrics_lifecycle_unit_observer.h" #include "chrome/browser/resource_coordinator/lifecycle_unit_source_observer.h" #include "chrome/browser/resource_coordinator/resource_coordinator_parts.h" @@ -79,16 +79,13 @@ } static void OnLifecycleStateChangedImpl( - const base::WeakPtr<WebContentsProxy>& contents_proxy, + const WebContentsProxy& contents_proxy, mojom::LifecycleState state) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); // If the web contents is still alive then dispatch to the actual // implementation in TabLifecycleUnitSource. - if (contents_proxy.get()) { - DCHECK(contents_proxy.get()->GetWebContents()); - TabLifecycleUnitSource::OnLifecycleStateChanged( - contents_proxy.get()->GetWebContents(), state); - } + if (auto* contents = contents_proxy.Get()) + TabLifecycleUnitSource::OnLifecycleStateChanged(contents, state); } void OnLifecycleStateChanged(PageNodeImpl* page_node) override {
diff --git a/chrome/browser/resource_coordinator/tab_manager_resource_coordinator_signal_observer.cc b/chrome/browser/resource_coordinator/tab_manager_resource_coordinator_signal_observer.cc index efbc7efe..c1154561 100644 --- a/chrome/browser/resource_coordinator/tab_manager_resource_coordinator_signal_observer.cc +++ b/chrome/browser/resource_coordinator/tab_manager_resource_coordinator_signal_observer.cc
@@ -81,20 +81,20 @@ content::WebContents* TabManager::ResourceCoordinatorSignalObserver::GetContentsForDispatch( const base::WeakPtr<TabManager>& tab_manager, - const base::WeakPtr<WebContentsProxy>& contents_proxy, + const WebContentsProxy& contents_proxy, int64_t navigation_id) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - if (!tab_manager.get() || !contents_proxy.get() || - contents_proxy.get()->LastNavigationId() != navigation_id) { + if (!tab_manager.get() || !contents_proxy.Get() || + contents_proxy.LastNavigationId() != navigation_id) { return nullptr; } - return contents_proxy.get()->GetWebContents(); + return contents_proxy.Get(); } // static void TabManager::ResourceCoordinatorSignalObserver::OnPageAlmostIdleOnUi( const base::WeakPtr<TabManager>& tab_manager, - const base::WeakPtr<WebContentsProxy>& contents_proxy, + const WebContentsProxy& contents_proxy, int64_t navigation_id) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); if (auto* contents = @@ -108,7 +108,7 @@ void TabManager::ResourceCoordinatorSignalObserver:: OnExpectedTaskQueueingDurationSampleOnUi( const base::WeakPtr<TabManager>& tab_manager, - const base::WeakPtr<WebContentsProxy>& contents_proxy, + const WebContentsProxy& contents_proxy, int64_t navigation_id, base::TimeDelta duration) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
diff --git a/chrome/browser/resource_coordinator/tab_manager_resource_coordinator_signal_observer.h b/chrome/browser/resource_coordinator/tab_manager_resource_coordinator_signal_observer.h index 251d436..83caecc0 100644 --- a/chrome/browser/resource_coordinator/tab_manager_resource_coordinator_signal_observer.h +++ b/chrome/browser/resource_coordinator/tab_manager_resource_coordinator_signal_observer.h
@@ -7,7 +7,7 @@ #include "base/macros.h" #include "chrome/browser/performance_manager/observers/graph_observer.h" -#include "chrome/browser/performance_manager/web_contents_proxy.h" +#include "chrome/browser/performance_manager/public/web_contents_proxy.h" #include "chrome/browser/resource_coordinator/tab_manager.h" namespace resource_coordinator { @@ -42,18 +42,17 @@ // UI thread. static content::WebContents* GetContentsForDispatch( const base::WeakPtr<TabManager>& tab_manager, - const base::WeakPtr<WebContentsProxy>& contents_proxy, + const WebContentsProxy& contents_proxy, int64_t navigation_id); // Equivalent to the the GraphObserver functions above, but these are the // counterparts that run on the UI thread. - static void OnPageAlmostIdleOnUi( - const base::WeakPtr<TabManager>& tab_manager, - const base::WeakPtr<WebContentsProxy>& contents_proxy, - int64_t navigation_id); + static void OnPageAlmostIdleOnUi(const base::WeakPtr<TabManager>& tab_manager, + const WebContentsProxy& contents_proxy, + int64_t navigation_id); static void OnExpectedTaskQueueingDurationSampleOnUi( const base::WeakPtr<TabManager>& tab_manager, - const base::WeakPtr<WebContentsProxy>& contents_proxy, + const WebContentsProxy& contents_proxy, int64_t navigation_id, base::TimeDelta duration);
diff --git a/chrome/browser/resources/bluetooth_internals/.eslintrc.js b/chrome/browser/resources/bluetooth_internals/.eslintrc.js deleted file mode 100644 index 7a5ac71..0000000 --- a/chrome/browser/resources/bluetooth_internals/.eslintrc.js +++ /dev/null
@@ -1,10 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -module.exports = { - 'rules': { - 'no-var': 'off', - 'prefer-const': 'off', - }, -};
diff --git a/chrome/browser/resources/bluetooth_internals/adapter_broker.js b/chrome/browser/resources/bluetooth_internals/adapter_broker.js index 214b788..0167d09 100644 --- a/chrome/browser/resources/bluetooth_internals/adapter_broker.js +++ b/chrome/browser/resources/bluetooth_internals/adapter_broker.js
@@ -8,17 +8,17 @@ */ cr.define('adapter_broker', function() { /** @typedef {bluetooth.mojom.AdapterProxy} */ - var AdapterProxy; + let AdapterProxy; /** @typedef {bluetooth.mojom.DeviceProxy} */ - var DeviceProxy; + let DeviceProxy; /** @typedef {bluetooth.mojom.DiscoverySessionProxy} */ - var DiscoverySessionProxy; + let DiscoverySessionProxy; /** * Enum of adapter property names. Used for adapterchanged events. * @enum {string} */ - var AdapterProperty = { + const AdapterProperty = { DISCOVERABLE: 'discoverable', DISCOVERING: 'discovering', POWERED: 'powered', @@ -104,8 +104,8 @@ if (response.result != bluetooth.mojom.ConnectResult.SUCCESS) { // TODO(crbug.com/663394): Replace with more descriptive error // messages. - var ConnectResult = bluetooth.mojom.ConnectResult; - var errorString = Object.keys(ConnectResult).find(function(key) { + const ConnectResult = bluetooth.mojom.ConnectResult; + const errorString = Object.keys(ConnectResult).find(function(key) { return ConnectResult[key] === response.result; }); @@ -148,7 +148,7 @@ } } - var adapterBroker = null; + let adapterBroker = null; /** * Initializes an AdapterBroker if one doesn't exist. @@ -160,7 +160,8 @@ return Promise.resolve(adapterBroker); } - var bluetoothInternalsHandler = mojom.BluetoothInternalsHandler.getProxy(); + const bluetoothInternalsHandler = + mojom.BluetoothInternalsHandler.getProxy(); // Get an Adapter service. return bluetoothInternalsHandler.getAdapter().then(function(response) {
diff --git a/chrome/browser/resources/bluetooth_internals/adapter_page.js b/chrome/browser/resources/bluetooth_internals/adapter_page.js index b5b95ea..9d67872 100644 --- a/chrome/browser/resources/bluetooth_internals/adapter_page.js +++ b/chrome/browser/resources/bluetooth_internals/adapter_page.js
@@ -7,7 +7,7 @@ */ cr.define('adapter_page', function() { - var PROPERTY_NAMES = { + const PROPERTY_NAMES = { address: 'Address', name: 'Name', initialized: 'Initialized',
diff --git a/chrome/browser/resources/bluetooth_internals/bluetooth_internals.js b/chrome/browser/resources/bluetooth_internals/bluetooth_internals.js index 6c86d60..12521d2d 100644 --- a/chrome/browser/resources/bluetooth_internals/bluetooth_internals.js +++ b/chrome/browser/resources/bluetooth_internals/bluetooth_internals.js
@@ -9,39 +9,39 @@ // Expose for testing. /** @type {adapter_broker.AdapterBroker} */ -var adapterBroker = null; +let adapterBroker = null; /** @type {device_collection.DeviceCollection} */ -var devices = null; +let devices = null; /** @type {sidebar.Sidebar} */ -var sidebarObj = null; +let sidebarObj = null; cr.define('bluetooth_internals', function() { - /** @const */ var AdapterPage = adapter_page.AdapterPage; - /** @const */ var DeviceDetailsPage = device_details_page.DeviceDetailsPage; - /** @const */ var DevicesPage = devices_page.DevicesPage; - /** @const */ var PageManager = cr.ui.pageManager.PageManager; - /** @const */ var Snackbar = snackbar.Snackbar; - /** @const */ var SnackbarType = snackbar.SnackbarType; + const AdapterPage = adapter_page.AdapterPage; + const DeviceDetailsPage = device_details_page.DeviceDetailsPage; + const DevicesPage = devices_page.DevicesPage; + const PageManager = cr.ui.pageManager.PageManager; + const Snackbar = snackbar.Snackbar; + const SnackbarType = snackbar.SnackbarType; devices = new device_collection.DeviceCollection([]); /** @type {adapter_page.AdapterPage} */ - var adapterPage = null; + let adapterPage = null; /** @type {devices_page.DevicesPage} */ - var devicesPage = null; + let devicesPage = null; /** @type {bluetooth.mojom.DiscoverySessionProxy} */ - var discoverySession = null; + let discoverySession = null; /** @type {boolean} */ - var userRequestedScanStop = false; + let userRequestedScanStop = false; /** * Observer for page changes. Used to update page title header. * @constructor * @extends {cr.ui.pageManager.PageManager.Observer} */ - var PageObserver = function() {}; + const PageObserver = function() {}; PageObserver.prototype = { __proto__: PageManager.Observer.prototype, @@ -66,10 +66,10 @@ * @param {string} address */ function removeDeviceDetailsPage(address) { - var id = 'devices/' + address.toLowerCase(); + const id = 'devices/' + address.toLowerCase(); sidebarObj.removeItem(id); - var deviceDetailsPage = PageManager.registeredPages[id]; + const deviceDetailsPage = PageManager.registeredPages[id]; assert(deviceDetailsPage, 'Device Details page must exist'); deviceDetailsPage.disconnect(); @@ -92,13 +92,13 @@ * @return {!device_details_page.DeviceDetailsPage} */ function makeDeviceDetailsPage(deviceInfo) { - var deviceDetailsPageId = 'devices/' + deviceInfo.address.toLowerCase(); - var deviceDetailsPage = PageManager.registeredPages[deviceDetailsPageId]; + const deviceDetailsPageId = 'devices/' + deviceInfo.address.toLowerCase(); + let deviceDetailsPage = PageManager.registeredPages[deviceDetailsPageId]; if (deviceDetailsPage) { return deviceDetailsPage; } - var pageSection = document.createElement('section'); + const pageSection = document.createElement('section'); pageSection.hidden = true; pageSection.id = deviceDetailsPageId; $('page-container').appendChild(pageSection); @@ -140,8 +140,8 @@ * @param {string} address */ function updateDeviceDetailsPage(address) { - var detailPageId = 'devices/' + address.toLowerCase(); - var page = PageManager.registeredPages[detailPageId]; + const detailPageId = 'devices/' + address.toLowerCase(); + const page = PageManager.registeredPages[detailPageId]; if (page) { page.redraw(); } @@ -195,7 +195,7 @@ devicesPage.setDevices(devices); devicesPage.pageDiv.addEventListener('inspectpressed', function(event) { - var detailsPage = + const detailsPage = makeDeviceDetailsPage(devices.getByAddress(event.detail.address)); PageManager.showPageByName(detailsPage.name); }); @@ -262,7 +262,7 @@ // Set up hash-based navigation. window.addEventListener('hashchange', function() { // If a user navigates and the page doesn't exist, do nothing. - var pageName = window.location.hash.substr(1); + const pageName = window.location.hash.substr(1); if ($(pageName)) { PageManager.showPageByName(pageName); }
diff --git a/chrome/browser/resources/bluetooth_internals/characteristic_list.js b/chrome/browser/resources/bluetooth_internals/characteristic_list.js index 029fe11..2ae6a00 100644 --- a/chrome/browser/resources/bluetooth_internals/characteristic_list.js +++ b/chrome/browser/resources/bluetooth_internals/characteristic_list.js
@@ -8,20 +8,20 @@ */ cr.define('characteristic_list', function() { - /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel; - /** @const */ var ExpandableList = expandable_list.ExpandableList; - /** @const */ var ExpandableListItem = expandable_list.ExpandableListItem; - /** @const */ var Snackbar = snackbar.Snackbar; - /** @const */ var SnackbarType = snackbar.SnackbarType; + const ArrayDataModel = cr.ui.ArrayDataModel; + const ExpandableList = expandable_list.ExpandableList; + const ExpandableListItem = expandable_list.ExpandableListItem; + const Snackbar = snackbar.Snackbar; + const SnackbarType = snackbar.SnackbarType; /** Property names for the CharacteristicInfo fieldset */ - var INFO_PROPERTY_NAMES = { + const INFO_PROPERTY_NAMES = { id: 'ID', 'uuid.uuid': 'UUID', }; /** Property names for the Properties fieldset. */ - var PROPERTIES_PROPERTY_NAMES = { + const PROPERTIES_PROPERTY_NAMES = { broadcast: 'Broadcast', read: 'Read', write_without_response: 'Write Without Response', @@ -51,7 +51,7 @@ */ function CharacteristicListItem( characteristicInfo, deviceAddress, serviceId) { - var listItem = new ExpandableListItem(); + const listItem = new ExpandableListItem(); listItem.__proto__ = CharacteristicListItem.prototype; /** @type {!bluetooth.mojom.CharacteristicInfo} */ @@ -88,7 +88,7 @@ this.propertiesFieldSet_ = new object_fieldset.ObjectFieldSet(); this.propertiesFieldSet_.setPropertyDisplayNames( PROPERTIES_PROPERTY_NAMES); - var Property = bluetooth.mojom.Property; + const Property = bluetooth.mojom.Property; this.propertiesFieldSet_.showAll = false; this.propertiesFieldSet_.setObject({ broadcast: (this.info.properties & Property.BROADCAST) > 0, @@ -128,29 +128,29 @@ this.descriptorList_ = new descriptor_list.DescriptorList(); // Create content for display in brief content container. - var characteristicHeaderText = document.createElement('div'); + const characteristicHeaderText = document.createElement('div'); characteristicHeaderText.textContent = 'Characteristic:'; - var characteristicHeaderValue = document.createElement('div'); + const characteristicHeaderValue = document.createElement('div'); characteristicHeaderValue.textContent = this.info.uuid.uuid; - var characteristicHeader = document.createElement('div'); + const characteristicHeader = document.createElement('div'); characteristicHeader.appendChild(characteristicHeaderText); characteristicHeader.appendChild(characteristicHeaderValue); this.briefContent_.appendChild(characteristicHeader); // Create content for display in expanded content container. - var characteristicInfoHeader = document.createElement('h4'); + const characteristicInfoHeader = document.createElement('h4'); characteristicInfoHeader.textContent = 'Characteristic Info'; - var characteristicDiv = document.createElement('div'); + const characteristicDiv = document.createElement('div'); characteristicDiv.classList.add('flex'); characteristicDiv.appendChild(this.characteristicFieldSet_); - var propertiesHeader = document.createElement('h4'); + const propertiesHeader = document.createElement('h4'); propertiesHeader.textContent = 'Properties'; - var propertiesBtn = document.createElement('button'); + const propertiesBtn = document.createElement('button'); propertiesBtn.textContent = 'Show All'; propertiesBtn.classList.add('show-all-properties'); propertiesBtn.addEventListener('click', () => { @@ -161,17 +161,17 @@ }); propertiesHeader.appendChild(propertiesBtn); - var propertiesDiv = document.createElement('div'); + const propertiesDiv = document.createElement('div'); propertiesDiv.classList.add('flex'); propertiesDiv.appendChild(this.propertiesFieldSet_); - var descriptorsHeader = document.createElement('h4'); + const descriptorsHeader = document.createElement('h4'); descriptorsHeader.textContent = 'Descriptors'; - var infoDiv = document.createElement('div'); + const infoDiv = document.createElement('div'); infoDiv.classList.add('info-container'); - var valueHeader = document.createElement('h4'); + const valueHeader = document.createElement('h4'); valueHeader.textContent = 'Value'; infoDiv.appendChild(characteristicInfoHeader); @@ -198,7 +198,7 @@ * @constructor * @extends {expandable_list.ExpandableList} */ - var CharacteristicList = cr.ui.define('list'); + const CharacteristicList = cr.ui.define('list'); CharacteristicList.prototype = { __proto__: ExpandableList.prototype,
diff --git a/chrome/browser/resources/bluetooth_internals/descriptor_list.js b/chrome/browser/resources/bluetooth_internals/descriptor_list.js index 936573d6..c158d38 100644 --- a/chrome/browser/resources/bluetooth_internals/descriptor_list.js +++ b/chrome/browser/resources/bluetooth_internals/descriptor_list.js
@@ -8,14 +8,14 @@ */ cr.define('descriptor_list', function() { - /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel; - /** @const */ var ExpandableList = expandable_list.ExpandableList; - /** @const */ var ExpandableListItem = expandable_list.ExpandableListItem; - /** @const */ var Snackbar = snackbar.Snackbar; - /** @const */ var SnackbarType = snackbar.SnackbarType; + const ArrayDataModel = cr.ui.ArrayDataModel; + const ExpandableList = expandable_list.ExpandableList; + const ExpandableListItem = expandable_list.ExpandableListItem; + const Snackbar = snackbar.Snackbar; + const SnackbarType = snackbar.SnackbarType; /** Property names for the DescriptorInfo fieldset */ - var INFO_PROPERTY_NAMES = { + const INFO_PROPERTY_NAMES = { id: 'ID', 'uuid.uuid': 'UUID', }; @@ -33,7 +33,7 @@ */ function DescriptorListItem( descriptorInfo, deviceAddress, serviceId, characteristicId) { - var listItem = new ExpandableListItem(); + const listItem = new ExpandableListItem(); listItem.__proto__ = DescriptorListItem.prototype; /** @type {!bluetooth.mojom.DescriptorInfo} */ @@ -78,29 +78,29 @@ }); // Create content for display in brief content container. - var descriptorHeaderText = document.createElement('div'); + const descriptorHeaderText = document.createElement('div'); descriptorHeaderText.textContent = 'Descriptor:'; - var descriptorHeaderValue = document.createElement('div'); + const descriptorHeaderValue = document.createElement('div'); descriptorHeaderValue.textContent = this.info.uuid.uuid; - var descriptorHeader = document.createElement('div'); + const descriptorHeader = document.createElement('div'); descriptorHeader.appendChild(descriptorHeaderText); descriptorHeader.appendChild(descriptorHeaderValue); this.briefContent_.appendChild(descriptorHeader); // Create content for display in expanded content container. - var descriptorInfoHeader = document.createElement('h4'); + const descriptorInfoHeader = document.createElement('h4'); descriptorInfoHeader.textContent = 'Descriptor Info'; - var descriptorDiv = document.createElement('div'); + const descriptorDiv = document.createElement('div'); descriptorDiv.classList.add('flex'); descriptorDiv.appendChild(this.descriptorFieldSet_); - var valueHeader = document.createElement('h4'); + const valueHeader = document.createElement('h4'); valueHeader.textContent = 'Value'; - var infoDiv = document.createElement('div'); + const infoDiv = document.createElement('div'); infoDiv.classList.add('info-container'); infoDiv.appendChild(descriptorInfoHeader); infoDiv.appendChild(descriptorDiv); @@ -116,7 +116,7 @@ * @constructor * @extends {expandable_list.ExpandableList} */ - var DescriptorList = cr.ui.define('list'); + const DescriptorList = cr.ui.define('list'); DescriptorList.prototype = { __proto__: ExpandableList.prototype,
diff --git a/chrome/browser/resources/bluetooth_internals/device_broker.js b/chrome/browser/resources/bluetooth_internals/device_broker.js index 3b7ad0b5..e166d114 100644 --- a/chrome/browser/resources/bluetooth_internals/device_broker.js +++ b/chrome/browser/resources/bluetooth_internals/device_broker.js
@@ -14,7 +14,7 @@ * @type {?Map<string, * !bluetooth.mojom.DeviceProxy|!Promise<!bluetooth.mojom.DeviceProxy>>} */ -var connectedDevices = null; +let connectedDevices = null; cr.define('device_broker', function() { connectedDevices = new Map(); @@ -28,12 +28,12 @@ * @return {!Promise<!bluetooth.mojom.DeviceProxy>} */ function connectToDevice(address) { - var deviceOrPromise = connectedDevices.get(address) || null; + const deviceOrPromise = connectedDevices.get(address) || null; if (deviceOrPromise !== null) { return Promise.resolve(deviceOrPromise); } - var promise = /** @type {!Promise<!bluetooth.mojom.DeviceProxy>} */ ( + const promise = /** @type {!Promise<!bluetooth.mojom.DeviceProxy>} */ ( adapter_broker.getAdapterBroker() .then(function(adapterBroker) { return adapterBroker.connectToDevice(address);
diff --git a/chrome/browser/resources/bluetooth_internals/device_collection.js b/chrome/browser/resources/bluetooth_internals/device_collection.js index a867246..15ab49a 100644 --- a/chrome/browser/resources/bluetooth_internals/device_collection.js +++ b/chrome/browser/resources/bluetooth_internals/device_collection.js
@@ -15,7 +15,7 @@ * by default. * @enum {number} */ - var ConnectionStatus = { + const ConnectionStatus = { DISCONNECTED: 0, CONNECTING: 1, CONNECTED: 2, @@ -39,8 +39,8 @@ * @param {string} address */ getByAddress(address) { - for (var i = 0; i < this.length; i++) { - var device = this.item(i); + for (let i = 0; i < this.length; i++) { + const device = this.item(i); if (address == device.address) { return device; } @@ -54,11 +54,11 @@ */ addOrUpdate(deviceInfo) { deviceInfo.removed = false; - var oldDeviceInfo = this.getByAddress(deviceInfo.address); + const oldDeviceInfo = this.getByAddress(deviceInfo.address); if (oldDeviceInfo) { // Update rssi if it's valid - var rssi = (deviceInfo.rssi && deviceInfo.rssi.value) || + const rssi = (deviceInfo.rssi && deviceInfo.rssi.value) || (oldDeviceInfo.rssi && oldDeviceInfo.rssi.value); // The connectionStatus and connectionMessage properties may not exist @@ -78,7 +78,7 @@ * @param {!bluetooth.mojom.DeviceInfo} deviceInfo */ remove(deviceInfo) { - var device = this.getByAddress(deviceInfo.address); + const device = this.getByAddress(deviceInfo.address); assert(device, 'Device does not exist.'); device.removed = true; this.updateIndex(this.indexOf(device)); @@ -90,7 +90,8 @@ * @param {number} status The new connection status. */ updateConnectionStatus(address, status) { - var device = assert(this.getByAddress(address), 'Device does not exist'); + const device = + assert(this.getByAddress(address), 'Device does not exist'); device.connectionStatus = status; this.updateIndex(this.indexOf(device)); }
diff --git a/chrome/browser/resources/bluetooth_internals/device_details_page.js b/chrome/browser/resources/bluetooth_internals/device_details_page.js index 3f10603..0a2f977e 100644 --- a/chrome/browser/resources/bluetooth_internals/device_details_page.js +++ b/chrome/browser/resources/bluetooth_internals/device_details_page.js
@@ -9,15 +9,15 @@ */ cr.define('device_details_page', function() { - /** @const */ var Page = cr.ui.pageManager.Page; - /** @const */ var Snackbar = snackbar.Snackbar; - /** @const */ var SnackbarType = snackbar.SnackbarType; + const Page = cr.ui.pageManager.Page; + const Snackbar = snackbar.Snackbar; + const SnackbarType = snackbar.SnackbarType; /** * Property names that will be displayed in the ObjectFieldSet which contains * the DeviceInfo object. */ - var PROPERTY_NAMES = { + const PROPERTY_NAMES = { name: 'Name', address: 'Address', isGattConnected: 'GATT Connected', @@ -143,7 +143,7 @@ /** Redraws the contents of the page with the current |deviceInfo|. */ redraw() { - var isConnected = this.deviceInfo.isGattConnected; + const isConnected = this.deviceInfo.isGattConnected; // Update status if connection has changed. if (isConnected) { @@ -152,22 +152,22 @@ this.disconnect(); } - var connectedText = isConnected ? 'Connected' : 'Not Connected'; + const connectedText = isConnected ? 'Connected' : 'Not Connected'; - var rssi = this.deviceInfo.rssi || {}; - var services = this.services; + const rssi = this.deviceInfo.rssi || {}; + const services = this.services; - var rssiValue = 'Unknown'; + let rssiValue = 'Unknown'; if (rssi.value != null && rssi.value <= 0) { rssiValue = rssi.value; } - var serviceCount = 'Unknown'; + let serviceCount = 'Unknown'; if (services != null && services.length >= 0) { serviceCount = services.length; } - var deviceViewObj = { + const deviceViewObj = { name: this.deviceInfo.nameForDisplay, address: this.deviceInfo.address, isGattConnected: connectedText,
diff --git a/chrome/browser/resources/bluetooth_internals/device_table.js b/chrome/browser/resources/bluetooth_internals/device_table.js index cc8ce62..91ca24e2 100644 --- a/chrome/browser/resources/bluetooth_internals/device_table.js +++ b/chrome/browser/resources/bluetooth_internals/device_table.js
@@ -7,7 +7,7 @@ */ cr.define('device_table', function() { - var COLUMNS = { + const COLUMNS = { NAME: 0, ADDRESS: 1, RSSI: 2, @@ -23,7 +23,7 @@ * @constructor * @extends {HTMLTableElement} */ - var DeviceTable = cr.ui.define(function() { + const DeviceTable = cr.ui.define(function() { /** @private {?Array<bluetooth.mojom.DeviceInfo>} */ this.devices_ = null; @@ -80,7 +80,7 @@ * @private */ handleForgetClick_: function(index) { - var event = new CustomEvent('forgetpressed', { + const event = new CustomEvent('forgetpressed', { bubbles: true, detail: { address: this.devices_.item(index).address, @@ -104,7 +104,7 @@ * @private */ handleInspectClick_: function(index) { - var event = new CustomEvent('inspectpressed', { + const event = new CustomEvent('inspectpressed', { bubbles: true, detail: { address: this.devices_.item(index).address, @@ -135,10 +135,10 @@ * @private */ insertRow_: function(device, index) { - var row = this.body_.insertRow(index); + const row = this.body_.insertRow(index); row.id = device.address; - for (var i = 0; i < this.headers_.length; i++) { + for (let i = 0; i < this.headers_.length; i++) { // Skip the LINKS column. It has no data-field attribute. if (i === COLUMNS.LINKS) { continue; @@ -147,16 +147,16 @@ } // Make two extra cells for the inspect link and connect errors. - var inspectCell = row.insertCell(); + const inspectCell = row.insertCell(); - var inspectLink = document.createElement('a', 'action-link'); + const inspectLink = document.createElement('a', 'action-link'); inspectLink.textContent = 'Inspect'; inspectCell.appendChild(inspectLink); inspectLink.addEventListener('click', function() { this.handleInspectClick_(row.sectionRowIndex); }.bind(this)); - var forgetLink = document.createElement('a', 'action-link'); + const forgetLink = document.createElement('a', 'action-link'); forgetLink.textContent = 'Forget'; inspectCell.appendChild(forgetLink); forgetLink.addEventListener('click', function() { @@ -176,7 +176,7 @@ this.body_ = this.tBodies[0]; this.body_.classList.add('table-body'); - for (var i = 0; i < this.devices_.length; i++) { + for (let i = 0; i < this.devices_.length; i++) { this.insertRow_(this.devices_.item(i), null); } }, @@ -188,12 +188,12 @@ * @private */ updateRow_: function(device, index) { - var row = this.body_.rows[index]; + const row = this.body_.rows[index]; assert(row, 'Row ' + index + ' is not in the table.'); row.classList.toggle('removed', device.removed); - var forgetLink = row.cells[COLUMNS.LINKS].children[1]; + const forgetLink = row.cells[COLUMNS.LINKS].children[1]; if (this.inspectionMap_.has(device)) { forgetLink.disabled = !this.inspectionMap_.get(device); @@ -202,19 +202,19 @@ } // Update the properties based on the header field path. - for (var i = 0; i < this.headers_.length; i++) { + for (let i = 0; i < this.headers_.length; i++) { // Skip the LINKS column. It has no data-field attribute. if (i === COLUMNS.LINKS) { continue; } - var header = this.headers_[i]; - var propName = header.dataset.field; + const header = this.headers_[i]; + const propName = header.dataset.field; - var parts = propName.split('.'); - var obj = device; + const parts = propName.split('.'); + let obj = device; while (obj != null && parts.length > 0) { - var part = parts.shift(); + const part = parts.shift(); obj = obj[part]; } @@ -222,7 +222,7 @@ obj = obj ? 'Connected' : 'Not Connected'; } - var cell = row.cells[i]; + const cell = row.cells[i]; cell.textContent = obj == null ? 'Unknown' : obj; cell.dataset.label = header.textContent; }
diff --git a/chrome/browser/resources/bluetooth_internals/devices_page.js b/chrome/browser/resources/bluetooth_internals/devices_page.js index af4f1b58..11bbe53 100644 --- a/chrome/browser/resources/bluetooth_internals/devices_page.js +++ b/chrome/browser/resources/bluetooth_internals/devices_page.js
@@ -12,7 +12,7 @@ * Enum of scan status for the devices page. * @enum {number} */ - var ScanStatus = { + const ScanStatus = { OFF: 0, STARTING: 1, ON: 2,
diff --git a/chrome/browser/resources/bluetooth_internals/expandable_list.js b/chrome/browser/resources/bluetooth_internals/expandable_list.js index 1a2cc3b5..e9dcc73 100644 --- a/chrome/browser/resources/bluetooth_internals/expandable_list.js +++ b/chrome/browser/resources/bluetooth_internals/expandable_list.js
@@ -8,8 +8,8 @@ */ cr.define('expandable_list', function() { - /** @const */ var List = cr.ui.List; - /** @const */ var ListItem = cr.ui.ListItem; + const List = cr.ui.List; + const ListItem = cr.ui.ListItem; /** * A list item that has expandable content that toggles when the item is @@ -17,7 +17,7 @@ * @constructor * @extends {cr.ui.ListItem} */ - var ExpandableListItem = cr.ui.define('li'); + const ExpandableListItem = cr.ui.define('li'); ExpandableListItem.prototype = { __proto__: ListItem.prototype, @@ -59,7 +59,7 @@ * @constructor * @extends {cr.ui.List} */ - var ExpandableList = cr.ui.define('list'); + const ExpandableList = cr.ui.define('list'); ExpandableList.prototype = { __proto__: List.prototype,
diff --git a/chrome/browser/resources/bluetooth_internals/object_fieldset.js b/chrome/browser/resources/bluetooth_internals/object_fieldset.js index 833c5bf..baf0f50c 100644 --- a/chrome/browser/resources/bluetooth_internals/object_fieldset.js +++ b/chrome/browser/resources/bluetooth_internals/object_fieldset.js
@@ -21,7 +21,7 @@ * @constructor * @extends {HTMLFieldSetElement} */ - var ObjectFieldSet = cr.ui.define('fieldset'); + const ObjectFieldSet = cr.ui.define('fieldset'); ObjectFieldSet.prototype = { __proto__: HTMLFieldSetElement.prototype, @@ -72,20 +72,20 @@ this.innerHTML = ''; Object.keys(assert(this.value)).forEach(function(propName) { - var value = this.value[propName]; + const value = this.value[propName]; if (value === false && !this.showAll_) { return; } - var name = this.nameMap_[propName] || propName; - var newField = document.createElement('div'); + const name = this.nameMap_[propName] || propName; + const newField = document.createElement('div'); newField.classList.add('status'); - var nameDiv = document.createElement('div'); + const nameDiv = document.createElement('div'); nameDiv.textContent = name + ':'; newField.appendChild(nameDiv); - var valueDiv = document.createElement('div'); + const valueDiv = document.createElement('div'); valueDiv.dataset.field = propName; if (typeof(value) === 'boolean') {
diff --git a/chrome/browser/resources/bluetooth_internals/service_list.js b/chrome/browser/resources/bluetooth_internals/service_list.js index e15d870..3776681 100644 --- a/chrome/browser/resources/bluetooth_internals/service_list.js +++ b/chrome/browser/resources/bluetooth_internals/service_list.js
@@ -8,17 +8,17 @@ */ cr.define('service_list', function() { - /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel; - /** @const */ var ExpandableList = expandable_list.ExpandableList; - /** @const */ var ExpandableListItem = expandable_list.ExpandableListItem; - /** @const */ var Snackbar = snackbar.Snackbar; - /** @const */ var SnackbarType = snackbar.SnackbarType; + const ArrayDataModel = cr.ui.ArrayDataModel; + const ExpandableList = expandable_list.ExpandableList; + const ExpandableListItem = expandable_list.ExpandableListItem; + const Snackbar = snackbar.Snackbar; + const SnackbarType = snackbar.SnackbarType; /** * Property names that will be displayed in the ObjectFieldSet which contains * the ServiceInfo object. */ - var PROPERTY_NAMES = { + const PROPERTY_NAMES = { id: 'ID', 'uuid.uuid': 'UUID', isPrimary: 'Type', @@ -36,7 +36,7 @@ * @constructor */ function ServiceListItem(serviceInfo, deviceAddress) { - var listItem = new ExpandableListItem(); + const listItem = new ExpandableListItem(); listItem.__proto__ = ServiceListItem.prototype; /** @type {!bluetooth.mojom.ServiceInfo} */ @@ -69,30 +69,30 @@ }); // Create content for display in brief content container. - var serviceHeaderText = document.createElement('div'); + const serviceHeaderText = document.createElement('div'); serviceHeaderText.textContent = 'Service:'; - var serviceHeaderValue = document.createElement('div'); + const serviceHeaderValue = document.createElement('div'); serviceHeaderValue.textContent = this.info.uuid.uuid; - var serviceHeader = document.createElement('div'); + const serviceHeader = document.createElement('div'); serviceHeader.appendChild(serviceHeaderText); serviceHeader.appendChild(serviceHeaderValue); this.briefContent_.appendChild(serviceHeader); // Create content for display in expanded content container. - var serviceInfoHeader = document.createElement('h4'); + const serviceInfoHeader = document.createElement('h4'); serviceInfoHeader.textContent = 'Service Info'; - var serviceDiv = document.createElement('div'); + const serviceDiv = document.createElement('div'); serviceDiv.classList.add('flex'); serviceDiv.appendChild(this.serviceFieldSet_); - var characteristicsListHeader = document.createElement('h4'); + const characteristicsListHeader = document.createElement('h4'); characteristicsListHeader.textContent = 'Characteristics'; this.characteristicList_ = new characteristic_list.CharacteristicList(); - var infoDiv = document.createElement('div'); + const infoDiv = document.createElement('div'); infoDiv.classList.add('info-container'); infoDiv.appendChild(serviceInfoHeader); infoDiv.appendChild(serviceDiv); @@ -113,7 +113,7 @@ * @constructor * @extends {expandable_list.ExpandableList} */ - var ServiceList = cr.ui.define('list'); + const ServiceList = cr.ui.define('list'); ServiceList.prototype = { __proto__: ExpandableList.prototype,
diff --git a/chrome/browser/resources/bluetooth_internals/sidebar.js b/chrome/browser/resources/bluetooth_internals/sidebar.js index 3436495..1739e71 100644 --- a/chrome/browser/resources/bluetooth_internals/sidebar.js +++ b/chrome/browser/resources/bluetooth_internals/sidebar.js
@@ -8,10 +8,10 @@ cr.define('sidebar', function() { /** @typedef {{pageName: string, text: string}} */ - var SidebarItem; + let SidebarItem; /** @const {!Object}*/ - var PageManager = cr.ui.pageManager.PageManager; + const PageManager = cr.ui.pageManager.PageManager; /** * A side menu that lists the currently navigable pages. @@ -52,10 +52,10 @@ * @param {!SidebarItem} item */ addItem: function(item) { - var sidebarItem = document.createElement('li'); + const sidebarItem = document.createElement('li'); sidebarItem.dataset.pageName = item.pageName.toLowerCase(); - var button = document.createElement('button'); + const button = document.createElement('button'); button.classList.add('custom-appearance'); button.textContent = item.text; button.addEventListener('click', this.onItemClick_.bind(this)); @@ -88,7 +88,7 @@ */ removeItem: function(pageName) { pageName = pageName.toLowerCase(); - var query = 'li[data-page-name="' + pageName + '"]'; + const query = 'li[data-page-name="' + pageName + '"]'; this.sidebarList_.removeChild(this.sidebarList_.querySelector(query)); },
diff --git a/chrome/browser/resources/bluetooth_internals/snackbar.js b/chrome/browser/resources/bluetooth_internals/snackbar.js index 19debcb..988ddd4b0 100644 --- a/chrome/browser/resources/bluetooth_internals/snackbar.js +++ b/chrome/browser/resources/bluetooth_internals/snackbar.js
@@ -14,17 +14,17 @@ * action: (function()|undefined) * }} */ - var SnackbarOptions; + let SnackbarOptions; - /** @const {number} */ var SHOW_DURATION = 5000; - /** @const {number} */ var TRANSITION_DURATION = 225; + /** @type {number} */ const SHOW_DURATION = 5000; + /** @type {number} */ const TRANSITION_DURATION = 225; /** * Enum of Snackbar types. Used by Snackbar to determine the styling for the * Snackbar. * @enum {string} */ - var SnackbarType = { + const SnackbarType = { INFO: 'info', SUCCESS: 'success', WARNING: 'warning', @@ -39,7 +39,7 @@ * @constructor * @extends {HTMLDivElement} */ - var Snackbar = cr.ui.define('div'); + const Snackbar = cr.ui.define('div'); Snackbar.prototype = { __proto__: HTMLDivElement.prototype, @@ -179,14 +179,14 @@ * @return {!snackbar.Snackbar} */ Snackbar.show = function(message, opt_type, opt_actionText, opt_action) { - var options = { + const options = { message: message, type: opt_type || SnackbarType.INFO, actionText: opt_actionText, action: opt_action, }; - var newSnackbar = new Snackbar(); + const newSnackbar = new Snackbar(); newSnackbar.initialize(options); if (Snackbar.current_) { @@ -210,7 +210,7 @@ newSnackbar.addEventListener('dismissed', function() { $('snackbar-container').removeChild(Snackbar.current_); - var newSnackbar = Snackbar.queue_.shift(); + const newSnackbar = Snackbar.queue_.shift(); if (newSnackbar) { Snackbar.show_(newSnackbar); return;
diff --git a/chrome/browser/resources/bluetooth_internals/value_control.js b/chrome/browser/resources/bluetooth_internals/value_control.js index b102217..b942f6d 100644 --- a/chrome/browser/resources/bluetooth_internals/value_control.js +++ b/chrome/browser/resources/bluetooth_internals/value_control.js
@@ -7,8 +7,8 @@ */ cr.define('value_control', function() { - /** @const */ var Snackbar = snackbar.Snackbar; - /** @const */ var SnackbarType = snackbar.SnackbarType; + const Snackbar = snackbar.Snackbar; + const SnackbarType = snackbar.SnackbarType; /** * @typedef {{ @@ -19,10 +19,10 @@ * properties: (number|undefined), * }} */ - var ValueLoadOptions; + let ValueLoadOptions; /** @enum {string} */ - var ValueDataType = { + const ValueDataType = { HEXADECIMAL: 'Hexadecimal', UTF8: 'UTF-8', DECIMAL: 'Decimal', @@ -129,8 +129,8 @@ throw new Error('Expected new value to start with "0x".'); } - var result = []; - for (var i = 2; i < newValue.length; i += 2) { + const result = []; + for (let i = 2; i < newValue.length; i += 2) { result.push(parseInt(newValue.substr(i, 2), 16)); } @@ -203,7 +203,7 @@ * @constructor * @extends {HTMLDivElement} */ - var ValueControl = cr.ui.define('div'); + const ValueControl = cr.ui.define('div'); ValueControl.prototype = { __proto__: HTMLDivElement.prototype, @@ -245,8 +245,8 @@ this.typeSelect_ = document.createElement('select'); Object.keys(ValueDataType).forEach(function(key) { - var type = ValueDataType[key]; - var option = document.createElement('option'); + const type = ValueDataType[key]; + const option = document.createElement('option'); option.value = type; option.text = type; this.typeSelect_.add(option); @@ -254,7 +254,7 @@ this.typeSelect_.addEventListener('change', this.redraw.bind(this)); - var inputDiv = document.createElement('div'); + const inputDiv = document.createElement('div'); inputDiv.appendChild(this.valueInput_); inputDiv.appendChild(this.typeSelect_); @@ -266,7 +266,7 @@ this.writeBtn_.textContent = 'Write'; this.writeBtn_.addEventListener('click', this.writeValue_.bind(this)); - var buttonsDiv = document.createElement('div'); + const buttonsDiv = document.createElement('div'); buttonsDiv.appendChild(this.readBtn_); buttonsDiv.appendChild(this.writeBtn_); @@ -306,7 +306,7 @@ (this.properties_ & bluetooth.mojom.Property.WRITE_WITHOUT_RESPONSE) === 0; - var isAvailable = !this.readBtn_.hidden || !this.writeBtn_.hidden; + const isAvailable = !this.readBtn_.hidden || !this.writeBtn_.hidden; this.unavailableMessage_.hidden = isAvailable; this.valueInput_.hidden = !isAvailable; this.typeSelect_.hidden = !isAvailable; @@ -335,7 +335,7 @@ getErrorString_: function(result) { // TODO(crbug.com/663394): Replace with more descriptive error // messages. - var GattResult = bluetooth.mojom.GattResult; + const GattResult = bluetooth.mojom.GattResult; return Object.keys(GattResult).find(function(key) { return GattResult[key] === result; }); @@ -372,7 +372,7 @@ return; } - var errorString = this.getErrorString_(response.result); + const errorString = this.getErrorString_(response.result); Snackbar.show( this.deviceAddress_ + ': ' + errorString, SnackbarType.ERROR, 'Retry', this.readValue_.bind(this)); @@ -411,7 +411,7 @@ return; } - var errorString = this.getErrorString_(response.result); + const errorString = this.getErrorString_(response.result); Snackbar.show( this.deviceAddress_ + ': ' + errorString, SnackbarType.ERROR, 'Retry', this.writeValue_.bind(this));
diff --git a/chrome/browser/resources/chromeos/login/oobe_welcome.html b/chrome/browser/resources/chromeos/login/oobe_welcome.html index e36aae3b..fb1f46d 100644 --- a/chrome/browser/resources/chromeos/login/oobe_welcome.html +++ b/chrome/browser/resources/chromeos/login/oobe_welcome.html
@@ -60,6 +60,7 @@ <oobe-welcome-dialog id="welcomeScreen" role="dialog" aria-label$="[[i18nDynamic(locale, 'welcomeScreenGreeting')]]" current-language="[[currentLanguage]]" + current-keyboard="[[currentKeyboard]]" on-language-button-clicked="onWelcomeSelectLanguageButtonClicked_" on-accessibility-button-clicked="onWelcomeAccessibilityButtonClicked_" on-timezone-button-clicked="onWelcomeTimezoneButtonClicked_" @@ -76,11 +77,11 @@ icon1x="oobe-welcome-32:language" icon2x="oobe-welcome-64:language"> </hd-iron-icon> <h1 slot="title">[[i18nDynamic(locale, 'languageSectionTitle')]]</h1> - <div slot="footer" class="layout vertical"> + <div id="setup-container" slot="footer" class="layout vertical"> <div id="languageDropdownContainer" class="flex layout center horizontal justified language-selection-entry"> - <div class= + <div id="language-dropdown" class= "language-selection-title layout vertical center-justified"> [[i18nDynamic(locale, 'languageDropdownTitle')]] </div> @@ -93,7 +94,7 @@ <div id="keyboardDropdownContainer" class="flex layout center horizontal justified language-selection-entry"> - <div class= + <div id="keyboard-dropdown" class= "language-selection-title layout vertical center-justified"> [[i18nDynamic(locale, 'keyboardDropdownTitle')]] </div> @@ -105,7 +106,8 @@ </div> </div> <div slot="bottom-buttons" class="layout horizontal end-justified"> - <oobe-text-button inverse on-tap="closeLanguageSection_"> + <oobe-text-button id="ok-button-language" + inverse on-tap="closeLanguageSection_"> <div>[[i18nDynamic(locale, 'oobeOKButtonText')]]</div> </oobe-text-button> </div> @@ -123,7 +125,8 @@ [[i18nDynamic(locale, 'accessibilitySectionHint')]] </div> <div slot="footer" class="layout vertical scroll"> - <oobe-a11y-option checked="[[a11yStatus.spokenFeedbackEnabled]]" + <oobe-a11y-option id="accessibility-spoken-feedback" + checked="[[a11yStatus.spokenFeedbackEnabled]]" on-change="onA11yOptionChanged_" chrome-message="enableSpokenFeedback" label-for-aria="[[i18nDynamic(locale, 'spokenFeedbackOption')]]" @@ -138,7 +141,8 @@ [[i18nDynamic(locale, 'a11ySettingToggleOptionOff')]] </span> </oobe-a11y-option> - <oobe-a11y-option checked="[[a11yStatus.largeCursorEnabled]]" + <oobe-a11y-option id="accessibility-large-cursor" + checked="[[a11yStatus.largeCursorEnabled]]" on-change="onA11yOptionChanged_" chrome-message="enableLargeCursor" label-for-aria="[[i18nDynamic(locale, 'largeCursorOption')]]"> <span slot="title"> @@ -151,7 +155,8 @@ [[i18nDynamic(locale, 'largeCursorOptionOff')]] </span> </oobe-a11y-option> - <oobe-a11y-option checked="[[a11yStatus.highContrastEnabled]]" + <oobe-a11y-option id="accessibility-high-contrast" + checked="[[a11yStatus.highContrastEnabled]]" on-change="onA11yOptionChanged_" chrome-message="enableHighContrast" label-for-aria="[[i18nDynamic(locale, 'highContrastOption')]]"> <span slot="title"> @@ -164,7 +169,8 @@ [[i18nDynamic(locale, 'a11ySettingToggleOptionOff')]] </span> </oobe-a11y-option> - <oobe-a11y-option checked="[[a11yStatus.screenMagnifierEnabled]]" + <oobe-a11y-option id="accessibility-screen-magnifier" + checked="[[a11yStatus.screenMagnifierEnabled]]" on-change="onA11yOptionChanged_" chrome-message="enableScreenMagnifier" label-for-aria="[[i18nDynamic(locale, 'screenMagnifierOption')]]"> @@ -178,7 +184,8 @@ [[i18nDynamic(locale, 'a11ySettingToggleOptionOff')]] </span> </oobe-a11y-option> - <oobe-a11y-option checked="[[a11yStatus.selectToSpeakEnabled]]" + <oobe-a11y-option id="accessibility-select-to-speak" + checked="[[a11yStatus.selectToSpeakEnabled]]" on-change="onA11yOptionChanged_" chrome-message="enableSelectToSpeak" label-for-aria="[[i18nDynamic(locale, 'selectToSpeakOption')]]" @@ -193,7 +200,8 @@ [[i18nDynamic(locale, 'a11ySettingToggleOptionOff')]] </span> </oobe-a11y-option> - <oobe-a11y-option checked="[[a11yStatus.dockedMagnifierEnabled]]" + <oobe-a11y-option id="accessibility-screen-docked-magnifier" + checked="[[a11yStatus.dockedMagnifierEnabled]]" on-change="onA11yOptionChanged_" chrome-message="enableDockedMagnifier" label-for-aria="[[i18nDynamic(locale, 'dockedMagnifierOption')]]" @@ -225,7 +233,8 @@ </oobe-a11y-option> </div> <div slot="bottom-buttons" class="layout horizontal end-justified"> - <oobe-text-button inverse on-tap="closeAccessibilitySection_"> + <oobe-text-button id="ok-button-accessibility" + inverse on-tap="closeAccessibilitySection_"> <div>[[i18nDynamic(locale, 'oobeOKButtonText')]]</div> </oobe-text-button> </div> @@ -251,7 +260,8 @@ </div> </div> <div slot="bottom-buttons" class="layout horizontal end-justified"> - <oobe-text-button inverse on-tap="closeTimezoneSection_"> + <oobe-text-button id="ok-button-timezone" + inverse on-tap="closeTimezoneSection_"> <div>[[i18nDynamic(locale, 'oobeOKButtonText')]]</div> </oobe-text-button> </div> @@ -289,7 +299,8 @@ </div> </div> <div slot="bottom-buttons" class="layout horizontal end-justified"> - <oobe-text-button inverse on-tap="closeAdvancedOptionsSection_"> + <oobe-text-button id="ok-button-advanced-options" + inverse on-tap="closeAdvancedOptionsSection_"> <div>[[i18nDynamic(locale, 'oobeOKButtonText')]]</div> </oobe-text-button> </div>
diff --git a/chrome/browser/resources/kiosk_next_internal_resources.grd b/chrome/browser/resources/kiosk_next_internal_resources.grd index f90c89f..38f4805 100644 --- a/chrome/browser/resources/kiosk_next_internal_resources.grd +++ b/chrome/browser/resources/kiosk_next_internal_resources.grd
@@ -10,7 +10,7 @@ </outputs> <release seq="1"> <includes> - <part file="chromeos/kiosk_next_home/internal/kiosk_next_resources_internal.grdp" /> + <include name="IDR_KIOSK_NEXT_INTENT_CONFIG_JSON" file="chromeos/kiosk_next_home/internal/intent_config.json" type="BINDATA" /> </includes> </release> </grit>
diff --git a/chrome/browser/resources/local_ntp/custom_backgrounds.js b/chrome/browser/resources/local_ntp/custom_backgrounds.js index 6a79869..ec0438ab 100644 --- a/chrome/browser/resources/local_ntp/custom_backgrounds.js +++ b/chrome/browser/resources/local_ntp/custom_backgrounds.js
@@ -75,6 +75,7 @@ BACK: 'bg-sel-back', BACK_CIRCLE: 'bg-sel-back-circle', CANCEL: 'bg-sel-footer-cancel', + CUSTOMIZATION_MENU: 'customization-menu', CUSTOM_LINKS_RESTORE_DEFAULT: 'custom-links-restore-default', CUSTOM_LINKS_RESTORE_DEFAULT_TEXT: 'custom-links-restore-default-text', DEFAULT_WALLPAPERS: 'edit-bg-default-wallpapers', @@ -86,6 +87,7 @@ EDIT_BG_ICON: 'edit-bg-icon', EDIT_BG_MENU: 'edit-bg-menu', EDIT_BG_TEXT: 'edit-bg-text', + MENU_CANCEL: 'menu-cancel', MSG_BOX: 'message-box', MSG_BOX_MSG: 'message-box-message', MSG_BOX_LINK: 'message-box-link', @@ -804,13 +806,22 @@ // Edit gear icon interaction events. const editBackgroundInteraction = function() { - editDialog.showModal(); + if (configData.richerPicker) { + $(customBackgrounds.IDS.CUSTOMIZATION_MENU).showModal(); + } else { + editDialog.showModal(); + } }; $(customBackgrounds.IDS.EDIT_BG).onclick = function(event) { editDialog.classList.add(customBackgrounds.CLASSES.MOUSE_NAV); editBackgroundInteraction(); }; + $(customBackgrounds.IDS.MENU_CANCEL).onclick = function(event) { + $(customBackgrounds.IDS.CUSTOMIZATION_MENU).close(); + }; + + // Find the first menu option that is not hidden or disabled. const findFirstMenuOption = () => { const editMenu = $(customBackgrounds.IDS.EDIT_BG_MENU);
diff --git a/chrome/browser/resources/local_ntp/local_ntp.css b/chrome/browser/resources/local_ntp/local_ntp.css index 2f9285e7..7e56f55 100644 --- a/chrome/browser/resources/local_ntp/local_ntp.css +++ b/chrome/browser/resources/local_ntp/local_ntp.css
@@ -837,3 +837,117 @@ #user-content { z-index: -1; } + +#customization-menu { + border: none; + border-radius: 8px; + box-shadow: 0 1px 3px 0 rgba(var(--GG800-rgb), .3), + 0 4px 8px 3px rgba(var(--GG800-rgb), .15); + height: 528px; + padding: 0; + width: 800px; +} + +#menu-nav-panel { + height: 384px; + left: 0; + width: 192px; +} + +.menu-option { + border-radius: 0 16px 16px 0; + font-size: 14px; + height: 32px; + left: 0; + line-height: 32px; + margin-bottom: 16px; + outline: none; + user-select: none; + width: 192px; +} + +html[dir=rtl] .menu-option { + border-radius: 16px 0 0 16px; +} + +.menu-option:hover, +.menu-option:focus { + background-color: rgba(var(--GB900-rgb), .1); +} + +.menu-option:active { + background-color: rgb(232, 240, 254); + color: rgb(var(--GB600-rgb)); +} + +.menu-option-icon-wrapper { + display: inline-block; + height: 20px; + margin-inline-start: 24px; + width: 20px; +} + +.menu-option-icon { + -webkit-mask-image: url(icons/icon_pencil.svg); + -webkit-mask-position-x: center; + -webkit-mask-position-y: center; + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: 20px; + background-color: black; + background-size: 20px 20px; + height: 20px; + margin-top: 6px; + width: 20px; +} + +.menu-option:active .menu-option-icon-wrapper .menu-option-icon { + background-color: rgb(var(--GB600-rgb)); +} + +.menu-option-label { + display: inline-block; + height: 32px; + line-height: 32px; + margin-inline-start: 16px; + text-align: center; + user-select: none; +} + +#menu-header { + font-size: 16px; + height: 80px; +} + +#menu-title { + height: 48px; + line-height: 48px; + margin-bottom: 8px; + margin-inline-start: 235px; + margin-top: 24px; + user-select: none; +} + +#menu-footer { + border-top: 1px solid rgb(var(--GG200-rgb)); + bottom: 0; + color: rgb(var(--GG800-rgb)); + height: 64px; + left: 0; + padding-inline-start: 0; + position: absolute; + text-align: end; + user-select: none; + width: 100%; +} + +#menu-done { + height: 32px; + line-height: 32px; + margin-inline-end: 16px; +} + +#menu-cancel { + height: 32px; + line-height: 32px; + margin-inline-end: 8px; +}
diff --git a/chrome/browser/resources/local_ntp/local_ntp.html b/chrome/browser/resources/local_ntp/local_ntp.html index 619a001..64ec2f1 100644 --- a/chrome/browser/resources/local_ntp/local_ntp.html +++ b/chrome/browser/resources/local_ntp/local_ntp.html
@@ -173,6 +173,38 @@ </div> </dialog> + <dialog id="customization-menu" class="customize-dialog"> + <div id="menu-header"> + <div id="menu-title">$i18n{customizeMenuTitle}</div> + </div> + <div id="menu-nav-panel"> + <div class="menu-option" tabindex="0"> + <div class="menu-option-icon-wrapper"> + <div class="menu-option-icon" id="backgrounds-icon"></div> + </div> + <div class="menu-option-label" id="backgrounds-button">Backgrounds</div> + </div> + <div class="menu-option" tabindex="0"> + <div class="menu-option-icon-wrapper"> + <div class="menu-option-icon" id="shortcuts-icon"></div> + </div> + <div class="menu-option-label" id="shortcuts-button">Shortcuts</div> + </div> + <div class="menu-option" tabindex="0"> + <div class="menu-option-icon-wrapper"> + <div class="menu-option-icon" id="colors-icon"></div> + </div> + <div class="menu-option-label" id="colors-button">Color and themes</div> + </div> + </div> + <div id="menu-footer"> + <button id="menu-cancel" class="bg-sel-footer-button paper secondary + ripple">$i18n{cancelButton}</button> + <button id="menu-done" class="bg-sel-footer-button paper primary ripple" + disabled>$i18n{doneButton}</button> + </div> + </dialog> + <dialog id="voice-overlay-dialog" class="overlay-dialog"> <div id="voice-overlay" class="overlay-hidden"> <button id="voice-close-button" class="close-button">×</button>
diff --git a/chrome/browser/resources/settings/internet_page/internet_detail_page.js b/chrome/browser/resources/settings/internet_page/internet_detail_page.js index 828c618..44b742d5 100644 --- a/chrome/browser/resources/settings/internet_page/internet_detail_page.js +++ b/chrome/browser/resources/settings/internet_page/internet_detail_page.js
@@ -10,8 +10,6 @@ (function() { 'use strict'; -const CARRIER_VERIZON = 'Verizon Wireless'; - Polymer({ is: 'settings-internet-detail-page', @@ -760,13 +758,9 @@ return false; } - // Only show if online payment URL is provided or the carrier is Verizon. - const carrier = CrOnc.getActiveValue(networkProperties.Cellular.Carrier); - if (carrier != CARRIER_VERIZON) { - const paymentPortal = networkProperties.Cellular.PaymentPortal; - if (!paymentPortal || !paymentPortal.Url) { - return false; - } + const paymentPortal = networkProperties.Cellular.PaymentPortal; + if (!paymentPortal || !paymentPortal.Url) { + return false; } // Only show for connected networks or LTE networks with a valid MDN. @@ -1307,7 +1301,7 @@ } if (type == CrOnc.Type.CELLULAR && !!this.networkProperties_.Cellular) { fields.push( - 'Cellular.Carrier', 'Cellular.Family', 'Cellular.NetworkTechnology', + 'Cellular.Family', 'Cellular.NetworkTechnology', 'Cellular.ServingOperator.Code'); } else if (type == CrOnc.Type.WI_FI) { fields.push(
diff --git a/chrome/browser/search/instant_service.cc b/chrome/browser/search/instant_service.cc index 8b2c60fc..24731ad 100644 --- a/chrome/browser/search/instant_service.cc +++ b/chrome/browser/search/instant_service.cc
@@ -548,10 +548,9 @@ // Get theme information from theme service. theme_info_.reset(new ThemeBackgroundInfo()); - // Get if the current theme is the default theme (or GTK+ on linux). + // Get if the current theme is the default theme. ThemeService* theme_service = ThemeServiceFactory::GetForProfile(profile_); - theme_info_->using_default_theme = - theme_service->UsingDefaultTheme() || theme_service->UsingSystemTheme(); + theme_info_->using_default_theme = theme_service->UsingDefaultTheme(); theme_info_->using_dark_mode = dark_mode_observer_->InDarkMode();
diff --git a/chrome/browser/search/local_ntp_source.cc b/chrome/browser/search/local_ntp_source.cc index f12ba07..1b46a1a 100644 --- a/chrome/browser/search/local_ntp_source.cc +++ b/chrome/browser/search/local_ntp_source.cc
@@ -20,6 +20,7 @@ #include "base/strings/strcat.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" #include "base/task/post_task.h" #include "base/values.h" #include "build/build_config.h" @@ -596,6 +597,9 @@ "showFakeboxPlaceholderOnFocus", base::FeatureList::IsEnabled( omnibox::kUIExperimentShowPlaceholderWhenCaretShowing)); + config_data.SetBoolean( + "richerPicker", + base::FeatureList::IsEnabled(features::kNtpCustomizationMenuV2)); } // Serialize the dictionary. @@ -956,6 +960,13 @@ // URLDataSource::GetContentSecurityPolicy*() methods? replacements["contentSecurityPolicy"] = GetContentSecurityPolicy(); + replacements["customizeMenuTitle"] = base::UTF16ToUTF8( + l10n_util::GetStringUTF16(IDS_NTP_CUSTOM_BG_CUSTOMIZE_NTP_LABEL)); + replacements["cancelButton"] = + base::UTF16ToUTF8(l10n_util::GetStringUTF16(IDS_NTP_CUSTOM_BG_CANCEL)); + replacements["doneButton"] = + base::UTF16ToUTF8(l10n_util::GetStringUTF16(IDS_NTP_CUSTOM_LINKS_DONE)); + ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance(); base::StringPiece html = bundle.GetRawDataResource(IDR_LOCAL_NTP_HTML); std::string replaced = ui::ReplaceTemplateExpressions(html, replacements);
diff --git a/chrome/browser/themes/browser_theme_pack.cc b/chrome/browser/themes/browser_theme_pack.cc index d2b886e..9a72375 100644 --- a/chrome/browser/themes/browser_theme_pack.cc +++ b/chrome/browser/themes/browser_theme_pack.cc
@@ -60,7 +60,7 @@ // change default theme assets, if you need themes to recreate their generated // images (which are cached), or if you changed how missing values are // generated. -const int kThemePackVersion = 65; +const int kThemePackVersion = 66; // IDs that are in the DataPack won't clash with the positive integer // uint16_t. kHeaderID should always have the maximum value because we want the @@ -1163,8 +1163,6 @@ display_properties_[i].id = -1; display_properties_[i].property = 0; } - display_properties_[0].id = TP::NTP_LOGO_ALTERNATE; - display_properties_[0].property = 1; } void BrowserThemePack::InitSourceImages() {
diff --git a/chrome/browser/themes/theme_service_unittest.cc b/chrome/browser/themes/theme_service_unittest.cc index de97d59..260a869 100644 --- a/chrome/browser/themes/theme_service_unittest.cc +++ b/chrome/browser/themes/theme_service_unittest.cc
@@ -356,6 +356,58 @@ ThemeProperties::COLOR_TOOLBAR_BUTTON_ICON)); } +TEST_F(ThemeServiceTest, NTPLogoAlternate) { + ThemeService* theme_service = + ThemeServiceFactory::GetForProfile(profile_.get()); + theme_service->UseDefaultTheme(); + // Let the ThemeService uninstall unused themes. + base::RunLoop().RunUntilIdle(); + + const ui::ThemeProvider& theme_provider = + ThemeService::GetThemeProviderForProfile(profile_.get()); + { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + LoadUnpackedTheme(temp_dir.GetPath(), + "extensions/theme_grey_ntp/manifest.json"); + // When logo alternate is not specified and ntp is grey, logo should be + // colorful. + EXPECT_EQ(0, theme_provider.GetDisplayProperty( + ThemeProperties::NTP_LOGO_ALTERNATE)); + } + + { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + LoadUnpackedTheme(temp_dir.GetPath(), + "extensions/theme_grey_ntp_white_logo/manifest.json"); + // Logo alternate should match what is specified in the manifest. + EXPECT_EQ(1, theme_provider.GetDisplayProperty( + ThemeProperties::NTP_LOGO_ALTERNATE)); + } + + { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + LoadUnpackedTheme(temp_dir.GetPath(), + "extensions/theme_color_ntp_white_logo/manifest.json"); + // When logo alternate is not specified and ntp is colorful, logo should be + // white. + EXPECT_EQ(1, theme_provider.GetDisplayProperty( + ThemeProperties::NTP_LOGO_ALTERNATE)); + } + + { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + LoadUnpackedTheme(temp_dir.GetPath(), + "extensions/theme_color_ntp_colorful_logo/manifest.json"); + // Logo alternate should match what is specified in the manifest. + EXPECT_EQ(0, theme_provider.GetDisplayProperty( + ThemeProperties::NTP_LOGO_ALTERNATE)); + } +} + namespace { // NotificationObserver which emulates an infobar getting destroyed when the
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn index c07aa3f..595716f9 100644 --- a/chrome/browser/ui/BUILD.gn +++ b/chrome/browser/ui/BUILD.gn
@@ -1406,8 +1406,6 @@ "ash/keyboard/chrome_keyboard_web_contents.h", "ash/kiosk_next_shell_client.cc", "ash/kiosk_next_shell_client.h", - "ash/ksv/keyboard_shortcut_viewer_util.cc", - "ash/ksv/keyboard_shortcut_viewer_util.h", "ash/launcher/app_shortcut_launcher_item_controller.cc", "ash/launcher/app_shortcut_launcher_item_controller.h", "ash/launcher/app_window_launcher_controller.cc", @@ -1514,8 +1512,6 @@ "views/frame/browser_frame_ash.h", "views/frame/browser_frame_header_ash.cc", "views/frame/browser_frame_header_ash.h", - "views/frame/browser_frame_mash.cc", - "views/frame/browser_frame_mash.h", "views/frame/browser_non_client_frame_view_ash.cc", "views/frame/browser_non_client_frame_view_ash.h", "views/frame/immersive_mode_controller_ash.cc",
diff --git a/chrome/browser/ui/app_list/internal_app/internal_app_metadata.cc b/chrome/browser/ui/app_list/internal_app/internal_app_metadata.cc index 42e3fe9..9ccc4c6 100644 --- a/chrome/browser/ui/app_list/internal_app/internal_app_metadata.cc +++ b/chrome/browser/ui/app_list/internal_app/internal_app_metadata.cc
@@ -7,6 +7,7 @@ #include <memory> #include "ash/public/cpp/app_list/internal_app_id_constants.h" +#include "ash/public/cpp/keyboard_shortcut_viewer.h" #include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h" #include "ash/public/cpp/shelf_model.h" #include "base/bind.h" @@ -15,6 +16,7 @@ #include "base/no_destructor.h" #include "base/strings/string16.h" #include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" #include "base/time/time.h" #include "chrome/browser/chromeos/arc/arc_util.h" #include "chrome/browser/chromeos/plugin_vm/plugin_vm_util.h" @@ -25,7 +27,6 @@ #include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h" #include "chrome/browser/ui/app_list/arc/arc_app_utils.h" #include "chrome/browser/ui/app_list/extension_app_utils.h" -#include "chrome/browser/ui/ash/ksv/keyboard_shortcut_viewer_util.h" #include "chrome/browser/ui/ash/launcher/app_window_launcher_item_controller.h" #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller.h" #include "chrome/browser/ui/extensions/app_launch_params.h" @@ -281,7 +282,7 @@ Profile* profile, int event_flags) { if (app_id == kInternalAppIdKeyboardShortcutViewer) { - keyboard_shortcut_viewer_util::ToggleKeyboardShortcutViewer(); + ash::ToggleKeyboardShortcutViewer(); } else if (app_id == kInternalAppIdSettings) { chrome::SettingsWindowManager::GetInstance()->ShowOSSettings(profile); } else if (app_id == kInternalAppIdCamera) { @@ -379,8 +380,11 @@ if (latest_timestamp < tab->timestamp) { has_recommendation = true; latest_timestamp = tab->timestamp; - if (title) - *title = navigation.title(); + if (title) { + *title = navigation.title().empty() + ? base::UTF8ToUTF16(virtual_url.spec()) + : navigation.title(); + } if (url) *url = virtual_url;
diff --git a/chrome/browser/ui/app_list/search/app_search_provider.cc b/chrome/browser/ui/app_list/search/app_search_provider.cc index 28b09763..1c9e11a11 100644 --- a/chrome/browser/ui/app_list/search/app_search_provider.cc +++ b/chrome/browser/ui/app_list/search/app_search_provider.cc
@@ -756,9 +756,14 @@ base::string16 title = app->name(); if (app->id() == kInternalAppIdContinueReading) { - if (HasRecommendableForeignTab(profile_, &title, /*url=*/nullptr, + base::string16 navigation_title; + if (HasRecommendableForeignTab(profile_, &navigation_title, + /*url=*/nullptr, open_tabs_ui_delegate_for_testing())) { - app->AddSearchableText(title); + if (!navigation_title.empty()) { + title = navigation_title; + app->AddSearchableText(title); + } } else { continue; }
diff --git a/chrome/browser/ui/ash/DEPS b/chrome/browser/ui/ash/DEPS index 36c62ca4..416ca5c 100644 --- a/chrome/browser/ui/ash/DEPS +++ b/chrome/browser/ui/ash/DEPS
@@ -8,7 +8,7 @@ specific_include_rules = { ".*test.*": [ "!ash", - "+ash/components/shortcut_viewer/public", + "+ash/components/shortcut_viewer", "+ash/public", ], # AshShellInit supports classic (non-mash) mode so allow ash/ includes.
diff --git a/chrome/browser/ui/ash/chrome_new_window_client.cc b/chrome/browser/ui/ash/chrome_new_window_client.cc index a0d3978d..a267f1a8 100644 --- a/chrome/browser/ui/ash/chrome_new_window_client.cc +++ b/chrome/browser/ui/ash/chrome_new_window_client.cc
@@ -8,6 +8,7 @@ #include <utility> #include "ash/public/cpp/ash_features.h" +#include "ash/public/cpp/keyboard_shortcut_viewer.h" #include "ash/public/interfaces/constants.mojom.h" #include "base/macros.h" #include "base/metrics/histogram_macros.h" @@ -23,7 +24,6 @@ #include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/sessions/tab_restore_service_factory.h" #include "chrome/browser/tab_contents/tab_util.h" -#include "chrome/browser/ui/ash/ksv/keyboard_shortcut_viewer_util.h" #include "chrome/browser/ui/ash/multi_user/multi_user_util.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_commands.h" @@ -388,7 +388,7 @@ } void ChromeNewWindowClient::ShowKeyboardShortcutViewer() { - keyboard_shortcut_viewer_util::ToggleKeyboardShortcutViewer(); + ash::ToggleKeyboardShortcutViewer(); } void ChromeNewWindowClient::ShowTaskManager() {
diff --git a/chrome/browser/ui/ash/ksv/keyboard_shortcut_viewer_metadata_unittest.cc b/chrome/browser/ui/ash/keyboard_shortcut_viewer_metadata_unittest.cc similarity index 100% rename from chrome/browser/ui/ash/ksv/keyboard_shortcut_viewer_metadata_unittest.cc rename to chrome/browser/ui/ash/keyboard_shortcut_viewer_metadata_unittest.cc
diff --git a/chrome/browser/ui/ash/ksv/DEPS b/chrome/browser/ui/ash/ksv/DEPS deleted file mode 100644 index 17b17d3..0000000 --- a/chrome/browser/ui/ash/ksv/DEPS +++ /dev/null
@@ -1,13 +0,0 @@ -include_rules = [ - "-chrome", - "+ash/components/shortcut_viewer/public", - "+chrome/browser/ui/ash/ksv", -] - -specific_include_rules = { - # Tests. - "keyboard_shortcut_viewer_metadata_unittest\.cc": [ - "+ash/components/shortcut_viewer", - "+chrome/browser/ui/views", - ], -}
diff --git a/chrome/browser/ui/ash/ksv/keyboard_shortcut_viewer_util.cc b/chrome/browser/ui/ash/ksv/keyboard_shortcut_viewer_util.cc deleted file mode 100644 index 10a4c29..0000000 --- a/chrome/browser/ui/ash/ksv/keyboard_shortcut_viewer_util.cc +++ /dev/null
@@ -1,16 +0,0 @@ -// Copyright 2018 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/ui/ash/ksv/keyboard_shortcut_viewer_util.h" - -#include "ash/components/shortcut_viewer/shortcut_viewer.h" -#include "base/time/time.h" - -namespace keyboard_shortcut_viewer_util { - -void ToggleKeyboardShortcutViewer() { - keyboard_shortcut_viewer::Toggle(base::TimeTicks::Now()); -} - -} // namespace keyboard_shortcut_viewer_util
diff --git a/chrome/browser/ui/ash/ksv/keyboard_shortcut_viewer_util.h b/chrome/browser/ui/ash/ksv/keyboard_shortcut_viewer_util.h deleted file mode 100644 index a27c697..0000000 --- a/chrome/browser/ui/ash/ksv/keyboard_shortcut_viewer_util.h +++ /dev/null
@@ -1,15 +0,0 @@ -// Copyright 2018 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_UI_ASH_KSV_KEYBOARD_SHORTCUT_VIEWER_UTIL_H_ -#define CHROME_BROWSER_UI_ASH_KSV_KEYBOARD_SHORTCUT_VIEWER_UTIL_H_ - -namespace keyboard_shortcut_viewer_util { - -// Opens or closes the keyboard shortcut viewer. -void ToggleKeyboardShortcutViewer(); - -} // namespace keyboard_shortcut_viewer_util - -#endif // CHROME_BROWSER_UI_ASH_KSV_KEYBOARD_SHORTCUT_VIEWER_UTIL_H_
diff --git a/chrome/browser/ui/toolbar/app_menu_model.cc b/chrome/browser/ui/toolbar/app_menu_model.cc index fcdc14ed..388647e 100644 --- a/chrome/browser/ui/toolbar/app_menu_model.cc +++ b/chrome/browser/ui/toolbar/app_menu_model.cc
@@ -82,6 +82,7 @@ #endif #if defined(OS_CHROMEOS) +#include "chrome/browser/ui/ash/tablet_mode_client.h" #include "chromeos/constants/chromeos_switches.h" #endif @@ -785,8 +786,21 @@ } if (base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kEnableDomDistiller)) + switches::kEnableDomDistiller)) { AddItemWithStringId(IDC_DISTILL_PAGE, IDS_DISTILL_PAGE); + } + +#if defined(OS_CHROMEOS) + // Always show this option if we're in tablet mode on Chrome OS. + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + chromeos::switches::kEnableRequestTabletSite) || + (TabletModeClient::Get() && + TabletModeClient::Get()->tablet_mode_enabled())) { + AddCheckItemWithStringId(IDC_TOGGLE_REQUEST_TABLET_SITE, + IDS_TOGGLE_REQUEST_TABLET_SITE); + } +#endif + tools_menu_model_.reset(new ToolsMenuModel(this, browser_)); AddSubMenuWithStringId( IDC_MORE_TOOLS_MENU, IDS_MORE_TOOLS_MENU, tools_menu_model_.get()); @@ -805,12 +819,6 @@ #else AddItem(IDC_ABOUT, l10n_util::GetStringUTF16(IDS_ABOUT)); #endif -#if defined(OS_CHROMEOS) - if (base::CommandLine::ForCurrentProcess()->HasSwitch( - chromeos::switches::kEnableRequestTabletSite)) - AddCheckItemWithStringId(IDC_TOGGLE_REQUEST_TABLET_SITE, - IDS_TOGGLE_REQUEST_TABLET_SITE); -#endif if (browser_defaults::kShowExitMenuItem) { AddSeparator(ui::NORMAL_SEPARATOR);
diff --git a/chrome/browser/ui/views/frame/browser_frame_ash.cc b/chrome/browser/ui/views/frame/browser_frame_ash.cc index b3f4a38..acf07bf9 100644 --- a/chrome/browser/ui/views/frame/browser_frame_ash.cc +++ b/chrome/browser/ui/views/frame/browser_frame_ash.cc
@@ -6,16 +6,13 @@ #include <memory> -// This file is only instantiated in classic ash/mus. It is never used in mash. -// See native_browser_frame_factory_chromeos.cc switches on -// features::IsUsingWindowService(). #include "ash/public/cpp/window_properties.h" #include "ash/public/cpp/window_state_type.h" -#include "ash/shell.h" // mash-ok -#include "ash/wm/window_properties.h" // mash-ok -#include "ash/wm/window_state.h" // mash-ok -#include "ash/wm/window_state_delegate.h" // mash-ok -#include "ash/wm/window_util.h" // mash-ok +#include "ash/shell.h" +#include "ash/wm/window_properties.h" +#include "ash/wm/window_state.h" +#include "ash/wm/window_state_delegate.h" +#include "ash/wm/window_util.h" #include "base/macros.h" #include "build/build_config.h" #include "chrome/browser/ui/browser_commands.h" @@ -24,7 +21,6 @@ #include "ui/aura/client/aura_constants.h" #include "ui/aura/window.h" #include "ui/aura/window_observer.h" -#include "ui/base/ui_base_features.h" #include "ui/views/view.h" namespace { @@ -63,7 +59,6 @@ BrowserView* browser_view) : views::NativeWidgetAura(browser_frame), browser_view_(browser_view) { - DCHECK(!features::IsUsingWindowService()); GetNativeWindow()->SetName("BrowserFrameAsh"); Browser* browser = browser_view->browser();
diff --git a/chrome/browser/ui/views/frame/browser_frame_ash_browsertest.cc b/chrome/browser/ui/views/frame/browser_frame_ash_browsertest.cc index 392e127..426f367c 100644 --- a/chrome/browser/ui/views/frame/browser_frame_ash_browsertest.cc +++ b/chrome/browser/ui/views/frame/browser_frame_ash_browsertest.cc
@@ -8,7 +8,6 @@ #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/views/frame/browser_view.h" #include "chrome/test/base/in_process_browser_test.h" -#include "ui/base/ui_base_features.h" #include "ui/display/display.h" #include "ui/display/screen.h" #include "ui/views/widget/widget.h" @@ -16,39 +15,6 @@ namespace { -// Waits for the given widget's bounds to change to the given rect. -class WidgetBoundsWatcher : public views::WidgetObserver { - public: - WidgetBoundsWatcher(views::Widget* widget, const gfx::Rect& target_bounds) - : widget_(widget), target_bounds_(target_bounds) {} - - ~WidgetBoundsWatcher() override {} - - void Wait() { - // Unconditionally wait for the widget bounds to update, even if the bounds - // already match |target_bounds_|. Otherwise multiple values for - // |target_bounds_| will pass the test (either the current or re-positioned - // bounds would be accepted). - widget_->AddObserver(this); - run_loop_.Run(); - } - - private: - void OnWidgetBoundsChanged(views::Widget* widget, - const gfx::Rect& new_bounds) override { - if (new_bounds == target_bounds_) { - run_loop_.Quit(); - widget_->RemoveObserver(this); - } - } - - views::Widget* widget_; - const gfx::Rect target_bounds_; - base::RunLoop run_loop_; - DISALLOW_COPY_AND_ASSIGN(WidgetBoundsWatcher); -}; - -// Tests both BrowserFrameAsh and BrowserFrameMus. class BrowserTestParam : public InProcessBrowserTest, public testing::WithParamInterface<bool> { public: @@ -83,13 +49,6 @@ Browser* browser = new Browser(params); browser->window()->Show(); - if (features::IsUsingWindowService()) { - WidgetBoundsWatcher watch( - BrowserView::GetBrowserViewForBrowser(browser)->GetWidget(), - original_bounds); - watch.Wait(); - } - // The bounds passed via |initial_bounds| should be respected regardless of // the window type. EXPECT_EQ(original_bounds, browser->window()->GetBounds()); @@ -114,13 +73,6 @@ expectation.set_y(original_bounds.y()); } - if (features::IsUsingWindowService()) { - WidgetBoundsWatcher watch( - BrowserView::GetBrowserViewForBrowser(browser)->GetWidget(), - expectation); - watch.Wait(); - } - EXPECT_EQ(expectation, browser->window()->GetBounds()) << (is_test_app ? "for app window" : "for tabbed browser window"); }
diff --git a/chrome/browser/ui/views/frame/browser_frame_mash.cc b/chrome/browser/ui/views/frame/browser_frame_mash.cc deleted file mode 100644 index 0536dcb..0000000 --- a/chrome/browser/ui/views/frame/browser_frame_mash.cc +++ /dev/null
@@ -1,142 +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 "chrome/browser/ui/views/frame/browser_frame_mash.h" - -#include <stdint.h> - -#include <memory> - -#include "ash/public/cpp/app_types.h" -#include "ash/public/cpp/shelf_types.h" -#include "ash/public/cpp/window_properties.h" -#include "ash/public/cpp/window_state_type.h" -#include "ash/public/interfaces/window_properties.mojom.h" -#include "chrome/browser/ui/browser_window_state.h" -#include "chrome/browser/ui/views/frame/browser_frame.h" -#include "chrome/browser/ui/views/frame/browser_frame_ash.h" -#include "chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h" -#include "chrome/browser/ui/views/frame/browser_view.h" -#include "chrome/common/extensions/extension_constants.h" -#include "services/ws/public/cpp/property_type_converters.h" -#include "services/ws/public/mojom/window_manager.mojom.h" -#include "services/ws/public/mojom/window_tree.mojom.h" -#include "ui/aura/client/aura_constants.h" -#include "ui/aura/mus/window_tree_host_mus_init_params.h" -#include "ui/base/ui_base_features.h" -#include "ui/views/mus/desktop_window_tree_host_mus.h" -#include "ui/views/mus/mus_client.h" - -BrowserFrameMash::BrowserFrameMash(BrowserFrame* browser_frame, - BrowserView* browser_view) - : views::DesktopNativeWidgetAura(browser_frame), - browser_frame_(browser_frame), - browser_view_(browser_view) { - DCHECK(browser_frame_); - DCHECK(browser_view_); - DCHECK(features::IsUsingWindowService()); -} - -BrowserFrameMash::~BrowserFrameMash() {} - -void BrowserFrameMash::OnWindowTargetVisibilityChanged(bool visible) { - if (visible && !browser_view_->browser()->is_type_popup()) { - // Once the window has been shown we know the requested bounds - // (if provided) have been honored and we can switch on window management. - GetNativeWindow()->GetRootWindow()->SetProperty( - ash::kWindowPositionManagedTypeKey, true); - } - views::DesktopNativeWidgetAura::OnWindowTargetVisibilityChanged(visible); -} - -views::Widget::InitParams BrowserFrameMash::GetWidgetParams() { - views::Widget::InitParams params; - params.native_widget = this; - chrome::GetSavedWindowBoundsAndShowState(browser_view_->browser(), - ¶ms.bounds, ¶ms.show_state); - params.delegate = browser_view_; - // The client will draw the frame. - params.remove_standard_frame = true; - - std::map<std::string, std::vector<uint8_t>> properties = - views::MusClient::ConfigurePropertiesFromParams(params); - - // ChromeLauncherController manages the browser shortcut shelf item; set the - // window's shelf item type property to be ignored by ash::ShelfWindowWatcher. - properties[ws::mojom::WindowManager::kShelfItemType_Property] = - mojo::ConvertTo<std::vector<uint8_t>>( - static_cast<int64_t>(ash::TYPE_BROWSER_SHORTCUT)); - - Browser* browser = browser_view_->browser(); - properties[ash::mojom::kWindowPositionManaged_Property] = - mojo::ConvertTo<std::vector<uint8_t>>(static_cast<int64_t>( - !browser->bounds_overridden() && !browser->is_session_restore() && - !browser->is_type_popup())); - properties[ash::mojom::kCanConsumeSystemKeys_Property] = - mojo::ConvertTo<std::vector<uint8_t>>( - static_cast<int64_t>(browser->is_app())); - properties[ash::mojom::kAppType_Property] = - mojo::ConvertTo<std::vector<uint8_t>>(static_cast<int64_t>( - BrowserNonClientFrameViewAsh::UsePackagedAppHeaderStyle(browser) - ? ash::AppType::CHROME_APP - : ash::AppType::BROWSER)); - - aura::WindowTreeHostMusInitParams window_tree_host_init_params = - aura::CreateInitParamsForTopLevel( - views::MusClient::Get()->window_tree_client(), std::move(properties)); - std::unique_ptr<views::DesktopWindowTreeHostMus> desktop_window_tree_host = - std::make_unique<views::DesktopWindowTreeHostMus>( - std::move(window_tree_host_init_params), browser_frame_, this); - SetDesktopWindowTreeHost(std::move(desktop_window_tree_host)); - return params; -} - -bool BrowserFrameMash::UseCustomFrame() const { - return true; -} - -bool BrowserFrameMash::UsesNativeSystemMenu() const { - return false; -} - -bool BrowserFrameMash::ShouldSaveWindowPlacement() const { - return nullptr == GetWidget()->GetNativeWindow()->GetProperty( - ash::kRestoreBoundsOverrideKey); -} - -void BrowserFrameMash::GetWindowPlacement( - gfx::Rect* bounds, - ui::WindowShowState* show_state) const { - DesktopNativeWidgetAura::GetWindowPlacement(bounds, show_state); - - gfx::Rect* override_bounds = GetWidget()->GetNativeWindow()->GetProperty( - ash::kRestoreBoundsOverrideKey); - if (override_bounds && !override_bounds->IsEmpty()) { - *bounds = *override_bounds; - *show_state = - ash::ToWindowShowState(GetWidget()->GetNativeWindow()->GetProperty( - ash::kRestoreWindowStateTypeOverrideKey)); - } - - // Session restore might be unable to correctly restore other states. - // For the record, https://crbug.com/396272 - if (*show_state != ui::SHOW_STATE_MAXIMIZED && - *show_state != ui::SHOW_STATE_MINIMIZED) { - *show_state = ui::SHOW_STATE_NORMAL; - } -} - -content::KeyboardEventProcessingResult BrowserFrameMash::PreHandleKeyboardEvent( - const content::NativeWebKeyboardEvent& event) { - return content::KeyboardEventProcessingResult::NOT_HANDLED; -} - -bool BrowserFrameMash::HandleKeyboardEvent( - const content::NativeWebKeyboardEvent& event) { - return false; -} - -int BrowserFrameMash::GetMinimizeButtonOffset() const { - return 0; -}
diff --git a/chrome/browser/ui/views/frame/browser_frame_mash.h b/chrome/browser/ui/views/frame/browser_frame_mash.h deleted file mode 100644 index 7626302e..0000000 --- a/chrome/browser/ui/views/frame/browser_frame_mash.h +++ /dev/null
@@ -1,45 +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 CHROME_BROWSER_UI_VIEWS_FRAME_BROWSER_FRAME_MASH_H_ -#define CHROME_BROWSER_UI_VIEWS_FRAME_BROWSER_FRAME_MASH_H_ - -#include "base/macros.h" -#include "chrome/browser/ui/views/frame/native_browser_frame.h" -#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" - -class BrowserFrame; -class BrowserView; - -// Used with mash on Chrome OS. -class BrowserFrameMash : public views::DesktopNativeWidgetAura, - public NativeBrowserFrame { - public: - BrowserFrameMash(BrowserFrame* browser_frame, BrowserView* browser_view); - ~BrowserFrameMash() override; - - private: - // views::DesktopNativeWidgetAura: - void OnWindowTargetVisibilityChanged(bool visible) override; - - // Overridden from NativeBrowserFrame: - views::Widget::InitParams GetWidgetParams() override; - bool UseCustomFrame() const override; - bool UsesNativeSystemMenu() const override; - bool ShouldSaveWindowPlacement() const override; - void GetWindowPlacement(gfx::Rect* bounds, - ui::WindowShowState* show_state) const override; - content::KeyboardEventProcessingResult PreHandleKeyboardEvent( - const content::NativeWebKeyboardEvent& event) override; - bool HandleKeyboardEvent( - const content::NativeWebKeyboardEvent& event) override; - int GetMinimizeButtonOffset() const override; - - BrowserFrame* browser_frame_; - BrowserView* browser_view_; - - DISALLOW_COPY_AND_ASSIGN(BrowserFrameMash); -}; - -#endif // CHROME_BROWSER_UI_VIEWS_FRAME_BROWSER_FRAME_MASH_H_
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 08c25e0c..17e945a 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
@@ -109,6 +109,14 @@ return views::WindowManagerFrameValues::instance(); } +// Returns true if the header should be painted so that it looks the same as +// the header used for packaged apps. +bool UsePackagedAppHeaderStyle(const Browser* browser) { + // Use for non tabbed trusted source windows, e.g. Settings, as well as apps. + return (!browser->is_type_tabbed() && browser->is_trusted_source()) || + browser->is_app(); +} + } // namespace /////////////////////////////////////////////////////////////////////////////// @@ -611,14 +619,6 @@ OnImmersiveRevealEnded(); } -// static -bool BrowserNonClientFrameViewAsh::UsePackagedAppHeaderStyle( - const Browser* browser) { - // Use for non tabbed trusted source windows, e.g. Settings, as well as apps. - return (!browser->is_type_tabbed() && browser->is_trusted_source()) || - browser->is_app(); -} - /////////////////////////////////////////////////////////////////////////////// // BrowserNonClientFrameViewAsh, protected:
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h index 7243e9ac8..4bec40e 100644 --- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h +++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.h
@@ -21,8 +21,6 @@ #include "mojo/public/cpp/bindings/binding.h" #include "ui/aura/window_observer.h" -class Browser; - namespace { class HostedAppNonClientFrameViewAshTest; } @@ -119,10 +117,6 @@ void OnImmersiveRevealEnded() override; void OnImmersiveFullscreenExited() override; - // Returns true if the header should be painted so that it looks the same as - // the header used for packaged apps. - static bool UsePackagedAppHeaderStyle(const Browser* browser); - protected: // BrowserNonClientFrameView: void OnProfileAvatarChanged(const base::FilePath& profile_path) override;
diff --git a/chrome/browser/ui/views/frame/native_browser_frame_factory_chromeos.cc b/chrome/browser/ui/views/frame/native_browser_frame_factory_chromeos.cc index a8c9d52..3a30309c 100644 --- a/chrome/browser/ui/views/frame/native_browser_frame_factory_chromeos.cc +++ b/chrome/browser/ui/views/frame/native_browser_frame_factory_chromeos.cc
@@ -5,13 +5,9 @@ #include "chrome/browser/ui/views/frame/native_browser_frame_factory.h" #include "chrome/browser/ui/views/frame/browser_frame_ash.h" -#include "chrome/browser/ui/views/frame/browser_frame_mash.h" -#include "ui/base/ui_base_features.h" NativeBrowserFrame* NativeBrowserFrameFactory::Create( BrowserFrame* browser_frame, BrowserView* browser_view) { - if (features::IsUsingWindowService()) - return new BrowserFrameMash(browser_frame, browser_view); return new BrowserFrameAsh(browser_frame, browser_view); }
diff --git a/chrome/browser/ui/views/omnibox/omnibox_match_cell_view.cc b/chrome/browser/ui/views/omnibox/omnibox_match_cell_view.cc index d93937f..49fdb1d 100644 --- a/chrome/browser/ui/views/omnibox/omnibox_match_cell_view.cc +++ b/chrome/browser/ui/views/omnibox/omnibox_match_cell_view.cc
@@ -258,10 +258,9 @@ void OmniboxMatchCellView::OnMatchUpdate(const OmniboxResultView* result_view, const AutocompleteMatch& match) { - is_rich_suggestion_ = - (!!match.answer || match.type == AutocompleteMatchType::CALCULATOR) || - (OmniboxFieldTrial::IsRichEntitySuggestionsEnabled() && - !match.image_url.empty()); + is_rich_suggestion_ = !!match.answer || + match.type == AutocompleteMatchType::CALCULATOR || + !match.image_url.empty(); is_search_type_ = AutocompleteMatch::IsSearchType(match.type); // Decide layout style once before Layout, while match data is available.
diff --git a/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view_browsertest.cc b/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view_browsertest.cc index 50b3335..010d090 100644 --- a/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view_browsertest.cc +++ b/chrome/browser/ui/views/omnibox/omnibox_popup_contents_view_browsertest.cc
@@ -241,7 +241,7 @@ } // TODO(tapted): https://crbug.com/905508 Fix and enable on Mac. -#if defined(OS_MACOSX) && !defined(USE_AURA) +#if defined(OS_MACOSX) #define MAYBE_ClickOmnibox DISABLED_ClickOmnibox #else #define MAYBE_ClickOmnibox ClickOmnibox
diff --git a/chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.cc b/chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.cc index 75874004..6013fe4 100644 --- a/chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.cc +++ b/chrome/browser/ui/webui/chromeos/network_element_localized_strings_provider.cc
@@ -71,7 +71,6 @@ IDS_ONC_CELLULAR_ACTIVATION_STATE_NOT_ACTIVATED}, {"OncCellular-ActivationState_PartiallyActivated", IDS_ONC_CELLULAR_ACTIVATION_STATE_PARTIALLY_ACTIVATED}, - {"OncCellular-Carrier", IDS_ONC_CELLULAR_CARRIER}, {"OncCellular-Family", IDS_ONC_CELLULAR_FAMILY}, {"OncCellular-FirmwareRevision", IDS_ONC_CELLULAR_FIRMWARE_REVISION}, {"OncCellular-HardwareRevision", IDS_ONC_CELLULAR_HARDWARE_REVISION},
diff --git a/chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler.cc b/chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler.cc index 20bc089..78ca474 100644 --- a/chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler.cc +++ b/chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler.cc
@@ -4,12 +4,12 @@ #include "chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler.h" +#include "ash/public/cpp/keyboard_shortcut_viewer.h" #include "ash/public/interfaces/constants.mojom.h" #include "ash/public/interfaces/new_window.mojom.h" #include "base/bind.h" #include "base/command_line.h" #include "base/values.h" -#include "chrome/browser/ui/ash/ksv/keyboard_shortcut_viewer_util.h" #include "chrome/browser/ui/ash/tablet_mode_client.h" #include "chromeos/constants/chromeos_switches.h" #include "chromeos/services/assistant/public/features.h" @@ -105,7 +105,7 @@ void KeyboardHandler::HandleShowKeyboardShortcutViewer( const base::ListValue* args) const { - keyboard_shortcut_viewer_util::ToggleKeyboardShortcutViewer(); + ash::ToggleKeyboardShortcutViewer(); } void KeyboardHandler::HandleKeyboardChange(const base::ListValue* args) {
diff --git a/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.cc b/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.cc index b565a907..50b58ae3b 100644 --- a/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.cc +++ b/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.cc
@@ -34,7 +34,9 @@ #include "chromeos/cryptohome/cryptohome_util.h" #include "chromeos/dbus/cryptohome/cryptohome_client.h" #include "chromeos/dbus/dbus_thread_manager.h" +#include "components/arc/arc_service_manager.h" #include "components/arc/arc_util.h" +#include "components/arc/session/arc_bridge_service.h" #include "components/browsing_data/content/conditional_cache_counting_helper.h" #include "components/drive/chromeos/file_system_interface.h" #include "components/user_manager/user_manager.h" @@ -77,10 +79,15 @@ updating_android_size_(false), updating_crostini_size_(false), updating_other_users_size_(false), + is_android_running_(false), profile_(profile), weak_ptr_factory_(this) {} StorageHandler::~StorageHandler() { + arc::ArcServiceManager::Get() + ->arc_bridge_service() + ->storage_manager() + ->RemoveObserver(this); } void StorageHandler::RegisterMessages() { @@ -103,9 +110,27 @@ base::Unretained(this))); } +void StorageHandler::OnJavascriptAllowed() { + // Start observing the mojo connection UpdateAndroidSize() relies on. Note + // that OnConnectionReady() will be called immediately if the connection has + // already been established. + arc::ArcServiceManager::Get() + ->arc_bridge_service() + ->storage_manager() + ->AddObserver(this); +} + void StorageHandler::OnJavascriptDisallowed() { // Ensure that pending callbacks do not complete and cause JS to be evaluated. weak_ptr_factory_.InvalidateWeakPtrs(); + + // Stop observing the mojo connection so that OnConnectionReady() and + // OnConnectionClosed() that use FireWebUIListener() won't be called while JS + // is disabled. + arc::ArcServiceManager::Get() + ->arc_bridge_service() + ->storage_manager() + ->RemoveObserver(this); } void StorageHandler::HandleUpdateStorageInfo(const base::ListValue* args) { @@ -294,16 +319,13 @@ } void StorageHandler::UpdateAndroidSize() { - if (!arc::IsArcPlayStoreEnabledForProfile(profile_)) + if (!is_android_running_) return; if (updating_android_size_) return; updating_android_size_ = true; - // Shows the item "Android apps and cache" and starts calculating size. - FireWebUIListener("storage-android-enabled-changed", base::Value(true)); - bool success = false; auto* arc_storage_manager = arc::ArcStorageManager::GetForBrowserContext(profile_); @@ -395,5 +417,16 @@ } } +void StorageHandler::OnConnectionReady() { + is_android_running_ = true; + FireWebUIListener("storage-android-enabled-changed", base::Value(true)); + UpdateAndroidSize(); +} + +void StorageHandler::OnConnectionClosed() { + is_android_running_ = false; + FireWebUIListener("storage-android-enabled-changed", base::Value(false)); +} + } // namespace settings } // namespace chromeos
diff --git a/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.h b/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.h index 64c8c6f6..b8ac8464 100644 --- a/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.h +++ b/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.h
@@ -16,6 +16,8 @@ #include "chrome/browser/browsing_data/site_data_size_collector.h" #include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h" #include "chromeos/dbus/cryptohome/rpc.pb.h" +#include "components/arc/common/storage_manager.mojom.h" +#include "components/arc/session/connection_observer.h" #include "components/arc/storage_manager/arc_storage_manager.h" #include "components/user_manager/user.h" @@ -28,7 +30,9 @@ namespace chromeos { namespace settings { -class StorageHandler : public ::settings::SettingsPageUIHandler { +class StorageHandler + : public ::settings::SettingsPageUIHandler, + public arc::ConnectionObserver<arc::mojom::StorageManagerInstance> { public: // Enumeration for device state about remaining space. These values must be // kept in sync with settings.StorageSpaceState in JS code. @@ -41,11 +45,15 @@ explicit StorageHandler(Profile* profile); ~StorageHandler() override; - // SettingsPageUIHandler implementation. + // ::settings::SettingsPageUIHandler: void RegisterMessages() override; - void OnJavascriptAllowed() override {} + void OnJavascriptAllowed() override; void OnJavascriptDisallowed() override; + // arc::ConnectionObserver<arc::mojom::StorageManagerInstance>: + void OnConnectionReady() override; + void OnConnectionClosed() override; + private: // Handlers of JS messages. void HandleUpdateStorageInfo(const base::ListValue* unused_args); @@ -132,6 +140,10 @@ bool updating_crostini_size_; bool updating_other_users_size_; + // A flag for keeping track of the mojo connection status to the ARC + // container. + bool is_android_running_; + Profile* const profile_; base::WeakPtrFactory<StorageHandler> weak_ptr_factory_;
diff --git a/chrome/browser/ui/webui/settings/site_settings_handler.cc b/chrome/browser/ui/webui/settings/site_settings_handler.cc index 7b65a8d4..a6fe5920 100644 --- a/chrome/browser/ui/webui/settings/site_settings_handler.cc +++ b/chrome/browser/ui/webui/settings/site_settings_handler.cc
@@ -1137,12 +1137,10 @@ GURL requesting_origin(origin_str); CHECK(requesting_origin.is_valid()); - // An empty embedding origin means that the user exception object was granted - // for any embedding origin. std::string embedding_origin_str; CHECK(args->GetString(2, &embedding_origin_str)); GURL embedding_origin(embedding_origin_str); - CHECK(embedding_origin.is_valid() || embedding_origin.is_empty()); + CHECK(embedding_origin.is_valid()); ChooserContextBase* chooser_context = chooser_type->get_context(profile_); chooser_context->RevokeObjectPermission(requesting_origin, embedding_origin,
diff --git a/chrome/browser/usb/usb_chooser_context.cc b/chrome/browser/usb/usb_chooser_context.cc index 9fbd427..5d18dab6 100644 --- a/chrome/browser/usb/usb_chooser_context.cc +++ b/chrome/browser/usb/usb_chooser_context.cc
@@ -203,6 +203,26 @@ weak_factory_.GetWeakPtr())); } +#if defined(OS_ANDROID) +void UsbChooserContext::OnDeviceInfoRefreshed( + device::mojom::UsbDeviceManager::RefreshDeviceInfoCallback callback, + device::mojom::UsbDeviceInfoPtr device_info) { + if (!device_info) { + std::move(callback).Run(nullptr); + return; + } + + auto it = devices_.find(device_info->guid); + if (it == devices_.end()) { + std::move(callback).Run(nullptr); + return; + } + + it->second = std::move(device_info); + std::move(callback).Run(it->second->Clone()); +} +#endif + UsbChooserContext::~UsbChooserContext() { OnDeviceManagerConnectionError(); } @@ -477,6 +497,17 @@ return it == devices_.end() ? nullptr : it->second.get(); } +#if defined(OS_ANDROID) +void UsbChooserContext::RefreshDeviceInfo( + const std::string& guid, + device::mojom::UsbDeviceManager::RefreshDeviceInfoCallback callback) { + EnsureConnectionWithDeviceManager(); + device_manager_->RefreshDeviceInfo( + guid, base::BindOnce(&UsbChooserContext::OnDeviceInfoRefreshed, + weak_factory_.GetWeakPtr(), std::move(callback))); +} +#endif + void UsbChooserContext::AddObserver(DeviceObserver* observer) { EnsureConnectionWithDeviceManager(); device_observer_list_.AddObserver(observer);
diff --git a/chrome/browser/usb/usb_chooser_context.h b/chrome/browser/usb/usb_chooser_context.h index d7d5f09..1081691 100644 --- a/chrome/browser/usb/usb_chooser_context.h +++ b/chrome/browser/usb/usb_chooser_context.h
@@ -16,6 +16,7 @@ #include "base/macros.h" #include "base/observer_list.h" #include "base/values.h" +#include "build/build_config.h" #include "chrome/browser/permissions/chooser_context_base.h" #include "chrome/browser/usb/usb_policy_allowed_devices.h" #include "device/usb/public/mojom/device_manager.mojom.h" @@ -71,6 +72,11 @@ void GetDevice(const std::string& guid, device::mojom::UsbDeviceRequest device_request, device::mojom::UsbDeviceClientPtr device_client); +#if defined(OS_ANDROID) + void RefreshDeviceInfo( + const std::string& guid, + device::mojom::UsbDeviceManager::RefreshDeviceInfoCallback callback); +#endif // This method should only be called when you are sure that |devices_| has // been initialized. It will return nullptr if the guid cannot be found. @@ -96,6 +102,11 @@ void OnDeviceManagerConnectionError(); void EnsureConnectionWithDeviceManager(); void SetUpDeviceManagerConnection(); +#if defined(OS_ANDROID) + void OnDeviceInfoRefreshed( + device::mojom::UsbDeviceManager::RefreshDeviceInfoCallback callback, + device::mojom::UsbDeviceInfoPtr device_info); +#endif bool is_incognito_; bool is_initialized_ = false;
diff --git a/chrome/browser/usb/usb_chooser_controller.cc b/chrome/browser/usb/usb_chooser_controller.cc index 323a505..5db4d3b53 100644 --- a/chrome/browser/usb/usb_chooser_controller.cc +++ b/chrome/browser/usb/usb_chooser_controller.cc
@@ -63,6 +63,27 @@ return device_name; } +void OnDeviceInfoRefreshed( + base::WeakPtr<UsbChooserContext> chooser_context, + const url::Origin& requesting_origin, + const url::Origin& embedding_origin, + blink::mojom::WebUsbService::GetPermissionCallback callback, + device::mojom::UsbDeviceInfoPtr device_info) { + if (!chooser_context || !device_info) { + std::move(callback).Run(nullptr); + return; + } + + RecordWebUsbChooserClosure( + device_info->serial_number->empty() + ? WEBUSB_CHOOSER_CLOSED_EPHEMERAL_PERMISSION_GRANTED + : WEBUSB_CHOOSER_CLOSED_PERMISSION_GRANTED); + + chooser_context->GrantDevicePermission(requesting_origin, embedding_origin, + *device_info); + std::move(callback).Run(std::move(device_info)); +} + } // namespace UsbChooserController::UsbChooserController( @@ -90,7 +111,7 @@ } UsbChooserController::~UsbChooserController() { - if (!callback_.is_null()) + if (callback_) std::move(callback_).Run(nullptr); } @@ -142,6 +163,7 @@ DCHECK_EQ(1u, indices.size()); size_t index = indices[0]; DCHECK_LT(index, devices_.size()); + const std::string& guid = devices_[index].first; if (!chooser_context_) { // Return nullptr for GetPermissionCallback. @@ -149,16 +171,20 @@ return; } - auto* device_info = chooser_context_->GetDeviceInfo(devices_[index].first); + // The prompt is about to close, destroying |this| so all the parameters + // necessary to grant permission to access the device need to be bound to + // this callback. + auto on_device_info_refreshed = base::BindOnce( + &OnDeviceInfoRefreshed, chooser_context_, requesting_origin_, + embedding_origin_, std::move(callback_)); +#if defined(OS_ANDROID) + chooser_context_->RefreshDeviceInfo(guid, + std::move(on_device_info_refreshed)); +#else + auto* device_info = chooser_context_->GetDeviceInfo(guid); DCHECK(device_info); - chooser_context_->GrantDevicePermission(requesting_origin_, embedding_origin_, - *device_info); - std::move(callback_).Run(device_info->Clone()); - - RecordWebUsbChooserClosure( - device_info->serial_number->empty() - ? WEBUSB_CHOOSER_CLOSED_EPHEMERAL_PERMISSION_GRANTED - : WEBUSB_CHOOSER_CLOSED_PERMISSION_GRANTED); + std::move(on_device_info_refreshed).Run(device_info->Clone()); +#endif } void UsbChooserController::Cancel() {
diff --git a/chrome/common/mac/staging_watcher.h b/chrome/common/mac/staging_watcher.h index 586ceecd..14d9acc 100644 --- a/chrome/common/mac/staging_watcher.h +++ b/chrome/common/mac/staging_watcher.h
@@ -27,6 +27,17 @@ // Returns a boolean indicating whether or not the staging key is set. - (BOOL)isStagingKeySet; +// Returns a boolean indicating whether or not the staging key is set. This will +// not return the correct answer in testing. ++ (BOOL)isStagingKeySet; + +// Returns the path to the staged update, or nil if there is no staging key set. +- (NSString*)stagingLocation; + +// Returns the path to the staged update, or nil if there is no staging key set. +// This will not return the correct answer in testing. ++ (NSString*)stagingLocation; + // Sleeps until the staging key is clear. If there is no staging key set, // returns immediately. - (void)waitForStagingKeyToClear; @@ -52,7 +63,8 @@ - (BOOL)lastWaitWasBlockedForTesting; // Returns the NSUserDefaults key that is used to indicate staging. The value to -// be used is an array of strings, each string being a file path to the bundle. +// be used is a dictionary of strings, with the key being the file path to the +// existing bundle, and the value being the file path to the staged bundle. + (NSString*)stagingKeyForTesting; @end
diff --git a/chrome/common/mac/staging_watcher.mm b/chrome/common/mac/staging_watcher.mm index cf81325..12fed3c 100644 --- a/chrome/common/mac/staging_watcher.mm +++ b/chrome/common/mac/staging_watcher.mm
@@ -5,6 +5,7 @@ #include "chrome/common/mac/staging_watcher.h" #include "base/mac/bundle_locations.h" +#include "base/mac/foundation_util.h" #include "base/mac/mac_util.h" #include "base/mac/scoped_block.h" #include "base/mac/scoped_nsobject.h" @@ -44,6 +45,8 @@ BOOL lastWaitWasBlockedForTesting_; } ++ (NSString*)stagingLocationWithUserDefaults:(NSUserDefaults*)defaults; + @end @implementation CrStagingKeyWatcher @@ -78,14 +81,32 @@ return self; } -- (BOOL)isStagingKeySet { - NSArray<NSString*>* paths = [defaults_ stringArrayForKey:kStagingKey]; - if (!paths) - return NO; ++ (NSString*)stagingLocationWithUserDefaults:(NSUserDefaults*)defaults { + NSDictionary<NSString*, id>* stagedPathPairs = + [defaults dictionaryForKey:kStagingKey]; + if (!stagedPathPairs) + return nil; NSString* appPath = [base::mac::OuterBundle() bundlePath]; - return [paths containsObject:appPath]; + return base::mac::ObjCCast<NSString>([stagedPathPairs objectForKey:appPath]); +} + +- (BOOL)isStagingKeySet { + return [self stagingLocation] != nil; +} + ++ (BOOL)isStagingKeySet { + return [self stagingLocation] != nil; +} + +- (NSString*)stagingLocation { + return [CrStagingKeyWatcher stagingLocationWithUserDefaults:defaults_]; +} + ++ (NSString*)stagingLocation { + return [self + stagingLocationWithUserDefaults:[NSUserDefaults standardUserDefaults]]; } - (void)waitForStagingKeyToClear {
diff --git a/chrome/common/mac/staging_watcher_unittest.mm b/chrome/common/mac/staging_watcher_unittest.mm index 469494a..e3eb86a 100644 --- a/chrome/common/mac/staging_watcher_unittest.mm +++ b/chrome/common/mac/staging_watcher_unittest.mm
@@ -64,7 +64,7 @@ arguments:@[ @"write", testingBundleID_.get(), [CrStagingKeyWatcher stagingKeyForTesting], - @"-array", appPath + @"-dict", appPath, appPath ]]; } @@ -87,14 +87,14 @@ } TEST_P(StagingKeyWatcherTest, NoBlockingWhenWrongKeyType) { - SetDefaultsValue(@"this is not an string array"); + SetDefaultsValue(@"this is not a dictionary"); base::scoped_nsobject<CrStagingKeyWatcher> watcher = CreateKeyWatcher(); [watcher waitForStagingKeyToClear]; ASSERT_FALSE([watcher lastWaitWasBlockedForTesting]); } -TEST_P(StagingKeyWatcherTest, NoBlockingWhenWrongArrayType) { +TEST_P(StagingKeyWatcherTest, NoBlockingWhenArrayType) { SetDefaultsValue(@[ @3, @1, @4, @1, @5 ]); base::scoped_nsobject<CrStagingKeyWatcher> watcher = CreateKeyWatcher(); @@ -110,9 +110,17 @@ ASSERT_FALSE([watcher lastWaitWasBlockedForTesting]); } +TEST_P(StagingKeyWatcherTest, NoBlockingWhenEmptyDictionary) { + SetDefaultsValue(@{}); + + base::scoped_nsobject<CrStagingKeyWatcher> watcher = CreateKeyWatcher(); + [watcher waitForStagingKeyToClear]; + ASSERT_FALSE([watcher lastWaitWasBlockedForTesting]); +} + TEST_P(StagingKeyWatcherTest, BlockFunctionality) { NSString* appPath = [base::mac::OuterBundle() bundlePath]; - SetDefaultsValue(@[ appPath ]); + SetDefaultsValue(@{appPath : appPath}); NSRunLoop* runloop = [NSRunLoop currentRunLoop]; ASSERT_EQ(nil, [runloop currentMode]); @@ -151,7 +159,7 @@ TEST_P(StagingKeyWatcherTest, CallbackOnKeyUnset) { NSString* appPath = [base::mac::OuterBundle() bundlePath]; - SetDefaultsValue(@[ appPath ]); + SetDefaultsValue(@{appPath : appPath}); base::scoped_nsobject<CrStagingKeyWatcher> watcher = CreateKeyWatcher(); NSRunLoop* runloop = [NSRunLoop currentRunLoop];
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index 9797503..9e890888 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn
@@ -758,6 +758,7 @@ "../browser/metrics/startup_metrics_browsertest.cc", "../browser/metrics/tab_reactivation_tracker_browsertest.cc", "../browser/metrics/tab_stats_tracker_browsertest.cc", + "../browser/metrics/ukm_background_recorder_browsertest.cc", "../browser/metrics/ukm_browsertest.cc", "../browser/metrics/variations/force_field_trials_browsertest.cc", "../browser/navigation_predictor/navigation_predictor_browsertest.cc", @@ -1891,6 +1892,7 @@ "../browser/chromeos/login/screens/recommend_apps_screen_browsertest.cc", "../browser/chromeos/login/screens/update_screen_browsertest.cc", "../browser/chromeos/login/screens/user_selection_screen_browsertest.cc", + "../browser/chromeos/login/screens/welcome_screen_browsertest.cc", "../browser/chromeos/login/session/chrome_session_manager_browsertest.cc", "../browser/chromeos/login/session_login_browsertest.cc", "../browser/chromeos/login/signin/device_id_browsertest.cc", @@ -3615,10 +3617,13 @@ "../browser/media/router/providers/cast/cast_internal_message_util_unittest.cc", "../browser/media/router/providers/cast/cast_media_route_provider_metrics_unittest.cc", "../browser/media/router/providers/cast/cast_media_route_provider_unittest.cc", + "../browser/media/router/providers/cast/cast_session_client_unittest.cc", "../browser/media/router/providers/cast/cast_session_tracker_unittest.cc", "../browser/media/router/providers/cast/dual_media_sink_service_unittest.cc", "../browser/media/router/providers/cast/mock_cast_activity_record.cc", "../browser/media/router/providers/cast/mock_cast_activity_record.h", + "../browser/media/router/providers/cast/test_util.cc", + "../browser/media/router/providers/cast/test_util.h", "../browser/media/router/providers/dial/dial_activity_manager_unittest.cc", "../browser/media/router/providers/dial/dial_internal_message_util_unittest.cc", "../browser/media/router/providers/dial/dial_media_route_provider_unittest.cc", @@ -3747,7 +3752,7 @@ "../browser/ui/ash/ime_controller_client_unittest.cc", "../browser/ui/ash/keyboard/chrome_keyboard_ui_unittest.cc", "../browser/ui/ash/keyboard/chrome_keyboard_web_contents_unittest.cc", - "../browser/ui/ash/ksv/keyboard_shortcut_viewer_metadata_unittest.cc", + "../browser/ui/ash/keyboard_shortcut_viewer_metadata_unittest.cc", "../browser/ui/ash/launcher/arc_app_shelf_id_unittest.cc", "../browser/ui/ash/launcher/chrome_launcher_controller_unittest.cc", "../browser/ui/ash/launcher/launcher_context_menu_unittest.cc", @@ -6090,3 +6095,40 @@ generate_python = false } } + +if (is_win) { + test("delayloads_unittests") { + output_name = "delayloads_unittests" + sources = [ + "delayload/delayloads_unittest.cc", + ] + include_dirs = [ "$target_gen_dir" ] + deps = [ + "//base", + "//base/test:test_support", + "//chrome", + "//chrome/install_static:install_static_util", + "//chrome/install_static/test:test_support", + "//testing/gtest", + ] + + # It's not easily possible to have //chrome in data_deps without changing + # the //chrome target to bundle up both initial/chrome.exe and chrome.exe. + # As a workaround, explicitly include a data dep on just chrome.exe, and + # add //chrome to deps above to make sure it's been built. + data = [ + "$root_out_dir/chrome.exe", + ] + + # Don't want the test-specific dependencies to affect load tests. + # In particular, a few system DLLs cause user32 to be loaded, which is bad. + ldflags = [ + "/DELAYLOAD:advapi32.dll", + "/DELAYLOAD:ole32.dll", + "/DELAYLOAD:shell32.dll", + "/DELAYLOAD:shlwapi.dll", + "/DELAYLOAD:user32.dll", + "/DELAYLOAD:winmm.dll", + ] + } +}
diff --git a/chrome/test/base/browser_tests_main.cc b/chrome/test/base/browser_tests_main.cc index 3653698..c271eef 100644 --- a/chrome/test/base/browser_tests_main.cc +++ b/chrome/test/base/browser_tests_main.cc
@@ -23,6 +23,13 @@ } #if defined(OS_WIN) + // Many tests validate code that requires user32.dll to be loaded. Loading it, + // however, cannot be done on the main thread loop because it is a blocking + // call, and all the test code runs on the main thread loop. Instead, just + // load and pin the module early on in startup before the blocking becomes an + // issue. + base::win::PinUser32(); + // Enable high-DPI for interactive tests where the user is expected to // manually verify results. if (base::CommandLine::ForCurrentProcess()->HasSwitch(
diff --git a/chrome/test/chromedriver/test/test_expectations b/chrome/test/chromedriver/test/test_expectations index e027a96f..5fa9ecd3 100644 --- a/chrome/test/chromedriver/test/test_expectations +++ b/chrome/test/chromedriver/test/test_expectations
@@ -168,9 +168,6 @@ # https://bugs.chromium.org/p/chromedriver/issues/detail?id=1176 'ChromeDriverTests.clientLogShouldBeEnabledByDefault', - # https://bugs.chromium.org/p/chromedriver/issues/detail?id=1119 - 'CombinedInputActionsTest.testCanClickOnLinksWithAnOffset', - 'CombinedInputActionsTest.testMouseMovementWorksWhenNavigatingToAnotherPage', # https://bugs.chromium.org/p/chromedriver/issues/detail?id=1005 'WindowTest.*',
diff --git a/chrome/test/data/extensions/api_test/networking_private/alias/test.js b/chrome/test/data/extensions/api_test/networking_private/alias/test.js index 978f74e7..a13a11e 100644 --- a/chrome/test/data/extensions/api_test/networking_private/alias/test.js +++ b/chrome/test/data/extensions/api_test/networking_private/alias/test.js
@@ -34,7 +34,6 @@ ActivationState: 'NotActivated', AllowRoaming: false, AutoConnect: true, - Carrier: 'Cellular1_Carrier', Family: 'GSM', HomeProvider: { Code: '000000',
diff --git a/chrome/test/data/extensions/api_test/networking_private/chromeos/test.js b/chrome/test/data/extensions/api_test/networking_private/chromeos/test.js index 9fdec7f..bdeb958 100644 --- a/chrome/test/data/extensions/api_test/networking_private/chromeos/test.js +++ b/chrome/test/data/extensions/api_test/networking_private/chromeos/test.js
@@ -577,7 +577,6 @@ ActivationState: ActivationStateType.NOT_ACTIVATED, AllowRoaming: false, AutoConnect: true, - Carrier: 'Cellular1_Carrier', Family: 'GSM', HomeProvider: { Code: '000000', @@ -611,7 +610,6 @@ assertEq({ Cellular: { AllowRoaming: false, - Carrier: 'Cellular1_Carrier', ESN: "test_esn", Family: 'GSM', HomeProvider: {
diff --git a/chrome/test/data/extensions/theme_color_ntp_colorful_logo/Cached Theme.pak b/chrome/test/data/extensions/theme_color_ntp_colorful_logo/Cached Theme.pak new file mode 100644 index 0000000..3561ecb5 --- /dev/null +++ b/chrome/test/data/extensions/theme_color_ntp_colorful_logo/Cached Theme.pak Binary files differ
diff --git a/chrome/test/data/extensions/theme_color_ntp_colorful_logo/manifest.json b/chrome/test/data/extensions/theme_color_ntp_colorful_logo/manifest.json new file mode 100644 index 0000000..dc6f2ca --- /dev/null +++ b/chrome/test/data/extensions/theme_color_ntp_colorful_logo/manifest.json
@@ -0,0 +1,14 @@ +{ + "manifest_version": 2, + "name": "minimal", + "theme": { + "colors": { + "ntp_background": [150, 150, 100] + }, + "properties": { + "ntp_logo_alternate": 0 + } + + }, + "version": "2.0" +}
diff --git a/chrome/test/data/extensions/theme_color_ntp_white_logo/manifest.json b/chrome/test/data/extensions/theme_color_ntp_white_logo/manifest.json new file mode 100644 index 0000000..82e6d7b --- /dev/null +++ b/chrome/test/data/extensions/theme_color_ntp_white_logo/manifest.json
@@ -0,0 +1,11 @@ +{ + "manifest_version": 2, + "name": "minimal", + "theme": { + "colors": { + "ntp_background": [150, 150, 100] + } + + }, + "version": "2.0" +}
diff --git a/chrome/test/data/extensions/theme_grey_ntp/manifest.json b/chrome/test/data/extensions/theme_grey_ntp/manifest.json new file mode 100644 index 0000000..a82eeb1 --- /dev/null +++ b/chrome/test/data/extensions/theme_grey_ntp/manifest.json
@@ -0,0 +1,10 @@ +{ + "manifest_version": 2, + "name": "grey ntp", + "theme": { + "colors": { + "ntp_background": [100, 100, 100] + } + }, + "version": "2.0" +}
diff --git a/chrome/test/data/extensions/theme_grey_ntp_white_logo/manifest.json b/chrome/test/data/extensions/theme_grey_ntp_white_logo/manifest.json new file mode 100644 index 0000000..87e4c1b --- /dev/null +++ b/chrome/test/data/extensions/theme_grey_ntp_white_logo/manifest.json
@@ -0,0 +1,14 @@ +{ + "manifest_version": 2, + "name": "minimal", + "theme": { + "colors": { + "ntp_background": [100, 100, 100] + }, + "properties": { + "ntp_logo_alternate": 1 + } + + }, + "version": "2.0" +}
diff --git a/chrome/test/data/local_ntp/local_ntp_browsertest.html b/chrome/test/data/local_ntp/local_ntp_browsertest.html index 8617d999..a202ffc8 100644 --- a/chrome/test/data/local_ntp/local_ntp_browsertest.html +++ b/chrome/test/data/local_ntp/local_ntp_browsertest.html
@@ -160,6 +160,38 @@ </div> </dialog> + <dialog id="customization-menu" class="customize-dialog"> + <div id="menu-header"> + <div id="menu-title">$i18n{customizeMenuTitle}</div> + </div> + <div id="menu-nav-panel"> + <div class="menu-option" tabindex="0"> + <div class="menu-option-icon-wrapper"> + <div class="menu-option-icon" id="backgrounds-icon"></div> + </div> + <div class="menu-option-label" id="backgrounds-button">Backgrounds</div> + </div> + <div class="menu-option" tabindex="0"> + <div class="menu-option-icon-wrapper"> + <div class="menu-option-icon" id="shortcuts-icon"></div> + </div> + <div class="menu-option-label" id="shortcuts-button">Shortcuts</div> + </div> + <div class="menu-option" tabindex="0"> + <div class="menu-option-icon-wrapper"> + <div class="menu-option-icon" id="colors-icon"></div> + </div> + <div class="menu-option-label" id="colors-button">Color and themes</div> + </div> + </div> + <div id="menu-footer"> + <button id="menu-cancel" class="bg-sel-footer-button paper secondary + ripple">$i18n{cancelButton}</button> + <button id="menu-done" class="bg-sel-footer-button paper primary ripple" + >$i18n{doneButton}</button> + </div> + </dialog> + <dialog id="voice-overlay-dialog" class="overlay-dialog"> <div id="voice-overlay" class="overlay-hidden"> <button id="voice-close-button" class="close-button">×</button>
diff --git a/chrome/test/data/webui/cr_elements/cr_checkbox_test.js b/chrome/test/data/webui/cr_elements/cr_checkbox_test.js index 9fe27264..c2dc5a8d 100644 --- a/chrome/test/data/webui/cr_elements/cr_checkbox_test.js +++ b/chrome/test/data/webui/cr_elements/cr_checkbox_test.js
@@ -33,6 +33,7 @@ function assertDisabled() { assertTrue(checkbox.disabled); + assertFalse(checkbox.hasAttribute('tabindex')); assertEquals('-1', checkbox.$.checkbox.getAttribute('tabindex')); assertTrue(checkbox.hasAttribute('disabled')); assertEquals('true', checkbox.$.checkbox.getAttribute('aria-disabled')); @@ -41,6 +42,7 @@ function assertNotDisabled() { assertFalse(checkbox.disabled); + assertFalse(checkbox.hasAttribute('tabindex')); assertEquals('0', checkbox.$.checkbox.getAttribute('tabindex')); assertFalse(checkbox.hasAttribute('disabled')); assertEquals('false', checkbox.$.checkbox.getAttribute('aria-disabled')); @@ -161,10 +163,10 @@ // Should not override tabindex if it is initialized. assertEquals(-1, checkbox.tabIndex); + assertFalse(checkbox.hasAttribute('tabindex')); assertEquals('-1', checkbox.$.checkbox.getAttribute('tabindex')); }); - test('InitializingWithDisabled', function() { PolymerTest.clearBody(); document.body.innerHTML = ` @@ -175,6 +177,18 @@ // Initializing with disabled should make tabindex="-1". assertEquals(-1, checkbox.tabIndex); + assertFalse(checkbox.hasAttribute('tabindex')); assertEquals('-1', checkbox.$.checkbox.getAttribute('tabindex')); }); + + test('tabindex attribute is controlled by tabIndex', () => { + PolymerTest.clearBody(); + document.body.innerHTML = ` + <cr-checkbox id="checkbox" tabindex="-1"></cr-checkbox> + `; + checkbox = document.querySelector('cr-checkbox'); + assertEquals(0, checkbox.tabIndex); + assertFalse(checkbox.hasAttribute('tabindex')); + assertEquals('0', checkbox.$.checkbox.getAttribute('tabindex')); + }); });
diff --git a/chrome/test/delayload/delayloads_unittest.cc b/chrome/test/delayload/delayloads_unittest.cc new file mode 100644 index 0000000..22bac73 --- /dev/null +++ b/chrome/test/delayload/delayloads_unittest.cc
@@ -0,0 +1,319 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <windows.h> + +#include <stdint.h> + +#include <algorithm> +#include <vector> + +#include "base/base_paths.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/files/memory_mapped_file.h" +#include "base/path_service.h" +#include "base/strings/pattern.h" +#include "base/strings/string_util.h" +#include "base/test/launcher/test_launcher.h" +#include "base/test/launcher/unit_test_launcher.h" +#include "base/test/test_suite.h" +#include "base/win/pe_image.h" +#include "base/win/windows_version.h" +#include "build/build_config.h" +#include "chrome/install_static/test/scoped_install_details.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class DelayloadsTest : public testing::Test { + protected: + static bool ImportsCallback(const base::win::PEImage& image, + LPCSTR module, + PIMAGE_THUNK_DATA name_table, + PIMAGE_THUNK_DATA iat, + PVOID cookie) { + std::vector<std::string>* import_list = + reinterpret_cast<std::vector<std::string>*>(cookie); + import_list->push_back(module); + return true; + } + + static void GetImports(const base::FilePath& module_path, + std::vector<std::string>* imports) { + ASSERT_TRUE(imports != NULL); + + base::MemoryMappedFile module_mmap; + + ASSERT_TRUE(module_mmap.Initialize(module_path)); + base::win::PEImageAsData pe_image_data( + reinterpret_cast<HMODULE>(const_cast<uint8_t*>(module_mmap.data()))); + pe_image_data.EnumImportChunks(DelayloadsTest::ImportsCallback, imports); + } +}; + +// Run this test only in Release builds. +// +// These tests make sure that chrome.dll, chrome_child.dll, and chrome.exe +// have only certain types of imports. +// In particular, we explicitly want to ensure user32.dll and its many related +// dlls are delayloaded and not automatically brought in via some other +// dependent dll. The primary reason for this is that the sandbox for the +// renderer process prevents user32 from working at all and therefore we have +// no reason to load the dll. +// However, they directly and indirectly depend on base, which has lots more +// imports than are allowed here. +// +// In release builds, the offending imports are all stripped since this +// depends on a relatively small portion of base. +// +// If you break these tests, you may have changed base or the Windows sandbox +// such that more system imports are required to link. +// +// Also note that the dlls are listed with specific case-sensitive names. If +// you fail a test double-check that casing of the name. +#if defined(NDEBUG) && !defined(COMPONENT_BUILD) + +TEST_F(DelayloadsTest, ChromeDllDelayloadsCheck) { + base::FilePath dll; + ASSERT_TRUE(base::PathService::Get(base::DIR_EXE, &dll)); + dll = dll.Append(L"chrome.dll"); + std::vector<std::string> dll_imports; + GetImports(dll, &dll_imports); + + // Check that the dll has imports. + ASSERT_LT(0u, dll_imports.size()) + << "Ensure the delayloads_unittests " + "target was built, instead of delayloads_unittests.exe"; + + static const char* const kValidFilePatterns[] = { + "KERNEL32.dll", + "chrome_elf.dll", + "DWrite.dll", + "oneds.dll", + "telclient.dll", + // On 64 bit the Version API's like VerQueryValue come from VERSION.dll. + // It depends on kernel32, advapi32 and api-ms-win-crt*.dll. This should + // be ok. + "VERSION.dll", + }; + + // Make sure all of chrome.dll's imports are in the valid imports list. + for (const std::string& dll_import : dll_imports) { + bool match = false; + for (const char* kValidFilePattern : kValidFilePatterns) { + if (base::MatchPattern(dll_import, kValidFilePattern)) { + match = true; + break; + } + } + EXPECT_TRUE(match) << "Illegal import in chrome.dll: " << dll_import; + } +} + +TEST_F(DelayloadsTest, ChromeDllLoadSanityTest) { + // On Win7 we expect this test to result in user32.dll getting loaded. As a + // result, we need to ensure it is executed in its own test process. This + // "test" will re-launch with custom parameters to accomplish that. + base::CommandLine new_test = + base::CommandLine(base::CommandLine::ForCurrentProcess()->GetProgram()); + new_test.AppendSwitchASCII(base::kGTestFilterFlag, + "DelayloadsTest.DISABLED_ChromeDllLoadSanityTest"); + new_test.AppendSwitch("gtest_also_run_disabled_tests"); + new_test.AppendSwitch("single-process-tests"); + + std::string output; + ASSERT_TRUE(base::GetAppOutput(new_test, &output)); + std::string crash_string = + "OK ] DelayloadsTest.DISABLED_ChromeDllLoadSanityTest"; + + if (output.find(crash_string) == std::string::npos) { + GTEST_FAIL() << "Couldn't find\n" + << crash_string << "\n in output\n " << output; + } +} + +// Note: This test is not actually disabled, it's just tagged disabled so that +// the real run (above, in ChromeDllLoadSanityTest) can run it with an argument +// added to the command line. +TEST_F(DelayloadsTest, DISABLED_ChromeDllLoadSanityTest) { + base::FilePath dll; + ASSERT_TRUE(base::PathService::Get(base::DIR_EXE, &dll)); + dll = dll.Append(L"chrome.dll"); + + // We don't expect user32 to be loaded in delayloads_unittests. If this + // test case fails, then it means that a dependency on user32 has crept into + // the delayloads_unittests executable, which needs to be removed. + // NOTE: it may be a secondary dependency of another system DLL. If so, + // try adding a "/DELAYLOAD:<blah>.dll" to the build.gn file. + ASSERT_EQ(nullptr, ::GetModuleHandle(L"user32.dll")); + + HMODULE chrome_module_handle = ::LoadLibrary(dll.value().c_str()); + ASSERT_TRUE(chrome_module_handle != nullptr); + // Loading chrome.dll should not load user32.dll on Win10. + // On Win7, chains of system dlls and lack of apisets result in it loading. + if (base::win::GetVersion() >= base::win::Version::WIN10) { + EXPECT_EQ(nullptr, ::GetModuleHandle(L"user32.dll")); + } else { + EXPECT_NE(nullptr, ::GetModuleHandle(L"user32.dll")); + } + EXPECT_TRUE(!!::FreeLibrary(chrome_module_handle)); +} + +TEST_F(DelayloadsTest, ChromeChildDllDelayloadsCheck) { + base::FilePath dll; + ASSERT_TRUE(base::PathService::Get(base::DIR_EXE, &dll)); + dll = dll.Append(L"chrome_child.dll"); + std::vector<std::string> dll_imports; + GetImports(dll, &dll_imports); + + // Check that the dll has imports. + ASSERT_LT(0u, dll_imports.size()) + << "Ensure the delayloads_unittests " + "target was built, instead of delayloads_unittests.exe"; + + static const char* const kValidFilePatterns[] = { + "KERNEL32.dll", + "chrome_elf.dll", + "DWrite.dll", + "ADVAPI32.dll", + "CRYPT32.dll", + "dbghelp.dll", + "dhcpcsvc.DLL", + "IPHLPAPI.DLL", + "ntdll.dll", + "OLEAUT32.dll", + "Secur32.dll", + "UIAutomationCore.DLL", + "USERENV.dll", + "WINHTTP.dll", + "WINMM.dll", + "WINSPOOL.DRV", + "WINTRUST.dll", + "WS2_32.dll", + "WTSAPI32.dll", + // On 64 bit the Version API's like VerQueryValue come from VERSION.dll. + // It depends on kernel32, advapi32 and api-ms-win-crt*.dll. This should + // be ok. + "VERSION.dll", + }; + + // Make sure all of chrome_child.dll's imports are in the valid imports list. + for (const std::string& dll_import : dll_imports) { + bool match = false; + for (const char* kValidFilePattern : kValidFilePatterns) { + if (base::MatchPattern(dll_import, kValidFilePattern)) { + match = true; + break; + } + } + EXPECT_TRUE(match) << "Illegal import in chrome_child.dll: " << dll_import; + } +} + +TEST_F(DelayloadsTest, ChromeChildDllLoadSanityTest) { + // On Win7 we expect this test to result in user32.dll getting loaded. As a + // result, we need to ensure it is executed in its own test process. This + // "test" will re-launch with custom parameters to accomplish that. + base::CommandLine new_test = + base::CommandLine(base::CommandLine::ForCurrentProcess()->GetProgram()); + new_test.AppendSwitchASCII( + base::kGTestFilterFlag, + "DelayloadsTest.DISABLED_ChromeChildDllLoadSanityTest"); + new_test.AppendSwitch("gtest_also_run_disabled_tests"); + new_test.AppendSwitch("single-process-tests"); + + std::string output; + ASSERT_TRUE(base::GetAppOutput(new_test, &output)); + std::string crash_string = + "OK ] DelayloadsTest.DISABLED_ChromeChildDllLoadSanityTest"; + + if (output.find(crash_string) == std::string::npos) { + GTEST_FAIL() << "Couldn't find\n" + << crash_string << "\n in output\n " << output; + } +} + +// Note: This test is not actually disabled, it's just tagged disabled so that +// the real run (above, in ChromeChildDllLoadSanityTest) can run it with an +// argument added to the command line. +TEST_F(DelayloadsTest, DISABLED_ChromeChildDllLoadSanityTest) { + base::FilePath dll; + ASSERT_TRUE(base::PathService::Get(base::DIR_EXE, &dll)); + dll = dll.Append(L"chrome_child.dll"); + + // We don't expect user32 to be loaded in delayloads_unittests. If this + // test case fails, then it means that a dependency on user32 has crept into + // the delayloads_unittests executable, which needs to be removed. + // NOTE: it may be a secondary dependency of another system DLL. If so, + // try adding a "/DELAYLOAD:<blah>.dll" to the build.gn file. + ASSERT_EQ(nullptr, ::GetModuleHandle(L"user32.dll")); + + HMODULE chrome_child_module_handle = ::LoadLibrary(dll.value().c_str()); + ASSERT_TRUE(chrome_child_module_handle != nullptr); + // Loading chrome.dll should not load user32.dll on Win10. + // On Win7, chains of system dlls and lack of apisets result in it loading. + if (base::win::GetVersion() >= base::win::Version::WIN10) { + EXPECT_EQ(nullptr, ::GetModuleHandle(L"user32.dll")); + } else { + EXPECT_NE(nullptr, ::GetModuleHandle(L"user32.dll")); + } + EXPECT_TRUE(!!::FreeLibrary(chrome_child_module_handle)); +} + +TEST_F(DelayloadsTest, ChromeExeDelayloadsCheck) { + std::vector<std::string> exe_imports; + base::FilePath exe; + ASSERT_TRUE(base::PathService::Get(base::DIR_EXE, &exe)); + exe = exe.Append(L"chrome.exe"); + GetImports(exe, &exe_imports); + + // Check that chrome.exe has imports. + ASSERT_LT(0u, exe_imports.size()) + << "Ensure the delayloads_unittests " + "target was built, instead of delayloads_unittests.exe"; + + static const char* const kValidFilePatterns[] = { + "KERNEL32.dll", + "chrome_elf.dll", + // On 64 bit the Version API's like VerQueryValue come from VERSION.dll. + // It depends on kernel32, advapi32 and api-ms-win-crt*.dll. This should + // be ok. + "VERSION.dll", + }; + + // Make sure all of chrome.exe's imports are in the valid imports list. + for (const std::string& exe_import : exe_imports) { + bool match = false; + for (const char* kValidFilePattern : kValidFilePatterns) { + if (base::MatchPattern(exe_import, kValidFilePattern)) { + match = true; + break; + } + } + EXPECT_TRUE(match) << "Illegal import in chrome.exe: " << exe_import; + } +} + +#endif // NDEBUG && !COMPONENT_BUILD + +} // namespace + +int main(int argc, char** argv) { + // Ensure that the CommandLine instance honors the command line passed in + // instead of the default behavior on Windows which is to use the shell32 + // CommandLineToArgvW API. The delayloads_unittests test suite should + // not depend on user32 directly or indirectly (For the curious shell32 + // depends on user32) + base::CommandLine::InitUsingArgvForTesting(argc, argv); + + install_static::ScopedInstallDetails scoped_install_details; + + base::TestSuite test_suite(argc, argv); + return base::LaunchUnitTests( + argc, argv, + base::BindOnce(&base::TestSuite::Run, base::Unretained(&test_suite))); +}
diff --git a/chrome_elf/BUILD.gn b/chrome_elf/BUILD.gn index 7643f279..b637b09 100644 --- a/chrome_elf/BUILD.gn +++ b/chrome_elf/BUILD.gn
@@ -92,7 +92,13 @@ ldflags = [ "/DELAYLOAD:advapi32.dll", "/DELAYLOAD:dbghelp.dll", + "/DELAYLOAD:ole32.dll", + "/DELAYLOAD:oleaut32.dll", + "/DELAYLOAD:propsys.dll", "/DELAYLOAD:rpcrt4.dll", + "/DELAYLOAD:shell32.dll", + "/DELAYLOAD:shlwapi.dll", + "/DELAYLOAD:user32.dll", "/DELAYLOAD:winmm.dll", ] if (current_cpu == "x86") {
diff --git a/chromeos/dbus/session_manager/fake_session_manager_client.cc b/chromeos/dbus/session_manager/fake_session_manager_client.cc index f5ddf40..4c468e5 100644 --- a/chromeos/dbus/session_manager/fake_session_manager_client.cc +++ b/chromeos/dbus/session_manager/fake_session_manager_client.cc
@@ -34,7 +34,6 @@ namespace { -constexpr char kFakeContainerInstanceId[] = "0123456789ABCDEF"; constexpr char kStubDevicePolicyFileNamePrefix[] = "stub_device_policy"; constexpr char kStubPerAccountPolicyFileNamePrefix[] = "stub_policy"; constexpr char kStubStateKeysFileName[] = "stub_state_keys"; @@ -564,16 +563,16 @@ void FakeSessionManagerClient::StartArcMiniContainer( const login_manager::StartArcMiniContainerRequest& request, - StartArcMiniContainerCallback callback) { + VoidDBusMethodCallback callback) { last_start_arc_mini_container_request_ = request; if (!arc_available_) { - PostReply(FROM_HERE, std::move(callback), base::nullopt); + PostReply(FROM_HERE, std::move(callback), false); return; } // This is starting a new container. - base::Base64Encode(kFakeContainerInstanceId, &container_instance_id_); - PostReply(FROM_HERE, std::move(callback), container_instance_id_); + container_running_ = true; + PostReply(FROM_HERE, std::move(callback), true); } void FakeSessionManagerClient::UpgradeArcContainer( @@ -591,8 +590,7 @@ FROM_HERE, base::BindOnce(&FakeSessionManagerClient::NotifyArcInstanceStopped, weak_ptr_factory_.GetWeakPtr(), - login_manager::ArcContainerStopReason::LOW_DISK_SPACE, - std::move(container_instance_id_))); + login_manager::ArcContainerStopReason::LOW_DISK_SPACE)); PostReply(FROM_HERE, std::move(error_callback), true); return; } @@ -602,7 +600,7 @@ void FakeSessionManagerClient::StopArcInstance( VoidDBusMethodCallback callback) { - if (!arc_available_ || container_instance_id_.empty()) { + if (!arc_available_ || !container_running_) { PostReply(FROM_HERE, std::move(callback), false /* result */); return; } @@ -613,9 +611,8 @@ FROM_HERE, base::BindOnce(&FakeSessionManagerClient::NotifyArcInstanceStopped, weak_ptr_factory_.GetWeakPtr(), - login_manager::ArcContainerStopReason::USER_REQUEST, - std::move(container_instance_id_))); - container_instance_id_.clear(); + login_manager::ArcContainerStopReason::USER_REQUEST)); + container_running_ = false; } void FakeSessionManagerClient::SetArcCpuRestriction( @@ -638,10 +635,9 @@ } void FakeSessionManagerClient::NotifyArcInstanceStopped( - login_manager::ArcContainerStopReason reason, - const std::string& container_instance_id) { + login_manager::ArcContainerStopReason reason) { for (auto& observer : observers_) - observer.ArcInstanceStopped(reason, container_instance_id); + observer.ArcInstanceStopped(reason); } bool FakeSessionManagerClient::GetFlagsForUser(
diff --git a/chromeos/dbus/session_manager/fake_session_manager_client.h b/chromeos/dbus/session_manager/fake_session_manager_client.h index c4b40ae..31a7d23 100644 --- a/chromeos/dbus/session_manager/fake_session_manager_client.h +++ b/chromeos/dbus/session_manager/fake_session_manager_client.h
@@ -109,7 +109,7 @@ void StartArcMiniContainer( const login_manager::StartArcMiniContainerRequest& request, - StartArcMiniContainerCallback callback) override; + VoidDBusMethodCallback callback) override; void UpgradeArcContainer( const login_manager::UpgradeArcContainerRequest& request, base::OnceClosure success_callback, @@ -123,8 +123,7 @@ void GetArcStartTime(DBusMethodCallback<base::TimeTicks> callback) override; // Notifies observers as if ArcInstanceStopped signal is received. - void NotifyArcInstanceStopped(login_manager::ArcContainerStopReason, - const std::string& conainer_instance_id); + void NotifyArcInstanceStopped(login_manager::ArcContainerStopReason); // Returns true if flags for |cryptohome_id| have been set. If the return // value is |true|, |*out_flags_for_user| is filled with the flags passed to @@ -241,10 +240,6 @@ force_state_keys_missing_ = force_state_keys_missing; } - const std::string& container_instance_id() const { - return container_instance_id_; - } - bool session_stopped() const { return session_stopped_; } const SessionManagerClient::ActiveSessionsMap& user_sessions() const { @@ -292,8 +287,7 @@ base::TimeTicks arc_start_time_; bool low_disk_ = false; - // Pseudo running container id. If not running, empty. - std::string container_instance_id_; + bool container_running_ = false; // Contains last request passed to StartArcMiniContainer login_manager::StartArcMiniContainerRequest
diff --git a/chromeos/dbus/session_manager/session_manager_client.cc b/chromeos/dbus/session_manager/session_manager_client.cc index cccdcdf..7a951402 100644 --- a/chromeos/dbus/session_manager/session_manager_client.cc +++ b/chromeos/dbus/session_manager/session_manager_client.cc
@@ -402,7 +402,7 @@ void StartArcMiniContainer( const login_manager::StartArcMiniContainerRequest& request, - StartArcMiniContainerCallback callback) override { + VoidDBusMethodCallback callback) override { DCHECK(!callback.is_null()); dbus::MethodCall method_call( login_manager::kSessionManagerInterface, @@ -413,7 +413,7 @@ session_manager_proxy_->CallMethod( &method_call, kStartArcTimeout, - base::BindOnce(&SessionManagerClientImpl::OnStartArcMiniContainer, + base::BindOnce(&SessionManagerClientImpl::OnVoidMethod, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } @@ -739,13 +739,8 @@ return; } - std::string container_instance_id; - if (!reader.PopString(&container_instance_id)) { - LOG(ERROR) << "Invalid signal: " << signal->ToString(); - return; - } for (auto& observer : observers_) - observer.ArcInstanceStopped(reason, container_instance_id); + observer.ArcInstanceStopped(reason); } // Called when the object is connected to the signal. @@ -800,23 +795,6 @@ std::move(callback).Run(base::TimeTicks::FromInternalValue(ticks)); } - void OnStartArcMiniContainer(StartArcMiniContainerCallback callback, - dbus::Response* response) { - if (!response) { - std::move(callback).Run(base::nullopt); - return; - } - - dbus::MessageReader reader(response); - std::string container_instance_id; - if (!reader.PopString(&container_instance_id)) { - LOG(ERROR) << "Invalid response: " << response->ToString(); - std::move(callback).Run(base::nullopt); - return; - } - std::move(callback).Run(std::move(container_instance_id)); - } - void OnUpgradeArcContainer(base::OnceClosure success_callback, UpgradeErrorCallback error_callback, dbus::Response* response,
diff --git a/chromeos/dbus/session_manager/session_manager_client.h b/chromeos/dbus/session_manager/session_manager_client.h index 381d195..67f25444 100644 --- a/chromeos/dbus/session_manager/session_manager_client.h +++ b/chromeos/dbus/session_manager/session_manager_client.h
@@ -75,11 +75,9 @@ // Called when the ARC instance is stopped after it had already started. // |clean| is true if the instance was stopped as a result of an explicit // request, false if it died unexpectedly. - // |container_instance_id| is the identifier of the container instance. // See details for StartArcInstanceCallback. virtual void ArcInstanceStopped( - login_manager::ArcContainerStopReason reason, - const std::string& container_instance_id) {} + login_manager::ArcContainerStopReason reason) {} }; // Interface for performing actions on behalf of the stub implementation. @@ -335,15 +333,10 @@ virtual void GetServerBackedStateKeys(StateKeysCallback callback) = 0; // StartArcMiniContainer starts a container with only a handful of ARC - // processes for Chrome OS login screen. In case of success, callback will be - // called with |container_instance_id| set to a string. The ID is passed to - // ArcInstanceStopped() to identify which instance is stopped. In case of - // error, |container_instance_id| will be nullopt. - using StartArcMiniContainerCallback = - DBusMethodCallback<std::string /* container_instance_id */>; + // processes for Chrome OS login screen. virtual void StartArcMiniContainer( const login_manager::StartArcMiniContainerRequest& request, - StartArcMiniContainerCallback callback) = 0; + VoidDBusMethodCallback callback) = 0; // UpgradeArcContainer upgrades a mini-container to a full ARC container. In // case of success, success_callback is called. In case of error,
diff --git a/chromeos/dbus/shill/fake_shill_device_client.cc b/chromeos/dbus/shill/fake_shill_device_client.cc index e6d3157..75eaf2fd 100644 --- a/chromeos/dbus/shill/fake_shill_device_client.cc +++ b/chromeos/dbus/shill/fake_shill_device_client.cc
@@ -52,12 +52,6 @@ PostError(shill::kErrorResultNotFound, error_callback); } -bool IsReadOnlyProperty(const std::string& name) { - if (name == shill::kCarrierProperty) - return true; - return false; -} - } // namespace const char FakeShillDeviceClient::kDefaultSimPin[] = "1111"; @@ -98,8 +92,6 @@ const base::Value& value, const base::Closure& callback, const ErrorCallback& error_callback) { - if (IsReadOnlyProperty(name)) - PostError(shill::kErrorResultInvalidArguments, error_callback); SetPropertyInternal(device_path, name, value, callback, error_callback, /*notify_changed=*/true); }
diff --git a/chromeos/dbus/shill/fake_shill_manager_client.cc b/chromeos/dbus/shill/fake_shill_manager_client.cc index 06feb8ee..f74f989 100644 --- a/chromeos/dbus/shill/fake_shill_manager_client.cc +++ b/chromeos/dbus/shill/fake_shill_manager_client.cc
@@ -225,7 +225,6 @@ FakeShillManagerClient::FakeShillManagerClient() : interactive_delay_(0), - cellular_carrier_(shill::kCarrierSprint), cellular_technology_(shill::kNetworkTechnologyGsm), weak_ptr_factory_(this) { ParseCommandLineSwitch(); @@ -845,8 +844,6 @@ AddTechnology(shill::kTypeCellular, enabled); devices->AddDevice("/device/cellular1", shill::kTypeCellular, "stub_cellular_device1"); - SetInitialDeviceProperty("/device/cellular1", shill::kCarrierProperty, - base::Value(cellular_carrier_)); if (roaming_state_ == kRoamingRequired) { SetInitialDeviceProperty("/device/cellular1", shill::kProviderRequiresRoamingProperty, @@ -1174,9 +1171,6 @@ else s_tdls_busy_count = 1; return true; - } else if (arg0 == "carrier") { - cellular_carrier_ = arg1; - return true; } else if (arg0 == "olp") { cellular_olp_ = arg1; return true;
diff --git a/chromeos/dbus/shill/fake_shill_manager_client.h b/chromeos/dbus/shill/fake_shill_manager_client.h index a5df436..3eb41d4 100644 --- a/chromeos/dbus/shill/fake_shill_manager_client.h +++ b/chromeos/dbus/shill/fake_shill_manager_client.h
@@ -134,9 +134,6 @@ // Initial state for fake services. std::map<std::string, std::string> shill_initial_state_map_; - // Carrier for fake cellular service. - std::string cellular_carrier_; - // URL used for cellular activation. std::string cellular_olp_;
diff --git a/chromeos/network/device_state.cc b/chromeos/network/device_state.cc index 8eba468..db6e4c22 100644 --- a/chromeos/network/device_state.cc +++ b/chromeos/network/device_state.cc
@@ -64,8 +64,6 @@ country_code_ = country_code ? country_code->GetString() : ""; } else if (key == shill::kTechnologyFamilyProperty) { return GetStringValue(key, value, &technology_family_); - } else if (key == shill::kCarrierProperty) { - return GetStringValue(key, value, &carrier_); } else if (key == shill::kFoundNetworksProperty) { const base::ListValue* list = nullptr; if (!value.GetAsList(&list))
diff --git a/chromeos/network/device_state.h b/chromeos/network/device_state.h index a7ddd1af..8c6c155 100644 --- a/chromeos/network/device_state.h +++ b/chromeos/network/device_state.h
@@ -43,7 +43,6 @@ bool provider_requires_roaming() const { return provider_requires_roaming_; } bool support_network_scan() const { return support_network_scan_; } const std::string& technology_family() const { return technology_family_; } - const std::string& carrier() const { return carrier_; } bool sim_present() const { return sim_present_; } const std::string& sim_lock_type() const { return sim_lock_type_; } int sim_retries_left() const { return sim_retries_left_; } @@ -98,7 +97,6 @@ bool support_network_scan_; bool scanning_; std::string technology_family_; - std::string carrier_; std::string sim_lock_type_; int sim_retries_left_; bool sim_lock_enabled_;
diff --git a/chromeos/network/network_connect.cc b/chromeos/network/network_connect.cc index 78e9241..dd711d6 100644 --- a/chromeos/network/network_connect.cc +++ b/chromeos/network/network_connect.cc
@@ -31,14 +31,6 @@ void IgnoreDisconnectError(const std::string& error_name, std::unique_ptr<base::DictionaryValue> error_data) {} -// Returns true for carriers that can be activated through Shill instead of -// through a WebUI dialog. -bool IsDirectActivatedCarrier(const std::string& carrier) { - if (carrier == shill::kCarrierSprint) - return true; - return false; -} - const NetworkState* GetNetworkStateFromId(const std::string& network_id) { // Note: network_id === NetworkState::guid. return NetworkHandler::Get() @@ -76,10 +68,6 @@ void OnConnectSucceeded(const std::string& network_id); void CallConnectToNetwork(const std::string& network_id, bool check_error_state); - void OnActivateFailed(const std::string& network_id, - const std::string& error_name, - std::unique_ptr<base::DictionaryValue> error_data); - void OnActivateSucceeded(const std::string& network_id); void OnConfigureFailed(const std::string& error_name, std::unique_ptr<base::DictionaryValue> error_data); void OnConfigureSucceeded(bool connect_on_configure, @@ -234,19 +222,6 @@ check_error_state, ConnectCallbackMode::ON_COMPLETED); } -void NetworkConnectImpl::OnActivateFailed( - const std::string& network_id, - const std::string& error_name, - std::unique_ptr<base::DictionaryValue> error_data) { - NET_LOG_ERROR("Unable to activate network", network_id); - delegate_->ShowNetworkConnectError( - NetworkConnectionHandler::kErrorActivateFailed, network_id); -} - -void NetworkConnectImpl::OnActivateSucceeded(const std::string& network_id) { - NET_LOG_USER("Activation Succeeded", network_id); -} - void NetworkConnectImpl::OnConfigureFailed( const std::string& error_name, std::unique_ptr<base::DictionaryValue> error_data) { @@ -442,31 +417,9 @@ NET_LOG_ERROR("ActivateCellular with no Service", network_id); return; } - const DeviceState* cellular_device = - NetworkHandler::Get()->network_state_handler()->GetDeviceState( - cellular->device_path()); - if (!cellular_device) { - NET_LOG_ERROR("ActivateCellular with no Device", network_id); - return; - } - if (!IsDirectActivatedCarrier(cellular_device->carrier())) { - // For non direct activation, show the mobile setup dialog which can be - // used to activate the network. - ShowMobileSetup(network_id); - return; - } - if (cellular->activation_state() == shill::kActivationStateActivated) { - NET_LOG_ERROR("ActivateCellular for activated service", network_id); - return; - } - - NetworkHandler::Get()->network_activation_handler()->Activate( - cellular->path(), - "", // carrier - base::Bind(&NetworkConnectImpl::OnActivateSucceeded, - weak_factory_.GetWeakPtr(), network_id), - base::Bind(&NetworkConnectImpl::OnActivateFailed, - weak_factory_.GetWeakPtr(), network_id)); + // Cellular activation now always goes through an online portal shown by the + // mobile setup dialog. + ShowMobileSetup(network_id); } void NetworkConnectImpl::ShowMobileSetup(const std::string& network_id) {
diff --git a/chromeos/network/onc/onc_translation_tables.cc b/chromeos/network/onc/onc_translation_tables.cc index 29e9337..3df64b79 100644 --- a/chromeos/network/onc/onc_translation_tables.cc +++ b/chromeos/network/onc/onc_translation_tables.cc
@@ -400,7 +400,6 @@ // This field is converted during translation, see onc_translator_*. // { ::onc::cellular::kAPNList, shill::kCellularApnListProperty}, {::onc::cellular::kAllowRoaming, shill::kCellularAllowRoamingProperty}, - {::onc::cellular::kCarrier, shill::kCarrierProperty}, {::onc::cellular::kESN, shill::kEsnProperty}, {::onc::cellular::kFamily, shill::kTechnologyFamilyProperty}, {::onc::cellular::kFirmwareRevision, shill::kFirmwareRevisionProperty},
diff --git a/chromeos/test/data/network/shill_cellular_with_state.json b/chromeos/test/data/network/shill_cellular_with_state.json index 90abf69a..a343f09 100644 --- a/chromeos/test/data/network/shill_cellular_with_state.json +++ b/chromeos/test/data/network/shill_cellular_with_state.json
@@ -48,7 +48,6 @@ "password": "test-password3" } ], - "Cellular.Carrier": "cellular_provider", "Cellular.HomeProvider": { "country": "us", "name": "cellular_provider"
diff --git a/chromeos/test/data/network/translation_of_shill_cellular_with_state.onc b/chromeos/test/data/network/translation_of_shill_cellular_with_state.onc index ff2b31ac..e4387244 100644 --- a/chromeos/test/data/network/translation_of_shill_cellular_with_state.onc +++ b/chromeos/test/data/network/translation_of_shill_cellular_with_state.onc
@@ -6,7 +6,6 @@ "ActivationType": "OTASP", "AllowRoaming": true, "AutoConnect": true, - "Carrier": "cellular_provider", "HomeProvider": { "Country": "us", "Name": "cellular_provider"
diff --git a/components/arc/session/arc_client_adapter.h b/components/arc/session/arc_client_adapter.h index 464d94b..7ab0a70 100644 --- a/components/arc/session/arc_client_adapter.h +++ b/components/arc/session/arc_client_adapter.h
@@ -30,8 +30,7 @@ class Observer { public: virtual ~Observer() = default; - virtual void ArcInstanceStopped(ArcContainerStopReason stop_reason, - const std::string& instance_id) = 0; + virtual void ArcInstanceStopped(ArcContainerStopReason stop_reason) = 0; }; // Creates a default instance of ArcClientAdapter. @@ -39,14 +38,9 @@ virtual ~ArcClientAdapter(); // StartMiniArc starts ARC with only a handful of ARC processes for Chrome OS - // login screen. In case of success, callback will be called with - // |instance_id| set to a string. The ID is passed to ArcInstanceStopped() - // to identify which instance is stopped. In case of error, |instance_id| will - // be nullopt. - using StartMiniArcCallback = - base::OnceCallback<void(base::Optional<std::string> instance_id)>; + // login screen. virtual void StartMiniArc(const StartArcMiniContainerRequest& request, - StartMiniArcCallback callback) = 0; + chromeos::VoidDBusMethodCallback callback) = 0; // UpgradeArc upgrades a mini ARC instance to a full ARC instance. In case of // success, success_callback is called. In case of error, |error_callback|
diff --git a/components/arc/session/arc_container_client_adapter.cc b/components/arc/session/arc_container_client_adapter.cc index 2bed3ba..8acd633e 100644 --- a/components/arc/session/arc_container_client_adapter.cc +++ b/components/arc/session/arc_container_client_adapter.cc
@@ -31,7 +31,7 @@ // ArcClientAdapter overrides: void StartMiniArc(const StartArcMiniContainerRequest& request, - StartMiniArcCallback callback) override { + chromeos::VoidDBusMethodCallback callback) override { chromeos::SessionManagerClient::Get()->StartArcMiniContainer( request, std::move(callback)); } @@ -51,10 +51,10 @@ } // chromeos::SessionManagerClient::Observer overrides: - void ArcInstanceStopped(login_manager::ArcContainerStopReason stop_reason, - const std::string& container_instance_id) override { + void ArcInstanceStopped( + login_manager::ArcContainerStopReason stop_reason) override { for (auto& observer : observer_list_) - observer.ArcInstanceStopped(stop_reason, container_instance_id); + observer.ArcInstanceStopped(stop_reason); } private:
diff --git a/components/arc/session/arc_session_impl.cc b/components/arc/session/arc_session_impl.cc index 8eec8e4..fbc855d0 100644 --- a/components/arc/session/arc_session_impl.cc +++ b/components/arc/session/arc_session_impl.cc
@@ -445,19 +445,16 @@ } } -void ArcSessionImpl::OnMiniInstanceStarted( - base::Optional<std::string> container_instance_id) { +void ArcSessionImpl::OnMiniInstanceStarted(bool result) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_EQ(state_, State::STARTING_MINI_INSTANCE); - if (!container_instance_id) { + if (!result) { OnStopped(GetArcStopReason(false, stop_requested_)); return; } - container_instance_id_ = std::move(*container_instance_id); - VLOG(2) << "ARC mini instance is successfully started: " - << container_instance_id_; + VLOG(2) << "ARC mini container has been successfully started."; if (stop_requested_) { // The ARC instance has started to run. Request to stop. @@ -663,22 +660,11 @@ client_->StopArcInstance(); } -void ArcSessionImpl::ArcInstanceStopped( - ArcContainerStopReason stop_reason, - const std::string& container_instance_id) { +void ArcSessionImpl::ArcInstanceStopped(ArcContainerStopReason stop_reason) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); VLOG(1) << "Notified that ARC instance is stopped " << static_cast<uint32_t>(stop_reason); - if (container_instance_id != container_instance_id_) { - VLOG(1) << "Container instance id mismatch. Do nothing." - << container_instance_id << " vs " << container_instance_id_; - return; - } - - // Release |container_instance_id_| to avoid duplicate invocation situation. - container_instance_id_.clear(); - // In case that crash happens during before the Mojo channel is connected, // unlock the ThreadPool's thread. accept_cancel_pipe_.reset();
diff --git a/components/arc/session/arc_session_impl.h b/components/arc/session/arc_session_impl.h index 6c19395d..6c24d18 100644 --- a/components/arc/session/arc_session_impl.h +++ b/components/arc/session/arc_session_impl.h
@@ -180,7 +180,7 @@ private: // D-Bus callback for StartArcMiniContainer(). - void OnMiniInstanceStarted(base::Optional<std::string> container_instance_id); + void OnMiniInstanceStarted(bool result); // Sends a D-Bus message to upgrade to a full instance. void DoUpgrade(); @@ -205,8 +205,7 @@ void StopArcInstance(); // ArcClientAdapter::Observer: - void ArcInstanceStopped(ArcContainerStopReason stop_reason, - const std::string& container_instance_id) override; + void ArcInstanceStopped(ArcContainerStopReason stop_reason) override; // Completes the termination procedure. Note that calling this may end up with // deleting |this| because the function calls observers' OnSessionStopped(). @@ -234,10 +233,6 @@ // Whether the full container has been requested bool upgrade_requested_ = false; - // Container instance id passed from session_manager. - // Should be available only after On{Mini,Full}InstanceStarted(). - std::string container_instance_id_; - // In CONNECTING_MOJO state, this is set to the write side of the pipe // to notify cancelling of the procedure. base::ScopedFD accept_cancel_pipe_;
diff --git a/components/arc/session/arc_session_impl_unittest.cc b/components/arc/session/arc_session_impl_unittest.cc index 34e5821..a7761dffa 100644 --- a/components/arc/session/arc_session_impl_unittest.cc +++ b/components/arc/session/arc_session_impl_unittest.cc
@@ -553,8 +553,7 @@ // Deliver the ArcInstanceStopped D-Bus signal. chromeos::FakeSessionManagerClient::Get()->NotifyArcInstanceStopped( - login_manager::ArcContainerStopReason::CRASH, - chromeos::FakeSessionManagerClient::Get()->container_instance_id()); + login_manager::ArcContainerStopReason::CRASH); EXPECT_EQ(ArcSessionImpl::State::STOPPED, arc_session->GetStateForTesting()); ASSERT_TRUE(observer.on_session_stopped_args().has_value()); @@ -563,26 +562,6 @@ EXPECT_TRUE(observer.on_session_stopped_args()->upgrade_requested); } -// ArcStopInstance for the *previous* ARC container may be reported -// to the current instance in very racy timing. -// Unrelated ArcStopInstance signal should be ignored. -TEST_F(ArcSessionImplTest, ArcStopInstance_WrongContainerInstanceId) { - auto arc_session = CreateArcSession(); - arc_session->StartMiniInstance(); - arc_session->RequestUpgrade(DefaultUpgradeParams()); - base::RunLoop().RunUntilIdle(); - ASSERT_EQ(ArcSessionImpl::State::RUNNING_FULL_INSTANCE, - arc_session->GetStateForTesting()); - - // Deliver the ArcInstanceStopped D-Bus signal. - chromeos::FakeSessionManagerClient::Get()->NotifyArcInstanceStopped( - login_manager::ArcContainerStopReason::CRASH, "dummy instance id"); - - // The signal should be ignored. - EXPECT_EQ(ArcSessionImpl::State::RUNNING_FULL_INSTANCE, - arc_session->GetStateForTesting()); -} - struct PackagesCacheModeState { // Possible values for chromeos::switches::kArcPackagesCacheMode const char* chrome_switch;
diff --git a/components/arc/session/arc_vm_client_adapter.cc b/components/arc/session/arc_vm_client_adapter.cc index e0c5ad1..abf12b81 100644 --- a/components/arc/session/arc_vm_client_adapter.cc +++ b/components/arc/session/arc_vm_client_adapter.cc
@@ -38,13 +38,10 @@ // ArcClientAdapter overrides: void StartMiniArc(const StartArcMiniContainerRequest& request, - StartMiniArcCallback callback) override { + chromeos::VoidDBusMethodCallback callback) override { // TODO(yusukes): Support mini ARC. VLOG(2) << "Mini ARC instance is not supported yet."; - base::PostTask( - FROM_HERE, - base::BindOnce(&ArcVmClientAdapter::OnArcMiniInstanceStarted, - weak_factory_.GetWeakPtr(), std::move(callback))); + base::PostTask(FROM_HERE, base::BindOnce(std::move(callback), true)); } void UpgradeArc(const UpgradeArcContainerRequest& request, @@ -79,11 +76,6 @@ } private: - void OnArcMiniInstanceStarted(StartMiniArcCallback callback) { - current_instance_id_ = base::GenerateGUID(); - std::move(callback).Run(current_instance_id_); - } - void OnArcInstanceUpgraded(base::OnceClosure success_callback, UpgradeErrorCallback error_callback, bool result) { @@ -99,14 +91,9 @@ if (!result) LOG(WARNING) << "Failed to stop arcvm. Instance not running?"; for (auto& observer : observer_list_) - observer.ArcInstanceStopped(kDummyReason, current_instance_id_); - if (result) - current_instance_id_.clear(); + observer.ArcInstanceStopped(kDummyReason); } - // A unique ID associated with the current Upstart job. - std::string current_instance_id_; - // For callbacks. base::WeakPtrFactory<ArcVmClientAdapter> weak_factory_;
diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn index e39d2fdb..dd1a57f 100644 --- a/components/autofill/core/browser/BUILD.gn +++ b/components/autofill/core/browser/BUILD.gn
@@ -629,7 +629,6 @@ "//third_party/libaddressinput:test_support", "//third_party/libaddressinput:util", "//third_party/libphonenumber", - "//third_party/re2:re2", "//ui/base", "//url", ]
diff --git a/components/autofill/core/browser/autofill_download_manager.cc b/components/autofill/core/browser/autofill_download_manager.cc index 19cfb64..1b9a042 100644 --- a/components/autofill/core/browser/autofill_download_manager.cc +++ b/components/autofill/core/browser/autofill_download_manager.cc
@@ -20,6 +20,7 @@ #include "base/rand_util.h" #include "base/strings/strcat.h" #include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/threading/thread_task_runner_handle.h" @@ -59,7 +60,7 @@ {3314445, 3314448}, {3314854, 3314883}, }; -const size_t kMaxQueryGetSize = 1400; // 1.25 KiB +const size_t kMaxQueryGetSize = 1400; // 1.25KB const size_t kAutofillDownloadManagerMaxFormCacheSize = 16; const size_t kMaxFieldsPerQueryRequest = 100; @@ -447,22 +448,16 @@ return upload_request.SerializeToString(payload); } -// Gets an API method URL given its type (query or upload), an optional -// resource ID, and the HTTP method to be used. +// Gets an API method URL given its type (query or upload) and an optional +// resource ID. // Example usage: -// * GetAPIMethodUrl(REQUEST_QUERY, "1234", "GET") will return "/v1/pages/1234". -// * GetAPIMethodUrl(REQUEST_QUERY, "1234", "POST") will return "/v1/pages:get". -// * GetAPIMethodUrl(REQUEST_UPLOAD, "", "POST") will return "/v1/forms:vote". +// * GetAPIMethodUrl(REQUEST_QUERY, "1234") will return "/v1/pages/1234". +// * GetAPIMethodUrl(REQUEST_UPLOAD, "") will return "/v1/forms:vote". std::string GetAPIMethodUrl(AutofillDownloadManager::RequestType type, - base::StringPiece resource_id, - base::StringPiece method) { + base::StringPiece resource_id) { const char* api_method_url; if (type == AutofillDownloadManager::REQUEST_QUERY) { - if (method == "POST") { - api_method_url = "/v1/pages:get"; - } else { - api_method_url = "/v1/pages"; - } + api_method_url = "/v1/pages"; } else if (type == AutofillDownloadManager::REQUEST_UPLOAD) { api_method_url = "/v1/forms:vote"; } else { @@ -476,35 +471,6 @@ return base::StrCat({api_method_url, "/", resource_id}); } -// Gets HTTP body payload for API POST request. -std::string GetAPIBodyPayload(const std::string& payload, - AutofillDownloadManager::RequestType type) { - // Don't do anything for payloads not related to Query. - if (type != AutofillDownloadManager::REQUEST_QUERY) { - return payload; - } - // Wrap query payload in a request proto to interface with API Query method. - AutofillPageResourceQueryRequest request; - request.set_serialized_request(payload); - std::string new_payload; - DCHECK(request.SerializeToString(&new_payload)) - << "could not serialize AutofillPageResourceQueryRequest payload"; - return new_payload; -} - -// Gets the data payload for API Query (POST and GET). -bool GetAPIQueryPayload(const AutofillQueryContents& query, - std::string* payload) { - std::string serialized_query; - if (!CreateApiRequestFromLegacyRequest(query).SerializeToString( - &serialized_query)) { - return false; - } - base::Base64UrlEncode(serialized_query, - base::Base64UrlEncodePolicy::INCLUDE_PADDING, payload); - return true; -} - } // namespace struct AutofillDownloadManager::FormRequestData { @@ -576,8 +542,10 @@ // Get the query request payload. std::string payload; - bool is_payload_serialized = UseApi() ? GetAPIQueryPayload(query, &payload) - : query.SerializeToString(&payload); + bool is_payload_serialized = + UseApi() + ? CreateApiRequestFromLegacyRequest(query).SerializeToString(&payload) + : query.SerializeToString(&payload); if (!is_payload_serialized) { return false; } @@ -673,11 +641,6 @@ } } -size_t AutofillDownloadManager::GetPayloadLength( - base::StringPiece payload) const { - return payload.length(); -} - std::tuple<GURL, std::string> AutofillDownloadManager::GetRequestURLAndMethod( const FormRequestData& request_data) const { std::string method("POST"); @@ -711,27 +674,31 @@ // ID of the resource to add to the API request URL. Nothing will be added if // |resource_id| is empty. std::string resource_id; - std::string method = "POST"; + // Get the resource id of corresponding webpage when doing a query request. if (request_data.request_type == AutofillDownloadManager::REQUEST_QUERY) { - if (request_data.payload.length() <= kMaxAPIQueryGetSize) { - resource_id = request_data.payload; - method = "GET"; - UMA_HISTOGRAM_BOOLEAN("Autofill.Query.ApiUrlIsTooLong", false); - } else { - UMA_HISTOGRAM_BOOLEAN("Autofill.Query.ApiUrlIsTooLong", true); + if (request_data.payload.length() <= kMaxQueryGetSize) { + base::Base64UrlEncode(request_data.payload, + base::Base64UrlEncodePolicy::INCLUDE_PADDING, + &resource_id); } - UMA_HISTOGRAM_BOOLEAN("Autofill.Query.Method", (method == "GET") ? 0 : 1); + // Query method is always GET (represented by 0) with API. + UMA_HISTOGRAM_BOOLEAN("Autofill.Query.Method", 0); } // Make the canonical URL to query the API, e.g., // https://autofill.googleapis.com/v1/forms/1234?alt=proto. GURL url = autofill_server_url_.Resolve( - GetAPIMethodUrl(request_data.request_type, resource_id, method)); + GetAPIMethodUrl(request_data.request_type, resource_id)); // Add the query parameter to set the response format to a serialized proto. url = net::AppendQueryParameter(url, "alt", "proto"); + // Determine the HTTP method that should be used. + std::string method = + (request_data.request_type == AutofillDownloadManager::REQUEST_QUERY) + ? "GET" + : "POST"; return std::make_tuple(std::move(url), std::move(method)); } @@ -747,14 +714,6 @@ UseApi() ? GetRequestURLAndMethodForApi(request_data) : GetRequestURLAndMethod(request_data); - // Track the URL length for GET queries because the URL length can be in the - // thousands when rich metadata is enabled. - if (request_data.request_type == AutofillDownloadManager::REQUEST_QUERY && - method == "GET") { - UMA_HISTOGRAM_COUNTS_100000("Autofill.Query.GetUrlLength", - request_url.spec().length()); - } - auto resource_request = std::make_unique<network::ResourceRequest>(); resource_request->url = request_url; resource_request->load_flags = @@ -791,14 +750,10 @@ simple_loader->SetAllowHttpErrorResults(true); if (method == "POST") { - const std::string& content_type = + const std::string content_type = UseApi() ? "application/x-protobuf" : "text/proto"; - const std::string& payload = - UseApi() - ? GetAPIBodyPayload(request_data.payload, request_data.request_type) - : request_data.payload; // Attach payload data and add data format header. - simple_loader->AttachStringForUpload(payload, content_type); + simple_loader->AttachStringForUpload(request_data.payload, content_type); } // Transfer ownership of the loader into url_loaders_. Temporarily hang
diff --git a/components/autofill/core/browser/autofill_download_manager.h b/components/autofill/core/browser/autofill_download_manager.h index 4fb0ea0..f38c3ae8 100644 --- a/components/autofill/core/browser/autofill_download_manager.h +++ b/components/autofill/core/browser/autofill_download_manager.h
@@ -17,7 +17,6 @@ #include "base/compiler_specific.h" #include "base/gtest_prod_util.h" #include "base/memory/weak_ptr.h" -#include "base/strings/string_piece.h" #include "base/time/time.h" #include "components/autofill/core/browser/autofill_type.h" #include "components/variations/variations_http_header_provider.h" @@ -32,8 +31,6 @@ class AutofillDriver; class FormStructure; -const size_t kMaxAPIQueryGetSize = 10240; // 10 KiB - // A helper to make sure that tests which modify the set of active autofill // experiments do not interfere with one another. struct ScopedActiveAutofillExperiments { @@ -117,12 +114,6 @@ // pair. static void ClearUploadHistory(PrefService* pref_service); - protected: - // Gets the length of the payload from request data. Used to simulate - // different payload sizes when testing without the need for data. Do not use - // this when the length is needed to read/write a buffer. - virtual size_t GetPayloadLength(base::StringPiece payload) const; - private: friend class AutofillDownloadManagerTest; friend struct ScopedActiveAutofillExperiments;
diff --git a/components/autofill/core/browser/autofill_download_manager_unittest.cc b/components/autofill/core/browser/autofill_download_manager_unittest.cc index dc65b18..b6c34b6 100644 --- a/components/autofill/core/browser/autofill_download_manager_unittest.cc +++ b/components/autofill/core/browser/autofill_download_manager_unittest.cc
@@ -52,7 +52,6 @@ #include "services/network/test/test_utils.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" -#include "third_party/re2/src/re2/re2.h" #include "url/third_party/mozilla/url_parse.h" using base::UTF8ToUTF16; @@ -118,54 +117,6 @@ return true; } -bool GetAutofillPageResourceQueryRequestFromRequest( - network::TestURLLoaderFactory::PendingRequest* loader_request, - AutofillPageResourceQueryRequest* query_request) { - if (loader_request->request.request_body == nullptr) { - return false; - } - - std::string request_body_content = GetStringFromDataElements( - loader_request->request.request_body->elements()); - if (!query_request->ParseFromString(request_body_content)) { - return false; - } - return true; -} - -bool DeserializeAutofillPageQueryRequest(base::StringPiece serialized_content, - AutofillPageQueryRequest* request) { - std::string decoded_content; - if (!base::Base64UrlDecode(serialized_content, - base::Base64UrlDecodePolicy::REQUIRE_PADDING, - &decoded_content)) { - return false; - } - if (!request->ParseFromString(decoded_content)) { - return false; - } - return true; -} - -class AutofillDownloadManagerWithCustomPayloadSize - : public AutofillDownloadManager { - public: - ~AutofillDownloadManagerWithCustomPayloadSize() override {} - AutofillDownloadManagerWithCustomPayloadSize(AutofillDriver* driver, - Observer* observer, - const std::string& api_key, - size_t length) - : AutofillDownloadManager(driver, observer, api_key), length_(length) {} - - protected: - size_t GetPayloadLength(base::StringPiece payload) const override { - return length_; - } - - private: - size_t length_; -}; - } // namespace // This tests AutofillDownloadManager. AutofillDownloadManagerTest implements @@ -548,151 +499,22 @@ // Inspect the request that the test URL loader sent. network::TestURLLoaderFactory::PendingRequest* request = test_url_loader_factory_.GetPendingRequest(0); - - // Verify request URL and the data payload it carries. - { - // This is the URL we expect to query the API. The sub-path right after - // "/page" corresponds to the serialized AutofillPageQueryRequest proto - // (that we filled forms in) encoded in base64. The Autofill - // https://clients1.google.com/ domain URL corresponds to the default domain - // used by the download manager, which is invalid, but good for testing. - const std::string expected_url = - R"(https://clients1.google.com/v1/pages/(.+)\?alt=proto)"; - std::string encoded_request; - ASSERT_TRUE(re2::RE2::FullMatch(request->request.url.spec(), expected_url, - &encoded_request)); - AutofillPageQueryRequest request_content; - ASSERT_TRUE( - DeserializeAutofillPageQueryRequest(encoded_request, &request_content)); - // Verify form content. - ASSERT_EQ(request_content.forms().size(), 1); - EXPECT_EQ(request_content.forms(0).signature(), - form_structures[0]->form_signature()); - // Verify field content. - ASSERT_EQ(request_content.forms(0).fields().size(), 2); - EXPECT_EQ(request_content.forms(0).fields(0).signature(), - form_structures[0]->field(0)->GetFieldSignature()); - EXPECT_EQ(request_content.forms(0).fields(1).signature(), - form_structures[0]->field(1)->GetFieldSignature()); - } - - // Verify API key header. - { - std::string header_value; - EXPECT_TRUE( - request->request.headers.GetHeader("X-Goog-Api-Key", &header_value)); - EXPECT_EQ(header_value, "dummykey"); - } - // Verify binary response header. - { - std::string header_value; - ASSERT_TRUE(request->request.headers.GetHeader( - "X-Goog-Encode-Response-If-Executable", &header_value)); - EXPECT_EQ(header_value, "base64"); - } - - // Verify response. - test_url_loader_factory_.SimulateResponseWithoutRemovingFromPendingList( - request, "dummy response"); - // Upon reception of a suggestions query, we expect OnLoadedServerPredictions - // to be called back from the observer and some histograms be incremented. - EXPECT_EQ(1U, responses_.size()); - EXPECT_EQ(responses_.front().type_of_response, - AutofillDownloadManagerTest::QUERY_SUCCESSFULL); - histogram.ExpectBucketCount("Autofill.Query.WasInCache", CACHE_MISS, 1); - histogram.ExpectBucketCount("Autofill.Query.HttpResponseOrErrorCode", - net::HTTP_OK, 1); -} - -TEST_F(AutofillDownloadManagerTest, QueryAPITestWhenTooLongUrl) { - base::test::ScopedFeatureList feature_list; - feature_list.InitWithFeatures( - // Enabled - // We want to query the API rather than the legacy server. - {features::kAutofillUseApi}, - // Disabled - {}); - - // Build the form structures that we want to query. - FormData form; - FormFieldData field; - // Fill a really long field that will bust the request URL size limit of 10 - // KiB. This is not a lot of memory, hence this should not cause problems for - // machines running this test. This will force the fallback to POST. - field.name_attribute = base::string16(kMaxAPIQueryGetSize, 'a'); - field.form_control_type = "text"; - form.fields.push_back(field); - - std::vector<std::unique_ptr<FormStructure>> form_structures; - { - auto form_structure = std::make_unique<FormStructure>(form); - form_structure->set_is_rich_query_enabled(true); - form_structures.push_back(std::move(form_structure)); - } - - AutofillDownloadManagerWithCustomPayloadSize download_manager( - &driver_, this, "dummykey", kMaxAPIQueryGetSize); - - // Start the query request and look if it is successful. No response was - // received yet. - base::HistogramTester histogram; - EXPECT_TRUE( - download_manager.StartQueryRequest(ToRawPointerVector(form_structures))); - - // Verify request. - // Verify if histograms are right. - histogram.ExpectUniqueSample("Autofill.ServerQueryResponse", - AutofillMetrics::QUERY_SENT, 1); - // Verify that the logged method is POST. - histogram.ExpectUniqueSample("Autofill.Query.Method", METHOD_POST, 1); - - // Get the latest request that the test URL loader sent. - network::TestURLLoaderFactory::PendingRequest* request = - test_url_loader_factory_.GetPendingRequest(0); - // Verify that the POST URL is used when request data too large. + // This is the URL we expect to query the API. The sub-path right after + // "/page" corresponds to the serialized AutofillPageQueryRequest proto (that + // we filled forms in) encoded in base64. The Autofill + // https://clients1.google.com/ domain URL corresponds to the default domain + // used by the download manager. const std::string expected_url = { - "https://clients1.google.com/v1/pages:get?alt=proto"}; - // Verify API key header. + "https://clients1.google.com/v1/pages/" + "Chc2LjEuMTcxNS4xNDQyL2VuIChHR0xMKRIlCU9O84MyjH9NEgsNeu" + "FP4BIAGgAiABILDZxOStASABoAIgAaAA==?" + "alt=proto"}; EXPECT_EQ(request->request.url, expected_url); - { - std::string header_value; - EXPECT_TRUE( - request->request.headers.GetHeader("X-Goog-Api-Key", &header_value)); - EXPECT_EQ(header_value, "dummykey"); - } - // Verify Content-Type header. - { - std::string header_value; - ASSERT_TRUE( - request->request.headers.GetHeader("Content-Type", &header_value)); - EXPECT_EQ(header_value, "application/x-protobuf"); - } - // Verify binary response header. - { - std::string header_value; - ASSERT_TRUE(request->request.headers.GetHeader( - "X-Goog-Encode-Response-If-Executable", &header_value)); - EXPECT_EQ(header_value, "base64"); - } - // Verify content of the POST body data. - { - AutofillPageResourceQueryRequest query_request; - ASSERT_TRUE(GetAutofillPageResourceQueryRequestFromRequest(request, - &query_request)); - AutofillPageQueryRequest request_content; - ASSERT_TRUE(DeserializeAutofillPageQueryRequest( - query_request.serialized_request(), &request_content)); - // Verify form content. - ASSERT_EQ(request_content.forms().size(), 1); - EXPECT_EQ(request_content.forms(0).signature(), - form_structures[0]->form_signature()); - // Verify field content. - ASSERT_EQ(request_content.forms(0).fields().size(), 1); - EXPECT_EQ(request_content.forms(0).fields(0).signature(), - form_structures[0]->field(0)->GetFieldSignature()); - } + std::string api_key_header_value; + EXPECT_TRUE(request->request.headers.GetHeader("X-Goog-Api-Key", + &api_key_header_value)); + EXPECT_EQ(api_key_header_value, "dummykey"); - // Verify response. test_url_loader_factory_.SimulateResponseWithoutRemovingFromPendingList( request, "dummy response"); // Upon reception of a suggestions query, we expect OnLoadedServerPredictions
diff --git a/components/autofill/core/browser/personal_data_manager.cc b/components/autofill/core/browser/personal_data_manager.cc index e4795380..a89e682 100644 --- a/components/autofill/core/browser/personal_data_manager.cc +++ b/components/autofill/core/browser/personal_data_manager.cc
@@ -2038,7 +2038,11 @@ bool profile_changes_are_ongoing = ProfileChangesAreOngoing(); for (PersonalDataManagerObserver& observer : observers_) { observer.OnPersonalDataChanged(); - if (!profile_changes_are_ongoing) { + } + if (!profile_changes_are_ongoing) { + // Call OnPersonalDataFinishedProfileTasks in a separate loop as + // the observers might have removed themselves in OnPersonalDataChanged + for (PersonalDataManagerObserver& observer : observers_) { observer.OnPersonalDataFinishedProfileTasks(); } }
diff --git a/components/autofill/core/browser/personal_data_manager_unittest.cc b/components/autofill/core/browser/personal_data_manager_unittest.cc index c2f1f6f..304793f2 100644 --- a/components/autofill/core/browser/personal_data_manager_unittest.cc +++ b/components/autofill/core/browser/personal_data_manager_unittest.cc
@@ -7853,4 +7853,46 @@ } } +namespace { + +class OneTimeObserver : public PersonalDataManagerObserver { + public: + OneTimeObserver(PersonalDataManager* manager) : manager_(manager) {} + + ~OneTimeObserver() override { + if (manager_) + manager_->RemoveObserver(this); + } + + void OnPersonalDataChanged() override { + ASSERT_TRUE(manager_) << "Callback called after RemoveObserver()"; + manager_->RemoveObserver(this); + manager_ = nullptr; + } + + void OnPersonalDataFinishedProfileTasks() override { + EXPECT_TRUE(manager_) << "Callback called after RemoveObserver()"; + } + + bool IsConnected() { return manager_; } + + private: + PersonalDataManager* manager_; +}; + +} // namespace + +TEST_F(PersonalDataManagerTest, RemoveObserverInOnPersonalDataChanged) { + OneTimeObserver observer(personal_data_.get()); + + personal_data_->AddObserver(&observer); + + // Do something to trigger a data change + personal_data_->AddProfile(test::GetFullProfile()); + + WaitForOnPersonalDataChanged(); + + EXPECT_FALSE(observer.IsConnected()) << "Observer not called"; +} + } // namespace autofill
diff --git a/components/browser_sync/profile_sync_components_factory_impl.cc b/components/browser_sync/profile_sync_components_factory_impl.cc index 2d48f06..51fa026 100644 --- a/components/browser_sync/profile_sync_components_factory_impl.cc +++ b/components/browser_sync/profile_sync_components_factory_impl.cc
@@ -27,6 +27,7 @@ #include "components/password_manager/core/browser/sync/password_model_type_controller.h" #include "components/prefs/pref_service.h" #include "components/reading_list/features/reading_list_switches.h" +#include "components/send_tab_to_self/send_tab_to_self_model_type_controller.h" #include "components/send_tab_to_self/send_tab_to_self_sync_service.h" #include "components/sync/base/report_unrecoverable_error.h" #include "components/sync/device_info/device_info_sync_service.h" @@ -378,12 +379,13 @@ if (!disabled_types.Has(syncer::SEND_TAB_TO_SELF) && base::FeatureList::IsEnabled(switches::kSyncSendTabToSelf)) { - controllers.push_back(std::make_unique<syncer::ModelTypeController>( - syncer::SEND_TAB_TO_SELF, - std::make_unique<syncer::ForwardingModelTypeControllerDelegate>( - sync_client_->GetSendTabToSelfSyncService() - ->GetControllerDelegate() - .get()))); + controllers.push_back( + std::make_unique<send_tab_to_self::SendTabToSelfModelTypeController>( + sync_service, + std::make_unique<syncer::ForwardingModelTypeControllerDelegate>( + sync_client_->GetSendTabToSelfSyncService() + ->GetControllerDelegate() + .get()))); } // Forward both on-disk and in-memory storage modes to the same delegate,
diff --git a/components/crash/content/app/crashpad_linux.cc b/components/crash/content/app/crashpad_linux.cc index 8fdee23..aed45c36 100644 --- a/components/crash/content/app/crashpad_linux.cc +++ b/components/crash/content/app/crashpad_linux.cc
@@ -134,7 +134,7 @@ ScopedPrSetDumpable set_dumpable(/* may_log= */ false); - ExceptionHandlerClient handler_client(connection.get()); + ExceptionHandlerClient handler_client(connection.get(), false); handler_client.SetCanSetPtracer(false); handler_client.RequestCrashDump(info); } @@ -670,7 +670,7 @@ crashpad::ScopedPrSetDumpable set_dumpable(/* may_log= */ false); - crashpad::ExceptionHandlerClient handler_client(connection.get()); + crashpad::ExceptionHandlerClient handler_client(connection.get(), false); return handler_client.RequestCrashDump(info) == 0; }
diff --git a/components/feedback/feedback_uploader.cc b/components/feedback/feedback_uploader.cc index 90a5b3fd..a1a9e6d5 100644 --- a/components/feedback/feedback_uploader.cc +++ b/components/feedback/feedback_uploader.cc
@@ -105,10 +105,7 @@ retry_delay_ *= 2; report_being_dispatched_->set_upload_at(retry_delay_ + base::Time::Now()); reports_queue_.emplace(report_being_dispatched_); - VLOG(1) << "Report upload failed. Will retry again after " - << retry_delay_.InSeconds() << " seconds."; } else { - VLOG(1) << "Report upload failed. Will discard."; // The report won't be retried, hence explicitly delete its file on disk. report_being_dispatched_->DeleteReportOnDisk(); } @@ -132,7 +129,6 @@ network::ResourceRequest* resource_request) {} void FeedbackUploader::DispatchReport() { - VLOG(1) << "Uploading report."; net::NetworkTrafficAnnotationTag traffic_annotation = net::DefineNetworkTrafficAnnotation("chrome_feedback_report_app", R"( semantics { @@ -260,7 +256,6 @@ void FeedbackUploader::QueueReportWithDelay(std::unique_ptr<std::string> data, base::TimeDelta delay) { - VLOG(1) << "Queuing report with delay = " << delay.InSeconds() << " seconds."; reports_queue_.emplace(base::MakeRefCounted<FeedbackReport>( feedback_reports_path_, base::Time::Now() + delay, std::move(data), task_runner_));
diff --git a/components/gwp_asan/crash_handler/BUILD.gn b/components/gwp_asan/crash_handler/BUILD.gn index 2da1723..3757ab08 100644 --- a/components/gwp_asan/crash_handler/BUILD.gn +++ b/components/gwp_asan/crash_handler/BUILD.gn
@@ -45,6 +45,7 @@ "//base/test:test_support", "//components/gwp_asan/client", "//components/gwp_asan/common", + "//testing/gmock", "//testing/gtest", "//third_party/crashpad/crashpad/client", "//third_party/crashpad/crashpad/handler",
diff --git a/components/gwp_asan/crash_handler/crash_analyzer.cc b/components/gwp_asan/crash_handler/crash_analyzer.cc index bf5be934..531efbbf 100644 --- a/components/gwp_asan/crash_handler/crash_analyzer.cc +++ b/components/gwp_asan/crash_handler/crash_analyzer.cc
@@ -10,6 +10,7 @@ #include <string> #include "base/logging.h" +#include "base/metrics/histogram_macros.h" #include "base/process/process_metrics.h" #include "base/strings/string_number_conversions.h" #include "build/build_config.h" @@ -28,22 +29,23 @@ namespace internal { using GetMetadataReturnType = AllocatorState::GetMetadataReturnType; -using GwpAsanCrashAnalysisResult = CrashAnalyzer::GwpAsanCrashAnalysisResult; -GwpAsanCrashAnalysisResult CrashAnalyzer::GetExceptionInfo( +bool CrashAnalyzer::GetExceptionInfo( const crashpad::ProcessSnapshot& process_snapshot, gwp_asan::Crash* proto) { crashpad::VMAddress gpa_ptr = GetAllocatorAddress(process_snapshot); // If the annotation wasn't present, GWP-ASan wasn't enabled. if (!gpa_ptr) - return GwpAsanCrashAnalysisResult::kUnrelatedCrash; + return false; const crashpad::ExceptionSnapshot* exception = process_snapshot.Exception(); if (!exception) - return GwpAsanCrashAnalysisResult::kUnrelatedCrash; + return false; - if (!exception->Context()) - return GwpAsanCrashAnalysisResult::kErrorNullCpuContext; + if (!exception->Context()) { + ReportHistogram(GwpAsanCrashAnalysisResult::kErrorNullCpuContext); + return false; + } #if defined(ARCH_CPU_64_BITS) constexpr bool is_64_bit = true; @@ -53,11 +55,15 @@ // TODO(vtsyrklevich): Look at using crashpad's process_types to read the GPA // state bitness-independently. - if (exception->Context()->Is64Bit() != is_64_bit) - return GwpAsanCrashAnalysisResult::kErrorMismatchedBitness; + if (exception->Context()->Is64Bit() != is_64_bit) { + ReportHistogram(GwpAsanCrashAnalysisResult::kErrorMismatchedBitness); + return false; + } - if (!process_snapshot.Memory()) - return GwpAsanCrashAnalysisResult::kErrorNullProcessMemory; + if (!process_snapshot.Memory()) { + ReportHistogram(GwpAsanCrashAnalysisResult::kErrorNullProcessMemory); + return false; + } return AnalyzeCrashedAllocator(*process_snapshot.Memory(), *exception, gpa_ptr, proto); @@ -88,7 +94,7 @@ return 0; } -GwpAsanCrashAnalysisResult CrashAnalyzer::AnalyzeCrashedAllocator( +bool CrashAnalyzer::AnalyzeCrashedAllocator( const crashpad::ProcessMemory& memory, const crashpad::ExceptionSnapshot& exception, crashpad::VMAddress gpa_addr, @@ -96,12 +102,15 @@ AllocatorState unsafe_state; if (!memory.Read(gpa_addr, sizeof(unsafe_state), &unsafe_state)) { DLOG(ERROR) << "Failed to read AllocatorState from process."; - return GwpAsanCrashAnalysisResult::kErrorFailedToReadAllocator; + ReportHistogram(GwpAsanCrashAnalysisResult::kErrorFailedToReadAllocator); + return false; } if (!unsafe_state.IsValid()) { DLOG(ERROR) << "Allocator sanity check failed!"; - return GwpAsanCrashAnalysisResult::kErrorAllocatorFailedSanityCheck; + ReportHistogram( + GwpAsanCrashAnalysisResult::kErrorAllocatorFailedSanityCheck); + return false; } const AllocatorState& valid_state = unsafe_state; @@ -112,7 +121,7 @@ exception_addr = valid_state.free_invalid_address; if (!exception_addr || !valid_state.PointerIsMine(exception_addr)) - return GwpAsanCrashAnalysisResult::kUnrelatedCrash; + return false; // All errors that occur below happen for an exception known to be related to // GWP-ASan so we fill out the protobuf on error as well and include an error // string. @@ -133,7 +142,8 @@ sizeof(AllocatorState::SlotMetadata) * valid_state.num_metadata, metadata_arr.get())) { proto->set_internal_error("Failed to read metadata."); - return GwpAsanCrashAnalysisResult::kErrorFailedToReadSlotMetadata; + ReportHistogram(GwpAsanCrashAnalysisResult::kErrorFailedToReadSlotMetadata); + return true; } // Read the allocator's slot_to_metadata mapping. @@ -144,7 +154,9 @@ sizeof(AllocatorState::MetadataIdx) * valid_state.total_pages, slot_to_metadata.get())) { proto->set_internal_error("Failed to read slot_to_metadata."); - return GwpAsanCrashAnalysisResult::kErrorFailedToReadSlotMetadataMapping; + ReportHistogram( + GwpAsanCrashAnalysisResult::kErrorFailedToReadSlotMetadataMapping); + return true; } AllocatorState::MetadataIdx metadata_idx; @@ -152,14 +164,16 @@ auto ret = valid_state.GetMetadataForAddress( exception_addr, metadata_arr.get(), slot_to_metadata.get(), &metadata_idx, &error); - if (!error.empty()) - proto->set_internal_error(error); if (ret == GetMetadataReturnType::kErrorBadSlot) - return GwpAsanCrashAnalysisResult::kErrorBadSlot; + ReportHistogram(GwpAsanCrashAnalysisResult::kErrorBadSlot); if (ret == GetMetadataReturnType::kErrorBadMetadataIndex) - return GwpAsanCrashAnalysisResult::kErrorBadMetadataIndex; + ReportHistogram(GwpAsanCrashAnalysisResult::kErrorBadMetadataIndex); if (ret == GetMetadataReturnType::kErrorOutdatedMetadataIndex) - return GwpAsanCrashAnalysisResult::kErrorOutdatedMetadataIndex; + ReportHistogram(GwpAsanCrashAnalysisResult::kErrorOutdatedMetadataIndex); + if (!error.empty()) { + proto->set_internal_error(error); + return true; + } if (ret == GetMetadataReturnType::kGwpAsanCrash) { SlotMetadata& metadata = metadata_arr[metadata_idx]; @@ -180,7 +194,7 @@ metadata.dealloc, proto->mutable_deallocation()); } - return GwpAsanCrashAnalysisResult::kGwpAsanCrash; + return true; } void CrashAnalyzer::ReadAllocationInfo( @@ -218,5 +232,9 @@ output[i] = unpacked_stack_trace[i]; } +void CrashAnalyzer::ReportHistogram(GwpAsanCrashAnalysisResult result) { + UMA_HISTOGRAM_ENUMERATION(kCrashAnalysisHistogram, result); +} + } // namespace internal } // namespace gwp_asan
diff --git a/components/gwp_asan/crash_handler/crash_analyzer.h b/components/gwp_asan/crash_handler/crash_analyzer.h index f0251b8..dc2943b 100644 --- a/components/gwp_asan/crash_handler/crash_analyzer.h +++ b/components/gwp_asan/crash_handler/crash_analyzer.h
@@ -23,6 +23,19 @@ class CrashAnalyzer { public: + // Given a ProcessSnapshot, determine if the exception is related to GWP-ASan. + // If it is, returns true and fill out the |proto| parameter with details + // about the exception. Otherwise, returns false. + static bool GetExceptionInfo( + const crashpad::ProcessSnapshot& process_snapshot, + gwp_asan::Crash* proto); + + private: + using SlotMetadata = AllocatorState::SlotMetadata; + + static constexpr const char* kCrashAnalysisHistogram = + "GwpAsan.CrashAnalysisResult"; + // Captures the result of the GWP-ASan crash analyzer, whether the crash is // determined to be related or unrelated to GWP-ASan or if an error was // encountered analyzing the exception. @@ -60,17 +73,6 @@ kMaxValue = kErrorFailedToReadSlotMetadataMapping }; - // Given a ProcessSnapshot, determine if the exception is related to GWP-ASan. - // If it is, return kGwpAsanCrash and fill out the info parameter with - // details about the exception. Otherwise, return a value indicating that the - // crash is unrelated or that an error occured. - static GwpAsanCrashAnalysisResult GetExceptionInfo( - const crashpad::ProcessSnapshot& process_snapshot, - gwp_asan::Crash* proto); - - private: - using SlotMetadata = AllocatorState::SlotMetadata; - // Given an ExceptionSnapshot, return the address of where the exception // occurred (or null if it was not a data access exception.) static crashpad::VMAddress GetAccessAddress( @@ -82,10 +84,9 @@ const crashpad::ProcessSnapshot& process_snapshot); // This method implements the underlying logic for GetExceptionInfo(). It - // analyzes the GuardedPageAllocator of the crashing process, and if the - // exception occurred in the GWP-ASan region it fills out the protobuf - // parameter and returns kGwpAsanCrash. - static GwpAsanCrashAnalysisResult AnalyzeCrashedAllocator( + // analyzes the AllocatorState of the crashing process, if the exception is + // related to GWP-ASan it fills out the |proto| parameter and returns true. + static bool AnalyzeCrashedAllocator( const crashpad::ProcessMemory& memory, const crashpad::ExceptionSnapshot& exception, crashpad::VMAddress gpa_addr, @@ -98,6 +99,9 @@ const SlotMetadata::AllocationInfo& slot_info, gwp_asan::Crash_AllocationInfo* proto_info); + // Report a GWP-ASan crash analysis result via UMA. + static void ReportHistogram(GwpAsanCrashAnalysisResult analysis_result); + FRIEND_TEST_ALL_PREFIXES(CrashAnalyzerTest, InternalError); FRIEND_TEST_ALL_PREFIXES(CrashAnalyzerTest, StackTraceCollection); };
diff --git a/components/gwp_asan/crash_handler/crash_analyzer_unittest.cc b/components/gwp_asan/crash_handler/crash_analyzer_unittest.cc index 90b62d9..98da9cca 100644 --- a/components/gwp_asan/crash_handler/crash_analyzer_unittest.cc +++ b/components/gwp_asan/crash_handler/crash_analyzer_unittest.cc
@@ -6,9 +6,11 @@ #include "base/debug/stack_trace.h" #include "base/test/gtest_util.h" +#include "base/test/metrics/histogram_tester.h" #include "components/gwp_asan/client/guarded_page_allocator.h" #include "components/gwp_asan/common/allocator_state.h" #include "components/gwp_asan/crash_handler/crash.pb.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/crashpad/crashpad/snapshot/test/test_exception_snapshot.h" #include "third_party/crashpad/crashpad/test/process_type.h" @@ -32,10 +34,12 @@ crashpad::test::TestExceptionSnapshot exception_snapshot; gpa.state_.double_free_address = reinterpret_cast<uintptr_t>(ptr); + base::HistogramTester histogram_tester; gwp_asan::Crash proto; - auto result = CrashAnalyzer::AnalyzeCrashedAllocator( + CrashAnalyzer::AnalyzeCrashedAllocator( memory, exception_snapshot, reinterpret_cast<uintptr_t>(&gpa), &proto); - EXPECT_EQ(result, CrashAnalyzer::GwpAsanCrashAnalysisResult::kGwpAsanCrash); + + histogram_tester.ExpectTotalCount(CrashAnalyzer::kCrashAnalysisHistogram, 0); ASSERT_TRUE(proto.has_allocation()); ASSERT_TRUE(proto.has_deallocation()); @@ -92,11 +96,17 @@ // single entry slot/metadata entry. gpa.slot_to_metadata_idx_[0] = 5; + base::HistogramTester histogram_tester; gwp_asan::Crash proto; - auto result = CrashAnalyzer::AnalyzeCrashedAllocator( + CrashAnalyzer::AnalyzeCrashedAllocator( memory, exception_snapshot, reinterpret_cast<uintptr_t>(&gpa), &proto); - EXPECT_EQ(result, - CrashAnalyzer::GwpAsanCrashAnalysisResult::kErrorBadMetadataIndex); + + int result = static_cast<int>( + CrashAnalyzer::GwpAsanCrashAnalysisResult::kErrorBadMetadataIndex); + EXPECT_THAT( + histogram_tester.GetAllSamples(CrashAnalyzer::kCrashAnalysisHistogram), + testing::ElementsAre(base::Bucket(result, 1))); + EXPECT_TRUE(proto.has_internal_error()); ASSERT_TRUE(proto.has_missing_metadata()); EXPECT_TRUE(proto.missing_metadata());
diff --git a/components/gwp_asan/crash_handler/crash_handler.cc b/components/gwp_asan/crash_handler/crash_handler.cc index 0deb875d..61de779a 100644 --- a/components/gwp_asan/crash_handler/crash_handler.cc +++ b/components/gwp_asan/crash_handler/crash_handler.cc
@@ -10,7 +10,6 @@ #include "base/compiler_specific.h" #include "base/logging.h" -#include "base/metrics/histogram_macros.h" #include "components/gwp_asan/crash_handler/crash.pb.h" #include "components/gwp_asan/crash_handler/crash_analyzer.h" #include "third_party/crashpad/crashpad/minidump/minidump_user_extension_stream_data_source.h" @@ -20,8 +19,6 @@ namespace internal { namespace { -using GwpAsanCrashAnalysisResult = CrashAnalyzer::GwpAsanCrashAnalysisResult; - // Return a serialized protobuf using a wrapper interface that // crashpad::UserStreamDataSource expects us to return. class BufferExtensionStreamDataSource final @@ -79,10 +76,7 @@ std::unique_ptr<crashpad::MinidumpUserExtensionStreamDataSource> HandleException(const crashpad::ProcessSnapshot& snapshot) { gwp_asan::Crash proto; - auto result = CrashAnalyzer::GetExceptionInfo(snapshot, &proto); - if (result != GwpAsanCrashAnalysisResult::kUnrelatedCrash) - UMA_HISTOGRAM_ENUMERATION("GwpAsan.CrashAnalysisResult", result); - + CrashAnalyzer::GetExceptionInfo(snapshot, &proto); // The missing_metadata field is always set for all exceptions. if (!proto.has_missing_metadata()) return nullptr;
diff --git a/components/language/core/browser/language_prefs.cc b/components/language/core/browser/language_prefs.cc index 41ef9d0d..0c0bf016 100644 --- a/components/language/core/browser/language_prefs.cc +++ b/components/language/core/browser/language_prefs.cc
@@ -113,4 +113,13 @@ return fluents->GetList().size(); } +void ResetLanguagePrefs(PrefService* prefs) { + prefs->ClearPref(language::prefs::kAcceptLanguages); + prefs->ClearPref(language::prefs::kFluentLanguages); +#if defined(OS_CHROMEOS) + prefs->ClearPref(language::prefs::kPreferredLanguages); + prefs->ClearPref(language::prefs::kPreferredLanguagesSyncable); +#endif +} + } // namespace language
diff --git a/components/language/core/browser/language_prefs.h b/components/language/core/browser/language_prefs.h index fb32a8c..43015a5 100644 --- a/components/language/core/browser/language_prefs.h +++ b/components/language/core/browser/language_prefs.h
@@ -19,6 +19,8 @@ class PrefRegistrySyncable; } +class PrefService; + namespace language { extern const char kFallbackInputMethodLocale[]; @@ -54,6 +56,8 @@ DISALLOW_COPY_AND_ASSIGN(LanguagePrefs); }; +void ResetLanguagePrefs(PrefService* prefs); + } // namespace language #endif // COMPONENTS_LANGUAGE_CORE_BROWSER_LANGUAGE_PREFS_H_
diff --git a/components/metrics/persisted_logs.cc b/components/metrics/persisted_logs.cc index f23969ec..4e42081 100644 --- a/components/metrics/persisted_logs.cc +++ b/components/metrics/persisted_logs.cc
@@ -30,8 +30,7 @@ const char kLogDataKey[] = "data"; std::string EncodeToBase64(const std::string& to_convert) { - // CHECK to diagnose crbug.com/695433 - CHECK(to_convert.data()); + DCHECK(to_convert.data()); std::string base64_result; base::Base64Encode(to_convert, &base64_result); return base64_result; @@ -142,9 +141,7 @@ } void PersistedLogs::DiscardStagedLog() { - // CHECK, rather than DCHECK, to diagnose cause of crashes from the field, - // for crbug.com/695433. - CHECK(has_staged_log()); + DCHECK(has_staged_log()); DCHECK_LT(static_cast<size_t>(staged_log_index_), list_.size()); list_.erase(list_.begin() + staged_log_index_); staged_log_index_ = -1;
diff --git a/components/omnibox/browser/omnibox_field_trial.cc b/components/omnibox/browser/omnibox_field_trial.cc index 6191530..e1588a3 100644 --- a/components/omnibox/browser/omnibox_field_trial.cc +++ b/components/omnibox/browser/omnibox_field_trial.cc
@@ -549,11 +549,6 @@ return static_cast<EmphasizeTitlesCondition>(value); } -bool OmniboxFieldTrial::IsRichEntitySuggestionsEnabled() { - return base::FeatureList::IsEnabled(omnibox::kOmniboxRichEntitySuggestions) || - base::FeatureList::IsEnabled(omnibox::kOmniboxLocalEntitySuggestions); -} - bool OmniboxFieldTrial::IsReverseAnswersEnabled() { return base::FeatureList::IsEnabled(omnibox::kOmniboxReverseAnswers); }
diff --git a/components/omnibox/browser/omnibox_field_trial.h b/components/omnibox/browser/omnibox_field_trial.h index d8143e32..d63dac93 100644 --- a/components/omnibox/browser/omnibox_field_trial.h +++ b/components/omnibox/browser/omnibox_field_trial.h
@@ -380,9 +380,6 @@ // --------------------------------------------------------- // For UI experiments. -// Returns true if the rich entities flag is enabled. -bool IsRichEntitySuggestionsEnabled(); - // Returns true if the reverse answers flag is enabled. bool IsReverseAnswersEnabled();
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc index 5a053ff..d922a4f 100644 --- a/components/omnibox/common/omnibox_features.cc +++ b/components/omnibox/common/omnibox_features.cc
@@ -67,7 +67,13 @@ // Feature used to enable entity suggestion images and enhanced presentation // showing more context and descriptive text about the entity. const base::Feature kOmniboxRichEntitySuggestions{ - "OmniboxRichEntitySuggestions", base::FEATURE_DISABLED_BY_DEFAULT}; + "OmniboxRichEntitySuggestions", +#if defined(OS_IOS) || defined(OS_ANDROID) + base::FEATURE_DISABLED_BY_DEFAULT +#else + base::FEATURE_ENABLED_BY_DEFAULT +#endif +}; // Feature used to enable enhanced presentation showing larger images. // This is currently only used on Android.
diff --git a/components/previews/content/previews_user_data.cc b/components/previews/content/previews_user_data.cc index 29fd906..3e0951d8 100644 --- a/components/previews/content/previews_user_data.cc +++ b/components/previews/content/previews_user_data.cc
@@ -45,8 +45,14 @@ committed_previews_type_ = previews_type; } -void PreviewsUserData::SetRandomCoinFlipForNavigationForTesting(bool decision) { - random_coin_flip_for_navigation_ = decision; +bool PreviewsUserData::CoinFlipForNavigation() const { + if (params::ShouldOverrideNavigationCoinFlipToHoldback()) + return true; + + if (params::ShouldOverrideNavigationCoinFlipToAllowed()) + return false; + + return random_coin_flip_for_navigation_; } } // namespace previews
diff --git a/components/previews/content/previews_user_data.h b/components/previews/content/previews_user_data.h index 252232fd..a3c6e0e 100644 --- a/components/previews/content/previews_user_data.h +++ b/components/previews/content/previews_user_data.h
@@ -52,10 +52,8 @@ // A session unique ID related to this navigation. uint64_t page_id() const { return page_id_; } - // A random bool that is used in the coin flip holdback logic. - bool random_coin_flip_for_navigation() const { - return random_coin_flip_for_navigation_; - } + // The bool that is used in the coin flip holdback logic. + bool CoinFlipForNavigation() const; // The effective connection type value for the navigation. net::EffectiveConnectionType navigation_ect() const { @@ -117,9 +115,6 @@ // Sets the committed previews type for testing. Can be called multiple times. void SetCommittedPreviewsTypeForTesting(previews::PreviewsType previews_type); - // Sets |random_coin_flip_for_navigation_| for testing; - void SetRandomCoinFlipForNavigationForTesting(bool decision); - bool offline_preview_used() const { return offline_preview_used_; } // Whether an offline preview is being served. void set_offline_preview_used(bool offline_preview_used) {
diff --git a/components/previews/core/previews_experiments.cc b/components/previews/core/previews_experiments.cc index 7218cbd..aa8822c 100644 --- a/components/previews/core/previews_experiments.cc +++ b/components/previews/core/previews_experiments.cc
@@ -199,6 +199,10 @@ } bool LitePagePreviewsOverridePageHints() { + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kLitePageRedirectOverridesPageHints)) { + return true; + } return base::GetFieldTrialParamByFeatureAsBool( features::kLitePageServerPreviews, "override_pagehints", false); } @@ -393,11 +397,16 @@ 100); } -bool ShouldOverrideCoinFlipHoldbackResult() { +bool ShouldOverrideNavigationCoinFlipToHoldback() { return base::GetFieldTrialParamByFeatureAsBool( features::kCoinFlipHoldback, "force_coin_flip_always_holdback", false); } +bool ShouldOverrideNavigationCoinFlipToAllowed() { + return base::GetFieldTrialParamByFeatureAsBool( + features::kCoinFlipHoldback, "force_coin_flip_always_allow", false); +} + } // namespace params std::string GetStringNameForType(PreviewsType type) {
diff --git a/components/previews/core/previews_experiments.h b/components/previews/core/previews_experiments.h index 38b2146..645f3d7 100644 --- a/components/previews/core/previews_experiments.h +++ b/components/previews/core/previews_experiments.h
@@ -214,7 +214,10 @@ size_t OfflinePreviewsHelperMaxPrefSize(); // Forces the coin flip holdback, if enabled, to always come up "holdback". -bool ShouldOverrideCoinFlipHoldbackResult(); +bool ShouldOverrideNavigationCoinFlipToHoldback(); + +// Forces the coin flip holdback, if enabled, to always come up "allowed". +bool ShouldOverrideNavigationCoinFlipToAllowed(); } // namespace params
diff --git a/components/previews/core/previews_switches.cc b/components/previews/core/previews_switches.cc index 3906dd8..b0b0fa55 100644 --- a/components/previews/core/previews_switches.cc +++ b/components/previews/core/previews_switches.cc
@@ -51,5 +51,9 @@ // fresh data. const char kPurgeHintCacheStore[] = "purge_hint_cache_store"; +// Sets the trigger ordering of Lite Page Redirect to be higher than page hints. +const char kLitePageRedirectOverridesPageHints[] = + "litepage_redirect_overrides_page_hints"; + } // namespace switches } // namespace previews
diff --git a/components/previews/core/previews_switches.h b/components/previews/core/previews_switches.h index 9fa115f..ffb8bbe 100644 --- a/components/previews/core/previews_switches.h +++ b/components/previews/core/previews_switches.h
@@ -18,6 +18,7 @@ extern const char kOptimizationGuideServiceURL[]; extern const char kOptimizationGuideServiceAPIKey[]; extern const char kPurgeHintCacheStore[]; +extern const char kLitePageRedirectOverridesPageHints[]; } // namespace switches } // namespace previews
diff --git a/components/send_tab_to_self/BUILD.gn b/components/send_tab_to_self/BUILD.gn index 558220b..114f03c 100644 --- a/components/send_tab_to_self/BUILD.gn +++ b/components/send_tab_to_self/BUILD.gn
@@ -15,8 +15,12 @@ "send_tab_to_self_model.cc", "send_tab_to_self_model.h", "send_tab_to_self_model_observer.h", + "send_tab_to_self_model_type_controller.cc", + "send_tab_to_self_model_type_controller.h", "send_tab_to_self_sync_service.cc", "send_tab_to_self_sync_service.h", + "target_device_info.cc", + "target_device_info.h", ] deps = [ "//base", @@ -26,6 +30,7 @@ "//components/sync", "//components/sync/device_info", "//components/version_info", + "//google_apis", "//url", ] public_deps = [
diff --git a/components/send_tab_to_self/DEPS b/components/send_tab_to_self/DEPS index 615aeb3..21648ea 100644 --- a/components/send_tab_to_self/DEPS +++ b/components/send_tab_to_self/DEPS
@@ -4,4 +4,5 @@ "+components/sync", "+components/version_info", "+components/history", + "+google_apis", ]
diff --git a/components/send_tab_to_self/send_tab_to_self_bridge.cc b/components/send_tab_to_self/send_tab_to_self_bridge.cc index 7458e3a..9aeb3354 100644 --- a/components/send_tab_to_self/send_tab_to_self_bridge.cc +++ b/components/send_tab_to_self/send_tab_to_self_bridge.cc
@@ -17,6 +17,7 @@ #include "components/history/core/browser/history_service.h" #include "components/send_tab_to_self/features.h" #include "components/send_tab_to_self/proto/send_tab_to_self.pb.h" +#include "components/send_tab_to_self/target_device_info.h" #include "components/sync/base/get_session_name.h" #include "components/sync/device_info/device_info_tracker.h" #include "components/sync/model/entity_change.h" @@ -393,12 +394,12 @@ return change_processor()->IsTrackingMetadata(); } -std::map<std::string, std::string> -SendTabToSelfBridge::GetTargetDeviceNameToCacheGuidMap() { - if (ShouldUpdateTargetDeviceNameToCacheGuidMap()) { - SetTargetDeviceNameToCacheGuidMap(); +std::map<std::string, TargetDeviceInfo> +SendTabToSelfBridge::GetTargetDeviceNameToCacheInfoMap() { + if (ShouldUpdateTargetDeviceNameToCacheInfoMap()) { + SetTargetDeviceNameToCacheInfoMap(); } - return target_device_name_to_cache_guid_; + return target_device_name_to_cache_info_; } // static @@ -408,8 +409,8 @@ return std::move(bridge->store_); } -bool SendTabToSelfBridge::ShouldUpdateTargetDeviceNameToCacheGuidMapForTest() { - return ShouldUpdateTargetDeviceNameToCacheGuidMap(); +bool SendTabToSelfBridge::ShouldUpdateTargetDeviceNameToCacheInfoMapForTest() { + return ShouldUpdateTargetDeviceNameToCacheInfoMap(); } void SendTabToSelfBridge::NotifyRemoteSendTabToSelfEntryAdded( @@ -544,12 +545,12 @@ NotifyRemoteSendTabToSelfEntryDeleted(removed); } -bool SendTabToSelfBridge::ShouldUpdateTargetDeviceNameToCacheGuidMap() const { +bool SendTabToSelfBridge::ShouldUpdateTargetDeviceNameToCacheInfoMap() const { // The map should be updated if any of these is true: // * The map is empty. // * The number of total devices changed. // * The oldest non-expired entry in the map is now expired. - return target_device_name_to_cache_guid_.empty() || + return target_device_name_to_cache_info_.empty() || device_info_tracker_->GetAllDeviceInfo().size() != number_of_devices_ || clock_->Now() - change_processor()->GetEntityModificationTime( @@ -557,7 +558,7 @@ kDeviceExpiration; } -void SendTabToSelfBridge::SetTargetDeviceNameToCacheGuidMap() { +void SendTabToSelfBridge::SetTargetDeviceNameToCacheInfoMap() { std::vector<std::unique_ptr<syncer::DeviceInfo>> all_devices = device_info_tracker_->GetAllDeviceInfo(); number_of_devices_ = all_devices.size(); @@ -575,7 +576,7 @@ change_processor_ptr->GetEntityModificationTime(device2->guid()); }); - target_device_name_to_cache_guid_.clear(); + target_device_name_to_cache_info_.clear(); for (const auto& device : all_devices) { // If the current device is considered expired for our purposes, stop here // since the next devices in the vector are at least as expired than this @@ -600,8 +601,9 @@ // Only keep one device per device name. We only keep the first occurrence // which is the most recent. - target_device_name_to_cache_guid_.emplace(device->client_name(), - device->guid()); + TargetDeviceInfo device_info_for_ui(device->guid(), device->device_type()); + target_device_name_to_cache_info_.emplace(device->client_name(), + device_info_for_ui); oldest_device_cache_guid_ = device->guid(); } }
diff --git a/components/send_tab_to_self/send_tab_to_self_bridge.h b/components/send_tab_to_self/send_tab_to_self_bridge.h index 42086b644..228e7403 100644 --- a/components/send_tab_to_self/send_tab_to_self_bridge.h +++ b/components/send_tab_to_self/send_tab_to_self_bridge.h
@@ -36,6 +36,8 @@ namespace send_tab_to_self { +struct TargetDeviceInfo; + // Interface for a persistence layer for send tab to self. // All interface methods have to be called on main thread. class SendTabToSelfBridge : public syncer::ModelTypeSyncBridge, @@ -79,7 +81,7 @@ void DeleteEntry(const std::string& guid) override; void DismissEntry(const std::string& guid) override; bool IsReady() override; - std::map<std::string, std::string> GetTargetDeviceNameToCacheGuidMap() + std::map<std::string, TargetDeviceInfo> GetTargetDeviceNameToCacheInfoMap() override; // history::HistoryServiceObserver: @@ -89,7 +91,7 @@ // For testing only. static std::unique_ptr<syncer::ModelTypeStore> DestroyAndStealStoreForTest( std::unique_ptr<SendTabToSelfBridge> bridge); - bool ShouldUpdateTargetDeviceNameToCacheGuidMapForTest(); + bool ShouldUpdateTargetDeviceNameToCacheInfoMapForTest(); private: using SendTabToSelfEntries = @@ -129,10 +131,10 @@ void DoGarbageCollection(); // Returns whether the target device name to cache guid map should be updated. - bool ShouldUpdateTargetDeviceNameToCacheGuidMap() const; + bool ShouldUpdateTargetDeviceNameToCacheInfoMap() const; // Sets the target device name to cache guid map. - void SetTargetDeviceNameToCacheGuidMap(); + void SetTargetDeviceNameToCacheInfoMap(); // |entries_| is keyed by GUIDs. SendTabToSelfEntries entries_; @@ -155,8 +157,8 @@ // A pointer to the most recently used entry used for deduplication. const SendTabToSelfEntry* mru_entry_; - // A map of target devices names to their associated cache guid. - std::map<std::string, std::string> target_device_name_to_cache_guid_; + // A map of target devices names to their associated cache information. + std::map<std::string, TargetDeviceInfo> target_device_name_to_cache_info_; // The following two variables are used to determine whether we should update // the target device name to cache guid map.
diff --git a/components/send_tab_to_self/send_tab_to_self_bridge_unittest.cc b/components/send_tab_to_self/send_tab_to_self_bridge_unittest.cc index 2880b0b..ba2c1352 100644 --- a/components/send_tab_to_self/send_tab_to_self_bridge_unittest.cc +++ b/components/send_tab_to_self/send_tab_to_self_bridge_unittest.cc
@@ -17,6 +17,7 @@ #include "components/history/core/browser/history_service.h" #include "components/send_tab_to_self/features.h" #include "components/send_tab_to_self/proto/send_tab_to_self.pb.h" +#include "components/send_tab_to_self/target_device_info.h" #include "components/sync/device_info/device_info.h" #include "components/sync/device_info/device_info_tracker.h" #include "components/sync/model/entity_change.h" @@ -627,7 +628,7 @@ // Tests that only the most recent device's guid is returned when multiple // devices have the same name. TEST_F(SendTabToSelfBridgeTest, - GetTargetDeviceNameToCacheGuidMap_OneDevicePerName) { + GetTargetDeviceNameToCacheInfoMap_OneDevicePerName) { const std::string kRecentGuid = "guid1"; const std::string kOldGuid = "guid2"; const std::string kOlderGuid = "guid3"; @@ -662,14 +663,17 @@ ON_CALL(*processor(), GetEntityModificationTime(kOlderGuid)) .WillByDefault(Return(clock()->Now() - base::TimeDelta::FromDays(5))); - EXPECT_THAT(bridge()->GetTargetDeviceNameToCacheGuidMap(), - ElementsAre(Pair("device_name", kRecentGuid))); + TargetDeviceInfo device_info_for_ui(kRecentGuid, + sync_pb::SyncEnums_DeviceType_TYPE_LINUX); + + EXPECT_THAT(bridge()->GetTargetDeviceNameToCacheInfoMap(), + ElementsAre(Pair("device_name", device_info_for_ui))); } // Tests that only devices that have the send tab to self receiving feature // enabled are returned. TEST_F(SendTabToSelfBridgeTest, - GetTargetDeviceNameToCacheGuidMap_OnlyReceivingEnabled) { + GetTargetDeviceNameToCacheInfoMap_OnlyReceivingEnabled) { InitializeBridge(); syncer::DeviceInfo enabled_device( @@ -690,13 +694,16 @@ ON_CALL(*processor(), GetEntityModificationTime("disabled_guid")) .WillByDefault(Return(clock()->Now() - base::TimeDelta::FromDays(1))); - EXPECT_THAT(bridge()->GetTargetDeviceNameToCacheGuidMap(), - ElementsAre(Pair("enabled_device_name", "enabled_guid"))); + TargetDeviceInfo device_info_for_ui("enabled_guid", + sync_pb::SyncEnums_DeviceType_TYPE_LINUX); + + EXPECT_THAT(bridge()->GetTargetDeviceNameToCacheInfoMap(), + ElementsAre(Pair("enabled_device_name", device_info_for_ui))); } // Tests that only devices that are not expired are returned. TEST_F(SendTabToSelfBridgeTest, - GetTargetDeviceNameToCacheGuidMap_NoExpiredDevices) { + GetTargetDeviceNameToCacheInfoMap_NoExpiredDevices) { InitializeBridge(); syncer::DeviceInfo expired_device( @@ -717,13 +724,16 @@ ON_CALL(*processor(), GetEntityModificationTime("valid_guid")) .WillByDefault(Return(clock()->Now() - base::TimeDelta::FromDays(1))); - EXPECT_THAT(bridge()->GetTargetDeviceNameToCacheGuidMap(), - ElementsAre(Pair("valid_device_name", "valid_guid"))); + TargetDeviceInfo device_info_for_ui("valid_guid", + sync_pb::SyncEnums_DeviceType_TYPE_LINUX); + + EXPECT_THAT(bridge()->GetTargetDeviceNameToCacheInfoMap(), + ElementsAre(Pair("valid_device_name", device_info_for_ui))); } // Tests that the local device is not returned. TEST_F(SendTabToSelfBridgeTest, - GetTargetDeviceNameToCacheGuidMap_NoLocalDevice) { + GetTargetDeviceNameToCacheInfoMap_NoLocalDevice) { InitializeBridge(); syncer::DeviceInfo local_device( @@ -744,13 +754,16 @@ ON_CALL(*processor(), GetEntityModificationTime("other_guid")) .WillByDefault(Return(clock()->Now() - base::TimeDelta::FromDays(1))); - EXPECT_THAT(bridge()->GetTargetDeviceNameToCacheGuidMap(), - ElementsAre(Pair("other_device_name", "other_guid"))); + TargetDeviceInfo device_info_for_ui("other_guid", + sync_pb::SyncEnums_DeviceType_TYPE_LINUX); + + EXPECT_THAT(bridge()->GetTargetDeviceNameToCacheInfoMap(), + ElementsAre(Pair("other_device_name", device_info_for_ui))); } // Tests that the local device is not returned. TEST_F(SendTabToSelfBridgeTest, - GetTargetDeviceNameToCacheGuidMap_Updated_DeviceExpired) { + GetTargetDeviceNameToCacheInfoMap_Updated_DeviceExpired) { InitializeBridge(); // Set a device that is about to expire and a more recent device. @@ -772,22 +785,27 @@ ON_CALL(*processor(), GetEntityModificationTime("recent_guid")) .WillByDefault(Return(clock()->Now() - base::TimeDelta::FromDays(1))); + TargetDeviceInfo device_info_1("older_guid", + sync_pb::SyncEnums_DeviceType_TYPE_LINUX); + TargetDeviceInfo device_info_2("recent_guid", + sync_pb::SyncEnums_DeviceType_TYPE_LINUX); + // Set the map by calling it. Make sure it has the 2 devices. - EXPECT_THAT(bridge()->GetTargetDeviceNameToCacheGuidMap(), - UnorderedElementsAre(Pair("older_name", "older_guid"), - Pair("recent_name", "recent_guid"))); + EXPECT_THAT(bridge()->GetTargetDeviceNameToCacheInfoMap(), + UnorderedElementsAre(Pair("older_name", device_info_1), + Pair("recent_name", device_info_2))); // Advance the time so that the older device expires. clock()->Advance(base::TimeDelta::FromDays(5)); // Make sure only the recent device is in the map. - EXPECT_THAT(bridge()->GetTargetDeviceNameToCacheGuidMap(), - ElementsAre(Pair("recent_name", "recent_guid"))); + EXPECT_THAT(bridge()->GetTargetDeviceNameToCacheInfoMap(), + ElementsAre(Pair("recent_name", device_info_2))); } // Tests that the local device is not returned. TEST_F(SendTabToSelfBridgeTest, - GetTargetDeviceNameToCacheGuidMap_Updated_NewEntries) { + GetTargetDeviceNameToCacheInfoMap_Updated_NewEntries) { InitializeBridge(); // Set a valid device. @@ -800,8 +818,11 @@ .WillByDefault(Return(clock()->Now() - base::TimeDelta::FromDays(1))); // Set the map by calling it. Make sure it has the device. - EXPECT_THAT(bridge()->GetTargetDeviceNameToCacheGuidMap(), - ElementsAre(Pair("name", "guid"))); + TargetDeviceInfo device_info_1("guid", + sync_pb::SyncEnums_DeviceType_TYPE_LINUX); + + EXPECT_THAT(bridge()->GetTargetDeviceNameToCacheInfoMap(), + ElementsAre(Pair("name", device_info_1))); // Add a new device. syncer::DeviceInfo new_device("new_guid", "new_name", "72", "agent", @@ -813,9 +834,12 @@ .WillByDefault(Return(clock()->Now() - base::TimeDelta::FromDays(1))); // Make sure both devices are in the map. - EXPECT_THAT( - bridge()->GetTargetDeviceNameToCacheGuidMap(), - UnorderedElementsAre(Pair("name", "guid"), Pair("new_name", "new_guid"))); + TargetDeviceInfo device_info_2("new_guid", + sync_pb::SyncEnums_DeviceType_TYPE_LINUX); + + EXPECT_THAT(bridge()->GetTargetDeviceNameToCacheInfoMap(), + UnorderedElementsAre(Pair("name", device_info_1), + Pair("new_name", device_info_2))); } } // namespace
diff --git a/components/send_tab_to_self/send_tab_to_self_model.h b/components/send_tab_to_self/send_tab_to_self_model.h index 657af975..3330d425 100644 --- a/components/send_tab_to_self/send_tab_to_self_model.h +++ b/components/send_tab_to_self/send_tab_to_self_model.h
@@ -16,6 +16,8 @@ namespace send_tab_to_self { +struct TargetDeviceInfo; + // The send tab to self model contains a list of entries of shared urls. // This object should only be accessed from one thread, which is usually the // main thread. @@ -69,8 +71,8 @@ // Returns a map of the name of possible target devices for the send tab to // self feature to their cache guid. This is a thin layer on top of // DeviceInfoTracker. - virtual std::map<std::string, std::string> - GetTargetDeviceNameToCacheGuidMap() = 0; + virtual std::map<std::string, TargetDeviceInfo> + GetTargetDeviceNameToCacheInfoMap() = 0; protected: // The observers.
diff --git a/components/send_tab_to_self/send_tab_to_self_model_type_controller.cc b/components/send_tab_to_self/send_tab_to_self_model_type_controller.cc new file mode 100644 index 0000000..cad730e1 --- /dev/null +++ b/components/send_tab_to_self/send_tab_to_self_model_type_controller.cc
@@ -0,0 +1,42 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/send_tab_to_self/send_tab_to_self_model_type_controller.h" + +#include <utility> + +#include "base/feature_list.h" +#include "components/sync/driver/sync_auth_util.h" +#include "components/sync/driver/sync_service.h" +#include "google_apis/gaia/google_service_auth_error.h" + +namespace send_tab_to_self { + +SendTabToSelfModelTypeController::SendTabToSelfModelTypeController( + syncer::SyncService* sync_service, + std::unique_ptr<syncer::ModelTypeControllerDelegate> delegate) + : ModelTypeController(syncer::SEND_TAB_TO_SELF, std::move(delegate)), + sync_service_(sync_service) { + // TODO(crbug.com/906995): Remove this observing mechanism once all sync + // datatypes are stopped by ProfileSyncService, when sync is paused. + sync_service_->AddObserver(this); +} + +SendTabToSelfModelTypeController::~SendTabToSelfModelTypeController() { + sync_service_->RemoveObserver(this); +} + +bool SendTabToSelfModelTypeController::ReadyForStart() const { + DCHECK(CalledOnValidThread()); + return !(syncer::IsWebSignout(sync_service_->GetAuthError())); +} + +void SendTabToSelfModelTypeController::OnStateChanged( + syncer::SyncService* sync) { + DCHECK(CalledOnValidThread()); + // Most of these calls will be no-ops but SyncService handles that just fine. + sync_service_->ReadyForStartChanged(type()); +} + +} // namespace send_tab_to_self
diff --git a/components/send_tab_to_self/send_tab_to_self_model_type_controller.h b/components/send_tab_to_self/send_tab_to_self_model_type_controller.h new file mode 100644 index 0000000..fe9244b --- /dev/null +++ b/components/send_tab_to_self/send_tab_to_self_model_type_controller.h
@@ -0,0 +1,43 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_MODEL_TYPE_CONTROLLER_H_ +#define COMPONENTS_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_MODEL_TYPE_CONTROLLER_H_ + +#include "base/macros.h" +#include "components/sync/driver/model_type_controller.h" +#include "components/sync/driver/sync_service_observer.h" + +namespace syncer { +class SyncService; +} // namespace syncer + +namespace send_tab_to_self { + +// Controls syncing of SEND_TAB_TO_SELF. +class SendTabToSelfModelTypeController : public syncer::ModelTypeController, + public syncer::SyncServiceObserver { + public: + // The |delegate| and |sync_service| must not be null. Furthermore, + // |sync_service| must outlive this object. + SendTabToSelfModelTypeController( + syncer::SyncService* sync_service, + std::unique_ptr<syncer::ModelTypeControllerDelegate> delegate); + ~SendTabToSelfModelTypeController() override; + + // DataTypeController overrides. + bool ReadyForStart() const override; + + // syncer::SyncServiceObserver implementation. + void OnStateChanged(syncer::SyncService* sync) override; + + private: + syncer::SyncService* const sync_service_; + + DISALLOW_COPY_AND_ASSIGN(SendTabToSelfModelTypeController); +}; + +} // namespace send_tab_to_self + +#endif // COMPONENTS_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_MODEL_TYPE_CONTROLLER_H_
diff --git a/components/send_tab_to_self/target_device_info.cc b/components/send_tab_to_self/target_device_info.cc new file mode 100644 index 0000000..d735aec --- /dev/null +++ b/components/send_tab_to_self/target_device_info.cc
@@ -0,0 +1,19 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/send_tab_to_self/target_device_info.h" + +namespace send_tab_to_self { + +TargetDeviceInfo::TargetDeviceInfo( + const std::string& cache_guid, + const sync_pb::SyncEnums::DeviceType device_type) + : cache_guid(cache_guid), device_type(device_type) {} + +bool TargetDeviceInfo::operator==(const TargetDeviceInfo& rhs) const { + return this->cache_guid == rhs.cache_guid && + this->device_type == rhs.device_type; +} + +} // namespace send_tab_to_self
diff --git a/components/send_tab_to_self/target_device_info.h b/components/send_tab_to_self/target_device_info.h new file mode 100644 index 0000000..2a394ef --- /dev/null +++ b/components/send_tab_to_self/target_device_info.h
@@ -0,0 +1,31 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SEND_TAB_TO_SELF_TARGET_DEVICE_INFO_H_ +#define COMPONENTS_SEND_TAB_TO_SELF_TARGET_DEVICE_INFO_H_ + +#include <string> + +#include "components/sync/protocol/sync.pb.h" + +namespace send_tab_to_self { +// Device information for generating send tab to self UI. +struct TargetDeviceInfo { + public: + TargetDeviceInfo(const std::string& cache_guid, + const sync_pb::SyncEnums::DeviceType device_type); + TargetDeviceInfo(const TargetDeviceInfo& other) = default; + ~TargetDeviceInfo() = default; + + bool operator==(const TargetDeviceInfo& rhs) const; + + // Device guid. + std::string cache_guid; + // Device type. + sync_pb::SyncEnums::DeviceType device_type; +}; + +} // namespace send_tab_to_self + +#endif // COMPONENTS_SEND_TAB_TO_SELF_TARGET_DEVICE_INFO_H_
diff --git a/components/send_tab_to_self/test_send_tab_to_self_model.cc b/components/send_tab_to_self/test_send_tab_to_self_model.cc index e7e39a1..c9a4f7f 100644 --- a/components/send_tab_to_self/test_send_tab_to_self_model.cc +++ b/components/send_tab_to_self/test_send_tab_to_self_model.cc
@@ -33,8 +33,8 @@ return false; } -std::map<std::string, std::string> -TestSendTabToSelfModel::GetTargetDeviceNameToCacheGuidMap() { +std::map<std::string, TargetDeviceInfo> +TestSendTabToSelfModel::GetTargetDeviceNameToCacheInfoMap() { return {}; }
diff --git a/components/send_tab_to_self/test_send_tab_to_self_model.h b/components/send_tab_to_self/test_send_tab_to_self_model.h index 971988f53..32a1108 100644 --- a/components/send_tab_to_self/test_send_tab_to_self_model.h +++ b/components/send_tab_to_self/test_send_tab_to_self_model.h
@@ -11,6 +11,7 @@ #include "base/time/time.h" #include "components/send_tab_to_self/send_tab_to_self_model.h" +#include "components/send_tab_to_self/target_device_info.h" namespace send_tab_to_self { @@ -33,7 +34,7 @@ void DeleteEntry(const std::string& guid) override; void DismissEntry(const std::string& guid) override; bool IsReady() override; - std::map<std::string, std::string> GetTargetDeviceNameToCacheGuidMap() + std::map<std::string, TargetDeviceInfo> GetTargetDeviceNameToCacheInfoMap() override; };
diff --git a/components/ukm/ukm_recorder_impl.cc b/components/ukm/ukm_recorder_impl.cc index b6bb09bc..ae13c3fd 100644 --- a/components/ukm/ukm_recorder_impl.cc +++ b/components/ukm/ukm_recorder_impl.cc
@@ -52,7 +52,8 @@ bool IsWhitelistedSourceId(SourceId source_id) { return GetSourceIdType(source_id) == SourceIdType::NAVIGATION_ID || - GetSourceIdType(source_id) == SourceIdType::APP_ID; + GetSourceIdType(source_id) == SourceIdType::APP_ID || + GetSourceIdType(source_id) == SourceIdType::HISTORY_ID; } // Gets the maximum number of Sources we'll keep in memory before discarding any
diff --git a/components/viz/service/display_embedder/output_surface_provider_impl.cc b/components/viz/service/display_embedder/output_surface_provider_impl.cc index 264c5699..716cffb 100644 --- a/components/viz/service/display_embedder/output_surface_provider_impl.cc +++ b/components/viz/service/display_embedder/output_surface_provider_impl.cc
@@ -105,7 +105,7 @@ CreateSoftwareOutputDeviceForPlatform(surface_handle, display_client)); } else if (renderer_settings.use_skia_renderer || renderer_settings.use_skia_renderer_non_ddl) { -#if defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_MACOSX) // TODO(penghuang): Support SkiaRenderer for all platforms. NOTIMPLEMENTED(); return nullptr;
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn index 9955effa..b5390851 100644 --- a/content/browser/BUILD.gn +++ b/content/browser/BUILD.gn
@@ -1231,6 +1231,8 @@ "media/media_devices_permission_checker.h", "media/media_devices_util.cc", "media/media_devices_util.h", + "media/media_experiment_manager.cc", + "media/media_experiment_manager.h", "media/media_interface_proxy.cc", "media/media_interface_proxy.h", "media/media_internals.cc",
diff --git a/content/browser/accessibility/accessibility_tree_formatter_mac.mm b/content/browser/accessibility/accessibility_tree_formatter_mac.mm index 8a4fbbc..4f1c5cd1 100644 --- a/content/browser/accessibility/accessibility_tree_formatter_mac.mm +++ b/content/browser/accessibility/accessibility_tree_formatter_mac.mm
@@ -19,7 +19,7 @@ #include "content/browser/accessibility/browser_accessibility_manager.h" // This file uses the deprecated NSObject accessibility interface. -// TODO(crbug.com/921109): Migrate to the new NSAccessibility interface. +// TODO(crbug.com/948844): Migrate to the new NSAccessibility interface. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -118,7 +118,7 @@ // Include the description, title, or value - the first one not empty. id title = [obj title]; - id description = [obj description]; + id description = [obj descriptionForAccessibility]; id value = [obj value]; if (description && ![description isEqual:@""]) { [tokens addObject:description];
diff --git a/content/browser/accessibility/browser_accessibility_cocoa.h b/content/browser/accessibility/browser_accessibility_cocoa.h index d38fa48..743d136 100644 --- a/content/browser/accessibility/browser_accessibility_cocoa.h +++ b/content/browser/accessibility/browser_accessibility_cocoa.h
@@ -33,7 +33,7 @@ // object. The renderer converts webkit's accessibility tree into a // WebAccessibility tree and passes it to the browser process over IPC. // This class converts it into a format Cocoa can query. -@interface BrowserAccessibilityCocoa : NSObject { +@interface BrowserAccessibilityCocoa : NSAccessibilityElement { @private content::BrowserAccessibility* owner_; base::scoped_nsobject<NSMutableArray> children_; @@ -98,7 +98,7 @@ @property(nonatomic, readonly) NSArray* columns; @property(nonatomic, readonly) NSArray* columnHeaders; @property(nonatomic, readonly) NSValue* columnIndexRange; -@property(nonatomic, readonly) NSString* description; +@property(nonatomic, readonly) NSString* descriptionForAccessibility; @property(nonatomic, readonly) NSNumber* disclosing; @property(nonatomic, readonly) id disclosedByRow; @property(nonatomic, readonly) NSNumber* disclosureLevel;
diff --git a/content/browser/accessibility/browser_accessibility_cocoa.mm b/content/browser/accessibility/browser_accessibility_cocoa.mm index 21934df3..d622e30 100644 --- a/content/browser/accessibility/browser_accessibility_cocoa.mm +++ b/content/browser/accessibility/browser_accessibility_cocoa.mm
@@ -602,7 +602,7 @@ {NSAccessibilityColumnHeaderUIElementsAttribute, @"columnHeaders"}, {NSAccessibilityColumnIndexRangeAttribute, @"columnIndexRange"}, {NSAccessibilityContentsAttribute, @"contents"}, - {NSAccessibilityDescriptionAttribute, @"description"}, + {NSAccessibilityDescriptionAttribute, @"descriptionForAccessibility"}, {NSAccessibilityDisclosingAttribute, @"disclosing"}, {NSAccessibilityDisclosedByRowAttribute, @"disclosedByRow"}, {NSAccessibilityDisclosureLevelAttribute, @"disclosureLevel"}, @@ -937,7 +937,7 @@ return table; } -- (NSString*)description { +- (NSString*)descriptionForAccessibility { if (![self instanceActive]) return nil; @@ -2893,49 +2893,6 @@ return actions; } -// TODO(crbug.com/921109): Migrate from the NSObject accessibility interface to -// the NSAccessibility one, then remove this suppression. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - -// Returns a sub-array of values for the given attribute value, starting at -// index, with up to maxCount items. If the given index is out of bounds, -// or there are no values for the given attribute, it will return nil. -// This method is used for querying subsets of values, without having to -// return a large set of data, such as elements with a large number of -// children. -- (NSArray*)accessibilityArrayAttributeValues:(NSString*)attribute - index:(NSUInteger)index - maxCount:(NSUInteger)maxCount { - if (![self instanceActive]) - return nil; - - NSArray* fullArray = [self accessibilityAttributeValue:attribute]; - if (!fullArray) - return nil; - NSUInteger arrayCount = [fullArray count]; - if (index >= arrayCount) - return nil; - NSRange subRange; - if ((index + maxCount) > arrayCount) { - subRange = NSMakeRange(index, arrayCount - index); - } else { - subRange = NSMakeRange(index, maxCount); - } - return [fullArray subarrayWithRange:subRange]; -} - -// Returns the count of the specified accessibility array attribute. -- (NSUInteger)accessibilityArrayAttributeCount:(NSString*)attribute { - if (![self instanceActive]) - return 0; - - NSArray* fullArray = [self accessibilityAttributeValue:attribute]; - return [fullArray count]; -} - -#pragma clang diagnostic pop - // Returns the list of accessibility attributes that this object supports. - (NSArray*)accessibilityAttributeNames { if (![self instanceActive])
diff --git a/content/browser/accessibility/browser_accessibility_cocoa_browsertest.mm b/content/browser/accessibility/browser_accessibility_cocoa_browsertest.mm index c90657b..e7375e3 100644 --- a/content/browser/accessibility/browser_accessibility_cocoa_browsertest.mm +++ b/content/browser/accessibility/browser_accessibility_cocoa_browsertest.mm
@@ -21,11 +21,6 @@ #include "testing/gtest_mac.h" #include "url/gurl.h" -// This file uses the deprecated NSObject accessibility APIs: -// https://crbug.com/921109 -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - namespace content { namespace { @@ -103,24 +98,16 @@ // Test AXCellForColumnAndRow for four coordinates for (unsigned col = 0; col < 2; col++) { for (unsigned row = 0; row < 2; row++) { - id parameter = [[[NSMutableArray alloc] initWithCapacity:2] autorelease]; - [parameter addObject:[NSNumber numberWithInt:col]]; - [parameter addObject:[NSNumber numberWithInt:row]]; base::scoped_nsobject<BrowserAccessibilityCocoa> cell( - [[cocoa_table accessibilityAttributeValue:@"AXCellForColumnAndRow" - forParameter:parameter] retain]); + [[cocoa_table accessibilityCellForColumn:col row:row] retain]); // It should be a cell. - EXPECT_NSEQ(@"AXCell", [cell role]); + EXPECT_NSEQ(@"AXCell", [cell accessibilityRole]); // The column index and row index of the cell should match what we asked // for. - EXPECT_EQ(col, [[cell accessibilityAttributeValue:@"AXColumnIndexRange"] - rangeValue] - .location); - EXPECT_EQ(row, [[cell accessibilityAttributeValue:@"AXRowIndexRange"] - rangeValue] - .location); + EXPECT_NSEQ(NSMakeRange(col, 1), [cell accessibilityColumnIndexRange]); + EXPECT_NSEQ(NSMakeRange(row, 1), [cell accessibilityRowIndexRange]); } } } @@ -266,10 +253,8 @@ [ToBrowserAccessibilityCocoa(child) retain]); EXPECT_NSEQ(base::SysUTF8ToNSString(expected_descriptions[child_index]), - [child_obj description]); + [child_obj descriptionForAccessibility]); } } } // namespace content - -#pragma clang diagnostic pop
diff --git a/content/browser/accessibility/browser_accessibility_mac_unittest.mm b/content/browser/accessibility/browser_accessibility_mac_unittest.mm index 4ba51bf..d69793b6 100644 --- a/content/browser/accessibility/browser_accessibility_mac_unittest.mm +++ b/content/browser/accessibility/browser_accessibility_mac_unittest.mm
@@ -123,29 +123,18 @@ std::unique_ptr<BrowserAccessibilityManager> manager_; }; -// The next few tests all use the deprecated NSObject accessibility APIs: -// https://crbug.com/921109. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - // Standard hit test. TEST_F(BrowserAccessibilityMacTest, HitTestTest) { BrowserAccessibilityCocoa* firstChild = [accessibility_ accessibilityHitTest:NSMakePoint(50, 50)]; - EXPECT_NSEQ( - @"Child1", - [firstChild - accessibilityAttributeValue:NSAccessibilityDescriptionAttribute]); + EXPECT_NSEQ(@"Child1", firstChild.descriptionForAccessibility); } // Test doing a hit test on the edge of a child. TEST_F(BrowserAccessibilityMacTest, EdgeHitTest) { BrowserAccessibilityCocoa* firstChild = [accessibility_ accessibilityHitTest:NSZeroPoint]; - EXPECT_NSEQ( - @"Child1", - [firstChild - accessibilityAttributeValue:NSAccessibilityDescriptionAttribute]); + EXPECT_NSEQ(@"Child1", firstChild.descriptionForAccessibility); } // This will test a hit test with invalid coordinates. It is assumed that @@ -159,26 +148,14 @@ // Test to ensure querying standard attributes works. TEST_F(BrowserAccessibilityMacTest, BasicAttributeTest) { - NSString* helpText = - [accessibility_ accessibilityAttributeValue:NSAccessibilityHelpAttribute]; - EXPECT_NSEQ(@"HelpText", helpText); -} - -// Test querying for an invalid attribute to ensure it doesn't crash. -TEST_F(BrowserAccessibilityMacTest, InvalidAttributeTest) { - NSString* shouldBeNil = - [accessibility_ accessibilityAttributeValue:@"NSAnInvalidAttribute"]; - EXPECT_TRUE(shouldBeNil == nil); + EXPECT_NSEQ(@"HelpText", [accessibility_ accessibilityHelp]); } TEST_F(BrowserAccessibilityMacTest, RetainedDetachedObjectsReturnNil) { // Get the first child. BrowserAccessibilityCocoa* retainedFirstChild = [accessibility_ accessibilityHitTest:NSMakePoint(50, 50)]; - EXPECT_NSEQ( - @"Child1", - [retainedFirstChild - accessibilityAttributeValue:NSAccessibilityDescriptionAttribute]); + EXPECT_NSEQ(@"Child1", retainedFirstChild.descriptionForAccessibility); // Retain it. This simulates what the system might do with an // accessibility object. @@ -188,17 +165,12 @@ RebuildAccessibilityTree(); // Now any attributes we query should return nil. - EXPECT_EQ( - nil, - [retainedFirstChild - accessibilityAttributeValue:NSAccessibilityDescriptionAttribute]); + EXPECT_NSEQ(nil, retainedFirstChild.descriptionForAccessibility); // Don't leak memory in the test. [retainedFirstChild release]; } -#pragma clang diagnostic pop - TEST_F(BrowserAccessibilityMacTest, TestComputeTextEdit) { BrowserAccessibility* owner = [accessibility_ owner]; ASSERT_NE(nullptr, owner);
diff --git a/content/browser/frame_host/cross_process_frame_connector.cc b/content/browser/frame_host/cross_process_frame_connector.cc index 82510f19..814663a 100644 --- a/content/browser/frame_host/cross_process_frame_connector.cc +++ b/content/browser/frame_host/cross_process_frame_connector.cc
@@ -28,7 +28,6 @@ #include "content/public/common/use_zoom_for_dsf_policy.h" #include "gpu/ipc/common/gpu_messages.h" #include "third_party/blink/public/platform/web_input_event.h" -#include "ui/base/ui_base_features.h" #include "ui/base/ui_base_switches_util.h" #include "ui/gfx/geometry/dip_util.h" @@ -153,8 +152,7 @@ if (visibility_ != blink::mojom::FrameVisibility::kRenderedInViewport) OnVisibilityChanged(visibility_); FrameMsg_ViewChanged_Params params; - if (!features::IsMultiProcessMash()) - params.frame_sink_id = view_->GetFrameSinkId(); + params.frame_sink_id = view_->GetFrameSinkId(); frame_proxy_in_parent_renderer_->Send(new FrameMsg_ViewChanged( frame_proxy_in_parent_renderer_->GetRoutingID(), params)); }
diff --git a/content/browser/frame_host/navigation_request.cc b/content/browser/frame_host/navigation_request.cc index b8770db..ca78e34 100644 --- a/content/browser/frame_host/navigation_request.cc +++ b/content/browser/frame_host/navigation_request.cc
@@ -1319,6 +1319,8 @@ TRACE_EVENT_ASYNC_STEP_INTO0("navigation", "NavigationRequest", this, "OnResponseStarted"); state_ = RESPONSE_STARTED; + response_ = response; + ssl_info_ = response->head.ssl_info; // Check if the response should be sent to a renderer. response_should_be_rendered_ = @@ -1430,11 +1432,8 @@ common_params_.previews_state, navigation_handle_.get(), response->head.headers.get()); - // Store the response and the URLLoaderClient endpoints until checks have been - // processed. - response_ = response; + // Store the URLLoaderClient endpoints until checks have been processed. url_loader_client_endpoints_ = std::move(url_loader_client_endpoints); - ssl_info_ = response->head.ssl_info; subresource_loader_params_ = std::move(subresource_loader_params);
diff --git a/content/browser/media/media_experiment_manager.cc b/content/browser/media/media_experiment_manager.cc new file mode 100644 index 0000000..d3bcded --- /dev/null +++ b/content/browser/media/media_experiment_manager.cc
@@ -0,0 +1,60 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/media/media_experiment_manager.h" + +namespace content { + +MediaExperimentManager::MediaExperimentManager() = default; + +MediaExperimentManager::~MediaExperimentManager() = default; + +// Keeps track of all media players across all pages, and notifies them when +// they enter or leave an active experiment. +void MediaExperimentManager::PlayerCreated(const MediaPlayerId& player_id, + Client* client) { + // TODO: check that we don't know about it already. + player_ids_by_client_[client].insert(player_id); + players_[player_id].client = client; +} + +void MediaExperimentManager::PlayerDestroyed(const MediaPlayerId& player_id) { + ErasePlayersInternal({player_id}); +} + +void MediaExperimentManager::ClientDestroyed(Client* client) { + auto by_client = player_ids_by_client_.find(client); + // Make a copy, since ErasePlayers will modify the original. + ErasePlayersInternal(std::set<MediaPlayerId>(by_client->second)); +} + +void MediaExperimentManager::ErasePlayersInternal( + const std::set<MediaPlayerId>& player_ids) { + for (auto player_id : player_ids) { + auto player_iter = players_.find(player_id); + DCHECK(player_iter != players_.end()); + + // Erase this player from the client, and maybe the client if it's the + // only player owned by it. + auto by_client_iter = + player_ids_by_client_.find(player_iter->second.client); + DCHECK(by_client_iter != player_ids_by_client_.end()); + by_client_iter->second.erase(player_id); + if (by_client_iter->second.size() == 0) + player_ids_by_client_.erase(by_client_iter); + + players_.erase(player_iter); + } +} + +size_t MediaExperimentManager::GetPlayerCountForTesting() const { + return players_.size(); +} + +// static +MediaExperimentManager* MediaExperimentManager::Instance() { + return nullptr; +} + +} // namespace content
diff --git a/content/browser/media/media_experiment_manager.h b/content/browser/media/media_experiment_manager.h new file mode 100644 index 0000000..0d76268 --- /dev/null +++ b/content/browser/media/media_experiment_manager.h
@@ -0,0 +1,94 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CONTENT_BROWSER_MEDIA_MEDIA_EXPERIMENT_MANAGER_H_ +#define CONTENT_BROWSER_MEDIA_MEDIA_EXPERIMENT_MANAGER_H_ + +#include <map> +#include <set> +#include <vector> + +#include "base/macros.h" +#include "base/optional.h" +#include "content/common/content_export.h" +#include "content/public/browser/media_player_id.h" +#include "media/base/video_codecs.h" + +namespace content { + +// Keeps track of all media players across all pages, and notifies them when +// they enter or leave an active experiment. +class CONTENT_EXPORT MediaExperimentManager { + public: + // The Client interface allows the manager to send messages back to the + // player about experiment state. + class CONTENT_EXPORT Client { + public: + Client() = default; + virtual ~Client() = default; + + // Called when |player| becomes the focus of some experiment. + // TODO: Should include an ExperimentId, so the player knows what to do. + virtual void OnExperimentStarted(const MediaPlayerId& player) = 0; + + // Called when |player| stops being the focus of some experiment. Not + // called when the player is destroyed, or the player's client is destroyed, + // even though it does quit being the focus at that point too. + // TODO: Should include an ExperimentId, so the player knows what to do. + virtual void OnExperimentStopped(const MediaPlayerId& player) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(Client); + }; + + // State for one media player. + struct PlayerState { + Client* client = nullptr; + bool is_playing = false; + bool is_full_screen = false; + bool is_pip = false; + }; + + MediaExperimentManager(); + virtual ~MediaExperimentManager(); + + static MediaExperimentManager* Instance(); + + // Notifies us that |player| has been created, and is being managed by + // |client|. |client| must exist until all its players have been destroyed + // via calls to DestroyPlayer, or the client calls ClientDestroyed(). + virtual void PlayerCreated(const MediaPlayerId& player, Client* client); + + // Called when the given player has been destroyed. + virtual void PlayerDestroyed(const MediaPlayerId& player); + + // Notify us that |client| is being destroyed. All players that it created + // will be deleted. No further notifications will be sent to it. This is + // useful, for example, when a page is being destroyed so that we don't keep + // sending notifications while everything is being torn down. It's not an + // error if |client| has no active players. + virtual void ClientDestroyed(Client* client); + + // TODO(liberato): Allow clients to update the player's state. + + // Return the number of players total. + size_t GetPlayerCountForTesting() const; + + private: + // Erase all players in |player_ids|. Does not send any notifications, nor + // does it FindRunningExperiments. + void ErasePlayersInternal(const std::set<MediaPlayerId>& player_ids); + + // Set of all players that we know about. + std::map<MediaPlayerId, PlayerState> players_; + + // [client] == set of all player ids that it owns. + std::map<Client*, std::set<MediaPlayerId>> player_ids_by_client_; + + DISALLOW_COPY_AND_ASSIGN(MediaExperimentManager); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_MEDIA_MEDIA_EXPERIMENT_MANAGER_H_
diff --git a/content/browser/media/media_experiment_manager_unittest.cc b/content/browser/media/media_experiment_manager_unittest.cc new file mode 100644 index 0000000..c4d8744 --- /dev/null +++ b/content/browser/media/media_experiment_manager_unittest.cc
@@ -0,0 +1,84 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/media/media_experiment_manager.h" + +#include "base/bind_helpers.h" +#include "base/memory/ptr_util.h" +#include "base/time/time.h" +#include "base/version.h" +#include "media/base/media_controller.h" +#include "media/base/mock_filters.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace content { + +class MockExperimentClient : public MediaExperimentManager::Client { + public: + MOCK_METHOD1(OnExperimentStarted, void(const MediaPlayerId& player)); + MOCK_METHOD1(OnExperimentStopped, void(const MediaPlayerId& player)); +}; + +class MediaExperimentManagerTest : public testing::Test { + public: + MediaExperimentManagerTest() + : manager_(std::make_unique<MediaExperimentManager>()), + player_id_1_(nullptr, 1), + player_id_2_(nullptr, 2) {} + + protected: + std::unique_ptr<MediaExperimentManager> manager_; + // Unique player IDs. Note that we can't CreateMediaPlayerIdForTesting() + // since it doesn't return unique IDs. + MediaPlayerId player_id_1_; + MediaPlayerId player_id_2_; +}; + +TEST_F(MediaExperimentManagerTest, CreateAndDestroyPlayer) { + MockExperimentClient client; + + EXPECT_EQ(manager_->GetPlayerCountForTesting(), 0u); + + manager_->PlayerCreated(player_id_1_, &client); + EXPECT_EQ(manager_->GetPlayerCountForTesting(), 1u); + + manager_->PlayerCreated(player_id_2_, &client); + EXPECT_EQ(manager_->GetPlayerCountForTesting(), 2u); + + manager_->PlayerDestroyed(player_id_1_); + EXPECT_EQ(manager_->GetPlayerCountForTesting(), 1u); + + manager_->PlayerDestroyed(player_id_2_); + EXPECT_EQ(manager_->GetPlayerCountForTesting(), 0u); +} + +TEST_F(MediaExperimentManagerTest, CreatePlayerAndDestroyClient) { + // Create two players from one client, and make sure that destroying the + // client destroys both players. + MockExperimentClient client; + + manager_->PlayerCreated(player_id_1_, &client); + manager_->PlayerCreated(player_id_2_, &client); + EXPECT_EQ(manager_->GetPlayerCountForTesting(), 2u); + + manager_->ClientDestroyed(&client); + EXPECT_EQ(manager_->GetPlayerCountForTesting(), 0u); +} + +TEST_F(MediaExperimentManagerTest, CreateTwoClientsAndDestroyOneClient) { + // Create one player each in two clients, and verify that destroying one + // client destroys only one player. + MockExperimentClient client_1; + MockExperimentClient client_2; + + manager_->PlayerCreated(player_id_1_, &client_1); + manager_->PlayerCreated(player_id_2_, &client_2); + EXPECT_EQ(manager_->GetPlayerCountForTesting(), 2u); + + manager_->ClientDestroyed(&client_1); + EXPECT_EQ(manager_->GetPlayerCountForTesting(), 1u); +} + +} // namespace content
diff --git a/content/browser/renderer_host/browser_compositor_view_mac.mm b/content/browser/renderer_host/browser_compositor_view_mac.mm index 8739456..625566d 100644 --- a/content/browser/renderer_host/browser_compositor_view_mac.mm +++ b/content/browser/renderer_host/browser_compositor_view_mac.mm
@@ -166,7 +166,7 @@ const viz::LocalSurfaceIdAllocation& child_local_surface_id_allocation) { if (dfh_local_surface_id_allocator_.UpdateFromChild( child_local_surface_id_allocation)) { - dfh_display_.SetDeviceScaleFactor(new_device_scale_factor); + dfh_display_.set_device_scale_factor(new_device_scale_factor); dfh_size_dip_ = gfx::ConvertSizeToDIP(dfh_display_.device_scale_factor(), new_size_in_pixels); dfh_size_pixels_ = new_size_in_pixels; @@ -402,7 +402,7 @@ bool BrowserCompositorMac::ForceNewSurfaceForTesting() { display::Display new_display(dfh_display_); - new_display.SetDeviceScaleFactor(new_display.device_scale_factor() * 2.0f); + new_display.set_device_scale_factor(new_display.device_scale_factor() * 2.0f); return UpdateSurfaceFromNSView(dfh_size_dip_, new_display); }
diff --git a/content/browser/renderer_host/direct_manipulation_helper_win.cc b/content/browser/renderer_host/direct_manipulation_helper_win.cc index 4794b7ea..d986731 100644 --- a/content/browser/renderer_host/direct_manipulation_helper_win.cc +++ b/content/browser/renderer_host/direct_manipulation_helper_win.cc
@@ -10,6 +10,7 @@ #include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/threading/thread_task_runner_handle.h" +#include "base/win/win_util.h" #include "base/win/windows_version.h" #include "ui/base/ui_base_features.h" #include "ui/base/win/window_event_target.h" @@ -234,8 +235,8 @@ using GetPointerTypeFn = BOOL(WINAPI*)(UINT32, POINTER_INPUT_TYPE*); UINT32 pointer_id = GET_POINTERID_WPARAM(w_param); POINTER_INPUT_TYPE pointer_type; - static GetPointerTypeFn get_pointer_type = reinterpret_cast<GetPointerTypeFn>( - GetProcAddress(GetModuleHandleA("user32.dll"), "GetPointerType")); + static const auto get_pointer_type = reinterpret_cast<GetPointerTypeFn>( + base::win::GetUser32FunctionPointer("GetPointerType")); if (get_pointer_type && get_pointer_type(pointer_id, &pointer_type) && pointer_type == PT_TOUCHPAD) { HRESULT hr = viewport_->SetContact(pointer_id);
diff --git a/content/browser/renderer_host/render_widget_host_view_cocoa.h b/content/browser/renderer_host/render_widget_host_view_cocoa.h index 039c119..309379d 100644 --- a/content/browser/renderer_host/render_widget_host_view_cocoa.h +++ b/content/browser/renderer_host/render_widget_host_view_cocoa.h
@@ -53,10 +53,11 @@ // when it's removed from the view system. // TODO(ccameron): Hide this interface behind RenderWidgetHostNSViewBridge. @interface RenderWidgetHostViewCocoa - : ToolTipBaseView<CommandDispatcherTarget, - RenderWidgetHostNSViewClientOwner, - NSCandidateListTouchBarItemDelegate, - NSTextInputClient> { + : ToolTipBaseView <CommandDispatcherTarget, + RenderWidgetHostNSViewClientOwner, + NSCandidateListTouchBarItemDelegate, + NSTextInputClient, + NSAccessibility> { @private // The communications channel to the RenderWidgetHostViewMac. This pointer is // always valid. When the original client disconnects, |client_| is changed to
diff --git a/content/browser/renderer_host/render_widget_host_view_cocoa.mm b/content/browser/renderer_host/render_widget_host_view_cocoa.mm index 5f672831..bafebb3 100644 --- a/content/browser/renderer_host/render_widget_host_view_cocoa.mm +++ b/content/browser/renderer_host/render_widget_host_view_cocoa.mm
@@ -1412,53 +1412,6 @@ accessibilityParent_.reset(accessibilityParent, base::scoped_policy::RETAIN); } -// TODO(crbug.com/921109): Migrate from the NSObject accessibility API to the -// NSAccessibility API, then remove this suppression. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - -- (NSArray*)accessibilityArrayAttributeValues:(NSString*)attribute - index:(NSUInteger)index - maxCount:(NSUInteger)maxCount { - NSArray* fullArray = [self accessibilityAttributeValue:attribute]; - NSUInteger totalLength = [fullArray count]; - if (index >= totalLength) - return nil; - NSUInteger length = MIN(totalLength - index, maxCount); - return [fullArray subarrayWithRange:NSMakeRange(index, length)]; -} - -- (NSUInteger)accessibilityArrayAttributeCount:(NSString*)attribute { - NSArray* fullArray = [self accessibilityAttributeValue:attribute]; - return [fullArray count]; -} - -- (id)accessibilityAttributeValue:(NSString*)attribute { - id root_element = clientHelper_->GetRootBrowserAccessibilityElement(); - // Contents specifies document view of RenderWidgetHostViewCocoa provided by - // BrowserAccessibilityManager. Children includes all subviews in addition to - // contents. Currently we do not have subviews besides the document view. - if (([attribute isEqualToString:NSAccessibilityChildrenAttribute] || - [attribute isEqualToString:NSAccessibilityContentsAttribute]) && - root_element) { - return [NSArray arrayWithObjects:root_element, nil]; - } else if ([attribute isEqualToString:NSAccessibilityParentAttribute] && - accessibilityParent_) { - return NSAccessibilityUnignoredAncestor(accessibilityParent_); - } else if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) { - return NSAccessibilityScrollAreaRole; - } - id ret = [super accessibilityAttributeValue:attribute]; - return ret; -} - -- (NSArray*)accessibilityAttributeNames { - NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; - [ret addObject:NSAccessibilityContentsAttribute]; - [ret addObjectsFromArray:[super accessibilityAttributeNames]]; - return ret; -} - - (id)accessibilityHitTest:(NSPoint)point { id root_element = clientHelper_->GetRootBrowserAccessibilityElement(); if (!root_element) @@ -1471,26 +1424,32 @@ return obj; } -- (BOOL)accessibilityIsIgnored { - id root_element = clientHelper_->GetRootBrowserAccessibilityElement(); - return !root_element; -} - -- (NSUInteger)accessibilityGetIndexOf:(id)child { - id root_element = clientHelper_->GetRootBrowserAccessibilityElement(); - // Only child is root. - if (root_element == child) { - return 0; - } else { - return NSNotFound; - } -} - - (id)accessibilityFocusedUIElement { return clientHelper_->GetFocusedBrowserAccessibilityElement(); } -#pragma clang diagnostic pop +// NSAccessibility formal protocol: + +- (NSArray*)accessibilityChildren { + id root = clientHelper_->GetRootBrowserAccessibilityElement(); + if (root) + return @[ root ]; + return nil; +} + +- (NSArray*)accessibilityContents { + return self.accessibilityChildren; +} + +- (id)accessibilityParent { + if (accessibilityParent_) + return NSAccessibilityUnignoredAncestor(accessibilityParent_); + return [super accessibilityParent]; +} + +- (NSAccessibilityRole)accessibilityRole { + return NSAccessibilityScrollAreaRole; +} // Below is our NSTextInputClient implementation. //
diff --git a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm index 856c8a1c..71bcad9 100644 --- a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm +++ b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
@@ -2258,11 +2258,6 @@ EXPECT_EQ(point, gfx::PointF(105, 310)); } -// This test uses deprecated NSObject accessibility APIs - see -// https://crbug.com/921109. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - TEST_F(RenderWidgetHostViewMacTest, AccessibilityParentTest) { NSView* view = rwhv_mac_->cocoa_view(); @@ -2277,19 +2272,15 @@ [[window contentView] addSubview:accessibility_parent]; [parent_view addSubview:view]; - EXPECT_NSEQ([view accessibilityAttributeValue:NSAccessibilityParentAttribute], - parent_view); + EXPECT_NSEQ([view accessibilityParent], parent_view); rwhv_mac_->SetParentAccessibilityElement(accessibility_parent); - EXPECT_NSEQ([view accessibilityAttributeValue:NSAccessibilityParentAttribute], + EXPECT_NSEQ([view accessibilityParent], NSAccessibilityUnignoredAncestor(accessibility_parent)); - EXPECT_NE([view accessibilityAttributeValue:NSAccessibilityParentAttribute], - nil); + EXPECT_NSNE(nil, [view accessibilityParent]); rwhv_mac_->SetParentAccessibilityElement(nil); - EXPECT_NSEQ([view accessibilityAttributeValue:NSAccessibilityParentAttribute], - parent_view); + EXPECT_NSEQ([view accessibilityParent], parent_view); } -#pragma clang diagnostic pop } // namespace content
diff --git a/content/browser/webauth/webauth_browsertest.cc b/content/browser/webauth/webauth_browsertest.cc index b932edb4..98aeaaa 100644 --- a/content/browser/webauth/webauth_browsertest.cc +++ b/content/browser/webauth/webauth_browsertest.cc
@@ -15,6 +15,7 @@ #include "base/test/bind_test_util.h" #include "base/test/scoped_feature_list.h" #include "base/values.h" +#include "build/build_config.h" #include "components/network_session_configurator/common/network_switches.h" #include "content/browser/frame_host/render_frame_host_impl.h" #include "content/browser/webauth/authenticator_impl.h" @@ -47,6 +48,10 @@ #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/public/mojom/webauthn/authenticator.mojom.h" +#if defined(OS_WIN) +#include "device/fido/win/fake_webauthn_api.h" +#endif + namespace content { namespace { @@ -156,8 +161,9 @@ " timeout: 1000," " userVerification: '$1'," " $2}" - "}).catch(c => window.domAutomationController.send(" - " 'webauth: ' + c.toString()));"; + "}).then(c => window.domAutomationController.send('webauth: OK')," + " e => window.domAutomationController.send(" + " 'webauth: ' + e.toString()));"; // Default values for kGetPublicKeyTemplate. struct GetParameters { @@ -1008,6 +1014,84 @@ } } +#if defined(OS_WIN) +IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest, WinMakeCredential) { + NavigateToURL(shell(), GetHttpsURL("www.acme.com", "/title1.html")); + + device::ScopedFakeWinWebAuthnApi fake_api; + fake_api.set_available(true); + fake_api.set_is_uvpaa(true); + fake_api.set_hresult(S_OK); + fake_api.enable_fake_attestation(); + + base::Optional<std::string> result = ExecuteScriptAndExtractPrefixedString( + shell()->web_contents(), + BuildCreateCallWithParameters(CreateParameters()), "webauth: "); + ASSERT_TRUE(result); + ASSERT_EQ("webauth: OK", *result); +} + +IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest, + WinMakeCredentialReturnCodeFailure) { + NavigateToURL(shell(), GetHttpsURL("www.acme.com", "/title1.html")); + + device::ScopedFakeWinWebAuthnApi fake_api; + fake_api.set_available(true); + fake_api.set_is_uvpaa(true); + fake_api.set_hresult(E_FAIL); + fake_api.enable_fake_attestation(); + + // The authenticator response was good but the return code indicated failure. + base::Optional<std::string> result = ExecuteScriptAndExtractPrefixedString( + shell()->web_contents(), + BuildCreateCallWithParameters(CreateParameters()), "webauth: "); + ASSERT_TRUE(result); + ASSERT_EQ(kTimeoutErrorMessage, *result); +} + +IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest, WinGetAssertion) { + NavigateToURL(shell(), GetHttpsURL("www.acme.com", "/title1.html")); + + device::ScopedFakeWinWebAuthnApi fake_api; + fake_api.set_available(true); + fake_api.set_is_uvpaa(true); + fake_api.set_hresult(S_OK); + fake_api.enable_fake_assertion(); + + base::Optional<std::string> result = ExecuteScriptAndExtractPrefixedString( + shell()->web_contents(), BuildGetCallWithParameters(GetParameters()), + "webauth: "); + ASSERT_TRUE(result); + ASSERT_EQ("webauth: OK", *result); + + // The authenticator response was good but the return code indicated failure. + fake_api.set_hresult(E_FAIL); + result = ExecuteScriptAndExtractPrefixedString( + shell()->web_contents(), BuildGetCallWithParameters(GetParameters()), + "webauth: "); + ASSERT_TRUE(result); + ASSERT_EQ(kTimeoutErrorMessage, *result); +} + +IN_PROC_BROWSER_TEST_F(WebAuthJavascriptClientBrowserTest, + WinGetAssertionReturnCodeFailure) { + NavigateToURL(shell(), GetHttpsURL("www.acme.com", "/title1.html")); + + device::ScopedFakeWinWebAuthnApi fake_api; + fake_api.set_available(true); + fake_api.set_is_uvpaa(true); + fake_api.set_hresult(E_FAIL); + fake_api.enable_fake_assertion(); + + // The authenticator response was good but the return code indicated failure. + base::Optional<std::string> result = ExecuteScriptAndExtractPrefixedString( + shell()->web_contents(), BuildGetCallWithParameters(GetParameters()), + "webauth: "); + ASSERT_TRUE(result); + ASSERT_EQ(kTimeoutErrorMessage, *result); +} +#endif + // WebAuthBrowserCtapTest ---------------------------------------------- class WebAuthBrowserCtapTest : public WebAuthLocalClientBrowserTest {
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc index a6d70e8..82c60ef 100644 --- a/content/child/runtime_features.cc +++ b/content/child/runtime_features.cc
@@ -230,6 +230,10 @@ base::FeatureList::IsEnabled(blink::features::kBlinkGenPropertyTrees) || enable_experimental_web_platform_features); + WebRuntimeFeatures::EnableFeatureFromString( + "FastBorderRadius", + base::FeatureList::IsEnabled(blink::features::kFastBorderRadius)); + WebRuntimeFeatures::EnablePassiveDocumentEventListeners( base::FeatureList::IsEnabled(features::kPassiveDocumentEventListeners));
diff --git a/content/common/content_param_traits.cc b/content/common/content_param_traits.cc index 94dc538..1dc33c29 100644 --- a/content/common/content_param_traits.cc +++ b/content/common/content_param_traits.cc
@@ -22,7 +22,6 @@ #include "third_party/blink/public/common/messaging/transferable_message.h" #include "third_party/blink/public/mojom/messaging/transferable_message.mojom.h" #include "ui/accessibility/ax_mode.h" -#include "ui/base/ui_base_features.h" #include "ui/events/blink/web_input_event_traits.h" #include "ui/gfx/ipc/skia/gfx_skia_param_traits.h" @@ -230,8 +229,7 @@ void ParamTraits<content::FrameMsg_ViewChanged_Params>::Write( base::Pickle* m, const param_type& p) { - DCHECK(features::IsMultiProcessMash() || - (p.frame_sink_id.has_value() && p.frame_sink_id->is_valid())); + DCHECK(p.frame_sink_id.is_valid()); WriteParam(m, p.frame_sink_id); } @@ -241,8 +239,7 @@ param_type* r) { if (!ReadParam(m, iter, &(r->frame_sink_id))) return false; - if (!features::IsMultiProcessMash() && - (!r->frame_sink_id || !r->frame_sink_id->is_valid())) { + if (!r->frame_sink_id.is_valid()) { NOTREACHED(); return false; }
diff --git a/content/common/cursors/webcursor_unittest.cc b/content/common/cursors/webcursor_unittest.cc index 203f1c99..0c48c9fa 100644 --- a/content/common/cursors/webcursor_unittest.cc +++ b/content/common/cursors/webcursor_unittest.cc
@@ -126,7 +126,7 @@ WebCursor cursor(info); display::Display display; - display.SetDeviceScaleFactor(4.2f); + display.set_device_scale_factor(4.2f); cursor.SetDisplayInfo(display); #if defined(USE_OZONE) @@ -171,7 +171,7 @@ WebCursor cursor(info); display::Display display; - display.SetDeviceScaleFactor(scale); + display.set_device_scale_factor(scale); cursor.SetDisplayInfo(display); HCURSOR windows_cursor_handle = cursor.GetNativeCursor().platform();
diff --git a/content/common/frame_message_structs.h b/content/common/frame_message_structs.h index 981f17e..951dec2 100644 --- a/content/common/frame_message_structs.h +++ b/content/common/frame_message_structs.h
@@ -5,7 +5,6 @@ #ifndef CONTENT_COMMON_FRAME_MESSAGE_STRUCTS_H_ #define CONTENT_COMMON_FRAME_MESSAGE_STRUCTS_H_ -#include "base/optional.h" #include "components/viz/common/surfaces/frame_sink_id.h" #include "content/common/content_export.h" @@ -15,8 +14,7 @@ FrameMsg_ViewChanged_Params(); ~FrameMsg_ViewChanged_Params(); - // |frame_sink_id| is not used when mus is hosting viz. - base::Optional<viz::FrameSinkId> frame_sink_id; + viz::FrameSinkId frame_sink_id; }; } // namespace content
diff --git a/content/renderer/render_frame_proxy.cc b/content/renderer/render_frame_proxy.cc index 2dc2967..ead9441 100644 --- a/content/renderer/render_frame_proxy.cc +++ b/content/renderer/render_frame_proxy.cc
@@ -499,11 +499,11 @@ crashed_ = false; // The same ParentLocalSurfaceIdAllocator cannot provide LocalSurfaceIds for // two different frame sinks, so recreate it here. - if (frame_sink_id_ != *params.frame_sink_id) { + if (frame_sink_id_ != params.frame_sink_id) { parent_local_surface_id_allocator_ = std::make_unique<viz::ParentLocalSurfaceIdAllocator>(); } - frame_sink_id_ = *params.frame_sink_id; + frame_sink_id_ = params.frame_sink_id; // Resend the FrameRects and allocate a new viz::LocalSurfaceId when the view // changes.
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn index 2eb4d7e..1d9bd71 100644 --- a/content/test/BUILD.gn +++ b/content/test/BUILD.gn
@@ -1561,6 +1561,7 @@ "../browser/media/flinging_renderer_unittest.cc", "../browser/media/forwarding_audio_stream_factory_unittest.cc", "../browser/media/media_devices_permission_checker_unittest.cc", + "../browser/media/media_experiment_manager_unittest.cc", "../browser/media/media_internals_unittest.cc", "../browser/media/midi_host_unittest.cc", "../browser/media/session/media_session_controller_unittest.cc",
diff --git a/content/test/data/accessibility/event/menulist-collapse-expected-mac.txt b/content/test/data/accessibility/event/menulist-collapse-expected-mac.txt index 3259a45..4dc69e86 100644 --- a/content/test/data/accessibility/event/menulist-collapse-expected-mac.txt +++ b/content/test/data/accessibility/event/menulist-collapse-expected-mac.txt
@@ -1 +1,2 @@ -AXValueChanged on AXPopUpButton \ No newline at end of file +AXSelectedChildrenChanged on AXMenu +AXValueChanged on AXPopUpButton
diff --git a/content/test/data/accessibility/event/menulist-collapse-next-expected-mac.txt b/content/test/data/accessibility/event/menulist-collapse-next-expected-mac.txt index 6f03e73c..ac839fd 100644 --- a/content/test/data/accessibility/event/menulist-collapse-next-expected-mac.txt +++ b/content/test/data/accessibility/event/menulist-collapse-next-expected-mac.txt
@@ -1 +1,2 @@ +AXSelectedChildrenChanged on AXMenu AXValueChanged on AXPopUpButton AXValue="Orange"
diff --git a/content/test/data/accessibility/html/modal-dialog-closed-expected-mac.txt b/content/test/data/accessibility/html/modal-dialog-closed-expected-mac.txt index 823d48bd..bfcd352 100644 --- a/content/test/data/accessibility/html/modal-dialog-closed-expected-mac.txt +++ b/content/test/data/accessibility/html/modal-dialog-closed-expected-mac.txt
@@ -2,6 +2,6 @@ ++AXStaticText AXValue='Test that elements respawn in the accessibility tree after a modal dialog closes.' ++AXGroup AXSubrole=AXDocumentRegion ++++AXPopUpButton AXValue='This should be in the tree.' -++++++AXUnknown +++++++AXMenu ++++++++AXMenuItem AXValue='This should be in the tree.' ++AXColorWell AXValue='rgb 0.00000 0.00000 0.00000 1'
diff --git a/content/test/data/accessibility/html/select-expected-mac.txt b/content/test/data/accessibility/html/select-expected-mac.txt index e4c39c9..16eb3d54 100644 --- a/content/test/data/accessibility/html/select-expected-mac.txt +++ b/content/test/data/accessibility/html/select-expected-mac.txt
@@ -1,17 +1,17 @@ AXWebArea AXRoleDescription='HTML content' ++AXGroup AXRoleDescription='group' ++++AXPopUpButton AXRoleDescription='pop up button' AXValue='Placeholder option' -++++++AXUnknown AXRoleDescription='unknown' +++++++AXMenu AXRoleDescription='menu' ++++++++AXMenuItem AXRoleDescription='menu item' AXValue='Placeholder option' ++++++++AXMenuItem AXRoleDescription='menu item' AXValue='Option 1' ++++++++AXMenuItem AXRoleDescription='menu item' AXValue='Option 2' ++++AXPopUpButton AXRoleDescription='pop up button' AXValue='Option 2' -++++++AXUnknown AXRoleDescription='unknown' +++++++AXMenu AXRoleDescription='menu' ++++++++AXMenuItem AXRoleDescription='menu item' AXValue='Option 1' ++++++++AXMenuItem AXRoleDescription='menu item' AXValue='Option 2' ++++++++AXMenuItem AXRoleDescription='menu item' AXValue='Option 3' ++++AXPopUpButton AXRoleDescription='pop up button' AXValue='Option 1' -++++++AXUnknown AXRoleDescription='unknown' +++++++AXMenu AXRoleDescription='menu' ++++++++AXMenuItem AXRoleDescription='menu item' AXValue='Option 1' ++++++++AXMenuItem AXRoleDescription='menu item' AXValue='Option 2' ++++++++AXMenuItem AXRoleDescription='menu item' AXValue='Option 3'
diff --git a/device/bluetooth/bluetooth_adapter_winrt.cc b/device/bluetooth/bluetooth_adapter_winrt.cc index ba6a6bc8..21290bf 100644 --- a/device/bluetooth/bluetooth_adapter_winrt.cc +++ b/device/bluetooth/bluetooth_adapter_winrt.cc
@@ -27,6 +27,8 @@ #include "base/strings/string_piece.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" +#include "base/task/post_task.h" +#include "base/task/task_traits.h" #include "base/threading/thread_task_runner_handle.h" #include "base/win/core_winrt_util.h" #include "base/win/post_async_results.h" @@ -45,6 +47,9 @@ namespace uwp { using ABI::Windows::Devices::Bluetooth::BluetoothAdapter; } // namespace uwp +using ABI::Windows::Devices::Bluetooth::IBluetoothAdapter; +using ABI::Windows::Devices::Bluetooth::IBluetoothAdapterStatics; +using ABI::Windows::Devices::Bluetooth::IID_IBluetoothAdapterStatics; using ABI::Windows::Devices::Bluetooth::Advertisement:: BluetoothLEAdvertisementDataSection; using ABI::Windows::Devices::Bluetooth::Advertisement:: @@ -70,13 +75,13 @@ IBluetoothLEAdvertisementWatcher; using ABI::Windows::Devices::Bluetooth::Advertisement:: IBluetoothLEManufacturerData; -using ABI::Windows::Devices::Bluetooth::IBluetoothAdapter; -using ABI::Windows::Devices::Bluetooth::IBluetoothAdapterStatics; using ABI::Windows::Devices::Enumeration::DeviceInformation; using ABI::Windows::Devices::Enumeration::IDeviceInformation; using ABI::Windows::Devices::Enumeration::IDeviceInformationStatics; using ABI::Windows::Devices::Enumeration::IDeviceInformationUpdate; using ABI::Windows::Devices::Enumeration::IDeviceWatcher; +using ABI::Windows::Devices::Enumeration::IID_IDeviceInformationStatics; +using ABI::Windows::Devices::Radios::IID_IRadioStatics; using ABI::Windows::Devices::Radios::IRadio; using ABI::Windows::Devices::Radios::IRadioStatics; using ABI::Windows::Devices::Radios::Radio; @@ -88,10 +93,10 @@ using ABI::Windows::Devices::Radios::RadioState; using ABI::Windows::Devices::Radios::RadioState_Off; using ABI::Windows::Devices::Radios::RadioState_On; -using ABI::Windows::Foundation::Collections::IVector; -using ABI::Windows::Foundation::Collections::IVectorView; using ABI::Windows::Foundation::IAsyncOperation; using ABI::Windows::Foundation::IReference; +using ABI::Windows::Foundation::Collections::IVector; +using ABI::Windows::Foundation::Collections::IVectorView; using ABI::Windows::Storage::Streams::IBuffer; using ABI::Windows::Storage::Streams::IDataReader; using ABI::Windows::Storage::Streams::IDataReaderStatics; @@ -462,6 +467,13 @@ ExtractManufacturerData(advertisement.Get())); } +decltype(&::RoGetAgileReference) LoadGetAgileReference() { + auto funcptr = reinterpret_cast<decltype(&::RoGetAgileReference)>( + ::GetProcAddress(::GetModuleHandle(L"Ole32.dll"), "RoGetAgileReference")); + CHECK(funcptr); + return funcptr; +} + } // namespace std::string BluetoothAdapterWinrt::GetAddress() const { @@ -622,12 +634,181 @@ } } +BluetoothAdapterWinrt::StaticsInterfaces::StaticsInterfaces( + ComPtr<IAgileReference> adapter_statics_in, + ComPtr<IAgileReference> device_information_statics_in, + ComPtr<IAgileReference> radio_statics_in) + : adapter_statics(std::move(adapter_statics_in)), + device_information_statics(std::move(device_information_statics_in)), + radio_statics(std::move(radio_statics_in)) {} + +BluetoothAdapterWinrt::StaticsInterfaces::StaticsInterfaces( + const StaticsInterfaces& copy_from) = default; + +BluetoothAdapterWinrt::StaticsInterfaces::StaticsInterfaces() = default; + +BluetoothAdapterWinrt::StaticsInterfaces::~StaticsInterfaces() {} + void BluetoothAdapterWinrt::Init(InitCallback init_cb) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); - // We are wrapping |init_cb| in a ScopedClosureRunner to ensure it gets run no - // matter how the function exits. Furthermore, we set |is_initialized_| to - // true if adapter is still active when the callback gets run. + // Some of the initialization work requires loading libraries and should not + // be run on the browser main thread. + base::PostTaskWithTraitsAndReplyWithResult( + FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT}, + base::BindOnce(&BluetoothAdapterWinrt::PerformSlowInitTasks), + base::BindOnce(&BluetoothAdapterWinrt::CompleteInitAgile, + weak_ptr_factory_.GetWeakPtr(), std::move(init_cb))); +} + +void BluetoothAdapterWinrt::InitForTests( + InitCallback init_cb, + ComPtr<IBluetoothAdapterStatics> bluetooth_adapter_statics, + ComPtr<IDeviceInformationStatics> device_information_statics, + ComPtr<IRadioStatics> radio_statics) { + if (!ResolveCoreWinRT()) { + CompleteInit(std::move(init_cb), std::move(bluetooth_adapter_statics), + std::move(device_information_statics), + std::move(radio_statics)); + return; + } + + auto statics = PerformSlowInitTasks(); + + // This allows any passed in values (which would be fakes) to replace + // the return values of PerformSlowInitTasks(). + if (!bluetooth_adapter_statics) + statics.adapter_statics->Resolve(IID_IBluetoothAdapterStatics, + &bluetooth_adapter_statics); + if (!device_information_statics) + statics.device_information_statics->Resolve(IID_IDeviceInformationStatics, + &device_information_statics); + if (!radio_statics) + statics.radio_statics->Resolve(IID_IRadioStatics, &radio_statics); + + auto getAgileReferenceFunc = LoadGetAgileReference(); + + ComPtr<IAgileReference> radio_statics_agileref; + HRESULT hr = + getAgileReferenceFunc(AGILEREFERENCE_DEFAULT, IID_IRadioStatics, + radio_statics.Get(), &radio_statics_agileref); + DCHECK(SUCCEEDED(hr)); + ComPtr<IAgileReference> device_information_statics_agileref; + hr = getAgileReferenceFunc( + AGILEREFERENCE_DEFAULT, IID_IDeviceInformationStatics, + device_information_statics.Get(), &device_information_statics_agileref); + DCHECK(SUCCEEDED(hr)); + ComPtr<IAgileReference> adapter_statics_agileref; + hr = getAgileReferenceFunc( + AGILEREFERENCE_DEFAULT, IID_IBluetoothAdapterStatics, + bluetooth_adapter_statics.Get(), &adapter_statics_agileref); + DCHECK(SUCCEEDED(hr)); + CompleteInitAgile(std::move(init_cb), + StaticsInterfaces(adapter_statics_agileref, + device_information_statics_agileref, + radio_statics_agileref)); +} + +// static +BluetoothAdapterWinrt::StaticsInterfaces +BluetoothAdapterWinrt::PerformSlowInitTasks() { + if (!ResolveCoreWinRT()) + return BluetoothAdapterWinrt::StaticsInterfaces(); + + ComPtr<IBluetoothAdapterStatics> adapter_statics; + HRESULT hr = base::win::GetActivationFactory< + IBluetoothAdapterStatics, + RuntimeClass_Windows_Devices_Bluetooth_BluetoothAdapter>( + &adapter_statics); + if (FAILED(hr)) { + VLOG(2) << "GetBluetoothAdapterStaticsActivationFactory failed: " + << logging::SystemErrorCodeToString(hr); + return BluetoothAdapterWinrt::StaticsInterfaces(); + } + + ComPtr<IDeviceInformationStatics> device_information_statics; + hr = base::win::GetActivationFactory< + IDeviceInformationStatics, + RuntimeClass_Windows_Devices_Enumeration_DeviceInformation>( + &device_information_statics); + if (FAILED(hr)) { + VLOG(2) << "GetDeviceInformationStaticsActivationFactory failed: " + << logging::SystemErrorCodeToString(hr); + return BluetoothAdapterWinrt::StaticsInterfaces(); + } + + ComPtr<IRadioStatics> radio_statics; + hr = base::win::GetActivationFactory< + IRadioStatics, RuntimeClass_Windows_Devices_Radios_Radio>(&radio_statics); + if (FAILED(hr)) { + VLOG(2) << "GetRadioStaticsActivationFactory failed: " + << logging::SystemErrorCodeToString(hr); + return BluetoothAdapterWinrt::StaticsInterfaces(); + } + + auto getAgileReferenceFunc = LoadGetAgileReference(); + + ComPtr<IAgileReference> radio_statics_agileref; + hr = getAgileReferenceFunc(AGILEREFERENCE_DEFAULT, + ABI::Windows::Devices::Radios::IID_IRadioStatics, + radio_statics.Get(), &radio_statics_agileref); + if (FAILED(hr)) + return BluetoothAdapterWinrt::StaticsInterfaces(); + + ComPtr<IAgileReference> device_information_statics_agileref; + hr = getAgileReferenceFunc( + AGILEREFERENCE_DEFAULT, + ABI::Windows::Devices::Enumeration::IID_IDeviceInformationStatics, + device_information_statics.Get(), &device_information_statics_agileref); + if (FAILED(hr)) + return BluetoothAdapterWinrt::StaticsInterfaces(); + + ComPtr<IAgileReference> adapter_statics_agileref; + hr = getAgileReferenceFunc( + AGILEREFERENCE_DEFAULT, + ABI::Windows::Devices::Bluetooth::IID_IBluetoothAdapterStatics, + adapter_statics.Get(), &adapter_statics_agileref); + if (FAILED(hr)) + return BluetoothAdapterWinrt::StaticsInterfaces(); + + return BluetoothAdapterWinrt::StaticsInterfaces( + adapter_statics_agileref, device_information_statics_agileref, + radio_statics_agileref); +} + +void BluetoothAdapterWinrt::CompleteInitAgile(InitCallback init_cb, + StaticsInterfaces agile_statics) { + if (!agile_statics.adapter_statics || + !agile_statics.device_information_statics || + !agile_statics.radio_statics) { + CompleteInit(std::move(init_cb), nullptr, nullptr, nullptr); + return; + } + ComPtr<IBluetoothAdapterStatics> bluetooth_adapter_statics; + HRESULT hr = agile_statics.adapter_statics->Resolve( + IID_IBluetoothAdapterStatics, &bluetooth_adapter_statics); + DCHECK(SUCCEEDED(hr)); + ComPtr<IDeviceInformationStatics> device_information_statics; + hr = agile_statics.device_information_statics->Resolve( + IID_IDeviceInformationStatics, &device_information_statics); + DCHECK(SUCCEEDED(hr)); + ComPtr<IRadioStatics> radio_statics; + hr = agile_statics.radio_statics->Resolve(IID_IRadioStatics, &radio_statics); + DCHECK(SUCCEEDED(hr)); + + CompleteInit(std::move(init_cb), std::move(bluetooth_adapter_statics), + std::move(device_information_statics), std::move(radio_statics)); +} + +void BluetoothAdapterWinrt::CompleteInit( + InitCallback init_cb, + ComPtr<IBluetoothAdapterStatics> bluetooth_adapter_statics, + ComPtr<IDeviceInformationStatics> device_information_statics, + ComPtr<IRadioStatics> radio_statics) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + // We are wrapping |init_cb| in a ScopedClosureRunner to ensure it gets run + // no matter how the function exits. Furthermore, we set |is_initialized_| + // to true if adapter is still active when the callback gets run. base::ScopedClosureRunner on_init(base::BindOnce( [](base::WeakPtr<BluetoothAdapterWinrt> adapter, InitCallback init_cb) { if (adapter) @@ -636,19 +817,18 @@ }, weak_ptr_factory_.GetWeakPtr(), std::move(init_cb))); - if (!ResolveCoreWinRT()) - return; + bluetooth_adapter_statics_ = bluetooth_adapter_statics; + device_information_statics_ = device_information_statics; + radio_statics_ = radio_statics; - ComPtr<IBluetoothAdapterStatics> adapter_statics; - HRESULT hr = GetBluetoothAdapterStaticsActivationFactory(&adapter_statics); - if (FAILED(hr)) { - VLOG(2) << "GetBluetoothAdapterStaticsActivationFactory failed: " - << logging::SystemErrorCodeToString(hr); + if (!bluetooth_adapter_statics_ || !device_information_statics_ || + !radio_statics_) { return; } ComPtr<IAsyncOperation<uwp::BluetoothAdapter*>> get_default_adapter_op; - hr = adapter_statics->GetDefaultAsync(&get_default_adapter_op); + HRESULT hr = + bluetooth_adapter_statics_->GetDefaultAsync(&get_default_adapter_op); if (FAILED(hr)) { VLOG(2) << "BluetoothAdapter::GetDefaultAsync failed: " << logging::SystemErrorCodeToString(hr); @@ -667,8 +847,8 @@ } bool BluetoothAdapterWinrt::SetPoweredImpl(bool powered) { - // Due to an issue on WoW64 we might fail to obtain the radio in OnGetRadio(). - // This is why it can be null here. + // Due to an issue on WoW64 we might fail to obtain the radio in + // OnGetRadio(). This is why it can be null here. if (!radio_) return false; @@ -761,8 +941,8 @@ VLOG(2) << "Getting the Watcher Status failed: " << logging::SystemErrorCodeToString(hr); } else if (watcher_status == BluetoothLEAdvertisementWatcherStatus_Aborted) { - VLOG(2) - << "Starting Advertisement Watcher failed, it is in the Aborted state."; + VLOG(2) << "Starting Advertisement Watcher failed, it is in the Aborted " + "state."; RemoveAdvertisementReceivedHandler(); ui_task_runner_->PostTask( FROM_HERE, @@ -824,26 +1004,6 @@ NOTIMPLEMENTED(); } -HRESULT BluetoothAdapterWinrt::GetBluetoothAdapterStaticsActivationFactory( - IBluetoothAdapterStatics** statics) const { - return base::win::GetActivationFactory< - IBluetoothAdapterStatics, - RuntimeClass_Windows_Devices_Bluetooth_BluetoothAdapter>(statics); -} - -HRESULT BluetoothAdapterWinrt::GetDeviceInformationStaticsActivationFactory( - IDeviceInformationStatics** statics) const { - return base::win::GetActivationFactory< - IDeviceInformationStatics, - RuntimeClass_Windows_Devices_Enumeration_DeviceInformation>(statics); -} - -HRESULT BluetoothAdapterWinrt::GetRadioStaticsActivationFactory( - IRadioStatics** statics) const { - return base::win::GetActivationFactory< - IRadioStatics, RuntimeClass_Windows_Devices_Radios_Radio>(statics); -} - HRESULT BluetoothAdapterWinrt::ActivateBluetoothAdvertisementLEWatcherInstance( IBluetoothLEAdvertisementWatcher** instance) const { @@ -912,18 +1072,9 @@ return; } - ComPtr<IDeviceInformationStatics> device_information_statics; - hr = - GetDeviceInformationStaticsActivationFactory(&device_information_statics); - if (FAILED(hr)) { - VLOG(2) << "GetDeviceInformationStaticsActivationFactory failed: " - << logging::SystemErrorCodeToString(hr); - return; - } - ComPtr<IAsyncOperation<DeviceInformation*>> create_from_id_op; - hr = device_information_statics->CreateFromIdAsync(device_id, - &create_from_id_op); + hr = device_information_statics_->CreateFromIdAsync(device_id, + &create_from_id_op); if (FAILED(hr)) { VLOG(2) << "CreateFromIdAsync failed: " << logging::SystemErrorCodeToString(hr); @@ -958,16 +1109,8 @@ name_ = base::win::ScopedHString(name).GetAsUTF8(); - ComPtr<IRadioStatics> radio_statics; - hr = GetRadioStaticsActivationFactory(&radio_statics); - if (FAILED(hr)) { - VLOG(2) << "GetRadioStaticsActivationFactory failed: " - << logging::SystemErrorCodeToString(hr); - return; - } - ComPtr<IAsyncOperation<RadioAccessStatus>> request_access_op; - hr = radio_statics->RequestAccessAsync(&request_access_op); + hr = radio_statics_->RequestAccessAsync(&request_access_op); if (FAILED(hr)) { VLOG(2) << "RequestAccessAsync failed: " << logging::SystemErrorCodeToString(hr); @@ -1034,17 +1177,8 @@ // Attempt to create a DeviceWatcher for powered radios, so that querying // the power state is still possible. - ComPtr<IDeviceInformationStatics> device_information_statics; - HRESULT hr = - GetDeviceInformationStaticsActivationFactory(&device_information_statics); - if (FAILED(hr)) { - VLOG(2) << "GetDeviceInformationStaticsActivationFactory failed: " - << logging::SystemErrorCodeToString(hr); - return; - } - auto aqs_filter = base::win::ScopedHString::Create(kPoweredRadiosAqsFilter); - hr = device_information_statics->CreateWatcherAqsFilter( + HRESULT hr = device_information_statics_->CreateWatcherAqsFilter( aqs_filter.get(), &powered_radio_watcher_); if (FAILED(hr)) { VLOG(2) << "Creating Powered Radios Watcher failed: "
diff --git a/device/bluetooth/bluetooth_adapter_winrt.h b/device/bluetooth/bluetooth_adapter_winrt.h index 3daa169..f5f3f6f 100644 --- a/device/bluetooth/bluetooth_adapter_winrt.h +++ b/device/bluetooth/bluetooth_adapter_winrt.h
@@ -79,6 +79,17 @@ ~BluetoothAdapterWinrt() override; void Init(InitCallback init_cb); + // Allow tests to provide their own implementations of statics. + void InitForTests( + InitCallback init_cb, + Microsoft::WRL::ComPtr< + ABI::Windows::Devices::Bluetooth::IBluetoothAdapterStatics> + bluetooth_adapter_statics, + Microsoft::WRL::ComPtr< + ABI::Windows::Devices::Enumeration::IDeviceInformationStatics> + device_information_statics, + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Radios::IRadioStatics> + radio_statics); // BluetoothAdapter: bool SetPoweredImpl(bool powered) override; @@ -97,18 +108,7 @@ void RemovePairingDelegateInternal( BluetoothDevice::PairingDelegate* pairing_delegate) override; - // These are declared virtual so that they can be overridden by tests. - virtual HRESULT GetBluetoothAdapterStaticsActivationFactory( - ABI::Windows::Devices::Bluetooth::IBluetoothAdapterStatics** statics) - const; - - virtual HRESULT GetDeviceInformationStaticsActivationFactory( - ABI::Windows::Devices::Enumeration::IDeviceInformationStatics** statics) - const; - - virtual HRESULT GetRadioStaticsActivationFactory( - ABI::Windows::Devices::Radios::IRadioStatics** statics) const; - + // Declared virtual so that it can be overridden by tests. virtual HRESULT ActivateBluetoothAdvertisementLEWatcherInstance( ABI::Windows::Devices::Bluetooth::Advertisement:: IBluetoothLEAdvertisementWatcher** instance) const; @@ -120,6 +120,36 @@ uint64_t raw_address); private: + struct StaticsInterfaces { + StaticsInterfaces( + Microsoft::WRL::ComPtr<IAgileReference>, // IBluetoothStatics + Microsoft::WRL::ComPtr<IAgileReference>, // IDeviceInformationStatics + Microsoft::WRL::ComPtr<IAgileReference>); // IRadioStatics + StaticsInterfaces(); + StaticsInterfaces(const StaticsInterfaces&); + ~StaticsInterfaces(); + + Microsoft::WRL::ComPtr<IAgileReference> adapter_statics; + Microsoft::WRL::ComPtr<IAgileReference> device_information_statics; + Microsoft::WRL::ComPtr<IAgileReference> radio_statics; + }; + + static StaticsInterfaces PerformSlowInitTasks(); + + // CompleteInitAgile is a proxy to CompleteInit that resolves agile + // references. + void CompleteInitAgile(InitCallback init_cb, StaticsInterfaces statics); + void CompleteInit( + InitCallback init_cb, + Microsoft::WRL::ComPtr< + ABI::Windows::Devices::Bluetooth::IBluetoothAdapterStatics> + bluetooth_adapter_statics, + Microsoft::WRL::ComPtr< + ABI::Windows::Devices::Enumeration::IDeviceInformationStatics> + device_information_statics, + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Radios::IRadioStatics> + radio_statics); + void OnGetDefaultAdapter( base::ScopedClosureRunner on_init, Microsoft::WRL::ComPtr< @@ -203,6 +233,15 @@ IBluetoothLEAdvertisementWatcher> ble_advertisement_watcher_; + Microsoft::WRL::ComPtr< + ABI::Windows::Devices::Bluetooth::IBluetoothAdapterStatics> + bluetooth_adapter_statics_; + Microsoft::WRL::ComPtr< + ABI::Windows::Devices::Enumeration::IDeviceInformationStatics> + device_information_statics_; + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Radios::IRadioStatics> + radio_statics_; + THREAD_CHECKER(thread_checker_); // Note: This should remain the last member so it'll be destroyed and
diff --git a/device/bluetooth/test/bluetooth_test_win.cc b/device/bluetooth/test/bluetooth_test_win.cc index 93caee5..b7b56aca 100644 --- a/device/bluetooth/test/bluetooth_test_win.cc +++ b/device/bluetooth/test/bluetooth_test_win.cc
@@ -149,7 +149,14 @@ device_information_(std::move(device_information)), watcher_(Make<FakeBluetoothLEAdvertisementWatcherWinrt>()), bluetooth_test_winrt_(bluetooth_test_winrt) { - Init(std::move(init_cb)); + ComPtr<IBluetoothAdapterStatics> bluetooth_adapter_statics; + Make<FakeBluetoothAdapterStaticsWinrt>(adapter_).CopyTo( + (IBluetoothAdapterStatics**)&bluetooth_adapter_statics); + ComPtr<IDeviceInformationStatics> device_information_statics; + Make<FakeDeviceInformationStaticsWinrt>(device_information_) + .CopyTo((IDeviceInformationStatics**)&device_information_statics); + InitForTests(std::move(init_cb), std::move(bluetooth_adapter_statics), + std::move(device_information_statics), nullptr); } FakeBluetoothLEAdvertisementWatcherWinrt* watcher() { return watcher_.Get(); } @@ -157,15 +164,15 @@ protected: ~TestBluetoothAdapterWinrt() override = default; - HRESULT GetBluetoothAdapterStaticsActivationFactory( - IBluetoothAdapterStatics** statics) const override { + HRESULT GetTestBluetoothAdapterStaticsActivationFactory( + IBluetoothAdapterStatics** statics) const { auto adapter_statics = Make<FakeBluetoothAdapterStaticsWinrt>(adapter_); return adapter_statics.CopyTo(statics); } HRESULT - GetDeviceInformationStaticsActivationFactory( - IDeviceInformationStatics** statics) const override { + GetTestDeviceInformationStaticsActivationFactory( + IDeviceInformationStatics** statics) const { auto device_information_statics = Make<FakeDeviceInformationStaticsWinrt>(device_information_); return device_information_statics.CopyTo(statics);
diff --git a/device/fido/fido_test_data.h b/device/fido/fido_test_data.h index 4e07aaa0..8e397bf 100644 --- a/device/fido/fido_test_data.h +++ b/device/fido/fido_test_data.h
@@ -50,6 +50,11 @@ constexpr uint8_t kUserId[] = {0x10, 0x98, 0x23, 0x72, 0x35, 0x40, 0x98, 0x72}; +// "allowedCredential" encoded as uint8_t array +constexpr uint8_t kCredentialId[] = {0x61, 0x6C, 0x6C, 0x6F, 0x77, 0x65, + 0x64, 0x43, 0x72, 0x65, 0x64, 0x65, + 0x6E, 0x74, 0x69, 0x61, 0x6C}; + constexpr char kRelyingPartyId[] = "acme.com"; constexpr char kAppId[] = "acme.com/";
diff --git a/device/fido/win/fake_webauthn_api.cc b/device/fido/win/fake_webauthn_api.cc index 28ce3c87..23b4e4c2 100644 --- a/device/fido/win/fake_webauthn_api.cc +++ b/device/fido/win/fake_webauthn_api.cc
@@ -6,6 +6,8 @@ #include "base/logging.h" #include "base/optional.h" +#include "device/fido/fido_parsing_utils.h" +#include "device/fido/fido_test_data.h" namespace device { @@ -31,7 +33,8 @@ PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS options, PWEBAUTHN_CREDENTIAL_ATTESTATION* credential_attestation_ptr) { DCHECK(is_available_); - return E_NOTIMPL; + *credential_attestation_ptr = &attestation_; + return result_; } HRESULT FakeWinWebAuthnApi::AuthenticatorGetAssertion( @@ -41,7 +44,8 @@ PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS options, PWEBAUTHN_ASSERTION* assertion_ptr) { DCHECK(is_available_); - return E_NOTIMPL; + *assertion_ptr = &assertion_; + return result_; } HRESULT FakeWinWebAuthnApi::CancelCurrentOperation(GUID* cancellation_id) { @@ -60,6 +64,48 @@ void FakeWinWebAuthnApi::FreeAssertion(PWEBAUTHN_ASSERTION pWebAuthNAssertion) { } +WEBAUTHN_CREDENTIAL_ATTESTATION FakeWinWebAuthnApi::MakePackedAttestation() { + WEBAUTHN_CREDENTIAL_ATTESTATION attestation = {}; + attestation.dwVersion = WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_1; + attestation.cbAuthenticatorData = + sizeof(test_data::kCtap2MakeCredentialAuthData); + attestation.pbAuthenticatorData = reinterpret_cast<PBYTE>( + const_cast<uint8_t*>(device::test_data::kCtap2MakeCredentialAuthData)); + attestation.cbAttestation = + sizeof(test_data::kPackedAttestationStatementCBOR); + attestation.pbAttestation = reinterpret_cast<PBYTE>( + const_cast<uint8_t*>(device::test_data::kPackedAttestationStatementCBOR)); + attestation.cbAttestationObject = 0; + attestation.cbCredentialId = 0; + attestation.pwszFormatType = L"packed"; + attestation.dwAttestationDecodeType = 0; + return attestation; +} + +WEBAUTHN_ASSERTION FakeWinWebAuthnApi::MakeAssertion() { + WEBAUTHN_CREDENTIAL credential = {}; + // No constant macro available because 1 is the current version + credential.dwVersion = 1; + credential.cbId = sizeof(test_data::kCredentialId); + credential.pbId = + reinterpret_cast<PBYTE>(const_cast<uint8_t*>(test_data::kCredentialId)); + credential.pwszCredentialType = L"public-key"; + + WEBAUTHN_ASSERTION assertion = {}; + // No constant macro available because 1 is the current version + assertion.dwVersion = 1; + assertion.cbAuthenticatorData = sizeof(test_data::kTestSignAuthenticatorData); + assertion.pbAuthenticatorData = reinterpret_cast<PBYTE>( + const_cast<uint8_t*>(test_data::kTestSignAuthenticatorData)); + assertion.cbSignature = sizeof(test_data::kCtap2GetAssertionSignature); + assertion.pbSignature = reinterpret_cast<PBYTE>( + const_cast<uint8_t*>(test_data::kCtap2GetAssertionSignature)); + assertion.Credential = credential; + assertion.pbUserId = NULL; + assertion.cbUserId = 0; + return assertion; +} + ScopedFakeWinWebAuthnApi::ScopedFakeWinWebAuthnApi() : FakeWinWebAuthnApi() { WinWebAuthnApi::SetDefaultForTesting(this); }
diff --git a/device/fido/win/fake_webauthn_api.h b/device/fido/win/fake_webauthn_api.h index 0cc921f5..064e7f8 100644 --- a/device/fido/win/fake_webauthn_api.h +++ b/device/fido/win/fake_webauthn_api.h
@@ -20,6 +20,13 @@ // Inject the return value for WinWebAuthnApi::IsAvailable(). void set_available(bool available) { is_available_ = available; } + + void enable_fake_attestation() { attestation_ = MakePackedAttestation(); } + + void enable_fake_assertion() { assertion_ = MakeAssertion(); } + + void set_hresult(HRESULT result) { result_ = result; } + // Inject the return value for // WinWebAuthnApi::IsUserverifyingPlatformAuthenticatorAvailable(). void set_is_uvpaa(bool is_uvpaa) { is_uvpaa_ = is_uvpaa; } @@ -48,8 +55,14 @@ void FreeAssertion(PWEBAUTHN_ASSERTION pWebAuthNAssertion) override; private: + WEBAUTHN_CREDENTIAL_ATTESTATION MakePackedAttestation(); + WEBAUTHN_ASSERTION MakeAssertion(); + bool is_available_ = true; bool is_uvpaa_ = false; + WEBAUTHN_CREDENTIAL_ATTESTATION attestation_; + WEBAUTHN_ASSERTION assertion_; + HRESULT result_ = S_OK; }; // ScopedFakeWinWebAuthnApi overrides the value returned
diff --git a/device/gamepad/gamepad_device_linux.cc b/device/gamepad/gamepad_device_linux.cc index 9c60275..7803e106 100644 --- a/device/gamepad/gamepad_device_linux.cc +++ b/device/gamepad/gamepad_device_linux.cc
@@ -105,9 +105,10 @@ has_special_key->resize(kSpecialKeysLen, false); for (size_t special_index = 0; special_index < kSpecialKeysLen; ++special_index) { - (*has_special_key)[special_index] = - test_bit(kSpecialKeys[special_index], keybit); - ++found_special_keys; + if (test_bit(kSpecialKeys[special_index], keybit)) { + (*has_special_key)[special_index] = true; + ++found_special_keys; + } } return found_special_keys; @@ -207,6 +208,11 @@ if (dualshock4_ || hid_haptics_) return true; + // The Xbox Adaptive Controller reports force feedback capability, but the + // device itself does not have any vibration actuators. + if (gamepad_id_ == GamepadId::kMicrosoftProduct0b0a) + return false; + return supports_force_feedback_ && evdev_fd_.is_valid(); } @@ -420,6 +426,7 @@ product_id_ = product_id_int; version_number_ = version_number_int; name_ = name_string; + gamepad_id_ = GamepadIdList::Get().GetGamepadId(vendor_id_, product_id_); return true; } @@ -432,6 +439,7 @@ product_id_ = 0; version_number_ = 0; name_.clear(); + gamepad_id_ = GamepadId::kUnknownGamepad; // Button indices must be recomputed once the joydev node is closed. button_indices_used_.clear();
diff --git a/device/gamepad/gamepad_device_linux.h b/device/gamepad/gamepad_device_linux.h index 3254158..17b1209 100644 --- a/device/gamepad/gamepad_device_linux.h +++ b/device/gamepad/gamepad_device_linux.h
@@ -12,6 +12,7 @@ #include "base/files/scoped_file.h" #include "device/gamepad/abstract_haptic_gamepad.h" #include "device/gamepad/dualshock4_controller_linux.h" +#include "device/gamepad/gamepad_id_list.h" #include "device/gamepad/gamepad_standard_mappings.h" #include "device/gamepad/hid_haptic_gamepad_linux.h" #include "device/gamepad/udev_gamepad_linux.h" @@ -137,6 +138,9 @@ // indicating whether the button index is already mapped. std::vector<bool> button_indices_used_; + // An identifier for the gamepad device model. + GamepadId gamepad_id_ = GamepadId::kUnknownGamepad; + // The vendor ID of the device. uint16_t vendor_id_;
diff --git a/device/gamepad/gamepad_id_list.cc b/device/gamepad/gamepad_id_list.cc index 3c52e88..bf0ea08 100644 --- a/device/gamepad/gamepad_id_list.cc +++ b/device/gamepad/gamepad_id_list.cc
@@ -74,14 +74,15 @@ {0x045e, 0x02a1, kXInputTypeXbox360}, {0x045e, 0x02d1, kXInputTypeXboxOne}, {0x045e, 0x02dd, kXInputTypeXboxOne}, - {0x045e, 0x02e0, kXInputTypeXboxOne}, + {0x045e, 0x02e0, kXInputTypeNone}, {0x045e, 0x02e3, kXInputTypeXboxOne}, {0x045e, 0x02e6, kXInputTypeXbox360}, {0x045e, 0x02ea, kXInputTypeXboxOne}, - {0x045e, 0x02fd, kXInputTypeXboxOne}, + {0x045e, 0x02fd, kXInputTypeNone}, {0x045e, 0x02ff, kXInputTypeXboxOne}, {0x045e, 0x0719, kXInputTypeXbox360}, - {0x045e, 0x0b0a, kXInputTypeXbox360}, + {0x045e, 0x0b0a, kXInputTypeXboxOne}, + {0x045e, 0x0b0c, kXInputTypeNone}, // Logitech, Inc. {0x046d, 0xc208, kXInputTypeNone}, {0x046d, 0xc209, kXInputTypeNone},
diff --git a/device/gamepad/gamepad_id_list.h b/device/gamepad/gamepad_id_list.h index 5e4213a..6f249dc 100644 --- a/device/gamepad/gamepad_id_list.h +++ b/device/gamepad/gamepad_id_list.h
@@ -59,6 +59,8 @@ kMicrosoftProduct02ea = 0x045e02ea, kMicrosoftProduct02fd = 0x045e02fd, kMicrosoftProduct0719 = 0x045e0719, + kMicrosoftProduct0b0a = 0x045e0b0a, + kMicrosoftProduct0b0c = 0x045e0b0c, kNintendoProduct2006 = 0x057e2006, kNintendoProduct2007 = 0x057e2007, kNintendoProduct2009 = 0x057e2009,
diff --git a/device/gamepad/gamepad_standard_mappings_linux.cc b/device/gamepad/gamepad_standard_mappings_linux.cc index b91c23d..ba30570 100644 --- a/device/gamepad/gamepad_standard_mappings_linux.cc +++ b/device/gamepad/gamepad_standard_mappings_linux.cc
@@ -714,6 +714,8 @@ {GamepadId::kMicrosoftProduct02fd, MapperXboxOneS2016Firmware}, // Xbox 360 Wireless {GamepadId::kMicrosoftProduct0719, MapperXInputStyleGamepad}, + // Xbox Adaptive Controller + {GamepadId::kMicrosoftProduct0b0a, MapperXInputStyleGamepad}, // Logitech F310 D-mode {GamepadId::kLogitechProductc216, MapperLogitechDInput}, // Logitech F510 D-mode
diff --git a/device/gamepad/gamepad_standard_mappings_mac.mm b/device/gamepad/gamepad_standard_mappings_mac.mm index 46fa9be9..144f54e 100644 --- a/device/gamepad/gamepad_standard_mappings_mac.mm +++ b/device/gamepad/gamepad_standard_mappings_mac.mm
@@ -528,6 +528,30 @@ mapped->axes_length = AXIS_INDEX_COUNT; } +void MapperXboxAdaptiveControllerBluetooth(const Gamepad& input, + Gamepad* mapped) { + *mapped = input; + + mapped->buttons[BUTTON_INDEX_PRIMARY] = input.buttons[0]; + mapped->buttons[BUTTON_INDEX_SECONDARY] = input.buttons[1]; + mapped->buttons[BUTTON_INDEX_TERTIARY] = input.buttons[3]; + mapped->buttons[BUTTON_INDEX_QUATERNARY] = input.buttons[4]; + mapped->buttons[BUTTON_INDEX_LEFT_SHOULDER] = input.buttons[6]; + mapped->buttons[BUTTON_INDEX_RIGHT_SHOULDER] = input.buttons[7]; + mapped->buttons[BUTTON_INDEX_LEFT_TRIGGER] = AxisToButton(input.axes[10]); + mapped->buttons[BUTTON_INDEX_RIGHT_TRIGGER] = AxisToButton(input.axes[11]); + mapped->buttons[BUTTON_INDEX_BACK_SELECT] = input.buttons[31]; + mapped->buttons[BUTTON_INDEX_START] = input.buttons[11]; + mapped->buttons[BUTTON_INDEX_LEFT_THUMBSTICK] = input.buttons[13]; + mapped->buttons[BUTTON_INDEX_RIGHT_THUMBSTICK] = input.buttons[14]; + DpadFromAxis(mapped, input.axes[9]); + mapped->buttons[BUTTON_INDEX_META] = input.buttons[30]; + mapped->axes[AXIS_INDEX_RIGHT_STICK_Y] = input.axes[5]; + + mapped->buttons_length = BUTTON_INDEX_COUNT; + mapped->axes_length = AXIS_INDEX_COUNT; +} + constexpr struct MappingData { GamepadId gamepad_id; GamepadStandardMappingFunction function; @@ -552,6 +576,10 @@ {GamepadId::kMicrosoftProduct02fd, MapperXboxOneS2016Firmware}, // Xbox 360 Wireless {GamepadId::kMicrosoftProduct0719, MapperXbox360Gamepad}, + // Xbox Adaptive Controller (USB) + {GamepadId::kMicrosoftProduct0b0a, MapperXbox360Gamepad}, + // Xbox Adaptive Controller (Bluetooth) + {GamepadId::kMicrosoftProduct0b0c, MapperXboxAdaptiveControllerBluetooth}, // Logitech F310, D mode {GamepadId::kLogitechProductc216, MapperDirectInputStyle}, // Logitech F510, D mode
diff --git a/device/gamepad/xbox_controller_mac.h b/device/gamepad/xbox_controller_mac.h index bccd46ae..20ab56ee 100644 --- a/device/gamepad/xbox_controller_mac.h +++ b/device/gamepad/xbox_controller_mac.h
@@ -32,6 +32,7 @@ static const uint16_t kProductXboxOneController2015 = 0x02dd; static const uint16_t kProductXboxOneEliteController = 0x02e3; static const uint16_t kProductXboxOneSController = 0x02ea; + static const uint16_t kProductXboxAdaptiveController = 0x0b0a; enum ControllerType { UNKNOWN_CONTROLLER, @@ -39,7 +40,8 @@ XBOX_ONE_CONTROLLER_2013, XBOX_ONE_CONTROLLER_2015, XBOX_ONE_ELITE_CONTROLLER, - XBOX_ONE_S_CONTROLLER + XBOX_ONE_S_CONTROLLER, + XBOX_ADAPTIVE_CONTROLLER, }; enum LEDPattern { @@ -117,6 +119,7 @@ ControllerType GetControllerType() const; std::string GetControllerTypeString() const; std::string GetIdString() const; + bool SupportsVibration() const; private: static void WriteComplete(void* context, IOReturn result, void* arg0);
diff --git a/device/gamepad/xbox_controller_mac.mm b/device/gamepad/xbox_controller_mac.mm index de8dfd3..b9fcf2f5 100644 --- a/device/gamepad/xbox_controller_mac.mm +++ b/device/gamepad/xbox_controller_mac.mm
@@ -136,14 +136,6 @@ int16_t stick_right_y; }; -struct XboxOneEliteButtonData { - // The Xbox One Elite controller supports button remapping and exposes both - // the mapped and unmapped data in the button state report. - XboxOneButtonData button_data; - XboxOneButtonData true_button_data; - int8_t paddle; -}; - struct XboxOneGuideData { uint8_t down; uint8_t dummy1; @@ -169,11 +161,18 @@ static_assert(sizeof(Xbox360ButtonData) == 18, "Xbox360ButtonData wrong size"); static_assert(sizeof(Xbox360RumbleData) == 8, "Xbox360RumbleData wrong size"); static_assert(sizeof(XboxOneButtonData) == 14, "XboxOneButtonData wrong size"); -static_assert(sizeof(XboxOneEliteButtonData) == 29, - "XboxOneEliteButtonData wrong size"); static_assert(sizeof(XboxOneGuideData) == 2, "XboxOneGuideData wrong size"); static_assert(sizeof(XboxOneRumbleData) == 13, "XboxOneRumbleData wrong size"); +// Report lengths for the input reports that carry gamepad button and axis data +// on special Xbox One devices. These devices support input remapping and +// include both the mapped and unmapped data in the input report, along with +// additional data specific to the device. This driver only uses the mapped +// data, which is at the beginning of the report and has the same structure as +// the standard XboxOneButtonData report. +const size_t kXboxOneEliteButtonDataBytes = 29; +const size_t kXboxAdaptiveButtonDataBytes = 50; + // From MSDN: // http://msdn.microsoft.com/en-us/library/windows/desktop/ee417001(v=vs.85).aspx#dead_zone const int16_t kLeftThumbDeadzone = 7849; @@ -293,6 +292,8 @@ return XboxControllerMac::XBOX_ONE_ELITE_CONTROLLER; case XboxControllerMac::kProductXboxOneSController: return XboxControllerMac::XBOX_ONE_S_CONTROLLER; + case XboxControllerMac::kProductXboxAdaptiveController: + return XboxControllerMac::XBOX_ADAPTIVE_CONTROLLER; default: break; } @@ -397,6 +398,7 @@ case XBOX_ONE_CONTROLLER_2015: case XBOX_ONE_ELITE_CONTROLLER: case XBOX_ONE_S_CONTROLLER: + case XBOX_ADAPTIVE_CONTROLLER: read_endpoint_ = kXboxOneReadEndpoint; control_endpoint_ = kXboxOneControlEndpoint; request.bInterfaceClass = 255; @@ -530,7 +532,8 @@ if (controller_type_ == XBOX_ONE_CONTROLLER_2013 || controller_type_ == XBOX_ONE_CONTROLLER_2015 || controller_type_ == XBOX_ONE_ELITE_CONTROLLER || - controller_type_ == XBOX_ONE_S_CONTROLLER) + controller_type_ == XBOX_ONE_S_CONTROLLER || + controller_type_ == XBOX_ADAPTIVE_CONTROLLER) WriteXboxOneInit(); } } @@ -573,6 +576,7 @@ case XBOX_ONE_CONTROLLER_2015: case XBOX_ONE_ELITE_CONTROLLER: case XBOX_ONE_S_CONTROLLER: + case XBOX_ADAPTIVE_CONTROLLER: return kVendorMicrosoft; default: return 0; @@ -591,6 +595,8 @@ return kProductXboxOneEliteController; case XBOX_ONE_S_CONTROLLER: return kProductXboxOneSController; + case XBOX_ADAPTIVE_CONTROLLER: + return kProductXboxAdaptiveController; default: return 0; } @@ -608,6 +614,7 @@ case XBOX_ONE_CONTROLLER_2015: case XBOX_ONE_ELITE_CONTROLLER: case XBOX_ONE_S_CONTROLLER: + case XBOX_ADAPTIVE_CONTROLLER: return "Xbox One Controller"; default: return "Unrecognized Controller"; @@ -620,6 +627,11 @@ GetProductId()); } +bool XboxControllerMac::SupportsVibration() const { + // The Xbox Adaptive Controller has no vibration actuators. + return controller_type_ != XBOX_ADAPTIVE_CONTROLLER; +} + // static void XboxControllerMac::WriteComplete(void* context, IOReturn result, @@ -715,8 +727,10 @@ switch (type) { case XBOX_ONE_STATUS_MESSAGE_BUTTONS: { if (length != sizeof(XboxOneButtonData) && - length != sizeof(XboxOneEliteButtonData)) + length != kXboxOneEliteButtonDataBytes && + length != kXboxAdaptiveButtonDataBytes) { return; + } XboxOneButtonData* data = reinterpret_cast<XboxOneButtonData*>(buffer); Data normalized_data; NormalizeXboxOneButtonData(*data, &normalized_data);
diff --git a/device/gamepad/xbox_data_fetcher_mac.cc b/device/gamepad/xbox_data_fetcher_mac.cc index 097e07d0..eab0beb 100644 --- a/device/gamepad/xbox_data_fetcher_mac.cc +++ b/device/gamepad/xbox_data_fetcher_mac.cc
@@ -40,7 +40,10 @@ std::unique_ptr<XboxControllerMac> controller) : fetcher(fetcher), controller(std::move(controller)) {} -XboxDataFetcher::PendingController::~PendingController() = default; +XboxDataFetcher::PendingController::~PendingController() { + if (controller) + controller->Shutdown(); +} XboxDataFetcher::XboxDataFetcher() = default; @@ -219,6 +222,13 @@ &xbox_360_device_added_iter_, &xbox_360_device_removed_iter_)) return false; + if (!RegisterForDeviceNotifications( + XboxControllerMac::kVendorMicrosoft, + XboxControllerMac::kProductXboxAdaptiveController, + &xbox_adaptive_device_added_iter_, + &xbox_adaptive_device_removed_iter_)) + return false; + return true; } @@ -298,6 +308,7 @@ } void XboxDataFetcher::AddController(XboxControllerMac* controller) { + DCHECK(controller); DCHECK(!ControllerForLocation(controller->location_id())) << "Controller with location ID " << controller->location_id() << " already exists in the set of controllers."; @@ -325,12 +336,13 @@ state->axis_mask = 0; state->button_mask = 0; - // Assume all Xbox gamepads support vibration effects. state->data.vibration_actuator.type = GamepadHapticActuatorType::kDualRumble; - state->data.vibration_actuator.not_null = true; + state->data.vibration_actuator.not_null = controller->SupportsVibration(); } void XboxDataFetcher::RemoveController(XboxControllerMac* controller) { + DCHECK(controller); + controller->Shutdown(); controllers_.erase(controller); delete controller; }
diff --git a/device/gamepad/xbox_data_fetcher_mac.h b/device/gamepad/xbox_data_fetcher_mac.h index c6e59fa..2532ee11 100644 --- a/device/gamepad/xbox_data_fetcher_mac.h +++ b/device/gamepad/xbox_data_fetcher_mac.h
@@ -115,6 +115,8 @@ base::mac::ScopedIOObject<io_iterator_t> xbox_one_elite_device_removed_iter_; base::mac::ScopedIOObject<io_iterator_t> xbox_one_s_device_added_iter_; base::mac::ScopedIOObject<io_iterator_t> xbox_one_s_device_removed_iter_; + base::mac::ScopedIOObject<io_iterator_t> xbox_adaptive_device_added_iter_; + base::mac::ScopedIOObject<io_iterator_t> xbox_adaptive_device_removed_iter_; DISALLOW_COPY_AND_ASSIGN(XboxDataFetcher); };
diff --git a/device/usb/mojo/device_manager_impl.cc b/device/usb/mojo/device_manager_impl.cc index 5e4a00d..78b9304 100644 --- a/device/usb/mojo/device_manager_impl.cc +++ b/device/usb/mojo/device_manager_impl.cc
@@ -70,6 +70,39 @@ std::move(device_client)); } +#if defined(OS_ANDROID) +void DeviceManagerImpl::RefreshDeviceInfo(const std::string& guid, + RefreshDeviceInfoCallback callback) { + scoped_refptr<UsbDevice> device = usb_service_->GetDevice(guid); + if (!device) { + std::move(callback).Run(nullptr); + return; + } + + if (device->permission_granted()) { + std::move(callback).Run(mojom::UsbDeviceInfo::From(*device)); + return; + } + + device->RequestPermission( + base::BindOnce(&DeviceManagerImpl::OnPermissionGrantedToRefresh, + weak_factory_.GetWeakPtr(), device, std::move(callback))); +} + +void DeviceManagerImpl::OnPermissionGrantedToRefresh( + scoped_refptr<UsbDevice> device, + RefreshDeviceInfoCallback callback, + bool granted) { + DCHECK_EQ(granted, device->permission_granted()); + if (!device->permission_granted()) { + std::move(callback).Run(nullptr); + return; + } + + std::move(callback).Run(mojom::UsbDeviceInfo::From(*device)); +} +#endif // defined(OS_ANDROID) + #if defined(OS_CHROMEOS) void DeviceManagerImpl::CheckAccess(const std::string& guid, CheckAccessCallback callback) {
diff --git a/device/usb/mojo/device_manager_impl.h b/device/usb/mojo/device_manager_impl.h index 2b97266..c42ee54 100644 --- a/device/usb/mojo/device_manager_impl.h +++ b/device/usb/mojo/device_manager_impl.h
@@ -16,6 +16,7 @@ #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" #include "base/scoped_observer.h" +#include "build/build_config.h" #include "device/usb/public/mojom/device_manager.mojom.h" #include "device/usb/usb_service.h" #include "mojo/public/cpp/bindings/binding_set.h" @@ -48,6 +49,14 @@ mojom::UsbDeviceRequest device_request, mojom::UsbDeviceClientPtr device_client) override; +#if defined(OS_ANDROID) + void RefreshDeviceInfo(const std::string& guid, + RefreshDeviceInfoCallback callback) override; + void OnPermissionGrantedToRefresh(scoped_refptr<UsbDevice> device, + RefreshDeviceInfoCallback callback, + bool granted); +#endif // defined(OS_ANDROID) + #if defined(OS_CHROMEOS) void CheckAccess(const std::string& guid, CheckAccessCallback callback) override;
diff --git a/device/usb/public/cpp/fake_usb_device_manager.cc b/device/usb/public/cpp/fake_usb_device_manager.cc index 3e147752..bc0acfb 100644 --- a/device/usb/public/cpp/fake_usb_device_manager.cc +++ b/device/usb/public/cpp/fake_usb_device_manager.cc
@@ -58,6 +58,20 @@ std::move(device_client)); } +#if defined(OS_ANDROID) +void FakeUsbDeviceManager::RefreshDeviceInfo( + const std::string& guid, + RefreshDeviceInfoCallback callback) { + auto it = devices_.find(guid); + if (it == devices_.end()) { + std::move(callback).Run(nullptr); + return; + } + + std::move(callback).Run(it->second->GetDeviceInfo().Clone()); +} +#endif + #if defined(OS_CHROMEOS) void FakeUsbDeviceManager::CheckAccess(const std::string& guid, CheckAccessCallback callback) {
diff --git a/device/usb/public/cpp/fake_usb_device_manager.h b/device/usb/public/cpp/fake_usb_device_manager.h index f88e8450..a4af434 100644 --- a/device/usb/public/cpp/fake_usb_device_manager.h +++ b/device/usb/public/cpp/fake_usb_device_manager.h
@@ -11,6 +11,7 @@ #include "base/memory/scoped_refptr.h" #include "base/optional.h" +#include "build/build_config.h" #include "device/usb/public/cpp/fake_usb_device_info.h" #include "device/usb/public/mojom/device.mojom.h" #include "device/usb/public/mojom/device_manager.mojom.h" @@ -70,6 +71,11 @@ mojom::UsbDeviceRequest device_request, mojom::UsbDeviceClientPtr device_client) override; +#if defined(OS_ANDROID) + void RefreshDeviceInfo(const std::string& guid, + RefreshDeviceInfoCallback callback) override; +#endif + #if defined(OS_CHROMEOS) void CheckAccess(const std::string& guid, CheckAccessCallback callback) override;
diff --git a/device/usb/public/mojom/device_manager.mojom b/device/usb/public/mojom/device_manager.mojom index e1e319df..1ffb2c029 100644 --- a/device/usb/public/mojom/device_manager.mojom +++ b/device/usb/public/mojom/device_manager.mojom
@@ -23,6 +23,13 @@ GetDevice(string guid, UsbDevice& device_request, UsbDeviceClient? device_client); + // Check Android permissions to access the device identified by |guid|. If + // necessary the user is prompted to grant this access. If access is granted + // the device information is refreshed to include data not previously + // accessible. + [EnableIf=is_android] + RefreshDeviceInfo(string guid) => (UsbDeviceInfo? device_info); + // Check whether permission_broker will allow a future Open call for // a given USB device to succeed. [EnableIf=is_chromeos] @@ -36,4 +43,4 @@ // Sets the client for this DeviceManager service. The service will notify its // client of device events such as connection and disconnection. SetClient(associated UsbDeviceManagerClient client); -}; \ No newline at end of file +};
diff --git a/device/usb/usb_device_android.cc b/device/usb/usb_device_android.cc index ac5d5d0..cd707a2 100644 --- a/device/usb/usb_device_android.cc +++ b/device/usb/usb_device_android.cc
@@ -167,11 +167,19 @@ UsbDeviceAndroid::~UsbDeviceAndroid() {} -void UsbDeviceAndroid::PermissionGranted(bool granted) { - if (granted) - Open(base::Bind(&UsbDeviceAndroid::OnDeviceOpenedToReadDescriptors, this)); - else - CallRequestPermissionCallbacks(granted); +void UsbDeviceAndroid::PermissionGranted(JNIEnv* env, bool granted) { + if (!granted) { + CallRequestPermissionCallbacks(false); + return; + } + + ScopedJavaLocalRef<jstring> serial_jstring = + Java_ChromeUsbDevice_getSerialNumber(env, j_object_); + if (!serial_jstring.is_null()) + serial_number_ = ConvertJavaStringToUTF16(env, serial_jstring); + + Open( + base::BindOnce(&UsbDeviceAndroid::OnDeviceOpenedToReadDescriptors, this)); } void UsbDeviceAndroid::CallRequestPermissionCallbacks(bool granted) { @@ -184,13 +192,14 @@ void UsbDeviceAndroid::OnDeviceOpenedToReadDescriptors( scoped_refptr<UsbDeviceHandle> device_handle) { - if (device_handle) { - ReadUsbDescriptors( - device_handle, - base::Bind(&UsbDeviceAndroid::OnReadDescriptors, this, device_handle)); - } else { + if (!device_handle) { CallRequestPermissionCallbacks(false); + return; } + + ReadUsbDescriptors(device_handle, + base::BindOnce(&UsbDeviceAndroid::OnReadDescriptors, this, + device_handle)); } void UsbDeviceAndroid::OnReadDescriptors(
diff --git a/device/usb/usb_device_android.h b/device/usb/usb_device_android.h index 751b985..2b83bfb 100644 --- a/device/usb/usb_device_android.h +++ b/device/usb/usb_device_android.h
@@ -26,7 +26,7 @@ void Open(OpenCallback callback) override; jint device_id() const { return device_id_; } - void PermissionGranted(bool granted); + void PermissionGranted(JNIEnv* env, bool granted); private: UsbDeviceAndroid(
diff --git a/device/usb/usb_service_android.cc b/device/usb/usb_service_android.cc index 898c0af9..94c14c8 100644 --- a/device/usb/usb_service_android.cc +++ b/device/usb/usb_service_android.cc
@@ -76,7 +76,7 @@ jboolean granted) { const auto it = devices_by_id_.find(device_id); DCHECK(it != devices_by_id_.end()); - it->second->PermissionGranted(granted); + it->second->PermissionGranted(env, granted); } ScopedJavaLocalRef<jobject> UsbServiceAndroid::OpenDevice(
diff --git a/device/vr/BUILD.gn b/device/vr/BUILD.gn index b64384c..1e0aa57 100644 --- a/device/vr/BUILD.gn +++ b/device/vr/BUILD.gn
@@ -29,6 +29,8 @@ "util/copy_to_ustring.h", "util/fps_meter.cc", "util/fps_meter.h", + "util/gamepad_builder.cc", + "util/gamepad_builder.h", "util/sample_queue.cc", "util/sample_queue.h", "util/sliding_average.cc",
diff --git a/device/vr/oculus/oculus_gamepad_helper.cc b/device/vr/oculus/oculus_gamepad_helper.cc index bcb30013..99fa28db 100644 --- a/device/vr/oculus/oculus_gamepad_helper.cc +++ b/device/vr/oculus/oculus_gamepad_helper.cc
@@ -8,11 +8,8 @@ #include <memory> #include "base/logging.h" -#include "base/stl_util.h" -#include "base/strings/string16.h" -#include "base/strings/utf_string_conversions.h" #include "device/gamepad/public/cpp/gamepads.h" -#include "device/vr/util/copy_to_ustring.h" +#include "device/vr/util/gamepad_builder.h" #include "device/vr/vr_device.h" #include "third_party/libovr/src/Include/OVR_CAPI.h" #include "ui/gfx/transform.h" @@ -188,75 +185,63 @@ data->gamepads.push_back(std::move(remote)); } -class WebXROculusGamepadBuilder { +device::mojom::XRHandedness OculusToMojomHand(ovrHandType hand) { + switch (hand) { + case ovrHand_Left: + return device::mojom::XRHandedness::LEFT; + case ovrHand_Right: + return device::mojom::XRHandedness::RIGHT; + default: + return device::mojom::XRHandedness::NONE; + } +} + +class OculusGamepadBuilder : public GamepadBuilder { public: - explicit WebXROculusGamepadBuilder(ovrInputState state) : state_(state) { - gamepad_.connected = true; + // TODO(https://crbug.com/942201): Get correct ID string once WebXR spec issue + // #550 (https://github.com/immersive-web/webxr/issues/550) is resolved. + OculusGamepadBuilder(ovrInputState state, ovrHandType hand) + : GamepadBuilder("unknown", + GamepadMapping::kXRStandard, + OculusToMojomHand(hand)), + state_(state) {} - // According to device::Gamepad comments, this is how to get the timestamp. - gamepad_.timestamp = base::TimeTicks::Now().since_origin().InMicroseconds(); - } - - void SetID(const base::string16& id) { - CopyToUString(id, gamepad_.id, base::size(gamepad_.id)); - } - - void SetMapping(const base::string16& mapping) { - CopyToUString(mapping, gamepad_.mapping, base::size(gamepad_.mapping)); - } - - void SetHand(GamepadHand hand) { gamepad_.hand = hand; } + ~OculusGamepadBuilder() override = default; void AddStandardButton(ovrButton id) { bool pressed = (state_.Buttons & id) != 0; bool touched = (state_.Touches & id) != 0; double value = pressed ? 1.0 : 0.0; - AddButton(pressed, touched, value); + AddButton(GamepadButton(pressed, touched, value)); } void AddTouchButton(ovrTouch id) { bool touched = (state_.Touches & id) != 0; - AddButton(false, touched, 0.0f); + AddButton(GamepadButton(false, touched, 0.0f)); } void AddTriggerButton(float value) { value = ApplyTriggerDeadzone(value); bool pressed = value != 0; bool touched = pressed; - AddButton(pressed, touched, value); + AddButton(GamepadButton(pressed, touched, value)); } void AddTouchTriggerButton(ovrTouch id, float value) { value = ApplyTriggerDeadzone(value); bool pressed = value != 0; bool touched = (state_.Touches & id) != 0; - AddButton(pressed, touched, value); + AddButton(GamepadButton(pressed, touched, value)); } - // Used when xr-standard mapping requires an empty button in a reserved slot - // instead of just shifting all remaining buttons in the array down by 1 - // index. - void AddEmptyButton() { AddButton(false, false, 0); } - - void AddAxis(double value) { - DCHECK_LT(gamepad_.axes_length, Gamepad::kAxesLengthCap); - gamepad_.axes[gamepad_.axes_length++] = value; + bool IsValid() const override { + return GamepadBuilder::IsValid() && GetHandedness() != GamepadHand::kNone; } - Gamepad GetGamepad() { return gamepad_; } - private: - void AddButton(bool pressed, bool touched, double value) { - DCHECK_LT(gamepad_.buttons_length, Gamepad::kButtonsLengthCap); - gamepad_.buttons[gamepad_.buttons_length++] = - GamepadButton(pressed, touched, value); - } - ovrInputState state_; - Gamepad gamepad_; - - DISALLOW_COPY_AND_ASSIGN(WebXROculusGamepadBuilder); + DISALLOW_COPY_AND_ASSIGN(OculusGamepadBuilder); }; } // namespace @@ -304,36 +289,28 @@ return base::nullopt; } - WebXROculusGamepadBuilder touch(input_touch); - - // TODO(https://crbug.com/942201): Get correct ID string once WebXR spec issue - // #550 (https://github.com/immersive-web/webxr/issues/550) is resolved. - touch.SetID(base::UTF8ToUTF16("unknown")); - - touch.SetMapping(base::UTF8ToUTF16("xr-standard")); + OculusGamepadBuilder touch(input_touch, hand); touch.AddAxis(input_touch.Thumbstick[hand].x); touch.AddAxis(-input_touch.Thumbstick[hand].y); switch (hand) { case ovrHand_Left: - touch.SetHand(GamepadHand::kLeft); touch.AddTouchTriggerButton(ovrTouch_LIndexTrigger, input_touch.IndexTrigger[hand]); touch.AddStandardButton(ovrButton_LThumb); touch.AddTriggerButton(input_touch.HandTrigger[hand]); - touch.AddEmptyButton(); + touch.AddPlaceholderButton(); touch.AddStandardButton(ovrButton_X); touch.AddStandardButton(ovrButton_Y); touch.AddTouchButton(ovrTouch_LThumbRest); break; case ovrHand_Right: - touch.SetHand(GamepadHand::kRight); touch.AddTouchTriggerButton(ovrTouch_RIndexTrigger, input_touch.IndexTrigger[hand]); touch.AddStandardButton(ovrButton_RThumb); touch.AddTriggerButton(input_touch.HandTrigger[hand]); - touch.AddEmptyButton(); + touch.AddPlaceholderButton(); touch.AddStandardButton(ovrButton_A); touch.AddStandardButton(ovrButton_B); touch.AddTouchButton(ovrTouch_RThumbRest);
diff --git a/device/vr/openvr/openvr_gamepad_helper.cc b/device/vr/openvr/openvr_gamepad_helper.cc index 56cc73f..98293081 100644 --- a/device/vr/openvr/openvr_gamepad_helper.cc +++ b/device/vr/openvr/openvr_gamepad_helper.cc
@@ -5,10 +5,13 @@ #include "device/vr/openvr/openvr_gamepad_helper.h" #include <memory> +#include <unordered_set> +#include "base/compiler_specific.h" #include "base/logging.h" #include "base/strings/utf_string_conversions.h" #include "device/gamepad/public/cpp/gamepads.h" +#include "device/vr/util/gamepad_builder.h" #include "device/vr/vr_device.h" #include "third_party/openvr/src/headers/openvr.h" #include "ui/gfx/transform.h" @@ -18,26 +21,125 @@ namespace { -mojom::XRGamepadButtonPtr GetGamepadButton( - const vr::VRControllerState_t& controller_state, - uint64_t supported_buttons, - vr::EVRButtonId button_id) { +// Constants/functions used by both WebXR and WebVR. +constexpr double kJoystickDeadzone = 0.1; + +bool TryGetGamepadButton(const vr::VRControllerState_t& controller_state, + uint64_t supported_buttons, + vr::EVRButtonId button_id, + GamepadButton* button) { uint64_t button_mask = vr::ButtonMaskFromId(button_id); if ((supported_buttons & button_mask) != 0) { - auto ret = mojom::XRGamepadButton::New(); bool button_pressed = (controller_state.ulButtonPressed & button_mask) != 0; bool button_touched = (controller_state.ulButtonTouched & button_mask) != 0; - ret->touched = button_touched; - ret->pressed = button_pressed; - ret->value = button_pressed ? 1.0 : 0.0; - return ret; + button->touched = button_touched || button_pressed; + button->pressed = button_pressed; + button->value = button_pressed ? 1.0 : 0.0; + return true; } - return nullptr; + return false; +} + +vr::EVRButtonId GetAxisId(uint32_t axis_offset) { + return static_cast<vr::EVRButtonId>(vr::k_EButton_Axis0 + axis_offset); +} + +std::map<vr::EVRButtonId, GamepadBuilder::ButtonData> GetAxesButtons( + vr::IVRSystem* vr_system, + const vr::VRControllerState_t& controller_state, + uint64_t supported_buttons, + uint32_t controller_id) { + std::map<vr::EVRButtonId, GamepadBuilder::ButtonData> button_data_map; + + for (uint32_t j = 0; j < vr::k_unControllerStateAxisCount; ++j) { + int32_t axis_type = vr_system->GetInt32TrackedDeviceProperty( + controller_id, + static_cast<vr::TrackedDeviceProperty>(vr::Prop_Axis0Type_Int32 + j)); + + GamepadBuilder::ButtonData button_data; + double x_axis = controller_state.rAxis[j].x; + double y_axis = controller_state.rAxis[j].y; + + switch (axis_type) { + case vr::k_eControllerAxis_Joystick: + // We only want to apply the deadzone to joysticks, since various + // runtimes may not have already done that, but touchpads should + // be fine. + x_axis = std::fabs(x_axis) < kJoystickDeadzone ? 0 : x_axis; + y_axis = std::fabs(y_axis) < kJoystickDeadzone ? 0 : y_axis; + FALLTHROUGH; + case vr::k_eControllerAxis_TrackPad: { + button_data.has_both_axes = true; + button_data.x_axis = x_axis; + button_data.y_axis = y_axis; + vr::EVRButtonId button_id = GetAxisId(j); + GamepadButton button; + if (TryGetGamepadButton(controller_state, supported_buttons, button_id, + &button)) { + button_data.touched = button.touched; + button_data.pressed = button.pressed; + button_data.value = button.value; + button_data_map[button_id] = button_data; + } + } break; + case vr::k_eControllerAxis_Trigger: { + GamepadButton button; + GamepadBuilder::ButtonData button_data; + vr::EVRButtonId button_id = GetAxisId(j); + if (TryGetGamepadButton(controller_state, supported_buttons, button_id, + &button)) { + button_data.touched = button.touched; + button_data.pressed = button.pressed; + button_data.value = x_axis; + button_data_map[button_id] = button_data; + } + } break; + } + } + + return button_data_map; +} + +// Constants/functions only used by WebXR. +constexpr std::array<vr::EVRButtonId, 5> kWebXRButtonOrder = { + vr::k_EButton_A, vr::k_EButton_DPad_Left, vr::k_EButton_DPad_Up, + vr::k_EButton_DPad_Right, vr::k_EButton_DPad_Down, +}; + +// Constants/functions only used by WebVR. +constexpr std::array<vr::EVRButtonId, 7> kWebVRButtonOrder = { + vr::k_EButton_A, + vr::k_EButton_Grip, + vr::k_EButton_ApplicationMenu, + vr::k_EButton_DPad_Left, + vr::k_EButton_DPad_Up, + vr::k_EButton_DPad_Right, + vr::k_EButton_DPad_Down, +}; + +mojom::XRGamepadButtonPtr GetMojomGamepadButton( + const GamepadBuilder::ButtonData& data) { + auto ret = mojom::XRGamepadButton::New(); + ret->touched = data.touched; + ret->pressed = data.pressed; + ret->value = data.value; + + return ret; +} + +mojom::XRGamepadButtonPtr GetMojomGamepadButton(const GamepadButton& data) { + auto ret = mojom::XRGamepadButton::New(); + ret->touched = data.touched; + ret->pressed = data.pressed; + ret->value = data.value; + + return ret; } } // namespace +// WebVR Gamepad Getter. mojom::XRGamepadDataPtr OpenVRGamepadHelper::GetGamepadData( vr::IVRSystem* vr_system) { mojom::XRGamepadDataPtr ret = mojom::XRGamepadData::New(); @@ -78,64 +180,27 @@ uint64_t supported_buttons = vr_system->GetUint64TrackedDeviceProperty( i, vr::Prop_SupportedButtons_Uint64); - for (uint32_t j = 0; j < vr::k_unControllerStateAxisCount; ++j) { - int32_t axis_type = vr_system->GetInt32TrackedDeviceProperty( - i, - static_cast<vr::TrackedDeviceProperty>(vr::Prop_Axis0Type_Int32 + j)); - switch (axis_type) { - case vr::k_eControllerAxis_Joystick: - case vr::k_eControllerAxis_TrackPad: { - gamepad->axes.push_back(controller_state.rAxis[j].x); - gamepad->axes.push_back(controller_state.rAxis[j].y); - auto button = GetGamepadButton( - controller_state, supported_buttons, - static_cast<vr::EVRButtonId>(vr::k_EButton_Axis0 + j)); - if (button) { - gamepad->buttons.push_back(std::move(button)); - } - } break; - case vr::k_eControllerAxis_Trigger: { - auto button = mojom::XRGamepadButton::New(); - button->value = controller_state.rAxis[j].x; - uint64_t button_mask = vr::ButtonMaskFromId( - static_cast<vr::EVRButtonId>(vr::k_EButton_Axis0 + j)); - if ((supported_buttons & button_mask) != 0) { - button->pressed = - (controller_state.ulButtonPressed & button_mask) != 0; - } - gamepad->buttons.push_back(std::move(button)); - } break; + + std::map<vr::EVRButtonId, GamepadBuilder::ButtonData> button_data_map = + GetAxesButtons(vr_system, controller_state, supported_buttons, i); + + for (const auto& button_data_pair : button_data_map) { + GamepadBuilder::ButtonData data = button_data_pair.second; + + gamepad->buttons.push_back(GetMojomGamepadButton(data)); + if (data.has_both_axes) { + gamepad->axes.push_back(data.x_axis); + gamepad->axes.push_back(data.y_axis); } } - auto button = - GetGamepadButton(controller_state, supported_buttons, vr::k_EButton_A); - if (button) - gamepad->buttons.push_back(std::move(button)); - button = GetGamepadButton(controller_state, supported_buttons, - vr::k_EButton_Grip); - if (button) - gamepad->buttons.push_back(std::move(button)); - button = GetGamepadButton(controller_state, supported_buttons, - vr::k_EButton_ApplicationMenu); - if (button) - gamepad->buttons.push_back(std::move(button)); - button = GetGamepadButton(controller_state, supported_buttons, - vr::k_EButton_DPad_Left); - if (button) - gamepad->buttons.push_back(std::move(button)); - button = GetGamepadButton(controller_state, supported_buttons, - vr::k_EButton_DPad_Up); - if (button) - gamepad->buttons.push_back(std::move(button)); - button = GetGamepadButton(controller_state, supported_buttons, - vr::k_EButton_DPad_Right); - if (button) - gamepad->buttons.push_back(std::move(button)); - button = GetGamepadButton(controller_state, supported_buttons, - vr::k_EButton_DPad_Down); - if (button) - gamepad->buttons.push_back(std::move(button)); + for (const auto& button : kWebVRButtonOrder) { + GamepadButton data; + if (TryGetGamepadButton(controller_state, supported_buttons, button, + &data)) { + gamepad->buttons.push_back(GetMojomGamepadButton(data)); + } + } const vr::TrackedDevicePose_t& pose = tracked_devices_poses[i]; if (pose.bPoseIsValid) { @@ -170,4 +235,132 @@ return ret; } +// Helper classes and WebXR Getters +class OpenVRGamepadBuilder : public GamepadBuilder { + public: + enum class AxesRequirement { + kOptional = 0, + kRequired = 1, + }; + + // TODO(https://crbug.com/942201): Get correct ID string once WebXR spec issue + // #550 (https://github.com/immersive-web/webxr/issues/550) is resolved. + OpenVRGamepadBuilder(vr::IVRSystem* vr_system, + uint32_t controller_id, + vr::VRControllerState_t controller_state, + device::mojom::XRHandedness handedness) + : GamepadBuilder("unknown", GamepadMapping::kXRStandard, handedness), + controller_state_(controller_state) { + supported_buttons_ = vr_system->GetUint64TrackedDeviceProperty( + controller_id, vr::Prop_SupportedButtons_Uint64); + + axes_data_ = GetAxesButtons(vr_system, controller_state_, + supported_buttons_, controller_id); + } + + ~OpenVRGamepadBuilder() override = default; + + bool TryAddAxesButton( + vr::EVRButtonId button_id, + AxesRequirement requirement = AxesRequirement::kOptional) { + if (!IsInAxesData(button_id)) + return false; + + bool require_axes = (requirement == AxesRequirement::kRequired); + if (require_axes && !axes_data_[button_id].has_both_axes) + return false; + + AddButton(axes_data_[button_id]); + used_axes_.insert(button_id); + + return true; + } + + bool TryAddNextUnusedAxesButton() { + for (const auto& axes_data_pair : axes_data_) { + vr::EVRButtonId button_id = axes_data_pair.first; + if (IsUsed(button_id)) + continue; + + if (TryAddAxesButton(button_id, AxesRequirement::kRequired)) + return true; + } + + return false; + } + + bool TryAddButton(vr::EVRButtonId button_id) { + GamepadButton button; + if (TryGetGamepadButton(controller_state_, supported_buttons_, button_id, + &button)) { + AddButton(button); + return true; + } + + return false; + } + + // This will add any remaining unused values from axes_data to the gamepad. + void AddRemainingAxes() { + for (const auto& axes_data_pair : axes_data_) { + if (!IsUsed(axes_data_pair.first)) + AddButton(axes_data_pair.second); + } + } + + private: + bool IsUsed(vr::EVRButtonId button_id) { + auto it = used_axes_.find(button_id); + return it != used_axes_.end(); + } + + bool IsInAxesData(vr::EVRButtonId button_id) { + auto it = axes_data_.find(button_id); + return it != axes_data_.end(); + } + + const vr::VRControllerState_t controller_state_; + uint64_t supported_buttons_; + std::map<vr::EVRButtonId, ButtonData> axes_data_; + std::unordered_set<vr::EVRButtonId> used_axes_; + + DISALLOW_COPY_AND_ASSIGN(OpenVRGamepadBuilder); +}; + +base::Optional<Gamepad> OpenVRGamepadHelper::GetXRGamepad( + vr::IVRSystem* vr_system, + uint32_t controller_id, + vr::VRControllerState_t controller_state, + device::mojom::XRHandedness handedness) { + OpenVRGamepadBuilder builder(vr_system, controller_id, controller_state, + handedness); + + if (!builder.TryAddAxesButton(vr::k_EButton_SteamVR_Trigger)) + return base::nullopt; + + if (!builder.TryAddNextUnusedAxesButton()) + return base::nullopt; + + if (!builder.TryAddButton(vr::k_EButton_Grip)) + builder.AddPlaceholderButton(); + + // If we can't find any secondary button with an x and y axis, add a fake + // button. Note that we're not worried about ensuring that the axes data gets + // added, because if there were any other axes to add, we would've added them. + if (!builder.TryAddNextUnusedAxesButton()) + builder.AddPlaceholderButton(); + + // Now that all of the xr-standard reserved buttons have been filled in, we + // add the rest of the buttons in order of decreasing importance. + // First add regular buttons + for (const auto& button : kWebXRButtonOrder) { + builder.TryAddButton(button); + } + + // Finally, add any remaining axis buttons (triggers/josysticks/touchpads) + builder.AddRemainingAxes(); + + return builder.GetGamepad(); +} + } // namespace device
diff --git a/device/vr/openvr/openvr_gamepad_helper.h b/device/vr/openvr/openvr_gamepad_helper.h index 3bc7879..f7154b65 100644 --- a/device/vr/openvr/openvr_gamepad_helper.h +++ b/device/vr/openvr/openvr_gamepad_helper.h
@@ -13,6 +13,11 @@ class OpenVRGamepadHelper { public: static mojom::XRGamepadDataPtr GetGamepadData(vr::IVRSystem* system); + static base::Optional<Gamepad> GetXRGamepad( + vr::IVRSystem* system, + uint32_t controller_id, + vr::VRControllerState_t controller_state, + device::mojom::XRHandedness handedness); }; } // namespace device
diff --git a/device/vr/openvr/openvr_render_loop.cc b/device/vr/openvr/openvr_render_loop.cc index 1e7e744..a3d9174a 100644 --- a/device/vr/openvr/openvr_render_loop.cc +++ b/device/vr/openvr/openvr_render_loop.cc
@@ -43,6 +43,20 @@ 0, 0, 1); } +device::mojom::XRHandedness ConvertToMojoHandedness( + vr::ETrackedControllerRole controller_role) { + switch (controller_role) { + case vr::TrackedControllerRole_LeftHand: + return device::mojom::XRHandedness::LEFT; + case vr::TrackedControllerRole_RightHand: + return device::mojom::XRHandedness::RIGHT; + case vr::TrackedControllerRole_Invalid: + return device::mojom::XRHandedness::NONE; + } + + NOTREACHED(); +} + } // namespace OpenVRRenderLoop::OpenVRRenderLoop() : XRCompositorCommon() {} @@ -247,8 +261,11 @@ device::mojom::XRInputSourceState::New(); vr::VRControllerState_t controller_state; - openvr_->GetSystem()->GetControllerState(i, &controller_state, - sizeof(vr::VRControllerState_t)); + bool have_state = openvr_->GetSystem()->GetControllerState( + i, &controller_state, sizeof(vr::VRControllerState_t)); + if (!have_state) + continue; + bool pressed = controller_state.ulButtonPressed & vr::ButtonMaskFromId(vr::k_EButton_SteamVR_Trigger); @@ -270,6 +287,12 @@ vr::ETrackedControllerRole controller_role = openvr_->GetSystem()->GetControllerRoleForTrackedDeviceIndex(i); + device::mojom::XRHandedness handedness = + ConvertToMojoHandedness(controller_role); + + state->gamepad = OpenVRGamepadHelper::GetXRGamepad( + openvr_->GetSystem(), i, controller_state, handedness); + // If this is a newly active controller or if the handedness has changed // since the last update, re-send the controller's description. if (newly_active || controller_role != input_active_state.controller_role) { @@ -279,18 +302,7 @@ // It's a handheld pointing device. desc->target_ray_mode = device::mojom::XRTargetRayMode::POINTING; - // Set handedness. - switch (controller_role) { - case vr::TrackedControllerRole_LeftHand: - desc->handedness = device::mojom::XRHandedness::LEFT; - break; - case vr::TrackedControllerRole_RightHand: - desc->handedness = device::mojom::XRHandedness::RIGHT; - break; - default: - desc->handedness = device::mojom::XRHandedness::NONE; - break; - } + desc->handedness = handedness; input_active_state.controller_role = controller_role; // OpenVR controller are fully 6DoF.
diff --git a/device/vr/public/mojom/vr_service.mojom b/device/vr/public/mojom/vr_service.mojom index b6b0bd3..d914ba2 100644 --- a/device/vr/public/mojom/vr_service.mojom +++ b/device/vr/public/mojom/vr_service.mojom
@@ -237,7 +237,31 @@ bool wait_for_gpu_fence; }; +enum XRPlaneOrientation { + UNKNOWN = 0, + HORIZONTAL = 1, + VERTICAL = 2 +}; + +struct XRPlanePointData { + float x; + float z; +}; + struct XRPlaneData { + // Unique (per session) identifier of the plane. + int32 id; + + // Plane orientation relative to gravity. + XRPlaneOrientation orientation; + + // Pose of the plane's center. Defines new coordinate space. + // Y axis of the coordinate space describes plane's normal, the rotation of + // X and Z around the Y axis is arbitrary. + VRPose pose; + + // Vertices of 2D convex polygon approximating the plane. + array<XRPlanePointData> polygon; }; // The data needed for each animation frame of an XRSession.
diff --git a/device/vr/util/gamepad_builder.cc b/device/vr/util/gamepad_builder.cc new file mode 100644 index 0000000..0f22c0cc --- /dev/null +++ b/device/vr/util/gamepad_builder.cc
@@ -0,0 +1,125 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#include "device/vr/util/gamepad_builder.h" + +#include "base/logging.h" +#include "base/stl_util.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "device/vr/util/copy_to_ustring.h" + +namespace device { + +namespace { +GamepadHand MojoToGamepadHandedness(device::mojom::XRHandedness handedness) { + switch (handedness) { + case device::mojom::XRHandedness::LEFT: + return GamepadHand::kLeft; + case device::mojom::XRHandedness::RIGHT: + return GamepadHand::kRight; + case device::mojom::XRHandedness::NONE: + return GamepadHand::kNone; + } + + NOTREACHED(); +} + +// TODO(crbug.com/955809): Once Gamepad uses an enum this method can be removed. +std::string GamepadMappingToString(GamepadBuilder::GamepadMapping mapping) { + switch (mapping) { + case GamepadBuilder::GamepadMapping::kNone: + return ""; + break; + case GamepadBuilder::GamepadMapping::kStandard: + return "standard"; + break; + case GamepadBuilder::GamepadMapping::kXRStandard: + return "xr-standard"; + break; + } + + NOTREACHED(); +} + +} // anonymous namespace + +GamepadBuilder::GamepadBuilder(const std::string& gamepad_id, + GamepadMapping mapping, + device::mojom::XRHandedness handedness) + : mapping_(mapping) { + DCHECK_LT(gamepad_id.size(), Gamepad::kIdLengthCap); + + auto mapping_str = GamepadMappingToString(mapping); + DCHECK_LT(mapping_str.size(), Gamepad::kMappingLengthCap); + + gamepad_.connected = true; + gamepad_.timestamp = base::TimeTicks::Now().since_origin().InMicroseconds(); + gamepad_.hand = MojoToGamepadHandedness(handedness); + CopyToUString(base::UTF8ToUTF16(gamepad_id), gamepad_.id, + Gamepad::kIdLengthCap); + CopyToUString(base::UTF8ToUTF16(mapping_str), gamepad_.mapping, + Gamepad::kMappingLengthCap); +} + +GamepadBuilder::~GamepadBuilder() = default; + +bool GamepadBuilder::IsValid() const { + switch (mapping_) { + case GamepadMapping::kXRStandard: + // In order to satisfy the XRStandard mapping at least two buttons and one + // set of axes need to have been added. + return gamepad_.axes_length >= 2 && gamepad_.buttons_length >= 2; + case GamepadMapping::kStandard: + case GamepadMapping::kNone: + // Neither standard requires any buttons to be set, and all other data + // is set in the constructor. + return true; + } + + NOTREACHED(); +} + +base::Optional<Gamepad> GamepadBuilder::GetGamepad() const { + if (IsValid()) + return gamepad_; + + return base::nullopt; +} + +void GamepadBuilder::SetAxisDeadzone(double deadzone) { + DCHECK_GE(deadzone, 0); + axis_deadzone_ = deadzone; +} + +void GamepadBuilder::AddButton(const GamepadButton& button) { + DCHECK_LT(gamepad_.buttons_length, Gamepad::kButtonsLengthCap); + gamepad_.buttons[gamepad_.buttons_length++] = button; +} + +void GamepadBuilder::AddButton(const ButtonData& data) { + AddButton(GamepadButton(data.pressed, data.touched, data.value)); + if (data.has_both_axes) + AddAxes(data); +} + +void GamepadBuilder::AddAxis(double value) { + DCHECK_LT(gamepad_.axes_length, Gamepad::kAxesLengthCap); + gamepad_.axes[gamepad_.axes_length++] = ApplyAxisDeadzoneToValue(value); +} + +void GamepadBuilder::AddAxes(const ButtonData& data) { + DCHECK(data.has_both_axes); + AddAxis(data.x_axis); + AddAxis(data.y_axis); +} + +void GamepadBuilder::AddPlaceholderButton() { + AddButton(GamepadButton()); +} + +double GamepadBuilder::ApplyAxisDeadzoneToValue(double value) const { + return std::fabs(value) < axis_deadzone_ ? 0 : value; +} + +} // namespace device
diff --git a/device/vr/util/gamepad_builder.h b/device/vr/util/gamepad_builder.h new file mode 100644 index 0000000..8fc36904 --- /dev/null +++ b/device/vr/util/gamepad_builder.h
@@ -0,0 +1,61 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +#ifndef DEVICE_VR_UTIL_GAMEPAD_BUILDER_H_ +#define DEVICE_VR_UTIL_GAMEPAD_BUILDER_H_ + +#include <string> + +#include "base/macros.h" +#include "device/gamepad/public/cpp/gamepads.h" +#include "device/vr/public/mojom/isolated_xr_service.mojom.h" + +namespace device { + +class GamepadBuilder { + public: + // TODO(crbug.com/955809): Switch this to device::Gamepad's mapping enum. + enum class GamepadMapping { kNone = 0, kStandard = 1, kXRStandard = 2 }; + + // Helper struct that we don't want to pollute the device namespace + struct ButtonData { + bool touched = false; + bool pressed = false; + double value = 0.0; + + bool has_both_axes = false; + double x_axis = 0.0; + double y_axis = 0.0; + }; + + GamepadBuilder(const std::string& gamepad_id, + GamepadMapping mapping, + device::mojom::XRHandedness handedness); + virtual ~GamepadBuilder(); + + virtual bool IsValid() const; + base::Optional<Gamepad> GetGamepad() const; + void SetAxisDeadzone(double value); + + void AddButton(const GamepadButton& button); + void AddButton(const ButtonData& data); + void AddAxis(double value); + void AddPlaceholderButton(); + + protected: + void AddAxes(const ButtonData& data); + double ApplyAxisDeadzoneToValue(double value) const; + + GamepadHand GetHandedness() const { return gamepad_.hand; } + GamepadMapping GetMapping() const { return mapping_; } + + private: + const GamepadMapping mapping_; + double axis_deadzone_ = 0.0; + Gamepad gamepad_; + + DISALLOW_COPY_AND_ASSIGN(GamepadBuilder); +}; + +} // namespace device +#endif // DEVICE_VR_UTIL_GAMEPAD_BUILDER_H_
diff --git a/device/vr/windows_mixed_reality/mixed_reality_input_helper.cc b/device/vr/windows_mixed_reality/mixed_reality_input_helper.cc index 959b85c..649c0418 100644 --- a/device/vr/windows_mixed_reality/mixed_reality_input_helper.cc +++ b/device/vr/windows_mixed_reality/mixed_reality_input_helper.cc
@@ -13,11 +13,9 @@ #include <unordered_map> #include <vector> -#include "base/strings/string16.h" -#include "base/strings/utf_string_conversions.h" #include "device/gamepad/public/cpp/gamepads.h" #include "device/vr/public/mojom/isolated_xr_service.mojom.h" -#include "device/vr/util/copy_to_ustring.h" +#include "device/vr/util/gamepad_builder.h" #include "device/vr/windows_mixed_reality/wrappers/wmr_input_location.h" #include "device/vr/windows_mixed_reality/wrappers/wmr_input_manager.h" #include "device/vr/windows_mixed_reality/wrappers/wmr_input_source.h" @@ -46,13 +44,16 @@ ParsedInputState::ParsedInputState(ParsedInputState&& other) = default; namespace { + +// Helpers for WebVR Gamepad constexpr double kDeadzoneMinimum = 0.1; double ApplyAxisDeadzone(double value) { return std::fabs(value) < kDeadzoneMinimum ? 0 : value; } -void AddButton(mojom::XRGamepadPtr& gamepad, ButtonData* data) { +void AddButton(mojom::XRGamepadPtr& gamepad, + const GamepadBuilder::ButtonData* data) { if (data) { auto button = mojom::XRGamepadButton::New(); button->pressed = data->pressed; @@ -67,12 +68,14 @@ // These methods are only called for the thumbstick and touchpad, which both // have an X and Y. -void AddAxes(mojom::XRGamepadPtr& gamepad, ButtonData data) { +void AddAxes(mojom::XRGamepadPtr& gamepad, + const GamepadBuilder::ButtonData& data) { gamepad->axes.push_back(ApplyAxisDeadzone(data.x_axis)); gamepad->axes.push_back(ApplyAxisDeadzone(data.y_axis)); } -void AddButtonWithAxes(mojom::XRGamepadPtr& gamepad, ButtonData data) { +void AddButtonWithAxes(mojom::XRGamepadPtr& gamepad, + const GamepadBuilder::ButtonData& data) { AddButton(gamepad, &data); AddAxes(gamepad, data); } @@ -132,141 +135,6 @@ return gamepad_vector; } -mojom::XRGamepadPtr GetWebVRGamepad(ParsedInputState input_state) { - auto gamepad = mojom::XRGamepad::New(); - // This matches the order of button trigger events from Edge. Note that we - // use the polled button state for select here. Voice (which we cannot get - // via polling), lacks enough data to be considered a "Gamepad", and if we - // used eventing the pressed state may be inconsistent. - AddButtonWithAxes(gamepad, input_state.button_data[ButtonName::kThumbstick]); - AddButton(gamepad, &input_state.button_data[ButtonName::kSelect]); - AddButton(gamepad, &input_state.button_data[ButtonName::kGrip]); - AddButton(gamepad, nullptr); // Nothing seems to trigger this button in Edge. - AddButtonWithAxes(gamepad, input_state.button_data[ButtonName::kTouchpad]); - - gamepad->pose = ConvertToVRPose(input_state.gamepad_pose); - gamepad->hand = input_state.source_state->description->handedness; - gamepad->controller_id = input_state.source_state->source_id; - gamepad->can_provide_position = true; - gamepad->can_provide_orientation = true; - gamepad->timestamp = base::TimeTicks::Now(); - - return gamepad; -} - -GamepadHand MojoToGamepadHandedness(device::mojom::XRHandedness handedness) { - switch (handedness) { - case device::mojom::XRHandedness::LEFT: - return GamepadHand::kLeft; - case device::mojom::XRHandedness::RIGHT: - return GamepadHand::kRight; - case device::mojom::XRHandedness::NONE: - return GamepadHand::kNone; - } - - NOTREACHED(); -} - -void AddButton(Gamepad& gamepad, ButtonData* data) { - DCHECK_LT(gamepad.buttons_length, Gamepad::kButtonsLengthCap); - if (data) { - gamepad.buttons[gamepad.buttons_length++] = - GamepadButton(data->pressed, data->touched, data->value); - } else { - gamepad.buttons[gamepad.buttons_length++] = GamepadButton(); - } -} - -void AddAxes(Gamepad& gamepad, ButtonData data) { - DCHECK_LT(gamepad.axes_length + 1, Gamepad::kAxesLengthCap); - gamepad.axes[gamepad.axes_length++] = ApplyAxisDeadzone(data.x_axis); - gamepad.axes[gamepad.axes_length++] = ApplyAxisDeadzone(data.y_axis); -} - -void AddButtonWithAxes(Gamepad& gamepad, ButtonData data) { - AddButton(gamepad, &data); - AddAxes(gamepad, data); -} - -Gamepad GetWebXRGamepad(ParsedInputState& input_state) { - Gamepad gamepad; - gamepad.connected = true; - gamepad.timestamp = base::TimeTicks::Now().since_origin().InMicroseconds(); - - // TODO(https://crbug.com/942201): Get correct ID string once WebXR spec issue - // #550 (https://github.com/immersive-web/webxr/issues/550) is resolved. - CopyToUString(base::UTF8ToUTF16("unknown"), gamepad.id, - base::size(gamepad.id)); - - CopyToUString(base::UTF8ToUTF16("xr-standard"), gamepad.mapping, - base::size(gamepad.mapping)); - - if (input_state.source_state && input_state.source_state->description) { - gamepad.hand = MojoToGamepadHandedness( - input_state.source_state->description->handedness); - } else { - gamepad.hand = GamepadHand::kNone; - } - - // The order of these buttons is dictated by the xr-standard Gamepad mapping. - // Thumbstick is considered the primary 2D input axis, while the touchpad is - // the secondary 2D input axis. - AddButton(gamepad, &input_state.button_data[ButtonName::kSelect]); - AddButtonWithAxes(gamepad, input_state.button_data[ButtonName::kThumbstick]); - AddButton(gamepad, &input_state.button_data[ButtonName::kGrip]); - AddButtonWithAxes(gamepad, input_state.button_data[ButtonName::kTouchpad]); - return gamepad; -} - -// Note that since this is built by polling, and so eventing changes are not -// accounted for here. -std::unordered_map<ButtonName, ButtonData> ParseButtonState( - const WMRInputSourceState& source_state) { - std::unordered_map<ButtonName, ButtonData> button_map; - - ButtonData data = button_map[ButtonName::kSelect]; - data.pressed = source_state.IsSelectPressed(); - data.touched = data.pressed; - data.value = source_state.SelectPressedValue(); - button_map[ButtonName::kSelect] = data; - - data = button_map[ButtonName::kGrip]; - data.pressed = source_state.IsGrasped(); - data.touched = data.pressed; - data.value = data.pressed ? 1.0 : 0.0; - button_map[ButtonName::kGrip] = data; - - if (!source_state.SupportsControllerProperties()) - return button_map; - - data = button_map[ButtonName::kThumbstick]; - data.pressed = source_state.IsThumbstickPressed(); - data.touched = data.pressed; - data.value = data.pressed ? 1.0 : 0.0; - - data.x_axis = source_state.ThumbstickX(); - data.y_axis = source_state.ThumbstickY(); - button_map[ButtonName::kThumbstick] = data; - - data = button_map[ButtonName::kTouchpad]; - data.pressed = source_state.IsTouchpadPressed(); - data.touched = source_state.IsTouchpadTouched(); - data.value = data.pressed ? 1.0 : 0.0; - - // Touchpad must be touched if it is pressed - if (data.pressed && !data.touched) - data.touched = true; - - if (data.touched) { - data.x_axis = source_state.TouchpadX(); - data.y_axis = source_state.TouchpadY(); - } - - button_map[ButtonName::kTouchpad] = data; - - return button_map; -} - GamepadPose GetGamepadPose(const WMRInputLocation& location) { GamepadPose gamepad_pose; @@ -295,6 +163,111 @@ return gamepad_pose; } +mojom::XRGamepadPtr GetWebVRGamepad(ParsedInputState input_state) { + auto gamepad = mojom::XRGamepad::New(); + // This matches the order of button trigger events from Edge. Note that we + // use the polled button state for select here. Voice (which we cannot get + // via polling), lacks enough data to be considered a "Gamepad", and if we + // used eventing the pressed state may be inconsistent. + AddButtonWithAxes(gamepad, input_state.button_data[ButtonName::kThumbstick]); + AddButton(gamepad, &input_state.button_data[ButtonName::kSelect]); + AddButton(gamepad, &input_state.button_data[ButtonName::kGrip]); + AddButton(gamepad, nullptr); // Nothing seems to trigger this button in Edge. + AddButtonWithAxes(gamepad, input_state.button_data[ButtonName::kTouchpad]); + + gamepad->pose = ConvertToVRPose(input_state.gamepad_pose); + gamepad->hand = input_state.source_state->description->handedness; + gamepad->controller_id = input_state.source_state->source_id; + gamepad->can_provide_position = true; + gamepad->can_provide_orientation = true; + gamepad->timestamp = base::TimeTicks::Now(); + + return gamepad; +} + +// Helpers for WebXRGamepad +base::Optional<Gamepad> GetWebXRGamepad(ParsedInputState& input_state) { + device::mojom::XRHandedness handedness = device::mojom::XRHandedness::NONE; + if (input_state.source_state && input_state.source_state->description) + handedness = input_state.source_state->description->handedness; + + // TODO(https://crbug.com/942201): Get correct ID string once WebXR spec issue + // #550 (https://github.com/immersive-web/webxr/issues/550) is resolved. + GamepadBuilder builder("unknown", GamepadBuilder::GamepadMapping::kXRStandard, + handedness); + + builder.SetAxisDeadzone(kDeadzoneMinimum); + + // The order of these buttons is dictated by the xr-standard Gamepad mapping. + // Thumbstick is considered the primary 2D input axis, while the touchpad is + // the secondary 2D input axis. If any of these are missing, map will give + // us a default version, which is fine. + builder.AddButton(input_state.button_data[ButtonName::kSelect]); + builder.AddButton(input_state.button_data[ButtonName::kThumbstick]); + builder.AddButton(input_state.button_data[ButtonName::kGrip]); + builder.AddButton(input_state.button_data[ButtonName::kTouchpad]); + return builder.GetGamepad(); +} + +// Note that since this is built by polling, and so eventing changes are not +// accounted for here. +std::unordered_map<ButtonName, GamepadBuilder::ButtonData> ParseButtonState( + const WMRInputSourceState& source_state) { + std::unordered_map<ButtonName, GamepadBuilder::ButtonData> button_map; + + // Add the select button + GamepadBuilder::ButtonData data = button_map[ButtonName::kSelect]; + data.pressed = source_state.IsSelectPressed(); + data.touched = data.pressed; + data.value = source_state.SelectPressedValue(); + data.has_both_axes = false; + button_map[ButtonName::kSelect] = data; + + // Add the grip button + data = button_map[ButtonName::kGrip]; + data.pressed = source_state.IsGrasped(); + data.touched = data.pressed; + data.value = data.pressed ? 1.0 : 0.0; + data.has_both_axes = false; + button_map[ButtonName::kGrip] = data; + + // Select and grip are the only two required buttons, if we can't get the + // others, we can safely return just them. + if (!source_state.SupportsControllerProperties()) + return button_map; + + // Add the thumbstick + data = button_map[ButtonName::kThumbstick]; + data.pressed = source_state.IsThumbstickPressed(); + data.touched = data.pressed; + data.value = data.pressed ? 1.0 : 0.0; + + data.has_both_axes = true; + data.x_axis = source_state.ThumbstickX(); + data.y_axis = source_state.ThumbstickY(); + button_map[ButtonName::kThumbstick] = data; + + // Add the touchpad + data = button_map[ButtonName::kTouchpad]; + data.pressed = source_state.IsTouchpadPressed(); + data.touched = source_state.IsTouchpadTouched() || data.pressed; + data.value = data.pressed ? 1.0 : 0.0; + + // The Touchpad does have Axes, but if it's not touched, they are 0. + data.has_both_axes = true; + if (data.touched) { + data.x_axis = source_state.TouchpadX(); + data.y_axis = source_state.TouchpadY(); + } else { + data.x_axis = 0; + data.y_axis = 0; + } + + button_map[ButtonName::kTouchpad] = data; + + return button_map; +} + gfx::Transform CreateTransform(GamepadVector position, GamepadQuaternion rotation) { gfx::DecomposedTransform decomposed_transform;
diff --git a/device/vr/windows_mixed_reality/mixed_reality_input_helper.h b/device/vr/windows_mixed_reality/mixed_reality_input_helper.h index c82d24c..723ebf5 100644 --- a/device/vr/windows_mixed_reality/mixed_reality_input_helper.h +++ b/device/vr/windows_mixed_reality/mixed_reality_input_helper.h
@@ -17,16 +17,9 @@ #include "device/gamepad/public/cpp/gamepads.h" #include "device/vr/public/mojom/isolated_xr_service.mojom.h" #include "device/vr/public/mojom/vr_service.mojom.h" +#include "device/vr/util/gamepad_builder.h" namespace device { -struct ButtonData { - bool pressed; - bool touched; - double value; - - double x_axis; - double y_axis; -}; enum class ButtonName { kSelect, @@ -37,7 +30,7 @@ struct ParsedInputState { mojom::XRInputSourceStatePtr source_state; - std::unordered_map<ButtonName, ButtonData> button_data; + std::unordered_map<ButtonName, GamepadBuilder::ButtonData> button_data; GamepadPose gamepad_pose; ParsedInputState(); ~ParsedInputState(); @@ -97,6 +90,7 @@ DISALLOW_COPY_AND_ASSIGN(MixedRealityInputHelper); }; + } // namespace device #endif // DEVICE_VR_WINDOWS_MIXED_REALITY_MIXED_REALITY_INPUT_HELPER_H_
diff --git a/docs/mojo_and_services.md b/docs/mojo_and_services.md index 63dc18b..17a2412 100644 --- a/docs/mojo_and_services.md +++ b/docs/mojo_and_services.md
@@ -405,7 +405,7 @@ int32_t divisor, DivideCallback callback) { // Respond with the quotient! - callback.Run(dividend / divisor); + std::move(callback).Run(dividend / divisor); } } // namespace math @@ -525,7 +525,7 @@ ``` And don't forget to add a GN dependency from -`//chrome/app:packaged_service_manifests` onto +[`//chrome/app:chrome_packaged_service_manifests`](https://cs.chromium.org/chromium/src/chrome/app/BUILD.gn?l=564&rcl=a77d5ba9c4621cfe14e7e1cd03bbae16904f269e) onto `//chrome/services/math/public/cpp:manifest`! We're almost done with service setup. The last step is to teach Chromium (and @@ -634,6 +634,12 @@ divider->Divide( 42, 6, base::BindOnce([](int32_t quotient) { LOG(INFO) << quotient; })); ``` +*** aside +NOTE: To ensure the execution of the response callback, the DividerPtr +object must be kept alive (see +[this section](/mojo/public/cpp/bindings/README.md#A-Note-About-Endpoint-Lifetime-and-Callbacks) +and [this note from an earlier section](#sending-a-message)). +*** This should successfully spawn a new process to run the `math` service if it's not already running, then ask it to do a division, and ultimately log the result
diff --git a/docs/speed/perf_lab_platforms.md b/docs/speed/perf_lab_platforms.md index 8eda03d4..945557a 100644 --- a/docs/speed/perf_lab_platforms.md +++ b/docs/speed/perf_lab_platforms.md
@@ -6,6 +6,7 @@ ## Android + * [android-builder-perf](https://ci.chromium.org/p/chrome/builders/luci.chrome.ci/android-builder-perf): Static analysis of 32-bit ARM Android build products. * [android-go-perf](https://ci.chromium.org/p/chrome/builders/luci.chrome.ci/android-go-perf): Android O (gobo). * [android-go_webview-perf](https://ci.chromium.org/p/chrome/builders/luci.chrome.ci/android-go_webview-perf): Android OPM1.171019.021 (gobo). * [Android Nexus5 Perf](https://ci.chromium.org/p/chrome/builders/luci.chrome.ci/Android%20Nexus5%20Perf): Android KOT49H. @@ -13,6 +14,10 @@ * [Android Nexus5X WebView Perf](https://ci.chromium.org/p/chrome/builders/luci.chrome.ci/Android%20Nexus5X%20WebView%20Perf): Android AOSP MOB30K. * [Android Nexus6 WebView Perf](https://ci.chromium.org/p/chrome/builders/luci.chrome.ci/Android%20Nexus6%20WebView%20Perf): Android AOSP MOB30K. +## Android_Arm64 + + * [android_arm64-builder-perf](https://ci.chromium.org/p/chrome/builders/luci.chrome.ci/android_arm64-builder-perf): Static analysis of 64-bit ARM Android build products.. + ## Linux * [linux-perf](https://ci.chromium.org/p/chrome/builders/luci.chrome.ci/linux-perf): Ubuntu-14.04, 8 core, NVIDIA Quadro P400.
diff --git a/extensions/browser/api/feedback_private/feedback_private_api.cc b/extensions/browser/api/feedback_private/feedback_private_api.cc index c80b292..e8befae 100644 --- a/extensions/browser/api/feedback_private/feedback_private_api.cc +++ b/extensions/browser/api/feedback_private/feedback_private_api.cc
@@ -215,7 +215,6 @@ ExtensionFunction::ResponseAction FeedbackPrivateGetSystemInformationFunction::Run() { - VLOG(1) << "Fetching system logs started."; // Self-deleting object. system_logs::SystemLogsFetcher* fetcher = ExtensionsAPIClient::Get() @@ -229,7 +228,6 @@ void FeedbackPrivateGetSystemInformationFunction::OnCompleted( std::unique_ptr<system_logs::SystemLogsResponse> sys_info) { - VLOG(1) << "Received system logs."; SystemInformationList sys_info_list; if (sys_info) { sys_info_list.reserve(sys_info->size()); @@ -294,7 +292,6 @@ #endif // defined(OS_CHROMEOS) ExtensionFunction::ResponseAction FeedbackPrivateSendFeedbackFunction::Run() { - VLOG(1) << "Sending feedback report started."; std::unique_ptr<feedback_private::SendFeedback::Params> params( feedback_private::SendFeedback::Params::Create(*args_)); EXTENSION_FUNCTION_VALIDATE(params); @@ -368,7 +365,6 @@ bool send_histograms, bool send_bluetooth_logs, scoped_refptr<feedback::FeedbackData> feedback_data) { - VLOG(1) << "All logs have been fetched. Proceeding with sending the report."; feedback_data->CompressSystemInfo();
diff --git a/extensions/browser/api/feedback_private/feedback_service.cc b/extensions/browser/api/feedback_private/feedback_service.cc index 8775dfc..0df3fea 100644 --- a/extensions/browser/api/feedback_private/feedback_service.cc +++ b/extensions/browser/api/feedback_private/feedback_service.cc
@@ -40,7 +40,6 @@ feedback_data->set_user_agent(ExtensionsBrowserClient::Get()->GetUserAgent()); if (!feedback_data->attached_file_uuid().empty()) { - VLOG(1) << "Attaching file to the report."; // Self-deleting object. BlobReader* attached_file_reader = new BlobReader(browser_context_, feedback_data->attached_file_uuid(), @@ -50,7 +49,6 @@ } if (!feedback_data->screenshot_uuid().empty()) { - VLOG(1) << "Attaching screenshot to the report."; // Self-deleting object. BlobReader* screenshot_reader = new BlobReader(browser_context_, feedback_data->screenshot_uuid(), @@ -101,7 +99,6 @@ const bool screenshot_completed = feedback_data->screenshot_uuid().empty(); if (screenshot_completed && attached_file_completed) { - VLOG(1) << "Attachments are ready."; #if defined(OS_CHROMEOS) // Send feedback to Assistant server if triggered from Google Assistant. if (feedback_data->from_assistant()) {
diff --git a/extensions/browser/api/networking_private/networking_private_chromeos.cc b/extensions/browser/api/networking_private/networking_private_chromeos.cc index 61fe801..9b89e85 100644 --- a/extensions/browser/api/networking_private/networking_private_chromeos.cc +++ b/extensions/browser/api/networking_private/networking_private_chromeos.cc
@@ -515,33 +515,9 @@ return; } - std::string carrier(specified_carrier); - if (carrier.empty()) { - const chromeos::DeviceState* device = - GetStateHandler()->GetDeviceState(network->device_path()); - if (device) - carrier = device->carrier(); - } - if (carrier != shill::kCarrierSprint) { - // Only Sprint is directly activated. For other carriers, show the - // account details page. - if (ui_delegate()) - ui_delegate()->ShowAccountDetails(guid); - success_callback.Run(); - return; - } - - if (!network->RequiresActivation()) { - // If no activation is required, show the account details page. - if (ui_delegate()) - ui_delegate()->ShowAccountDetails(guid); - success_callback.Run(); - return; - } - - NetworkHandler::Get()->network_activation_handler()->Activate( - network->path(), carrier, success_callback, - base::Bind(&NetworkHandlerFailureCallback, failure_callback)); + if (ui_delegate()) + ui_delegate()->ShowAccountDetails(guid); + success_callback.Run(); } void NetworkingPrivateChromeOS::SetWifiTDLSEnabledState(
diff --git a/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc b/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc index daa98f20..eb30a21f 100644 --- a/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc +++ b/extensions/browser/api/networking_private/networking_private_chromeos_unittest.cc
@@ -199,8 +199,6 @@ // Add a Cellular GSM Device. device_test_->AddDevice(kCellularDevicePath, shill::kTypeCellular, "stub_cellular_device1"); - SetDeviceProperty(kCellularDevicePath, shill::kCarrierProperty, - base::Value("Cellular1_Carrier")); base::DictionaryValue home_provider; home_provider.SetString("name", "Cellular1_Provider"); @@ -1087,7 +1085,6 @@ DictionaryBuilder() .Set("AllowRoaming", false) .Set("AutoConnect", true) - .Set("Carrier", "Cellular1_Carrier") .Set("Family", "GSM") .Set("HomeProvider", DictionaryBuilder() .Set("Code", "000000") @@ -1135,7 +1132,6 @@ DictionaryBuilder() .Set("AllowRoaming", false) .Set("AutoConnect", true) - .Set("Carrier", "Cellular1_Carrier") .Set("ESN", "test_esn") .Set("Family", "GSM") .Set("HomeProvider", DictionaryBuilder()
diff --git a/extensions/browser/url_loader_factory_manager.cc b/extensions/browser/url_loader_factory_manager.cc index fc242e5..0bac1fb 100644 --- a/extensions/browser/url_loader_factory_manager.cc +++ b/extensions/browser/url_loader_factory_manager.cc
@@ -77,18 +77,15 @@ "109A37B889E7C8AEA7B0103559C3EB6AF73B7832", "11AF49711EE340C366CC820C98E3CA2AD7D7DE07", "16A81AEA09A67B03F7AEA5B957D24A4095E764BE", - "177508B365CBF1610CC2B53707749D79272F2F0B", "1AB9CC404876117F49135E67BAD813F935AAE9BA", "1B9251EF3EDD5A2B2872B168406F36FB18C72F37", "1CDDF7436E5F891E1D5E37164F7EB992AECA0E2D", "1DB115A4344B58C0B7CC1595662E86C9E92C0848", "1DF1ED7172DC5B9FF337909ED5C32DED74084825", "1E37F1A19C1C528E616637B105CFC4838ECF52B4", - "2294A18C41EFDE51AE8E4A9AE44C2CF65B9059FE", "24B24CBA3D4B395503F9B0D6286E55E0FDDE4D69", "260871EABEDE6F2D07D0D9450096093EDAFCBF34", "28A9EDFD65BC27B5048E516AA7165339F5ACBB30", - "29427534E31BB1820714C7CAEDF9C54B47BE154F", "2A661509BCE9F8384B00CFC96807597D71DFE94C", "2AA94E2D3F4DA33F0D3BCF5DD48F69B8BDB26F52", "2C116B242B7425D359E188AB053B3F88DB78F78D", @@ -96,7 +93,6 @@ "30E95BD31B11118CE488EB4FC5FF7135E0C59425", "31E6100DC7B4EAB4ABF6CA2A4E191D3945D3C731", "3230014EA01150A27C1887B700E019B27A6DBA08", - "3334952C8387B357A41DD8349D39AD9E7C423943", "34FB670464B5F16EF5ABD6CD53E26030E97C26B3", "360D8A88545D0C21CBDE2B55EA9E4E4285282B4C", "3787567233ED6BACC4FC05387BE30E03434CE4CC", @@ -127,6 +123,7 @@ "50DDD8734521B61564FCE273F8E60547F88BBCBE", "52865B2087D0ABCD195A83DFD4BD041A3B4EBC34", "52C94AC7680C3A03CCB6EA31445DD42BD0D5CA8E", + "537D26CC4EC6819A6CAAE8B8B5957F5ECFA7A44B", "5741A650C3AAA935154C57681310805B5FC121C3", "58BCF05A42C8ECED4E6D76F51E2E1A64AC4F7E7C", "5B6DC4EEFEDF06CB2BF439E4607A5BAF6E45CCA1", @@ -147,7 +144,6 @@ "71351EAA5C16350EC5A86C23D7A288317309E53D", "71CB78C3334D5122E7F23C8525AD24100CDE7D4A", "71EE66C0F71CD89BEE340F8568A44101D4C3A9A7", - "7499EF1AEC6F4D70E5E7DA2568A02E8ABC1A6447", "7527942941BFF13D66B46E7A2A56FDBA873FB9E6", "77D83E0A4157A0E77B51AD60BAB69A346CD4FEA3", "7879DB88205D880B64D55E51B9726E1D12F7261F", @@ -168,7 +164,6 @@ "8A0634388BCBB6D073E1C97B14C024396ED32D12", "8CDD303D6C341D9CAB16713C3CD7587B90A7A84A", "8CE6227B4E53DF42FF93B24F49D15EDE31E97E79", - "8E14F3080EF0D8AB5006544894610C2E0315B317", "905C225238FB337834F193DF48550D721445B438", "9233B7F0B98FCC181ED43AFAF58056C4EDCED162", "92F2B155490417F0797849C81292E3986EBE6811", @@ -186,7 +181,6 @@ "A04F08A772F1C83B7A14ED29788ACA4F000BBE05", "A059797AECB77D24DEB248C3413D99B0D3BF9A8C", "A07DA0EDB967D027E3B220208AD085FDC44C3231", - "A30E526CF62131BFBFD7CD9B56253A8F3F171777", "A3660FA31A0DBF07C9F80D5342FF215DBC962719", "A42FE007E0651EAC159EE1B393586AFFFE065DC2", "A6057397EDC4E6CF25BC3A142F866ACC653B1CF1", @@ -206,19 +200,16 @@ "C0A30989F3717CE5B1B2FE462797951EA6D3922A", "C4A81852B9ACE6CE02DAB58BB77BDA0AD75716EC", "C5539F4EBECABA792CC40D03A56144AAD3BF9D19", - "C5BCB9E2E47C3F6FD3F7F83ED982872F77852BA7", "C86D546CA47034163C12DC2C912910C3A12C3B07", "C940F83135D9612865F4A44391DDDFE3B7BE1393", "CA89BD35059845F2DB4B4398FD339B9F210E9337", "CC32A0FD1D88B403308EACBE4DE3CA5AC54B93EB", - "CC74B2408753932B5D49C81EC073E3E4CA766EE6", "CD8AF9C47DDE6327F8D9A3EFA81F34C6B6C26EBB", "CF40F6289951CBFA3B83B792EFA774E2EA06E4C0", "D0537B1BADCE856227CE76E31B3772F6B68F653C", "D347F78F32567E90BC32D9C16B085254EA269590", "D572BE31227F6D0BE95B9430BE2D5F21D7D9CF9A", "D7C3879A8898618E3A23B0E6BFB6A38D01606246", - "D9A97CD75380C697C65D37512E53DBECDFA45FB9", "DC39837AC518B832FCB2D2DC1CE8BA148F54758E", "DC88B4C9E547F3E321B3E64CCDBD4B698116D2F4", "DDA21167F058A65D878DF84C3CF3FCC60B053E80",
diff --git a/extensions/common/api/networking_onc.idl b/extensions/common/api/networking_onc.idl index 1e20eb05..9776b3ee 100644 --- a/extensions/common/api/networking_onc.idl +++ b/extensions/common/api/networking_onc.idl
@@ -425,8 +425,6 @@ ActivationStateType? ActivationState; // Whether roaming is allowed for the network. boolean? AllowRoaming; - // The name of the carrier for which the cellular device is configured. - DOMString? Carrier; // Cellular device technology family - <code>CDMA</code> or // <code>GSM</code>. DOMString? Family; @@ -477,8 +475,6 @@ ActivationStateType? ActivationState; // See $(ref:CellularProperties.AllowRoaming). boolean? AllowRoaming; - // See $(ref:CellularProperties.Carrier). - ManagedDOMString? Carrier; // See $(ref:CellularProperties.Family). DOMString? Family; // See $(ref:CellularProperties.FirmwareRevision).
diff --git a/extensions/common/api/networking_private.idl b/extensions/common/api/networking_private.idl index 1b808be..94d1085 100644 --- a/extensions/common/api/networking_private.idl +++ b/extensions/common/api/networking_private.idl
@@ -515,7 +515,6 @@ DOMString? ActivationType; ActivationStateType? ActivationState; boolean? AllowRoaming; - DOMString? Carrier; DOMString? ESN; DOMString? Family; DOMString? FirmwareRevision; @@ -549,7 +548,6 @@ DOMString? ActivationType; ActivationStateType? ActivationState; boolean? AllowRoaming; - ManagedDOMString? Carrier; DOMString? ESN; DOMString? Family; DOMString? FirmwareRevision;
diff --git a/gpu/vulkan/vulkan_swap_chain.cc b/gpu/vulkan/vulkan_swap_chain.cc index 2e1e43d..9047a47 100644 --- a/gpu/vulkan/vulkan_swap_chain.cc +++ b/gpu/vulkan/vulkan_swap_chain.cc
@@ -340,7 +340,6 @@ begin_write_semaphore_ = VK_NULL_HANDLE; end_write_semaphore_ = VK_NULL_HANDLE; images_.clear(); - fence_helper->GenerateCleanupFence(); } void VulkanSwapChain::BeginWriteCurrentImage(VkImage* image,
diff --git a/ios/chrome/browser/passwords/ios_chrome_save_password_infobar_delegate.h b/ios/chrome/browser/passwords/ios_chrome_save_password_infobar_delegate.h index 913498d..3961a02 100644 --- a/ios/chrome/browser/passwords/ios_chrome_save_password_infobar_delegate.h +++ b/ios/chrome/browser/passwords/ios_chrome_save_password_infobar_delegate.h
@@ -48,6 +48,10 @@ // created. bool IsPasswordUpdate() const; + // true if the current set of credentials has already been saved at the moment + // the InfobarModal is created. + bool IsCurrentPasswordSaved() const; + // The title for the InfobarModal being presented. NSString* GetInfobarModalTitleText() const; @@ -59,6 +63,10 @@ // created. bool password_update_ = false; + // true if the current set of credentials has already been saved at the moment + // the InfobarModal is created. + bool current_password_saved = false; + DISALLOW_COPY_AND_ASSIGN(IOSChromeSavePasswordInfoBarDelegate); };
diff --git a/ios/chrome/browser/passwords/ios_chrome_save_password_infobar_delegate.mm b/ios/chrome/browser/passwords/ios_chrome_save_password_infobar_delegate.mm index 41e74e2..c94abb9 100644 --- a/ios/chrome/browser/passwords/ios_chrome_save_password_infobar_delegate.mm +++ b/ios/chrome/browser/passwords/ios_chrome_save_password_infobar_delegate.mm
@@ -60,9 +60,7 @@ NSString* IOSChromeSavePasswordInfoBarDelegate::GetInfobarModalTitleText() const { DCHECK(IsInfobarUIRebootEnabled()); - return l10n_util::GetNSString( - IsPasswordUpdate() ? IDS_IOS_PASSWORD_MANAGER_UPDATE_PASSWORD_TITLE - : IDS_IOS_PASSWORD_MANAGER_SAVE_PASSWORD_TITLE); + return l10n_util::GetNSString(IDS_IOS_PASSWORD_MANAGER_SAVE_PASSWORD_TITLE); } base::string16 IOSChromeSavePasswordInfoBarDelegate::GetButtonLabel( @@ -95,6 +93,7 @@ form_to_save()->Save(); set_infobar_response(password_manager::metrics_util::CLICKED_SAVE); password_update_ = true; + current_password_saved = true; return true; } @@ -125,3 +124,8 @@ DCHECK(IsInfobarUIRebootEnabled()); return password_update_; } + +bool IOSChromeSavePasswordInfoBarDelegate::IsCurrentPasswordSaved() const { + DCHECK(IsInfobarUIRebootEnabled()); + return current_password_saved; +}
diff --git a/ios/chrome/browser/ui/infobars/coordinators/infobar_coordinator.mm b/ios/chrome/browser/ui/infobars/coordinators/infobar_coordinator.mm index 46dc91e..4be7708 100644 --- a/ios/chrome/browser/ui/infobars/coordinators/infobar_coordinator.mm +++ b/ios/chrome/browser/ui/infobars/coordinators/infobar_coordinator.mm
@@ -207,14 +207,31 @@ // Deselect infobar badge in parallel with modal dismissal. [self.badgeDelegate infobarModalWillDismiss]; __weak __typeof(self) weakSelf = self; - [self.baseViewController - dismissViewControllerAnimated:animated - completion:^{ - weakSelf.modalTransitionDriver = nil; - [weakSelf infobarWasDismissed]; - if (completion) - completion(); - }]; + ProceduralBlock modalCleanUp = ^{ + weakSelf.modalTransitionDriver = nil; + [weakSelf infobarWasDismissed]; + }; + + // If the Modal is being presented by the Banner, call dismiss on it. + // This way the modal dismissal will animate correctly and the completion + // block cleans up the banner correctly. + if (self.bannerViewController.presentedViewController) { + [self.bannerViewController + dismissViewControllerAnimated:animated + completion:^{ + modalCleanUp(); + [weakSelf + dismissInfobarBannerAnimated:NO + completion:completion]; + }]; + } else { + [self.baseViewController dismissViewControllerAnimated:animated + completion:^{ + modalCleanUp(); + if (completion) + completion(); + }]; + } } else { if (completion) completion();
diff --git a/ios/chrome/browser/ui/infobars/coordinators/infobar_password_coordinator.mm b/ios/chrome/browser/ui/infobars/coordinators/infobar_password_coordinator.mm index cfb41c31..c465f83 100644 --- a/ios/chrome/browser/ui/infobars/coordinators/infobar_password_coordinator.mm +++ b/ios/chrome/browser/ui/infobars/coordinators/infobar_password_coordinator.mm
@@ -104,6 +104,8 @@ base::SysUTF16ToNSString(self.passwordInfoBarDelegate->GetButtonLabel( ConfirmInfoBarDelegate::BUTTON_CANCEL)); self.modalViewController.URL = self.passwordInfoBarDelegate->GetURLHostText(); + self.modalViewController.currentCredentialsSaved = + self.passwordInfoBarDelegate->IsCurrentPasswordSaved(); } - (void)dismissBannerWhenInteractionIsFinished {
diff --git a/ios/chrome/browser/ui/infobars/modals/infobar_password_table_view_controller.h b/ios/chrome/browser/ui/infobars/modals/infobar_password_table_view_controller.h index 1dff4c9..24da1a75 100644 --- a/ios/chrome/browser/ui/infobars/modals/infobar_password_table_view_controller.h +++ b/ios/chrome/browser/ui/infobars/modals/infobar_password_table_view_controller.h
@@ -30,6 +30,8 @@ @property(nonatomic, copy) NSString* saveButtonText; // The text used for the cancel button. @property(nonatomic, copy) NSString* cancelButtonText; +// YES if the current set of credentials has already been saved. +@property(nonatomic, assign) BOOL currentCredentialsSaved; @end
diff --git a/ios/chrome/browser/ui/infobars/modals/infobar_password_table_view_controller.mm b/ios/chrome/browser/ui/infobars/modals/infobar_password_table_view_controller.mm index b31a9efa..2412499 100644 --- a/ios/chrome/browser/ui/infobars/modals/infobar_password_table_view_controller.mm +++ b/ios/chrome/browser/ui/infobars/modals/infobar_password_table_view_controller.mm
@@ -14,7 +14,7 @@ #import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h" #import "ios/chrome/browser/ui/util/uikit_ui_util.h" #include "ios/chrome/grit/ios_strings.h" -#include "ui/base/l10n/l10n_util_mac.h" +#include "ui/base/l10n/l10n_util.h" #if !defined(__has_feature) || !__has_feature(objc_arc) #error "This file requires ARC support." @@ -47,6 +47,8 @@ // Item that holds the cancel Button for this Infobar. e.g. "Never Save for this // site". @property(nonatomic, strong) TableViewTextButtonItem* cancelInfobarItem; +// Username at the time the InfobarModal is presented. +@property(nonatomic, copy) NSString* originalUsername; @end @implementation InfobarPasswordTableViewController @@ -102,12 +104,13 @@ URLItem.textFieldValue = self.URL; [model addItem:URLItem toSectionWithIdentifier:SectionIdentifierContent]; + self.originalUsername = self.username; self.usernameItem = [[TableViewTextEditItem alloc] initWithType:ItemTypeUsername]; self.usernameItem.textFieldName = l10n_util::GetNSString(IDS_IOS_SHOW_PASSWORD_VIEW_USERNAME); self.usernameItem.textFieldValue = self.username; - self.usernameItem.textFieldEnabled = YES; + self.usernameItem.textFieldEnabled = !self.currentCredentialsSaved; self.usernameItem.autoCapitalizationType = UITextAutocapitalizationTypeNone; [model addItem:self.usernameItem toSectionWithIdentifier:SectionIdentifierContent]; @@ -125,6 +128,7 @@ self.saveCredentialsItem.textAlignment = NSTextAlignmentNatural; self.saveCredentialsItem.text = self.detailsTextMessage; self.saveCredentialsItem.buttonText = self.saveButtonText; + self.saveCredentialsItem.enabled = !self.currentCredentialsSaved; self.saveCredentialsItem.disableButtonIntrinsicWidth = YES; [model addItem:self.saveCredentialsItem toSectionWithIdentifier:SectionIdentifierContent]; @@ -194,6 +198,18 @@ self.saveCredentialsItem.enabled = newButtonState; [self reconfigureCellsForItems:@[ self.saveCredentialsItem ]]; } + + // TODO(crbug.com/945478):Ideally the InfobarDelegate should update the button + // text. Once we have a consumer protocol we should be able to create a + // delegate that asks the InfobarDelegate for the correct text. + NSString* buttonText = + [self.usernameItem.textFieldValue isEqualToString:self.originalUsername] + ? self.saveButtonText + : l10n_util::GetNSString(IDS_IOS_PASSWORD_MANAGER_SAVE_BUTTON); + if (![self.saveCredentialsItem.buttonText isEqualToString:buttonText]) { + self.saveCredentialsItem.buttonText = buttonText; + [self reconfigureCellsForItems:@[ self.saveCredentialsItem ]]; + } } - (void)dismissInfobarModal:(UIButton*)sender {
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_cell.mm b/ios/chrome/browser/ui/table_view/cells/table_view_cell.mm index bdba2de..30adfce 100644 --- a/ios/chrome/browser/ui/table_view/cells/table_view_cell.mm +++ b/ios/chrome/browser/ui/table_view/cells/table_view_cell.mm
@@ -13,8 +13,8 @@ #endif namespace { -const int kTableViewCustomSeparatorColor = 0xE8EAED; -const CGFloat kTableViewCustomSeparatorHeight = 0.5; +const int kTableViewCustomSeparatorColor = 0xDADDE2; +const CGFloat kTableViewCustomSeparatorHeight = 0.75; } // namespace @interface TableViewCell ()
diff --git a/ios/web/navigation/resources/restore_session.html b/ios/web/navigation/resources/restore_session.html index 019c9f3..8ee2e20 100644 --- a/ios/web/navigation/resources/restore_session.html +++ b/ios/web/navigation/resources/restore_session.html
@@ -99,8 +99,16 @@ getRestoreURL(sessionHistoryObject.urls[i] || "about:blank")); } + // iOS12.2 added a throttling mechanism where previous pushStates may + // not immediately be available. Set a 10ms interval delay until + // history.length reaches sessionHistory.length. var currentItemOffset = parseInt(sessionHistoryObject.offset); - history.go(currentItemOffset); + var goWhenReady = setInterval(() => { + if (history.length == sessionHistoryObject.urls.length) { + history.go(currentItemOffset); + window.clearInterval(goWhenReady); + } + }, 10); } catch (e) { handleError(e.name + ": " + e.message + " raw session history: " + sessionHistory); }
diff --git a/ios/web/navigation/wk_based_navigation_manager_impl_unittest.mm b/ios/web/navigation/wk_based_navigation_manager_impl_unittest.mm index a1eece3..4eb36c0 100644 --- a/ios/web/navigation/wk_based_navigation_manager_impl_unittest.mm +++ b/ios/web/navigation/wk_based_navigation_manager_impl_unittest.mm
@@ -115,10 +115,7 @@ // Returns the value of the "#session=" URL hash component from |url|. static std::string ExtractRestoredSession(const GURL& url) { - std::string decoded = net::UnescapeURLComponent( - url.ref(), net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS | - net::UnescapeRule::SPACES | - net::UnescapeRule::PATH_SEPARATORS); + std::string decoded = net::UnescapeBinaryURLComponent(url.ref()); return decoded.substr( strlen(wk_navigation_util::kRestoreSessionSessionHashPrefix)); }
diff --git a/ios/web/navigation/wk_navigation_util.mm b/ios/web/navigation/wk_navigation_util.mm index bc8b0e4..05b7a0e 100644 --- a/ios/web/navigation/wk_navigation_util.mm +++ b/ios/web/navigation/wk_navigation_util.mm
@@ -25,9 +25,9 @@ // Session restoration algorithms uses pushState calls to restore back forward // navigation list. WKWebView does not allow pushing more than 100 items per -// 30 seconds. Limiting max session size to 49 will allow web pages to use push +// 30 seconds. Limiting max session size to 75 will allow web pages to use push // state calls. -const int kMaxSessionSize = 49; +const int kMaxSessionSize = 75; const char kRestoreSessionSessionHashPrefix[] = "session="; const char kRestoreSessionTargetUrlHashPrefix[] = "targetUrl="; @@ -178,11 +178,7 @@ if (success) { std::string encoded_target_url = restore_session_url.ref().substr( strlen(kRestoreSessionTargetUrlHashPrefix)); - net::UnescapeRule::Type unescape_rules = - net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS | - net::UnescapeRule::SPACES | net::UnescapeRule::PATH_SEPARATORS; - *target_url = - GURL(net::UnescapeURLComponent(encoded_target_url, unescape_rules)); + *target_url = GURL(net::UnescapeBinaryURLComponent(encoded_target_url)); } return success;
diff --git a/ios/web/navigation/wk_navigation_util_unittest.mm b/ios/web/navigation/wk_navigation_util_unittest.mm index ae69b496..b92d6bb 100644 --- a/ios/web/navigation/wk_navigation_util_unittest.mm +++ b/ios/web/navigation/wk_navigation_util_unittest.mm
@@ -48,14 +48,13 @@ NSString* fragment = net::NSURLWithGURL(restore_session_url).fragment; NSString* encoded_session = [fragment substringFromIndex:strlen(kRestoreSessionSessionHashPrefix)]; - std::string session_json = net::UnescapeURLComponent( - base::SysNSStringToUTF8(encoded_session), - net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS); - + std::string session_json = + net::UnescapeBinaryURLComponent(base::SysNSStringToUTF8(encoded_session)); return base::JSONReader::ReadAndReturnValueWithError(session_json, base::JSON_PARSE_RFC); } -} + +} // namespace typedef PlatformTest WKNavigationUtilTest; @@ -84,11 +83,8 @@ ASSERT_EQ(0, first_index); ASSERT_TRUE(IsRestoreSessionUrl(restore_session_url)); - net::UnescapeRule::Type unescape_rules = - net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS | - net::UnescapeRule::SPACES | net::UnescapeRule::PATH_SEPARATORS; std::string session_json = - net::UnescapeURLComponent(restore_session_url.ref(), unescape_rules); + net::UnescapeBinaryURLComponent(restore_session_url.ref()); EXPECT_EQ("session={\"offset\":-2,\"titles\":[\"Test Website 0\",\"\",\"\"]," "\"urls\":[\"http://www.0.com/\",\"http://www.1.com/\"," @@ -157,14 +153,14 @@ ASSERT_EQ(static_cast<size_t>(kMaxSessionSize), titles_value->GetList().size()); ASSERT_EQ("Test0", titles_value->GetList()[0].GetString()); - ASSERT_EQ("Test48", titles_value->GetList()[kMaxSessionSize - 1].GetString()); + ASSERT_EQ("Test74", titles_value->GetList()[kMaxSessionSize - 1].GetString()); base::Value* urls_value = value_with_error.value->FindKey("urls"); ASSERT_TRUE(urls_value); ASSERT_TRUE(urls_value->is_list()); ASSERT_EQ(static_cast<size_t>(kMaxSessionSize), urls_value->GetList().size()); - ASSERT_EQ("http:%2F%2Fwww.0.com%2F", urls_value->GetList()[0].GetString()); - ASSERT_EQ("http:%2F%2Fwww.48.com%2F", + ASSERT_EQ("http://www.0.com/", urls_value->GetList()[0].GetString()); + ASSERT_EQ("http://www.74.com/", urls_value->GetList()[kMaxSessionSize - 1].GetString()); // Verify the offset is correct. @@ -185,7 +181,7 @@ CreateRestoreSessionUrl( /*last_committed_item_index=*/kItemCount - 1, items, &restore_session_url, &first_index); - ASSERT_EQ(98, first_index); + ASSERT_EQ(150, first_index); ASSERT_TRUE(IsRestoreSessionUrl(restore_session_url)); // Extract session JSON from restoration URL. @@ -200,16 +196,16 @@ ASSERT_TRUE(titles_value->is_list()); ASSERT_EQ(static_cast<size_t>(kMaxSessionSize), titles_value->GetList().size()); - ASSERT_EQ("Test98", titles_value->GetList()[0].GetString()); - ASSERT_EQ("Test146", + ASSERT_EQ("Test150", titles_value->GetList()[0].GetString()); + ASSERT_EQ("Test224", titles_value->GetList()[kMaxSessionSize - 1].GetString()); base::Value* urls_value = value_with_error.value->FindKey("urls"); ASSERT_TRUE(urls_value); ASSERT_TRUE(urls_value->is_list()); ASSERT_EQ(static_cast<size_t>(kMaxSessionSize), urls_value->GetList().size()); - ASSERT_EQ("http:%2F%2Fwww.98.com%2F", urls_value->GetList()[0].GetString()); - ASSERT_EQ("http:%2F%2Fwww.146.com%2F", + ASSERT_EQ("http://www.150.com/", urls_value->GetList()[0].GetString()); + ASSERT_EQ("http://www.224.com/", urls_value->GetList()[kMaxSessionSize - 1].GetString()); // Verify the offset is correct. @@ -230,7 +226,7 @@ CreateRestoreSessionUrl( /*last_committed_item_index=*/kMaxSessionSize, items, &restore_session_url, &first_index); - ASSERT_EQ(25, first_index); + ASSERT_EQ(38, first_index); ASSERT_TRUE(IsRestoreSessionUrl(restore_session_url)); // Extract session JSON from restoration URL. @@ -245,15 +241,16 @@ ASSERT_TRUE(titles_value->is_list()); ASSERT_EQ(static_cast<size_t>(kMaxSessionSize), titles_value->GetList().size()); - ASSERT_EQ("Test25", titles_value->GetList()[0].GetString()); - ASSERT_EQ("Test73", titles_value->GetList()[kMaxSessionSize - 1].GetString()); + ASSERT_EQ("Test38", titles_value->GetList()[0].GetString()); + ASSERT_EQ("Test112", + titles_value->GetList()[kMaxSessionSize - 1].GetString()); base::Value* urls_value = value_with_error.value->FindKey("urls"); ASSERT_TRUE(urls_value); ASSERT_TRUE(urls_value->is_list()); ASSERT_EQ(static_cast<size_t>(kMaxSessionSize), urls_value->GetList().size()); - ASSERT_EQ("http:%2F%2Fwww.25.com%2F", urls_value->GetList()[0].GetString()); - ASSERT_EQ("http:%2F%2Fwww.73.com%2F", + ASSERT_EQ("http://www.38.com/", urls_value->GetList()[0].GetString()); + ASSERT_EQ("http://www.112.com/", urls_value->GetList()[kMaxSessionSize - 1].GetString()); // Verify the offset is correct.
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm index e7bd2065..3540ecd9 100644 --- a/ios/web/web_state/ui/crw_web_controller.mm +++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -1970,6 +1970,14 @@ rendererInitiated:NO placeholderNavigation:IsPlaceholderUrl(navigationURL)]; + // Disable |allowsBackForwardNavigationGestures| during restore. Otherwise, + // WebKit will trigger a snapshot for each (blank) page, and quickly + // overload system memory. + if (web::GetWebClient()->IsSlimNavigationManagerEnabled() && + self.navigationManagerImpl->IsRestoreSessionInProgress()) { + _webView.allowsBackForwardNavigationGestures = NO; + } + WKNavigation* navigation = nil; if (navigationURL.SchemeIsFile() && web::GetWebClient()->IsAppSpecificURL(virtualURL)) { @@ -2192,6 +2200,15 @@ if (_loadPhase == web::PAGE_LOADED) return; + // Restore allowsBackForwardNavigationGestures once restoration is complete. + if (web::GetWebClient()->IsSlimNavigationManagerEnabled() && + !self.navigationManagerImpl->IsRestoreSessionInProgress()) { + if (_webView.allowsBackForwardNavigationGestures != + _allowsBackForwardNavigationGestures) + _webView.allowsBackForwardNavigationGestures = + _allowsBackForwardNavigationGestures; + } + BOOL success = !context || !context->GetError(); [self loadCompleteWithSuccess:success forContext:context]; }
diff --git a/ios/web/web_state/ui/crw_web_controller_unittest.mm b/ios/web/web_state/ui/crw_web_controller_unittest.mm index 80f6eed..369c98b 100644 --- a/ios/web/web_state/ui/crw_web_controller_unittest.mm +++ b/ios/web/web_state/ui/crw_web_controller_unittest.mm
@@ -224,6 +224,7 @@ OCMStub([result removeObserver:web_controller() forKeyPath:OCMOCK_ANY]); OCMStub([result evaluateJavaScript:OCMOCK_ANY completionHandler:OCMOCK_ANY]); + OCMStub([result allowsBackForwardNavigationGestures]); OCMStub([result setAllowsBackForwardNavigationGestures:NO]); OCMStub([result setAllowsBackForwardNavigationGestures:YES]); OCMStub([result isLoading]);
diff --git a/media/base/container_names_unittest.cc b/media/base/container_names_unittest.cc index cc3f9c13..3b0d883db 100644 --- a/media/base/container_names_unittest.cc +++ b/media/base/container_names_unittest.cc
@@ -244,8 +244,6 @@ TEST(ContainerNamesTest, FileCheckUNKNOWN) { TestFile(CONTAINER_UNKNOWN, GetTestDataFilePath("ten_byte_file")); TestFile(CONTAINER_UNKNOWN, GetTestDataFilePath("README")); - TestFile(CONTAINER_UNKNOWN, GetTestDataFilePath("bali_640x360_P422.yuv")); - TestFile(CONTAINER_UNKNOWN, GetTestDataFilePath("bali_640x360_RGB24.rgb")); TestFile(CONTAINER_UNKNOWN, GetTestDataFilePath("webm_vp8_track_entry")); }
diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc index 1e3769a..71d8e8a 100644 --- a/media/blink/webmediaplayer_impl.cc +++ b/media/blink/webmediaplayer_impl.cc
@@ -2124,7 +2124,18 @@ return; pipeline_metadata_.natural_size = rotated_size; - UpdateSecondaryProperties(); + + if (using_media_player_renderer_ && old_size.IsEmpty()) { + // If we are using MediaPlayerRenderer and this is the first size change, we + // now know that there is a video track. This condition is paired with code + // in CreateWatchTimeReporter() that guesses the existence of a video track. + CreateWatchTimeReporter(); + } else { + // TODO(sandersd): If the size changed such that ShouldReportWatchTime() + // changes, |watch_time_reporter_| should be reinitialized. This should be + // internal to WatchTimeReporter. + UpdateSecondaryProperties(); + } if (video_decode_stats_reporter_ && !video_decode_stats_reporter_->MatchesBucketedNaturalSize( @@ -3053,12 +3064,18 @@ if (!HasVideo() && !HasAudio()) return; + // MediaPlayerRenderer does not know about tracks until playback starts. + // Assume audio-only unless the natural size has been detected. + bool has_video = pipeline_metadata_.has_video; + if (using_media_player_renderer_) { + has_video = !pipeline_metadata_.natural_size.IsEmpty(); + } + // Create the watch time reporter and synchronize its initial state. watch_time_reporter_.reset(new WatchTimeReporter( - mojom::PlaybackProperties::New(pipeline_metadata_.has_audio, - pipeline_metadata_.has_video, false, false, - !!chunk_demuxer_, is_encrypted_, - embedded_media_experience_enabled_), + mojom::PlaybackProperties::New( + pipeline_metadata_.has_audio, has_video, false, false, + !!chunk_demuxer_, is_encrypted_, embedded_media_experience_enabled_), pipeline_metadata_.natural_size, base::BindRepeating(&WebMediaPlayerImpl::GetCurrentTimeInternal, base::Unretained(this)),
diff --git a/media/capture/video/win/video_capture_device_utils_win.cc b/media/capture/video/win/video_capture_device_utils_win.cc index 07d4b1d..868ae19 100644 --- a/media/capture/video/win/video_capture_device_utils_win.cc +++ b/media/capture/video/win/video_capture_device_utils_win.cc
@@ -6,6 +6,7 @@ #include <iostream> +#include "base/win/win_util.h" #include "base/win/windows_version.h" namespace media { @@ -65,9 +66,8 @@ bool IsAutoRotationEnabled() { typedef BOOL(WINAPI * GetAutoRotationState)(PAR_STATE state); - GetAutoRotationState get_rotation_state = - reinterpret_cast<GetAutoRotationState>(::GetProcAddress( - GetModuleHandle(L"user32.dll"), "GetAutoRotationState")); + static const auto get_rotation_state = reinterpret_cast<GetAutoRotationState>( + base::win::GetUser32FunctionPointer("GetAutoRotationState")); if (get_rotation_state) { AR_STATE auto_rotation_state;
diff --git a/media/test/data/README.md b/media/test/data/README.md index 6b1231e..c37d9a0 100644 --- a/media/test/data/README.md +++ b/media/test/data/README.md
@@ -750,27 +750,10 @@ ### VEA test files: -#### bear_128x96_40frames.yuv -First 40 raw i420 frames of bear-1280x720.mp4 scaled down to 128x96 for -video_encode_accelerator_unittest. This is the size that could be encoded -with the lowest H264 level 1.0 in 30 fps. - #### bear_320x192_40frames.yuv First 40 raw i420 frames of bear-1280x720.mp4 scaled down to 320x192 for video_encode_accelerator_unittest. -#### bear_320x192_40frames.nv12.yuv -First 40 raw nv12 frames of bear-1280x720.mp4 scaled down to 320x192 for -video_encode_accelerator_unittest. - -#### bear_320x192_40frames.nv21.yuv -First 40 raw nv21 frames of bear-1280x720.mp4 scaled down to 320x192 for -video_encode_accelerator_unittest. - -#### bear_320x192_40frames.yv12.yuv -First 40 raw yv12 frames of bear-1280x720.mp4 scaled down to 320x192 for -video_encode_accelerator_unittest. - ### ImageProcessor Test Files #### bear\_320x192.i420.yuv
diff --git a/media/test/data/bali_640x360_P420_alpha.yuv b/media/test/data/bali_640x360_P420_alpha.yuv deleted file mode 100644 index ca52ae7..0000000 --- a/media/test/data/bali_640x360_P420_alpha.yuv +++ /dev/null
@@ -1 +0,0 @@ -DEHKLLMNNPPPPQSQSRRRSSRRQSQQPONMLJKJGGFCAA>>:;:;;88888899989::988:9767755544211100..--++)'))(())((((''(((('())))**++,,**++,,,,,,,,++++*)))))))))))((''&&&&&&&&&&&&&&&&&&''&&&&&&&&&&'''''''(()+,,-/0224557778899999999::9999::8876677:>EJQYahlquwxxwuqmjhhghjlnnoooopopoqstwy|~~~~|xrldYNA4.,)++++,*+++.4:BOZenx~ }wmgXH;/)%$$#$%%'*.178>CLS^emt{|zuoiaYSORQTTQSLD=60'%#$$""#"""!!#!"! #%&(+07=DKPW^cglrwxyxwuvvuuuok_WKFA??@@@@@@????==:99975543100/039<CNV]flqty{|zzvrmd]OE92,+)((''''&((()))+*-01236778=>?CFFJNU\bkptxz{}~}{yzvttx{ ~|zxutty |xpfXJ=4/.-,038=DNZbksx|~}{yvwwzz|ypdXOHEA><;FIILLLMNNPPPPQRQTSRRSSRRRRQQPONMLJJHHHEAA@=<;<<:8:::::89999:::98888876665544211100..--++*())(())((((''(((('())))**++,,,,++,,,,,,,,++++*)))))))))))((''&&&&&&&&&&&&&&&&&&''&&&&&&&&&&'''''''(()+,-/01124557888899999999::9999::999878;=DKQY_gosyz|||ztqljkklmooppoomlkjjjkkorwz~~~~wsk`UH<2+*++++++,,+,17?JVamw} {tk^P?3*&$$$%&(,/258=AEJRZcjr|zundZSOMTbpfVNE=6)#$###""#"""!!#""!!"$&+-4=CJPV\belquyz{zwuvvuuuskaVKEB??@@@@@@????>><:::755431000116<CMU]flqty{|zzwvof^RG;3.+)((''''&'(()))+,..0035689;=ABDGKOX^dkpty}~~}|{wsrrvyz} }{yyxy~ {umbRD92/...038=BMXbksx||zwwtssvxzxnbWNIFA><;HHIKLLNOOOPPQQQRQSRRRRRRQPPPNMLKJJHGEDA@@>=<;:8:9899899:9999::999988776655332100//..-,,++*(((((((((((((())))))))**,,,,,,,,--,,,,,,,,,,+*++**))))))((''''''''&&&&(''('%'&''&&&&&&''&&''''(((())*,..013345558888999988999999999:99778;>CGOU^bjqv{}}~{xvpnllklmnoommkigeca^adjmrw{|vqi^QB6.*+***++++++05=EQ_jr{ }xncTE6-&$$%&',/148=>BDHLU^eowzsh`VPN]euvo`fys@$!######""""!"""##$$*.39AGMS[`ejpsx{}|xvvuvvvsoj`TMD@????@@????@>==<;::755421/0..28;BMT\hnrux|~}|{vqicVJ>4.+)))((''(((())*+,-01134589=>@AEJMSZ_fkrv{ }|zwtrppquvy} ~~{yzz{}yrj\N>51-,,.037<CMW`irx|}{xwtrooruxwk^SNIFA>;:IJJKMLNOOOPPQQQRSRRRQQPPPPOOMLKKIIGFDCA@?><;;:9977999:::998:::999988776643332100......-,+*))(())(((((((())))))))**,,,,,,,,--,,,,,,,,,,+*++***)))))((('''''''&&&&(((('&'''&&&&&&&''&&''''(((()))+-/01223555468899998899999999;:9977::?DJRY^flsx|~}|yvsnljjihhiffefd`^ZXUUX\aeluz~|vmbWI;4,*,******++.28ANXcoy~}tj[M=0)$$$%+.168;?BDCFKOXdmu~}wncWOWl`ilpkdw^I+!$####""""!"""#"%(.3;@FMRY_cgnquy}|{yvvvwwwuph`TKDB????@@????>===<;::755411./..27;BJT\fnruxwz~}|wslcZN?81+())((''))(())*+-.013334679>>DINSY_aimtx} ~|yvtqoooppsux~~}{||~ {vofXI;3//0..025:BMU^gpw|~~~~|{xvtqmllnrv{ wj]RKGEB?<;HIJJLLMNOOOOQQRRRQRRQQPPPOPOLJJJHGGECA@?>=<;:9::888889::::::::99888865544322210000//----,+)))(((((((((((()))))))****,,,,,,,,,,,,,,,,,,,+**++*())*())))((''''''''''''''''''''&&''''''''((((((***+--.1112455678888999999::9899998769:;@ELSY`fmsvy{yvwspkhedd__^^^^_]ZWTRONOSX_fpz}xql\OB8/+*****+++,-28?IWamw{vm_RB4*%%%(.249<@CDEDFINU_iqzyri]U\ijqYIRiqcRWG! !$##"""#"""""&(-4;?EKSW]bdintyz||{xwwwwxwsoj_TJD???>>??????>>==<:997543211///06:@KS\ekrvwy{}{zzwqg\OB81-+))((''((')(**+,-/0232479:?DHNRY`dgmrv{ ~}xvtplkjjkmot{~}}}~ zslbRC82/..../26;BMWaiqwz~~}}{wusrmiiimqv| vgZNHGEC@><JJKJLLNNOOPPOOPRRQPPQQPPPOOMLJJJHGEDCA@?>=<;;:::99889:::::::::99888876543322100000////--,+*))((((())(((((())))++**++,,,,,,,,,,--,,,,,,,+++++,*))))))))((''''((((''''''''''''''((''''''(((())**++--.0103333567888999999::9899887778:;@EJPYaiouvvvtqolfc_ZYYXXZZXXYYXUOMJGEHPYcjs{}vmcWI<2.-****+++,,07<FS`ku~ yrdWG6-))).158:?BDFEFFLNRYdow~ {tk`\djkeWLQi[ONV\YT&""$$$##$"""$(-4:@EKQW]^bhlqvyz||zzwwwwvutpj`SICA==>>>>>>>>>>=<;:9975432311../38?IR\fmptwz{{}|zvqi_RG:2.)))((('((*)*++,,-./13369;@FKPUZ_dhmqvz} zxvtokhgeegjms{~~~}xri^O@71.-../226;CMXaiquz}~~~~~}{zywsmmkgggjnqx ~uh[NIGDC@?@KKKMMMMOOOPPPPRSQQPPPPOOPOMMKKIIHFEDBA?>==<;;;998899::::::::::9988887655321111111100/.--,,++******))**(())******++,,-------,,,--,,--,,,,--++,,**++))))(((((((((((('''((((('&&&''('''((((((()**++,,,-/01233466799888888::88986655678:>BGNW_gmqoomkhgb^XWTSRUUWWXXVVUPKFB><AGQ]fow {si^PB60,,*+++*++,-49BO[ht||tj]O>1++/479<>BCEFFGGILPX`jr{ |ulb^`S[NUTEOSJFDcjdZ%!$$$$$$#$&)038?ELQV[^`eimqstwzzzywvvwxxuqk`RJC?<<==>>>>>>==;;<:7786432210./.37>HQ[clqtvxz}~|zxtkaRG=40,)((((())+++,,,-../146:?EIORW\afinsux~ }{wspnkihdbcgint{~ }wqeYK=61-..-.148=FOX`iqv|}}}|}|}ywwsnkjgfccejqw~}teZRJHGDB@?JILLMLPNOOPPPPOPQQPPPPOOOMMMKJJHFDCB@?>====<;;::8899::::::::::9988887655321111111100/.--,,++******))**))))******++---------,----..---,,,--++,,++++))))(((((((((((('''((((('&%&''('''((((((()**++,,,-/1112335568888888899888755455669=AEKS^ehhffedb]ZURSRQRRRVVWWVSPMGA=879AJWaks} |voeYG:2.,*+++*+++,17?KXdq||umbRA4//269=?BDGFGGHHHKPV[emu~|xnc`UNG:C@59:>@CKT]S##$%%$$$&&*.5;?GLQXZ]`cfgkoqsuyyzyvuvuvvuph^QHA=<<<<======<<;:98777543220//..28=FRZckqvuw{}}||{tlbWK<41-)(*****)+++,,,.//258=BEKPTY^chmpsv|~}yuqoljgda_`cgjnty} }vncWJ<73.//./158>FO[cjpw{}~}||{zxurojgfcb``dipv~ ~tgYQKHGDBA@KKNNMMOOOOPPPQQQQOPPPPOOLLMLMJHFECBA???=>=<<<<;;;;::::::;;;;::::8877744432221122220000..-,,,**))******(*)))***++**,,,-----..........----,,,+++++++)))))())((((''))((((((((((((((''((((((((()**++,,+,--021122445677887777776655443568:>CJRY_bdb``^\ZWTRPPQSTTUUVVSPMGC;5124;EP\hqy~}wpj^PA6/+****(**++/7<FT`mxypeYJ;4369;?DFGGHHIIIHILQV_gqz}umd`TUWD;2CZ/-0AQEML8"%%%%$$'*04;AFLRV[^_bdeijjlqtvxxxssutttrmi_QH@<;:;;;;:;;;;;:98876534222100./19=DNZaiquvxz||||ytlcZM@81-+)))*(+++,,,-///26:>DHNSX^cikosu|}|wtpjhfca^]_cgimou{~ |ulaTF;740/0.026=BHR\dipx|}}~}|{wuqolgd_\\\\aiov} |sg\QJIFECA@LLNNMMOOOOPPPQQQRQPPPPOMLLKLJHHFDB@@??>?>=<<<<;;;;::::::;;;;::::8877744432221122110000....,,**********(**))***++**,,,,----............--,,,+++++++***)))))(((())))((((((((((((((''((((((((()**++,,+,--..00112445778877776655443333469=BINUWVWZ[[[ZZVUSSSSTUUWXVUROIC=60--27@KVcmv}zsmcVI;2-***+*)*++.29DQ^gt}|sk_QC97:=?CEEGGGGIIIIIIINW`jsyvhkpd]][MEBWY(+4<;7=DK'%%%%&(*05:@GLRW\]`bddgeefilpsvvvssutuurmf\PG@<999:;;:;;;;;:9::765342220//./27<DNXbiquvxz||||ysnf[NC82+,,++*)+++,-..0139=CHMQW\`fjnqvz|~}wskgea`\\\^behknptz~~~}~ |ulaTF=864200249>FLU]emsw|}|}|zwurmjeb_[YYXZ`jmv~ |sg]RLJFEC@>MMNNNNOOOOPPPQQQQQQQOONNMMKHIGDBA@@@????>=<;<<<<<<;;;;::::;;::98997765432233221110.011////-,,,,,++******++++****++++,,----//-.//.-....---,-,++++****)((())))(((()))((((((((((((((((((()))))))***,,++,,--///33454556666664455443211257;?EKOUVUY[[\[ZWWUVVVVWWXWVSOKF=71,*+.3;FP]gqx~{wpi]PA3.**)**))**,29@KWdny|tmeWJB?>@CFGGIIJJJJHHFFEGMWcmw|}skei`ed`VA<C:1*2.,0<Ab2%%&&(-06:@FLSXZ^_bcdddbacdjmrtsttssssspme[PD=988889:;:::::998875542221/00/016;EMXaipuvxyz{||yxqh_SD:3.--++,,,,-../139=AFLPV[aejnrvy| |{sjgb][YZ]_cehkmpsvz|}}}}~ |uk`SF>98541147=DJSYafnuz}~||{{xspjga_ZXSRSV^hmw}sh\RMHGDCA@MMNNPPOOOOPPQRQQQQNNMMMMLLJHGEBA@???@@>=>=;<<<<<:9;;;;::::99::98777765432233221110.0111///-,,,++**++****++++****++++,,--..---.//.-....-----,++++++**))))))))(((()))((((((((((((())(((()))))))*****++,,--/0100143556666664444332100146:>DINQUVZ[[\\\YYXXXXWWZXWTRMGA94.*)+-07@KWalu}~}ytmdUG:0+*********07=ES`jw~ |woh`TJEBDEGHHIIJJKKHGBBBBEP[gttoiflgegeXJC@G9123,9<EIcK%%(*,18;AGLRVY^_aacde`]\\^dhnqtutsssssojdZLA=;887789988888776665553221/0/..26;DNYbipuvxyz{|||xri_SI<71//----..//137;AFKOU[_ejnruw|~ ~zumg`[XYZ\_behkloqtw{{}}|~~|uj_RE?:965547;AHPV\cjsx}|||zwsmh`[XURPOOS\fmw {sh^RMHFCB@?MMOOQQQPOOOOPPOOPPONLLKIJIGFDBAB@@????@@===<====<<;;::::::888898776665443333222022022200/..-,,,,,,++**,,+*****++++,,--.........///....-,---,,,++++****))))))))(())))))))(((((((((((((()))')))***+++++,,---.//1231455665542433121./2569>DHLQUX\^^]^][YYYYXWYXYTSOIB=6.+(()*.4;FR^hqy~}{vph\N@4-*)**)****.6:BP\hq| }{undZQKFFFHHIIIJJJIGD><;=@JVctihigcfd`^B3LSX<@RSD<:CHXfU<$,08=BEKQW[]dfeee`\\WUVY]djqtvtsstrplibWJ@;6666788887777666654331//10//-/14:CMV_gotwwy{{{{|yslaVJ=740/////./025:@EKQW\`ekmrty{~~~~~zvqh_YVVY\_cfikmnoqtw{{{{{}{tj^RIA<;:888;>ELSY`fmtz|~}}|{wtolc]VSOMIHLQZemx~yqj_TNHECA?>MMOOPPPOOOOOPPOONNNMLKKJIGFDBA@?@@????@@?>=<====<<;;:::::::99:877766654444333223222211000/.-,,--,,++++,,,+****++++,,--.........///....-,---,,,++++*+**))))))))(((())))))(((((((((((((()))')))***++++++,,..-./0101222333322321110./2569?EJNTXZ\__``^\[[[[YXY[XVQLE=6/*))))),07ALXbnu}~~}ysmcUI90*)))()****07>IWdnw~|wqkaXPIGGHHIJJJJKJE@<969;ES^pookie`]YSMIJLS``__XKA8+;Rnn5(7<BFNYY[`liTMVa[[aRPPUZaipuutstrponh`UI?856656776666665544332220///.../04:CMV_gotwywy{{{|ysmcYL@:62111111348=DIPT\`fimruy|~}}}}|ytld]XWY\^bfikmooprvy|z{{||{tj^PGC?>=;;>@CJQX]ejqv{~~}}}{vrmf`YRMJECCHQ\eq| ~wsi_TMGDB@@>NNOOOONNOOONPPQPNNLLJJIHGFCA@@@@?>?????>??>===<<<<<<::;;::;;:9887776565233443344332221110//--,..--,,,,,+******++++,,--........00//....,---,,,,,,*,+*))))))))))))))(())(((((((((())(((())**))))*)****+++++,,-/////0111122122110/.01149<@GLRVZ^^_`aa`^]]][YY[ZXSNGB:3+()))))*.3;FS^itz~{wog[N>3*)))******/5<DS`lv||xuog\RKHHHIKKKKJHEA<82216AP_aniRhdZTRGQQJQYabaa`]U?4<I[rr:6>ILcmhcmwJOKTZ[YWJIKNV`iquwuwtssqkh^SF<643344554444555553221200/.,.././4:CLU^gnswwzxyz{||todZQC;663222327:@FMUY`ekotwz|~~}}~}}{xtphd[YZZ]afilooqrrtvy|}|||{ {rj\PIDBAA?AAFIQV]bhouy}~~}{xtpjc[SLFC?=?DO[gs~ }yskaUMEBA?>>NNOOOONNOOOOPPONLLKKJJHGDB@@@>>>>>?????>???===<<<<<<::;;::9988887776565565334455332221110///.-....--,,,+++****++++,,--........00//...-,---,,,,,,,,,*))))))))))))))(())(((((((((())(((())**)))))())))++++*+,---.//011111101100//.0126:=CGOTY\^`abcca___\[[ZZZUNKC=5.*'(((((),07AMXdnv}}~}yslaTE7-*))))****-39BM[fq{}zupi`WPMJJIKKKJIFA=84/-,1;QirsmM]aWUONPMGBNVb`XUX]T=9BT^qZLO[m~wjh[Ybmpwtd\P@AJT_ksuwvvtssqkh^SF<6422332222333333432210/-..,--,,-38@HS\hoswxxyyz{{{tni]SG>8765556:?DJOW^diotxz~}||}}~~}}~zywsqngb\YY]acglmprrrsux{}}zz{|{rj\RNHFFFEGIKOW^cflrx|~~~}{{ytmf^XOGA=9:<FR\hw {vrkaUMEA@>>>OONNOOOOPPOPNMNLKJIHFFFDBB@@??>>==>>??@@>=>>====<<<<;:<<;;;;888777665555555554554432222210000./0.--,,-,,,,,,+***++,,------//0001....-,--..---,,,,,++****))(((((((((())**))(())(())(((())))))))))**++++*))*---....//0111100///0./0137=AFLPVZ^`bdeddabaa_\[ZYYTOF?9/*('('&&''*.5<HR]gqz}}zvpfYL=3,****))**,16=IWcmw~|zvulg^UOKMMMNMJGB>:41+**.9Pq~xe`\GYYUQRNLMHQVYLL\_KGA*FL]TO[ntosmhmlepzqj\F=KUalswwxxvtsqng]PD:310011112211100111/0/----,-,+,,028>GR\enrvwwy{{{zzwrk`VH@<96689;BGLS[ahmru}~|}}|}~}~}}zzywurpmgb]\]_beinoqrrstvy||}zz{}|rh]TPMKLLKLNRX^beknsx}~|}||ywrle]QH@:777>GSbny {wslbVLFA?>:9OONNOOOOOOMNMLKJJIIHFCCA??====>>>>>>??@@>=>>==>>==<<<;<<;;;;988766665555555565554444442211100/0/..--.,-,,,,,+***++,,-----.//0000....----...---,,,,++****))))(((((((())))))(())(())(((())))))))))**((((**)*,,-....//00000////--/01459?CINTZ^`cefffeda``_[ZZZWQLB<3-*('('&&&')-18CMYcmw|~}||xql_SD6-**(())***.49DR_jr{~~{yupjaXQMLLLLKGC>:5/+('+/5Ts||{o_VHFVVQU[RKT\[jRIZdeZGBFKOJXf_lmqo{lgl_[pfgKU@HWdnswwxxxwvsng]PD:20//000000//000000/..-,,,,++*+-.17>GQ[entxxxxyz|zzwqkbXNE?=98<@DHOW^eipty~ |{{z{}|~~~~|zyxvvutsrpnhc__`adgjnpqrsvvxz}|{zz{}{sj_XTSPPPQRVZ\bfjoqvz~~}|||yvnh`YND<8447>IVdpz }yxqlbVLE@>=:9ONONOONNMLKMLKJHIHEDCB@?;;<<:<<<==>>>>?@>>>>====??==<<<<<;;::877777766667754576655554443211100100/.-,-----,,,+++++++,,--.//////....---.....-,,-,++********(*((''(((())))(((('((())))))))(((((())))))**)))))*++,,.///0/00..//.../166=AGLRX]`cefgijgfca_[ZZXWVPF=8/*('(('&&&''+.5=IT^iqy|~{||}xpe[J;0+)(())**)+28BLXdow}|{{zvsne[TNLJLJGD@;50)($"'+6Ybtwxo`SLJZ[TUa_STZ\^]ST^ldTJM^NQ`ikurfko[IGTV]RXSQRMKVdntwwwvwwvskh\PC92.----.-..////.....-..,,,,,,*+-/17>GPZenquxxxxy|{{xsng\OGB?<=BHMRWaglrw|}}zzyyyz{{{{|{zwrsssrsrpnifb``deiloqrsuvwz|}|{zz{}{slb\XXWUUVX[^agloruy|~~}|{{ytnf^SJ@83137@LXgs} }|xurkbVLC?<:99ONNNMMMMLKKKJIGFFEECA>>=;;:::;<<==>>>>?@>>>>====>>==<<<<<<;::99977776666777788665555444322211110//..------,,,+++++++,,--.../////...---.....-,,-,++********))(((((((())))((((''(())))))))((((((**))))**)))))*++,,+-/./...--//./.137;BGLRV]_cfgikkjgfca_][[YWRIC;4-(''(&'('&'((,19BNXcmvz|~{||{wrj`PB5,)(())**)*/5<GUcksz~}~}}{{yuph_VQMJIIEA<70*&#" $*;_afkqmeLFU[\VTX[YOXRQZX^`gibR@VNDe~ynf_V^K:8=QVHPSGFGKVdouwxxwyywuli\PC90-++++,---....-----,--,,,,++**,-17>GPZdmquxxxxwzzzwtoh^RJE@CFKPW\ahmtx} ~~}}zzxxwyzzzzzxwusqqqqrrpokfbbcdfiloqrsvvwz}}|{zz{} {slb^ZYXXXZ[]`gkprsx}~~~}|{{ytle[QE;52239BP_jv{xxvslcVLC?;999OOONMMNLKKJIHGFFDCB@=<<;77998:<<>=>>>>??>>??>>>>==>><<>>=;;;9887777766665678666646555554322102220/.//,-,------++,,++----../0....///.....-/-,--,+****))))**))))**(())))))))(*))(())))))))(((((())))))++++++**+++,----....../000227;?FLQU\`acgjjmmigfca^[[ZWSOG=70+((('&&'&&())*/4<GT^hrx{|{{}|ytmeWH:0))))))*)).28BN\how|}|}||{zxtmbYRLIIFB;63+&#! ##(;MUhfc\Y2@Z[WR]QPZVZUP^[W^`jeW4ONNoordbgXZWM8=GCQWD:6?M[gpvxxyyxxvtni_OB80*++,+++++**+-,,+***)+,,**+)++--07>FPZclqwwxxxxz{{xvpi`VNGFHNRY]dipuz }}{yvvuttuuwvvvwurqqpqqrsqokhdccdgiloqrsvvxz|}}{zz{~}vkda^\[\\_abeinsww{}}}}|{{zwrleYNB94224<FTcnz |yxxvtneXMC?;99:OOOMLLKJKKIHGFEEB?>;:::978999;;;;=>>????>>==>>>>==>>====;<;;:9887777666666778966675555543331121100010---------,,--,,----....-...///.......-,,,,+****))(())**))**))))))))))))))(())))))))(((((())))))++++,,**++++++--....../00237;@EKQVZadeikkllijifc`^[YXVPI@91-*)(('&&'&'&&()-27ANWbnuz{|{{}{wqi^N?4+*())))))*05=JXalt{|}~}{{|zuof_UNJFC=72,'#!$""#>`^RV_^\URRUZ\]T`\LKZ_^gSJ[c`\VFWKRg_dcgeeedQ4:HECB=/9CP[hqwxxxxxxvtnj_PA70****)*+**++,***))))))))(()**+,.27=EQ[cjruwxxxxx{{yupjbXQMLRV\`flsw}|zywtrpoprtsrrtvutppopqrrrmjebbcdgiloqrswwxz}|{{zz|}}vkdb_][^^acfjorvy|}}{{{{yvpkaUJ=73128>KXds} ~zyxwvtneXLD=:89:OOMMMMKIHGGGEDDA>=;:9887678:;;;;;<====>=>>>>>>>><>=====<<<;;::9877776677777777888876545543332211011//..-----,,------------...-./../.----..-,++,+**))(())(&(&)(**))))))))))))))(())(((())(((((((()))***++++++++++++----....00228<AGKQUZ_dfhkmlnnlijda^]ZWWSOE<5.**)((''''''(('',/4=HS^jqwzyz||{yrpdUD8/+)(((((()/3;GT`grx{}}}}|wsk`UOEB>94+)$!!#! /eg[FLd]VSQUURDN_iRBNXTW^VY[[XUUSPBHW]h^fcfk^<2EC=DE71:CQ\hqvxxxxvvvtoi^O@6.)''''())))(*)***)((((%%'()**+--16>ENWbkqtvxxxyz{{xvrnc[VSUX^dhms{~~~}ywwurnmllmklmpstrqonoqrrqrliec`befhlnoruwwxz}{{zzy{}}vleb_^^_`cfhlotvy|}}yzzyvtmg^OD;6433:@O]iv {wywvvtogYMC=989>OOMMLKIHGFEECBB?;:998787678:;;;;;<=====>>>>>>>====<<==>>==<<:;:9888877777777778866787655554333331110//..--++,,------------....//../.----..-,++,+**))(())()**)&))))))*)))))))))(())(((())(((((((()))***++++++++++++,,--.../0158<BGLQWZ^cgilmpqpqmkgc`^]ZWTOH@93,)*)((''''''(('')-18BNXenuxz{{zzzvqgZI=/*)(((((((-17?MXdowz}~~}|zuof[PGA:4/)&"!!! !7YghZ`]c_HMWTROQ^lT5@IKGS[^\WOFEEBHKJV`N]``e[QGDTG9((3:CQ\hqvxxvvvvusoi^O@6.)''''''(((())**)(''''%%'()**,//38>EQWbkqtvxwwwy{{{wunia]\]`glpsw}~}|zyutpokjiihjkmpstrqonoqrrrpkhca`bbdgjlmotwwy{|{zzzy}~}vledbaaabehkosvz{}~~}|yzzyvsleYME;6435<DSamy }ywvvvvtldYMC=89;@NMLKJGIHGFDB@B==:987766667889;::::<<<<<<==>>>>>>==>>==>><<====;;:9999788889999886788766665443333332100..--,,,,,,,,------........///....--.-,,,,++))))))))*/28-')))))**))))))))(((())))(((((((((()))*+*+,,*++,,++++,,,-//00159>DIKQW[_ceikmoppprmhea_\ZXWSLC<5/)('''''''&''&&(''*/5<IUaisx{zz{|{xskaQ@3+((('''''+.6=IUamuy|}}yrj]SE>6/,&# !! !#Ohmhcab`_SPSX\ZdTEC=DLP[`VPV?BEBEFHPQPOZW\_TIOU\O,.49CP\hquvvuuuuvtnh]N@4,('&((%%''&&))(()&%%%%&&&'(*,.1148?FQWaiosvxuuwxyzzyvsmgdcdhlrv{}}{zxuuqomkgdcfhjmorttrqmopqrqpmkgeaabbcfiimoquyyzzzzzyy| }vkffdccdfjkmouwy{}}}}|z{{zwqkcXMA95237?IVdo{ |xuuuwvtph[OB<7:=EMLKJJHGFECCB@=9966655444666678:::9:<<<<<==>>>>>>>>>>>>>>=<====;;::999888889999998777776665544333332100/---,,--++,,,,,,--........///....----,,,,+)))))))))*6CC4'())))**))))))))))(())((''(((((((())*++++,,*++,,++++,,,-//0258>CGMRV[`dfikmoqqppokfc`][XUTOF?81-*('''''''&''&&''')-28CR\fpuy||{||yuodVF6,((('''''),3;CQ]juy|zum_QF<3-*%"!!! !5Wmy\^ba^YXJKPZb[]YMBHFGKYWPPYC4:KORJHHFJLPPPLN\hVO)-5:DO\grvttuutvurmi]N@4+'&%%%%%&&&&()(('&%%&&&&()*,.03459?FQXajosvxuuwxy{{zxvrmjiiorw|~}}zxwuspplieaabfhknqrttqpnopqqpomiebaabccefhjnruyxz{{{zyy|}vkffddddfilnsuwy{{{zzyyz{yvpjbWI>85348@M\iuyvtttuvtog\QE<9=CJONLIGFFEDA?=<9666654434665557788::;<<<==>>====>>??>>>>??>>==<<<;::::::::99::::998899757776555555332200..--++++*+++++,,--------../..////.---,,,,+**))))**))2:9-)((((((((())))))(((()'(((((((())((+++,,+,-,,+,,,,,,,,.,..0237<CGMRW\_ehjmnpprrponkgc^[ZWTPJC93,)))''&&''&''''&(''&+/5@MXcntxzz{||{xsj\L<0)'&&'''()-29BNYenw|~~wqgVH;1-*%$#!!##\c_uQ>TJXdUA=O@LTSPNQ:;HMKHHNJUY\^behhaJBKFDBMNLYfVW+,2:EQ]gqssrrrstusni^OA4,(%%$$$$$$&&&'(('&%%'''()+-./1247;@FOV`hosuwuwxx{}}|yxvsqqrwy~~|{{xwuqpnkgc_acfhjlpsstsqpoooopnmkgca_``_acefhlqtx{{{|zwxy} |tlgfeeeefjknqrtyz{{zyyyxxxsmg`UG=9767;DR`lxxussttttqg]QE=>BGNLKJGGFDCB@>;97553333323455667988::9:;;==>>==>>??????@>??>>==<<<<;:;;<;::::::::998876778876666655332210..--++++*+++,,,,--------../..////.---,,,++**))))))))**++)((((((((()))))))(&)()))(((((())))+++,,+,-,,+,----,,,././047:@EIPV[`dgilnqrrropnlhda][YVRLE<6/+*))('&&''&''''+5</&(-2;GT_kty{{{|||{unaQB5+'&&'''()-1:ANYantz~zsiZJ;/(%&%$!!"7uYKB<;KOVaT?12;@?EBGJA8FIKEIEN[^`bdeecZ>=ICEB^NCP[\J(+3<EQ]gqssrrrstusmh^O@6,)%%&&$$$$%%%&&&&&%%$%')+,/01346:>CIOXaiosvxxwvxy{}~}{zwwwy}~|{zxusoolgb_^_acgjlorssutqpoooopnokeba_^]_``ddglqtwxy{|zwxy~{rlgeeeefgghmpqsvxzzyxyyxxvslf_QG=:99:=FTbmyxussttttqg]QD>AHLPLLIGFECA@><965432212223344557888999:;;<=>>>>??????@@?????>?>====<:;;<<:::::9::99998677777568774433211/---,,+++,,,,----------.......////.--,,,+***)))((((((+5B<,'''(((((((((((()((((((((((((())++++++,++,,,,,------./..0159>DHNRY_behklopssoommkgb_\YXSOI@81,*))*''('''''''&+4-'''+.7COZhpxz|zz||~yqfWE7,('&'&'(,/49BKUaksz}}vl_O;-$$###$#%<kL;453=LLbbaUB='799:BA=GKJLPTX]_`adbc^T<5@CEEjmMICDH105<FR[gqsttssttttmg^PA5-)%%%%$$%%$$%%%%%%&%'))*,.023579:?EKQYahosvxwuuwz}}~~ }{zwvqnnkeb``abdhjlppruutrrpopppnlmjfb_^]]]\^`bgjpsv{{{{zyvz~{rjfeedcbabfikosvwxwwwxywwtpjd]PD=:88<BJXer| |wtsssttsqi_RGCFJPVKIGFGDC?<:9754321101002244444788889:;;<=>>>>??????@@????@@?>====;<<;;;;::::9::99997679887877775432210.---+++++++++,,,,------......./---,--,,++**))))(((())'61**'((((((((((((((()(((((((((()))*+++++++++,,,,,----..////137:AFMSX\`dijmnprqqpnllhd`][XWQKC;5.+)(()))'&''''&&'%$%'('*.5?LVcowz|{zz||ztl\K</('&'&'*+06:AJT`jry}yqbR@1(#$$$$$'BnY>6;58AQbioqhP=F>46CGFPNMNVY[^`abb`]XTR5EGGBNaMJU]K.16=FR]iqsttssttttlh]OA5-)%%%%$$%%%$%%%%%%%&'*+.002457:<<AEKQYahosuyxwvx{~ }|yvsrmlifcaabbcfgkoqrtuutrpooooonlkhd`^]\\ZZ[\acinsv{{}|ywy| |siedda`_^`cfjosuvvuuuuwwwtpkcZNE><;;?FQ^ivzwtsssttsqiaUKFJPVZKIGEEC@;9865422100////11333356897889;;=>=>????@@@@A@@@AA@@?>=====<<<:;<:;<;:::99:97788888877775533210.--,,++++****,,,,,,----....,./.---,--,,****))))((((**'&(((())((((''''(((((()'((')))******+++++++++,,,----....000036:>DJLT[_aeijmonooonkjjfb^\[XTOG?70+*)))*2/((''''''&&&%$&#(-1;HUamuy||zz|{zun`P?2*'%&()*/39=AIS]fov}{tiZF6,&$##%(([kX<6<BAIYentwtVKHCADLPMGCLTXZ]``cba]XTNF>BACDKZTPLL9117>HR^jrtttusttsrli_QB5-)%'''%%&&%%%%&&%&''(,..01356:<>@CHMR\bhosvxyxxy{~ }|yvtpliidb`aabcefilnqstutrqooooponlieb_][[Z[[]]^bglrvz{|}zwz} ~vnhdcb_`\\`ehknsuuuuuusuuusoj`YOD?>>?EKVbmvyvsrrttvurleVOJMT[_HHFCA?<98655222100////01213356777889;;=>=>????@@@@A@@@AAAA?>>>====<<<;;;<<;:::99999999888877775544320.--,,+++++**+,,,,,,----....,./.--.---,,****))))(((((())''((((''(('''''((((()'((')))******+++++++++,,,--..//..000136:AFLSW\`cgikmnlljjiihhc_\[[VSLC;4-**)))**)((''''''%&&&$&%(+.7CQ]hry|}{{||{vofWD3)'%&(*.26:>CJR\elu{|ypaO>0'###$%)DKF8??=>JZdoswr[TNA>ABMKEPTYZ[^``a_\XTMGB:8MB@GUZZVE,247>JT^jrtttussssrli^PB7/+('''%%&&&&%%&&&'(*++.03457:;>@BEKQT\bhosvxwwvw} |{vsokhfb_`bbaceehklpqstutrqoonnomnlieb^\ZYYYXYZ\`glrvz{}{xwz} zslgdb`^^\\`eiloqttssssstvsrnj`XNGC@?BIO[dpzyurrsttuusmd[SOQY`dHEDB@=984553101100..../11123566699::;;==>>????@@AAAA?A@@A@???>==<<======<<;:::::::999977997766555310..-,++++++**++++++---.--+--/--,,--..,,++**))))((('''((((''''''''''''''''''''(((((())**+++,,,,,,,--,----.0300../00047<CINRW[_cgiklkiigggfeca]\[YUQI?80,))))(((((('&''&&&&&&%%&')05?O[epwz{z||||xri[K;-&'))-157;?DKOYbkrz~}uhVE6+&"$#$(@NJ>799<JUbqtwtlNCB97>FFPRW[]^_a`^[WRLF@852TC;GPYX\@0269@KVaksuuttstttrlh_QA60,)('''%''&&&$&&'(*,-.0357:;==BDFHOSX^djosvwyyy{ ~}ytqnjhc_^`_aeefghilkoqrtttsqoonmnmlieb^[[YXYXWWY\`flqvy|}{{{z~ }wqjfc_^^\]^afinprtsssrrstttrog`VNHECCFKU_gr{ ~zvtstuuutrke_WVX^diGEC@=9864332200000..../11123566699::;;==>>????@@AAAAABBBA@???>==<<======<<;;::::9999::77887766553210..-,++++++**++++++,,-.---./---------,,++**))))((('''''((''''''''''''''''''''(((((())**,++,,,,,,,--,---../0..../11469?DKPTX[_cghiihffeedca`^^\[XSNE=4.*)))))((((('&''&&&&&&&&&'+.2<IVclwz{zyy{|zuk^N?2*))+/379>@EKOWahpx~~wn_N=/'##$%&BuwD&)3:HTcpvxunK;./27.;VYZ]^`aa][VRLFA93.-7?EQOOL_M955:AJUbluwvuuuututlh_QA60,)(''''''''&'&'(*,-.03579<=?ADFHJQUZ`djosuvxz{ ~~yvrliea`]^`_accfghillopqsusqpppnmnmkgea]ZYWVWTSTTY\djqw{||zyyz~ zupfc`^]\]]^afjlprsrppooprrrpme]TMJGFFJNXblv}yvtstuvuusnja[Y\`fkFCA=:86743111./0//.....011344568::99;;==>>????@BAA@@BBAA@@????>>========<<;;;;99:998998877666644111/---,++**+++++++++++++-----,.--------,*)**)*)))((('((((''&&&&''''&&&&&&&&&&&&''(('(**+*++,,,,,,-----.--..--...//0368;@HNSTY\_behecbaabb``__`^^[WRKA:2,*****))((((''''''&&&&&&%%).3:DR`lswzzzyy{ztmcSC4+**-257<?CFJNU]gnv~ysfVC3'%$##"5id,#'/:CSbntusk`6%%*+7LYZ[_`_a_YUQKE=71/.338HPQOUNRW45:ALYcmuwyxuutwtrok`RB91-***(''''''''()+-/012468;=?ACFIKNSW[aflptvwxz}|ytolgd`\\^``acegffgikmnprtusqqoonomkifda]YVUTSTSTUX\dkpux{zxxxz~~ysleb`^]\[]^bejmpqrrnnnorssuqmg_WOMIGINU\fp{}xusttvvwwuqld]]_djoCA<:986543111...//.....0111245688899;;==>>???@@BAABBBBAA@@??????========<<;;;;;;:998998877666644111/---,++**+++++++++++++,----.---------,*)**)*)))((('((((''&&&&''''&&&&&&&&&&&&''(('(**+*++,,,,,,-----..../..///001269>DINSW[^^bedb`_][\\]]_```][VOF>6.******)))(((''''''&&&&&&%%(+28BO\iqvy{zxxxzvogXF8/--/38;>@DGJMT\dlv}}vl]J8+&#"" 3L="&/48DRantwxZ=$21?MUZ[[_`a`]YVQLE?80,-/20.?MQRTMII65:AMZcmuwwwvuwwtspi`RB91-**))(((((())),,-013468:;?ACEJMORW[_cglquvwy{~ }ytnjgc`\[\^``acfffgjklnppsvusqqooolljfd`][WVUSRQPQRS[aiouy|ywxxz~}vpga_^]\ZZ\_dhjmopppnnnorsttqkf^VPNLKMRXbkt} }xvtuuvvwwurngaabgmr@=;:8865432200..--..../111225677::::<<==<>??AAAAAAAABB@@@@??????>>>>====<;;;;;99:9988788666655554110-,-,****++++++,,++,,,,,,--,,,,,,,,,,,,))()))((((''&'((((&&&&&&&&%%&&&&&&''''''((()**++++,,,,,,----.///00..////2048;AGKOUW\]_bba^\\[[Z[]]`aa`^YTLD;3,******)))('&''''((''''&&&&'*/5?NXeovxy{zwxwtoi[M>411269>@AEGJMSZbhszxreS?0(#$.+10&$&226CQ`mty|iIJ[[YXXY\_`__]YUNJD=70--,.--.^SRONORI47<AOYdnvxwwuuvvwuokbRD:2-**))***'(()*,-.014579:<?BEHILPQSZ\beimpsvy{{unida^\Z[]]__bddefgiklopqttsrqpoonnkifb^[YVUTQPPOOOSYaiovxzzyxyz~{vmb^^][[[Z]adiknqsqpoomorsttpkd]VQQOOQV]fow |yvutvvvxxvuqjecehls=::86555432200.---..../111435677999:<<==>???AAAABBAABBA@@@??????>>>>====;;;;;;:::9987888877755435322/.-,****++++++,,++--,,++,,,,,,,,,,++++)))+**((((''&&'''''&&&&&&&%%%%%%%%&&&&''(((*****+,--,,,,----.///00//00/0336:?DIMSVZ\]_aa^\ZZYYXZ]_bbb`]XRI?61,******))*))'((('((''''&&&&'*-3;HT_msxyyxwwwurjaQD7337:<?ACBEHLOV`gpz}tkYF5+&&=6&%%!%+/6CQ`mtxzzxrme][Z\^_`_\YTPJD=5.,+,----7uXQNLJS?/6;DQ[cmuwwuvvvvwupj_RD:3.++*)**+*++,,--/024689<>@DGJKNRUW[_cgknstvx{~}uoid^[YYZZ[^__acdefgiknoqqttrqqpoonnjhfb]XWTSRONNMLNQWahpwzzzzz{{ ztlb^[ZYYZZ]afjjmqpqonnmoqrssold]XSPPSUZaks{ |yvuuuvvwwwvqjhgikos;:876644322000.-....../12233566699:;<<==>>?@BBBBBBBBBB@@???>>>??==>>==<<;:;:::9999888989::99:8997975520+*)**+-+-,,,-..-,,,,)++,+,,,,,,,,+*)**))(((((''&&&&&&&&%%%%%%%%%%%%%%&&&&(((())*****+,,,,,,--...///////001235:>CGKOUXZ[_`_^]ZYYYYZ^accdb`\VPE<6/+******))**))))('((''&&'''''(+09CP]iryzywwvwxvmeWH=679;>ACCDFJMMS^flu|zo^M<0'&#"$%$$$*/5AQ^mv{||{vnhc]Z^_`a]YUOID=6-+*+-....<jfPQNT^X87>DP[fouwuvwwvvwuoh`QE:3.-,++,,,,-,+,-.123569;>@DGIKNQTW[^beimnrsvx{ ytmd_ZXWWWYZZ]^_adefgjmopprsssqpoponmhhea[VTRQPNMLIKMOV_hptyxxwxxzyria]ZXXXYZ]afimproonnonquvtsojc]XTRSUY]gmu~ ~~|yxuwuvvwwwtroiginqu:9876654321111.-....../12233566699:;<<==>>?@BBBBBBBBBB@@???>>>??==<<===;:;;:::9999889:=>@@??@ABBBA>><83.+***./011121110/...--+,+,,,,,,,,+*))*))(((((''&&&&&&&&%%%%%%%%%%%%%%&&&&(((())*****+,,,,,---...///////001358<BFKORWX\^^^_]ZWVVVVY\accdba\VMC;4-+**++**))**))))('(('''''''''')-4>LXgpuxxwvuvwtqhZPB::;=?ADCEFJJLPZbjrz~zqgVC4(&##$25!#)-5AN^luz}{xplf`^_`_]YRNIB:5/,+*+,--..2QbSSSObgB7<FS^gpuwvuvvvvvspi]QE:3..-,,,,,,,+,-./14688:<>ADHJMPSVY\_cdhmqstww{ |wmg`[WUUVWYZZ\]_adeggkmopprsssrpoponmigc^YURPNMLKKIGIMT]fmsvyxwwz|xof^ZYVWWXY]ahjmmqponoopquvtsojb\ZXVUW]biqy~}}|yxusuvvwwwtrojhioqu98766544321110.-.....//123345677:::;==>>>>@ABBCCCCBBBAA@@@?>??????<===<<:99:::9998889;@DFHHHHIIKJJHIFA:3.,-/145679:::8854110/-+*---,,,,+***)((((((((&&&&&&%%&&%%%%%%$$%%%%%%%%&&((''))*****+,,,,--..-.....//0011368:>EKOTWX[[\^__\[ZXWYX\`cdefc^[TKA92,*+++++++++*))))))))''((((((&&),2:GUbmtvxxuuuxvskbSH=;=?ACDEEFFJMPW]fnw~~wm[I7+##!#*-##'.5@O\itz}~{yumfc`_][WQLIB92-+,++,,-//115FWQKNWV<8=GT^hrxwxuwwwwvsnh_PF:42...-,,,,++-/12335::<>@CFILORTV[^acgkprvwxy||yrkdZTUUTTUVXZZ[^_ceghlmnooqssrqpoqpqmjea[XTQPMKHGEDEFKQZflswxxywy|~wnf\XWWVWX[^bgjlmqromnnoprtsrojb\YYWW[`flt|~}|{||yvsuwvvwxwvromklpsw8766553332110..-.....//1234556779:;<==>>??@ABBCCCCBBBA@@@@?>>>========<;:;;::999879:=AFJMOOOPRSSRRSROIC931258;>@ADDDECB?<:9730/,--,,,,,+*****)))((((&&&&&&%%&&%%%%%%$$%%%%%%%%&&''''))*****+,,,,--....../////01357<@EKOSV[\\]_^]`^\[ZZZ]`acdedc]YPG>70+++++++++++*)))))))))'((((((&&'+07BP\iquwxvttvvsmgYNB=?@BDDEFGFHKOSYahr{{scP?0'$"!#""#'-2@MZitx~~{yungb_][WRMFA:2-+***+,-/00242LWKII[N68@JU`jtxxwwwwwwwtoj^OE<50....-,,,..-/12335:;=>ADHJMPRVX[^beimquvx{|}|wtng`WSURSSRTVYZ[^_adgjlmooprssrqpopomkgb_YUQOMKHFDCAACHOYclrwxwwx{}~wnf]YYXVWY\_bhknpoopnoorstutspib\YYY[]cipw ~|{x{}}ywuvwvwwxxwtqmkknsw6666553332110/-,..////0234676778:::<<<==>??@CCCCCCBBB@@>>??>==========<<;:::9989::;=@EINRVWWXY[[ZZZ\YTKC<:;>ACIKMPSTRONJJIE@=81/-,-+++*,**))*))(('''''&&&%%%%%%%$$$$$$$$%%%%%%%%''''()**++++-----..//////1/112248<?DIPRVZ[]^abcc`_^\\\]`baadeca]VMD;4.++**+++++,++****)+))((''''''&&()/5?LXforuuuutvwtpk_SHA@BCEFFFGGGIMMR[dmv}}tiXE5($"$#$##&,4@MZhsz}||yulec^\WQLFA:2-****++,-.//..3YTPIJMV=:AKV_lwyyxuuvxxvsnh]OD=62//....-,..-/023568:<?AFIJMQSVX\_dfjnrvx{|}~ }xrnhaZWSQPORRRSYZ[\_achjlnppsssrrooopolgda\WRONKJGCA><<?ELV_ipx{ywwy yne]XXXWXZ^`chkmnppppopqruutrnib]ZXZ^bgmv|~|zxxx||zwvuuwxyyywtqmkmqtv65666543321100.-0011113534677789:::;<<==>??@BBBBAAAAAA@?==?>========<<;;:::99979::<=AGKPSXZZ[\^^___a_\UMECDIMPVX[^_`b__]YVRNGB=62/.-,,,+++))*))(''''''''&%%%%%%%$$$$$$$$%%%%%%%%'''''(**++++----./////0/00011258=ADINRW[]^abdgffdb_^^^_accddec_\RJA91+++**+++++,++********((''''''(((),2;FS`kruwuuuuwvqngYNGCBDFGFGFFFHIILT^hqy~xo`M<.'#%#$$$(/9AN\grwz{{xtle_\UQKC?82,*****+,,-.//..2LdXJLLX=9AKWbmvyyxwuvxxvsmg]OC<622/110/.,../1023568:<@CFIKNRSVY]_dfkoquy||xtpic]WSQOOOPPRUVY[\^`chjlnppqrrqqqqqpomie_ZTNMKIFC@?;::<AJU_hovwwuwy~ xqg`ZZZY\[_cfhkooppoooqrsurtrmhb][Z[^djqx~zxttx{{xyvwwyyyyxwtqmknpru774455433211110..122113457777899::;;==??@>@AB@CCDDBBA@?>>=>>====>>>=;;:::99987889:;=BGKORVZZ[\]^adbab`]WPNOSX\_acejjhgffc_]XVQIA<52-++-++***)(((''''&&''&%%%$$$$####$$$$$$%%%%%%%&''''))++,,--../////000/001269=BFJNTVZ]]acehjkieec`__`acefdca[WMD>7/+,,++,,,,++,,++****++)())((((((&'*/7DQ]iruuuustxwtpk_TJCDDGEGFGGFDFEFMWcnx~{sdSA1'$$%&&',58DNZgrvwyyurme]WQJE?80+)))++*+--..//001CfVRNPV8=CMXcnuxzxxwwxxvtmg]PD<62322222/-./02213579:=@CGJMORUXY^acglprvz~ |xtpkc\WSPOOONPQSTVW[^_cdegmooopqrqqqqrpomhc^YSNKHCB@=;977:@KT_ipuwwuwz}zrid]ZXZ]\_aehlooqqqqprsuutsplgb\\\^_fmsz}yvstx|zxyxwyxxzzxwurmkmqtu775433432111110//022333568997788::;;:<???@@AAAAABBBA@A>=>===<<<<===<;;::9998778779<>@DILORUXWX[\_bbdfdc`[WZ^^ceggikkmikkigec_YUOH@92-,,**)**)(((''''&&%%%%%%$$$$##$$$$$$$$%%%%%%%&''''))++,,--..//////0011248;?DHLPTV^_bdeijkmllifeca`bbddedd^YSJA92.+,,++,,,,++,,++****+++)))(((((('&)-4@MYdnsvuutuwxwsofYOHDDFFFFGFDA?>BFP]jt||vjZF6+%%&&).17>FN[grvwxxurmcXRKD>80))*))+++,--..//000?baQLOLE<BNYeovyzxxwwxxvtmg]PD<843222221-.//0213579:=@CGJMPSVXZ^acglprv{ |ytqkc^WSQPOOPRSUUWYZ[^abdhjmooopqtsqqqrpolic\WPLFC@=<986568>JU`ipuwwuxy ypfb^\\\]^_bfinpqrrqrrtuvvvtqkea]\]_bhow~|xsqvy{zzxwwvxxxxxwurommnqs6654443210121121113334446577899:99::<;=???@A@AAA@@>@@@?>=<=>==<<==<<;:9999887666569;?BFJMPSUUTSTX]aceegec``bcfegiiiillkliigdcb`[ULE=5.,**)**)(((&&''&%%%%%%%$$######$$###$%%%%%%$%''''()*+,,--..///////01567<@EIMSWY\_dhjmnpprqojihdcabccbca`[TMC<5/.,,,,+,,,,,-,,++++++++****(())((((*-2<HUdmsvvvvvvyywti_SIEDEGGFEEB>;;<@LXdrzwp_L;.&&')-05;@IP\gqvwwwspk_UMF?5.+())**++,,,-..//1114NeRKOU^M?Q[gqwzzxxwwwwusmh\OE=854332220//////12479;>ACHKLNSUWZ^bcgmqtx~}ytqkf_XTROOORRUXXZZ]^_bcegjkmnopqrtssrrspnnid]VQKDA>;:762248?JV`kqvyxvx{ xogb`^]_``cehjnqrrrssuvuxvvsric^]\^bflsy{vtrtx||xxxwwxxyyxwtqmkmort6654443210122221114455556577989:::;;<=>????@A@AA@@A@??>>=<<=<<==<<;;::99989966654589<?BFHKJJIJJIOTX]_ehhhedegfeedddceefggfgihhheb[SLA60*))**))(('&''&%%%%%%%$$###########$%%%%%%%&''''()*+,,--..///////0469;AGLQRXZ`dgimoqsswuromjfecacdcbb`[UOF<51/.,,,,,,,,,,-,,++,,,,++++**(())((((),19DR_jqvutttvyzzumbXMGFFGGFEC?;878<FTbmvzseTB2)')+049?EMT]hqwxwwroi\PH@8/+))))**++,,,-..//1122=ghRPIZaJM[hsxzyxwwwwwusmh\OE=85434322000000004679;>ACHKMORTXY]^cglptx~ }ytpjd]XVTUSUXXZ\]_``bcdhiikmoopqpqsrsrrsrpomf`ZTMC=:97531028@KWakrvwxvx{ xogb``___adfiloqrssttvvwxvvrpic^\[^bgow}|xsppsy{{yxxyyxxyyxwtqomnprt55554442232233333455556666679;;;;;<<<>>>????AB@@@@@?>>====>=;;;;::::::998877555544557:9<>=>>:99;CIQW_dhihgfeeba\WTTX\\]]]`adfhjkmj`VLA4-*)**(()('&&&&&%%%%%%$$$$#"####"$##$%&&&&&'''((***+,.----//0001247<?DKPTVY_bgjmoqrvuyxwtqmieddddccb_YVQFA92,,,,,,----,,..----..-,----+**())))))),27?N]iqtuutuvwyywpg\QIGGHEGEC=74449AO_ky ~wkZH9.*-04:>CJQX_hqxxyvqoh\OD90*)))))****+,--...//103;RdTTGNNRQZgtz|zzyvvvvtqng\OF>:7665553110000013567:=ACGJMORSVXY`bhjnsx}|yvphc\XVWWVY[]abceegjklnnpqrssqqrssrrrqtrrqnje_XQJB<65532027@KValsvwwvz} yohdb`_^_bgfjnrqssstuxxxzwurlfa]]]`ejry |wqmpuy{{xwvvvxxwwyxutpoprsu55554444334344445555556666679;;;;<<<=?>>????@A@@@@?=====<;<<::;;::::::998776655433331456653300029@HQY]begefb_YTOHFFFHKLPQTY\afkmpnleYL>1,+++*))(('&&&&%%%%%%$$"""#####"$$$$%&&&&&''(((***+,.----//001249<AFKPUY\cginnrtvxz{|{wtrmjgfdccb`^YVOH@82-,,,,,,------..----....--...,+*))))))),/5>KYensuutuvwyyxtj_UMHFGFEC@:51024=JZgv~yqaN>30139>BHNSZ_hqvxyxsnh\L>4-()*)))**+++,--...//014=TNQOKQLBM]jv||{yxvvvvtqnh_RF>:7665533111100002467:=@BEHJMPSUY[^`eilrx~ ~{wtpjd^]XZ[[]`bdeiillnpqqrrvvvvttstsrrrtuttusokf^XPF@;8653238@KValsvwvx{~xohec`__`cefjnrsuttuvwyyywuqle^]]^`fnv||wplosy{{xxwvvxxwwxxutqpoqsu66554444444455555555665566899:::;;;=====??????????>><;<<=;=;;;::::::99887654543211/..0110--.,+,.58AJQY\]aaa\UMC:556878:;AFKQYcjlrvtmcUJ:1-.,+*))'&&&&&&&%%%$$$##""########$%%%&&'''(((**+,,----./001348?BHNQY]acfjmrsvwz||yzzvrnljeddbba]YRMIA:3-+,,,,------........//./012210--*)))))),/3;IWcmrutttvvx{wuoeWMIGFEC@=72-,.28EUbq| {vjYH:778>BGKOSZbjqw{yxtnh]M>3,))))****,,*,....0/0023ORY\RPRTKA[kuz|zywvwwvurmg^QF>97665432221100111345:=>ADGILPSUWY^_cfjqw~ }{xtojea__]^abdehijknprtuvwvxxwvvvttrrrstuvvwutrmi`\SHA:743126@LWbltxwvxz~woib`_^`bdghlprttuuwyzzyzwtnic^]]_chqx yrmkotxzzwvuvvxxxvxyvurpqutt6655444444445555555566668899::::;;;===>>??????>>>>==<;;;<<<;::::::::99886644442211/.....,,+,++,.04:DINRVYYXRI>40..-,+,.027?GPZcjquwskaUH<751/+)(&&&&&&&&%%%$$$##""########$%%%%&'''(()**+,----.//02358=DKNS[^cfhlnotwxz|{}yxwsplihecb`]ZXRKF?92-++,,,,------..//....///0124443.-,+**))),-2:FSaksutttuuwyzxoe]QKGFDB>:50,**06AR_my ~woaSC<;>BGKNSW\djqw{zyvqj^O?3,*())****,,,-,,...01123?bocRJKHPSaluz|zywvwwvurnh^QF>976654322211001113457:=>CEGKMPSWW[\_djpx~}zxtoieaa_`aacdfiikmmnqtwvxxzzyxxxutrrrsuvyyzzyvsphbZQJA;86538@LYdntwyxz{~wngbba`_adgimqtttuuwy{{yxvrmfa^]]`cjt| zqllotxzzwvuvwxxxvxyxurprvvu65554444555555555555557788:::;;;<<==><=???>>>>>>>===;;;;::;::9::9:;988976554321100//..-,++++++**+/5;AGLLONJC93-*))******+.4<FOZcmrwusj`RMD=:53-*(&&&&''&&%$#$$#"#"#####$$$$$$$%%&&''()))+,--..///0258<AJPT[_cgjmprsuwwz||yxwqonkgd`b^[XSOKD=61.-,,,,----.-..//./....//022446540/--,,*))*,09BP\gpttttttvxzyqi_VLGFC@<83.+().5?LZiv }tk[LCACGJNQSV\cirw{zzwqi^PA4.******++,,....//..0022MnaAKHJAMU`mv||{xvvwwvvtoh^QG?:855554411220011133556;>AEEHJMPTVW\]bhow} ~~}{yvrmhdbbcbacddfghklnoqsvxxyz{}yyxusrrrsuw{~||z}|xqkcZRIA:6448AMXdosvzyy~ wmfb^]\]_cfinrstuuuvxyyzwtpld_]^^cgow| wpkipuwyywwwwxwwxvxzywsopruu65554433555555444466668889:::;;;<<<<>=>???>>=====<<<::::<8::;999899988765544220/....--,+******))**-4:=@CA@:4,+*)****))))),.2;GP[eluvtqiaZROJE@:3,(''*++*(&%$$$#""######$$$$$$$%%&&''())*+,--./000148;BGMTZ`deknqqtwwxy{yvtspmkhhdb_]XURMGB;51---,,,,----....///.0000//1135456520.-,,,**,-18BMZensuttttvxywrj_VLGEB>951-)(),3:FVdr}{qbUMHIKMOQSX\cgptvwwwsmbSE>;,*++**++--...../..0246VcJBJGKIHW`mv||{zwwxxwvtoh_RH@:8555544442201111333568;>ADFIKNQSUX\bhpw} ~}{|ytplgcbcccc`cdefhijmopqrtwy{||zzxusrrrsvy{~}~|yrkdZRH?7248AMZfovxyy{ wngb^\[]_bejorsvvuvxz|{xwsnha^\\^bhpw~tljjotwyywwwvwvvwxz{ywtstuvw556677555556766677777788:::::;;;==<===>>>>==<<<<<<;;;;;;99<;::888777655555223///.,----++))))*)(()(),1569840,*(((****))))))+-39EP[equvurlgb^YQMF>80,-23322-'%$$##########$$%%%%%''&(())*+,---.//0147:?FMSZ^dgjlnpqrvwwwusromkifb_\[XTRNHD?730----,,----../-.0/../111122224467641/----++++.15>JUdlrttsssvwxwslaWLFBA<83/,)''*28CRaoz }wl_UONONQRUX[`fjrwwwwtmbTINJ-)))**++---...////016QlG7<9?IIJVamw{|{yxwyxwvuqlaRG@<96655433333111133443689<>AEGJMNPRT[`hqy~}|zyvrnieba`aaaaab`abdegghjnrsuxzzzxvtrrrrtvwy{z~~{qkbZQE<549DP[fpwyyyz~ypje\[ZZ\`djnsuuvvxyz{{xvqkd_[YYZ`hs{}skhjnsxyxwwuuvwxvx{{zwstuuvx666677556666766677777788:::::;<<==<===>>>>==<<<<;;:::::::999998887776555543210/..,,,,,++*)))))(())(*,-00-*))(())))))(())))*+-28CP\ipuwutpllfa[TKE?:;?@A??71,%$##########$$%%%%%'''((***+,-...//037:>CJPW^cfklmopstttsrpnkifda]\ZXSPMID@;62/.----,,----.../0////011112222335652//..--,,,,-03;GS`jrtsrsstvwwslbXNF@?950,,)'').6?L[iu}~}sg]UOOPUUWZ\^cglrvvvsk_RPGB/*****++----../////25a]98IQOOKUclx}~}yxxxxzxuqlbTG@<966554333332246887755679<>ADFHJLNPX^gov~ ~~}{ywvsmgda`^]^]\Z[Z[Z\]`cbdfimquvwwtsqqrrrtvvxzx|~~zrjaYOE<9;FQ]hqxzzz} ztlf^XWY[bhmrtttuvxyz{yutnh`[ZYX[bkt|{rliknsyyxwvwvutuvx{{zwtsuvuw77757755776676558867889989::9<==;>>?<<====<<==;;:999999999988898965555332322/..,-,,++*****))(('(('&'(())''(('())('))((((((((*.4=GS^hrwyvuuspnhb]VQNOPQSSOHA7,%#!""$$###$%%%%&&'''''()*,,,-..011258=BIOV\afikmoprutrqniiffc]ZYVTPLJGEB>820...-------------.001111334422211344220000.-,.---/39EP_gqtssrstvvusndYNEA=84.++)'').3:GWgpz}~zoe\VTSUVXZY[\ciosvutmaXS7>7+,,++,,+,--../000136UwgIJITTQOYdpx}}|yyxzyzyyslcUG@<96644432213469=<=<<;86579<=@CDFIJPU]gry ~}{zxvvpleb^\YYUVUTSSSSRTTUXZ]afknnqqqrrrrrrrsssuxxz}xribVKB>?JS_jqy{{z~ }wpg`]Y\^cjpsuttuwxz{zwsqjd]YWWW\dmw~{pkikmsyyxwuuvvwwvw{{{vtsvutt776777667777666677778899:9;;:;;;:;;<>>====<<;;;;999999998888888877664422120/..-+,,+++**)**))(('(('&'(('''''''())('))((((''(((+.6=HR]hrvyxxwutqnhfa`acdffd[SH:+$$""#####$%%%%&&''''())*,,,-./11147:AFMSY`dgjlnpqssolhfc`\\YURNKHEEA><750/.-..------------.0111121334443322452221110/-....-/28CP\gorssrrrtttrmdYNE@<72.+)('''+17AQ`lw}~ ~uh`YWVWWWWUWX^fmtuurhZXL7@:,,,++,,+,,-../00022>X_\TWC@NUUXfpx}}|yyxz{zyyslcUI@:866444333389<CFEFEDB>:7778;<=@CGJPY`iry ~{zzxvtnic_YTSPNMLJIJKLLLLMPQTX]ehknqpqqqprrrrrqqqruvz~|xpg[TIABKT`krwyy{} {tme`^^afkpsutuwxzyzxurnf`ZVWUX]eowwohgjmsyyxwvuvvvvwy{{zvrrttss7768988869887777667878::::;;;;<<<<<<<<<=<<<<<<::9988998888887777555521111110---,+***))))))((''''('''''''&&''''(((())))''''(('(,24>GQ[cjosvvuttssopoqruvvrjeWI9)"!!""$$$%$$%%&&&&'()*+,,,,-/012369=CIOV\afilnopsqlkfb\ZVRPLJHGCA=:9842/....------....----00012233555555445544322210////-..028BMXeosrqqqqstsqmeZOE>;60,''((')+.5?LZft|}~ ynf]YXXWURRPV\bjrvuraXY@6<2,+,,-------../01003;[f^Z`[;HTXVhpz}}{yxxyyyyztmcUJ@96545543246;?EJNPPROKD>967779<>AFKRZclsy }||zxuqmg_YSPMGFFEDEDDDEFGGIJMRY^dgjloqrrsrrronkklmmoqv|{tlb[PJFNWblsy{{} }vqjfeefmpstuuwxyyyzwsnhb]WRSSX]goywlefiouyywvuuvvvvuwzzzvtrqqqp888899997:889977668878::::;;;;<<<<<<<<<<<<<<::::88888877778757745555111100.--,,,+**))(('((''&&&&'&''''''&&''''((((((''''''(('(*+05=GNT]cfilnqrtuwxz|~ zsfVF5(!!""$$$%$$%%&&&&()*),----/002358;AFLSY`ehjlmnoqnhc\YTPNHDA?><;8764200///..------....0000011123345555555566444322000/0/-/.028@LVcorrqropqrqqmeZOE=84/+(()(()+-3:EVdpz}|}{qi`[XWUQQOLOT^irutvbQK?882.,,--------.//000125Mk_WdV8;PWWdqz}}{yxxyyy{ytmcUJ@:7755554459@GMSX[\[YUNB;85667:>@FOV]fnv}~~~||zxuqke^TLGCABBAABAAAABCCDFKPW]`cjmqppqqrttplieebbfhntz}yog_WPOQYbltz{{}~zuqkkjkpsuuvvxxyzzyurlf^URPRRW]hr| ~wmecjpvyywuuuvvvvuvxxxvsrpqpn9999999999888877877789::::::;<<<<<<<==<;<<;:::887776777777667546545311200/.-,+++**))''''&&(((())&%%&''''&&''''''''''''''''&('''')+4;BHNRXZ]bdhnuw}|reUG5&#!"$$$$$$%%%%'(****,-../011358;=BGNV\beiklooomia\TNHEA?=<96534321////----..........//00112333455566775566554433220011../137>JT`kpqroonpqppme[PC;51-*''()()*,/7BP_lw~}} ~umc^ZUQMIGGHP[jqvtkRBKNF>60,,--,,----/0//10/25H[[[i[76PTVdrz~zyyyyzzxxsmdUJ@:75564446;@HPX^bghfb_VKB964358@FKSZdkry }|~}zxvrjbZMGC>?>>@?@@@???A@CCGNT[afimoqrqqssqpje]YUVZ]ckswz}{rld[XUY^gouz}}~ |{{xxtpqqtuvvvwwy{zywtoic[TPOOPU\gq~ ~skffkqwzywtuvvvuuuvwwwsqnnnpn9999999999888877887789::::::;<<<<<<<==<;::::997766666566765555655431110//..,,****)(''''&&&''''((&%%&''''&&''''''''''''''''('''&&%(*059<CDHKOT\dnv| |ocRA.$"#$$$$$$%%&&'(*+++--../013467;?DJQX^dgjlonmmh`ZPJCA=:86544311200////----..........//00123344556677777777665533321000///137=HS_jpsspnmnorolg\QE:4/+)('()()*+/5<JZgs|~|||~xpg_YTPHECCGPZfptfM>D\\M?60,,--,,----.///00.2=RY^cdTIQQOVgqz~~zyyyyzzywsmfXKA:75543448=ENU^flqrple]PE<6538<BKPV`hou} ~}~~|zvrjbVMFA?=>=?<>==><=?>?CGNT[aeioppqpqsqqoic[QJHMTZckquzyvoia]\]bgov{{{zyzz{{zxvxwwwwwxyx{zywsmf_WPPNLOU^it~zriffjqwzxwuuvvvuuuvwwvrpmnmkj888899999987887788889:::::::;<<<<<<<<<;:::::987766665455556566555211000/.-++**)*)))('&&%&%%%&&%%&%%&''''&&&&''''''''''&&&&(&%%&%%%%'*/11238@IPYcjsz xo]L8'##$$$$$$$%(((*))*+--/.0013578=AGOUZaehknolljc\TIC=96654444312200000/-----...//....//1112334455667777777766554333021100//48;ER^iprqnmkmnrpmh]QE:3.+)(&())*+*.2;FVeqz~|z{~zrj\VPKB?==DMXfphI7B^i`QD80-.--,,..---./0./13A]]baUSYXSPYdq{~}{yxyzzzywtneWKA;8444446;AJT]fotyyzsj`UG=747;BJPV_fosz ~~}}||}{wqh_UJB?><<<=<>=;;<==?@DGNV[agjlnnoqqrrplg_WKC<?GQYahmuuvqkgdbadjry}~|}{{}{}}}{{zzzyyxyzz|{zuoiaZSNLJLOU_kvzqhdejrwzxvuuuvvvvuvwwwsoolljh99999999998788778888::::::::;<<<<<<<<<;:::9987776666445544553334521100.---*++*()))((&&''&%%%&&%%&%%&''''&&&&''''''''''&&&&''%%&%%%$$&())*)*0<CKT]flrvyz| ~tdS?*!!$$$$$$%&((()*+,,-../0013689>DJRX]bgjmnmmjd]ULB9874433333311100000/-----...//..../011233355557788777777765554322110110048;CNZioqqnllompqog^SG;2-*)(()))++*,39BP_lv}|zz{~{vj[SLE?:9:AKWbpkN9LjocRE90-...--....-./00123FbibSMjbRUSWfr{~}{yxxyyyzwtneWKA:7443346<BMVblu{}ulbUI<77;BHOV]flsy }}~|}~}{wqh_TJC?=<;;;<<;;;<<<<=BGNV[`fkkmpqpqromje\RJ?76=EPX`flswrmiifgimtz}~{ywsuw|}~{{zzxyzz|{ytmg]VNHIHIOU`mwxmebhlrwxywvvvvvvvuvwwvsomhhge:::::::9998777778888:99999;;::::;;<<<<:;:;99887765555455544443343201/---+,*)((''&&'&'&%$%%%%%%%%&&%%&&''&&&&&&&'&&''''''&&%'&&&&$$%%%%%&%&'+05?FNTZaeilpx~ yiXD-"$####$%&'())*++-,-../012469>AIRV\bfhknnnmg`XPF;7444443333212100000/....-.--//..001111334555579999999988776655442211111148;@LWdmpponkmnnqold\NA5-))*)*))**+,18=KZht{~~}zyy|~}wj_RHA<557?IT`jqJ?auqgVG<4/.,,....--./00003;Zz{eHN\\HIRYdqz|yxvxxyyxuoeXJ@84332137<EPZdpy~umaUI?<>AHPW_emrx} ~~}}~~~~|wqj_SI@;<;:999<<;;;;<9=AGOV]cilnoooononmgb[OG:304;ENV`fnqsojjkloru{}~ |vrniltx}~|{{{{{{}{xqkd\TLHFFIMWdp| ~ukcaglrwyywuuuuuvtvwvvuqnkjheb::::;;:99987777788889:99::;;;;::;;;;;;:9:8776676555444544444432221/0/.,,,*(((('''&'&'&$$%%%%%%%%$$%%&&&&&&&&&&&'&&''''''&&%'&&&&$$%%%%%$%$&&&*09@EJMNSZbnyym\D-$#####$%&'())***,---/0/1369<BGLTY^egjlnnmgc\UJB;7444443333212100000/....----....001112334555679999::::98877665442222111348;@KValpponlnnnppmg_RC6.***)*****)*/6;IXery~}zxxz}|wl_RF=6148=FR^kd=BgvofXJ=5.,++,...--.//../0Cn{v^ONYT>NYcer{~}{yxvxyzyxuodWJ@84311248@HP\gs|~ul`THAAFIOW[cjpx}~~}{|~~~~{wqh^PH@;:99999::99999:=AHPY^dilnooonnomjdaWOB61./3;FQ[cjnqroonprtw{~~zrkebemrx|~||{{{{{}{vniaXOICDEGMXfr}|qjbbgltyyywuuuuuuuwwvtspmkifeb;;;;;;;;::9887888888:8999::::9:::999;:888877765454445444234441220//0.-++**)((((('&'&%&&%&&%%%%%%$$$%&&''''&&%%&'%%&&&&&&&&&&&&&&%%%%$$%%$$$$$$',158;=CKWiv zn[C,##$$$$$%&'())*,,,---/0/136:>DKQX\afjjjnmid`XOF>96545444444333321000///.-----...0001133445567789988999998877665443322322136=BIT_iqrpnkmooprpkcWI<0+++))*++,+-/6;GVbnw}~{xxz}~~yobSD82036<EQ]jaDFowrh]L@5/---,,--....-.02<_klrXGNNZSQRgjt|~~{yxwwyzxwuogYJ@84211249=GR_jw~uk`TLGEIPVZ`gnv|~~||||~{wqi^PF?986857777787768<BGQZ_gimnmmnmmnjgc[RH=6.+-05=FS\eloqqqqtuxz|~} ~wla\Y^fksy~~}|||}|||yslf\SJEBABFO[ht{pfacfkszyxwuttvvvvvvuspnjhgea^;;;;::;;::987888888899999::;9:;;9999:877756666434465444323444111/.-.-,++**)(('(('&&&%&&%&&%%%%%%$$$%&&''''&&%%&'&&&&&&&&&&&&&&&&%%%%$$$$####"""$&',./6BQdsylX>)##$$$$$%&'())*,,,--.001369?DJNU[_ffgknnlhcZSJC<665554444443333210000//.-----...0001133355556789999999998767665543322231247<AIR]fnrpnlnooprrlf[M?4,,)+)*+++*,05<FUalv}~{xxy|}~{reSB72036:DO[g[ERutoj_NA6/-+++,--......03KjjulLGFHMLNRons{}~{yxwwwyxwtngYJ@84212239>HUbmy ~uk`TLHMSXY\dmt{ ~~~||}{~{vpg\PD=;76667777767767>CJSY_fjmomnmmnmkf_YPE:2.++-18ALVajmtttuxy{}~{sg[SPX`kqvz~~}}}}}||zwrkcZQGB??CHQ]jwxnf_bgltxyxvtttuuuuuuuspnjhfda];;;:::;;:9987788889998999;;;::::99988877655554555544333323554100....,+++**)'''''&&&%&%%$$%%%$$$$$%&%%&')'&&&''''''''&&''''&&%%%%#%$$$$####""#"$$#%&',4>N`q}ykW<'##$$$$%&'()**+++,,.12249=BFKQT[adghimnlif`WNG?8766544444442222110000///.------.0001123455557779999::9999888866554433332458=BIQZckpqommooqrvqjbQC6.))+*+**,+.3:?GS`js|}yyx{|~{sgWF:3149:BNYeTK^ztqk`PC70,,,+,,,----.0/3Rqzw_FEL>>LUUeiq|~{yxwwxxyxuofYJ@9412114;AIWcozxm`UQQSVX[agpw} ~~~}{{}~~}|upf[OB<864456766554457;BKSZ`gjllmmlkmlic]VMA8/,**,06?JT^imsvwwz{~~ zpeXKJP\dkrw{}~~~}}}{{xuog^XMD?==@GS_l{ uld`chntxxwtssssssuuttqpnjfec`[;;:;::;;:98777888888899989;;::99::8988664455544444333322222221//--,,+****)('''''&%&%&%$$$%%%$$$$$%%'(++--+('('''''''%%&&&&$$$%%%%$$$$$####""#"###$%&+1>M_p}ykW>'##%%$$%&'()**+,,-./0259?DHMQV[]deijkjjjic[SJA97566544444442222221/000.--------.0001123456657778999::999999776655443333346:>CIQ[dkpqonnoqstvtnfZJ<0****+*+,/26<BKT`kt|~zxx{|~|vl[K=5025:BMWdPSgvurmcSD;2-,,+,,,--,--.08lzuubNED>EQQTffq|}{yxwwxxyxtneXJ@9401/15;CMYfr~|pdYVVXZ\]dirx~ ~~~|||}~~|ytof[OC:644444555444457<DMU\bhkllmmlkiifa[TI@30+))+04:EP\fmrvy{| xmcUJJNWahpwz}~~}~~~|{ytle^RHA<:>BKWco||ujb`chntxwuttsrrrsttsqromjfc`][;::;;;;;9:877788877788889:::::989987776655433333443333221100//.-,+**+++*((''''&&&%%&%%%$$$$$#$$$$%&',03442-+)()))&&&%%$$$$$$#%$$&$####$#$##""#""! $')/:J\my zkXA)"$#$%%&'()))*+*,.-0259AEJNSV]_cdfhkkkkjf_VMD>86565544433332222110../0.------...0001112346667889989::::9999776665553333567:?DIQZcjoqponqrstvuqkaQB4**++,+.036;AEMValu}~z{{|~yoaOA71148ALWdLLjvvqleVF;2-++*+,,--,-,00IzrUK^]TPWLMQPiir~}{zywyyyyxtnfXJ?8411/27?FP[hv rg]XXY\]`elrz~}~|~~}~|yuoeYMA9522232233443348=ENV]ejkmmmnllkie^ZPE:2+))*,/38EO[cjrwy}~ ~uj`VKGMT^irx{}~}}{zwqld[OD=::;BLWgs}sga^bgotvwurnqqrrssssrqnljfa^[Y;::;;;;;:9877788787788889::::9778877776544333333222211111100//.-,+*****)((''''&&&%%&%$%$$$$$#$$$%$&'+05:=9722100.,(&$$##$$$$#%$$%#####$###"#"#"""!"%(/9GVjy|n]J1$%#$%%&'()))*+,-.036:@FKOSX\_acfjkkkjjic\SJB;64655544433333333110/.//-------...0001112345677889989:::::988877665553355668<@EIQXaiprrpoqrstvvtneWF8.-.,+.157;@DJQX`js}|{{z}{reUD82148AITcKFivvrlfWH=2-,,*+,,-----1DtwmZ8OOMSWQRROcjs}}{zyxwyyyxuneVI>843125;AIS^lxth`[[\]]bflrv| ~}}|||}~}zxtndZNA9521112211111149>GPY`gjmmmmmllkhc]WMB7/*)))+/3:FO[cipv{~ }vl`SKJMT^hpv{}~}}|{zvpjbVKC=::;BNZiuxpe`^bhntvvtrpqqqqrrrrqpmjgca^ZX;9;::;;;::89878788877899999888887766665522221111222210000/00//..,+**))))))'&%%''%%&&%%%$$$##%%$$##$&+.37;;:6777765/*%$$$$$$$####"#####""""""""!!!!#$)-6BRcs~~scP8'&$$'''(((**++,./25=CHMQTY]_bdhkkjmkhie^VLD?97665566553333332211///..-------../10011123455678899899999987899764434345676:>@FKPX`iqstrppqsuvxvpg\K;2----169=BEJMT[ajs| ~{z}~xk[J;3347>ISaJJjvtrofXI<4-,,*+,,-----.KxzqJ7UNLIMSMRWchr{~{zyxxyyywtmeYK=752548=ELXcp|}tib^\]]_aejpvy }}}}}~}~}yxumcYK?7321000000000/6:@GQYahmlmmmmlkie`ZUK?5-+++**.4?IS\cjqu{ ~vl_RJKQVajqy|~~~|||{xsne\QG?:89=DO_ly}vlc^^bgnuutsrqppppqqqqomkgfc`\WV;9;:9:;;99998777888778889998886677665543222211112111100/0///..--+,**))))('%%%%&&%%&&%%%$$$########$%(*.166456699872-'$$$$$$$####"#####""""""""!! !#&+2<M]o{vfU=*&%'+-,())**++-/26<BIMSU[^_befhjjjiigc_XOF@965665566553333332211///..-------../1001112345567887789999998789976653554568:<@CGKPU`hosttqppsuvxwslaP@6/..18<?BEJPRW]cks{ v{xo`O>5347>GR^IHlvtsofXJ=4-,,*+,,------;`pjQ8[WNLPYUMTc`fv}~}zyxxyyywtofXK@96689>ELS]ht~|rhc`_^]]^`djov}~}}}~~~}}yvrmcYK?731000....../04:BKSZcjmlllmmlkic]WPE<3-+),+-09BLU]cjrv{ }vl_RNNSZbksy|~~}~}||{wrkf\QG<888=FSco{ |sic^^biovvtspopqqqqqqqomjgdb^ZWW::<<::::;9::9887888888889977765555445543222211222200//////./.--,++*)('''''%%%%%##%%%$$$$%%#"######$$%'(*++-///26661.(%##""""####$$#""""""""!!!"""""#%)0:JZkxwi[F/&%*/23,)))*+,-28;AGKQUZ^`cdfhiiiigfdaYPHA<764555555554433322200//.-.-------..0011112325666788888899889888887775445567:<=AEJMQX_gouuusrrtvtvwsndUG:1027<@EHMPTX[_diq{~s^j|yz|seTD8336<FP_JGkvsqnfXK>5.,,)*,,,,,,-+2LenaPn|iQO^^NLa^Vk|zyyyyyxxvofYJ?;;=ACHLTZbmxznga`_]ZXY\bipw}~|}}|}~~~~}{ztqkcWI=630/...---,,-/5<CLU]djmklkllmjhe]WOD91,**,+/5@HRX^ckrw} |vl`SQRU[elty}~}|}|||{wpibXMA9688>FXdq~{qf`\^cjrvwtsqpqrrrssqoljlge^\ZYY::<<::;;;99988878888888888777655443344332111112222/////.///..--,++)*('''''''''%##%%$$$$$%%#"######$$$$$&&&')**+///+)&%##""""####$$#"""""""! !!"""""#%)/8EWftznaP6)).59;3*))*+.16;@FLQVZ\adcfihhiihgc_YRKB<7655555555554433322200//...---..--..0011112325666688889977899888777765655668:=ADHILQW_emtwusssrttvvtphYJ>859<ADINRVY\_dhlu|wnky~|p{}ujXI:337=EO^KHarsrnfZK>5.,,+,,,,,,,,,-4FSkkuteb`aZNBSfkz}{zyyyyyxxsmf\PDCACGJNRZajr|uic_\ZURSTX`fnw~|}{{}~~~~~|yvuqkbVI=63/.---,,,+++/4<CMW_gjmnlklllieb]WOD91,+++/5?GMU\diotx}~ |vlaUQSZ`gov{~~}|{}}|{upiaTI=7669?J\juynd[[^ciqvwsrqqrrrrssrokjhfb]\]^^::::;;;;:99877878877887787776643333322221111110011000///.,..,,,,*))))'&&''&&%%$$%%$$$$$$$$#########$$$%%&&%%&&'%(*''%$""""""""""""##!!"""" !!!"$'+4>Qboy|sgWB-)1;?A7,**,-169=CJQVX^bfeggjhiihfa]VOLD<97754545566444443221100//...-------../01111233456667766889989887766665666676:=@CEIJMQW\emsvwuspssssuuqj_PC<<>AEJNRX[^aegkpvz xwujfqf[hszyo^M=4249CP[PO_qsrkeZK>6/,*++++++,,--+++8YrlgdpdWVTS]hu{}{{zyzyxxxvoi_UMJJLMPTYahov~zre]ZVRMJMOS]fr|~|||}~~~~}~{xxupibTI>52.----,++++*05>GOYahknlmkkkljgcZULB8/+++04=GNV]binswy| |ulaVRU\biqx|~~~~}~~}xume^RE:6648AM\ly~vl`YY^elsvwusqrqqrrrrrolhgc`\\^`b;;::;;;;:98777878877777787775433111111110000001100.-////--.+,,,,)()'''&&&&&&%%$$$$$$$$$$$$##########$$%%%%$$%%$##%&%$#""""""""""""""!! !! !"#$)/9K]lw~tk\J3),4<?4))*,/38<CGNSX\adfghhhjigda[VOHD>955655545544444443221100//.-.-------../0111123344566776677887788776666655688:=@BEEILMQVZdlswwvtrrrrtssqlbWNEBDJMQU[_dfikpsvz}vut|dPD?@D^^XohJ8049?KZNM^lrrmeZK>6/,*++++++,,,,,,+5M]chkcDHMLS`ir{}{yywxyyyxuqkc\XRQPTV[`fnu|xmcYSLHDBEMU^iv}~||}}~~}|{xwtpibTI>52----,,++++,19@HP[bhlmkllkkkieaXSJ@5/*+-3;EPU]ciorwz|| |ui^WUX_fmty}~~|}}}||xukf\PB84459DP_o{ }uh^YY^elswwusrrqqssssrolheb^^^_bd<;;;::::98988677887766776566421111110000/11100////00/.--,,-,,+,,))('&&%%&&%%%%%$"#########$$""""######$$$$$$$$$"""$$$#""""""""""""""!! !"##'-5CVhs}{raR>.+/75/*+,.05;>DJPW\`cfiijjjiec_ZTOGB<765456544554445443222110/..---,--,,--...011112333555566777788898866666665679<=@CEFILKMOTYcltvwvtrrrrrrsqoh^TLHJOSY^bhknstvyz}mYZqhU[bi^PxVf`_T<:?YnbKGN_prleZK>5.-+,,,,,,,,,++,2;PVWbfQO]IELVeqz~~|xwxyz{{{yvphc^[YWZ^cgmryvk_QLD=8;@MYeq{~~}~}|{zxuupibTI=30--,,++**++-3:BJT\dhkjljjkjigc_XOF<3-)-2;FOV]djprvz{}~}vmbWSZaipx{}~~~}}}|}wuicWJ?62229DTbp}zqh]WX_fmtvtutqrsrsstsqnkfca_`_adf<;;;::::988877768877666655443111111100000000//0///0/.---,,,++*))))(''%%%&&%%%%%$"######"##$$""""######$$$$$$$$"$#"$$###"""""""""""""!! !!"##%)4@N`mz}ufYI5,+-,+*+-047>CGNUZ^bdgiijjjfa\VQJD@;7665663344554445442222110/..---,--,,--../01111233355456766778878876666666789==?BEHIKLMOPS[bkpuzyusqqqqsutsne[TPQUZ_ejquxvX@PZRP@X`^dlNJA@GKhrICLcsqkf[K>5.-+,,,,,,,,-/Hn}tcfjV[JN\Xeqz|}{xwzzy{{{zxvoga_][_chmsx}|uiZL@7339CP\hv}~~~}zyxxwspibTI=30--,,++**++-39CKT\djkkjjjihhea[VMB81,+07BKS]fjoty}~}}~ }vj_XW]emqy{}~~~|}xrh^UH;3122;FXgs|tkc[WX_fmtvurqqqrrqrrqpmjeca`aadef;;;:::999877776655665556422221001100////..-----//...--+++++)))*)(('&&&&%%%$$$$$$$$$$$###########$$""####$$""##""########""""""""!!!! !!""""$(.8GWgsz|xnaR>-*((**,-058>ELRY\`cdhijihd_YTNHB>976335444455553344332211000/....,,--.---../001202344555555556666887666447679:<>ABFHJJLNLNQQYbhptz{vtqqpprtvvqkd]XW[]djqw|~jVQZVZ\cfib\EDLQD@LhwmYGZwsole[K=6/--,,,,,,++-/[~vx}xiiwkRIV_Xdq|~~|yxwwy{{|{zwojeb`bdimry} {qeVF;404=HUamx }|{xxwwvrkbVJ=3.,,---,,+**/3:DNXaeiiiiiijieb\WQH>5.-/5>HT\dkov{~}|}~ ~uj^Z\`hosy{}~~~|xph\SE80-,0<J\iwyumbXTWahosvurprrsrqtsroliecccddefh<<;:::9988777766556655542222100///10////..----/./.----++++*+))))(('&&&&%%%$$$$$$$$$$############$$""####$$""##""##""""""!!!!!!!!!!!! !!!"""#&*3?P_mv|zsj[M6,+*+,/046;AHMTY]adgjmkjd^ZRJEA>9554454444455553344332211000/..--,---..--.0/0012023335555555566667775656679;=>@ABEGIKLMMMOPSV^gnuzzxusqsssuwwtpjd_^`diou~pVIIW_mrklnYGQCFGIIKTb^O[tnmic[L>6/,,,,,,,,+++,.>Yhpkfd_nkPO^^Tdpy~~|yxxxzz{|{yvojeccfimqvz} {qeTD8218BM[fr}}|{xxwwvqlbVI<3.,,---,,+**.5<GNXafiiiiiiihe`\XOE92/04<DOYbinsy~~}|}~ ~uka\_cipvz{}~|uph\O@5.++3>O_ly yrk^VSXaipuvsrqrrrrssssolieddeffghg::;;;;99877766656655444200111110/0./..//..--------,,,,,+**(*)'&&((('%%&%&&%%$$##$$$$$$$$####"""###""##""""""###"""!!!!!!!!!!!!!!!!!! ! !! !#$'.8GWfpz|wocTC2,--0136:@EKRUY]aeijjjhaYSLGB=:674445555444555433333321100//..--,,----..//./11111112445555554455555445568<<>@ABFIIJLMMNONOSX[ckrwxxtsrrqsvxxxuojfdehlqw^;BX]Wgag}pVd\KDCGOU[VOUijnlcZL>70,,,,,,++,,,,6SKNVWaPIWQNXR[Xepz}}{xwxyzz{|{{xsjfcffjotwz} zqdSC725<ER]jv}|yvvxxvqldWI;2-++,,.-++),/6=GR\bfhihhiiige`ZTMC8127;DKV^ekrv|~~}{} ~uk`Y_dkqw|~}~~{wodYL=4-+,5@Rap yphZVSWaiortsooqqqrrrsrmkheffgiihge::;;;;9987776665552211210/121221221/..//..------++))+++)***)()(('''&%%%$&&%%$$##$$####$$####"""###""##""""""""""""!!!!!!!!!!!!!!!!!! ! !! !"#%*2?O`jt}}wl\O@222568:?CGMQW[_bfhijgb[TMFA=:7654444444444444543333321100/...--..----..//./111111334444555544555545568:<=@BCDFHJJKLNNOONOSTY^hnuxwusrrqsvzzzwuojfijmt{u^VYP3]s|wj\TWRSMMYWQNU^fd`YMA7.,,,,,,++++*,2A]aPZ[Q]T:BEJT^eqz}|{xwxyyy{|{{vojfehhlpuux{| zqdSC837>IVaoy ~~}|yvvxxvqldWI;2-++,,-,+++-29AJT\bfhihhiiigd_YQJ@725=BJS\bhnsw{|}{zz{ ~tia^`hlsy|~}~~{wodYJ;4-,/6CXdt xmeYURXajptrrqpqqstssrpmkjijjhhhfd^<<;;::998766665533331011021146674310//..--,,,+,,,,++**++)))'(())&&%%%%%#%%%%################""""$$""""""""####""!!!!!!!!!!!! !! ! ! "!!""#&,9EUcnx}zreYK;669;>>CHLRTX]adfhigc]UPJB=8766654344445544554433333211//......-------..../11001243334455554555443688:<>@BDEGGIJKLMNNOONPOQSYbjqyvttsrqsvy|}ywrmijinuz kSyyob]LEV^YUQOMOPRYa\QC71--,,,,,+,,-*-5ZgWTaVcT:GLSX^hu|}}zzywwxz{{{xslgcccfiloruyy} zrdTE:58AN[gr{~~{zyvvwxwslcVH;2-++++****+.4;FNV\cfghgghhhhd_WOG<59<BHPX_dinrvyywvxyz }ujbabjpuz}}}~~{vndWG:3,*0;HZjxvlbUQRYajqvursqppqrssqomllkljjhddaZ<;:::::8876655643321//001/1458;<:8531100....-+--,,++****))(''(((&&%%%%%$%%%%$$####$$########""""##""""""""####""!!!!!!!! !!!!!""%%)2<IZhr{}vmcWG<;=?BDIMRVX]^beehhf`YSKF=976554664444455665544333333110///....-------..../110012243344444434445578;<<?ACDFFHIIJKLMNNOOOOPORV]dluxvtsrqsvwz}{ysnjjmqsv{ |{|{sg\NGV[XZPLJMOLLY^VH=4.+,,,,,,,,,,-5E]\G[VWOFWVG>Pdmx~}zzyxwyzzzxuojdbbbbgkkosuxz}ypeTE96:CP^jv ~~~}{zyvwyxwslcVH;1+*********06=GOW_fhiggghhgfb]WOG?<@DIQX\bhllruvvsrswz} ~uk``flrxz|}}~~~zskbSD81,+1;M_n{ |si^SPR[bjrttrrrrrrsttsqonlllljhf`[V;;99999876665343221///.//2278<>BB@=9554211/....---++*)))))((''('&&%%%%$$%%%%&&%%$$%%$$##########""""""""""""##""""!!!!!! !!!!""#%&*3BN^ht}zsjbUGABCHLPTWZ]_bdefgeb\WOGB;85455556655445566555544442200000/..----..----///011011212222222423435678<=>ABDEEEHIHIKLLMOOMNONNMOPU^grvvtststtwy|}zuoljllmpsz|{{}~{tk^RNUOPUVVMGKOSSTWRG;4+..-,,,---,..05:=QNQEDOPC>Pkgw|{xwwyyyzyxutlib[]^aeigmpquwy{~}wlbRF89=HTbny~~~}|{yxvxyxwslbVG90+))))**))+18@IRZafgihhhhggeaZUNFBAEJPY[_dhkmprqomorvz~ ~rgbbgmtyz{}~}xri]NB7.+,3?Obryqg[QPR[clquvtsrssttttrrppolkkkfc\UP;;:9998876554333221///./.16:;@CFIFDA><;855431120..,,+***))((''('&&%%%%$$%%%%&&%%$$$$$$##########""""""""""""##""""!!!!!! !!"#"""#%'/:GVcnx|wrjaSHHKNRVZ]`abdefedc`\UNF>966666666655555566665555443310000/..----..----///0//01011233545555555789<=AACCEGHHIIIJLLMNNNMLMLKHIJOWblsvusqrttwy{|zuolihfgglpy|yz|~~~yqj]UURRNNLNIDGOPNJMPJD9----,,,---,--.3=PWPLCISQCAOmiw|zxwwxyyzxwtslg_YXZ`fknolnnorvy|}{ukaPD89@JVdq|~~~}|yvvvxyxwslbVG90+)))))))),29AJS[bfghihhhgfb_YUMIDGJRX^_bejllmlkiilpuy| ~rhcfjpuz{|}~|wqh]N@6.+*6CTdtwndVOOT[dlttsssrrrrsttssqpoonmhc_WNI<<::99888554544121/..../036:=AFJMMKHGEB??<<;87765411.,))))(''&&&%%%%&&%%&&%%%%%%%%$$##$$########""#####$""##$$""!!!! !! !$$##!!$&+2?M\ht~|vrkdYTRVX\`ccefffifda_XQKC<6687667777776656668976553333100000/-./....-/./-/01224455566866786688;>???ACDFFHHIIJJKKKLJKKKMLKJHFCDIS^iqtvtsssuwxy{zuqkfd`_behpw{ ~yyxz{xsnha^[YVTMMJIJFCGKGCHPMLH/).-,,+-,----,06FXQQIQUGA=>Jky|{zwvuwzzwuurldZWWY^gmcF@Dcknquz~{uj_PC9=DO]hu}~~~~~~~~}~{zwwvvwxxvrkaSF80,(('''(()-4;BKS\cfghhiiiifc`[XRLKLQV\_ceiiijihdddintx{ tkggmsvy{|}}}~~xofXK?5-+.8FXjw~ulbWPNS^gmttssqqrrtttuuutspoolf`ZQIH<<;:98875554332210...../036:?DHLRRPNMKIHFDDC?@@><;9952-+*))'&&&&%$%%&&%%&&%%%%%%%%%%$$$$#########"#####%$$#$$$$"!!!! #"##"!$&'.8FXepz}{wrle`^^]adffijiiigc_[TMF>999886677777777888:89976643301000000//0/0//00.144456668::<=>=>@@ABBDDFFFFHIJIIIKKKKLLLLKKKKLKLJHGCB@?CLYdqtvtssttwyyyxvpib\XWZ\`krx|yywvtnecaZYWVYWMKLJE><CGGGIPSS\L%-.,-,+,,-.--.4LMWf[ST@<7:Iizz{xvvuwyysrurjd\XZ[jhM=D@8]iejsyzxsh]L@;?GT`mw~~}}}}}~}{ywvvvwxxvqjaSF81+(('''((*.4=FNW^eghghiiiifca^ZVPORW[^aeghhifb_^_cimtx{ |sjiiotz|{||}~}wofXH=4--1;L\n}~th^RNPT]elrussrrsstttuuuutqpmhc[SMHJ;;:98777543332101/--.....26;?EILRSSRQOOMKLKJIIJIFDB@=951.*)('('&%%%%&&&&&&&&%&&&%%%%%$%%$##$$$######$%$%%%%%%%$#""!! !! !!!!$&(-5@Q`lv}|xtplkhhhkkkkkjjieb^YRKD?;9999777788988889;<>??>>:64221/110/21132134577;<<<?BBCGGIJLLMOOPRRTSRTTSSSSRRSSRTRPPONNLJKKIIIFCB>;:9=IUcotusssstwwyzwtpe\SPNPS\cmqy|xwwwpf]YVRQMNVQFGFC;;?CGHKMOMOQ]@#*,-,-,,,-,--0<<Wc`^S=:3AZp{~yxwwvvusjhppje`\\aZ\UE@7Jcaajswvvph[M@<BJWdoz}}}}}}|~~|zxvtutvwwuqj`TG80+((''&'((06@IOZaeghhhhhjhhfb`\XVVVZ]`dfeedb_YXX\bhotxz~ ~wmklqty{|{z|~~|upeXI=4..5>N_o}{qf[QOQY`iqttqqqqrssttuwwutrolg`XNHEJ::8877775442220/..------/247=BGKQRRRSRPONMNPPPQROPLGDA=940+)('''&&&&&&&&&&&&%&&&%%%%%%%%%%#$$$$###$$%%'%%%%%%%$#""!! !!!!!! !!!"%').4>JZfq{|ywtussqpooppoojgd^UNHA<;999988889989;;=BFIJMNKIC@853223333467:;=<?@DFFIKPPPRUWWY[Z]aaacdgffeehgeeeeeddba_]\[XTQNLJHGDB?<:855<DR^jtvtssstvwxwwtmcZMGAELU^iov~~zxvwxskc]XSMJMRQJFDDAACCGLOQNLNJ^iL*&--,-,,-,-/07>`k__K:96Maqy}ywwwuvwp_V_hiea\aS=EH?KX`YW[fryxuneXL?@EN\it|}}}}}}|~}|zyutrrsutqoh^RE80+((''&'),19CKS[cghhhhhhiihhfea][[[\_acec`]YWTRW^ekqtxz~ xpmntxz{|{|}~~~}wqgZK=4//3?Rcrzod[QNSY`jqssssrprstuuvwwwwspme^ULEDP888877766431111/.---,,,,,.059?CGLPQQQQQPPOQSTTVVSUSSPKGA<52-)('(''%%&&&&&(''%&&&%%&&&&%%%%%%%%&&$$%&&&&'&%%%$$$$##"" !!! !!"""$$&+/4;DQ`js|~|zzzxvxvvurtrrnid]UNF?;:;;::9999::::=AGPTY\_`^[UMA654549;=@CEFIMMOSVVZZ\^_bedghiknmorssttuvuvvwwvvtuttrpooligd_[VOKGD@=87434<GP]irttssstvxvyvtndVLA:>GR[enu~ zxvuwvolf^XRVWTQKHDBACBDDILNTSLMJDl\9+---,/2///1@\rm_[C98>Xf]p|zxvvvuvo]]b^`[XZU[I^F@D]VMQblqurspgYKBBHS_lv~}}}}}}}~}{xvurqqrsrpmh]QE82+)((&'()-4<ENW^dhiihhiihjjigfc^\\]_`bcdc]URONPW_gmruy{~ zrqqvz|{zz{}~}|xpfZL>5229DVgvxnbYQNS[bkqrrqqqqqrrtvwvvwwuqoe]TJEHU8887776534311/00.---,,,,++.269>BGHJJLLMLNPPRSSWYZXXXVSPLGB:4/+)(''&%&&&&&('''(((''&&''&&&&%%&&&&&&''&&'''&%%$$$$$$$"! !!! !"""#%%(+/27>JWblu}~||||z|zzywwuusmg]UNE?=<;;<<;;;:9;;@EMV^eknprqoj`QC;:=@CHIMQTVXZ[]cdgikmnprrqttuwyzyx{|~}~~~~}|}}}|zzzywtpjf_XPJB=94348>FR^jsuussstwxwxxtmcP@228BNXaju} |xvvwxxuqjha^XSPPKGFFGEEDFHIOYSDBBJ`h6)+--,..11;OhhVWO999J`eS]{{yuuutoh^c_VWUPOLOb\]]EOR_gsmtp}uqdWJACJVcox~}||}}}}}{{zyvrqqqqrrpmh]QD70,)(()'(+.6>IQX`fhiihhiihjjjihfa]]]_aa_]YTQMJLPW`hmrwz{~ ~uttx{{|zz{}~~~~yrh[MA833;GXhxxnbWRRW_fkqrroopqqrrtvwzzyxwtpi`TKGJ\99976553331///....,+++,,+++./379=ABEGGHIKNNNPPUWXYZZWWVSQIC>84.+*'''&&&'((('((((''''''''')%%&'''()(''&''''%%$$$$%%$$"! !!!!!!""!"$&(*,/26<BNWdmtx|{}}}}~~}}{{yxvsld[RIB?>>><<=<<;=>AGQV_isz}{qcUHEHKOSX[_chhlmnqrstuwwy|z{{{|~~ }zuoibZQLF@<>ACIR^hquutsssvvxyyxy{{|^/-?IS^jt| |ywxvxyzxxricWVQRNMIKHFHEFGIMVLCADB?UK$*/,//1<`SKQP\SN9;;Q_jketzztswqeb^ZRPSQJJFDRSdaZJNOWbjih_WlbUICEOZhp{~}|||||||{zywtponnqrqolg]PC70+)(((')+18BJT[bgiiihhiiijjiigd___^^_^YWPLIGGKQXbjpuvz|} xuux{||z{}}}~~|xqh^QD;44>K]m|xmaWTT[agmqsspopqrsrsuwz|{zwuohaULIPb8876544200////..--,+++++***++-/1488:==@CCEFHJJPSUWYZ[XYXXRMIB;62.+('''')))(())))((''''''')''''(())))('((''&%$$$$%%##"! !!!!!!""!"%'(*+.25:@ENXdjqsuy{~}~~|{yyuld\TMFC>>=<<<=<?DIOX`kv}qfYSV\`dgloqstvwwy{{|}}~|}}}{{ |xtmf`YVRONNPV_irtttssstvvw £ z(3EP]gu|zywwxy{zvkiaQRXYRRNKIJJIJOQTQQA6DA<;WT,(121<OgoWb^YTM7<AS^dquwxwvtvrdYOR[SNMMJKQ_[ZZJS_^_\I[aQ^laVHGJR\hu}~|||||||||{zxuromnmpppnjf\OC70+)(((').4<GMU`fhhihhhiiijjihea]]]\\YVQLIEBBFLSZbjpuvz|}|yyz{||z{}}}~~|zsh_RF=66?O`p~wl`VUV^djprsspopqqrtuuwzz{zxvrle[QNZi7765333110//..----+++*))**))**(+--/1325:;<@ADEHLORTVXYYYZXVRNGA;51.,)(****)())(()(((((''(())))))**+)))'((('&&&%%&&$$#""" !!""""$%%&()-.046;@HOW_fimpsw{{}~}~}{upicZSMGB?@>=>?BHOX`kt~|reacgkptuwz|}}||}}~~}}}}{z{{{{}~ |zvsolgd_\\_fksuvuqsrstw£¡¢¥«®¨h,>OZiu|{ywwxzzzwnkcODT`XYQLMKLQRUUPJBOFE<=:BcXJVF0^ysrnW>QUQ:;JY_VivrpvxvvriURW]THJJJLRTMT[L[UV\U@`g^_nbRGHKValv~~|{{yy{{{{|{zwsponllooonje[OA80+*)((().6>HQXbgijiiiiiijligida^ZYWXUOHEA=>CIPW]fkqtxz}~~ |}||{{{{||~~}{{rh^SG=89BSds}vj_VRW_enrutspopqrqvuvx{}~~|zwqjcYXbr7766433110//..----+++*))**))))'*))),+-.10258<=@CEIMQUVWX[Z[YXSLD?:61.-,+++*)++)))))))((((())****))****()(('&&&%%&&$$$#""" !!""""$%%&'+-.0469<@HNUZ`ddhnpsxz||~}|ztnha[UMGEC@@DINV`hp|wmknrsuw|~~~~~}}}}}}||||||zzz{{{}~~ }zwwtpnmrtwwwspnnx¡¡¨¬°±®48IZiu~|yxxxzz{{yrbNCPSS^XQQIMUVWQN><MJC::;:Dc^Rlc\pifZ<P[T==@FLBX{oxxzxrbVcbRIMKJJMRT^][VSJD[hgd^`s\QKJKXcny~}|{{yy{{{{|zyurommllooonje[O@70+*)(()+18BKT\dhijihiihhhigeb`[XXXSNIDB>:;?DLSZagmsvy{}~~~~ }}}}{{{{||}}}|zwri_UJ@:;EWfv ui^VR[bhosttspopqqsvuwz{}~~yunh`bmy775543120/..--,,,,++++***)))'''())))**))*-..0359:?CGMSVY[]__]ZVPJC?:631,,,+**++,+++++****)+,*,,,++**++))**('&&&'''%%$$##"! !!!!""#%$%((,/0479:>@DJPQTW\cginqvx{~}zvohaXVQKFGKRXagpzxssvwyzz{}}~}~}{||{{||zzzzzxyzzz}}~~ }}}}~|xsv ¢¦«®¯®«A5GYft||zwxwz{|{si`SSR\hbWTLNWXTRK85IC=;:7:>Thf^D,`UUoVRQZI<>KUH;V}zZTabfd_]ZasiGGJJILLTXXPMINQdc`SR_cnmNIP^hs{~|{zzyz{{{{{ywsqnllklooomjd[PB70+*)*))/4<EMW_eikkhgghggfddb`[YWRPKFC=876<BHPU[`hotwz{}~}}~ }}|{{z{|}~~~}}zyvpi`UMB;=J[jy ~tg]WY]flstutsonpqpsvuwz|}ytnjnv55443211..--,,++++****)))(((((('''''(()))())*,.1258>CHOUW\^`^^\ZUOID@=731/-++,+,,,,,++++++,,,---,,,,,,+***)'''''''%%$$##"! !!!!"""#$%((*,/257:>BDDDEHLSY^bfimovyz |yvpid`ZVUWZahnyywwxxywz{{z{{yxyyyxxuuttrrrstuvvwwzz}}}~~~~||~~ ¡¦ªª¨¦¥T1HXgu||xyyz|~}|xrd_VWadb[RPSPRVPK7.38;;88>CLbqkDVvkYTZm_P@>?QJA@B:9<=<<<==GOQe\BEJKKKHOPUSPQRVWP=MOTTabWJVakv~~zzzzy{{{{{{ywspmllkloopnjd[MA60++*)+,27AJRYagjkkjigfffdbb^ZVSOLGB=77337<BHPW^ekouy|}}~}}~ }|{{z{}~~~~}|zxuohaYPE?AL]l{~tg^YYahovvtsponpqqrvuwz{~ }yutx5554200/.--,-,++**(((())''''((('''((''(''())****,./5:AFNUX\^`a_^[XROJEA<;80--++----,++,,,,-...----..,,+*)))(''''''%%$$$$"! !!""###$$%'')+.1369=>>??@DGLQW\`cgimpuz~}yuroieddfkpx }xtuwuvvuuvuuwtrromkjjhgggeffdfiklmpqrstuuuutuuttuvvwxy|}~ £¤¤¢s3DXgu}yzyyyz|}}wsic][ddecXXXYVVSC62/7=<:7>KSxx]HPhpjgXUQQ@A?<7>BDFGGFEGFFMTF?BBCMPNKKJIIOVWYSEEO68FUUGCXMXdmx}zyyyy{z{{{zxvsomkkklmonlhbYK?40,+,,.18@GNU\djljjhhhgffc_^ZUQLHB>:4/136:@DJQY`hmswz}~~~|~~}{{z{}}~~}}zyvrngbZPGBEPao} }rh_Z[ckswwvspmnqqqrtvwwy~ ||334211//.,,++,++**((((((''''((''''''&&''')))******,.19?DLOVZ]_aa_ZXXUQKGB>730..-...-,,----..........---,+++*''''''&&%%$$#" !!""###$$%'')*-0377;<<<<?CGKPVY]]`dfjmrw{ }}xusqprt|zurqsrqrqoollgeeeaa^]YXWUUVWWWZ\`abdhhiijjjjihhjjihiknortwz|~ ¢¡|AFXgu~|||zz{z}}{v_a^V_^ddZWV^_QWC9329;;978@HE5=IKCAloiUG>:CA=GDB=<;;88599@TNF@?EJJLKIFHNYb[UMFJUWNEKJEWNU\eqz~{yyyyxyz{{{zxtqmmkkklopnlhbXJ?4///0259=DJSZ`gkkjjigfffea_ZVRKDA;60.,/25<AFMT\cinswz}~ }|zzz{}}~~}}zywtohd\SJEHRcr }re]\ahmuxwuspoppprqrtuvy~ 220000..,++*))****(('''''''''(((((''''&'))))********-06:@GNSZ\\[^_]\[XVSMIC931...0.//..//////////.//---,+++*))((((&&&&%%$#!!!! !!! !!"#$$$%%%&()*+./369<===AEGLPTVXXYX[_chouz }|zy{}~sqpmmjjhdc`\[ZXTRPOMJHHGECDHHIKMRRVXZ[^```]^\[]\^_\[[]]aejnrux{~ }¡¢|y{px\LXhv}qbozzy|}~i[WXZ^dhYHJNK=KG7C::;95669;D;7?GAMSSvdP?<CCJH?F8Jr}wsnVT=MjPJCFJHGEFCDGOXVONKFDGGEWIQYSS_lt{}}zyxyyzzy{{zyvtqnjjkllnqomgaUH@64579<@EHLRV^dillkkhfgddba\WRLE<72-))*.27<CHOX_gjpuwz}~}{||z|~~~~~}zywsoid]VMHKVfu |sf^]cirwxwuqpqqppqrrrrsv|110011/,*)**(())))((''''''''''''&&''''&'))))))********-28?EJNSWYZ]_`a``_YSLD:200.//00///000000000/00...-,,,+))''((&&&&%%$#!!!! !! !!"####$%$$$%%%&(+,+.057;<=@@EIJMOSTTUTUUW[^fmu} ~zrkhfdb][XUSPKHFB?>=;8776789>ACEHKLPSUWYWWVUVTSRQQPPOOOOQTX[aejovz} p}~~|n|sLYgv iamcz wjb\VTa\FCHQRHRb]L::;6565649E@6Xh_UNTYOF;?<@AEI=4c{nWdjMKLHJIKMJFCBBAAHLMMKE=?@@KPEWX\_blu|}}zxwwxyyzzzyxzzrkkhgkklookf`UJ@::;?BDHNRVY]bgkmmiihhffd`]XSNH>5/,)))*.27<CHOY`eksuwz~}|||}~~~~~}{xvrmie_XOLNXhv{qg`agouzzwurqqqppqrrrrrty~211/00/++++*)(((((''''''&&&&&%&&%&''''(())(())******))+,.4:@EINSXY^`abbde^ULA610/.000001221123311111////..-,++*)))(('&&&%$""!! !!! !#&()('&%#$%%'$&)*+,/259<?BEGIKNMPRUUWWVWWX_cjoz |qf^]ZURNIC?=:9521223022259=?BFHJMPSUUWWWWVVWSPONKHD?>::>BEJQSY_dipw} ws{r|{{ZZguwpqroxkoh]Xfc][FGOZcmf;MG:78678659BDEZ`TOHIJ@@=?BNZcH<2Wzm]MPXRNJMNJGFLMFAA;;?DGIIFIJLJOO?XXRbhox~~}|ywvvwwvyxzwvohoz~sljorjig\SLHCEGJORTWY[`fhjklkkjhgfda_[VQJA8/+(()((,38;BLSZagmrwz{ }}{}}~~~~}|{xuqmjfaYSMQ\lz|rfcgisy{zvvsspooopqrronquz 1//.--+*+***((''''&&&&&&%&&&&%&&%&''''(())**++********+,,,059?EIMSY^bdfiif^TH<3101/0111122110222111100//..-,++*)))(('&&&%$""!! !! "$',/00/-+'&%$&&'(*+/359=@CFIKOPPQRRTVXZ\]]^aejrx |l\QNLF?;9531./0....-/2336<>BEHKMPRTWWWWXXWVTSRNJFD>:41/..28=BIOU[`gnvz~}zvy{nsmlyv~vztp}pxcagjcU\[\PPOIZdX9MK=8878;76=?GcTN7<68=FDAACKW]CF<a\be[@?JDNZVPHFFHC=>;8=AGIIJLLNOQXZXYXZepy}}|{xwuvtyxssvpfJL[p`X\SGK[^YQLHJLQVXZ[^cfkmnmnljihgecb`\TMG=4-*(()((,27=ELS[ahntxy| |{zz}}~~~~}}{xsolifa[URU_m}{sifimtz{zwussrpoopqppnkmqv{0/..--+)*)(()('&&&&&%%%%&&&&&&%%&&''(())))))********++,,---.06<AFLQX^dglmke[NB711222122233333222222210/.------+***(('&&&%$$"! ! ! "&+/4<>>>960,)(')))*.169=BFGJLOPRTTTTVZ\`deeefiqu{ zhREA=9731000../----..048<?BFIJLORRVXXZZZZXXWSSMKFB;60---++,017;AHRY`fmpvum¡´¼½¾¾¾»°}yqtzY{kzmcuxwj\fjkheb`RTUKEOb\+(=><;9;@E<98:FC@G?:987L@ACEJUSFI_]SZeRRX9BhrhUIFDABB?:7;@CGHIKMPRRUX[[X`foz~}|zwvwuuyt_`whbkrnna`JGFINNEYb\UPPQVY[]`deimopomkijhhgee`]ZTLD;1*)))())-49?FNU\cjpuw{}~ ~{yyz}}}~~~}{yvspliea\WUXbp~{slkmrw{|zxtssoonnpqqnkhhlpv~/...-+*)()('((&&&&%%%%%%%%%%%%%%%&''(())))))********++,,,,,-,.29>CKQY`hmrqkcUH:32222223344444333333321/.....--+***(('&&&%$"!! "%*/39@GKKKGC>70+)*,-/157=AFJLOORTUVVXX\`ehlmnnnprx} {iQD<77353//....----0036:>BDHJNQQRVWXYYZZZXWUURNJF@951-,,,++,,-05;DLT[beuykom¡¶»¹»½½½¼»»´vpihjk_]`vwy{kUVrnUZjmjll`]TQTNJSvu<413;<<:@KG847?FDLE;<9;J<ACEIUKH=MieZVJVO;nnniXLE@>=>A<8:<@CDGIJKLNQV[[X]gqu|}xxtsvvmUsmld]^inf^CDNPQJDH]le`ZUTWZ\^adfiloqqomkijhhgee`[XQJB90)()))*,08;AIOW^ejpvx{} }{yz{{{|}}}zyvsqnjgd`\WW]es{spnquy}}{wsssponnnnnkihijmqz --,,++('('''&&&%%%%%%%%%%%%%&&&&&&''(())))**********,+,,--..--.27:?JR]hmqsodXK=42222123444443333443212311/00..,,++))('''&%%#"!!! !"#$(/5=GMRVY[VRJC<60./0449=CFJLORRUUWZZ[\`bfmrtsqqsuy~ o]RLD<84541--//---.2268>BEIJLOPSSVWYYZ[ZZYWUTPLHB=61.++,,,,,,-148?INTYm}]5c ¸¾¼»¿¿¾½¼¾¶³º¤|_Rgcspgwz{zlW\dXL[cfb]agWHGNMLZmhJ:9ID><8DA9673EYSA39<<IMBDECJVED9Mwpk2B@Auyng`RNF??=>><;:>BDCDFGFHJPSWY[]gqwxzyvtslrnmorfG6@daHKKLLHFL\kpkhc]YWYZ_achikopqpnljhhhgfdc`ZWOG?5-)'''+,04:ADKRZ^emrvy{}~ ~|zyxy{{{|}}yvsplihfc`\[Y^gu{uqruy{~}zxtsrppoooopmjgfflpw}--,,+)''''&&&%$$$$$$$$$$$$$$%%&&&&''(())))********,,,,----../1/.24;DLXbjsvqiYL=53323234444556666444323331/000.--,,))))('&%%#"!!! !"$(-5>HRW^degfaYQGA;5457:?CGJLNQTVYYZ[[\`ejnrvvvttvyy~ ~zyyz ylc\WPID:71./..---0168:@DHIMNOPSSVWYY[[ZZXXURPJE?94/-,,+,+,-.036<BHNTT ~kyn]px{\]¶½¾½¼½¾¿¾¹¦ls³¸°¡wIZhuz|{{{scRWbOQQ_QAAKX^QNLIOLQX>8FEA<5COQ987<I`JLPNUOCDBRLJTBF<VxkD><<qywqefXHFFDB@=:9;=@CDFGHIJMQRVXWWhuxw{yyvsn`dmjoji_Y_OJKKHDSeklnnmkfa\Z]^bbeglmmooomkjhhhfecb^YUME=4,)'''+/38;BELTZ`hmrvy{}~ ~|zyxy{{{{yywtrojggec`\[Zakv{uqtw{}}zvusrppoooopmjgffinu{ ,,,+*(''&&&%%%$$$$$$$$$$$$$$$$$$&&''(())))******++--------..1311238>GT`jstqj[M<54433245544666644555434331/011/.---*)**))'&$""""" !#&+3;GPXahnoqqlg]TLF?<:=ADHJKPQSVX\[\\\`chnruvvvuvvx{~ ~yvvslou|wqmia[VOH?93/-.-/038=@BFIJLNPQSTVWYZ[[ZXVUTRMIB<72/-,----./028=@FKOTq ¤rYghuwvvo»¾¾¾²°¼¾¿½¸v§º¯ Q<H^ov~xtwzoTAsgZY\^H=?;<MaPMNMKBKOD@@A=8BID89689>6OVO@KCFC^YLQAE:nyM0=Bprwxteh^JGGDC@:78:<>ACEFHHJPUUUXQXmvzxxsHZspgZafeqo_TLJNNLOflkllmnnkhd_\]]_`ccgiikmkkjjhgfedc`]XRKC:1,)'&(,04;>BGMV^bhptwz|}~~~}~ }{ywxy{{{zyxurnjgefdc`^\\dny{usuy|}~}ywttsppoopoolifceintx~++**)'''$%%%$$$#########$$$$$$$$&&''(())))****++,,-------...1311238>GT`jrusiZJ=4343335665566665555543433211010.---,++*))'&%###""!#(-5@IT^fouyzxrlf_UQMGEGJIJNOPTUX[^]___aeknotvvusstuy| ~~~{wsplgc^agq~xxuqnkd^VL@91./158<?BDHJKMNQRSTVWXYZZXVUTRNID?93/.---,,-.148:?DHKP_¡¢£{xm`yu{ah|£¹¾½º¬·½½½¬°¥|iOTOnzoY^dlmleje]XTL?:9>EITRMKKKTMKEAB?><CE?=;:;A:;KLIA@EFJWXPJCB>zV12?b_fvvsk^YQFFFCB>;98;=>BDGHKLOW][YYdmvywuq;Qsd>[okvwNCSOJEQbihgjjlnoomgc_\\\\^\\^adfhjjjiggfecb`\WPIA80+('(+.38<?AGNV]dkptwz|}~~~}~ |zxwxy{{zyxwrolhfdecc``^_hp{ |vtu{|~}{yutsrppooponkgfceinpw~)'))'&%%%%%%$$##""""########$$$$&&&'(()))))*++,,++----......141/238>GU_jtyrh[H;33356556666666666666655554432110.,.-,+*+*'&%$##"!!!!#%*18BMW`hry}{ysoic^ZWTSNNPTSSRTWY[_``aacfikorrtsrrrtvy}~~{xtplgc_XQORYeq||}||yxtnibYNB;65:=@@DGJLLMNOPRTVWXZXVWSROMID>:630..//..1358<?CEKRR{¢ij¥ hZT^f|¶«»¼±¥¯¤ ²£} }^^tq_YLKShi]iniq^XR::;=?8JPOFLFESKEDHJLSB8<?;:;=PMSHGPLAD@a]RQAD@Jh27;MZ]gtupj_UNJFBCCB><<>@BEFIJOPRU[^Y\bnvxwttqusdQdovmK?GKJGWljihiklknnmjhb^\ZZWTRSUY\_fhjjiggfeca^\WNG?5.(&&*,059<@DHOV_fmquz|{|}}||~ {yyxxyyyxxvspmhebcecda`aelt {vwxz}{yvsrqoooopolkgebdglpv} *)(('&%%%%%%$#!!"""""####"""##$$&&&''()))*)*++,,++----....../321238>FS^itxsh[J;54456556666666666666655554432110110-++**('&&%$$"!!!##$'*19CMWbjrvyxvpqlhed^\[YWZ\ZZY[\_aeffffhijlpqrsrrqqqty}~}yvrmhd_[YTGABDMYgv}{}~~}zvskdYNC??@CDFIKLLMNOPQTVWYXURSNMMID?:842/////1258:>@CFHJN^wW\¤§tbYadIZ¾®²µ©®nq¡}egagf]cdYTWf\Teiic\bR;<>D=<FRNJRQCINOQMNRZE<>E=8;:H[fLKPCCBG`RUXAF=W=>CGPXKNqupg_WNJCACC@=<>@ACFMLNVYWZZ[]`hrxyvupoqpnnryfJBJJCAJbefhhhhklmlkie_[YXTOMJLKPY_cgijigfedba]ZUNG>4+'&(+.059>@EKRXbgnswz|{|}}||~~{yxwwxxxyyvspjfb`adddaabgox xvuw{~zwusrqoooopolkfcbdfkpv} )(''%$$$##""""!!""""""##"!!!!"$$&&&''()))*++++,,----....----/133128>HT^juxrh]L=56665576666666666666666665444221000/.,+*)'&'&%$#"""""#&+2:CMW`gnopoomljjjhgcb`^^__a_cefhlkjiijjlnqtsrqonpqvz~|zxvqke_[WRNJG@>@BFP`qzuy{~~}yuoeYPIHGGGJJJJLNNNQSUVVSQNMJIGD@:6440012357:<>@BEGIJPSvYQx~wf_( }½»©}¬®w[e>Z zUY\]twm_\]ZSVmhghVHE9=MSF@>Td\`u[FLNNGCUA=@=CA779DWS?5CBDC;@O\O>D?;9@JMUSLYjnqjZOMHB@BB?<:=AACFSUS]bba`_^bjsxxwsprpkHTxvFFMNFLgjfdfeegijkkklf`\VUQOGC@CDNV]dhiihgfcdca]ZUME<3,(')+/25:?AFLR[dipux{{||||{}}~}{ywxwxxxwvtqlhc_^^cdcbaejqyxsuw{}}|xutrrqnnooqpljgcbeglsx(&&&%$$$""""""!!"""""""""!!!!"$$&&&''())**++++,,--......----.022128>DO[epvtkaP@77765556666666666666666665444223200/.,+*)'&'&%$#"""""#&+2;AKU\ceddcacfijjjhgecdceffhklmopnmmmnnnqstsrrppqvz|zzzwupkf]WQJHDDA@>>=>BJZl}|rgkqv{~}{tle[SNKIIIJJMMNNPRSROKKIFFDC@;863322579;<>ACDFJLMQQd cQfZ*#x¼»´¥]|bVk{lp¤fC\_czvmdb\haV\X=9?DKWYD9BBc\WbYKFC@:LT38>IJ<435>FJHDAG@E7@GT=@<=:H]X[]?EOM^^_ZO??@ABBB@=>AACFTYW_cecbabgltxwtsl`\rlf_TMCL[dsmhedcbcehjkkkhc]YROJC:76<DMV]fhiihffccb`\XSKB80,(')+/25:?BGLR]flrux{{||{{{}}|zxvvvwxxvttoke`^]]`cbbcfms{~xsuw{}}|xurqqpnnoopoljfdeeiorz&%&&$$$$$#!! !!!!!!!!""""##"!$$''&''()*++,,------....--....0/01016<DLXfnstmbSH=6566557786666666666677775544442111//-,+*(''&%%#"""!"%&+29AHPU[\XTRV\afjlkiggfhhhlllnprrrpoopprttwxtrrsrsxyyvvsoje_YRLD@?@@?===;=AHVgywhXZaipw{{uof\UQNJIJLLNNOPPNHFDBEDBA>;7557879<>?BDFHKLMOPQT£¢puPW|{vs{ @*#hº¹¶²¦ `dvi`}}pcODfr}|cacSRX\R3>KS^kF/05MSMQJIFDDDW;88=@732548DD;>?BAS@AF@=BJRHVgQQW@F@0OOMP@;?DFHB?>?@BBDHQYZ_fgddefinuyxuqpc]jgJHSNMYesnieccbcceeiihgd_ZRLG@9215:CLV]dfhhfeedca^[XQIB7/*('),046<BCHOW_hmrwyz|||{{{{} ~}|ywvwvwwwvurmic]Y\[_accdhpx~ytrsw{||{xurppnnnnonmljhgefinq}&%$$###!! !! !! !!!!!!!!""##$$''''())*++,,------....--....///0004:@KWbjqrofYND<786555555665555555566665554443111/,,+*)(''&%%$#""!"#%(06>CKQSSOJIOTZaeghghhgijjmnpqqqrrqqquuw{{{zwttuvvwutrolga[SKD@>>?>=><<;<=AGRcxucOJOXbkrz yvod[SOKKLLMMMMHFCA@ABCBA=;<:9;>AABCDFHJLNOSUQSc¡qY~t^Md}{vqu2**Mµ´±°«rijmohu|mdtvb>>X¡uf[UKMX_^GKBFZUD329>5:NIIC>;FS:589<F;2228@NF=?7NYQYK9BCQAHVUORN7@20IKY@6:>CFIFA??AACGHU\YZchceagmr{wsj^gg^^RGF::KS_rmiecbadddhgggf`]UMD=5-,.3<FNX_dfgfeeca``^[TOH?7/*('),069;?CHOYaiouwyz|||{{{{~ }}zywuvwxwwutqlfa[XWZ_abcflqy~vqpruxz|ywtrppnnnnnmkjjhgeehmq}&$$$###! ! !!!"$$%%''())))*++,,--......-.////....//0027<FQ\fkppibWMC<97556345555555444455466653323311/-++*(((''%%$$"""##%).3:>DHKKHGFIOV[^`adcegijjkmopqqsrstvwxz{{||ywvvtrppolie^VLD?@?=>====;;;;<?FPaswdM;@ENYcmx ~yuk_VPMLMLMLFC?;::=ACCCCCCCDDCDDDGJKLNOQOQRUUp£¡JLnlPOqxwwpp3105£®§¨¤xj`TYWfjcmlhS8?T¥ tI<^ob[TNLFCCKRD3@D9::8>G43BKK?3A>?713059DC><Pe`i_;=<ANQIEOHMM;<F=C@?817?A@@AA@@AAHIJMMLR`hgfZ_pedcTKDEJEOOJ@BNCIdumgdabddcefhhfc^XPF<3+))/5>IQY`eggeddcb`_\XUPG>4-)((*,058:=BIRZbjpvvxy{{|{zz{}}|zwutuvvvutroje_ZUWY]`adhptz}tppptvxzxurqqonnnmmnlkkiifghms~$!##"! !"!"$$%%''())))*++,,--......-.////....////029AKW`ekpnh`ULF>;:86644445555444455466665323321/-,,+*((((&%%$"""##%&+039>@CBBCGINSWYX[\\]_aadgikmpqtstuxyy|||||yzxuqnlifc_XPGB?>>==><=<<::9:;=CM^p|jS715=HO^ju} ~{vpi[SOMLKHB?;;;>AEFGGIIJJJJJJJJLMNOQRRRRSWVv£n:IcxXNYrxzx`854.z¤¢¤~mdXNB?D]jhcJ<EQ~¢cCN|}vmd`SJPYXZPG<:FG:;=EA?ADUPBA705=6,036:?UbYVN<;=D^XHDFHQ]WLJMKM@>@;<?>:;>@A@>??CFGHITahk\FNN`kZOT]T4/@HCAFGGTrrlheddeccdeihe`[UK@5-())/6>IQ[bfhgedcbba_\XTOE=3-)((*,058:=AJS[bjpvvxy{{|{zz{~~|{yxvtuvvxwvtnic\XTTX\`aejqx~ yollnpvwzxurqonnnnmmmmmkjjjhjov"!!! ! !!!"!""#%%''())))*++,,--..//......00......,//15;DNYchopkd^UNGC@=:85331444444445566666544332//-,++)()''&&&$$##""$&'-15;:<;=?CEJOQTSSQQRTY]`adgjmprruwy{}~{xtqmid`_YRLE><<<<<<<;::999999;>IYm~q\>*+/7?KVdnv} yri]SLKGDB=<;>CGHJMNNPRSTSPOOOOOPQRSRSUVVV|£j6Ua{}dRLfu}V9788I£ }wl`ULFI\c\ZGAFJrzZsyli\ZV]lqyvjb[TUVUU\hPC?;FNI;019G7--/48Kf`URP=>CJA=>JMFDMNI>ALVVVSWF:=A=:>AACEGEAMRQ^SOY\IERZZdY[TC:>9EG<0<Rpwrooihncjhbbfeea[SH<1(('*/6?JS[cefgfdbaa`_^YRMD;2,**)+-1578<@IS\dlqvvxzz{{{zyz}{xxvuvvvvuusqmh_ZVRSW[_bgmu{}tniilpuwyvurqonmmmlnnnmmlkkiiox"!!! !!"!"##%%'(())))*++,,-...///.....//......,-..15=HQ[dhmmhd\XQLGE?<96334444444455666654443311/-,++))(''&&&%$###!"%$(-243578:>CHLMMKKLLMOQW[adgjmprrxyz|~{xsokf^ZWSMF?::;;;;;;::99887777:=DRey yhL0*),09CMZfqy~vmbUKFA@CCCEHKNQTUUUVWXWUTSSQQRPTSQUTUTZs,<Xq}hMGLfu|uJ:;<<:gwrtmeYPPTZUQQIFGGf}|]NQTMOUNRnps pbZMFKXONF;GO@2.37<8/-..3<?C?KVJMI;78;;:<>>A=8:;<CHJMP@ADA>?AABDCCFJJIMRPHCPQBEJITS@?B@AKQfktyxunftxwdX]T^^cfea]UF;1((')07@KU\dfggfdbaa`_\XRMD;2,)))+-1578<AJV_flqvxyzz{{{zyz~}{xvutuuuuvvtplg_XSOQU[_eiov|zohdgkpuwwusqqonmmmlnnnnnmmkkms|!! !!"#$&&'(()**+,+,,.../////.....----//.-,+,-,17=GQ\diiifd_ZUQNID@:653333334444554443332210/.-,,+*)('&&&&$$$#""#$$(-0132689>ADFHHGJLNQUZ[_cfknrrsw{|~}|ywqngc]XPMGB=;:::9::99998876666679>K_r}nW;))')-4=IS]hox{uvxpaTIEDGIJMRUY[]^]YWXZ]ZXVVUTSRSTRSSV]b}+9N`{ycNKDIPRIA=>>=<9nnmjb_WPPSQJMPIJGEJv~[AIOMLJE>5?[ajte[NRWHJL=?:52892550-*'*1;?>TL=74774395358;;:98==;:BQDA?EMGCA=??BGIR^Z]dTV^\J=RZQ:@LGEPapvsswtssmpieJSAH]cegea[SG;1+&'*19BKT]dfggfcbaa_^[WRJC92-*+),.0468=CLW`gmrvxzzz{z{{z}~|xvvuuuuuvuuoje]VQOQW^bfkqy}~vmeeejnswwtqoqponmmnmnpppnmnlpu~!!!! !"#$&&'(()**+,,,,.../////.....------,,.,,--047@FR[^cefeb`[XUSMHE=643222344445544433344100/-,++**('((&&%#%$$##$$$'+,-3789=??@CEFMRU[^`dgiklnquwzz}||zwvsojd]WPICA?>;;88889988887765555548:FUj{ vdG-(&()),5>HU]gpv}|pe_f{xpeZONPVZbilnooplf_ZY[_^ZVVVTTRPOPPOOY 318VnpSJPHA<;=@AAA@>7Er~sedaXZRKNNJKMLFGGJLYu z{wF:ABCAA@>;6/2Vvb`UQSI=D98849?=2.1/.-)<A98<86347;<AC:43662<BCG<99;=FFB>8<?@A>AECBIIYQjuMPipraIA@HIH=ThnpqrjZpfl^UR@@LU]chedb[SH:1+'(,2:CLU]dfggfbbaa_^\XRJB:3,++),.0158=ENW`iptvxz{{{zzzz}zxvvvuuuuuvuuoje]VQOQW^bglrzzrha`bgnsvvsqoooonoonmnpoonmnntz!###! "##$%%()()+++,--,..../.././/-----,,,,,,,-,,,.18@HQVZ^`abaa`]ZVPJE@:53322333344334333320/00.-+++*(''''%&$%##$$$##%'*,/37;>@?@CIMVZ]aiknnoopsvxwywvwutqnjc^XQJC<98;<::876567776666542333356=L`s |pW9'%%'((,/7@KVajojdonSj}th_]clv}~}}unghhie_YVSRPNLKLJIGL E.6Og\GIRJEB?@@@?>=26FQqnc]ZWVPOMIHFKLIIPRWZXix|mC;<;899:9763/;f~saSNICEKJ=26J;4,34/+*3?=DE953496<BD<7655434334577::>JVI60,38:<E?;=@FZ\QCL[gb]HAHJE=YmmmprtZClvtFW`WGIP[fghgb[RH:1+)*/4;DNW`dfggfdba`_^\YRIB:0*()*-11479>HSZckquxy{|||zyyy| zxwvuuvvvvuusmgc\SONSY_cgmt| ymd]\`gotuusqonponnnnnnnmmnnqqv} !#%%" ! !#$%%()()+++,--,......././/----,,,,**++,+,,,,16?FLOSX[]^`_`_\WSNHA:532233334433433321///0/-,,-+))'''&&$$#$%$$%%%'+059=ADFJKOU[^cfjnrrstvuwwwtsqomkif`\VPKB:756889::9867566655554331111237BVjy yeF,%'%')*06;DSbmhk]dxqxyxyrsrw }~xpg[QOLLGGECCCAzY.5A`VCHMJHEBCCBA@?^n^BXlg_XSRNOMKGHMMKMLTVWVXZooUB8;:576421.--Dj|eTNJDAIJ/),/32<:,*19,'4?94343413400334331000149<:=CLD/).375BM;?UXQSV`mZ@DPb]KD@B[njnpmpvweEb¨Kd]LBJQ`hhigb\SH:1+)*.3=GOY`dfggfdba`_^ZWQJB:322337;;<<@EMT\emswyy{||}{xxx{ ~{ywvtsvvvvuusmhbZQNOSY`dipw{rh^ZZ^ekrvusqopnonnnnnmmmmnnrrz#$&'% !"#$%%&'()))***,..-.--..-.///.,,--,,+++++*,+,**+-/4;?DIMQVY\]_`b^]UNLB>7312233343333221111//..,,-**)''&&&%$#&%%%%)+/39;@EHNQVWX_fjmnquxzzyxwurpkkgeb^YVQMFA;74446578788665544444443211000023;LburY=)%$%%(.1:GS[^Yacfoqqkiopu~~||xy}{l[LA>>B><9;Xq4/=X[IKMKKTFDCE@>:_jg`ecZSPLPOMGHJLMRMM[VKQTdl]QE;2247641.,*-He|zeV]WQSOD::249?<007:+(+44644438:EOD=7542/--0.038:?>E>5/1:?@?85EURYW_mj[LNLZmS]etulstwtyvUcODS[TAHWfgghhc[RG92-+,27@JSZaegggfecba_^[XTOJD@?BGHMOPPPSSX]ciqvzz{{{{zxyyy}}ywwuuuwwvvvvrlg_XNLOTY`elsxypdYRU[cltwtspnmoonnnnlkkkknoot{#$&'&" !"#$&&''()))***,..-------.///-,,--,,))++))**+***+,.36<@DGMPVX\_`a`_ZRKE=512233333333221111//..,,+**)''''&%$%$%(*+/49?DHLRVY\`deiptvwxyzyyxtpnigd`[WQNJHC>84311124455766543333333332111//-/015CXl~|jQ;&%)45.0<KNGGGONNRMQTWVUZhruyvvuw{r[H>889759~Q=?Q_PMNLIJFCDB@>7UvgVZ`YPMRNHEEJHKNNNYYSHJSq]IC43789952-***0J`|nadcsk\XSMD738DHI@7+/355326GLIKF67:864:A=8/+-7>AM]A2?CHBHG=GMQRU^gd]UVU`egIWorpmo~hib[\[YMDI[`H;FOffijfa]SJ<2-,/38BKSZaeggggecdcddeeb_^YUXX[`ehillnnppstwz||{zzzxwxxz~~{ywuvvwwvvvvrlg_WMKNTYafnu{uk^SMQYdmsvsrpnoqonnnnlkkkkkmns| !#&'&# "#$%&''('(*+***,-/----....00.,,,*++*))**)))***)++++,.259=@EJQV[]__`a\UNG;52222223322210000....---+**(((''&&')-/38;BHMQT[_bfikpstwy{{zyxurolid`[XRLHD>;95211100000033333322222422222210.----.4<LezueO9)6SUJGT\ZQPQSTY_PJFEFG?FILU^gp{xyw{aH93467H}jKGWcUOSMEFCBBB<7FrymZTXZVPIFEFGIGHHIKOPIFJQeA52>C>;720)()**5Ld}wturcbXLKLB<MNF>7;:75427<<:>??;:758DEK;.112>JOiqO/=RNLTQR_sZLRZgQ]R;=\fYOT`\^ZYb^VPQID@LJ@ENA<H:1^kkjhb\TI;1--/5<EMT\dgighffhjlnqrqtsppptyz}~}|{vuy}|zxwwwwxxwwutrle^TMJLS\ciqx}~vgWMIOYbltutrpnnnnnnnmkjiefiknu "$%%" !!"#%%&&'(()****,-..,---....//.,,,*++*))**))))****++***+-257=BGKRY\_bba]TLF;5222123321010000....--,++*(((((++.14:>EINUX]bhjmpstvxzz{yxutqojfa^XROIC=:623210/////00/02222112222243200000/.-,,-.16EZpyiO:CXWQRK]mpbeh]e`YdcRKE>DDEFGDI\mzYA7<GLh}rnngXLOMDDB?A?<;b~si^XUQMHDFIJJHGFILEFEGMM~ymF<7BGD@80(##%')*8P`xujuiqxdikYJW^QC<97840/025668:89GOG9CC7**+1Nhkmd_HBGCTPOS\mcL]fZMG9CNj[CcieXIIC??ELF77?MYRMGKQ[4Ogkijhb\TH>51139AHPX_cfijlnpqsvz} {z| |zxwwwwxxwwutqke^SKINS\dkry|rcQIGPZeotutrpnnnnnnnmkigbdfhnv"$$" !!"#$&'''()*))++--------.....--,,,*++***))(()))*****))**++//48<BGNU[addd]UME93311222200011//....---*+*)()*,/58<BGMPT[_dhmprtwyxxxwvtrpmkgb^YTOJF>;511000..////////./1111112211132111/...-,++,./2<Pg|xrww|~ufQ??Pcuy\OWX_nwtaICCCDFCACDGK\q~~} yc`ny ~qXLMIGCA?=ELVxmb]]ZRIHGHHIHFEFHE<=ESYltmiQ@>BFDB=3&! !$'*+>Q`qtniu~tvpebcZ\G;<;961345369585D;;?;7;65-/9^pqeV]UTOODLSV]WSN\KEPHBZL^m]_`VM+.953674=MQcl_UGDOecihikkgc^VI?8354;FKTYbgnprvvz} |{yxxwwwwwwutrkd\RIHMS\elrzym^MEGS]hqtusrpnnnnnnomkheccdhox !!"#$&'''()*))++--------.....-,,,,*++***))))))**))**++****++-04:@GOU[beif]VJB8322122100011//....--,++*)*,148?CGNT[\bhkoruwyyxvusqplkigb^YTOKEA:62/--./--.-...../.../00000011000221110..,-,++++-/7E[rtq~{qXMQdZ`QJPele~|\QHEDDGFCAEFEGIV_ ~|~ XhXJLKGC@A==Co¡§}ukgdcaaMH]f`WIFEEC929H\hx_`XGDCEB?>8-"!#'*+,/:M\jwxw}~zuj^OUH<DHOXT=:@<67B42>?CFZU<>31547Uid\V\[Q@;Cajh^\ZUFDL?AMXHVh^\NEK?G;?6?CH\ehxoNL=Aeffijkkgc^VLA:679?FOW`hotw| |{yxxwwwwwwutpjaYPHGLS\elt{xiVIEJT^jrtusrpnnnnnnomkhebbchqy !!!"#$%''()()**,,,-..,........-,+***++***))))((((((()(())*'))++-29?GNU_dgjd\RKB:55431200010..//.---.-,,.168?FJOT[_dilmpsuwvvtromihea`]YUPLFB;710.----.---.-----.-..//./0000//00121122/.++,+**))),0:Ndxz| }zlX]nbVOew|n`OEAFFEHEFHJLJA?Snq{y{q ]HJKEA?;::J}£§ek~unlkjiix\F>713Kekrnea]MHDDA??=2(%'),,,,-:L]gpv{uoaQbVNWUXUX>5ECEBD16A:FOKFC0.;9,4JZSNP\UF=FKTLQILQUGEGJMVWHIXZM@EOIG8?C89DXny}ysR4=Qhgggijlhb^WNE=8;<CLU_gqy} |{zwwwwwwwwusog`YOGFLT]fmv~whTHELW`kruurrpommnnnomjgdaachq}! !!$#$%''()()*+,,,-../........-,+******))))))((((((''(()))())++*+06>EPY_gkic[SMEA=876521010/.//----+-/137<CIOU\aejmpsssstrpomjeb^ZWSRNKEA;641,,,,,--,-,,,------+-....-.////////010000/.++++**))()*2@Teos yvv ~~\Rksnf]o|umg[WPEEJRKEDCEIKGGJ^rwzxycGIIEBCOG8Q¦©£^,X{yxvzz¡ydM>6/8Selsnga^UKA?A?@>81*.///-+*+:P[gjl¡{kceebXTDVM<>B?IA0=;AF?GN:.?VD-5IT[JAJ<<LLHP5:<@FWVFEEON\f_M85GYKSL67RY?DJfp_jrOCWcfghhjjlhd_XOF><>BMV_is}~{yy{~ |{zxwwwwwwwusog_UJFEKR[clw ufSIJR[emrssqppommllonmjgd``cis~!!""$$%&&')*(**,,,,-,-........,+++++**))))))))''((''''''))())()))),19@IR\dikib\WQKGC>=;9533100...-,-//47:?GMTZ`cgmprrtsqqljhea\XTOLHEA=9510.-.++,+++++++++,,--,,,,------.........//.//..,+**)))))(*+5FZf|~qbphioWXeX\ggd__qtlgbZMDFJPJEECDGIKLLOx u}y{wvqDLKIIC?>7Z¥ª¥+4Mr}y{¡ t]H;4/9MYbghc[VTOA>??AB>813310/-+*+5L_hdk¢¤¤¤¢~`VhqfUOaVE<HW>2<:DORK>/&4GWE2;GUcVCED5;F[S<737:EDHB1:AFab8<Y_XL[PKSnh`c`jnNV_]`fegefijkkheaXQHBCHNYbkuxrlfb``bdgkorx{|~ |{zxwxxwwwwurmf\PGDDJPZcmwufVOPW_gostrpnponnnnnnmjgea_djt!!""$%%&&')*+(*+,,,-,,........,++++++)))))))))(''(''''''((())(((((*.3:BLT^dhjhc^XVRNKFC?=:8632/.../0369>CHPV]agjmptrpnljfb^[USNKFA=9311-,,--,++*++++++++++,,,,++++,,,,,,--.......//...--,+**))))))(+5Mpzyuqmii^\vso}wtfZ]bTKTbuuimmgVDAIHJGDEEFFHILMuzvvzKGHFFEA>5c¡§§¥W2=?FI>Aj ufZS;11>LXZ]^`XTPMC=>BDGB?966431.-)*+5Rb_]nxixsh`[WZKGV`=94;AGFH89>H<E>9<HQYN<;_C=FZ^E1.66<HE@=EHHT^enkgeieimlfPUi`fl`ekiiecfgggkkkjfaZSLKNRZdoy{rj`XRJFEEGKLR[_glnsvz}}~~ |{zxwxxwwwwurne[OEAAGQ[eoywh\RTZclsttrqpponnnnnnmjgebadlv !!"$%&&'&'()**++-.----,-.......-,,+++(**)(((((((''((''&&&&((((&&')**/4;DMW\dhhgdd`[YWRQMIECA:6522237:>DHNTY_ehkmnonmjfa^XUPLJE>;730/.-,,++,-++**++++++++++++++++++++,,,,,,,,--------,,,,,+**))))((+0a}oeivodi^f{zym]f`^|ucdlt| yudUKLLGDEDFFJMMJi[r {yut\DIHHCA<:r£¨©¦t0<;974/P~yqeXPG5:BLRRTVXUOKHD<9AJKFD@;97651.+.17@V^[bkt~ ~uiUPUTUddLB7522;><?@4779<;:=A:60-:;>7gZ3-0>NA6<JPRLcxwrpnkjikii`Vhs`Zqqmihfeffghjmmljida\YY\cju~~ti`UKA>;<;;:<>BJOWagosvxzz{} ~}{ywwxxwwwvvqkdZQFABHR\hqyvj^YZ_gnuvtrpppnnnnnnnmjfdbabn{ !""$%&&'&'()**,,,--.--,-......-,++******)(((((((''((''&&&&((((&&&(**+/3<GNV]bdfedcc`^YWSSPMJEA;988==AFKQW]`bhjkihhea\YTPJF>:731//-++,,,,++-,++**++++++++++++++++++++,,,,,,,,,,------,,,,,++*))))*-3Gzqzwtwiikgju`MK[\egebd_g{}££xUCHHEGHHIHIIG`/9Ocqywuwh@GFEA@=Ey¢¥ª¦19;;;949fy{zvqg_XPLGGHIJNPNPRNJIF=4@NNKECA?:7642247:7CW]_dlpqsusvwrjhjfVIMUZQVXN12,9E?<IJ29<881/9KD00794<9=Q+,1692HemRPJqzvronkkjjknmqqu]Jmrnigffffghikklmmljijkpv|}vme]VOGA>=;<<<>?BFMV]fmsvwwvwy||~ ~}{ywxxxwwvutnjc[PE@CHQ\hq{vj_[]dkquwupppnnnnnnnnmjfdbaep~!##$$$&%&('()++,,,,,-........---,,+****))))))((((''(((('&((''((''''(*)+05=ELRX^acdcedaa^][[XTQLHEECDIMPV[_bdgihgda^[SOJE@:61--,,+++++,,,,,,,,,,++****++**************++,,,,--,,----,,++**++,,)+**.7Qdxdf ppffibOMLNMZgm_TOL^hpj¥¨¡xU@IFHIGIJHHHT@;;<Ig}{uvwxzBAFJC>>V~£¦¦}.9=<<:54Jgkljf_VTPNKJKJLNONNNNMKGA5APRPIFFE?<96:;;8799LX]`ejjptvxpZGFHJIDDGQJFHG>;C26<?<99EC=9:;B<<<35<17;?`SK?<<F[ebSOY{xurnkjjiijmnqrsmktpkigffgggihkprtvuvxz} ~{xrmhb\YRME?;:<==>?ACIRYbhptwvvvwyzz{|~ ~|zxxxxxvvwvsojcXNE?BHR]kt}wh__bjqvwwtrqpnnnonnoomjfeacfr !"#$$$&'(('()+,,,,,,-........---,,+****))))))((((''((((((((''((''''''('*,4;@GNRVY\^^_aa`a__^\ZVSQPPPTWZ^_acgffb^[WSLGA;50-++**)+*+,++,,,,,,,,,,++****++**************++,,,,--,,,,,,++++****++)+,+0Nsysly }|vsyydUP[[K`gdb][V`cd~¯³«¤G@FILMMLIDBGD4>>?E\yztwy{N?EGD@?h£}.68;:8617S__\][TMIIHFJNNNMMMLKLLLF@JRSVGDHIFD@<=BFB@>>DTXZZ[__ehnpne[TLB;<<GSPV]]Z8-4857;7BC;>B01;CSL8@769<OYID:8Xut\QLp{xsomkiiihilnqpnputokigffggijkpty}}zurnlifc`^]WQKC@<;;=>?@@EKV^dlsyxyywxyyyyz{} ~|zxwwxxvvvusniaWMD?AHT_lv}rg``hmuwyxvrqpnnnonnnnlihgdejv!"!"#$%'&'())**+,,,,,-......--..-,,+****))((((((((''((((((((''''''''(())'',07<CFJNQRVY]_^_`_a``a^][[\^__acdecb^ZTNID<52-+**))*)++**++*,,,,,,,,,,,,+++++++)************++--,,--,,++++++++*)****)+-/I~x|~xwwvzvpkffoghYFcpWWfcdefk°¬¯²¬®«¡wC<HKJGJEAABmk.:>@@@Sruowx|aCHHEADy q-6899:973>QVUWVRNJIIGGJMMNLMNJJHIKN\e`UEEEGLLJFDSbSCCB=OWUTPKGIOSY[Z\XK::ILROKIRXK=:.5?9:8>B:86.(*,44(9757?H<B>;<h}UM_|urpmkjgghiintoovtspmjhhghjkosx| ~vplgfc_`a_]]ZUOHA><<==>?@BEOW^hpv{}|{xxzyyyz|~~{zxwwxxvvvuqmg^ULC?AIUamxyoeacjouxzxsqqponnoomnmkjihefmy "#"##$%'&'())*+,,-,,,-......--..-,,+****))((((((((''((((((((''''''''''((''().26:@CGILMSUWX\_aadedcaaa`adeca`]YRMIC;2-*)()'****++++++,,,,,,,,,,,,,,+++++++,++**********+,------,,++++++++*))))))+,?yj|{|yur|}ypvy~vj\QXkgSGdwyzy¨ª³¹´·¹±h7IHGFFCCC=W}S4@?=@BNptqxz~xHHLFBGb,699::8521<KQQSQNMLGGGHMJLKMMIGEEKSWWVMJFGILQRPMQ^]PMC9HVRMD=9666:999510:FNQA=ADD8?><HI>9?DE95535/$2<=CA6;CD>:878ENMEMho}|xupnkhgghiorquuuspnlllnopsv{|ungc`a_^^^_^\[YTMG?<:;<<=>@DIQ[ajv|~{yyzzz{{ }{yxwwxxvvusplf]TJA>BJUcp{xmc^bipuxyvsppoonlmnnomkjhgfgp} !!#$$$%%&'(())*+++,--......//-----,,+****))('''((((''((((((((((((''''&&&(''((()-036:=AAGKLORVW\^bbcccccaaa_[WQMHA;3.)'(())((*+*++**++,---------------,+++,,********+++++,----,,--++++++***((()))*7kqXowz~|nvspwphcVc}oG4Xv|z £®·º»»»¹BBELJDBCD>F~} =8?>>ECawsy~ ONIFAJ^+677999730-8GNOOMKKIGGFKKMKMMJHFCFSMKNRSIIIJQVW[aiYLJB9EVSJB>@EH@:2//--07CE7223:;>C>DMK?=9BH96864479AC943<A=;9::=<748>@BJOXcnmliggiililtvuttqrrtvvx|~umgb^[[]]^^^]\\ZXRMH@;:::;==@EIS[foy ~}{yzz{{} {ywvwwvwvuuupke\RG@?CKYgs}ui]]ajouxxusqppnmlmmoonlljigjs~ !!#$$$%%&'(())*++,,-.......//-----,,+****))('''((((''(((((((())((''''&&&''''''(+*,.0156;?@CGLPTZ]^_````]\ZVQLGA:3.+)'()((())*++**++,,,---------------,+++,,++******+++++,----,,--,,,,++***())))*,GohWYqu|}{qulpmXEdsjRG]ft¨µ»»¼»ºµ]7>AEBCC?@Cd~~f1@@>CHqxxz~{z[@EGHJQ07:9:9863/.0;EMONMKIGGGEKMKKJHEFCDXQLNSTHIIKMU_rwod]KB=M[RHGMQRPH?30/00022//0325;;F;;EB938>A=7979:>>82363BO=6589:<;9=CB9:BEHR]aagihjootyzyyxvxz|~{skc^]\[XZ\\\]]\\\YVRKE=;:9:;<<>EJR_gq| |{z{|| ~zxvvwwvvvuttoid[OF@?EMZft~ |re\Y^gnswvrqpppnmlmmonnmljihmv !""$$%%&&''(****++*-............---,,+**))))((((((((((((((((''''''((''&&''''''&&((((++*-1468>BGLOQUVVYYWURNKE@83))''((')(**)**,,,+--,-......----------,,++,,,,+*++++++,,--------,-,,..06+)++**)*,5Y^cw^k~x||{{qmvoxu\AVp}ucY]b{£¯º¼»»º¸§p06=BADEDDCN~}H1?BKm~|{e?CCEP@3;;:::9863/.0=JLMLLLJJGIKMIDCAEDEEX[MOTSBHIKMN[mmlgfI?D[^RPVWWVK@82.*)-.../../147984852.1;A>;;69:;<981::144728BIG>75>CDFC>9?PX[Y\dmqtvy}~}}}~{pg]WXY[\\]]\\\\\\[[WUPJD<:99:;<<@GKS]kv }|z{|{ }zwuuvvvvvvtrmgaYOE?=EQ^kvxn_VU[blsvvtqponmkklmoppomkjjoz !""$$%%&&''())**++*-............---,,+**))))((((((((((((((((((''''((''&&''''''&&(((())((+,.257:>BDIJJMNKHGC@:2,)((''(('))*+***,,----,-......----------,,,,,,,,,+,,++++,,------------..4S@(++**)*2HRnqim~ {{vprmM?UBbxx|nX_j¨²»»»ºº¦t47<??CECCCFcyzD-C\~|} zIFAFLI5<9::::9542.+4ELONNKIJLLMKGLPRPKDAE]ONTS?EHJMNQ^b`^^MKR]]Y\_\WPE=86-&'*++-----0125@A><<FGHBF?<79779345631/26137:9?<:><?EKJGA7=Ni`H]x|z} xlcVNLPTXY[[\\[[[[\\[YWUPJC;:989;<?BEMWajs|{z{{| |yvttvvuuuutrmg`XKA<=EQ_lxwl]SSZcmtwusqpomlkklmopqpolkmt} !!""$$%$%'((*****++,.......//------,+,,**)())''''((((((((''''''''''((('''''''''''))))))))))*,/0357:?AAABA=;62,)'&''(()))))*++,-,+,,-,-.....//////----------,,,,,,,,,,,,,,-----------++,/1-*****+,AX_{x~RJTettwsupbT>LutrX_o¥®´¹»º¹£i17:<>AA@@FCDm}{};2g} VKDDK}P59:;<;;:941.,/@KPPLJHHLLMPQQQPMG:01aVMQM4=CFGJV]ba^^bbddddc`ZRE<977.'')**+++,,.0019=AHKFFOOKEB<E>FORF?961433453538CF=@A9:@GKIG?7QaV^t wndVMFFLSVYYYZZZ[[[[[[ZZWTOIA::989;<=AJX^alw}zzy{}|xvttuuuuuutqlf]UI@:=EQ_mz ~wi\QSZenuwvtrqonlkklmnoppnmkmu !!!""$$%$%'((*++**++........//------,+++**)())''''((((((((((''''''''((((''''''''''(((())))***)*,-..13677762/,)((((''(())))*+++--,,--..//../0//////-----------,,,,,,,,,,,,,------..---.-,-,,,**++-8OQyTb[OJMi|ty|zyoH?^xxunScq¦©¬°·ºµN/9:>>=@A>ABBK{}zx@w}{mNLEI|U59;;<;;9743.-,9IOSVWWWWVSQY[XOB3,(,]^OPJ,/49?T[`e_`jlmhiiij`TF?::992)(,*++***)*-..2=IKMPNXOILHAFMQVNB/264//1460/8K@;85NTIA99@DGA9=Q[Tk~}}}|yxoj`VLFDGKQTXXWXXXYYYYZZZZVRLFA::989;<?ALTW`lw}zy{{} ~ywvusuuuutttqke\TI@;>FRan{ }sfXPS[gqvwusrponlklmmnpqppnmqy !!!"#$#$%&('()++++,,..////////----,,-,+***))))((((((((((((''''&&''''((((''''''''''(((((()))*))******,,----+*)('&&((((()))**+,,,-....//./001111/////-..//..--------,,,,,,,,----..........--++++,-0JKP bMHEJSh®©¡ vvopkoO:;Hb~iV\y¥§§©³©|729<=<<?A@B>?BQ{{vw|w||{ROGIx`7<;;<==:841/+-8Xmvuoja]YV_eb\K4&%%'TcLLH-..0T`fbaexqpigjkml\IB?<===6/12210.)(%'+../9ALOIV`QJF?GKLEEJF1#093-./0,/?Mx_C<EPTSL=9:==A8<MXj |wsrrqssqoke\TJFEHLRTXXXXYYZZZZYY[ZVQMGA<989:<=>DIPYboy}zxzz} }xxvstuuutttspjd[SG?=>HTcp{qcWQW`jrvvurrponmmmmmnqqqmmot~ !!!!"#$$$%%&'())))++,,--..////..----,,,+****))))((((((((((((((''&')(''((((''''''''''(((((((()*))**))))))*****))(()(((((()))*+,,,-.//////./0011111/00//..//..--------,,......--....//......--,,++-.5VIhKGEXr¡§¬§¢}okmsr]??83Gmw~Y]p¤¡O4;;:::8;:<?>BMOd {ywy|x~ ~SIHOpj:<;=<=<85400..[}soi[WUTZ]]XE,&)(*HlULJ/,/Tkkkgl{yojhegkme\RJEA@B?<9768630*##$(,-./8AFQ[`RKLTQZc_WQI5!+<<1.00.+*<veI?JPPQN<7BDB:CWh |{vqliiiklnnmhc\SJFFIMQUYYXXYYZZZZYYYXVQKE@:98:;<=AFKT\dqz|yzz{ }xwutussttttrojd\RG?>CMYerzobVSX`jsywurqponmmmmmnpqpomm} !!""""#$%%%&&'((')***+,,,,....//.-----,+*)**(())))))))))))(((((()*,)&(''(((((('''(''''((((((()()))))****))())))((*))(())(())*+,....//.//1111111111221111/////.------..----,-...-............------./?]nnnG>a¨¦¡££«ªª©wx|zb[KHCDJqy}o^jwlD:=:<=ACC?=<>BHJRmxuv| zv{y t}hEIQgu<9:<=;:944/.+K{~zvrpe_XQJHCDGB400-.AlZML01Ejmrox|vnifchlomh^XTPOIA=<9<986,%"#$&*..-.2D^eh\PVXdlinlidZG8846/--4<?EYvz oQDLTUR>9=DB8=Wo{xsokhgfghjllmoic\SKGFIMRSXXXXXYZZZZ[Y[XUPJD>:989:;<AGLS\gs| zyyw{ }yvtrstttttsomgc[QG?@FP\huxm`UT\dntwttqponmmmmmmnqqpmp !!""""!"%%%&&'(()****+,,,,....//.-----+**)**))))))))))))))(((()))).9+'''((((((''''''''((((((()()))))****))()))))))))()))))**+-,,//./01001111111111222211/////.------..----,-..............//..----./EtzeCRBX¨¥¤£¦¨¥£¯®«¢ wlj`e[eW_w{{efr{tJB:A>==BIJOJDB??HQRq{wuw|sqy{wknwbs{ECM^wz@68;;962110,8q}ywuuk`VSTMEA:;;<9644>hZPP39fjqt}zroqslcftxofe_XQJE@>@=<7*$$$$&--.,),Gdeg^[[dqy }{~wa<04.+6FFD?CLL^`\s|}{[FGLFG@7?@=A[sypnjhgggghjlnmmkf]UNGHKORUVVXXXXYZZZZZZXUPJD>:989:;<BGNV_jt}}yxxx| yvtsrsstssromfaYOD?AGR^jwvj]TT\entwtsppollnnmmmnprmw !!""####"#$$%'''(())**+,,---....//.---,,,+****))))))))))))))((((((((''''''(((((((())((''(((())))))))))))))))))))))))())*)**++,-.,-..00111122222212111111110/////...-....-----..-....////......----,,./DmLB@EQ¨ª© ¡¡¬¯¯¥ ¢vnaenihfepl`nv}yT.4>D>?@?@@IEDM>:AFOIx}vsyzuttpWa}wm{UBEdr ~A77998432/0.G{yptpom`ps[:F@:;;;:8=]\PSA^jnx~zz}|n__^iwtmeaYXUQKGDB>2'#"$#*1..+*+K^_aT\Ya`m{usyzzrjT504>?=978>Qlkb]]csi[NJFOPI?=@>A[xrlkihgffhjlmmnkc\VNLIKOSTVVWWXXYYYYZZYWUPHB=:87:::=CIPXakv~{vvwy} zwsrrsrrrqppme^XNC?CJValy~tg\TW^gqvvtromlmmmmmmmomo !!""####"#%%&&'((())**+,,---....//.---,,,+****))))))))))))))((((((((''''''(((((((())((''(((())))))))**))))))))))))))()++**++,,..-///00111122222212422222110/////........-----.......////.......---,,./=TGCDTt³²£¦¨¢¤¥£ª§¥¢¡qdcdimgfkuyt|`kv}z|j<,29?>?@???BFHJA<>AJMLy}uvzxl[V\wcCCO ¡^?78977744566M~q~P;DD<;:<<;S\NWeqs xg`_^`bb_ab]XUSROMHF<)$#%$%.2/.++2N\_WGQ[]]amzywwurhijZJF>.6;5+*4T~g]SNPVNLM>8=>EZ~sigefhfijkmmmjf_XPMKMQSTVVVVXXYYYYZZXVSNGA=::9::;>DJQYcmw~}yvvwy} {utssttssrqpme^VLC?DKXcn{ |rfZSY`kswvtpponlmnnllmmr !"""""##$$$$''('(((***++,,--......--..,,++,+++**))))**))))))))))(((((((((('&((((''(((((('''(('(())))))))))))))******)(****,-,--../00/.002011333322243333331100000/..//..--.----..-................---+//>FFCNq¦²±®±²¶·¯¬£~vi[ojdehnvz{jfr{{x_1,01:CDDA>=<9CJH::=HMCR|y||kHQTk~mJCDv ¡sv?:;;<>?@>CB2V }opkU27?D?;<=<J_]gpo} rcbdfceb^UNTTURRPPNKC/%#$$&+442/-5DP^ZGCIW]\\ar~yqhimievhH:-0064./4R a^r|viXKGCEGBA@Amqjhhfgijlmlolg`YRMLNQSTVVVVVXZZZ[ZZZWSMF@=;::9;>>DLQZeoy~ |wvvx{} |wstrrrsssppmd]TJCAENZer}|qcXSZbksuuspnnmnlmmkmlv !"""""##%%%%''(((((***++,,--......----,,++,+++**))))**))))))))))(((((((((('&((((((()(((('''(('(())))))))))))))******+*****----..//00/0012322333355334433331100000/////.......-...-................---+.09AEOp°µµª¯³µ¹¸³ {zjbi`acst|~RbpxuX,(+17<@AB@>><;AA<=;AKH9Z}{{EIV_xtqQDEWvJBBCDFDC@==92n©¥¢~ |yg[IA7548?@;<:Cahkqv e`ghjjhdd^M;=LRPPQPMI>*""%'*056425EQPRF@FHP[\_[bnhfcXQRYciK301./2/,-8orv xM=FB?CELbsxmjgighjlnnojg`YROMNQTUVVVVVXZZ[\\ZYVSMF@<:;;:<@CHMT\gpz|wwwx{} }xtsssrttsqpmd]SGAAGP^juzn`UV\dmsuuspnmlmllkngw"!""!!""######$$$$''''(((***++,,--......--,,,,,,,+++*)))))**))))****))))(((((((('&''''(())((((''''''(())))))))))))))******+*++,.,--..//01111112332443344444433332211000000//..........................----,,,/<DUt ¥³´·¡¡«¥©©§¨£|xqggemnqt SRjutR'(+.86:IDA?=<:9=:=;?GMDF{{y |GOXzyaFCDi_DA?=?@=9:7R`B ®¢qhiZQC864324>C?<AWkntz igffimpmkf\B00>LRQRSRJ7&""'+,3659:@KROD7<AIPTWW]`cY\\H=72:GWO6//21/..I_gx{y{|~wkobT>CHEEGEPPS^cgghkllmolga]TOMNRUVVVUWYYYZ\]\\ZURLFA>===<>BGLPW`irz~yvxxy|} }xtqsrsttrqljd]QE?BHT`kxxm^VU]fotusqpnnmjikjh~"!!!""""######$$$$''''(((***++,,--......--,,,,,,,+++*)))))**))))****))))(((((((('&''''''''((((''''''(())))))))))))))******+*++,---..//001111123234444444554433332211000000//....................--..------,,,/?Qo®¦¥²p~¦¤¬© zsljckqut} ©nJejhL*+&,.>MKJBAA?<;>;;=>DHFFw~{{}uJRv yrNFCKsrLE@?=>=<::5GdX©~f[PMMC6643126>IHBE`osxsqkkjklpspjX9./3BOQRRPA-%$'+-0;?BABFGHLB89>HOQNVYYXOC3,.1334BOO:7618;6N=<Spuyzub]kqq m>LE>HHKMNQUV_dilnnnkgb_VQOOSVVVVUWYYYZ\]]]YWRLFA@?>?@BEJNR[bkt{ }yvwwy| ~~yssrstsssrnhaZQE?BKXcny wl\SW_hptusqpnlkmjil""####"#%%$$$$$%%%''''(((***++,,----....--,,--,,,+++*)))******))**)))))((((('''''&(('''''((((('''&''(())))))))***********+***,---..//0010011132455665444665444332222111000//...........-.---......--,,,,----,0Ce¡©«pr¥®®¬¬§}pifnrs{©®G_^]G..3.-6G:>FCB@<>=<<<=BKLHzrz|{ lMc{yndk}_EDBLcpTFEA>===;:814Zj¥yvmeWLCBA74334:CKSSSEGOUX\s |zqmlopvwqiS4-005HRTQH8-*+,.2>IMNOONGEF:8:>BJOOTSI@-+A`z£¦¥¥£~k]WF2=@5?Yosyzj\ayJCG<EKEUcXVUSWZ_ekkmmg_XTRQUVUVVUVXYYZ\[[[XUOLFBA@@@BDHLRV\dmu|{wvwv{| ~~~~~yusssstsrqlf_XOE>DMZeq} ~tgZSW_irtuspollkljj#######$%%$$%%%&&&''''(((***++,,----------,,,,,,,+++*)))******)))))))))((((('''''&(('''''((((('')'''(())))))))***********+***,--..//00111133333355666655665444332222111000////../......-.-,,------,,++,,----,0Jx£©²¶ªs{xtvsiqw|©°£RUWV@*+AC20037DA@@=?<==;;?DJG{vj|z`Rfmc^^^k~H?CAABGHDB?=>;95225Td¥|vi_UTLD?834;?GRVY[XYYMIGIOU]izxihry~zmF.,0317IOI</.-.12GY[Z]^YTOG<76<AFJMNK=2)7Xy¦¶»»»»º»ºµ®ªh/1540?Xku}{ws|mbAJEHMNnuhbd[VVW]]ddda[UTSUVUVVTUXYYZ[]\\XUQLGCBABBDFKOTX_fov|}yvuwx{| ~~~~~zuqqrssssplf^VMC@EQ]gs~|sfYT[ckrrtropmkjkk$#"#%%$%&&%%%%%&''(((((()+++,,,,.......-,,,,,+++++++**********))****))))((((''''&&&&&&&&''''''''&&(((((()))))))*****++++++,,,,--////112233433444456666665666554433221010000//.-/+.--.-----,,,,,,,,,,,,,,----+0[ §¬¨rwvwz{rx}p®¥XNPJ5(*7C/-0007CE=A><;;9;@EKH|~sj}wzZ_d[\]\ewo?@A@?BFDB>=;98400;Ub||pskXUPE997<CJOTVYX[]\`ZJDDKS\i|waZu p6-026628DA1,0221<Waahqm`WPF627@IJLH<3,0>Zz¤»¾¾¿¾¾¾¾½º·´°ª}9(024?Mdtw||LHHOSPZtkjie`]YYZ]^]YWUUUUUUUVWWXZZ[\\[YUSMIEDDEEGJNRV[bhpu}{wuvvx{~}{{}}~{zvssttssqojd^UKCBIT_jw{reWS[bkrvvqolkihk&$#$%%$%&&%%%&%&''((((())+++,,,,.......---,,,,++++++**********))****))))((((''''&&&&&&&&''''''''&&(((((()))))))*****++++++,,,,--////1122334334444566666656665544332210100/0//./.8/,,------,,,,,,,,,,,,,,--,++2g £¦¤¥¡£yy|~ywtv|{a3LPKE/'(,.,,+.16;FA?C=?<<;<AFEysmo|{~rSZRUWYfvU??=@CCCC==864100=R_y{plj]JJA457@JNSTYZ[\^_bbOEFLR[i{{bm\--13775353++1449T_emx|ueYJ>64@MONH6.,=HWv¦¹¾¾¿¾¾¾¾¾¼º·³°®¥8.4348Uhg~z}~{hGDNNNOyxkhjhgojd_][\YWXYWUVVUVXYZ[\]]\ZXURNIGGGHHJNRVZ^dlrw}}zuuvxz|}|}~~}}{wssttssqojd]TKCCJVbkyzpcYT[dmtuspnmkij(&$%%%%%&&&&''''(())))*+**++,,,,,,,,---,++,,,,++++************))))))(())((((''''&&&&&&&&''''''''&&(((((()))))))***++++,,,,,,----//0002223333344455565566665555553322110//...../MK0----,,,,,,++++++++++++,+,*+5h{ ¢¡¢ §¦¢xv{}|yvokojW;]dNMPN@+')+-/;1.027JD==>@>?;;?@Djxsnu|| ^XWTT[jz}xx{M>@ABCGH=:8431.0<R\xtqmd]KH=875?MSY[\aa`abc]JKNSTZduw £ A//0258:51--.049M^epz pYB:99DQWQB//5CMc¤¹¿¾¾¾¾¾¾¾½»º¸µ±¬¨ r018G5>XVou{|{cn>HGOPo~tmlhdedbb_[WUVWXXVVVUUWXY^`][ZZYXSNJIHHJKMQRV\`fmuz{vtuvwy} }}{}~}zwusssssqnicZPIDGO[cq{xmbVV]fnuwsqolml~'&$%%%''&&&&''''(()))*++**++,,,,,,,,---,++,,,,++++************))))))(())((((''''&&%%%%%%&&&&&'''&&(((((()))))))***++++,,,,,,----//0002223333344455565566665566553322110//.....,),.------,,,,++++++++++++,++++6f{££ ¢§zvuyyxrme\WQJPNNMPK<+'*),0.+,344459>>@@?=;=A?_vnln ~{}pQPU^dq|xsl}kB?CBCFD=:7521/0;MZtz~xsh\YSF>?:5;PWY_dfqslf]XWVLLQYao a130146674421036I\eow|l:7:;<<>@=70/7?Ef¶¾½¾¾¾¾¾¾¾»»º¶³§U0LW@0CBaYm}xFM~[?EKNV`^URJIIJLMRQMKJOSWWUVTWYVT[dee^Y[_]VPKIJIKLORUY^chov|~ztsuvx{} ~~~}zxutssssqnicZPFFHP\es}vl_VX_gpuvsrpkn ~t~''&&''''''''((''(((()***)*++,,,,,,,,,,,,++++***,++**********))))))(((())((((''&&&&&&&&&&%%%%&'''&&''(((()))))))*))**+++,,,,,----.0/102223433444555665566665555543311110///.-..-..----,,,,,,,++*)**********)*)6jyz{}wpbgaPEKQONOQLJ<'&()+-*)*,+/31619>>A>=?>?Puonlr|~|UASdpyzreY`xy\CDB?DH>:74310/:L` maVTSFBB>8<SX[ahnvywrvgbfegixnD6667986234453E`flu|b27765567530047Bu¬º½¾¾¿¾½¿½¾¼º¸µ²«¡~MKUT19TjX^}~~zywMENNeWSTRPUYVXWVQONMOWUTVX[\[TWZ\b^ZZ`ddf]OLKLLMQTX\`fjpv}~|yuttwx| }~~}|yuvttsqpmgaZPFEIS^hv|tj]WYairvvtqlm |jn ''''''''''''(((((()))***)*++,,,,++++++++++++*,++******++****))))((((''((((((''&&&&&&&&&&%%%%%&''&&''(((())))))))))**+++,,-,,--../00002223444554555665566665555543311110///.----..----,,,,,,,++*)**********++/6kzztniajcK9COOQOPOH8'(()***)*,+.5;JG9:@@=><9;Bzwwuw~qXahv{wn\G>HZeWECEC@@9731/0-;I`cGSTVHDGCCHRX_hjnwf^elgmrutw~wpV=:9898543258:Yttuxb675443020..266Eq´»»½¾¼¼¸´¶ºº¹³®¤ZRLUR14MO=h||xnTKLLMNPMONLDLXUMECCEGVRQ[YSYWceemkhhfmoh^QMLNNORUX\bhnsz~~zvtstwx|~~~}yusrrpnomgaZPIHMW`kx{si\W\ckrwwqpl |ddt((((((((''(((((()))))****)*,,,,,+*++++*****)(+*)))**))))))))))))((((''''''''&&&&%%%%%%%%%%%%&&&'&&&'((''))))))))))**++++,-,,..//./1011112344554555666666555555432211000/...---------,,,,++++***************0@>h }yqqpmlgcTJKKMNQNJH7'((())+(*.,+,7P=83<:??=<9;l~YitxzxjWD97:KaTB?CCCA841/..<KZvRGGWXMHCBLLV[\eu {`SMby tit|vsmhaS@<;98766656:Uw~{ c:863200/-.049<JUo¤¬³¶±§¨®°¯¨XIJGWI07E4Kty}wep>LP^c]_`VRHZhhlke[SUgRGX_Zcia[gmlc`Xkl^SOOOPPRUVZ^bgnuz}}yvusvxx| ~}yusrrpomjg_YPIJQ[epyzqf\X^fnuvwrp~ {d^iw((((((((''(((((()))))****)*,,,,,*+++++*****)(+*)))))))))))))))))((((''''''''&&&&%%%%%%%%%%%%&&&&&&&'((''(())()))))**++++,-,-,,--./1011112333555555666666555555332211000/..---,--,,,,,,,,++++***************,.0a wywvrme^UNLIKNNNKI:)((())**+---,/3+0VDB=?@>;=\}qrwwytgTC8548LZODAFDD=51//.<Mezs4@LNPLA<?BSX]dp tum]Rbx|{nrtrlfc[YPDA>=:97779:Mv g<75631/.-/39;CU_fks{}w~ZDDIKQP<<Mm}wx~~l_CMYccekhYP\flmmnlga`XPJM[Z]ZVOVbeUHA]dWQQOPPPRVXZ^diov{~}yvsuwxz} ~|xusqqqomjg_XOILS]gp{ ypf\\bipwvuqy zd^`jv((((((((((()))))********++++++++++++++******))(())))))))))))))))((('&&&(''&&&&&&%%%%$$%%%%%%%%%%&&&'''&''('((())))**++,,,-,,-...000110111233445556565565465554442120//.-------,,,,++++++**++****+,(*))****))),Z}}xvtmd]RE>BGMMKHH>-(')*(()*-.,+(),.:CP;?=><>J~ duxvpcO>777:EV\O?>B?@:40./:Kgh.6QdTHB>>AE[ad{vx{p~¤¢¢ ~rmhgd_ZZTOIB@>=;98:4[w{ |{jD6643/../15:?Ph}~ywma_becdlx|{ncY?@7<7EZBAbvxu|dL`OMijgg`^[_hjmlnnkha^__WYZXUW\]ajhB08WoWQQPQQQSVY\`djpw|}yttvxy|}xutttqomje^VMIOV`iu~xpd]]dksvuou~g__enz((((((((((()))))********+++++++++++*********))(())))))))))))))))((('&&&%%%&&&&&&%%%%$$%%%%%%%%%%&&&'''&''''((())))**++,,,----.//001110111233445556565565554444322100/.--------,,,,******))**))**/2))))(())))),Pm}xwwtoe_UD@CJNIKHF=-(')*'(((*,++.-3+).8<>?<:<@ inwup`LB88<DZf_TMDB@?;51//;H[}Y2Srx`LEA?@AOfnws¥«¥¦¦®°ª¥{rie`]ZVRNKEA==>??>;Z ~sw~{xnQ8753300158?BVv}iZVROMT[^`d`]\ZbtYB?=B4=AK7Kegnz h&bqSZ]dklfhcbhopppkgd__\ZXYYYZTT\^X6(1OaURRPQQRTXZ\`fkrx}}zvsuxy|}xtssspnlie^VMLQXakv~wod^`gnswtt iccbfq~))(((())))))))****)*********++,,++******++))))((((((((((((((''''&&''&&&&&&&%%%%%%%$$$$%%$$$$$%%%&%&&&&&'''''(((()))*)+,,,,,,-...////01/11222445555654444433333201///..----,,,,++**++++***********)))'''''''()+Ik~ } qtyslhaSLHGF<@IGE</(()*)((**+,+-,--*,/54=<7;Lruum[K<87D_lrh]WOF@B?810-8D_rPNwoSJHAA>NUe| µ²²²¬¥ ®³®¤}slec^YWXTMHB@@BCB@Cbxw|}yvo[@:;;<7257<@E[xqjc\VSMGFHHLXgsoLGI>G8>EDG?HWay8EIXVQVckb]lmqtqmhd`][ZYXXYZWZ]\VE?LWYUQPQPRRUY[_chltx|}zxwvz{} ~zwssssonlgd\TMNS[cmx ~wme`bhovuq keebbhv***)))))**))******)***********++))***)))**))))((((((((((((((''''&&''&&&&&&&%%%%%%%$$$$%%%%%%%%%%%$$$&&&&''''''(()))))+,,,,,,-...////0010122233555554444433333310////..----,,++++))****))))))))))*))&'''''''()+<sz~z~nv}qqqqocYQOMHF@AJEC>0'()*)('+**+,*)*)++-.17:@Kkurtm[ICCCJWeok`UPPIAA@:5/2C[qyNczzeG=E>NVP]y¥±³µ³°«¤ª¦¡¢tnjda\bpg\QHEDDDDDP|{z~wui_\Y]\_P48;>ABWo ywupcca]\[^afmpfBA=@BC;LAIG?RgvpePPWKJVOKWqrrtqniea^\[[YY[\\\\\XVZ[XUUSQPPRRUX\_cinsw||yvwx{|} zwssqqonkfbZSNPU]eq| ~wmdcgirwr| rhifbbjx ))****(****())++**))******++******))))((((((((((((((((((((''((''&&&&&&%%&&'%%%%%$$$$$$$$$$$$$%%%&%%&&&&&&&''''''''))(*,,++,,-...//00111012222244555544332233220./..-----,+++**))))))))('(((())))))''''''''&(),2kz}}sgd~q^_^`gdQQSUMLNNJFE?1'')))))(+(),*+*+,-,,047Uzz|{vqssu}jY[ZUNGFGA72?Njxl\tL@>JULRm¨±³°¬¨££¤¢£smkhmmjsjcZSKHHICBN}}wriiotzzve=68:>BNazzx{{zsfdjg`\_fgjnZ[[X96:>MKCFDO`y|[rTGRK6GMelprtsqoke_][[ZZZ\\[\][Z\ZWWUSQPPNRRUY]_cjnrv|~zxxy{|~ ~yvrqqqonlgc\RPTY_hr| ~wmfegmuv{oiiidbeoz))))))(****+++++**))********))**)))))))(((((((((((((((((((''''''&&&&&&%%&&%%%%%%$$$$$$$$$$$$$%%%%%%%%%%&&&''''''''))(*,,++,,-...//00//1012222244444444222233310//.------++++**))((((((''&&&&''((''''''''''&()/3N |winnftaOOPTV_]OMONKJKHGE?1''))*#*+**'&(+++*+,++.AnzuRPRXRMGEJC64Jmw{£ hiZO@Mf«°°²¥ ¡¬¤ £¤¤¢i[dovqnf_SJHKKGB@]xuxxqvsP46;??DSiy}qlqusqmgcgf_[_cbgb\`seB7;=>FJQG;Nx{d{\IO>+@T]dqqrssolfa]\\[[[[Z[[\\\\ZXUUQPPPOPRUY]_chlrvy~~|yy{{|~ ~zvsqqqonlgb[RPTY`is~ }wmfgjqu{tjnliecgq~**))))*++***++++**))******))))))))))))((''((((((((((((((((''&&&&&&&&&&&%%%%%%%%%$$$$$$$$#$$$$%%%%%%%%%&&%%''''''''(())**)+,-,--.000011101222223333333322112322///.-----,++****))(('''&''''''''&&''&&''''''&(,1;Ek qv[NLTXTGWb[ONNKJKFC=/%'(*,Im8#(,,))+,,././2T ~{z `VXXZ\UJFGB:>a¤¥¤ _@O¦®°¯¢ ¢¤¥¦¥ [<J`z~sleZJFHLKGADouz }~wa55:<=@HUflkcgif```[Y\][Z[_`_JQ\g`I:C?7ACFRGHex }RJT=-GA05eussqnjfa^][ZZZ[[Z[\[[\[ZVSRMMNNORUZ]`cejquz~~|zz|{}~ }yussqppnlg`XTPW\clu~|wmggntzvllmliffju********+*****++**))++****)))))))(((((((''((((((((((((((((''&&&&&&&&&&&%%%%%%%%%$$$$$$$$#$$$$%%%$$%%%%&%%%&&&&&&''((())))+,-,--.////1110122222332233221111110////------,++**))))''&&&&''&&&&&&&&''%%''&&''')18EYru} ~{z|¡pWOQ]cZXUii_LFLKJGC:-$&'))9=(&'(()(*/0-0/-=a~|}rW[Z]_`YMDDA@n¤ª¦s ¥`lª¬¬©Y<AJr}pibTJHFDEHAP} ~{tg>257<<AFR\][^`\VYYTOSTWXZ]WEBLU_la9;631=Y[eWVp cIJE8=@4=ftttrnifa^][ZZ[[Y[[\][[ZYUSPOOLOQRUZ^adgjptx}|{|||} |xtssqppnif`XRQW\dnv|wmiimu |lmonmkhhnw**********++++******))***)**))))))(('''''''(((((((''(('''&&&&&%%$$%%%%%%%&%%%%%%%%$$$$##$$$$$$$$%%%%&&&&$$%%&&&'''''(()))+++,,-....011111113221123212233000000..----,,,+*+**)))(''&%&&&&&&%%&&%%%%&&''(&')*+7F[u{tdmqtxullkls¢¤¡z^WY]aba^fglp_[SMHHB7*'''((&'''''&'((*+-/,5Ldx~|} |rdac\[_]VJB@?^h[q¡¦¨}¥©¤ WACJa{qjbQGBAFHD@a xtohH//28;>@CJRTWXWUTSPNQSTWYP:+<HNOSM@G?94@WJQON[p{eKCE@38E^pttsqnie`]\[[\\[ZZ[ZZ[ZZWSPNMNNOPSVZ^`chjorw|~|{|~ |xusrqpomje_XSRV^enx |vnkko nmnomonilqy**********++++******))**))))))))))(('''''''((((((('''(''&&&&&&%%%%%%%%%%%&%%%%%%%%$$$$##$$$$$$$$%%%%%%%%$$$%&&&'''''(()))+++,,-....011111113223321110000//////----,,,,+*)**)))((&&%%&&&%%%$$%%$$&&%&&%&(**-5EYnqa`[X^cd\SX[\]s¡`UWeiidfdghrzqmhe]KA0&&''''&'''''&'((*++,/:Vht~yyy ujgffd`^\ZQF=;[pfa\e£¢{gMNf¦£zoeUHDUthjyyrkifQ/,./58;=BIMPQQPPQONPRSQC00>?8;>:?CC=>=AJGIX[J^kuXFIRD<NYlttsqnid_]\[[[[[ZZ[\\ZXYURONMNNNOSVY\`cfhmrv{}}~~ }yrrqqpomid^XUUX^gpx zsnml qnqpopomjlt{++++++****++++******))))))((()((((('&&'''''(((((((''''((&&%%%%%%%%%%%%%%%%%%%%$$$$$$$$##$$$$$$$$$$$$$$$$$$$%&&&'''''(((()+++,,-...-/11110002112210/10/0...//.---,,,++++)))))(('&%%%%$$$$$$$$$$$$%%&&%'&&*.4BZrzjb^VWY\YOCKQTZmgUR[ibdjhcdjtpumplwxgD%'&*'('&''('&)()(*+.7EVft|~xxvpb]ZQH??}|m\_`_cx¢¦zs|`Tyª£vk]LDK]x~|xtnfa^V4-.)*.38<BFJMMMNMNOKH?3,/7BF7?FF@KOLI>BMI@LRT_bt xKAJZaPZopqssqnje_][ZZZZZZZZZZZXWTPNMLLMMNRUX\acdgjquz~}~ ~{vsqqppnlhd]XSV\`hry yrnkwulnmnpppmot|++++++****++++******)))))(((((((((('&&'''''(((((((''''''&&%%%%%%%%%%%%%%%%%%%%$$$$$$$$##$$$$$$$$$$$$$$$$$$$%&&&'%%'''((()+++,,-.../01111000211111021//.---..-,,,,++++**)(()(((&&%%%%%%##########%%%%%&'',1@Vu|uf^WYfdWQLEIVU\dx{|lZ]`kgiknlbhopkopshmr|d<),,)())(''''((**-;NZbx| |¡~h]\YPH=unZ`ppknp¦¡~s~~ph¥yocRDCHnyyskf^[ZV@,+('))+/479<????<81,*+.35EU>6;?EIK?BQNJ<LLNUV_hyr<=ERb[`opqssqnje_\[ZZZZZZZZ[\YWURONJIIKLNRUY]`bdgipty} ~zvrqqppnlhd\XUWZajry~wqkq{mqqrrtvvwx}++++++++++++**********))(((((())(())''('''''((((((''''''&&%%$$$$&&%%%%%%$$$$$$$$$$$$$$##$$$$##$$$$$$%%$$$$$%%&&&&&'''())*+++++-...00001101110011110.-----,-.-,++++**)++)((''&&'&%$##$$$$######$$%%%%&&(+1=UrzvsgUQ]qtc]]QIJ[bcspqSPWlumpmpnmmsmojrtmfsr~`7%(+*++*(19-)(().;MZc }}}lHXbfw¥¦¬ª¦wa^ZSLIj}r[s|yz¡£yop|~vuzqj`PCT}s unhaZWQNF0)(('('&')*))(('&')++.168=EJND>:=<HXUHI:0NMECRdj}{dNBBCMV`nrrsrqmid^\[ZZZZZZZZYYWUTQOMJJIKLOSVY[`ceeknry~ ~yurppppokhd]WUZ]bkt| }wqo qrvz||~,,,,,,,,++++**********))(((((((((((('''(''''((((((''''''&&%%$$$$%%%%%%%%$$$$$$$$$$$$$$##$$$$%%$$%%$$$$$$$$$$%%&&&&'''())*+++++-...//./00/011110000/.-----,,,,,++*****)()(('&&&&&$$###"####""""##%$$&''(-6Nlzuqxsnvwwuib\G/@jos}n}XGNYuqsumks vggmv~vflyv a7$(+++*0>0')))-;NZi{zvz`5OYfqx¥°°±©¡nc^\PIcv]u zx~}xtqmt¢ª¦¡~|vqldXEg | wngb]UOIC:.(&'&&&&&'((((')****,.16:=CDLHG<59NG<;@G:3AIHTdwve\VkhQV]fqrsrqmid^[ZZZZZZZZZYYWURONKJJJKLOSVY^_bbdhlqx} }yurppoomjfb\WUY_fmu| {vp {y,,++++++++++**++****))))''''''''''''''''''''(((((())((''&&&&%$%&%%%%%%%%%%%%$$$$$$##$$$$$$$$$$$$%%##$$$$$$$$$$&&&&&&&()))*,,,,--......//./0000////..-,++,,,,,,,+*))))(((''&&%%%$##""""""""""""##$$%%%(+5Fdyxsw||zz{vtj_UE<JdryuqlMO]ipszxwrmqtqjgnyz}jozuzxZ.+'*(()))+++.@PYp}~zwusy\GJRZk±®¯©td`ZTNat^v phtxkt¤ª¨ZPYkztqpneXEg ~wh[SPOJB9.'$&$%%$%&%%''(')('')-./792367?FRD564JOJY>3+1?P[cb^jjVWird\[`lqsqpmid^[ZZZYYZZYYYYVTQMKJHHJKMPSVZ\^`bdgkqv{ ~ysrqponliea\XV\`fmt{ {p~ ,,++++++++++********))))''''''''''''''''''''(((((())((''&&&&&&&&%%%%%%%%%%%%$$$$$$##$$$$$$%%##$$%%$#$$$$$$$$$$&&&&&&&()))*,,,,--......//./00//////..-,+++++++++*)))(((('&&&%%%$$##""##!!!!!!!!""$$%%%*0Dbxwqv|~|zxtph\PHQaj}~jwzWQWqymr{qqquytwuop~yto_urbmrN3((()**+++4GT^x {ptx|}vpmqzW:O]s°¬®§]IMNQNN_jP\ {jkox§«§£{SJJGY}sonnofPA] { tbG:4342.(%%#%%%$$$%$%%%&%'(*,/015?53/*+.2>^R?@QJ<QG-+4AZ_RTIOtrvnSQ]hprqpmid^[ZXXWWXXYYXXUSPLJHHHIKMPSVZ\^`acfjpuz |wsrqponliea\XX\`hov} ~z|,,++++++++********))))((((''''''''''''''(('''((())((((''&&&&&&''&&&&&&%%%%%%%%%%#####$$$$$%%###$%%%%%%$$$$$$$$&&&%&&(())(*,,,,------..////..---------,++**++***))))(((&&&&&$$$####!!!!!!!!""""##$%%$(/>Wsxqtz||z{zzrjf[NIWiv}piKNPcnnhikptuqfuxyposvkeX`plWmdg?2')))(*5ALYc}}|vnpstvrv}{urppz WQ`r®²°©C=DEDKPP[T:=ds{ {xy|h_aix¢~UFHOktrskhkoaHFSlx{rnyy~}cB40.,*(('&$#$$$%%$%&&%$%'',/1022473-(&&)*.E_PXYZYVI,+-9QPTL\y}}zSQOXcppqpmhc]ZXWWWWXVXXWWUQOKJGFFHJLORUY[^_`aeinty |vsrqonmkhe_[XX^cjry~ || ,,++++++++********))))((((''''''''''''''((''(((())))((''&&''&&''&&&&&&%%%%%%%%%%$$$$$%%%$$%%##$%%%%%%%$$$$$$$$&&&%&&(())*+,,,,------..////..---------,++****)))((((''''&&&%$$$#"##!!!!!!!!!"""###%'',8Lktrrw{|zz|yxqlbVNJ[vxu}}yYGGSinokekfjsmhl~rosohigGjnTnqhft?MD)(,*)2<LYf zyy{uljnnjkknr zqtrnve`vª¯¨wiQB==@PYZ[ V8ASgw{vrpopspjls~}T=>N{~cS\`befUHBFWswqi`jpuuS401/,*(('%$#$$$%%$%%$&(+-.12463220.(&%%')+<SXNVT\WH?/2@JORXR\lsyvmYMWnrqqmhc]ZXWWWWWWXWWWTPNIGFEEGILORUY[]]_aeikqw} {vsrqonmkhc^[XZ`eltz~ }++++++++********)))))(''))((('''(('''''''''((())))((((((''((''&&''&&&&&%&&%%%%%%$$$$$%%%&&&&%%%%%%&&$$%%%%%%%%&&%&&'())**+++----....--//--------.,,,,+*)))(())(((('&&&%%$$%$##""##!!!!!!!!!""#""$%(+5Gasqsv{|yyy{xusj`UJJcu}z{|nMEL]hiijebdgjmluwrcxoiGPw_Ekdf[hhnvZ:+++4?KW`~vywvsljkhcdifinquwx|uqnnrxlz¢wawQ=7@Xnph``9@aormjikpnjkw¤}{wdHA=MryM/3AQa_PJKUozrlb[dppkJ,/0/+*(('&&%$%%%&%('()+02121574.+*)%&')+)+7AFQQUMVgCA@JMRSMXV_]fk{toaKVirrpmgc]YWWWVVWWWWWVSOMHFDBCEHKNRUXZ]^^`dhkpv| zurqqommjhd_XZ_agou| ++++++++********))))((''))((('''((''''''''(((()*))(((((((((('''''''&&&&&&&%%%%%%%%$$#%&&&&&&%%%%%%&&%%&&%%%%%%&&%&&'())***++----....--..--------,++++*))))(())(((('&&&%%$$##"""!""!!!!!!!!!!"#$$%'*3C[noov{{wruwvwutg]UJIis|z|}bIGP`acfjffhcUNVYXcgQ]YWQVD>co]dWg]hfYVI9/3>JVZ~}zvuvrpoliecba^_agecby}qlnmlu}sRS[Q>:Jk rc{pLQkvynifbejioy¦zvqqkM=:<Kcu pD/,/?cg^Y[hsf]fmj_F*-0-+*('&&&%$%%"#%))**/4568>FCD<+'(&&&()))07?IY]U]cP@ELFMWk\SWS^kyK@]Lfkfqomgc]YWVVVVWWWWVURPKFEBABDGKNRUXZ\]^^afjou{ }ytprqommjhc^WY_diqw} ++++++******)))))))(''''(('((((((((((((('((((())))((((((((('&&((''''&&&&''%%%%&&&&&&&&&&&&&&%%%%%%%%&&%%%%&&&&&&&'((()()**++----..----.---------,+*****)))((((((((&&&&%$###"""!! !"!!""#$$'0B\nlmq{{tqrvxxywof\RDBn xkxxyqVFHQbhjmfbUTNDKHHEFJE>887;BAF^ZJpa~tpnaTKHA;>JPVy}ytqqqssohe_[ZWYZaaWHIqupoifkwu|[OVN?<Lvo_sNceezzwmgb]bfej£ xleh^HB=;GZgv {`<,,3GivnYXf qlmeU>)*,,**('&%%$#$$#"$&%&,:BEEDGGB=5)&&&&&()'*/3:DDJWd`ZVF@;@LWXZ\>B[T28fQN]irpmhb\XVTTUWWWWWUTQNJEA@@ACFJMQTXYZ[\]`chmrz|~ }xsqpqpmljga]Z[`djqx} ++++++******)))))))(''''(('(((((((((((((((((())))))((((((((())((((''&&&&'''%&&''''''''''&&&&&&%%%%%%&&%%%%&&&&&&&'((()()**++,,,,..----.---------,+**)))))(''''''''&&%%$###""""! !"!!!"#$%,<VqphntytmmprswxvnbVM=:hrrwrwykNCHUgilvdWGDGAJPKKJJDGA>;;@F?O]:k{ ~tcTMIEA@FJQrzvsronprnie_[YONT`^RF2Gjmnjhhnv|c@KNNEHo¢j\o y51Lwwkd^[^c\f|o\al\FB@DMWakqwvunU8+-;Rht~wd^dx{i_S4&)-,*)''&%%$$$$#""#$(/:BDC?:8760'''&'((''*16:=8GQX[YVOIKCLYZRPF6Q\MVjYJYrspnhb\XVTTTSVVUUTSPLJEA@?@BEJMQTXYXZZ\^aejqw}~ |vsqqqpmljga]Z]afks{ |y{~,,+++++*******)))))('()(''((''))))(())(((((())))))**)(((()*((()))('''&''''''''((''''''''''''&&''&&&&&&&&%%%%%%''''(((())*+++++,,------------,,+++***)))))(''''&&&&&%##$#"""!""!!!! !!!!!!!!###&)7OmrikqvslijkoqvwtkcVK?;jtyrpxwbKHPag_`hWJFDICHNKLKMECB@>?>E@AJD:<w}rbUMFDCCBFLgwusqnlnongb[X[SNNCLOI9,FjjfjhjowzG<BIIGm¢££¡sa]C+={wh_\YXZSu¡p[PY\M;:BIRW[adfjkaM7,7I\inv~vjy z}k\J,%(***('&%$%%$###"""!&/7:<?>==;81('''(('(&*1;=79BGI[^[\GFADKKIV`R@YRcn^J\stpmgb\WUTSTTTTTTSRNKGC@??@AEILPTWXYZZZ\^bgntx}~ |vrqpppoljga]Z\`hnu{ ~|yyxy| ,,,,,,,+******)))))('()(''(((())))(())((((()))))))****))()*((()))''''&''''''''((''((((((''''&&''&&&&&&&&%%%%%%''''(((())*+++++,,------------,,+++***)))))(''''&&&&&%####""!!!!! !!!!!!!!!!!!#"#)0FftmkptsmkhijmpsurkcVJ>6f~quxz\CHQek_]_PDFILGMQOMJJDBA@A?@BDCCF@nzkYNKECBBADG_ xvtqnjikjga^ZVPG>63;=867VmggffgowqF:7;_£¦¤¦u`eyK1@zsbZXVVNTyjONYP;19BKQWY\_cfe\J60=Qcikku|~z|wdT>*&'))*('&%$%%$###"""#*3:=?A>>;630+&''((((&)-2319<:DU[RW\D?DQTU^i]Lbwx~gDXktomfb\WUSRSSTTTTSRNKEC@??@AEILPTVWXYYYY\`dkqx}~ {urqpqonkhe`\[^cimv} |yxxwwx{}**************)())))(((())))))))))))))))))******++**,,,+****))))))((((((''''))))))))((''((''''((&&''''&&&&&&&&'''')))***)*,,,,,,,,,,..--,,,,,+**))))))((('&&&&&&%%%$##"""!!! !! !!! "$#$(/B^qmjottlhfdadintsskbUH81Uvnszm[HKSda\]ZLDJONNORSRKGGEFILA<>@?CEDWrgUJGCBAB@>@\ysopokhhhea]YTPH91155556<iligcehm}X02As¢¥§¤ m\vq`7>w}shVRUTTMq yn^OZaT54;GNTX\^abc`YF9?KZdgfglphysi^T?,())())'%%$#####""!#%09<=>?@<831/*''((((&&(-.1347:<JZZJSOC=@IHLPLZmwnidEWmuunic[XSRQPPSTSSRQOJEB>=>@BFIMNRUVWXXWWX]bhnuz| {urqppoljfc_\]_ciqx} }yvvuvvwy{{**************)((())(((())))))))))))))))))***+**++--,,,+**++*)))))(((((((((())))))))((((((((''''''''''&&&&&&&&''''((*****+,,,,,,,,,,--,,,+,,,+**))))))))('&&&&&&%%%$$$"""!!! !! !! "(5%&-?Wrtlntunieb_\_inswqjaTG6-JtrrvaOJLO]YSROCFMONKLPY[PHHIFHLPNBA@@CDA }n_QICA>;=<9;T }qopnhfgec_\VUPD533113356Mplhfecltt>,O~ £¢}cZw`v|r:6oskXNORRQWwmcXTVZUB?DKRW[]_`caZRHELR\bdgkpy}sng_ZU@,*'(())'%%$####!""!#,7<=>?@?<8530+('((((''&+.135557;CMOBFUZ>AO\TVY\bMS\JRivwple_WRPPQPQSSSRQNHDA>=>@AEIMOSUVWWWVVW[_fmrw} ytrqppoljfc_\]`fkry~ }xwtuvwwwxz{{ ****++******))(''()))())))))))))(())**++*+++++,,++,,,,,,,,,,*)))))(()))))))))))))))))))))))))(''''''''&&&&&&&&''''((()++**,,,,,,,,,,,,,,,+--++*)))))(((('&%%%%%%$$$#%%"""! !! !! "%(,>Vounqsvojea]WV]iqvvrk`SF58[|prsl\QFGPTLDACDGMKKJLN]\OFDBA@@RVD=A>D>>ivmd[TD;888779B~~rlljhdc_XVWVRL<..1/./0275drja`cgis_9^|nNR^CavsG7rxlaGGGMNNdwh^ORTPLSUPSVY\^__bdb`RJNW]`dlolp|oieb[YS=+(&''(&&%%$###$"!!"&1;==?@A@>;864-&$&&''%$&(,/0256777<IFFKPGCFNMUdbZTVVOEIezqlf^XRPPQOQQQQRQLHC@>=>@DGJMOSUVVVUTUVY]cjpv| ~xrpqqpnljeb^]]ahntz }zwwvuwwwxyyz{|****++******))(''())))))))))))))(())**++++++++,,++,,,,,,,,,,*)))))))))))))))))))))))))))))))))((''''''&&&&&&&&''''((()**)+,,,,,,,,,,,,,,,+++++*)))))((((&&%%%%%%$$$#$$"""! !! ""'-<Vpunnswpifb\TPP^krxvog]PD56Mwtmps^LTEDLHEA?DEGFIIIIPVVOEEF?>=;9D??>=A@JtlgdTA;6764468b~smjgec_\QLNMIE6**/.,./018Oojedcabfp[c{zmRFFCBW~upU:vtk\BEGLKRkvui^TSTVXC<PZY[Z]`bcdfebVP^giglxl]w|nga_]VUR?+(('('&&%%$##$#"!!!)5<>>?@A@?=<85+$"##$$##%&*.10379<;:9889CMLLQ[UX_aWEI_dKOqtmd]XSQPOQQQQQPOLHC@>=?ACFINOSTUVVUTTUW\bkpw| }wrpqqpnljda^]^biqu{ |wwvvuvvwwwxxz{}********))*))))'()))))))))))****))**))**+,*,++,,,,----..,,,-+*++**++****))**++****))))))))))))))''''''''''''''(())(())***+++++--,,,,,,,,++*)***)))((('((&%%%%%%%$$$#"""""!! !! #')9TovpnswskeaZTMJO]iotsme[NC75I]kfhqSEGC=BEGCBDEHFGIIIKQQNF@BBCED>;B==>?@:m{rkfcM@D=411235;rrieeeb`ZQKNOKE6-**--.//.7@inifb`\`jiiwxlT>FBAKX}xjbBztfW@BHJLVdge\TSTVVE4.8Vg``_ceddggg\_imlr{r^`|le_]\WQPN>+%&''''&%%%$$$!"!"&1:=?@@@CB@?>;4,$"""$$""$%'*./15:===71.+.6HVXPRYYiiLIbbZLmukc\XXZROPPPQQQNJFA>=>@CCFJMPRSRUWTSSUW[bjpv| {trppppnljc`]]_eipu| zyywwwwvwwwwvxyz{))******)))))))())))))))))))******++**+++,++++,---------,,-,,+++++++++****++++++++**))**))))))))((((''''''''''(())(())))*+++,,----,,,,,,++*)))))**((('&&&%%%%%&$$$##"""""! !!#%+7Vpyolqvvoga\UMDEO]gnqskbWJ>424`~hgmoJFC>:@CGEDDEDGHHIIFKOQTXdnnqokcURU_P79Wxqjg_OB@93212312Hrjcba_^YQLIMNMG;.*.1/./237Joni_\[]bmsw{|uk\A?BDMS`rn__MuseSAGIJMZ]ZXVTSUR@0.4C_jmnkggfgiieckortzg[W`z{lc]YXYRPMJ;*&%&(''&%%%$#"""!#-9<>AA@@B@@?>;5("####$##$%%%(*.1379862,++.<R`Z[_alfaPIFSJfrne\W_^^YOPPPPNKHDA>=>@CFILNQQQQSTSQQTX\ckqx~ ztqppppnkic`]]`flsv| }yxyxxwwvwwwxxxzz{(())))))(((())))))))))))))))****+++++++++++,,,,--...------..,,,,++++++**++++**,,,,+***++*)**))))((((((''''''''(())**))****++,,,,--,,,,,,**+***)))*)))&%%&%$$##%%$"!"##""!!! !! !#'5Qnxrnpuuslc]VLA=DP]forpi^WJ<411f|khokEE?99=BEFFEEDFIECFCCLixvwiU_qyY7?snke[M?621110./13Xkc`^]\XRMJIJMNNG9+*-/-/343RrmaWXY\amzvpjbM<@?B]`gbYZXRmm`NHJMLORRTTUSQM=303@Sgqtxzvokilljmry}rZIZPWffc^[XYTMJHD5'$&&')('%%%$#"# #)7=>=@@@@AA@@?<3& ##"#%$$#$$%'(),-.*+*+*)+-7P\RZcdba[\i[K[soi_c[@V_SOOOMLHGC@==>ABDGLOQQQRTRQPQSX\cksy} ~ysppppomjhca]^bgnsy} {{xxxxyyxvwwwxxwwz|(())))))(((())))))))))))))))****++++++++++,,,,,.......------,,,,++++++++,,,,++,,++*+++++*)**))))((((((''''''''(())**))****++,,,,,,,,,,,,**))*)))(((((&&&&%$$##%%$$""##""!!! !!#$(7LkwmmsuvuqiaZOA:=DO^hmnlf^UH<43>tvhgncAC@:9?ECDEDCAEHKFBBHh£ªª¡|wx{hWg{T/`ole[K=62231/./12<dc_[YXVSPLKKKKOMJB61-+-31+/Zf^XU]]_fowjaYC=<Facb`]ZPTdd{dUGHMPQRTUUUQOH?411;N^mwtrwxurqqqprstu]GVODQZ^_[XUURLHE?2&$'''''&%%%$##"#(3<=>?@@@@AA@@?<2% ##"#&&##$$&.))&%&***+*++,.3FJ[__X`dZ_YLVqrbZE)4NNLOOOMLHEB?==>ABFILOQQRSRQPPQSX_dlsy} ~ztppppomigb`]^bhnty~ ywyzyxxyxwwwwvwwwxz|))**))))(())))))))))*)))))******++++,,+++,---.-.//......-.-,-,--,,,,,,,,--,,++,,,,+++++++*++****))((((''(((())*******(******,,,,+,,,,,,,*)()))**('&&&%%%%%%%%%&'**%$#""!""! "# !!%(0Ijwmhntxtsnf\SC76?HQ^hoplg\QF:46P~rabl_ABA:6?EBBBCC@CHQJ?H^¥¥qZb}w;FrkeZO@622233/.155>^`WVUTSRNLMMKLLKKH</),/-&'4_b_[X[^deqn^R=;@^_UZZ\WPGPQk`PCJJMNRX\`]ZNE;313EYfpvuqprqmjloonnlpKOUGFQ[]\ZVUTNJFB:-##$%&''%%%%$##$'29;<>??@AAAA@@?:/#! !"""#&#!"%)+))++**++++,*,//1DVmigf_fjXRVngh`N+1FOPNNNLKGDA>==>ABDGMOPPRRQPPPORY`gou{~ ~ysoooonlhda`_adjpv| |{yzxyxxwwxxxxxxxwx{~(())(((((())))))))))*)))))**++**++++,,,,+,---..///......-.,,-,--,,----,,++,,+,,,,,,,,,,++*++****))((((''(((())*******(******,,,,+,,,,,,,*()+))))(&&&&%%%%%%%%%&*00&#"!"!""! !!!!#%&1FdyogkrxvvrjaWJ807AJT`iprme[OD81<apefnZ@D@96<CDAACCBDEHEKd ¤¬§}vtj e3pje\RC2.,,***+,/0/;V\WSPPONMLMKMKJIHE@5.1.)$):Z^abca]^hhaH:BUZW[`aYRLIDGNXMFLQOORU[a_VL@9307J_kotvsnmlgeijgg_\ZGZNIHR]^\ZVURJFC@5*$$$$''&'&&&%##&08;;<?@?@AAAA@@?:/##%!!""""##%'(***++**++++++./38@TbhmoideUMWije^\XUONMNNNLJFDA>==>ABGJMNPPRRPPOONSZ`hpu{~ }uqoooonlhda``bflrx~ ~y{yyxxxxwwwwwwwwwwy|(((())(()))))*****))+*++**++++,,,,++,,,,,,..--//..........--....,,..------,,,,,,,,,,,,,,,,++**++(((((((())))))))**++**+++++++++,,,,,,,,+**))))(('&%%%%%%$$$$$$&&)(%#""!!""!!!!!! !"$)-@auskkrvxxtoe\M>01:DPZbjqricXLC71EulgjqVAF>99?CDCBBCACEDIa£¦¨ª¬ª¡vyz|Edlf`WOGGFEEFEFJLWZTHLRSRONMLKKKKKHGDDD>73.*'#%>YYW[\]]^]bUIRYRTWVUSMJG@4;EADIMLLQTW\\VI<701=P`lptvtnkhgfiige_[NURGHMZaa^[USMGD@=1($##%%%&&&&&$""+59:<>>??@BBAA@@?:/##$#""!###""%((**++++***+,.148=BDGTfggmiNL^jjd`ZTLLNMNNNMKGC@>=>?ADFIMMPPPPPPNOOUZbiqx} ztpoooomkgecbbchnuz~yyxywxxxxwxxwwwwwwxz}(((())(())))*+******+*+++*++,,,,,,++,,----..-.//..........--....,-,,,,----,,,,--.-,,,,,,,,+++*++*((((((())))))****++**+++++++++,,,,,,,,+**))))((&&%%%%%%%#$$$$##%$"#""!!""!!!!""!!#%(1?[vskkqvvvvof\QB4/5>HU^fnqng_UI=67V{lfiqT?E>99BEEDCCCBCEHYp¤¬«ª®£¡riQd_]]\YYVTUTSTTRWWY[SNMNKLKLKJIIJHEEBA>:73-(&'&CWPPW\YWZ`bUUWYUTRPKGBB?43>HCEIJLOQUXWQF=722>N]hpxxqkiifffghgXD8FMJHR^`a\XUPHDA=9,%#"#$$%&&&&%##&08;<>@???@BBAA@@=;/# ""!!###""%'**********+/069?<=<?DKQ_qmIGXY_de[SVQNNNNNMLHE?==>?BFHKMMPPPPOONOQV\biqz~ |uqnooooljgebcefjqw{ wy|yyzxxwwxxxwwvvwwxz~ ''(((((())))))++++,,+++++*++,,,,,,,,----..--//////..................----..----,,------++,,++**,,+***))))**))))**********++++++++,,,,+***))((''''&%%%%%$$$$$$$$$$&$####"!""!!!!""#"%)1=XsqjlprrsrmdYNC5/07BMVbiopke[OC75Be~lhflO@C@87BGECCEFC@@Nk§ªª¬¢££¤¢}zRYXWUTOOPPOOOOPPRV[[UPIIFEEFEEFHHHHEA?>:853,'&(.PZUSUTSUY]bWRRNPNKFA=?A7.3=AADFKMNRWYQF:834>KWelrslhhgfffghicN6@FEIX^^\XUSKEB?;5)######$%%$$###)29=>?@@??@A@@@??>:.#!!""!!"""""$&+++)******,04666689<CFEHUTFG\R]ef[W\SMNMNNMKGD?==?@DGHKMMNNNOPOOPQV_ckrw} ~ytpoppnnkigeecghlsx} zszzxyyyyxxwwwwwxzyww{''(((((())))))++++,,,,,,,,------,,------....////....................----..----..------,,,,,,++,,+***))))**))))**********++++++++,,++****))((''''%%%%%%$$$$$$$$$$%$####"!""!!!!!!"#'-7Povjknprrqg`VL?2,.2<GR[fmooi`XK?21Iq}lgjlL@CC:8@DEDEGGC?C^}£®°®°®©«tv£¡£`QUSRQQQQQRRQQPPQSTSVQMKGECB@A@?CCBB@>>:9630*%'*2R_XPRURPTa[DICEFEB><>>5/,-:@AEJLMNNPOC9743:HU^glmlihfffe_XVLOULCFOZ^\ZVSOHC><93'$#####$%%$$###)3:=>?@@@?@@@@@??>:.#!!""!!""""!"$(**)******,/3444569@GIIIMC@AUhonfbcgSLMMNMLJFC@??@ADGHKMMNNOPRTTTWY_fktz} ~ ytpooonnljgedehiov{sx{{yyyxxxxwwwwwwwxxy|''((((()))**++++++,,,,,,+-----------......////00//..............................------,,,,,,,,,,+***))********++****++**++++++++,+******))''&&&&&&$$$$$$$%$$$#$$$%#"""#""""!!!##%(*7Kjypilmppme\QD91++.8@KVajpple\QG;35Y{ylgknQ@EC<7?DEEFHGB?No ¡¯¹º»¸ª§|¢sMUXXVWVSRSSRPRRUUUUWPNJHFFEECBBAAAA@>><;:63.)(+*5T][XSQOR[aJ;??CB?;8873/--9DIKLNPOOQO?652.5DQWY]dihkkfgi\OW8LYKGNY]\ZVSQKD@<:91&####$"#$$####")4=????@A@AA@@@@@?<-#! """"""""!"#'')*+*+))++-102479669=@ED?CFAKfg`_\ZUNMNNMLJFCA??ABEHIJLLNNPRVWZZ]aaemt{~~|wronmmnmjhgdefjlpw}zux|}zyxxxxwwwwwvuuxxy~''((((()))**++++++,,,,,,,...--------.....///////................................--...-,,,,,,,,,,+*************++********++++++++,+******)(''&&&&&&%%$$$$$%$$$#$$$$#"""#"!!"!""##'.7EfyrkknmlkcXK?4,))-3:GP[fmqpkbXMA516^{y~mein`?CC>8>EEEFIHDHaw¤±¹»¼º²¯©ª¬® ¥\XXXVTTTPQQQPPQSUXYYTPMFFEEDDEDCC@@A@>>=;;950-+,.7UUWWRQPTZUB;;>>=;8751//2>HLMMNPPOQUG(/0.2<EOSV^bbegihkid_QQTOOWZ[XWTOJD@?;95-%""$#"##$$####")6>A@AAAAABA@@@@@@>/#! """"""""!"##$$()()**++,/135653668=?>CCC=I_ff\ZTUOLNNMLJFCA?@BCEHJLLLOORUYZ_aegglpw{~~ytpnnmmnmjhfefgjmty~utz|{{zxxxxwwwwwvvvxxy~ (())**)))******++++---------......--/......0////.../............///////.////////-./.....-,,,,,++,,,,++++++++*********+,,,,++***+++++**))('''&&%%$$%%%%%%%%$$$$$$$$########"""#$'+1=Zxrjjqpph]TH8-(%&)-5?IT^fnome]RF;20>gwvkbelvK?D@::EDDFGFL`t£±µ¶º»¶¹»º»º®¦¢jZVRRQSSTPPNMMRTRTUUSQNKHFEDCDCCCBBBA@@>===9841...AWVQPQPOMUV;5;<=:8432019GMNMNNNNNQSSG=)(/5;CJMV[_bdfgfcabUVTKPY[[YUQMEA<;:60'$$$$$$##"#""""#'2>ACCB@??@BAAAA@?=3%! "!!!!!!!!!!"""&))**++*+/1324468;;:9==BGMR[fba\VPTQNNMJFB@@BBCFIKMLMMQUZ\`ekoppptw}{wronmmmlkjgffgilqw{ ywx{}}zyxxwwwwwwwwwwwxz(())**))****++++,----.------......../...//./////../-............///////.........-./.....-,,,,,++,,,,++++++++*********+,,,,++**++++++**))('''&&%%$$%%%%%%%%$$$$$$$$########""""%(-8Mrxpmqtri_SF5)'%%&)09CNWbkpmh`YO@2/0Cmxxmccnzr=A?:8ACBEAG_v ¤¯®°¹¼¼¹³°µ´µ©©¡wUTYesyyyyum^NLLNNORRPNKHFEDECCCCBBA@@@?>==:9863..,@SPONNKKMQR:69:8652202=KPONNNNNNPUUTT929:;=AFRWZ[_b`a_\_YSNKOXZYXSPJ?<9750*%$$$$$$######""#&/;@ACB@?@AAABBA@?<5)!!!!!!!!!!!!!!"""$&(**(()*,/0124547755699;BJSblbZRPOSPMMJFCACCCDHJLMLNORU[afkrvwxxxy}{ysommmmmlkifgggimrx| yy{|||zyxxwwwwwwwwwwwx{))))******+++++,,----.-............././0//00//////..........................................-,,,,,,,+++++++*****++++,,,,,,++*++++++*****)'&&&&%%$$%%%%%%%%$$$$$$###########"#$'+4GivnjqvvncVF5'$$$%&)1:EPZdlpkf]RG:.-4Usxxlcahw~U:@76?ACDAQu¥¦¬¹¾¼³³²³±¬¤¨¦^bt~~}|{xxutk`YXWURROLJGDEFFDDDBCA@??@>=<;:98754.-/?OLMLJIGIPL66965310/5CMRRNOMMLLMPSW\VF;9<;=@IPVWZZZY[ZYUPLLSXZVRNLC9840,(%$$$$$$$$$$###""#$+8@ABCBAAA@@AA>??<7*!""!!!!!""""""##""$&+*)**)****/2244010/57<@EJ[fjaYTOPPNLKGCABAEFIKLMMMNQU]aipx|}}{zz~~ytommmlmmkjihghhjnty~yyy|}{{zyxxvvwwwwwwvvvy}))))****+++++,,,-..................../00//00//////......................//..................-,,,,,,,+++++++*****++++,,,,,,+++++++++*****)'&&&&%%$$%%%%%%%%$$$$$$#########$#"#%(0=^ysinvvqgZL6($#$$%&+3;FQ[ennjaZMA3--8\xws~pachtu@=:5<@ACLf ¤²¶·´±¯¬nt{zvwvwtssstqomjhdb]YTRNLEDDBABA@AA@?==<;:9875430--@MGGFFFFKQF686530//8IQSSQOMMKKLLPRYWXL;69<=CJMRTVTTTUVQMMSXYVSOLIB81/+%#$$$$$$%%$$%#"$""##&2=?@AACCA?=@BB@?>8,""""!!!!""""""##""$$+)*+,*(((+0//0-**1403879>OWaWQZYQPMKIFC@@CEEHLMKMMLOT[cjsz~~~ {wrnmmmlllkjhhghhjouz}zxz{}{zzxwwvvvvwwwwvvvy}))))***++++,,,,,-.......////....--//000000//////................//--00////////....///.......-,----,,++*,++++,,,,,,,,,,,,,,+,,,,,++++***)*)&&&&%%$$$$$$%%$$$$$$##########$$##%),6Nswljruqk_O:*$!""#$'+3=HR^dkje]RH=1,5Hk}}sbchte5968?CB[|¤£©«¯³´©£tgtuutrqroonnnllifb`__^\\\XUTQMJHEA==>===<:98765210..<KHEFCBBGRA6872111=LTWWSQPOKKKKMQRSNTSE<=?@CHORUTONNLPPPPRRPNKID>6/.+&%$$$$$$$$$$$$$$####$+8>@@@@BA>>?>?=<;5+"!!""!""""""!!####$$'((*))(+./,+)*))*-17<=CGNW[aotbUONMKGDB@AEHHIKMMMMLORZbkv|~| ysomkkklkkkigfghimqv{ |yy{|{{yxxwvvuuvvvvwwxz~))))**+*,,,,,,---...../0////////..//000000//////....////........//--//////////....///.......-,--,,,,,,,,++++,,,,,,,,,,,,,,,,,,,,++++***)('&&&&%%$$$$$$%%$$$$$$##$$$$$$$$$$#$(+2Ek|okpttncUB-#""""#$'-5=HR]fjjbXMB705Gi|~}v}wbdfp~H776=AHj£¤¢¦¬®±¦kooqqpmmkjkjiihdbe`__ZYXXXWTQSRSRRPG?==;::98776422...=DBACBBDIT>664024BOSVWSSPOLJJLLMQSMNTSLB?@BDIMNMMJDDEGHFDDCEFC@91,+)'&%%$$%%$$$$%%%%%%%%#%,6<???@???@><:83*#""!!"!""""""""####$$&'''&*,09<3(('&')1=CEMPQUVY_py^XNKLIA@BBCFGIKMNNNMNOU^gr{~ {wrnlkkklkkkigfghimqv{|zyz|||{yxwvvvuuvvvvwwxz~))))++,,,,,-----....../0////////0000000000//00//..////..........00////////....//......////...---,,,,,,-+++++,,,,,,,,--,,,-,,,,,,++++**))('''&&%%%%%%$$$$$$$$$$!#$$$$$$$$#$$$(.<`tlqvyqhZN5$!!##!#%'.5<GQ[dieaVJA96?]~zv{{hbbl~t195<@Ov¢¡¤ ¤¥¤¡ {txu{ikklmjkigghffda[V[\ZXZXTQPPOMMMMNQSRGA<;9:89886551/0/2FGEEBDEIRP633216GQTTUURROMKKKKKMMPONOLD62:?AEFGHFBCCCB?CFIJHFA<0+(')'''&&%%$%$%''&&&$$%%%&*07;>?=?@?<84-&#!"#!"" """""""""""#&&%&'''(')4:/(&%''+1;BEJNQSSWXYj~]^oPHGC@@@BEFIKLLNNMNMQWcmv}~}}~ }wtqlkkkkkkkjhfffgimqv{|yy||}{zxwwuuuuuuvuvuuxz )))))*,,,-----......../1////////00000000000/000/..////..........//////////..-///......////...---,,,,,,-+++++,,,,,,,,--,,--,,,,,,++++**))('''&&%%%%%%$$$$$$$$$$$$$$$$$$$$%%$&)3Oyqpw}zpdTC-#!""##$&(+3<GR\bfd`UKDCL`{zwvz|ia`j|Z+26>Uz¥¦¥§¢ ¢uiba\nv_gghhfecbdeb___]ZXZXVQOMIIIKKKLKKMNMGC?<:99875554110.9HEBEDFFJOF22437HRUTTSSSQOMKJKLKKMMKKJ?0%',29;CGFD@@A@BFJIHFC@6/*'''&''''&&&%$%'('''&$%%%%%&*-38:=>;61*$""%$"#$%!""""""""##"#$$%%&&'(&'(&'('&&%'*26>DGMNPSZcnaN^\ICA??@BFHIKLLNNMNJOS]iqy~}|} |wsnmlkkkkkkjhffffhlqv{{vxyzz{ywvwuuuuuuuuvuuy{****,-,,,-......./////00//11////0000000000000011//////.0////..//..//////........////////.....-......,,,,,,,,,,,,----,,-,--..--,,,,++**))((&&&&%%%%%%%%$$%$$$$$%$$$$$$$%%%%%'+;fwqr{yk^M:(#"!"#"#&(*1:EOZ`cc`VOQ\m}xxv|}k`^dzC+2<Tz§©ª©¥¡ ~wlghiaq}]bfhfdb_dbb`^][YVVVTQOKIGGEFHHIJJJKMOOHC<97765665333/0CKGFGGCFIM?112:GRTUSSRSQPNMLLLKKJIGFD:,#$$%)-6<=><>AABFIECA:4,(('*++*()(''&&&&&&'&''&$&%$###"%+.231-'"""#$%""###"""""#"$%%&&)+'&&$&&%%''&&''(()),.18=EJPW`jvu\Y\j^G@AA?AFHIKKKLNMKJLPWemv|~~~}z~ {wsnkkkkjjkjhggggghlqv| ~zyxy{{zxvvutusuuvvvuwvx} ****,-,,,,....////////00////////000000000001001100//////////..//..//////........////////.....-......,,,,,,,,,,,,----,,,-....--,,,,++**)(''&&&&%%%%%%%%$$$$$$$$!$$$$$$$%%%%&),?qtryufYF1$#"""#"#%'*19DOZ`cdaZ]gq~y~j`\^vz6/5Ow §©©¨§¢|yuvsfagkr}u~lV_cb`\\_]\[\XVSSSQPMKHFDAABAACDDDEGIIJGB<8665666533227KOGGGGJGHL=028FPTTUSRPOPNMLKKKKJIFEB8*###%&'&)+/:<<@DDEFB5-*+---*++*)*)((''&&&&&&&%%&&%$$$#"##"$$""!""!##"""###"""""#$$),15975.)'((%%''&&''(((*+*,/7DMTX[\[^hiWPJC?>?ACFHIKKKKLMKJJOVbitz~~~}z zvpliklljjjigfffgijnrw{ }yxxy{{{yvvutstuuvvvuuwy~**+++,--+,..//0000001111111100110011001111111111111100//////.../..////////....//..00////.....----..-,,,,,,,,------..,,,.....----,,+***)(((&&&&&&%%%%%%$$%$$$####$$$$$$$$%%%+.@outyp_Q=+$###"#"#%(,08CNX^ac__ku} | |m_[\t {nv~b21Dp ¥¨¨¨¨£wuugelqj`eZpp]qyUSWYYYZZVWVTSSNOPLLKIFDA?=<;<?@A@CEFFECCA<756446555662;KICDCHHHIN=16FPTUTTUSPONNMMLKKIIGE?5%##$$%&''(*-2;BFDA;2--,---,-,*+*((((((&&%'%%%%%%%$####""""""""##""#"#""###""####%*18AGIHD<3*$#%%&&&&'')*++)+,0;ELJOPV]c^UNJEB@?@ACFHHJKKMMKJIJOT]hry~} }wsnjijjjjjjhgffggilptx| {xwyz{zyxvvuttstuvvuuvwz~**+++,--./..//0000001111111100110011001111111100111100//////.../..////////....0000//..//.....----..-,,,,,,,,------..........----,,+***)((('''&&&%%%%%%$$%$$$####$$$$$$$$%%',/>juszzeWI3'$#####"#$'*-6ALV]`ba_n| xx{}}o]Z[gf_i{|H)>d}¤¦¨¦¢u|vprpoleahhdgpeN^Ic YMQSTUSUUSRRPNHIKJJGEB@=;98999;=<?BCCEEBB@<764456666643>IEDCDEDFHTC6BOTUTSQQOONNMLKKKIIGE?2$%#$$%&''(*)4@BB?7.+-./,..--,,++(''''''&&((('%%%%$####""""""""##"""#!#""##$$##"%*4?JSWWVRLA3'$#%&&&&)),/-,.36;<?HKP]llg_YQNGC??@ACFHHJKKKKJIHILR]gpx~ zvpkjjhjjjjjhgffghjlptx| }zwwyz{zyxvvuttstuvvtvwx} **+++,....-.//11000000001111001100111111111000//00000000////////....//////............//..--....,---,,,,,,,,------..--......----,,++**)(((((('&&&&&&%$%%$$%$######$$%%$$%%'-3>eusv|paP=+$#%$######&)-6@KV\adebn uwx{~o\YZfcgpzx|=-Vs ¥§¥£z{wqjihjegjhqw{rTHQW~bIOONKPMSRNLJHDEHEDDCA?<6544366779>BBBAAA@@<963465654468=FEDDEECELWG@MSUSSQPOOOMMMLKKJJHF>1$$#$%%((''(*+4960+-..--..-.-.-**($#&'*'&'())*'&&$$$##""!######"""##%$"!"!!""""#(2@LVaihgaZPB3*$$%%&&(*05:=A=8L\lvuvvsngaZQMHD@?ABDGIIJJJKKIIIIJP[grz ~xrnjjiijjjjihhgfhjjmrvz~ }yvvyz{zxwuutsrrtuvvvvvw~**+++,....-.00112222220011110011111111111110000000000000////////....//////............--..--..--,---,,,,,,,,------..--......----,,++**)(((('%%&&&&&&%$%%$$%$######$$%%$$%&)/6Aa~usuxkZJ5&"$%$$$####&)-5?IS\adgnz}kpwvxzr`]^dguz{y}x/>hz£¢¡ zvpggfhgegox|xwob\Wg]CKJFFIJTUMGFD@?BA?@?@>:63310034459>@@???>>>>843556665467@HFDDFEEHOXKHPUURQPOOOMMLLLLKKIG?3$$#$%%''&'(*('*'(,/.///.0.-/./-'%##&'*(''(((&''&%$%$#"""######""##)52%!!!!""""$,8DS_ktxtoh\N?0*%%%').0/4<HY[Puwsvvutqmf]TLFB?AACEGIIJJJKKIIHHJP[grz {vpkiiiiiijiihhggjkkosxz }yvvxzzyxwutssrrtuvvvvwz~,*+--,--..-.11002233222222221111001111112200122211110/00////////////////////.........---..-,--,,....,,,,--------------......------**)))(((''((''&&&&%%%%%%%$$$$$##$$$$$%&'+1:B\|uqqqlV?-&$#%$%$###$&).2;EQZbhmvz_Omjippsda`fs~xvy a3[rpgaaehgb^fowpovxxovo8DGBAA?AEEDAC>;=<;<=:853211/./11159<=>>>>===;8556776888:<DHFGEFFFHMTNHQUSQPOONMMMMLKKJKFA5&%$%%%&'&'))(&%$(+-//...00//01/'""#%',*(((''''''%%%%$$##"""""""###&-*$"!!!!!""%/=KZiv~zrg\M>1)'),04<JTMDOO_~|vvuutqmf^VMFB?DABDGIIIJKIIHHGGHOYgr| ~xtnjhhiihhjjihggfikmpty| ~{xwvxzyxwvtssssstuuuuvy| ,*+-..--../011112233222222221111001111112211333311110/00////////////////////.........---..-,--,,----,,,,--------------......----,,**)))(((''''''&&&&%%%%%%%$$$$$##$$$$$%%(.4;FWwuqqonS7'%%#%%%$###$&)-39CPZcmt{|ub<Gkppsqwhdcfs|wvPCetp\`aghc`]cly}ukl{||L9BC<;;<@><<=;998778641//./-,-///269:;>>>====;877777997:<=DGFDDFEEHLPNHNPPONMLMMMMLJKJIGC9&$$%%%&''())(&%$%+../..22/0355/' !#',*(((((''''&&&&%%$$""""""#$##!""#"#!!!!""&0?LYj{zqf\MA8436ASalk[]bgy}wttuusnibWNEBBABDFGIIIJKIIHHGGKR]is~ |vqljhhiihhjjihgggiknquz} }yxvwyzyxwvsssssstuuuvwy|---.....//01112222222221121122111211222233333333330000//////////................//--.-,,,,,,,,,,------------------....--./....--,,+))))))(''&&''''&&%%%%%%%%%%%%"$$$%%%%'+05<IXmxkhkmY2$%%$%%%%%%%&&(+28AM\hu|~|{wp\;(?fyrqtzjdcbmt{ }{z|uIRmym_\`a^adcgt}pktxtwj-9><867:::867553266510/-,,,(*-,,/1467:<<<;;<=977899<DKA>:?EGGEDEEFGIOEAKNKLMLKKKKKKJHGGE=+#%%&&'')*,)(&%$%,.///111688:80& "'-.,*&)()'''''&%%%&$$$$$############""!!""%.=L]m}umh^TPIFHTbfekeelsvyx|zvuwtrqkcYPHCAADEGHHIIJJJJGGFGLS^it ~xrnjihhhhhhhhhggghjkprv{~ }zwwyzyzvvutsttttttttsvx| ,-./..../001122222223321121122111211222233333322000000//////////................//,,,-,,,,,,,,,,------------------....--./....----+))))))('''&'''''&&%%%%%%%%%%%%$$$%&%%)-33=JYgrngfid@$%%$%%%%%&&%%(*.6ANat|yumaJ4.6Yxsry{laagirywwzzdK]pxyvlhddgaY\binv |rllnqsv>1475455564322210231/..,,,,('))+,/02389:<<<<<;:89<=?YbHB@>BIIFFGFFHGHJBCHJKLKJJJJKKJHGGD@5&%%&&(***+*'%%$%,.//.048=>;=;3' "*012/-,++)''))&%%%%&$$$$$$##########""!!""'.<K^n}yupkf`WWX]fg_[SO]dRJkqmrvuuqmd\RJCBCDEGIIIIHHHHGGFGLS]jv ztqmkihhhhjjjjhgggijmqty{~ |ywvxzywwvutsssssstttvvz ........000111222233332223333322333333333333333322110000/////////////..............-,,,,,,,,,,--,,----------------....--.///..--,,+)*****)(())('((('&%&&%%%%%%%%%$%%&%%%*/16=MZfonhdcfV+!%$$$$%%%&'(().6FXo}xofUE?69Tupl||kcbeiv usuvyzbL`pprv|{rjgihhmlkmryxz~ymaVKKGMK.03111112310..//-//---++,,'$))'(,,03588::9:;:::;:=??BCCBADFILIFHGDFIJH@>FHJLIFGIJKJHFEC@9)$$&$(++++*(%%%&+.00.1:=@B@=:4(%-24785/-.*&&%&''%%%%$$$#%#%$%%##""""##"""#%*4GYj}~{zwmlgY[W\f^F@F5<`v|wssntog\SJDCCBEGHIIIIIIIHHEHMT_mx}ytoljhhhiiiihhhgggiknrtx{~~zxvuwxxwvuutsssssssuuvy{........00011122223322222333332233333333333333334411000000///////////.............-,,,,,,,,,,,,,,,----------------....--/.////--,,+)*****)(())))((('&%&&%%%%%%%%$'+)'%&(+,/5>LZfoqkgcfcA"$#%$%%%%%&))+3DTl {tj_VPIDFRabf~zncahk~{ssuvw|yOQgljkoustkgjklot|}yq^MD;1224---.0011.///,*++++-,,,++++(%&&'(*+.135787567779::>BDFHJFFFFHJLMKJIHKKJG@@DHJIDDGIJIHGEB?9+$$%&(+,,,+)&%%%*.00/6>@DCA?>3( !"'07<<99852.(('%&&%%%%$$$#"#$%%%%#####$$$$$#$(2GYj| {un]\cieZPVO?Kj}~vthC`qhaUMFEEEGHHIJJIIGGFFEHMT`nx |wsmkihhhhhiihhhgggiknruy{ }zxvuwxxwvuutsssssssuuvy|--..///-/011112222222222233333223333334222333333332211111000/.//////..--..........-+,,,,,,,,,,,,,,------------------............,,,+++++****))))(()('&%%%%%%&&&'*8C7'&'&)-06?L[fotpigbeZ/$##$&$$&'(,3=Ocx wld`YWRNOXadn{wngfhpyknrprw{WAWbfacegwnalos{~kWD86444471*()/.----,,*)))*****(''''%#$&&''*,/1345533333788<BFIJIJJHILLPRQPOQNKHIHCABEFBBCGGHIHF@?9,%%%'(+-..-*(%##)-..1:@EIEBB=1$#%+2:=?=:8642+*)%$%$###%%#"$$%%%%$%&%&''())('),2EVgy |pdkoji[]h[5Adv|wrbcpjaXNFEEGIIIJJJIIGGDDFGKUamx |xrljhhhhggiihhhggijlosvz} ~{yxwvxxwvtttsrrrssssuuuy~ --..//0/111111333333333334444433333333343333333333222221100000//////..--........,,++,,,,,,,,,,,,,,++----------------............++++++++++++))))(()()'&&&&&&'''(-FZL00+')-28ANZenrsliccfQ)##%%$#((.9K`tyjcaa`\XVUYhuvsxrhfiounnnopwyY8KW\^]Z[nyjwyr} oW>9448?;697.'(*,--,,+*('''((''&'&%%%#$$#$&%'),./10222222478>CIKLLLMONNQTWXXXVROKJIGBAA?=?BCEFGFD@<5*&&'()*,,.-*('&%(-.00<CEIIEB:+! "%07;;;<;:741.,+'#%%###$$#"""!!!##$$$%&'())+,.18FYjz yrh^OG>40031:AMmyuvvsokc[QIFFHJJJJJJJJHHFFEGKUamw |vpkihhhhggiihhhggijlosvz~{wvuvxxwvtttsrrrrsssuuw{~ --////0011111133333333333333333344443333443333333322441000000000................,,,,,,,,,,,,,,,,,,++,,--------------......//..--++++++**++**))))((()((((''((((()+?UP<;-)*.2:EP[dmorlhdbegC#"%&$%+.;Oi|xcXX^a`_]ZWWe{{ozsggfmtxyvpomoruz\36KVVXXR\ntxrbk~z}iP@98865889;?5('''*,*)(('%%%$&%%%%$$$$$$"!$%$&(*.,-///0111499?EGMNOOOQTUVWYY]ZYXURNJIIGDB><=ABABCB>8/*''&')**+,,*(((%'+/03@BGJJH@2' &5:<=>=93/--.--*(&$$#!!!#"!! !"$$%())*.-,.04EZp~}}uj`WOE7.(&#&*6:@NYiottnf^SLHGIIJJJJJIIIHFFEFLWalx {voljihhhgghhhhhghjknotw{ }|xwtvxwvvtssrrrrrrsstuy} ..//0000111111333333333333333355444433334433333333334410000000..................,,,,,,,,,,,,,,,,,,++,,--------------.....///..----++++**++**))))(((((((((()))))**2IXK:-*+/6>GR^gkkflmc`cif;"$$%),5Ih |oXJNPX^bb``\Zc}~{ujjhkmouxrmnnruw^1.7KQPQMNX^ZRFBi}{uzvkP;56:987768;<;.&%%&''%%%$$$$#$##$##"""""!!##$%&)+,,----.0147;>FJKNPQQSVWZ[\Y[]\ZYUSONNKIGDB>>@@AA?<5/*''&'((*+++*(((''+..4BBGIHF?-# )5;=?>72.+)&$(,,*&$#"#""!#"!! !!#$%'*)*,,05;9ESd{x|yskfZRB9.((+09DFLONQ\kkicWOJIIJLJJJJIIHGEEEFLWany zumjhhggggghhhhhghjmorux~ }zxwuvxwvutssrrrrrrsssux} ////000022221133333333333333444455553333444444443344432011000000..........----------+++++,,,++,,++,,,,,,,,,,,,------.....///..--++,,,,**++**))**))(())'')+**,,+,-09LT<--/3:BLU`hjga`khbbcoc/$(+/>^~ w]B5<ELRX\_adbbds{nkjijlrtpkklouu_2,.9HKHNKJLBB?4;dxzpbN;4579;;:968:;;6(%$$%%%$$##""""""""""""""!!!#$%&&(+---,--/.;A=BEILORSTUUUX\^]^^]]\XUSSTRQPIFD@=;>=<94.*))(((((*++****(')*.4AFHHEB7) "#,6=><940,*('$$'(*($###$$"!"""! !$%'(+--/36<Qhnjp{ytlf\RE732249?GEDGIKWZY]aTIHKMIKLIJIIHFEDEFLW`nx zsligggggggghhhhhijmosxx }zyvvwxwuttssrqssssrstw| ////002222112233333333333333444455555333444444443344431121000000..........----,,,,,,+++++,,,++,,++,,,,,,,,,,,,------......//..--++,,,,,*+++*))**))(())**+*-.////111/GA1128?HP[cije[QZmgbagqT$#),2HlyiI.)24;DJRXZ]adfhuqigfhlmkiikloqv_,)+.4FHEGF>65IF53Vd]H<55689;;::979:;=-%%#$%%$$##"""""" !!!! #$&&%&(*+***,.49;<CHNORRRSSTUW\]]^bb`^YVXYXXWWUMIC=978730,+)))(((*++*****(*,/5AFFEC=/# !%-6<?;71.+)&%##"#$&#"$))%%#"###"#%')*,022/1=\u~zy|~|wpic\PFA:768:??ADFINKNHJ^eSUZHFJJJJJGFEDDHOXboz }vrmigffffggghhhhhjlprtw|~|ywuvwxwuttssttrrrrrstw| 00112233332222223343234444444444555555334444445555443322322200000.......--,...--,,,,+++++,+,+++,*,,,,,------..------..........----..--,+,,,+****))*))++-..00121100119B=57>ENT]fjjbUKHakecdnq>!(*6Qy}~sW9&(+,15<AHPTZ_dlr~z|shdbgmqmkijkoop^+)+**7FIEC>3,131*1DC843247:::;;;78:;<6(('&%&''%$""##""!!!!!! ! "#$%(***,,,.367;<BGLORQQQSVVZZ\[`aca^\]^]Y\^WSNID<75640..-,*)())**++++,+,-17@DEDA7+!""#(.4;>:4/*)&%#""!"$!%&)))&$#"$%%%&&*++,./-.;KXctxws|{y| |xrib\VOB:>:=@CEGHLNUQVZJHVS_i\MIJJKIGECDHQXdoy {vqmhgfeeeggghghhimmpqtv }{yxyyyutssrrrrrrqqssuv{001122333322222233432344444444445555554444444455554433323333200000......------,,,,,,+++++++,,+++*,,,,,------,,------......--..--....--,+,,,+****))+++--/0122334444444>JC9BLT]fklj`UE8Hiidbita*#+:Z|}xwc7%'*),,059@EMS[frx yxse``aipofgkmnor],(*-129FA@<6**)('(,/023598::9:;;9779<;-(*+,())*)('%#$$""""!! "#$%&)))*,,,/3689<AEJORRPPTVX[\\_^`ab`___\[[[ZWINH<74220/.-,+)))**+++++-,-0;@DED?5& !"$).39;950+'&$#""""##$%(***$%###$&('*++++*+3APWB=9<:Zlr~|us{~xpkg_UNDB@@CFKKMLLQNH?KKLOXglbIIKIHFDCEIP\epy zuqmgffeffgghfgiiilnqruv ~{yxyywttssrrqqqpqqssux~ 001222332222333333433444444455555555555555444444556644444222100000/.....------,,,,,,++++++,-++++,,+,,,--,,,,,,------....--------------,++++***++**,,//1233456656567788DLDHRY`fmmi[P>05VpiccjqH$)8Z~{xs\.&&)((+,039?FKXiwyzud^\]clphggilorZ-'*-5;9RH=:92+%'$&&)-.27@=989:998668<<7-+-/,,,...,)%#$##""" "##$&'())),,/373139>AHMPQOQSUVXZ]][[adcbb_^_ab\RTRG;753320-.-+++,,++,,./003;@CCA;1$!!%'(-26:93-)(&%$""$%#$%()),-(%$$$&''()**)(+/8ETU=5*'/BXk}}ukhqx~xsleaYRKCBFFFIPQSWTLIPNORYeV]]MKJJGEDEJS]fqy ~xsokggedffggghghikmprtvx ~|xxzxwtsssrrqqrqqqstsy222222332222333333433444444455555555555566555555556655554422111100/.....------,,,,,,,+++++,-++++,,,-,,--,,,,,,------....--------------,+++**,,++,-/03367668899888:;;;;>JURT\cjrmcWJ8.,?fmebelf;$5Qw|vq^6&'))'()*.38>GWlyu|zvh^\[^chggegmprZ+'*-5<Cc[B793,,(%(()*,.5<;557887876577:2++-,++,-./,*)(&&$"! "##$&'('((()-110/05:>AGNRTPRSSUUWXX[\]a_`__[[YY[VSNG@674210---++,,,,--.1333;>AA?9/$!"(+,/19<ED>3+'&$&&#$#&))(*..-,)%$%'*((((((+264<1*''*5CZn~|qjeiqx}xsokeYRLJIOOQUY\\VQWRKNTWZUIPXKDEEEDFNT]grx }xsokffedffggghiijlnpruv} ~|yxzuwtrssrrqqqqqqrst{333333331344333322444555443345555555556677555555556666554432221111/...----,,,,----,,,,,,++++++++,,--,,--------------....----..--....--,+,,,,---.1222559;::;;;<;;<<===@CFOVZ\fnvnaSF5.,3Mpifcem]/*Dj{upeA&%')'&))*/48AZryoszvna][^efffcfjlkZ-%&,17Jg[N8//,++((*++*,.13224577685596582*)+,,++,,+,+)(''&$ ""#$&''''''((*(),/28>AKQQRTSRSSSTTRUXZ\Y[^]]dc`ZRQQMI887211/0-------//34548=>><6+##$&)/19M`lpk_RA3+*&"""&-64688830-,*++*))))&%*+'%%'''(2C\p|pc\`hry}ztpkhbbca`_b_^][bmhY[^]YWP@8OVeZGFFGLU`isz}wrnjgfedfffghhiijmorsty ~}|zyzvvsqrrrrrqppqqrsu{333333333444333322444555444456555555556677655555556666554432221111/...------,,----,,,,,,++++++++,,--,,--------------....----..--....--,+,,,,-../24758;>?=>>>@@@AAABBCDFJLSVYZjvn_OD5/,-9ZtiedjlZ.2YwvpiO*')'(&)*)+/5D`wsjn~ztoqf^\\`bffcgmlm\.&&++0MgYRQ9**++,,-,,-.---//14587556875@P+*-,,+++*+++,*('&$""""##$$%%''((*/98<FLRSTPTXUTQOQOQSUVW^ab_bfc]UTRQM:5;9400....--/025684489;60'##%%+6F[s zm`TB.%$,9AFGFEACB<4442221000/-*'$$$$$&(4H_u{qaWY`jsy|wqjghddda`afkllc[WXW`efaa[LUk\JEFKOXbisz| }{wrnjfeedffffffiijmorsu| ~|zyxvvsqrrrrrqppqqrsv}2222223344465544334444445555554466666666556666666666766544442111110/..----,,,,----,,++,,+,,,-,+,----,,+,..--------....------....//.--,*+,,--/0125789<>?ABACDCDCDDDEEEGHKQY_^\cpkZL?3/.-0AipldfmsY-GlwumY5&()'))*,-1;Sqxljr||tomqm`][_acfgijlm_.%*,.1Pe]WUV:+'(((()*-./-,,-.0445314433KxR!//.---,++*++*('&%" ! !!#$$#"&&&(,37=EJOSWSSPROQNQOPRTRZ`efghddZN>QNY@0;=7220000100168862452*&#%&(4J^pzkT1):EMIA?EFFEA;:8:::<<:863,&! """%'7Ndw}ncUSYckt{ zzyxxwmlnmuyupklqosrikjkno]NTOGCMUZbirx{~ }yupnjggfffeggfgijkmprvv ~|zyxvvsqqqqqrrqqppqrx2222333344465544444444445555554455556666556666666666666543443222110/..----,,,,----,,++,,+,,,-,+,-----,,-..--------....------....//../.,-..//113689<>?ABDEFGGGGGGHHIIIKJNU]dggadg[L?2/.-04TpkidhruJ<azwpc=%'&'()*+-0A`xvklx~xsmlovd]\[aceffikl]/&*,-3Sg[Y[XN9*'&&')(-00/.++-012311699Ie6&.00/11..-,++('&%$$%%"!" ! !! !#""')-/7?DFINQLNPRPMJLNQRRSW]effhgdZUVKIZN67=:62111111278864230(%%',Cax wO0:BMB><@LJID?967:;<<9998-$ !$$$*?Si}tdSKPZeov| {zwuqsxzyxtufadkjpqong_TWgbfTLT\ejsvz} ~|xtomifeeefeggfgijlnqqtw ~|zzyvtsqqqqqqqooppqry3333444444454455554444555555555544456666455677665666666654443322110/..------------,,,,,,,,,,,,,,------,,--,,----------------....////010...013559:;?ACDDEGIIJJLKKLLLKLNOSY`gmpmd\UK?62////;_phedjtk@UwysiF(''))()*-3Fi{rkn|zqmkdavk\]\`cdcchjj]0'*+-3XhZYYVRJ>,&&'''+23/..,,-../24>FPaxw+13234321/-+((%#"####" !! !#$&)-3:@DEFHHGJLNONNMMPOQU\`feijgd[RMJZeXF?<:7320112366876332+'')AilB?FLEDMITYUOMIB==>AA>>?<2($#%%&'.BXmyeNBHR_ipx ~yvutuqtwwkfc]Z`^bfb[a\Z=AQd]^[_emrvzz| }zvsnlieeeefffffgijnosst{ ~|yxyusrpqqqpppoopppu{ 333344444454445555444455555555554445666645567766566666665444332211//..------------,,,,,,,,,,,,,,------,,--,,----..----------......0335400023378<>?CEFEHJMNNNOPPOPPPOQRRUZahnospbRC>7543//1Cjpfeely^Nt|voP+&(**((*-2Jmyolrxnlgbdopb`^^acabeih\0'+,06_e[]][POL@.('&'(.2/-,,*+,,-7BNYix[-4566321/-+*)(&#"""! !#$$&(-269>ABDEEINKOPPOPMOSV\l_aoljc[QLYe_UOIA98852212567798662,(9b{R<FIJJKNSVXX\aa^UUQONMGA4&##%&((.AWmudI9=JW`kv}}~~tjada^`h]aYUWafaWZa ACW_[`diosvxy| ~|yurmkheeeeffeefgijnosst| ~|yywusrpppqqppooppqv| 33444444554444445544444455444455555566666677666666667664554433331100..----,,---,----,,,,,,,,,,,,,,--..,,,-,,----......------..//..014577667799>@ABFGKMMOOQQSUUTTTTSSSSUV\ciqqqoe[PC<8542105Pqieffru]qysY0&&)*)),.5Rqvmmu~qeggiiith_^\_``cdghZ0&,-1:ee^`^[OMNQD-)('%'//+)+**+/8GWbn{6$/3444400--.-,)&$!! !"""#%)-.16:>@BCEMSRQNMIHMQU[ejoplmj`SNK`cUPPNI?8743334478:<<<949Z ^>IIJHLMRTZ^_bcd]Z^]ZXTE0%##%%&&-ASi}|pbE/1>JXelw xjaZY^\WWW\^^_jeXi}LZa^ZYforsvz| ~~~~}zxtqkkgedeeedeeghilnpsss ~|zxvuqqpppoooooonopw} 44444444554444445555444444444455555566666677776666667634554433331100/.----,,---,----,,,,--,,,,,,----..,,,,,,..--......--------//134468;<<<<<;;=@DDHJLNOQRSUWXYXYXXXXXXZ[`fkpsqoeZQPKA8433208^qgdhnvuxxa3(*(**+,.<Zxsmls~vjiiiijpma]\^cabfhfY0+.34?fd^]^ZMJPTSC,'&$$&*.*-317AKWhp|x(/244310/,--++)%#! "!#&(*,,-279<@BGMPQIBHKNSY__ejglni\TVMRdXSOPPMHA80/13666666<8=YzlFFIILMOQSVY\^__\\[\[XQB,$"##$$&)7McxynX=,)/@LYet~yvtdefeZZ^jjg_LHNk tSPLQYgnsuuw| ~||}}{xtqkieedddddfffgilnpqtu ~zywtsqqpoooooooonnrx 44444444554444444434444444554455555566666677777777778AC;6644443231000/....,,---,------------,,----..--------......------....--//13558;=AABB@==@CFHKNPRTUXY[[[\]\\Z[[\\^_bhnsvtqg]TLQUPB5211/>enhfhqy~yi>%'*())*0Bdyolkqukhgiigflc_]^bbdgfgV31114Gfc]__YFFNSTRC+&$##'6=69?EOX`jx U*,.10/...-,++*'$"""%'*+++,/37:@JKQQNJOLNPSW^^^acaaZVYSUc^QONNNLHE>72.+//02345OspsrHAHIJLNQSSSVWYWW\^^\XUE,!! "$%$'4Jbw~uiR3(56>DO^o| ztneZWVX^djdedRL[{}of[Valonw} {|{{{yxtokheddccddeeeghloqrty |yvvusqpqppoonnnnlot{ 44444444554444444434444444444455555566666677777777778DWN96544432310000....-,--------------..------..--------......------....--//23579;?DDEFEBACEIKNQSTVX[]^_````_`````bdgkpsvtrk`WMHDHMKA4101Gkkdchp} zpV0(*(+-17Jiumkhozojhhiieceha`_^bdeefU31112Pga_c`WEFKPQRP?)###%1DNDCMSZbiy ;/.00//./.-++*'$"! !#%&&*,,-/37?DEKMNKIJMOQSWZ^^a`b^YUSUc]QPNNNLHEDC>830.,+//DiwU=AYx uPDHIIILMNORSURRTWYXVRPC.!! "$%$&2E]r}sfR/&7;?AMany yskcYSQPKOVX^_^_v}WU[TU[`cp~~|{zz{|ywsnjgdddccddeeegimpstu~|yxvtrqqpoooonnnnnpu| 44443455444444444444555544334455665566776677776666667:GN<555444331//00...---..-...,,--..------..---...........------------....//1469;>BHJMMLIGGJLNPSWY[^_bbcddddccddeeffjnquvwrlcZQJCBBFIID:10Pofcbfr~~ykWC536:BJYoqjgenumkijjheecmda`_bdddeS0,*,2[idaghWIKMOOOOL9&$%(0AVXKHRXagr{%(.320//.+)***(&# !""$&'*-/24:ADGPTYYZ[]^emvustpkf`_ZPWf`SPOOMKIFDBBC=:421+2\~ mK*()=cvPAIJKKKMOONNNKKPSSONMKC.!!""$%%(2H]r{obM-)+/6?Malu| yskfYUQPR]_]\bgo{ ¢¥¤WLNSQJG@;ALVl~~zyyz{zyvsmhedddddddeeegjmqstu}xwuspoppppnnnnmnopu~ 44443455444444444444555544444455555566776677776666666657:3554443310000....--..-..-..-.//------..---...........------------....//357:<@DIMORRPMJLNQSUXZ_abeegffhhiihhiiiinqtuvurlcZTMFB@>=DMNE?>\ldccis}yrf\UV[`dlrnigdlxoijihfbdbmlfecdfddcR0)**<gidfegOLOMNNMNNK8((.6:K\ZLQY]dmw~ h(//./-+)(())('$!!""#%%),/112FWdlqrvxzvrttvtrtsstrmaUXeaTONNNLIFDBCBDFIA0/Aoz{~ }a7%()%2W} sIDIJKKKMNNMMIFKLNNNOKIB-!!""$%%(3I_t~wn^F(+3/*3M_iqx|}xpieb[U[\^\]agq iSHVaain\MJHGGDBJf||zyz{zyxurmhddddddddeeegjmqsuw|zxwuspopooonmnnmnopv 44444444444444445555554454554455456666677766666655665563455543333222110./.---.............//..........////..--------..--------00368;?CFKNRVXWSOPQTWZ[^bcfehijkkkkkjkllmnrsssutpkb]TNKDC@><:=CEEOghddfoy||wysppprropnhefbk{pgfffeccbmvjfeadcdbQ,**,IlgfhgfKKONNONLLKH82599=SZTOSZ_iq|C*,,***''&&&&&%# !"!&'*/9Nahjjijpyyvustsqooponprrtsqm`UQOONLIGECBBEJQ``P[{{||~m8&(%'/NwhCDIJKKJKNMMLFFJKLLKJIG>.! "#$$%(3K`w ~{tl\G.&1)(:P[elqw{~ {smijkhaZ^cgkpxB.0;M]`nqtfVXchimmh^RVgy{xyxzzyxupkfdbbddddddeegknqss{~|yvtrqpoooomlllmmosy 4433444444445544555544445455445545666667776666665566555553554333332211110/..-../00//////..//..........////..--------....------/0/28;@DHLQVY[[ZUSTUZ\^begjjlmmnnnnnmnpprsuvvvtsoib]UPMHEB@=9856;CRfeceisyuxvttuxvsolfbbebi}pijhjhiijo{nihcceb^N,*+/XlhfjnaELPPOPNLKIGFB?<=;=E9<KV^fnr|,!&(''&&&%%$$#"! !#"'?Zfgdcdegrxxutvtsromkkllnpprv{}t\PLNNLHFECBCDHSamrz~{}}}~y],$%&$$/NtZ9DHLMMLMNMLKFHKLMMKJIG>.#!"""%(8Pfz~~{tiX>+,0,.EQZ^gkquz|}~|yutspngefhkqx\2DL\ba]nsm£©¦ogmvwxzywxsojedbbdddddefgimooru~}{yvtrqpoooollllmmosz5544443333444444444555444444445555775556666666666666665555542244332111000//../....//////////////......////..../---..--..------/0146:@DHLQX\^a`ZXYZ\``dhijlmnooooooqqrrstwwvutqmfa\WQMJHDA?;96213<Wjcfeorghgfjkjifgiffed`fxsomiifeghh}rhfdaga\N,*)<gkhilmSBKOPQQONLIGCB@B>?9;54BQZbglus $'&'%%$$""!! "7[lica`acirzwuuwvtsrpnnlllkkloptzwZLONLKGECCCCJT`kt}y{{xuoJ)&&%&&.Nt zO7BFJLLMLLLKHEIKKMKJHFD>.# !!!%,<Tk~}yrgS8"'18=LQZ\`fknqrwy|~~~|wxwuqqqojnszA'7EQ[]QUb}³¼»º¹±¢~pwt|zzzyvsnkgcbbdddccdegilnqss }zxvtrpooonmllllnlot|5544443333444444444555444444445555665556666666666666665555454444332111000//..///..////0/////////....../////.....--..--..----..00146:>AHLPV\`cda^\]_abfhilnppqoooooqqrsuwxxvtsokfa\VSPMJFD@=:74111GjeacilXKRUTTNJKWhfa_\Zdxpjidgjjgdexxkhfchg[J,'.PnihlojEELPSTRQOMIEC><@E>;9=9?TV[`gmv~X&&$$$$%%""!! 4Zkje``bcclvyuvuxxxxusqpnnlljjjklnszy\ONLKGECBBEJRant}ywwsp`9,,(&'(/OsvI7BFJLLMLJJICEIJIIIHIGE?-$##!"#%,@XmzrdP5""&2BMPVY\aeikmqtxz|||{{|{wtttrptx~71<ES_VQMn ¶¾º¶·£«´fXp{yvrmgccbccccccdehjmoqsu }zwutrpooonmllllmmpu} 554444444433334444554444444444446666555677666666666666555344444444321100000////.//////////////////////.////.//.//.....---------/1358;@EJOT[aejiea]_bdfjljmnnpnnnnnqrtwyzxwvtrnid`\WTROLHFB@<97312:^ia`bkX04;:62/7Nhd\ZWS]xrigbdhjhilr}mfcbjo\G'(@hkijpp]?HNSTUSQPNIEA?=<>EB=8@ARdQYafku}=&((%&%$##""!! "Eloe^\`ddfjxzwvwxy{zyxvurqpqmkkkiijlr{z[PLJFECDCDITant~zvsstqrZ7/0,(''2Qu~p=3AHKJLMLJIABGIIIJIHIHF>,"#%#$%'/C[q }ypaM2 !%9KPTVUX\^cegklsvxy{{{}~zzxxuvy| 33<DT[LEX´½½«¨¤¬¹² w_nxwvokgecbbbcccccegjmoqsw {xwusrpoooomkklllnqv 554444444433334444554444444444446666555677666666666666555344444444322210000///////////////////////////.////./////.....--------/01357:=BFMS[aejmjfcbdehkkjmnnpnnnopqsvx{{xwtsqmgd`\XVSPMJGDB>:74213Nj`]`gg5'*++,.9Re_WVPMVxshggcekejkntiffmv`C&BdjhilpqNBKPUVVTROLGEC?=9:@JJ?9>E[XPX`bju{- ()'''%%%""!! /]nk_^`cegjqzwuvwxy{}}}}ywvtrqonmkjfhgksyqULJFECCCDJTblr|zrppqnnmaE1//++5Uw ~~i2#5EJKNMLG?=CIKJJJIHIHF<*"##"%%&0G_w }wo`H-#%,BPTWWWYZZ]\`dfkqtvxyy{}}}}|xz}~ y77;AMRJDo¦·¾¶| ´º¸¶~uvutokgecbaabbbbcegjmost| }zvtrrqpnnkkkklllnpw 445543555544444444333353444455556655555656665566667777664444443344343311111100////////0000//./00////00.///0000/..-....----..../134679<AELRZ`fknnjgfgiiijiklnpooorswx{|yxxwvsnige_\ZXSQOKIFB?:8622/?cc^afjG&(*)+/;Te[WRMNTtwfghiiifhlp~mkjhuzpgihhlpnphCGMQUWURRNIFEA>=;859FI?:9BIPPV^fmu~w%"(((&%%$! !""Bjnb^`cefjpw{wtvxw{|~}~~}zxuvwtrqojgfeffmsz|iPJGFCECGLUbrxyupmnpoopomaG2-,7Vy~~d0#$*;FHJH?53<CIKLKKJHHE8%""""$$&3Md{ }vm]C)"*:LUWZ]\\[ZYTUZdhlqttuwx~~}}~~~rG9@CKICM©¶½¶{Y©²ª¡£uqrokfbbaaabbccdfhkoqqr~~zwtsqoooonmlkklljmpx 555543455544444444333344444455444455555665555566667777664444444444222211111100////////0000//./00////00.///0000/..-....----..//0135789<AEKQX^djppmjgfhhijlknoqpqswy|}~{yzywrmjfc_\[XTRPMIFB?;962304[g`_bhW(%)),0<VfZUQOPUrxheeebekhimzskjio|zxoloppq\AHNPSUTROLGC@><:95328CG>15=CFQ[^enu{k%&&""#" ! #Nnl`_eddirwz|wtsuxz{{|}}||xtuxvtttrpkigefhmszx`KHDCDDHMXfrxwrqnmonnqromk`TB?\z|~y[(""#!*8AA7/9@BFGIKLJIHD6%""""$$(:Qi{ xmZ?&%1DRVY[^__ZYWTSY]ekmoompsx|}}|mVCFEIE@a¨²·¸ª|¦ronhfdbbbbaacddfglnqrs~zwusqooonmlkkklmknry 65554345555555444433443333445544555566666655445566756666545544443333211111110000//////0000//0000..//00//////00/..-....----..000135689<AEIQU[biptrnjggfhikloqvyy|}~{zwqmjfb_\[XUROLJGC?<973213Li^]aca0$()+0>XdWSQRTXirfabcaailrpqujffhq{zyuonprnNDJNORRROKJEA><:75331/2@C:37<?DT\acnu| X"$!! "#$&# -Yokedhhinrusvwustuvyzyz{yrjiihquqprvwrolhfilpswrSIFFBEIP]krwvonmnpmprqojeiooko|~}wS$!#$#"%#+*(/:?EGIIJEFF?1%#$""$%-?VmylX:&*;JU[ZZ]__]ZWUSV[`eikljkowz|||}~mVOLGIBAl¥««¨¡ ¡skidbbbabbbcceggjnqss }zwsqonnmllkkjjjkkns{ 655555665555554444554433334455545555666666555555666766666555444433333311111100000/////0000//0000////00//////00/..-....----..00013568:=?CHNTZ`gnrusnjighhinqv| ~{zwqnkfb_\[XUROLJGC?<973200@c`X]be@%((*/A[bVRQTVVdsg`ccchmmokgqsh^_cimrx{xttm]GIKMNOOOKKJEA>;9653321/2<=437AGRYYaemuy M "%(+-% 7amhdgimmrxunprrqposvvwyzphcehmnopnmkkrxwoihjlmrtsdMHDCGKUcotvtnmmonkntrpljhimr{{~|sK$!$$"$$$'*+).:AEHIKMLE<0&$$##&'0DYq whT4%0BLRYZ[^``_\WUSTY^afhiknpuquz|}n[ZZTQHDq¦¥£jedabbabbbcceghkppsv~}zvsqnnmlllkkjjkjlnt~ 55665566645555444455443333345544555555556666666666666666665554443333332211001111/////0//00//0000///////////////..-....----..//123579:=?CGLSX_flqwurokighiot{ }zwrolhdb^[[YUSOLJGC?;8621./4SdZW[cS&%(+2C]aXRQVYX`ricjjnlkmkllcgie`]cinquzzn^OMNMMNNMLKJIEB?=96522/.../6A?7>GNWaU]dlt{A !!!! !%(/4-Aemhehjknvwpiknrponoqttuvkcaaagjmnqnlifju{slljjlnpqmWFCCHNXeptxqnopqnjmrroljjhkt}|}}rC#$$"##"%%',.**4=BGHGHE</%$$$$&)2G^u tgS1&6DKOV[\_^]_\YVTTX\`caceikppmuw|pdgcZVQOu¦¥£¡ |fbca`abaaccegilqquy ~{xuqnmmllkjkkjhkkmow 9999886676555533445544333333444455555555666666666666667666664444333333221122111100///0//////000000/////////////..-....----..//013579:=?CGLPW\ciorutpmifhltx| {vspnlhdb^[ZWTQNKIHC?;86220/1Cb[XZ`]2%+.0@Z]VSUWXY`smdhpqqnnqmnk_]bidfgklngaUQSSTQQOOMKIHGEB?>;762100//,-7FE?CHNidU_glqx}~8 """""""" !#"'/8HZhhhkmmqwvligillmmopqqtr][[[_`ekopnoqmjditwqokkmlnnreIDBIQ[hqwunnpppmfirrpnkljiq{z|{k;$&%$%%%#%',12+$(6ADGFB7+#$%$$&*6Mcz|reL.&7EKOSYY\^]][WUUUX\^abddccikjorx||rnkge`\\|¤¢z¢}kcbbbabaaccegilqrt{ ~{xupnmllljjkkjkjjmqx 9899998777665554444444333333444444446666556666666666666665664444433322221111111111//////--//0000110000//00/////..-....----.../013579;>@BFJOTY_eiptwsplggmwz} ~zvusplifca^[YVROMJHFC>:86330//5Y`WX\aC&),0A\]WVVYYY_sqijkmqljjlnjjdXYYR=:>BFRSWYYYVSOOKJGFDB@?<;9740/.----/8FKFFCSrcY`iisz}2! !!"####"$$%$$""(%5HMRZdjootxwiceddgggknpnojRQXW[\_cfhlpsutnjdkyyspllmlorpUDCJQ]muxrnoppnibhqspmmmmlu}|{yh7%''%%'(&'*./41*$%-:DC@3*$$$$'',9Pg|yn_A)(<EJNRVY[]]\\[[ZZ[]_`a``_beedhlpx{}|wsspkgfe ~sVoyydaccabaabcegjmpru}~ {wtpnmkkkkjkkhijkmsz ;::::::877665554444444333333444444445555556666666666666665554444443321221111110011//////--//0000..//////00/////..-....----.../013579;>@BDHLRV\agmrvvspljnuy{~|zyuroljgeb`^[ZWROMJHFC>:86330..1Ib\Y[`T((+0B[]WUUWWX\ovlnquyqoqrkike]\ZL==@HNTXY[[YVSQOMIDCBA>>=<:8520...//159CLIB2F^g]^djsy2! "$#$$$$$$$%%%$%5SlbNJWblqx{obaacaaccbgklnaAELRXY]bcdgmquuwsmiit~yqnkmmmprcHEJUarvxropppnb_gqupmkjhlr|~}}ze1&&'%%&(())&%)24,&%%.;>1&$%%('(-?VoynZ8(0?GJNQTY\][]][[[[]^^_`_^]^bcbcgjovy}}}{uttrsqopv{|o`ababaabcegjmqst }zuspmmkkkkjkkijilns| <;::::::98887766444444333444333344556655555555666655666655444444333322221111000000////....//////00//0//////.....--....-------.012479:=?AEGKNRW]bjpuwvtmlovx{{~~|z|{{zxvqolkiecb_\YWTROMJEEA=977321-./9]aYY_a6&+0C[^YYYZ[[_kwqnmpy{xtwohef^WTG<>BHMRVZ\[ZWUSPLHDA@><<;:8764311/015;?;;JMFAB]g_`dkry/"&#$$%&##!$$%#*FhuqdLAJ`t|yg_b^_acccaaeikT?CJHOTWZaa`dintyzwtqloy{rmmmmkmmiPILWfvxuqqrqpm\^fptqmkkiks}{}xa,'('(('(*)'&'(-00'(()*//*)'&(%&0F\syjU1'5EKJLPRTXZ\\\\[\]]\]``_`]^^[Y\`elotwyz}~xrstuwutv ¬¬¢z{¡xdb``aabacehknrtt {wvuqnmlkjjjkkkkjkow =<;;;;::98887766544444333444333344556655555555666655666655444433333322221111000000////....//////00/////////.....--....-------.012479:=?ADEILOSY^dkrvwwrprsvwz{z{{zzzywuroljheba^[YUROMKHEB?;976411../2Ma[Y[bO'+/=X`\\\^^`agwuoqwwvxrkheUJOA:=@CKQUXYYYWVRPMJEC@>=;::::86532334<=;;=<>S]Zcijcddmsy~+('&%&&$#%$!(Onxi`c^K<ATntdY[]^bbbfe_`hdO=?ICGHNUVUWZ`hoty|{xuqlt|tlllkjhhhXKR[mwvqpqrqpeW[fosplkkjmt}||wY)'(())*))('''(*-4.(()****))*)))3Kat xfL-,<GJKLNQRVXYZZ\\\\^^\Z^]Z]__\YZ\^biostwywqnpuuuuvw ³¶®£{ |rabaa```bfhknrtv~ {wtqpmmlkjjjkkkkkmrw ==>=<<<;;;99887765444444334433445555665555666666665566665544333333321111111111////....---.//00//00/////...........//..//.-....012468:=?ACEGJNPV[_fjntwurqrtvxyxxyyxxwutonlihfb_]ZWSPNLIFC?>:97422/.--.=^^YY`a;(-=Xe`_^_ciigs}ut| ywwunj_QJPE:=@CGNQVVWVUURPMIFDA@><:::::7544579=<<;=6,3L^fginndbjqw{ &!)')'&%$#"5qyrke_]XN?>QRVYYY\^bbeeeefWD=<BHEHFMQOQT_hosstvyywups}xolkkjhif[RTapvtprssrm^VYbnrnjjjimt~}~uT'()(()*)(('()'),21)'(*+*+,,0--/;Si|}scC+/>IKKMPPQRUWXYZZ\]_^]]]]\\]^ZWXY[[`ippquqghmosvxwz®³« ¡~f`_``a`adhmprtw }{wsqnmlkkkkkkkllkmqx @@?===<<;;99887777555544334433445555665555666666665566665544333333321111111100////....---.////--00/////...........//..//.-.....02468:=?ACEILMNRV[`glpvxxtrtvvvwwuuvvutsnmkgfe`_]XUQNLJGDB?=;:8641/.---2Q`[Z^bR*,9Udcddelppir~z}|v|unf`UK=:9<?AFHMRSTSTSRPMJGEB@>=;:;:7544456;>=<;;3/+*<Yfgjrthcdktz~,"))'''#@rvnjjkijfZA8IMEOUVY\acdeg^C4E?AQKEHHILNS]flrttstuxyxvx}}umjlhffcaWZiuwsprstphVTXalrnjkijnu~|}rK$')))()((***)*')03-''(****-0339G\s |r`A+3BJKKMPPPPRTVYYYZ\___]\\[Y_^[TUY_`ZbjjmodZ`gnswy| ¨©~o_`__`_cfkmppuv ~yurplllkkkkkkklllory A@ACB@?><<;:::88776666554455444444445566665566555566665554443333222222011110//////....---.--...........---............//.......02458:;>@BDFILMQUX]dhlquxtrttuuusssrtrroljhfdb_[YVSPLJHFCB?>:87641/-,,,-A_]YZ^b=)7Qffiknqutpt|pmuslj_XH789:<=AEKPQPPQPONOKFB@?>=;;988543237=>><<81...+0Jchhqqogfmsx}(!'+('"3suokkmmloo[C8<ODJUXZ[\^^[L94586EMIBIJJLT\cgimprqppuxzzz}}unhjgcaab\`pzxqqqrslbPPW`jpkjljjqy|~qC&''(''()*))**)(*-150'(())*,234>Rfz }yo]:+9EIMOOOPOOPRRVWYZ[^^_`]YXXW][TSUYed_dehk]V^fmtz{¡ yda`aaadgiloswz |ywtqpomkjkkjjjjjjkns} BAA@?@?>>;;:::88776666554455444444445566666666555566666644443333222222011110//////....------...........---............//....../123468;=@CEEGJLPSVZ]cglquuqqqpprrppqpmmljheca^ZXVTQMKHFEBA><:76420.,+++-3O`VUY`W,2Jaklmqvxvqqsmtlnvieh^XWB8::99:=AHNPMKLNOONLHC?<;:8778854325:?@><<71//0/.+5Sjjjurhejr{v$('(&jwmhjprttxrZG<7GIESWYZZXE657:81:DB@BDHIQ]dgfghginnprvxwvyzuojhea_`a_dswvsqrrpiYNOU`ilijljjq||m<%'()))*+*+**++*++/471&'()),18<I\r wjV4,:DIMOOPRQONOPRUYZ]^_a`]YWUU\_]YSONaggfgfZYagrv|~ ¢tfeddegilmpsw~~|vsqmllmlkijjjjjjlou FFEDDCA???<:998877777755333344443344555566666655555566555544333322211101111000////....--,,,,--..//------........--...........//112356;;>>@CEGINQTWY^chnnoommnnoommmkkjhgeba^[YTRQNKIHECA>>;86631/.,,++,.<\XST\_A)=Tflos{zvolgin[bqxg[OSSB::;9977;CJMKHFJKMLKEB<::88887643237>BB@><5111100/,'@]flmpmjmvy~O#(&Qzqkjpuw{}`VG;3;B;?IUZO5)6;9=@=;;>BDGIQZchifabc`_eikoqstsrqqjd`_^`_chwxtturqodRMNT_fhjmkikr|}m@2.*++++****+,--,--08</&(),..9ASgy ~udH-+:CGKNOPRSRONNORSX[]___^ZVTQZ`c_ZWTU_giie[_hnuz~¤¤¢~}lhkiikmnrsuw~~{wurolllkjikiiiijjmpw HHFECCBBA?>><<:988777655433344443344665566666655555555555544333322211111111000////....--,,,,--....------........--...........//0123578:;=?BEGHKORVXY]begikkkiijjiiihhfedb_^[WUSPNLJHFCA?=<886420,+++++,,2M_VQW[W,0=P^houztmh_WQJZr sfe`I=9;99778>CIHEEFHIHGC@<:85545653357=CDBB=94222010../,+Dcjmpqmpsv~ w $"B{qhjovy|w\VVI9257876;8,05679:>>>EHLQTZ[aijfa^cf_Z\_abfoqmlnqjd`_^^]alvtqqrqoo`LKOU]cekmjjnu}rRIE<0,,+*++*+..,+--.2:92)-8>7=Mbt ueJ/'0>FJKNQPPOONMNNORX]]^_^[WTSW_fhdcbejklnjcipv|¢£ vpnmooqrswxy ~~{wurpmlljiihiiiijjmpx MMIHGFDBCA@?=<;;8986556666554444444455555555556644444445544333332222222221//////......,,,,,,--,.---...------------..--..........0023668;<>@BEHHMORVXZ\`bdeddbcegfeefecba^[ZXURPOLJHHEAA>;;86421.++++++,-,<[YRU[bE&19EW`ksoha\XUIH`| {uujN<9<:87658=@A@BCCCCDA?<96430//146;?BEECB=6323311210320(3Rgmmjt}wtvzj7 4uujjnvw~u[TUVN>423565432788::99CJNPT\_`^ejhe`cghec_]YPP`omjlqkb]\]^\^ovqopqrqlRLLOSX\ellgimv ylc[SD4-+++++++,++++,.4;=033:>DZn{uj][WLEADFHNONONLJD@ILTZZYX\\[WY^flooprsstqsrqsw{ }wustvwwxwz|~~~|ywtqpnlljjjiiihhkklqy NNLKIHEDECCA?=<<:996556666553333444455555555554444444445533333332222222221//////......,,,,,,--,.--,---------------..--..........00234779:<>?ACGJMQSUUVX\\^]]^_acba``_`^]ZWVTROOKIGFCA?><;98521.,++++++-,-1G_WQZd_0(0:MX[ced^XYZRJNlztpoorbQA<54421/14554678;<><:;964332158?BEFFEB>9311222255555450/=Zinny}{ueRT8'/ltihlow~|gWWZ[XRA3468<3/1666:>?BJMOTWW[__bfgfedeehea[YRE@Qfjjkpi`[[\\\eyzppqsqpbJHKNPU\eolikp{ ypd[I6,+++*++,++++*,/5>735=BQiz}tj\]affeWJFGLLJKGA.*>BFVXXY\`_]^emrsstvyyxxwvxz}{wyzzz|| ~~~{ywrqpnlkiiiiiihhjklq{ ONNMKJHFEDCB@?>=;;977677555555444444444444444444333334544333333322222211110///....----,,,,,,,,,,--,---------------------.....-.-001445789:;=?AFGKLOPQQSVWXXXXZ[]]\]\\\ZXURRPOLJJGECAA?;9764320,,,,**,,,,,-7U[SU]eO&,4@MSUYXTRVYXUR]lmkfhibXK8-.-+*(('''''*-148<ACDDEGGC@AEFGGGFEA;40000155766655633.0Ibgs{|xX $+kvlnqoryscW\\[^\UA556530-/4DIFFXdTQVZZY^^bded```dcdc\WTM?:CUbggkg_ZY[]]n~wpprqqsXFEKNQW]ejlmrxsk^L4-,,,-,,-+++++-16:;;BLZr|sh`]bfcbbYGOMG>9=?9#/3.GXZ\_cdcfkrvwyyyyz|~}|} ~~~zxvrppnlkiihfhiiiikms} PONNMKJHGEDB@>>=<;987677555555444444444444444444333334544333333322222211110///....----,,,,,,,,,,,,,,,,,,----------------.....-..//03345789:;=@DEIJLNMOOPQRRRUWYZYXYXXWVTQONMLJIHFDB@>;977531/.,,,,++,,,,,,/C_WSXch>*-3@LPLLLMNRVYVTgkkhcbb^[J+('&%##!!""#$&)09ENQRPOONMJIIHGGFCA<7100001567666445554105I^owyye0*izmnmnrxw\XZ[\]a__G65531-.04FJILelSSSRSW]]acb_\\\]_^ZYQNC:7=O`g`ij`\\\[ax~urpsqqlLBEIMQW^djnuyvk[D0-.----,,++++,/4;;=@Nbv{pe_DFciea^Scmg_VL=@;9<@KY_acefhnrvyz||||}~}~ ~~~|zwurppnlkiihfffhhijov TSRPNNMKIGFDCB>==;9:8677466655444444444444444443333345442222331133222211000///..--,,,,,,,,,,,+++++++,,--------------------........013355778:=@DDFHIKMMMMNNOOPSTSTTTSSRROMMKIIGGFD@?<<:75421.-,,,--,,,,,,,-.5TaVX_fa0+/6ALJFGHGHMVUSXiopcZ\daWC'%%#!!""##$')/:KUTRPOOONJJIIGDDCA=7300001446777;895653560.07?EB5,jxnnrqpzz]XZ\_aa`aO@74430/--26>ABJNQWXUTUZ]__ZZ\[]]YTQMGA<66=O\^V`lb]]]\l~uqqrsp_DBFHLOV_cgr| tgV=//......++,+-.07<@ERj|pdcP=^gbeehhhhlolbODKQQYafgknrsx{~~~~|}~ ¡¢£¡£¢ ~~~~~~}~{yvtqonllkihgfeeggijpw UTSSPOOMKIFFCB@>=;:8977765665544444444443344443233333332222211111122221100/////.--,,,,,,,,++++++****++++,,++,,------------......//01115577:<<>ACDFFHJJLMLNNNNPQPQPPONLLJJHFFEDCB@?<::86420/.--,+----,,,,,,..?a`WX_gO(*.6BMJGIEDHKMTW^mqf_`de^T9!$"! """$',1?P[YXYVRUQNJGFFDA@?<9400001244668<><9557754410/-.+1evmkoqpw{cY[^^dffP>8;84422101347:BKPX[YZYY]]]]ZZ[\\YTPMI=8656=KSTQYhe[]]aw}vtutsqR?BFGKOV_hou}sbM5---....,,,++-,27K\dtylcbcZ^eccefiiggjooppokdgmrsuuyz~|{zxy{|} £¦ª««««¬®°ªª©£ ~~|~||}}{yvtqomllkihffefggijqz XWUSRRQOMKHIEC@>=;;9977765554444333333333322333344222211111100110120110011//...---,,++++++******))**++**++*+++,,,,--,,----..-.////01333356799;=?ACEEFHIIKKLMNNMMMKKJHGFDDBCCB@A@><;967410/.---,+----,,-,++--4PeYWX`g=#,/6CKJFDCBEFHQWerjihb]X\S2($!! !"%)/=S^^``\[[TQKFBAA?>778821001233478;@A=8875544320-,-6lwljonnwzh\\[]`aW:(.7;9520036435:?GPUXWTVYY^^]\Z[]^^ZTRQG;7524:FMQSVdf]^[fzwvtstvkK=BEJQU\ky~ym]C2.,//00...++-..4Caw ug`_aabacddggeehnrrvy~}||~~~|{xwtrsttt~¦¬¯±´´´¶·º»»º´³²®©¥~zzy{{{|} ~~}}~~zxurqnmkjiihffffggijs|YXWWUTSPNMIHECA?><<:877765554444333322222211222233222211111100110120000000//...---,,++**********))****))+++*++,,,,--,,--....-.//001122223668::<=??ACDEGGIIIJJJLLIGFFDCDA@A@@>=><<9864520//..--,,----,,-,+,,-/;ZaZUYed3&,/9CKHEECAAAHPZkuwp^PU`bJ( !!!!#%+6N`bedb_YOHA8500277569842111233567:>=:8875544533,-Bmumjmqqvyga\`^aX:))-07:952028755:?HORX[WOOVZaa^\Z[^]^ZXVWNB<655<FOUSR^g^Z\mxvustsqvtSAGLMP_s}~sfW>149;;<862-*+022C_y |ob_baabbdeeeggjkprtx} zxwttpqonnqu«±²·¹¹¹»¼¾½½¼»º¹·°¬¡ ~|ywvuvvvwy{~ }}~|yvsrnmlkkjihffhhhhimt} [[ZXXXTQONLIGDB@?=<;88875555323333332222221122000111111111//00001110//0/-/..--.---+++*))))**)((((()()*+++++++,,,,,,,----..--..0000000012445788:<<=>ABDDEEFGGGGFEGDCCCAAA?===:<=;9864332///..------,,---------3Hg`WU\g_)&&/5@HGFDA@@BIT_nro`PT]^W;!!""$'.D]ccdc\ND7,(#!#'(+.15:<943347679;==@:897565445307b}xmgkqsuw`Z\_abS-&(-/36477418;899>FOTUWWTPPRW^___\\^\[ZZ\\XK?<89DNTUVV[daW_v}yvtprrjjwxXEIMVcw|qcSEGLPPPMHC@81214Ed|xk`_cbcaabcfghjossu{ ~yvspohfimpmn²³¸¼¼½½¾¾¾½¼»»»»¶°§{yxvsrorpprrvw{~ ~~{xwsplllljiiihfiihhilu[[[[YYVTRNMJGEB@@>=<998755553333333322222211220001111111110000000000100//..-,,,++++++)))))**)((((()(')*****+++++,,,,----..--..00110000124446889:=>=>?ABBCDEEDDDCCB@>>===<<;;;:::77522221///.----..--.-...-,,--3ShZWY^iQ#%*,3>FHDCB@<AIRfppk`[^]]T+!!"%*6QdbceZF0,)%$"##$$&(*/9;844447;==?BBE@;:86766750Ku}tkimqvzua[_Z_gK((+.-1038:7<FE>;>>GMVXTSSSPQSQUZ^^]ZZYYYX^d`OCB>?FRUUUVXa`[i|wususj`bkuxXFKXo}~zzxsid_bfffb]VQJA846Ml|rdb`bcbb`abdglptw{| ~}}}|xsnjff_hmpko¯´¸»¾¾¾½¾¾½¾½¼»»»ºµ¬¦zz}~zxvtromkhhjjljoy}v{ ~~zwupnkjiijiiihfhhhhjpw[[\\[YWVSQNLJHFD@>=<:9765542223322221102222222111100000011000000//0/....---,,,+*++**))(())))(())((()()))*****++++,,,----------/1..0011223333667799<>>@@AAAAAA@??=>><;;::::999866555221110000----.....--------./8Wc\XYcd@!%)+1;FIB@@>=@EUjsrmkihieD!""#'.?^iedbL3*+)%#!#""#&(+/79666679=@BCFHGB?;8766543I~yrmilqsyud[^]aaD$)++-.047:=9>JF<9=ELPTXURRRNMONPSV[ZXZZ[VTZa]NHFCCDLRUUVV_`ar|xwvtvtd]`glssVO^s }qotvy}zxy{}|zwrkf\VKFK[q |xkaadbaccehiknquy}} ~zvvwvvusmhb\_dacgm¡«³¸»½½½¾½¶¾¿¾½½½½¼´ª styyyz{zxurpkgffijhdemnw{ww|{wtrpllkkjjiighhhiikq| ¡¡^^\\[YWVTROLJHEECA=<:976654222331111111111111100//000000110/////./..--..,,,,--**++**))((((((''(('''()))))))******+++**,-----..--//001122445566779:;<>>>@?????>>><;:9998888778866553221110000/-..-,---,-------./0?_cXVYba.#''+.7CGFBB>;<DXv}sjgbnt]/ !$'1Hhmml]7*-)'%#"!#%#$&*-46666679=@DFHHGC@<985441:suonqstyyb^`]agI%%*/9:78>>?A>=FD?=@FMNLMLKNQMLNOOQRSVWW[\VTUWUNKKGEEHNUUVV\dj}|vusuuuua]bbekso_i{xkhmtrx {tlf\\_m{ }tf]^_bdcfgklquw{ wnjjkljd[WTQRUSW`¡ª¶³µ»¸¸¼¾¼½½¿½½½½¼¹¦}imqtuvvwvuronkidipopkfltsjekpuv }wurqllkkjiiighiiiikq{ £¤]]\\[YXWTRPNLIGDB?<;:987643322111100111100000000////....//..........----,,,,--++**)((()(&'((&&''&&'((((())))()****++++,,,,--,.--0000112222223456778;<=====<<<<<;::::9988776888655533210000000///..--,,.---...---1Fd^UV]hV%&'&(+3?IGA??<:C^qjjbapsqM##'3NmpqnP--,)$""!!"#""$),0456678:=@DHJHGA>=875110^xtqtuz|~gY_]bgU&,)-4=GEHLOIGDEIEFEDEKLIFCEJKIIKNRTQQRUYY\\URTQOPOKHFELSVWUYds{wrsvvvwr_[__agkqtt yecholm{ |wolqx{o`Z_`^bgjnqsv|}~wz}|{sfZZXTPMWPGQ^jl£¨µ¹»²¬¾¾¿¾¾½½¼½·¢rYcilppqttttrqpnkgjonoppmqvm\[`emz ~xurolkjlhhiiihhhhjou}£¢^^]][YYWVSQOLJHDB?<;:987643322221100////../000//....//////------/.--,,,,,,,,,,****((((('&'(('&''&&&'((((((()**))**++++,,,,,-,,.-..00112222223456779:;<<===<<;;97777777776654446533332111000000////..//..--...--,.3PgYRXbeF#%$#$(.;EIF@?>>Hcpncekhkf8!$(1PopokG.,)&#" !!"#"!#(+-14679:<>?AEIJIA=>>:50/[yxsqtx|{~pV\^`h^,),,0:CDEOQSXSMQOCINMGFGEDCCDIIHGMNTUTUVXZ^_YTSQOOQOLHDHQVWX[fuztrsstuwr_[\``ago|wdbdljbq} {m^[^^^einrwz| {ytquy|vvld[T^aFDl§²·¸¸¹¾¦w»¾¾¿¾¾¾½²]RW^cgkloqtssrqqroloromkkloooaORV`t }xtpmlkihhhiiihhhkpw ^^]][ZYWTSQONJHEC@<;:976644310110000//.....0////..........----,,--++,-,,+++***))))('')''('''&&&&&&''''''((((()))(***,,++,,,,---.111122113333335556798:::::8:::888777666666544443333222221111000////.//.-..--.----/6[bRQWab9"##""',6BHGCBB?PgbXZ]gknZ("%-Ffmjf@+,'&#! !$%##"$(+-/336:<==>AFIKC??A<25`wrlstxz||u[Z]]dg:++,-.6=AGOTXfbWICDMLIGEB@ABBCGIGHJNSVWWYZZ]a]YUSRRSNMHDEMUXZ[fsvqswxvuvq`[^]`dgstbccii^j{ whZTX^bhkos{~}zzzug\armnge^_qsIZ¬±´¹»»½¾¼»µ¬¸°³¼¾¾¾¿¾º§uHEORX\`dkoppprrqsvvrrxtlgginrriRAMWo ~zwrnllihhhhhhhhimqz ^]]][ZYWUTQNLIGEDB=<:9766433211100..//.....0//..,,........----,,,,+++,++***)))))(('&'(''('&&&&&&&&'''''''''((((()***++++,,,,--./111122113333335556778899999888777666666666444443333222221111000/////00/...--.-,,+--=b\TUZ_W-!"!#"(2BJJHCBAX_OLOW_hkC#)9YhibA,*&$" !$$#!!#$'+-0369<==>ADHIGCA?8Lpvmmlpvvyzs_Y]\^eI)-,11059@HOWakhWJGJNNJEA@>>??BDFHILMOSVXYXZ[]^\XXWWTONJEDGQWYZervsvzyxuwr_XY\_dkx~qbacfg]h{~vh^WUajghov}}}ytonj]SIennpuhU\jLo¦´¸»½¿¿¾»´³·º»½¾¾¿¿½»°dFKSQOQUZ^bgjmprqrruyyrswqppoqwvshKEYp |yupnlkhhhhhhhhimr{ ^^^^[ZZXVSQOMJJGEB@>;96664331111//.-----....--..,,--..---,,,-,,,++++++++***)**))))(''&&&&&%%%%%%%%$%'&''(('&''))((******+,,,.../00112111223334446655777787777777666666777766664333333322222211100000//..----,,,,,,+-GdWRU[cV,!"! "'0<MROKGKa]QJKMYnc.%1Rd`V?,&""""!!#%%##""$(),.2449:<>?ABAB>;H_tqljiputvvq`Y\]^f_)+/0325888?HXil_UMMPSUSME==<<<?CFHJLMMQSVYYYWX\[ZYXXTPNIECDMSW[ennpruvsuvng\X]_hs oaaabeal{~vlb^_gossy}}{wutskqo_YpsmklsjZX[Wuª¸º¼¾¾¼¸±ªµº¼¾¾½¾¿»´rQKTTQMNNNSV\`dilooppsyvwwssmiprpoaBKp ~{xusoljhhhggghjms~[[[[[ZYWVSQOMJHEDB?=;97764331111//.-------------,,------,,,,,,+++++***))))))))))))'&&&&&&&%%%%%%%%$%'&''(('&''))((******+,,,.../00112111223334445555777787666667777666777765664333333322222211000000//..----,,,,)***0QfUPV\b[-! &+9HQTMIQe\NNPSetK!$/NaYUK;'""""!!#%%$##"$')*,/14668;;;<;7Icrsmfgjopu{wmaZY\^`d@-,/02589736AR\UKHPRUUWZUL@=;::=?DGLLMMORVYYYXYZZXUUUSOLIDABHOVXbjjjlpsqsrke_YZ`o{oaaaekiq yrnjhny~ }zwuvrrronpqrnoqmkhfYS_c|©·º½½»º¶¯¥°¸»½¾¿»¶©~UMRRQOLMNOOOQV[_cgkpqqrt||wqsxqj`mvxwn\ay}} }{wtqmlhgggghhimu~ZZZZZYYWTSQOMJHEDB?=;:9764322000//.-----------,,,,,,,,,,,,,,+**(++++))((((('''(((('&&&%%$$$$$$$$$$#$&&&&&&&&''(())))**++,,,,../000112222233323444466666677666667777666667766554333333333221110////////.-----,,++,,+()5_bSPU\fa2 "(*7CRTOJ[i[PKQZpk5 ,Jc]TMG*""""!"$%%&%#$$',+**.//1269841Owpmgdfimppwpmc\YZ]^`Q1//2258<;:8<=KE=;EQWVXZ\ZQF>;;;;=BFLNNMPPUZZYY[\YUQOOLKJHDCACKRW`gghjmompmdcb\Zdssb`bhru} {yxsrx||zywsqpomppkjknpqmiejc^c_v¥µº¼¾¼º¹µ£¬¸»¼¹±eJLSRQQQPPOOOMMQTZ^agipqrtssusv{|tq}py}y||}~ }{wrpnjihfgggkox\\\\ZYXVUTQONKIFC@?=;:9764322000//------,,,,,,,,,,,,,,,,,,++***())**)()))))(((((((&&&&%%$$$$$$$$$$#$&&&&&&&&''(())))****++,,-./000112222233333565566666666666666777666667766554333333333221110///////../----,,++,,))'):d`USV_id4!"##$%+3>MSPSgnZNNSatX#(>afYMB*""""!"$%&'%&%%(0/*))**+++,.,NrqkhgkooopviXea\ZZYUX4..366679;78@?B;19EORPRUSQJD=999;=BFLNPRTTUWYZ[\\YRLKIIGHEDCAAFKT]ddehklkmj_`hd`hwub_dkv{~~ ~}~xuqmmrsppqonmlklonnncY`ed`ZU`´¹»º¸¸¸´®°©²º¸¬QLMRRRPOQPPNMMNLNQUY^chlnrqprvx zy zx{{{|}} }{zwsomjhggghkpz}YYYYYXWUSRPNLKIFFD?==:8764332000/.---,,,,,,,--,,--++,,++******((**))))))))(((((('&$&%%%%%%%%$$$$$$"#%%%%&&''&&(())))***+,,,--//00000123323444456666666555766666676666677666655543333333322221100//../...------++,++('''BkbTRW_hc8!#$$$()*6FQTXliYPTXhsA)3FOJ</%!! !!"%&'&$(&(+/,('&(&$$!*Lrnmhhlmnmpr[Khf`\ZYQYL*.1699788:75786328DKMKKMMHEB>:88:>AELNQTWXVVXZ\]\XPMJLJFEGDEEDAFMW`abehiikfZ]ghjp} wd_fpz }{voga\_chklpqroonllnmlU@MX_[H@Y°¶¹¶¯µ·«¯±¶¶ªNOQOQQSRRSQPNNLOONNSUX\bfimoqt| vvyzzzz{~ ~zuqolkjihjls| }|YYYYYXWUSRPNLKIFFDA?<<8764332000.----,++++****++,,++**))))))))(())))(()(((''''&&&&&%%%%%%%%%$$$$$$#$%%%%&&''''(())))***+,,..-//00000122233454456666666556666666665666677666655543333333322221100//......----,,++,+++(%#)HmaYWW_ggC!$%&%%$'/;JP\teWUW_rj2,,()%# !! !!#$')./,,(&+)('%$""$%WtmollnokmrrF>hle\XZPXW0+159:<<;>;55463.39DIHHFFEDBA>:88:<?DGLOSXYVVXZ[\XVPJKKHGFFFGGGFCIPX]`dgijjcX[cinw yd_ft}|}uhXLIORT\`bcfmpoonnjeN=R_^V>3Np¥´¶¯°¦²°± oMOT]WSTXWURQQPOOOOOPQUX[`hq}¡¢ wuyzzzz{|~ ~~}xvtpnkihilt w}WWXXWWUTTSPNMKHEEA?=<;98532220//-,---,+*++++++++****)))))*))))))(((('''((('''&&&%%%%$$$$$$%%$$$$$$%%%%%%&&''(((((()))*++,,,--/////00122244455566666666666555555556566556666666433333332221/111//00......,,,,++,,+,*,,(%%*KmbXVW]diK##$$$$"! &2?FkwcXWUevP#''%#"! !!! !##%.9@<:/(++)&%#"#!=spklonmposk:+`kid\[UK[A)/49::=@@=86542-149@DDA=?ABAB?<:98;=BEJLQVVTTVXYZVTOJKLKHHHHJKJHDDIMV^ceefheWXdmt}h_jv |zm[N\a\]^^ZZXY`ejmmmeWG=GJKNB?M^¯²R¤}³·ª v\MNRXQTYa]TUVVTQPRRPNOPR]n~¢£¢ £~xvxxzz{{z|~ ~}{wutrollkny }rxWWWWUUTTRQPNLJHEDA?=<;98652220//-,,--+*+************))))))))))))(''''''(((((&&&&%%%%$$$$$$$$$$$$$$%%%%%%&&''(((((()))*++,,,--///11001222444555666666666655555555565665566666664333333322210011////....--,,,,+++++++,,)&#(/Mm`VUX^fl[&"$%#" !)3HlqbWVZhp2&#$#!! !!! !$"(.3782/++-*'%#"#-hnkjjmpllta.5_kigc`bJ[Q++<>;:=?D>7559830258<@=:7:>>>?>=:97:<?DIMOSSQQSUVWTRNJKKKKIGJMMLIFACIOW\acffcYZepz}h_ly ~|xy~ ~wod[mm[RWVY`\XXY^dffc\UF51GQORZ]z¢ª_s²µ¨vVIILOOOSVVROTUTUTRQQRQNMa~¥«°¯ª§¡ywxxzz{{{{|}~~|zywtromnqzvoxVVVVTTTRQONLJHGECAA><:87543320//-,++++**********))))))))((((((((''''''''''''&&&%%%$$$###""$#$$$$$$%%%%&'''''((((()))**++,--.-.000022223433555666666666665555556666666666666666443322111111000/0.//.-..-,++++++)))*,--+)%$'-MobUTW]dl_4!$#"! !%*Epn`YY`rU#%"""! "$(-/2761,-./,)%$""XvmggllmukO3Cahjjhd^L\[5.:@?@@BB>835:@B<4379976567::<><<::78;>CIOQQQONOQTUSQLLKKLLKKMNNNLGDABFOT\acec\[iv ~jbp{|wqosy| xsssrqigjhiicWPQW[ZVTUUOCCTSVY]get¤ £¯¯¦|WJHIMLLRUQOPPRUWWUQRTOTf¦²¹¼º¸°©¥ ¡{v{zzz{{{zy|~}}~~}}||ywrqppu} {rnw UUTTSSSPPNLKIGEDBA@=<;7574331///-,++++**********))))))))((((((((''''''''''&&%%%%%%$$$###""##$$$$$$%%%%&'''''((((())**+++,-....00002222344445666666666666555555666666666666664444332211111100///.//.-..,,++++++*)()+++*(&(+./PndXTWZ^ffC"" !Gwm^VZeq:!##"! #&-2111,+,-12.*'$#<sjjgikpt_<;ZegjfggaMV_F29E?DCEC<;756>FEA955564332599;;<<;:99<AEJPRTRMLMOPQQMLKKKLLNNOOOOMGDA@BGKRYaec[^o| |jcq~~ypggmsv{~ ~vrpppqstqoc]aSEEPWTNHINPMKQQPTSfa`|¥¨©¬ªmLHHMNMMNPPSTTSRSTRQQPUnª·»½¿¼ºµ¨ ¤£¡ |u{{zz{{yyzy}{{|}~~}}}}{zwtrrxxnmx TTRRSQPOONLIHFDCA?><;:866532100/-,,+++*)****))))((((((((((((((((''''''&&%%%%%%$$$$$%$#########$$$$%%$%%&'''''()))))**++,,-.-./0101223345554555557766666656665566666655555555444433221111100/..--/.-,--,+++****)((**+-,)'+,.0/HkcVSTX]_fQ/!!#Uzg][^j`%"# "! !! #(*,*++),,-461,(%2lnffjijp]<HgfaabcccWRVI65=;=C??=97532:BDB<543230312589;=>=;:<@DJMPSVURPONNMLKKKLLMNNPQPOOMHFCA@@CHRZaa`fx|y{ {jes~vl`_dkosw| }|zwtqqqpqqn\9489<DINMKKKMIJFMRRPllj §¨©¨UFGHJJILLOKKPOMMOPPQOXw¸»½¾¾¾¾¼´¤ ¤ ¦¥vz|{zzzzyxy|~wwy}}|}}}}}||yxtrz}uonz RRQQPOONMMKHFECBA?=<;976653210/.-,,,++*)****))))((((((((((((((((''''''&&%%%%&&%%$$$$##########$$$$%%$%%&''&&'())))**+++,,-.../0101223345555555557766666656665566666655555555554433221111000/.---..-,++*)))))''*)))*+-,)),,-013Egj]TRSY^dbF$!!!"'fxb[]`nJ""! !&(*++***--.120,,/cpjedhkn_M`jhdaacb[_[J?47899;<:97565108ABB=6310//012237:;=>>=?DHKMPRTSSQPNMKKKKKLLNPPQTSPOLIFCB@>?@GQU[bm|}vqu {jiv ~}xma]`eimqv}~|{zwvtpqqpqnM)5688<CDJPSOKHKMVZZV^_l¨ª§¤wDHIIKJILLMJJLKJLMLMMTs¯¸¼¼¾¾¾¾¾¼¹³¤ ¥¥¡|z}{z}|zzxx{}vsuwz{|}}||||{ywtzyqon{ QQPPOOMMMKHFECBAA?;;986654320///-,-+,,****))))((((((((((((((''''((''&&&&%%%%%%$$$$$$##########$$$$%%%%$%'''''()))***++,,-,-./01123223455555555445577666666665555555555555544333333111100////...-,,,+,,++****))))())-/0.+*+/3477Bcn^SQSU]cj[1 $$3lrb^[am0 "!""$(*,*,+++./012/,.`oghgijphdjkhe``acb]VH8205768>@<84364107AFDB:430.013454679<>AABEFHKNOOQSRPNLJIIKKMMOPSSUSPNLIFDD@AA>AEKTarznkvyjjw }zvkd_^_ehmsy |}~~~}zywvuutuvkR/.:<>CGKGGHOPQXXV\`aeidm§©¦ mAFJILKJIJIJJJJJJIKJSl®·¼½¾¾¾¾¾¾¾¾¹µ®¥£¡££z~}{{{{yzxxz~sptuww|{|~~~}}{yv{ uoon{ OONNMMLLLJGFDB@@@=;::76643220///-,++,,****))))((((((((((((((''''((''&&&&%%%%%%$$$$$$##########$$$$%%%%%''''''()))**++,,,,-..001132223455555555555566666666665555555555555544333333111100/.....--,,,+++**))))))(()))-/21/-.147:=>>UjeXSRVX_ihN% %((;qr]_akb "!!!$(*,,.---./0110,UrfcdhkpnjhgefdaabcdfY51333459AGC94451104>EFA:51/.013566779<>BBCEFJJLLLNOQSOMLIIJJLMPRTTUSOMJHFDCAAA@>?BO[p ~zqhltymlx |{|vkb_\[_`fmu~ {vqpqou|~ ~|{|{{{yxy{wgJ@AFP[^XKMX\]^\Zadefkkc{§§¥cCHJIJKJIHHIIJJHHIIOfª·º¾¾½¾¾¾¾¾¾¾¾º¶«§£¡¢¢{{|{{{zxxy{}nlnqttxy{|}|||{zy{}uoon}LLLKKKIIJGDBCA@>>;:87755421000/...,+,,****))))(((((((((())((''''''''&&'&&&&%%%%%$$$$########$$$$$$%%%&''((''()*****++,,---//0012433334455555665555555555666655555555555444333333221110///..--,--**++**)(((''''(((()-1442/236:<BEA>Idm\SQQUZfma9"%',,Htjc``pF! "!%&+-/20..000221LrhbcejppigdddddcefdjbE1255348:DX]93332103:EKF;53.,/13567879;>BCDFFIGJJKLNORRRNKIHHJJMOSTURMKIGDCB@@@A@@?FSiz ~}|ujcivyomy ~||zufefed^^ait zvmbaees{|}|}~~yphdehiidW[`a``bcfgjkohbl§©¤SCFKKIJIHFGGGHJGGHI]£²¹¼½¾¾¾¾¾¾¾¾¾¾½¸³¥ zyzz{zyxxyy~pjllqrtvy|~|z|||z}|tnon|KKKIJJHHGHFDBA?=<::877663210000.--,+,,****))))(((((((((())((''''''''&&'&&&&%%%%%$$##########$$$$$$%%%&''(())))****++,,,---/00112433334455555665555555555666655555555555444333333221110///.---,,+**))**)(((''''''(()-167524:<>BFJBC@?VkbWQPS[_gn^4$+,-.Oxj__`l4 "$&&)-1232244441IsmfffinphedddddeegddYC7534437;?CLN723322128CHF=830../0156669;<@BDFFGFGHHIMPPQNLKIHHHHJNPSTPMKIGDCA?@@CDCAAHYjt|~}|~|wnbbkw{omy}z||yulhjhe`^^iv }woeWNQXmvy{}~}wqssqnjdW_a`__`eeghlogcg{¥¥£JEKJIHFHGGGGGGGIHFLn¯¸¼¿¾¾¾¾¾¾¾¾¾¾¾½»´¬¦¡ {yzyyzzyyz{qjggnqpsvxz|z{{}{~ ytnno}IIIIHHGEDDCBB@?><;98866543000//-.--,,+*)))**))((((((''))))))))))((''''&&%%%%%%%%$$$$$$$$$$%%%%$$%%&&%&'())**))*,++,,....///011223344446456666666666666664455545564444433422222221100//...-,,++*)))))''&&''(('&%%&')+16:976>?BHLMJIC?<PbeZRRUX\_inR1)))))SsbZ`j[ "$$&***.024545775=ikhhkklqgdeghhcdeih^G@B332227FD@732121111466:@C<;6..//1234569;<=ACDEFFGEGHILNNMMKIHHGGILNPROLJIGEDC@@@BDDDDEIS`ktxz{{|vqh^`jw}on{|{{{|{skjjhebcn~ ztf^MCDVmswz|~}~tppmmcX\___`abdfgeckgdiu ¤ G?GIKIFFFFFFFHEGFFZ¨·¼¾¾¾¾¾¾¾¾¾¾¾¾¾½¹²¬§¤ zyyxxxxzzz{qgehhmoou{|}}{{{z~}yrnmnGGGGEEEDCCA@@?=<:987755443000//----,,+*)))**))(((((((())))))))))((''''&&%%%%%%%%$$$$$$$$$$%%%%$$%%&&%&'())**))*,+,,-....//011122334444566666666666666644445554334544443334222222100//..---,++*))))((''&&&&''&&%%&'(*/7<=;8<BHJLLJIED?=GXje[XXXY[bhfR*"()(Ztd]blL&$&**+-0378:;987fjgikjophcfggghfgilWA?B7353337CKE731///0124699799951.././025789;<>ABCEEEEEDGGHHIIHHFFGGGJOQROLJHFEDC@@@@CEEEEGHP[dmty{wqfb^_ky {oo{|{{{{}}ypoljiffq }ztgZTKHPctvvy{}~wponj[_`a``cacefgc^efhnqF@FGHGHEEEEEFEEDFOt²º½¾¾¾¾¾¾¾¾¾¾¾¾¾½¹²¬§£ {yyyyyyyywy}rgeggjnnsvyz|{{{z |xpmlsEEEEDDBA@@A@><;::876745422100/...-,,,,+*******))))))))******))))((''((&&&&%%&&%%%%$$%%%%%%%%%%%%&&''''()))*****,,,+,././/111111233443444555555556655554345555334443333332333111111//....-,++**)()(('''&%'%&&&&%%%%&*19?BB==FKKLJJHEB??A;I`jdYYYWY]`kgA&&&(bvdbdj:!(+*,--145688?jkgiigltkdfghhiill_H:BE>3153016A[R520/..01348<:966642-,---/04798;==?AABBCCDCCCCCCCDEDEFFGGJKNQLIGFEECA@?@CDGGFFIGMV_gosqgSX_bn{|qp{~|{yxyy}}tonnkilv ~~ysha[WQQZhsuwxz}wpljb]`abbbacdfgd^Ydfemrzx@BFEDHGGECDFDDCEJd¬¸º½¾¾¾¾¾¾¾¾¾¾¾¾½¼¶²¬§¢ }{{zzyxxwvw}sddgefkloswx{{{{z{yutw~BBBBBBAA@@@?<;::9765554322210/...-**+++*******))))))))******))))))((((&&&&%%&&%%%%$$%%%%%%%%%%%%&&''''))))*****,,,,,//.//111111134443455555555556655554445554334443333222233211110/..-.-,,+**)((((('&&%%%$%%%%%%&'(+/:BGHC=FNOPNJJEB?=<:69Mhi]XVTUY]ciV2!$,`vccbg1"*,0321378<8=iqjhjhiolfgjjgjnn[B9:?DI;3442016?PI30////276769<<9853/++,,--/159:;==>@@@@>?A@??=>>@?AACDDDDEHJJIHFDEECA@?@CDEFGFHGGJOX`fcVIO^en| |pqz}~}yyzzz|wtqonlny~~~{xriea[WW]isuwwz} xpgc``abccccdfffa]]hhhkruu<BEFIGFGDCDECCBEW¥´»½½¾¾¾¾¾¾¾¾¾¾¾¾¾º¶²®©¢~}{~ ~{{zz{zxwvw}wgefgejlopsuy{{}| @@AA@@??=>><::998744343322110/..++,+,++)))******++****++++++******('(('&&&&&&&&&&&%%%&&&%%%%%%&&&&&&''')******++,---.///0011202224445566666655555555555545543333333333222122111000..----,+**)))((('&&%%%&%$$$$$%$&&*.8ELPIDGRTVRMIDA><<992.8VebWUSVX\^ggK+'craeh]'*,27=CKLI@IjsmjmiipnfhiikngZL;9:=?@;642200467;71/11/037:97:@A=:53/++,----/2669;;<?=;;:;=><;;=>>??@ACCBADFFGGFDCBB@?@?@BCCDDEEEFEGJQUQJEGWfs~ympw{}{zyyyz|~wqqoor} z{z~ ~|}}}|uqmida`cbisvww{|ypebbcdccccccfdcc]^ijjmrstr>AEDECCEDDDEDBDKm®¶º½¾¾¾¾¾¾¾¾¾¾¾¾¾¾º¸µ®« ~zy~~{zzzyxxwvu|zhehgdejnnqtvy|}| @@@@????<<;:99887666533322110/..,+++++*)))******++****++++++****))*((('&&&&&&&&&&&&&&&&&%%%%&&'''''''()*))++++,,,--..//0001111222344556666665555555555553454333333333322122211100/.--,-,++*))(((('&&&%$$&&$$%&$#$$&)-8EOTRMKSXVSOKGC?;:732/.,=XgaYTSVX[`gbB&$^oaeeV:85=@ILQXcrrlmljkppjiklojSEFGHCABA>84420//36763.-.11149=;7;BD@=71/+,,----/14678:;=:889<<<;;;:::===>?ABCCDDEDCA@@@AA??@@AABBBBDCDDCDEFDEEShu tmqxxzzzyyyz{|xurqqw {zy{~~ywxzyzwxpnlmmlhmtwwx{} unjfbfeddcccccdb`_floljpxyoCBGDEDACDDDEDBCW ¦³·¼¾¾¾¾¾¾¾¾¾¾¾¾¾¾½½¹¸²« ~vuyyyzzyxwwuvyzgeedefiklnrvy}}|????>>==>;;;9788767643321100.-..++++*+++********++****,,,,,,,,+**,+(**((&&''''''''''''((''&%&'''(()))))***+,,,....../0/00010022223445555666655665566554444443333332122221101000/..--,,+++*))((('''&&&%$$$&$#####$#$&+6ALVZVQSXYXTNKHD?<8310./,,Dah^VTTTX_chcE,^i]chP+7<ET]djosjjlmmppllmmgP<BMQOHILIA:5432.-055540/,+253389989@DCB<51---,,.//134468898777;::98989::9;=<=@ABDABBB@?@@>>????@@AAAAABBBCCCBBBETiv{oiovvwxxxyzuuvyvtrx{xxx| xpptwuwvusrsutqlktwxx{ }uspkfcefffdccbc`[]cgkqnoru{pFCCEEEBABCDEFCJj¬µ»¼¾¿¾¾¾¾¾¾¾¾¾¾¾¾¾¾½»¹³¨£¢ ¡vuwxyzzywwwvxz}heecfdghknrvy}{} <<<<<<;;<9888755656532211100.---++++************++***+,,,,,,,,,,+**)((((''''''''''''''((''''''''(()))))*+++,,,.../../0000010022223445555666655665566554444443333332122111100000/.--,,++***)(('''''&%%%$$$$$$#####$$%)0:FQYXQQXYXXSOLHD?;752/.-/*1HeiYSSVXY\dibVjqcdbJ8K_eggntlhjlmppkml]=-5I[]TOMEGG=74230/,0:=621.--03668::89>AEB@93/--,,-.0222455567778989:988888889;;=?@ABBBB?>??>>>>>>?@@AAA???@AAB@BAJ[ky{{|}yrkipttvwwwyxrglzxvsy|yyy| xoimmopsvvuuwvutpmrwz{ ytpnijgffecccca]Z]cgmuuuomppLDDDEDACCCDDEFV£²¸»½¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¼»¸¶µ±ª¦£zuwxyzzywwwwwz}hedcdeghimqtz|{} <<;;;;::998876776655211100..-,++**************++**++--,,------,,,+****))((('''((((((((((((''(((())))))*+,,+,----..////00011102222344445556665555555555444444333311110000000////.--,-,++***))(('''&&%$#$$##"#""""###$&-6CMSTPNUZ[[YTMIGB?<732/+,,,+3Ui_ZWWWYYZbjqutponligeblrjhgjnqrnj\>")3G]_ZRMB8EE;5220/./7HL<31//..3789;867<BFCA<71/-,,-/1324435555666668777799887788;=>?@@???>?>=<====>?@AAA@@>>>@@?BIWcrzwvzyqj^blqsuuvwwwqcWn}yx{}zz{| |sb_dghlnpsuxxvtstttuz~ {wxusoigfedcbb_^Z^fmtwvuplivpQGFFFDCBCDEEAJdª´¹½½½¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾»°«¦zuwyzyyywwwvwz}lccdddfhjmrs{z|~ ::::;;::99776666554431110/..-,++,,************++**++----......,,++++++)))((((())))))))))))))(((())))))*+,,,,------////000111022223444455566655555555555444443333111100000////....-,+++***))(('''&&%%$$####""!!""""!#$)3>GLIFIOY\^]XQNLGC?:74/+*)+(*+<Ye_UZUVWZaefjjjgefb`jspihkntwo\8'-6/Ifg`]WO76JC851///..2:9421.0111567;857=BEEC=82.+,,-/024543444445554566779988777788;<==??>===<<====>?AAAA??>?>>?AIT_mwvtvupeXOUafkoqtuywn[;2m}{~}zz|xiYY]`dfglrvwwvvuutwz| ~zyzuromgddcb__^]ajsywvupmko~YDGFEBBBCDEEANs¬¶¹¼½¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾½¾¾¾¾½µ¬ xnpsuwwyyyywwwvwz}kccbddfhjmqtz||~ {99:::9::8888666644442011/..--,++****)))***+++,,+++,,--........-,+,+*++))**)))))()'((((((**))))(((()*++++,,--..--..//0000001101222233334555666655554455333444222211110.//00//--...-,++**)))((''&&&%%$#"##""""!!!!!!""#'09CEA?BMWZ__^WUPKGC?90('('&&&'#*C[_YWSRUWY\`bccdcclwqiglptyh?!#((9Neidcc[L42A9420//.,-/1/011/15412579888=EGFD@;50,++../1344433333344456677667755557788:;>>=<<<<<<==<?@@@AA@@><==AES_isvrrqldUB>FPX\bgmstthYVLf~~}q_QSUZ]`fjrttuuwvvyyz}~||zuupkhfda_][\eipsussnjigr|cGFFFDBCCDCDBR~ ¶º½½¾¿¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾½½¾¾¾¾½µ¥scdeinsxyzzyxwvwwy~ncbbdfegjlou{|} yumd99::99998888655532222000...--,++))++**)*+++++,,+,,,,---........,--,+,,+*++***)))*(**))))**))**))))*+++++,,--..--..//0000001112332222333455444455554444333444222211110.//....----++++**)))((''&&&%%$$##""!!!!!!!!!!""#'-8BGD>BHPW]`_^\ZTNKE?.'&&%((('&$$(@]bTOTWWZ^_acedluspkorvxe;%&(&'=]iiec_UI,+86310/.-*+,./00002672236::89>BDEEA<61,++...0133333222244444456466655555677:;<==9::;;;<=<?@?@??C@>==?GRZgrtqtrkcU>029?DIQZ`fmnh_ehr zmVMOPTW]dkpqsttvwxyz|}}~}|yttnjgc`^^\ajpppqqqmihfiroPLJJEBBBBABEU £°·»½½¾¿¾¾¾¾¾¾¾¾¾¾¾¾¾¾½¼¼¼¾½¼¹©hY_dehhkqxyyyywvvvy~sfccgiijlnqv|}~ ~ztldYQP8899997788875555322220/....--,++++++***++++++,----............--..-+,,,++++*))****))****+)***+*(*++,++++,,--..--..//0000/1111222223322333345445555334433453222220011/././....-,,,++**))((((&&&%%&%$##"""!!!!!!!!! !"#&+4AFFDGIOUZ_bca_\WSM>.))%$$$&(*((($*G]]UY]`acchhmsplkmqswkN4,+&'*C^ibWRYSJ-(76000/--*,0/11121389757:<><;;@CEEB?;50,+,,-.0112332200223344444444554234679;<;978888:;<>==??BBBA?<>CP[fqussng_R@2//0369@FOW_b`[^V]t ylVJHJLR[gnnoprtwyxx{}~~~~{xvrnjd`^[]ehjmpqonligddfkWTPICBABBBAEX¤°¶º¼¼¾¾¾¾¾¾¾¾¾¾¾¾¾¾½¼»ºº½½»· \PW]`dghijrwxxxvvwx|yqprssssttw}~wpg`XNGDHN8888887788755555322220/....--,++++++***++++++,----......//////....-.,,,,,+++**++++**++**+,***+++*++,++++,,--..--..//000000111222223322333333443333554433330002220000../....---,+++**))(')&'&&%%%$$##""!! !! !#&+4?HJJKNPSZ_eeffb^XM4)+(&$%'&&+-..+&%*:KWgljkmnkurmmosuwlSI52<=12LcdZSLMMG0%65/00.---/201212369=;988;<==>@?@B@?=92.,,,-.0112332210113344443333444233568:;989888879:;;;<=@ABA?=@NYcopqsoh\N<.-,-/00047:CINPWYRZi ~o[HDEHQcikkmprxywwy{{{|}}~~}{yurnje[PT\efgnonnligddb`XRPFDBBAAABDX¥±·¹¼¾½¾¾¾¾¾¾¾¾¾¾¾¾¾¼º¸·»»½¹±\EPY\^bfgihltxyyzz| ~~~~ysg_VNEEFJLPS88777777765555553222200///.--,,,,,++++*,,,,,,,..--//....00////......--..,,,,+++*++++****,,++++,,**,,,,+-,,--..--..//000000011112222222343333333333333333320.0111000/......--,,+++**))(&&''&&%%$$$##""!!! !! !! !"%+3=EILOQQT[`fkjfdc[H1,/,()))')*,,-/.01128AQX`cfrroortuteQPO;;HOJO[`XURKKOF1$3500/-.-16855324469?>=;;<@@BA><?B@@=;4/----/1122222222222244321122332121156775677777888899::<=?@=@HWanqqrog\P>0.-,--//011248<BHIKPXj~|y|~s\HBCJ[cffhmryywwyzzzzz{|~~~{zwusole_ahd_dmoonlhfbaa_[NNHFCBBBB>DY¦°·¹»½½¾¾¾¾¾¾¾¾¾¾¾¾¼»¹¶µ·º»¹FEOV\^`ddgiiow{{| }xuqncVLFBA@CHMMPT77777768765555553222200///.---,,,,++*)*,++,,--....--..//00////..----....----,+++,,**++++,,++++,,,,,,,,+--,--..--..//00000001110122222233333333332233333333210011//......----++++))))(''&'&&%%$$$##""!! !! !! !#(.6>EIKPSTW_djnliebV>.-,***--(**,,,**-.0110..((drllkhim`ILNPDCNRQY^ZY][UUTR<'/6830.--17=<:63335;@AACA?@BBA<:<>==<:51.---/11112222222222331111113321//112443345555667799::<=>?>FP]krnolf\N=0.,,,,-////00101359;>AK`f`dhkpqsvz} vcG?HWcfdekpzyvwyz{yxxxz{|}~}||zvsrooplknkkoooljhfbab`\KLOGCBBBBABV£®¶¹¼½¾¾¾¾¾¾¾¾¾¾¾¾¾¼º¸³¯µ»»·§pHLQY\^bdgjjjms{ uld`ZOE@<=>A@CHLNOR77777777665544333222201000/...-,------............11110000000000--..-.0...--..----,,,,,,,,--,,,,,,,,--,.....-...000000000.11110022221111345433222222221111110.00--------,,+,++****))((((&%&%%%$$##! !! "$*08@EHKPSVZbfkonie^O0*)))**-1,,,,--*%#$',0016.Zvjjkolh\UJIJIEKROX]a`aaZX_^ZK/)4A@5.-0:ACB<64469;?CDEHDBABC<78999:8840--,.00111122112222221000002211//01001122444466668989;===@JVdnlnje[M;3/,+,,--..//00//00221236@EEIMSU[achotyz yeIGVdeacisyyvvyyzywtqvwz|}}}~}}{yvsrnnoonmlmnjggebaab]OKSPDBDCBCDRw®¶º¼½¼½½½¾¾¾¾¾¾¿¿¼»»µ«´¸·®o_cdddgjjkmnnry} vkaUME>98:;<>@@DFILLL77777777665544333222201000/.....------....,,...../11110000000000/...-.0.......----,,,,,,,,--,,,,,,------....-...000000000.//00//11111111233133222222221111111/00....----,,+,+**)**))((((''%%$$$###!!!! !$',4=CFGHKQUZafhlkieZB-(*++**((++++,,,'#"%%),2;Zulikjke[MLEGE95JPQW`_[__ZZ[_VS:+/DM9//3@DEE@99:88<>?BDHID??::7555589983.-,.00111122112200000000000011///1001111233444566879899:ANYhmmje\N>520.+,,--..//00//00221220/04468<@GHNV\diouy}|hSX_dbdowxwvvyywvpnnotwz{}}~~~}|zyvtrqqpnnmljfdbaa`a`VMMPHCCCCBDKo¬¶¹¼½¼¼¼¼½½½½¼¼»»¸·±¦¦«¯®¤xzwwvvwwwvyy}} }xngZOE>;:7879<=?@ADFGJJJ77777766665544333222310/00/...----..-------/..//0000000000111100000000//00////..------,,,,,,----....../-..--..//00//////////00//00001122211212222211100000000000//..----,+++**)))))('''''&%$$#""!!""!! !#)/6>EFFFINUY`ehjiigW7.-,-)**))((((*++)(''%&*(IpjjfgecPAKMIDB@@FNXbd\Zb_WQR\YOD:36=2./08?EFB;>@:889:;?HKF?:75654467;;852/--//0011112222////0000////00//./0011112222123466777759CP^hjhf]P?6421.,,,,,./....//00110222000/0//0026<CIOU\bjnrvx{}~qeabfnvwxvuuutrmhggjmswyz|~~}~~|ywuvstrppomifdda___a[KIJLIECD@AGm©²¹ºº¼¼ºº¹¸µ³±°®«¨¦£¡ ¤£ {vqiaZMC=<===<:989<???CDDDDDD77777766666554333222310/000///..--..-------/00//0000000000110000000000//00////..------,,,,,,,,--....../.......////////////0000//0000112221021122111110000000////....----+++**))))))(''''&&$$##""!!""!! !&)/6>DGGHGKPX]`ddeebI-++)+'))))(((((***))'&&'>hfgfgdcN@DMML><JNIW_c__`d`TTZ]aOGB;48879114<CCBB?:99778;FLME=74435567:<><830-//00001122220///////////..//./0011111101111233444448DQ^ggd_UC54410/-,,,,--,,..../011022211100.-..,../37<@GMSY^dhlpsx sghqy|{xwwvqlgd\TV`gmrwxz}~}~}|}{yxvutsrqomjfba___`]PHJIHDACCGQj£°±±±±¬ª©§¤¤¡ }ypjcZOGB;9;;;<==<<:9:=?A@CDDCCAA887766776666553332222210110/////------........////000000111100000000//////////..------,,----,+--......./////................////00001122221121110000//000///..--....---,++*****)))('''(&%$$#$##"""!!!! #%)/6?FHHHHKOTY[^_b`W9'*)'((((()(((()+++*(),$Cl`bbdceP;B<:KG>=EDSda]]ab_c^ab`bTHDHVJ>BG9,05;?BGB:766336AJMGB:53355679>?A>:51..//0011235510////00..////////0000000000000011222238CQ^cb`XJ;4430///-,,,,,,,--//0111111121000/.-----..--.13;?DHLRW^dipuwwrlmsxwwyvspja]XMHQZ]dmrrw}~~~~~}~}|{ywvutusplhfa_``__[NLJEEHPV^jv ¤¦¦¦¤£ ~|zvrnia\ULF>;:9:89;<<==<<;:<>>BBABC@@@?777777776666553332222210000/////------......//////000000111100000000000000////..------,,--------.......///////..............////00001111221111110000//000///..--..--,,,*+*,+**))))''''&%$$####""""!! !#%)/4<BGHIIIPUVWZYZWF-''''((((((((((++,,,)&&=md[[afhS>@;76@F=556Pcccddda_`debcXLDGJA;@A:1.028ADE?634012:BEFB;7445579;=?AA>:51/0011112344420/////...////////////0////00////////16AMZa]ZRE9121////..,,,,,,--//0111111121120/..-,-----./../0036:>DIMSV[^^^afjklljgd]UQMMOIEQ^fjmpu{||||}}{yyvvttqmjhecaa```TLNQX_ks| }wmfdaYQKHA<;;;;;:;<<;<<==<<:;<=??BBBB@@?@776678776655442211223310////////--....//..////..00000011111111111111111100////....--------------....////////................/////0//0000221111000000//00/....-..--,,++++++**)(*(''''&&%$###"#""!!!! !"%()-3;ADFHJLORSUUSM?4)''''))(((())))++-++)*0ll`_adfV8AD<239@DBKEI_baffbc_cddgeguK@>?=>@A90/.7EED@821.-/29@BB?;6543368:<?BB?:62011001234452200//..../......--..////////......../3>IW[VSLA70///...--,,,,,-.../1000001111100/.-.-,---..0..//0/00/1568<>ACEHMPTVVXUSNECCFKF?DR[aflov}||}||}|{yxwwsqomjgebcacb\_kq{ wl_UJE@?A?B@:<<;<;<<<<<<=<<=:9<<==?BCCB@BD776678776655443322223100//00////......////////..00000011111111111111111100////....--------------....////////................/////0//0000111111000000//00/....-,,,,+++++++***((*'''''&%%$#"""""""!!! "#&),04<ADFGJLNPRRSOG7-'''('))(((())))++*,+(,ekhhfcgW7AHG8-/7:=>D?Peegjecddb_aebfdGBBDEEEE>3/.279AE>3/.-./29>A?976645578;>@@@>:611100123444422210/....--....----.................1<FSVRNI@6-,,,-..--,,,,,-.../10////0011100/.-..--.//.0///000////./0../10468=>?BAA>;;;>BB>?ENSZbenu}~}}|}~|{yxyyvtqonkjgilrvx|~ zri^TJC?@@?BA=<<;<<<<;=<<<;;;<;<<==@CDDEEFF66776866775554332222210/..///////0////....////////0000111111111111111111//.////...--,,-------------.00//////..........------....///////////000000000/////....-,,,,++++++**)())((''&&&%%%$##""!""!!!! !$&+.27?BHIIJLNOPQQMH:,'&&&'()((((**))*))*,-]legdbfV,<FRQJ=337;55:Rfqnme^abc_]af[ROKKJHJIHB80/125@GD:1-----16<;;755544568==>?@=97321011244554444410...--...-------..//..--..----,09BMUPLF?5-,,,,,,,,,,++,-...///00000011110///......////0000000000........./0/001111367:;;==AGKPW_hry~}|}~}|{{{zvvtsqonotz }~{tmia\PB@@?B@><<<==;;;;;;<;<:<=???@BEFFGGHG77776866776554332222210/////////-/////....////////0000111111111111111110///./.....--,,-------------.00//////..........------....///////////000000000/////....-,,,,++++++**('))((''&&%%%$$##""!""!!!! !"#&(,039AFIIIJLNOPPNID4('&&&'((((')))*(((()-[ndge`h[-2>GTYXQF8:<98?^ltrlhje^`aaff\SROLNONLLH?3/237=CF?4/---,-/477765554679<=>@A?<9621011234555577522///.-...-------......--..-----.7AJROJF?5-******++++++,-...///00000011110000////..////0000000000..--........../////13366778;<?ENYcnw}~~}|zy{{zywuvwrrz~ |xsoh`QB=<><;;<<<<;;;;;;;=>??@BBBCEGGGHHHG88777766665544322222211000///000//////..////....////0001111111110/./1100///.--.,--,,,,----,,,,,,....////////........--...--.........///.//000000000000///....---,,++,,+**)''''(('&&%%%$$#""!"!!! "" #%(),15<CIJIHIJKNMLLE?.'&%&'((('')()(((()*(Qmcedac]./7=FPWXYM729:?<\trrpcYa^[`fhe]WSQQRQPNLJC933/18=CA81-**+*,.135445678879<>@A>=:7520/1213455689643100.-...,,,,,++,,-----,,,----,,5@FQNIE@7.(())))****,,--..--..////0000111000//////////00000000000/...-..--.../////0001222344556;IWcmx}~zyxz{{{{||uolhffkmpqs{thnz |z{{} }wsh]M@?<;::;;<<;;:;;;>@@AABCDFFFHGGGFFF6677996666554432222221100////010//////..//////....//0000111111110/./00//.-..---,--,,,-----,,,,,,....////..///.....------.--.............//000000000000///....---,,+++++*)'''''&&&&&%%$$$#""!"!!! !! !#%(++.37>EJJHEDFGIKJGC;+&&&''())'')('((()+%Oibde`ea0,38=GNUYXP4/9AEHlttto`X`[Z_chg`YVTTQPPQPLGA:53127=@:2,***)**-012357888779<>??>=:752112133467876542000/..-,,,,,++++,,,,-,,,++,,--1<ENMHE@94*())))**))+++,++--..////00001111110/////////00000000000/....--..///000//00011223443333:GVblv}}~~{vsvz~ wpic[UPMHHMQ`]_dnsqlgs £¢¡¡¡¡}ytrssx{~ ~~~~yqj]L@===<;;:<<<;<>>ACCDDEFGGGGGFFEEEE7777666655554432222210000000//0///////-///////...../0000111111000///.......-------,,,,,,+,,,,,,,--..////00////....,.....----..........,-..//////0000/////....---,,++++*(((''''%%&&%%$$$##""!!!!! %'*+,049@FIIHEBBFGGFE?5)&&&''((((''(()****Tm`bea`c1*566=DIPWXT>==><KlpstUV`cabeeegc]YXWSPPQQMLF>81/138<<3,+*)))*,/13589:9:7778:>@AA@=:642012334567535422110..---,,++++++,,++,,++**)).8>FKGDA:5.''(''&((())))()++--./../0010001221111//////////00//11//..--.......///1/00/1112344442249CS`ksy}|wqotx} ~woh`XSNLFCDDDEM]XROTZ_gn |toiddeimsw|~}~~~}~~~zrgZF><;;<<><>>?ABCEFGGFFFFFFFFEEECCD6677776655554432220000000000///.//////-///0000......///001////00////.....---,,,++++++++++,,,,,,,--..////00////.....-....----........,,-...//////0000/////....---,,++**)(((''''&%&%%%$$###""!!!!! "$',-/39<@FIIGB??BEFEA=/(&&&&&''''''())*+._kaab_dd5&29;>>BJOPPI>;<ACTmqrne[Ycaekmkhe^][YVRPSROMJE=30/24993,+*))))+.01489:;<97779=@BBA=::6520013345655555432110/////--,+++,,,,,,++**)),2:@HHC@<6/('''''''())))()**++,--.../100001111110/////////00//00//..---------.112311111123555522247CQ^ipz}|zvpoquy}}}}~{ri\TLIIKLONLJHHIKQQOKIKT_o{sme`\YVVX[beipsv{~~~|{{|}}||~~|wodWF===@AA?@@ACFGFFGGGGFFFFFFEEEFCC6666665544554432210000////000010//////.---............//////////..--------,,,+++++++**,,++,,,,,,--....////////....-,-...----........---/...///////////../...-,,,,,++)))(''''''&&%%%$$####""!! !! "$%),.27;@CHHGFA>>BCECB:,'%%&&''((()')(*)3el^`_]`j?"/4:<@B@CHJJ?8;A@<Zqqsomi]eghlppida^^\WSPQPONKGC:3005473.+*))((*,-045789:86777:=ABB><;:752111224567797553442213330/.,,,----++**+)))*-6<DJA?:51*'&'''%%''&%&''(****+++./..001111221110//....////00..00-----------04455322012456766333347?NZeov|~{zxunkloux{||zzyxtsqnmlg^VNJJIIKMLNNJGGHJLKHFGKNZm{}|{}yzvqjb[UMGDBEGOUY^_deilqtyxy}~}|{{{zz{{|}||{|}|tkbUI@AABCCDEEFFFGGGGFGHHGGFFFFFFGG6666665544555422210000////000010//////.---............/////////...------,,,,+++*****++,,++,,,,,,--..//////////...-----------........---....//////////////...-,,,,,++)))(''''''&&%%$$$##"#""!! !#%(+/38<BEGIHFCA>>ABDB?7)'%%&&''((((**+':ki^__]boG -238:DGKFEHG=>Qd[DOjlmgjiegkmpqnief`\ZWTQOMLLKGF>7114363.-+))(()*+-13379:766677:=>>><<;975211123435579:76753333330/....--,,++*****)(+19@FC>:53.(''''%%%%%&%%%&')))*)*,,-..011112222210/....////..//..----------.156884320235677883333246?JYcmux~}{yvrmjkorv{||zxwusqniida\XRPNKKKKJIFCCDFECCB@ADOZ[YYYYYVQNFA=98779?EJRV\___aceimpqvz}~|{zzzz{{{|||{{|~yskaWKCDEEEDDEEEEEEFFEFFFFFFFEEGFGE6654556644333322102211000///////000.......-.//..----/.........----,,------,,+*******))*+,,,,----+,--..//00..........--,-----------..............///...///.------++++)))(''&&&&&&$$%%$#$$#"!! #$(+-18<DGJLLJGB?ABCEFB=2&('&&&''''(')))Gog`[^abjU!.:<==;EGXOLJHB=EOWWXdjhgiiabiqrojchhd_]ZVTQLIKIFDA:614675/,,++))))*,-015988855569:;<>===;97543002222568997995442101100///.++-*****))()-5;@C>;73/,)%$&&%%%$%%%%%&''''()**,---./012322220000.0///.--..--,,,,,,**--/1468534422345896666521134=IV`irv}~zyvrljfkotx|{}{zzwusqmie_[XTQMLJGEB@?=@>><<;;==?????><<9:::89::9:<BHMSX[^`^]_^behlpux}~|yyyxxzz{y{{|{||~|ytldZMFFDFFEECCCCCEEEEDEEFGFHHGFHF4433332233323311211100///.......//......-.--..--,,,,,---/-----,,,,,,----++,,+*))))***)*+,,,,,,,,+,--..////..........--,-----------------......//....//....------+*++)))(''&&&&&&%%%%$#$$""!! "%*,.29AFKPOLJGDBABEFIF<,('&&&&((')%(')Vlc_\bhglS2JLLNSUJGJNKIA<D@BGQ^aeghji_gjnplbejgbac^WTPLHGFEDB=834675/++++****))++0269995534457:=>>>=;97532111333335888876421/0000000/.-,,**))))())-6;@>:641.*&$&&$$%$%%%%%&''''''))++,,../02222221111////..------,,--,+,,,-/11344123434456866666411344<FR[eov{}}ywtrkdgjlquy{{||{yxwtrokgc`\WVTQNJFA<:88887677::::9;;;<=>><9;;99;?EJOS[^`_`_\ZY[`ejqvz{{yyxxzz|{y{{||||}~}ytmc[NEFFEDCDCBBBCCCCDEFGGIIHHFGF54224322322211110000////////......//..----++,--,+-,,,,,,-+***+++,,++,,,,++++++**)))))))*++++++++,,,,..----..........---------------,------....//--...0--------,,+*****('''&&&&&&&&%$####"""" !"%()-/4;AHMPPLIFEB@BBDJG8**'$$&('''&'$7fmc]_chhlZ&+CKJNMOTSKING@9NaVZWR]dilkkqnlnpob_eggebb_WSPLHCBACB?:30254.,,++++,,+**+-/3578652113688;<=>>>>;955312331024425775422001123431/--***))))(((18;=:6640.+)%%%%%%%%&&&&&&&&''(()***,,-.01000011111/00////.-,,,+**+*+++-.0111112333223455566766644559ANYbkqx~}xurniefgjmqvy{{{zzzxxvtpligfca]XSMIFDA>::6777677899<<==<<=<:9889<@FJPUZ]```][USTZ]dhpv{{zzxyyy{{{|}|}{{|}}|xtkb[OEDEDCCBAAACCCDEFFHHJJIHFFD33222222221110010000//......----....--,,,,++*,,+,++++++++++++,,,++**++++********)))))))*++,,++++,,,,..--,-..........----------------..----......--....--------,,,+****('''&&&&&&&&%$$$$#"" "#&)),.28>DGJKKIFEBCBCBFB0(+'&'&'''(*%@pmbb`ffhoL#*0EQJGF@BLQFKJ?<FT[dWVcinonlopklpeW_jjhica_XRRMIDA>>=<93/011,++++++,,+,+,,/.3565531/057789;<>@@?=:76334300011114445332222234320--,**)))(&&'*38:<865430,*(&%%%%%%%%%&&&&&&''()**,,-//0000011111000////.-,,++++**+++,-...../01221233355666555334437?KT]emty~|vrpnlgbegjoux{{zzzzyywtrnlkkihd`[XUPMHFA;877666777::;;;;::887779<BGLQW[]__][WQMMTY`gptwzzwwxyyzzy{|||{zy{}}xribYNEDCCCABAACCDEFFJKJJJIGFED1132222211111/.//........-------,.,,++*,+++,+++*****++**++++**,,*)****++****(******+*)****++,,,,,,++------......--..--------------,,------.......-..-,--------,,++++))((''&&&&&&&&%$$$###""" #$'*,-.26;@BFIJIGGDAAA@?8-*,('%''&(''<pkddegjlh=+/3?ZLCFG<PRNKIGFLQ_f`X_gmoonpqopof_ejhgiffa]XRNGB=;:;:74110-,++**)*,+--,*,./01345420037589::<>A@?<:8697531//.//122444433355431/.-,,*)(*)'%''-2499856720-+'%%%%$$$$$%%%%&$%%'(**,,........---/0000..0..-,,++++++**++,,,,----01111233444433332232126:BNWbjqx{zurmmkfbbekqtvzxyzyxxvttolmmmljiggb^ZVQLE?986656777789::998866569>BFMSX\^^^]YUNIHJS[ckqwz{xuvvwxwwyz{}}z{{{zvpi`XPFDCB@@ABCDDDGHHJLKJHGECA1110111100/.//-..-------,+++,,,,-,++++*+)*++***)))******++**))++))**))))))))()((''(**)****++,,,,++++,,------------..--------------,,--,,,,--,,,,.,-.-,------,,,,++++))((''''&&&&&&&%%%$$#""" !$%)*,-.16;?CEGJJIHEBBA@9.*++('()(((*>kmfcfkmog5&.10=XXKNKJLMRSNDFFPQhieafltolknnknl`bdffefghf_ZVRJB=;:866:;83/-,*(()**,-./...../124642223568999<??>=><:;9753221/0132334433344321/.-,,+*)))('&&),15::997661/,'$%$$$$##$$$$$$&&'(**,,--......---/.....///.---,,++++**++,,,,--..////0112222211112210/0235>JV^gotyyvrpmlhcaejlptwxyyxvsqomjiijjkjmmkie`ZSKD<87666667777776666555768>DINTZ\\^^]UQJFBGOXaiotxxxvttuvvuuy{{zxyyzxvqibYPECABBCDDEGHIJIKKJJHFCAB000000//0/////0.----+,,,,*,,,,,,,+**))*)())))))*))*)****++**))))(('(((('(()))((((((())********++++++,,,,,,,,,,------..--,,------,,++++**+++++++++++-----,,+*,,+,,,****(('''''%''&&&&%%$#$#"" "%(),+.147>BEFHIKJIHDDC?0*)*(&(*&)(*)dpgeejjoW*(0/4DV\LKHILKBIRKE?IY_YX[`jpskjjelnlg_aegc_afhe_]YUNGB=:879=D?51-+)((((+,--//..--.//3345521136779;<<<????>><9866533334444542300000.-..-,+*)))('&'(,27::;;9841.*$#####""""##$%$&()*,,,,-....--,,,---.././/....--,,,,++,,,,,,,,----..//01000000//11/.////15;FO\ckry{wtpmiheaagjoruwwtqqnkihfdddhjijljgd_YQH>987665566775544445555569?ELQWY[^_]ZSMIC@IPX`gntyywurrstrrruy{yyyz|xvpjd\SJAACDFEHJJJJIIKJIFFCA?////....//.././---,,+,,,,*,,++))(*))(()('((('())***)))))**))))))(('((((&''(('&'''''())**********++++,,,,,,,,,,--------,,,,------,,++++**+++++++++++++,,+,,+*,,++++****((''''&&''&&&&&&$#$#"" "&)*,.148>CEFIJKKJJIFFF9++**)'&&'((#TojijnpoO,43.-4CJNOQMSSSOLKDBGG[^UK\biqommjgmomifdba``_cfb^ZYVPKD?<<=<<B=53/)((((()+,//./.-----/2466321135689;;=??@@@@??=<;:998888755410....,.-..-,+*)))(''&'(-147:;97630,($####""""###$%'&'(*,,,-....--,,,-----////....--,,,,--,,,,,,,,----..///00000//..00....../128DNX`hpuzywoljgeb`agnqssuqomiedb`[\^`cfhihd_YPG>988665566775544445544568>CJNTZ\]_][WPJEDDJQZbhpswxvtrpqqqrruz{zyyz{zwsog_XMFECGHIJJJJIIIIIFDA=<....--...--,-.--,,))****+((((((('''''''''''(((''''(((())**(((())(((('&&&&&&&&&&&''&'))****++))))))+++++++,,*++,-,,,,+*++*,,+,,,,****++++**++++++******+*+,++++++******))))('&'((''%%&&%%$$!! $(+,/137;AGLMNLJKJLKJJC.&)'%'''(('&Mqhciopi?"300/14=GRZ^XVXYUMIB6<HbcZYhqmrrqmnmiijkgda_b`_^]\USUSQMIC@@C?8:9561(&''((())*./00.,+++,,-03101/025788:;=???@@AAB>@@@?>>=;;8542/-,---,-.,,+**)))('&&&%&+/24688441.,'"#######$$$$%&'(++,,--..,,++,,--......////..,,,,------..--------..//000011//..//..--....017?IS\emswwvpjhhd_^^flnpppnlgba^[WTSSX[`ea_ZWME=986665566775566556666669<AGMSWZ\_]\YSNHFEHMT]ekpuwxxvspmnopqsx{zyyy{zwtqjc[UMIHIHIJIGIGHGFC@=;9....-----,,--,,,,+**))((('''((((''''''''(('(''''''(((())**))(((()(((&&&&&&&&&&&&''&'(())))**(())))+++++++,+**+,-,,+,**+,,+**,,,,****++++***+++********+**,++++++******))))('''((''%%&&%%$$"" "%),/147;=BHNRQOMLKKMLH7*&&('&'&&($Ntiejjle2#120.306EIOWYVVVWVMD;08F^RU[akjruqmprnjiggdb`]^^YUTNMPRNLMF??A<763551+'''(((()*-.//.-,,++*+,-.00/0035778::;<>@@@@AABBBAAA?=;87531//.--,,,+++**)))''&&&%$&(+.025442/-)%#######$$%%%&'(++,,,,--,,++,,--......////.-,,,,------......----.///0000111/0000/.--..//0122;EP[ainvyupmjie_Z]bhknqrplfb^YUQNLOOSXYWUPJB9786666677887777889999:::;AFKQVY[\]^ZUQKGFHMRXahlpuwyywrmmoonorv{zzz{||yxtolc\RKIIIIHHGEEDC?=;76....,+-,,,--,,,,+****(''(&&&&&''''&&''''''&&%%''''(((())(())((('''''%%%%%%%%%%%%&&&&&&''(''''())))*)*+++*++++++,++++****))+++*++**********))))**************++++******)))))((((((('%%%%%$$""!! $&)-037;>@CHMPQONNLLMK?-)(('*('&%&TvjefjdgC#.210.-/9HORXYY^dZTMG>1<HZZTW^hjmqqswvqiaafd_ZY^_VPNIHJNMMMIDB@<854774-(%&'()))),/..-...+)(('))*--.014566899;<=>??A=>?@@BBA=::8664210//-,+**+*))(('&%%$#$$$%),.1234/.+&#"$##$#%%&&&''(****++++,,,,----.-......//.-,,,+---------.-------.///00011001100/.00//00230149AJV]fkrxwqpjec]ZZ^cglmonlgb[VSPIGGHJMMKFC=88:9887788888888:<<<;;;;<;?CHMRXZZ\^[WRJIGILQUZagmqtxyxvrmonpqrsv{{{{|}{xsohaVNIHGGDDCDA?;:752,,,,--,*++++******((*)))&%%%&&&&&&%%&&&&''&&%%''''&&(())((((('((''&&%%%%%%%%%%%%&&&&&&'''&&&'())))*)))++*******+***++***))****))))))))))))(()))*))))))))**))**********)))))(((((((((((&%$$""!! $(*-037?CDEHIJLMOMMLJB2''''(*)%%$StkafhiiM&.10..-+/>MWPUVY__][TH>7CQ^^UWc_[htstwyzqffki`[Z[\ZRKIHJJJKJJFDA<875654-(%&'())))+-,,-....*''&$%(*+-/25568988;<<<===;::;>>><9::::8732100.,+***+))((&&%%##$$##%&*,.11/.-*%%$##$$%%&&&''(****++++,,,,--...-......//.-,,,+,,,,-----.-------.////00000011100/1111222223236>GQYajpuxrnhc^[VY^`ekmnnnhc\VSNHDA@CCB@==:;<;::::;;;;;;;;<=======>==BDIPSWXZ\YWTNJKJJNSW\dgkqtxyxvromqrrrty|{{|~}wqlaULIHEBCB?=;97633++,,,,,+****))))(((&&&&&&%%%%%%%%%%%%%%%%%%%%%&&''&&&'((((((((''&&%%%#%%%%%%%%%%%%&&&&%&&&&&'))(''*())()))))))****++*)))))))(((())))(()))((())))))(((())))))))))))**)))))))((())((''((&$$%$#"!!! %'*/27:@EGFGEFIKNNMJD7,*((()*+$#A{m_^edj_%+1038/++-8DLORSUZ[^]QJB=HRWYQRZRTbrstx}|wusoh`\YVUTPHHIHGGEFJGDA>976543.('''(((')*++++-//.,*'%"$'(+.046776679=<<<=<:976798877:;<<;76320/.,*****))(('&&&#$$$$$#$'*-.10/.,)&$$$%%%%&&''(())**++,,----..///,---...//.-,,++**++++,----,----....//00001112221133334455434467<DLT_eltvsmhc_YUTX^cgkmmmje]XRNHE?<:<>>?>>=>>???<<=>????@@????>>>=<>BHLPTVXYZWUPLKHINRUY_dhnrvyzyvrqqqrssvy~ ~yri_QGFC?==;9644330+++,,+******))(((((&&&&&&%$$$$%%%%%%%%%%%%%%%%&&''&&%&((''((((''&&%%$%%%%%%%%%%%%%&&&&%%&&&&'''''')((('(()))))))(())))))))))((((((''''(((((())))))(((((())))))))))**)))))))((())(('''''%%%$###!! %),/4:=BGIIHFEGJNONNG>3))))*)((-qwi`dijb5%.1122-**,5BLNNSW[]\ZNF<8HVY[[S`dY[bmptswwwunjaZTQPPLFGHIFDDEIJHCB?;98851+((()))*)')*++,...,-,(%$$'*.26887668:;=<<<;:9664666577;9897521//.-,,,,,**))('''&$$$$$#$%(*.01//0-)(&%%%%%&&''(()))*++,,----..-------......-,,++**++++,---..........//001122123333344455777877778;@IQYdhpttmha[ZSRV]`dkmmlid^XUOLHC?@@@AAA@@@AAA@@?@AAAA@@??==>><;;<?BGMSUVXYYVRMLJLNRTX\afkqsvwzywtrqqtvxx} wocVGC@<;:964332/.**))****))(())(('''&&&&&%$##$$$$$$%%%%%%%%%%%%%%&&&&&%&&(&'(('&&''&&%%%%%%%%%%%%%%%%%%$$%&''''''''((((((((*((((((((())))((((((((''''''(((((((((((((((())(((((())****))))))*)()))((''(('%&&%$##""! "%(,/4;?AEHIGFEFLNONMNG7'')**)((]{mceiph@!.20/.,*)+-7AFJRRTX^c^OEA=M_ffjnwq_YS`edkrxyumjd[VSPOJDEFHFDDDHHGFFFC>:852+)))+*+,,((')+,-.-,.,*)'&&(+.59854799<<=<::9755545555653666210///.-,/..,++,****'%$$$#$$$%),.21121.+(%#%''&&&'(((()*++++,,,,....----....,-..,+++****++,,--/.....//..//00223221243345556677888888779>EMU]emsrmib][WSV\_cgkijiec[XTOLGEDBCCDCBCCBBAAAABBBB@@?=<;;;;99:;@EKPRWYZZXSPOLOQSXY\achlqsvy{{xusuuuwz| zqfYHA<98755211.,,**))((**))(())((''&&&&%%$$###$$$$$%%%%%%&&%%%%%%&&&&&%''(&''''&&''&&%%%%%%%%%%%%%%%%%%$$%&''''''''((((((((('''''''''((((''''''''&&&&&&''''((((((''((((''(((((())****)))))))(()))((''(('%&&&%$#""! $&(,.37;<?ADA@AEKLMMLHC4(('))*%UwolhnrpW&+/3/..,+**+8@KORRUVVY^QCCO^gkjgmjVYa^ZWWcortplni_YVRNHC@CEGFEFGEFHIIFD?;94.**-**.11.+(')+,,,,-+,+,))()-164345688;:988764222333332222210//-----....,--****(&%%%$$$$%(*.100210.+(&%&&&&&'(((()*++++,,,,......--//..,---++++****++,,--/...//////00112343233334455666778999888998;AIQ[bnrsole`[[YXY_cgikjjhe^[XSPJGEDEFECCCBBAAAABBBBA@?=<;::9999:>EHNRUW[ZYVSQQRTVX[]`bbhmpswx||zxusvx||~ ~yri[J>8754220/.-+,**))(())))(()('''&&&%%$$$$$$$$%%$$$$%%%%%%%%&&&&%%'''''''&''&&&&''&%%%&%%%%%$$$$$$%%%%%%%&&&'&''(('''%'('&&&&&&&''&(''''''&&&%%%%%%&&&&&%%&&'''''''''''''((((()(((**))(()))(())))'(((((('''&$###! $%*,-1467:<=;>@BIJLJJF>2*'()(%Syocagso]$'//.--++***,/BKMRSSQPU[ZLDNbkooerj_[heaaMWglomkjgc^]XNGB?@ACEFDDEHIIHFDB@=7/,,//03682.*'(())+,+,--..++--000011236676544111012222100.//////..--..//11/.,*))*)('&&#%$$%)-1323330/,)&%%%&'''(((**++,,,,,-...../..////..-+**++*))++++++,.//01111222222233333444556666666887799888777?FOZciqusnje`^[YX\`eghklid`\WUPKHHGFEECCBA??@@BBAAA@?=<;:987889<@DJOTWY[[YVTVVXY[\]_ddghlprtx||{xuvxz} ~|yriZI;5531/.-,++++))))(())))(()('''&&&%%$$$$$$$$%%$$$$%%%%%%%%%%&&%%'''''''&''&&&&''&%%%&%%%%%$$$$$$%%%%%%%&&&'&''(('''%%%%%&&&&&&&&&'''''%%&&%$$$$%%%%%%%&&%%&&&&''&&&&&&''''((&'((**))(()))))))))'(((((('''&#$##! "%(+-023347:;>BDFIKKJFB6*'))&G|o`_fqqe7 ,0.---++***-=GDGQTWROQYYRJSbjq^ovbeoha`bRP\dhjhd_`b^YQGB@?@BCCDDEFFFFEDB@=7/-/13425420+)((((***,/12200..--//../02241111011012222100///////...-..//11/..,,,*)('&&$%$$$'+-2555410-+(&%%&''''((**++,,,,-....../00110000/-+++++**+,,,,+,.//011112222222222223434566655557877998887666<CNXaiqtvtqjgb`][Z]dgkjhhfc^YVTPKHGFEDCBAAABBBBBBA@?=<;:977889<AEJOQUWZ[[[Y[[Z[\_acceffhknqtx{|zwxx{|{{yuqg^M=200.-,,+++++(((((())))(''''''&&%%%$$$$$$##%%%%$%&%%%&&%%$$%%%%&&'''''%((''&&''''&&%%%%%%$$%%$$$$$$%%&&&&&&'&''&&''%%%%%%&&&&&&&&&&&&%&%%%%$$$$$$$$$$$$$$%%%%&&&%&&&&&&'''''''())))))***)(())('''(('&&&('$%#""" #')./12237:=@BCFHJMLGA4)(*'6uvkkjpqj8%-.----++))*0FCBIQSQPOSRMXY[`k]Knovnigd^YYVRYabe`\][[\SKFA?>?@CFFEDCEECBA@<72.04872-121-,*))()(),/255442..,..--///.///////00133332111..//0000..--00000/.-,,*)('&$%%$$$$(+.2455211.+)'%%''((()++++----........001111200/,,----,,--,,,,..//1111111122111101223444554444566688888888659BLV_jsx|zwrkgc`ZX]adghhjhd`\YVQLIGFEDC@??BDCDECBA@?;;;:89:::<?CHNSWZ\^^____^^_bdffffcbehkorvz||zz{}~{yvtsni^QA2..-+,,-++**(((((())))('''&&''&%%%$$$$$$##%%%%%&&%%%&&&&''%%%%&&''''&'((''&&&&''&&%%%%%%$$%%$$$$$$%%&&&&&&'&''&&&&%%%%%%%%%%%%%%%%%%&$$$$$##############$$##$$$$%%%%&&&&&'&''()))))))))(((((('))(('&&&'&'%$##"" ""'),.01358<BCDDFJLOLGA2((,,l|kkqrnlA#*------++*))-/9FLQPPPPKLPW^`[b^W_eproiiec][X]`aa]\ZUXXSOLE?<=>?CCBAAEFB@?ABA90.1550-/211/.,)))++/-0358830.,,,--...-.--..-0/013333332200001111../000000/.,++*)(''&%%$$$$%),0244221/-,('%%%((()++++------......000000001011/---..--,,----./00000011110011012223444455444466777777774448@LWaju|~}zvpie_YW[]cffijifb\ZVQLIGGFEBBDFGFGGECB@@?=<;9:::;=ADHNSWZ\__``aa``acegggfc``bdjosx}||zz{~}xvtrqmh_VF6---+++,,,++''(((((())('&&&&&'&%%%$$$$##$$%%&&&&&&&&''&&&&&&&&&&''%%%&((''(('''''&&&$&&&%%%%%%%%%%%%&&''&&&&%%&&%%&&$$%%%%$$%%%%%$$$$#####""""""!"""""""####$$##$%%%%%%&&&&&'(((''))))))))))(())))''&((&&'%%$"" !##')*+.039<@BFJMNNOOJFA1()*[wlmvsoP #,,,,,,,,,**+15:@HORONKHKKUadXQZeigackh[Xda___\__\ZYSQORRNHB@><;;=<<=?====?DKA1**.0.//33201/,*-0110214883/+*)),+,,,,,-....///02123234433221100//..00110/.,,++*(''&&%$%#$#&'++/0111//-+)'''((())*+++,,,+++,...-0000//01001100..--..--,,----,..../00////000011222233443333466666777654447@LWclv||vrie\USX\aeiijifb_[VRMIIHGGFGHJJJJGEDCC@>=<;;==??AEHMRVZ^`accbbabedeffffc`][\djnsuy{{zz{~~~ywuronlicYM=.***+++,+))(((((((())('''&&&&&%%%$$$$##$$%%&&&&&&&&''&&&&&&&&&&&&%%%&''''((''''&%&&&%&&%%%%%%%%%%%%&&''&&&&&&&&%%%%###%%%$$$$$$$####"##"""!!!"""!!!!!!!""!!""""#$$$%%%%%&&&('''''))))))))))))))))****''''&$$#!!!!"$')*+-/16>BFIMPRRTRJF?.()9zrinwwqT! "&,,++,++,,***/05>FILLIJLMLXbcYEHST_fldda_`bike^^_^]ZRPQSRLEBA><87788989999;BPI1*),-022421/0//023445222330-*)**)+++----.////0/0100023443333221100..//00/./--,+*)()(&&%%$#$$$'(*-./0100/.,)'(()(()**)+,,....//.-////..,-////....-...--,,,,,,,-..-.//////0000001100002322222333334554444469@LYcpv{{}}wqh_VPQW\aehjjlgda\VRMLKJJJKLLMMLIGFDC@?=<<<==??CGHMRVY]__cddebddeeffffc]]ZX[aflpsyz{{yz~~}xvrpmnlje^TC1+)*)**+*(('''())('))('''''&&%%%%%%%%$$$$%%&&&&&&''''''&&&&&&%%''&&''''''''&&&&''%%&%%%$$%&%%&&%%%%%&&&%%''&&%%%%$$$$"###$$$$##""""""!!!!! !!!!!! ! !!!!!##""!"##$$$%%%%%&'&&(((((())))))))))))****((((&%$$""!!!$()*++./5=AEJSUTTVQJE;*)(Tuqmsyte* "!%*,,***+,.,+++*3=BHKGHHFDFP_SFKSQTbgeijf^_\bjdZX[\\ZSPTVSQLCB?;:7544545688:AE@8,)+,.334422145665676310//,+*)(((()+----.0/011////0001331223333110/,-/../-.,-,+++*))(&$$$$$#$$$'(+,,/0221/-+*)))******+,..00/0/-./////....////......-,,,++++,,--....//////00////////00122211111123334434578ANYenwz{|yuoeXPPQTZbfgjkjgdb]XRPOMOMNOONNLIHFED@@?>=<>>>>AEINQU[]]acefffgffggffdb_[URQV^dhkrvz|z{|zzxuqrllkif_XJ:,)+*))))(('''())('))('''''&&%%%%%%$#$$$%%%&&&&&&&&&&''&&&&&&%%''&&''''''''&&&&&&&&&%%%%%%&%%&&%%%%%&&&%%%%&&%%%%$$$$#"""####""!!!!!! !!! !!! !$$!!!"##$%$$%%&&&'))(((((())))))))********(((('%$$##""#&())*+..48>HKRVUURKHD9)(,ksjqxsi7$"#'*++**)+,./,*,+5AGGIIF@?;=FRJ?IRYkhfhhceg\P]gaXTWXZZVQUSMMJCC@==:755534688<>>><0))+-0144434688875431/---,+*)((((()*,../0/011///////01113234445320/,+../----,++**++(''%$&$$$$#&&(*+.133220//,**++++,,,.0000/0110.////....////....//-,,,----..--....//111100//////////002211222223334454576:CNYcjsyyyurj_VOSRW\bgmklkieb\XWRQPPOPPNNJHGEDDB@@??=??AACEGNQU[]^bdgihghggggfeda]XRONORYaglptx{zyzzxxwsmmligd\SC4-*)((''((~~~~~~}}||}}~~~~~~~~~~}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~}|{zzzzyyyyyyyyyz} |{zzzzz|} ~~~~~~~~~~~~}{||{}~~~~~~~}}} ~}}}}||{{{{||{}||}~~||{|~~~}{xwyz~~}|}~~~~}|~~~~~~}}||}}~~~~~~~~~}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~}|{zzzzyyyyyyyyyz}~~~|{zzzzz{}~ ~x{ ~~~~~}{||{}}}~~~~~}}}~ ~}}}}||{{{{||}~~~}~~|yy{|~~~~~~}{zyz{~~}|}~~~~}{y~~~~}}{{}~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~}||zyyzzyyyyyyyy{} ~~}}|{z{{zz{~ yssu~~~~~}|}}}}|||}~~~}}}}~ ~~}}}|{zz{|}}~}|{yz{|~~~}}}}zzyz|}~~|~~~~~~~yy~~||}}}}~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}||z{{zzyyyyyyyyy| }|{}}{z{{{{{}~yvuvz~~}|}}}}|||}~~~~}|}} ~~}}}|{{{|}|{xwxy{|~~}|}~|{zy{|}}}|~~~~~~{yy~~}}|{}}}}}~~~~~}}}||}}}}}}}}}}~~~~}}~~~~~~~~~~~~~~~~~~}}||{{yyyyyyxxxxyz| }|{{||{zzzz|}~~}|z{~|{}}}}}}|}~~}}}}|~ }~~}|||}~|yxwwxz|}}}|{| ~|{{{|~}{z}~~~~~{yx}}||{z||}~~~~}}}}||}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~}}||{{zzyyyyxxxxyz{~ |{{{|}{zzzzz|~}|}~}}}~}~ ~~~|{}}}}}}}~~~~}}||} }~~~~~~|}}zywvvxz|}}}|{| ~}{||}}{yz|~~~~~~}{y~~{|{{{{|~}}}}}|{}}}}}}}}}}}}}}}~~~~~~}}~~}}}}}}~~~~~~}}||||{{zyyyxxxxy{|~ }{z{{}~{{{zz{{}~|}~~~~}}}}~|||~}|||}}}}}}~~~|{{}~ ~}~~~}zyvuuw{|}}||z| ~}~~~|zwxz}~|z~}}{{{zz{}~}}}}||{}}}}}}}}}}}}}}}~~~~~~}}~~}}}}}}~~~~~~}}}}||}|{{zyyyxxxxy{} |{|{{{}|{{zz{{|}~}}~~~}{{ ~}|~}|||}}}}}}~~~~}{{| ~}~~~}zxuttw{~}||z| ~|wvw{~}{~~}{{zzz{~~~~~}}}|{{{{|||||||}}}}}}}}}}~~}}}}}}~~}}}}}}}}}}}}}}}}|||zzyxxxxwxy} ||||||||~~|{zz{{|}}~~~~~~}||y~{vvz|}|~|{{{|||}|~}}~~}}{|~ ~~~~~~}~~~~{xusstw{|}}|{{}zwvy|~~~~~z~}|{{{{{|~~~~}~}|{{{{|||||||}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||zzyxxywxz{ ~||||||||}~~}zzz{{{|}}}~}|}~~}|zxyzx|~xtqqrtw|~}|}}||}}|~}}}}||{|~~}||}}~}~~}}yvsrrsw{}~}|{{} }yvwy~~~~ {~}||{{|}}}~~~~~~}|{zz{{||||||||}}}}}}}}}}}}|||||}||||}}}}}}}}}}||{|{|zzyxxwy|~ ~||||||||}}~~|{{||{}~ ~}}~}|}~|{{zzywvv|~xtvxz}~~}|||||}}}}}}}}|{{{}}|{y{|~~~}}}zxusqqrvz}~}|z|~ {xvxy~~~~ |}}|{|||}}}~~~~||{zzz||{{||||||}}}}}}}}}}~~~~||||||||}}}}}}}}}}||{{{{zzyxyx{~}{||||||||}}~|{{||{|}~~~}~~|zz{{zxxvw{~zz{||||}~}||||}}}}}}}}|{{yz}~|yyxy{~~~}}|ywtqppsvz}~|{{}~ {xvx|~~~ |~~}}|}}~~~~}~~~~}|{{{{{{{{{{{{||||}}~~~~}~~~||||||||}}}}}}}}}}{{{{{{zzyyy{~}||||||}}|}|}}~}y{||{||}}~~~|yz~}}~~||}}|{wvyxuy~{z{~~~}}}}}}}}}||zyzyyz}}|{ywvwx|}~}~~}|xuspnpruy}|{|~ }zwvz}~~~~~}}|}}~~~~~~~~}|{{{{{{{{{{{||||||}}||||{}~~~|||||||||}}}}}}}}||{{{{zzzzyyz|}||||||}}|||}}~~}}||}~}~~}ztsw}~}|}~|tswwy~{z{~~~}~~}}}}}}{{yyxxwx{}~{zywvvwz}~~|zwurpnnpuy}|{|~ |zwx{~~~}~~~~~~}|{{{zz{{{{{{||}}}}}}}}}}}}||||||}}}}}}}}}}||||{{{{{{zzzzxz|}~~}||||||||}}}}}~~||}~}~~||yy|} ~~~wsuwz}}|z|}|}}}}~~}|zyyxwutvy}~}|zxwvuuvy~|zusqpnnpuz}}|{|~}zvvy|~~~}~~~~}|{z{{zz{{{{{{||||}}}}}}}}}}||||||}}}}}}}}}}||||{{{{{{zzzzxz|~}{z|||||||{|}}}}~~}~~~~}}{}} }ysprw{|z{|~}|}}}}}}|zyxwwutrtx}}|zywvusuvz~|yutrpnnpuz}}|{|~~zwuuy|~~~~~~}|{{{{{{{{{{z|||}}}}||||}}}}}}}}}}~~~~~~}}}}|||z{{{{zzzyyyy|~~}}~}||{{||||||}}}}}~~|||~}}{xtolu|~{{|}|||||||{yxwwvtsprv{~}||}~~}|yxvvusstw|~|yvtsrompty}}||}~~~~}zxusvz}}~~~}|{{{{{{{{{{z|||}}}}||||}}}}}}}}}}~~~~~~}}}}|||z{{{{zzzyyyz}~}}~}{||{{||||||}}}}}~~{xy||~}{ywuoks|}{{|}{z||||{{zywvsrponouz}~~|zz{|}|zxxvtsrrtw}~|xttrqonpty}}||}~~}}{xtrsv{~~~}|{{zz{{{{{{||}}}}}}}}}}}}~~}}||~~~~~~}}}}||{{zzzzyyyxxz{}}~|{{{{{||||||}}}}~~~|{||~ }{zyzxtmqz|z{}~{z{{|{{{zxwvsqnmlmry}~|zyxxz{}|yxwtrqpqsx}~|wtsrqonpty}}||~}|yxtpqrw}}}~} ~}}}}~~||}}}}}}}}}}}}~~}}}}~~~~~~}}}}||{{zzzzyyyxy{~|{{{||||||||}}}}~~~~}}}~ ~|zyyxxvpnw{z{}~{z{{{{{zyxutqnkjikpv|~}zxwvvvx{||zywtrqpquy~~|vtsrrpnpv{}}||~}|zxtqnorw}}||}~} ~ ~}|||}}}}}}}~~}}~~~~~~~~~~~|||||{{zzzzyyyy{~}|{{{||||||||}}}}}}}~~~~{{zzyxwwpnuyy|}}{z{{yyzxxvtqomkifiov|~{xvtsstxz{zzyvtrqpqtyzvtssrqqrx||||}~~|zxvrokouy~~z|~~ ~~}}}}}}}}~~}}~~~~~~~~~~~|||||{{yyyyyyy{ |{{{{||||||||}}}}}}}~}|}~}~|{{zzyxwwvsuyy}~}{z{{{{{zxvuroljgfiov|~{xvtutuxz{zyxvtrqpqu{{wutusrsux||||}~~|zwtqnkou{{y{}~~~}}}}}}~~~~~~~~}}~~~~}}||{{zzyyxzy{} }{{{{{||||||||}}}}}}~}|~~~|{zzyxxwwwxtsxz|}{z{{{{zzywurolifeflu|~|yxuvvwyz|{zyxvsrstx}zxvwvsuuvz}{{{|}yvqollpv}}zx{~~} ~}}}}}}~~~~~~~~~~~~~~}}||{{zzyyxz| ~{z{|||||||||||}}}}}}~~{{~|{zzzyxxwwwwssy{|~}{z{{{{zzywurolifeflu|~~}{zxxz{}~~|{yxxy|~~{yxwvutvy{{{{|}yvqnkkry~}xwz~~}~|}} ~{{~~~~}}}}}}~~~~~~~~~~}}|{{{zzyz{} ~||{{{{zz{{{{{{||}}||}}|}}||} |{zzzyyxxvxwtrvz~|{yz{||{zyxutoljgfhlu|~~~}|||~~}~~~{yywutvz|||}~~}yupljltz~{wuz~~|}}~}xwxxyy| ~~~~~~~~}}|{{{zzz|} |zzz{{{{zz{{{{{{||}}|||||}~}{{| }{zzzyyxwxxxtqry||{yz{||{zyxutqnkhgimu|~~~~}~~{xvuwz|||}~~~}yupllpu|}yvwz~~~~~~~~~}}||}}~zxxyzz{{|~ ~~~~~~}}|{{{zzz} |zxxxzz{{zz{{{{||||}}||{{{||~}||{| }|{zzyyxxxyvsnpw||{zzz}}}{zywtrokhhinv|~}}~~~~~~zwuwy|||}}}~{wtojkpw}}yux{~~~~~~~~~}}||||{{xxy{{{||}}~ ~~~~~~~~}}|{{{zz{~ ~~||{zxwyzzzzzzzz{{{{zz{{zzzz{{{|}}|{{z{~ }|{zzyywwx{}ytrw|}}{||}}|~~}ywspoljkov{}~}~~|zyxyyz|~}}|~|wuwy|||}}~~|~~~~~}{wrmkmtz~}yux{~~~~~}}}}}}}|~~}}||{{{{{{}}}}~~ }~~~~}}||{{{{{|} ~}|yyyywwwyzzzzzz||{{{zyzzyzzzz{{z{~~}}{{{{|~ ~~{zyyxxwwxx{{utx|}~}|||}~|zvsonnnrz}~~|zxwvtrqruy~~~|||}~xwxz|||~~~~~~~}{urmjnv}~|xux|~~~~~}}}}}}}|}}||}}{{{{||}}}}}}}~~ ~}}~~~~}}||{{{{{|~~{zxwvwwwwwwwyzzzzzz{{{{yyyy{yyyzz{{z{|~~}|{{{{{} |{}}{yyyxxwwvvwvwvwz}~}|||}}xvtrstx{~~zyxwvusqpqty~~{vxz~|xyz|||~~~~~~~~~~}yupnmqy~~|wux|~~~~~}}||||||||||||||||||||||||||}|{{}} }}~~~}}}||{{zz{}~{xvvwwvvxxwxyyzzzzzzzy{{zyzzzzyyyyyz{{|}~}{||zz||~~~~|v|~|zyyxxxwvvuvywvy}~}||~ |zyy{{{~~yxxwuutrrsuz~}yutw{}{yz{{{|}}}~~~~|zuqoot{|wux|}~~~~}}||||||||||||||||||||||||||}}|{||}||~~~}}}||{{zz{}|ywvvvvvvxxwxyyzzzzzzzyyyyyyyyyxxyyyz{{|}~~|{||{{||||}{xy}{yyxxxwxzxvvtvy}~}|{||}~~}~~~{yxwuutsstw{|vsty~|zz{{{|}}}~}|}~yuqnou||vux|}}}}}||||||||||}}}}||||||||||~~~~~~}}{}~}|{| }yz}~}}}}{zzz{{}~zxwwwwwwwxxyxyyzzzzzzzz{{yyxxyyxxyyyyzz|}~|{|||}|~}|z|{x{}{yxxxwx{zussv{}}|zzz||~~~~{yxxxvvsuuz~}yutw|~z{{{{z{}}~~}{yz}~|xtpnpw}~~xttz}~|}||||||||||||||||}}}}}||||}}~~~~~~~~~~}|~|yz}~}}}}|{zz{~|yxwwwwwwwxxyxyyzzzzzzzzyyyyxxyyxxyyyy{{}~|{|}}~~}|z|{x{}{yxxxwy}zuttv{}}|zz{|||~~|yxxxvwwxw{~}zvtw{~|{{{{z{}}}~~{wvy||wtposx~~~|wrtz}~|~}}||{{||||||||||}}~~~|}}|{||}}}}~~~~~~ ~{{}~}}}|||||~ |wwwwwwwwwxxyxzz{{{{{{zyzyyyxxxxxxyyyyz|~}|}|~|z{zv~}{yxxxyz}ytrrtz~|zzz{{|}~|zyyyxxwxz~}{xuvz}}|{{{z{}}}}yvx{}}~~~{vsopty~{uruz}~{~~~||||{{{||||||||||~~~~~|||}}}}~~~~~~~~ {{}~}}}||}~ ~zxwwwwwwwwxxyxzz{{{{{{zyzzyyxxxxxxyyyy|}~~|~ ||yxx~}{yxxxy||wuurtz~}{{{{{|}~}|{{{zyxy{}{xwwx{|{}||{{{|}}}}~zwx{}}~~~{wsppu{~~~~zuruz}{x~~~~~~}}{{{{{{||}}}}}}}}||~}|||~~~~~~~~~~~~~ |z|}}|}} ~|yxxxwwwwxxxxyyzz{{{{{{zzzzyyyyyyyyyyyz|~ |{xvv}}{zzyyyz}~yuuqtz~~}~}|z{|~}~~~{z{{{zyyz}~|{yyyyyz{{{{{||||{}~~{wy{}~~~{vsqrx|}~}}~~|yssu{}zu~~~~~~~~~~}}}}||||||}}}}}}}}||}~~~~}|||~~~~~~~~~~~~~~~}}~ {ywvwwwwwwxxxyyyzz{{{{{{zzzzyyyywwwwyyz{|}|yvv|}{zzyyxxx|~{xsu{~}|{|~}z|~~~}{{{{zyyz}}{{{{{{{yy{{{||||{}~~|zz|~~~{wsqsx|}~~~~~}ytsw|}yw~~~~~~~~~}}}{||}}}}}}}}}}||||||}}}}||}}}}}}~~~~~~~~ }~~ ~zxwwvvvwwxxxxzzz{zz{{{{{{zzzzyyxxyyyyyy{} ~~~vu{}{zzzz{zwx}}xtu{~}||}||{{~}{{{{zyy{}}|{|}~~}{zz{{{zz{{z|~||||}}~~~{wsruy}}~~~}xttw|{xw~~~~~~~~~}}}{||}}}}}}}}}}||||||}}}}}}}}}}}}~~~~~~~~ }}}}~ }zwxxvvvvwwxxyyzz{{{{{{{{{{zzzzyyyyyyyyz{} ~}zvz}{zzzz|~ysu{~ ~||~~|z{|~~}{{{{zyy|}}|{|}~~}{xzz{{zz{{z|~||||}~~~{wsrwz~}~~~}xttx||zx~~~~~~~~}}~~}|||}}}}}}}}}}||||}}}}}}}}~~}}~~~~~~}~~~~ ~~~~|yxwwwwwwwwwxxyyzz{{{{{{{{{{zzzzzzzzyyzz| ~|{y{}|{zzzxxww{zvrv{ }}}{{|~|}}{yyz{~~{{}}~~~}{xyz{{zz{{z|~{{|}}|~~}wstx|}~||||wuty~~~~~~~}}~~}|||}}}}}}}}}}}}}}}}}}}}}}~~}}~~}}~~~~~~}~~~~ |zxwvwwwwwwwwxxyyzz{{{{{{{{{{zzzzzzzzz{|~ }~}|{zzz{{}}{xttv{}~}}}{{|}{{{{zyz{~~}}~~~~~}{xyz{{zz{{z|~}{{|}~|~~}wttx|}~||||wuuy~~~~}}}}||~~~~~~~~}}~~}}}}}}}}}}~~}}}}~~~~~~~~ |yxuvvvvwwwwwwxxyyzz{{||{{{{{{{{{{zzz||~~~|{{{zy|}|xvux{~~}||{{}~}|{{zzyz}~~~~~}yxxy{{{{zz||~{{|~~}}|wtvz|~}}}}~|wtv{~}}~~~~~~~~~~~~}}}}}}}}}}}}~~}}}}~~~~~~~~ ~zwwvvvvwwwwwwxxyyzz{{||||||~~~~~~~~}~~~~~|{{{zyzz{usv{~~}{{}}{{{}~}||{zzy{}~~~|{yxxyzz{{||||~}{{|~~}}|vtuz|~}}}}~|xuw{ ~~~~~~~~~~~~}}}}}}}}}}||}}}~~|~~~~~~~~~~~~~~|zwuuuuuvvvxxyzyyyy{|{|~~}~~~}{}~~~~~~{z{zzyy{vvz{}~}zzy}~~}{{}~}|{{zz{|~||{{yyyyz{|||{|}|||~~}}{wuuz}}}}~~~yvx|~~~~}~}~~~~~~~~~}}}}}}}}}}||}}|}}~~~~~~~~~~~~~~~yvtttttuvvz~~}{{{|~ ~|}~~~~~~~~{{{{zzyz||}~|zz~~|zz}~}|{{zzz|~~{{z{yyyyz{|||{|~|||~~}}{utx|}}}}~~~yxy|~~~~~| ~~}}~~~}~~~~~~}}}}}}||||}}}}~~~~~~~~~~~~ }xtstsrrsw| ~~}~~~~z{{|zz{{}}~ {zz}~}||z{y{}~{z{zyyxzxz||||{}~~||}}~}}zuuy{}}}}}~{y{|}~ | }~~~}~~~~~~}}}}}}||||}}}}~~~~~~~~~~~~ ~zwtsttx| ~~}~{x{}}~{zz}~}||z{z{}~}{z{zyyxzxz||||z{|~||}}~}}ztvz|}}}}}~{y{|}~|~~}|~~|~~~|||||||||}}}~~~~~~~~~~~~~~ zvvw{ ~~~~~~~~~{|}~{z|~~~}|zz{{}~}zzzyyyyz{{{{zz|}}{||{|{~ytvz|}}}}}}z|}~~~}}~|~~~ }}}||||~~~|||||||||}}}~~~~~~~~~~~~~~~~}} ~~ ~~~~~~~~}|~~}}}}}~|}~{z|~~~}|zyz{}~}zzzyyyyz{{{{zz|}~~}{||{|{~ytvz|}}}}~~{}}~~~}}}~}~~ ~}}}}}}}}||||||||||}}~~~~~~~~~~~~~~~~||}} ~~ {}~~}~~~~}|{}}||~}z{~~~~~~}}~}|}~~}~~~~}zzzzyyzz{{{{{{z{~~||||{||}ztvz|||||~~}}~~}{{|~~~~~}}}}}}}}||||||||||}}~~~~~~~~~~~~~~~~~} ~||{{|||~}}}}z}~}}}}z{ ~|||}~~}~ }|z{~~~~~||{}~~~}zzzzyyzz{{{{{{z{}}||||{||}ztvz|||||~~~~~}{z{|~~~~~~~}} ~{|||||{{{{{zz||||||}}~~~~~}}~~~ ~{zyyyyyz|~}{{{{}}~~}~}|~~ ~~~~}zz|}~~~{{|~}{{}|~~|{zyzzyyy{{{{{{{yy{{||||{||}zuw{|||{}~~~~~~|zxz{}~~~~~}}}~|z{{||{{{zzz{{{{{{|||}~~~~~~ ~|{zyy{{|~~ ~}}}|}~ ~~ ~~~{yz}~}~~~~~~~~~}}{||}|~|{|~}~~~~}{{~}}}|{zyxxyyy{{{zzyyyyzz{{{{{||}zvx{|||{}~~~~}zxxz|}~~~~~~~~~~~}}}} zyzz||{{{zzzzz{{{{{|||}}~~~ ~}|}~~~~~ ~|~~~}}|~~~}~~}}}~}~~~}}~~~}z{|}~}}~}||}|{zxwwwyzz{{zzzzyyzz{{{{|{{|~~zwy{}||{}~~~}{xvx{|~~~~~~~}}||}}}} {yzz||{{{zzzzzzzzz{{||}}~~ ~{zz~ ~~~~~~}~~~~}wz~~}|~||}~||{z{{{}~}}}|{zz{}}}~~~~~~}z{~~~~~~}~~~|{zxwwwyzz{{zzzzyyzz{{{{|{{|~~zxz{}||{}~~}{xwy|}~~}~~~~~~~~}}||||}}{yz{{{{{{{{{yyzzyyz{|}||~~ }|zwvuz ~~}zwy|}}||~~~|vtw|~|{~}|z{zzz{{{|~~}~{{{zyyxwz|}}}~~~~z{}}}}}||~}{yxxxxxz{||zzzxyyyy{{z|||||~|yx{||{{{}~}zwx{~~~~~~~~~~~~}}||||}}{yz{{{{{zzzzyyzzyyz{z{||~~ zwtsrruz {trtxxwxyyzuxzy{}||{zzz|{||||zzzyz|||~}{z|{yyy{{zyy|}}~~}~~~~zz}~ ~{{|~}||~}{yxxxxxz{{{zzzxyyyy{{|}}}||}}|yy||{{{{}}zwx{}~~~~~~~~}}||||}} |{|{zzzyyyyzzzzzzzyz{{||}~ ~wtppqqquz} |vtsuvustuy{xx{xxwyz|{{zxyzxwyyxxz|||}{{{{}~}}~~~}~~{{~}|||}~|{}~}{yyyyyyyzzzyyzzyyxz{||}}{zz}}{zz{|z{{{}~~}zwx{~~~~~~~~~~}}||||}}} }||{zzyyyyzzzzzzyxyz{||}~ |vsrqqrrsvx}{} ~ywwtuvvustw|}||{}}}{zwvxz|{zxwz|~}}|{zz|||}||{|}~~~~~}}~~~~~~~~|||{|}~~}}~}}}{yyyyyyyzzzyyxxyyxz{||}}{{{||{z{||{{{{}~~~~}zwxz~~~~~~~~~~~}}}}}}}|||||~ ||yyyyyyyyyyyyyyzzz|}}~~ ~{wsssssrstux~{z|~ ~zzywtuvussuyz|~~{{zyvvz{zxvsu{~~zz|zyzz{{}}~~~~}}|{}~~~~~|xvx{zz{~~~~~}}~~|zxxxxxxzzzzyyxxwxyzz{{~~}|{|||{z{|z||{}}}~~}}~~yxyz~~~~~~~~}}}}}}}}}}|||||}~ |{zzyyyyyyyyyyzzz|}}~~ ~{xvttttttstuvx|||}}~ ~{{ywtturqruvy||yxwvtwywusqpqsux{|}}|{zz|||}}}|{|||~~~}}~{sry{z{}~~~~~|||||zyyyyzz{{zzyyxxwxyzz{{~~}zy|{{{{||z|{|~{{}~}}}~~{yz|~~~~~~~~}}}}}||||||||||}}~ ~{zyyyyyyyyyyyz{{|~}~~~ }zxvvwxvvvuuuuvvw|} |{zzwtuvuttwxz~{ywvtttsrrrssuuuuuy{}}~~}~~||~~~}}}|~||~~~~~~}|st|{z|}~~~|{|||{yyyxyy{{zyyyyxwxyzz{{~~|{z{{{{{|{{{|~{{}~~~~}}~~|z{}~~~~~~~~~~}}}}|||||||||||}}|} ~{yyyyyyyyyyyz{{{|}~ ~{zxwuvwyyywwvvvvwwx{~~}{{~ ~~}}|{|}zxwwtutv{|xx{}|zxtsuusolmrwwvvwvtvz~~~}||~~}~~~~}~|y{}zvx}||}~~~}|zzyxyy{{zyyyyxwxyzz{{~~|zyzz{z{|{{{}~xy|}~~}}}}zy}~~~~~~~~~~~~~~~|||||||{||||||||}}}}}} }zyxxxxwwyyyyzz{| ~}|{yxxxxxxxxxwwwwwwwxy{~}vuwxxwz|} ~}}xvvvtwxwrpuxyzwustwwphhpvyxyyyxwy{~~}{|~}~}~{zz|~}|{z{}||}~|z| ~}}~ }|zyyz{{yxxxxwwxz{zz|~{yyzzy{{{{{{|zvy|~~}|wwy}~~~~~~~~~~~~~}}~~|||||||{||||||||}}}}}}}}}}zzzzzyyyyyyzz|~ }|{zxyyyyyyyyyyyxxxxxxxyy{}}|xusrsuvvxz|||}}} }yuvvtrqrnknsw{zwtttuoecjswxz{{zyvx{{z~}{zz}}}}~~}{zz{{|~~{zz{|||||}~ ~~~|{z{{yxxyxwwxyzzz|~{yxxyyz{{{{{|~yvy|~~}{wvy}~~~~~~~~~~~~~~~~~||||||{{{{||||||}}}}~~}}}} ~|{{zzzyyyz|}|{zz{zzzzzyyzzzzzzyyyyyy{{z{{}wolotuqsyy{{z}~~zwwuupklnlmpw}}xuttsoihlswy{||{{xwwwtx|z{~~{||}{xuvxz{}{xxx{|~}~}{zyyywwxxwxyzyz|~|xwwwy{{{zz|}~zvx|~~{vux}~~~~~~~~~~~~~~~~~||||||{{{{||}}}}}}}}~~~~~~ }}|||}~ ~}||||{{||||{{{{{zz{{zzyyyy{{{{{|~yrlinrrnpux|||~~}ywwunmiopnnrvsmmptusomptwy{||{{yxwvstx}}|{{}}|}zyz{{x{~~ }|~~~~|zyyyyxwwxyzyz||xwvwyz{{zz|}|x{|~~yuvy}~~~~~}}~~~~~~}}||||||{{{{||}}}}}}~~~~~~ ~|}{{{{||}}|}||||{{{{{{{{{{zzzz{{{{|~|smjlmrvux{}}}}|zxvrmiglqttromlmquvtqoruwxz{}{zyyxvtppty}} }}~}}~~}yx{{||~~~}|{{}~~|yyxxvvwzyy{|}|xwvvxzzzzz{}{|}~~~|yvw{}}~~~~~~}}||||||{{{{||}}}}~~~~~~ |zz||}}}}}}}|}||||||||||||||{{{{|||||~ ~|ztlhikr|~~~~}|| |yvohdelrvvtroooqruutstuwwwz{~|zwvxxwuuvwxww|~}}}{z~~~~~}|zy{{|~~zvwwvvvxxyz|}|xuvvxzzzzz{}|}}~~~|yvx}}}}}~~~~~~}}}}||||{{z|||}}}}~~ ~{{zz{|~~~~~}}}}|||||||||||||||||||}}||~ |unlmv~}}~}~}z| |ytnf`emrtwwtrrssrtuusstustyz}}xtsuwxwstwwwx|~~}||yz}}}~~}|| ~~~|{zzyz||ywvvvvxxy{z}}xttwyzzzzz{~}~~~~~~~}{ywy|}~~~~~~}}}}||||{{z|||}}}}~~~~~ }|{{|{}}}~~~~~}}}}|||||||||||||||||||}}}} ~{vtw}~}{{{|~~|z||y{ ztnfcfmsvxxwwvuttuvwvwutqruvyywrqsuxvttvvwwz|}~~}}}zz|zwvtw{~}|~ ~{{|}zyyxwy{~|yvvvvxxyz{~}xttwyzzzzz{}{|~~~~~~~}{ywy|~~~~~~}}||||||||||||||~~~~~~~~~~}}~~|{{{}}}~~~~~~~}}|||{||||||||||||||{|~~~~| zy{~~~~zyyzz}~{xzztojegnruvvxxxutttwxyz{yusuwwvrporssrqrtuvxzyz|}}}}~~~|xtns|~|yyyyzxxxvvz}yvttwxxxy{~zustwyzyyxz}~{xz}}|||zwwy} ~~~~~~}}||||||||||||||~~~~~~~~~~~~}|||}~~}{{z|||}}~~~~~~~~}}|||{||||||||||||||{|}}}}~~ }z{|zyyyxz}~|wtxztojfgnruvwwxxuttvwxy|}ywuwxwtpnnooonorsuwyzyz{}~~~}zvtx~zwwvwxwxxuvy{}}xuuwxxxx{~ytqtwyzyyxz}}xwz|~}}|yvz ~~~~~~~~}}||||||||||||~~~~~~~~~~~~~~~~~|}}||||||}}}}~~~~~~~~~~~~}}|{||||||||}}|||||||||}z| }|~|||zyvwy~}~|zurw}unkhhnsstuxywsqsuwyy||zxxyxvsommljgkmqtuxz{|{z{|}~}}~zvuy|yuuuvvvvuvwwyz|~|ywwxxxx{}xsrtwxyyyx{~}uu{}~~}|{y|~~~~~~~~}}||||||||||||~~~~~~~~~~~~~~~~~|}}||||||}}}}~~~~~~~~~~~~}}|{||||||||{{|||||||||} }z}xwz|yssx{xttx~}|~~}~~{ywvyz{zwtqw }umkhjnstuxxwpjfgijmrsvwvvwwtqokgddhgkruxz{|}|{{z{ ~}zwyz|}~}}}{wz{wtsstvvvuuvwyy{~|wwwwwx{~|wrqtwyzyyx{ysu|~~}||~~}}}}}|}}||||||}}}}~~~~~~~~~~~~~~~~~~}}}}||||||||~~~~~~~~~~~~}}}|||||||{{zz{{{{|||||}{z}zrttuvtuvspoont||zxw{~~}zwvvxyzwrry}wolihlruxxwrg_]]\`joqsqrrtsrqmhdddijmrx|~~~}{z{}|z|~}ywwwywvsuz}}yw||yvssrsuuvutuwwxz}~zxwwwy|~~zuqqtwz{yxx{}vty}~~}{|~~~~~~}}}}}}}|||||||}}}}~~~~~~~~~~~~~~~~~~~~}}}}||||||||~~~~~~~~~~~~~~}}}|||||||{{{{{{{{{{{{z{|zwxzwruz||zrnoonnt}}xvvz}~}{xwwxyyxvvz }xqnjimsvxvvpcTOOMWdorsqponllikjiggjmnosvz}~|zyz}~{{|}{ywx}~||~~|wvx||wtrrrsuuuttuwwxx|}|ywvww{~~zuqqtwz{yxx{~{vty}~~}{}~~~~~~~~}}}}}}}|||||||}}|~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}}}{||||||}}}}~~~~~~~~~~}|}|||||||{{zz{{{{{{{{{|}yvuzvnqwwz~~tqoopos|~vuv{}~}{zxwxxwwwxz~unjkpsuuuvqiTLNV_irttsqoifeefhigefmqrsuwz{{yxxz|~}|}|{} |zwz}|urrv||}~|vtqqrstttstvvvwwz||}~ywuvx{~yrpquxyyyyx{{uty}}~}|~}}}}~~~~~~}}}}}}}|||||||}}|~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}}}{||||||}}}}~~~~~~~~~~}|}|||||||{z{{{{{{{{{{{||xpnqplmrssw{ztqqqooq{yvw{|}}~}|{yxxxvvuwy|~z}ztpqtuuvttpka\[bhmqrpnmnnkigeccccgntttuxyzywutwz}}}~ {y{|~|yxtorv{~{urqqrstttstvvvvvyz|}zwwwx{~xrpquxyyyyx{~ysuz}}}|}}}}}}~~~~~~~~~~~~}}}}}}}}}}~~~~}}~~~~~~~~~~~~~~~}}}||||||||}}}}}}}}~~}}||||{|}}||||||||{{{{|}|wohhklosqory{uprtsqu||wx||||~~||{zwvuttvww{{{}zwvvututrnifbchopoljgfimqpmhe_Z[dpvxxxxxvsnmprw|~~} ~z{|}~~}vqpuz}~~ }yurpqssssstvvvuuuvx{}|wvvx}}vqqrwxxxwxx{zttz~~}~}~~||}}~~~~~~~~~~~~}}}}}}}}}}}}~~}}~~~~~~~~~~~~~~~}}}||||||||}}}}}}}}~~}}||||{|}}||||||||}}}}|}}xpkkjkmpmmpv|ytsvvtw|~|yz||||{|}|zywvtstuvvx||}~~|yxwvwwwtoljhkopolgb``chnpnjgc_`hrvxxxxurmhhnsvz~~~~~~~}|~~ztqotwz{|~} |xtqpqssssstvvutttuwz||wvvx}~{uposwxxxwwy{~~xtvz|}~{y{||||}}~~~~}}}}}}~~}}}}}}}}}}~~~~~~~~~~~~~~~~}~~~~~~}}}|}}||||}}}}}}}}~~~~}}}}}}{{{||||||||||||||||}zrljklmlghks{zxvuuvy}~}{{|~|z|{{{zzwvvsrruux|}}}|{yxvvwtpnlnoqrpkd]XX]cjomidb^_fptuvxxtnhdfovwxyyz{~~~~}~}{sopqqty}|wtrqrrrrrstvvutsstwy|{wuvy|ztppswyxxxwy{~|urvx{{vy{{{{{||~~~~}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~~~~~}~~~~~~}}}|}}||||}}}}}}}}~~~~}}}}||{{{||||||||||||||||}zrljlllgdfjqy}zywxxz}}|||{||z{zzz{zyxvussstty}}}|{yyyyyvnjmoqqqokcZVW_fhecbaaacgnqrtuurlfadkruwzz{}{xxz} ~}~zwvy|~{wrpoqrrrrstvvutsstw{~zusuy|~ytppswyxxwwy|~zvtux{vuy{{{{{zz}}~~}}}}||}}}}}}}}||}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}{{||}}}}}}~~}~~~}}||||zz{{|||}}}||||~~}}~~~{rkhkmkedhnr{~{zzzz|}|{{||||{zzyzzzzywtsrrsv~~}|||yxyyxuoklopppmib[XZdc^VXY\^cgjmpqrstqkgcciouwz{|}|zwvy~~}~}~~~zupoopqqssstvvusssrv{~{vsuz}|wrnpsyyyyvvy{~ystw}}vrtz{{{zzz}}}}}}}}}||}}}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}||||}}}}}}}}}}||||{{{{|||}}}}}}}~~}}~~}skijkieflsv|~|}}z|}|{{||||{{zz{{zyywsqqrsv{|vuvwxupmljjlmnkf__iok[KNNOWagjkloqrqoiebcimrvz{|}}~~}}~~~~~}ytpoopqqssstuuussstx|ytsuy||wqpqtwwwwvvy{|wstyyrrw{}{{zzz}}}}}}}}||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}|||||}}}}}}}}}}}||||||||||}}}}}}}}~~~~~}ukhhijgipuy|~~}~}||{{{{|}}{{{zz{||zxrpnorsx~}wrstvwtpiecfjkkhdhjlh\RKHHPZefbagopokgc``cgmv{|{{} ~}~~}}}~~~~~~~~|wsonoppqssstuuussstw}~xttvy{{vopruwwvvvvy{{wsw~zurrw{{zyz{|}}}}}}}}||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}|||||}}}}}}}}}}}||||||||||}}}}~~~~~~~~|pjjkkoquy}~}||}|{{{}~||{{{yzzwtpnmnqqtz~xrqqtvsohcdghg^_cjlmhaWQNMOYfaWW`imnkfb_]_bmu|}{{} ||}~~~~~~~~~~|xspmlmpqssstuuussstw}~wstvy{ztopsvwwvvvvy{{wu|~{wurrw{{zyz{~||||||||||||}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}||||||||||||}}}}}}{{}}}}~~}}~~~~}}~~vpnmovxz|~}{|}~~}{{|}}}}|{{zyvtrqmjkoqqu{|zuqnorrqoheehlh]UXckig_WTVY^bi`WPYekjga_^\`dmv||{| }}}~~~~~~~~~}{wsollmprrrssttssrrvy~|vssuz{xsopruwvuuuvx{yw{zvvrotyzzzzz}||||||||||||}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}||||||||||||}}||}}~~}}}}~~~~~~~~}}~}xvvtux{}~}|}~ }}}}}}}|{zyxvsqnmlkmopqtwwxvuqpnnppqogbempm`VWYacd^YZ``_`d^ZPS`hcUSV]]`dnty~~|{}~ }}}~~~}}}}~~}{wsolkloqrrssttssrrvz{vssuyzvqnoruvvutuvxzw{{xxzxx|~||||||||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}{{||||}}||||||||}}}}}}}}~~~~~~~~}||~~zzz|~~}{|~}}|~}||zyxvspnnonklorsrrqpqtrnkmopogcfkigdYQV\bca`bc_\XYVRQU^fcSJNY\`fnsx~~}}|}|~}{}~}||||}~~{yvsojklopqrtttrssqrvzzsrsuyyupnpruuttstvxxz~~||||||||||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}||||||||||||||}}}}}}}}~~~~~~~~~~~z}~}|~ ~~}||zyxvromkmmkjnprrttqnoqqlklnmiegic]Z[UW[a`^\_fga\XTOTX]baWMOX_ekrvz}}~~}~ ~~~~}~}||||}~}zxvsojjknoqrtttrssrtx|~ysrsuwwtonpruuttsuvvz ||}}||||}}}}~~~~~~}}~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}{{||{{{{zz||||}}}}}}}}~~~~~~~~~~~~ {z~~}~ ~~|{zxwtrnjklmjjlorrturmonjinoonlhiicYY\Z]aa^TOZiojbZXZ__`de[PT\dkqvz|}}~~~~ ~}~~}|||||~|{xurniijnpqrtttrrrruy}~yrqsuwvqnnpsutsstsvy }}}}||||}}}}~~~~~~}}}}}}}}}}~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~||{{{{}}||||}}}}}}}}~~~~~~~~~~ zz~}}~~ }|zxvsqmjhjjkkkoqrtsqnmklprqppljjlf^[]abc`VMUalojb]^enmhig`Z]ckrwyz|}}}} ~~~}|||||~~{zwurniijnpqrtttrrrsvz~}wrqsuvtpmnpsutsrstv| ~}}}}}}|||||}}}~~~~~~}}}}}}}}}}~~}}}}~~~~~~~~~~~~~~~~}}~~~~}}||{{|{{||||||||}~~}}~~~~~~~~~~~ }yy||{|yyy|}} ~~|{wqnmighjnonnqrttsomnrrppopljlke]]aefaYNS]gnnjgdbfooiiiaY[dnuxzz|}}}}~ }~}||{{{~~|zywvspljknoqrtttrrrsw||uqqsuvromnrsutsssvzzxz{|y{}}}}|||||}}}~~~~~~}}}}}}}}}}}}||||~~~~~~~~~~~~~~~~~~~~~~}}||{{|{{||||||||}~~~~~~~~~~~~~~|{}|zyxwwwwy{|| ~}}{xqnmkiikoqsrsssstsrssqrrrppool_Y^ehe]UT^gloligecipkcbfc\]epvy{z|}}}}~~~~~~~}||{{{~~}{yxvvspljknoqrtttrrrty|zuqqsuupmlnrsttsrry}{vqrtwz{y|~~||}}}}||~~~~~~~~~~~~~~}}}}||||}}}}~~~~~~~~~~~~~~~~~~~~~}}}||||||||}}}}}}~~~~~~~~~~~~~~ ~|~|xxxxvvuw{|| }|||zrnmlllmprvvvtttttuvtqrttrrqpi`[bkne[V\ejmnkgddgmmidegdbekruz|}}~~~~~}||{|~ ~~}{zzz{}~}{zxxwvtqnllmoqsssrrrruz}yspqrttplloqtssrqu|}vsqomorxyz|}{}}}}}}}}~~~~~~~~~~~~~~}}}}||||}}}}~~~~~~~~~~~~~~~~~~~~}}}}||||||||}}}}}}~~~~~~~~~~~~~~ }{|zxxxxvvuvz|}~ }|}|zrnmopoprtxxxwvvttvtsrsuusrqqj`_gnph__ejmnmjfefikkhiihffjmquz|}}~~~~~||{z|~ ~~|{zz{}~~~|zxxwvuqnlmnpqsssrrrrvz~~wrpqrtsommpstssrtz}wsponmlmsyz{~~{z~~~~}}|~~~~~~~~~~~~~~~}}}}||||}}}}}}}}}}~~~~~~~~~~~~}}}}}}}}||}}}}}}}}~~~~~~~~~~~ ~|z{yxxxxvvuvxy{|}}z{|yuomprtuvx{{{ywuttvurruuttrqrngekoolhilopnhgfeghffimlihkmprvz||}~}~~}}{{z{}~~~}~}{{{{|~}|{zxxwvupnmmoqqrssrrruy}~wrpqstqnlmpsssrsy~xsrponnmloswy{~{zy~~~~~}|~~~~~~~~~~~~~~~}}}}||||}}}}}}}}}}}}}}~~~~~~~~~~~~}}}}}}}}||}}}}}}}}~~~~~~~~~~ |ywxxwwxxvwwwtnlmx }zyzzxroqsuwz}~~}|yxvvvuvvssqppoqokkoppmmnqqogaacbaaadjmmklnpqux{||}|}}}zzzzyz}~~~}}~}{{{|}|{{zxxwvuqnmnpqqrssrrruz}{tqpqstpllmpssssv|yrppooonkjmqwz{}}zxx~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}~~~}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~~~~ }ywwvvvwyyyxxqmjjr~y{{{ywtrtwz|~yxwvvutsssponloqpprrommprqmd__aba``glonmnpprvy|}}}}}|{{{zz{{}~~~~}~ ~}{zz{||{zzzxxwvuromnqqrssrqqruz}~ysqpqqqnkkmqssuv|spoooomlkjnuwy|~yxww~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~{ywwvvvwxyxwurnmmp| ~|zxuvwz|~|{zxvvutsqnmnnpstuutpoprsrngcccdcdhloplloqrtwz|~}}}}|{{{zz{{}~~~~}~ }~|zzz{||{zzzxxwvuromnqqrssrqqsvz}~~|wtqpqqqnkloqssw{wponnnnmlkjnuyz}~yxww~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~}}}}}}}}}}}}}}}}|~~~~~~~~~}}~~~~ }} {ywwvvuwxxutttrqmn{ ~|zxxxy{|~~{yyyxvuutrooqsttvvwvtrqruuqjedeeefhnponoprsuz|}}}}|||{zzzzz{}~~}~~~~~~~{zxx{||{zzyyxwwtqonnpqssrqpqty|}|||vrppppomllortv{ ~qnlnommlkkkntxz{yxvv~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~}}}}}}}}}}}}}}}}|~~~~~~~~~}}~~~~ }|~}ywwvvwvvurrry{vtu} ~{zzzzz{{|}~}|{zyxwvwvtsstuwwwxzzywstvvsokihfegipqppqrtuw{|}}}}|||{zzzzz{}~~}~~~}~|zz{||{zzyyxwutqonnpqssrqpqty|}||ytpopponmlmpsux}~vllmmnmmlkkkovz}~zxvvv~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~}}}}}}||}}~~~~~~}}~~~~ }|~~}ywwvxwvuropsy|{}~ ~}|{zzzz|||}~~~~|{zzyxxvvvvvwwyy{}}|xvvwwupnkihjlrqrrtwwx{||}}}}}|{z{zzzz{|~~~}}{zzz}{{z{{zxvtttsrpopqrrrrorw|}}|zuroooonmlmnprw|zollmmmlkkjilrx{}yuutv~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~}}}}}}||}}~~~~~~~~}}~~ {{~ ~}|xxxxxvsspmosw}~|{{{|||||}~~~~|{zzyxyyxxwxxxz{|}}|zyvwwwurqomnosttvwzzy|||}}}}}|{z{zz{z{|~~~~~}{xy{}}{|||{{{zxvtsssrpopqrrrrrux|}|zwrpoooonmlmnptz{rmllmmmlkkjjmsy|~{vuutv~~~~~~~~~~||~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~}}}}}}}}}}}}}}}}~~~~~~~~}}~~ ~||}yyzzyxurpmmrw|}{{~~~|}{zyzyzyzxxwwxy{}~~~}{xwvvyywutrtvuwxz|||}~~}}}}}|||{zzz{{}~}}||{xuuy~}{{zzzzxvtrrssqpprrrqqrvz|{{ytqoooooomkmoru{yqmkllmlkkjiinuy}|xvvvvv~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~}}}}}}}}}}}}~~}}~~~~~~~~~~~ }|}}~|y{{|zxuqonqv{}~{x}~|{zzyyxyyzyxwwxz{}~~~}{|z{zz{zyxxxyz|}}}}z|||}}}||||||{||{|~~~||}|zxz}}{zzzzxvtrrssqpprrrqqsx{}zywsonoooomlkkns}|tnkkllmlkkjiintx|{wuuuuu~~~~~~~~~~~~}}}}~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||}}~~~~~~~~~~~~~~~~ ||}}~~zz}}{zxuqorvz{|{|~|{zzywwyy{{yxwx|}~~~~}}}}}||}|{z{~|z{yyz|}}||}}}}}}}}~~~~~~~}}{{|{|~zyyyxxwusqrtrqqsrrssvz||zxtrpnnnommkikov}yrmklmmmkkkjjkou{}xuttttt~~~~~~~~~~~~}}}}~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~~||}~ ~}}ywx|}|zwsqrtwxx{} ~|{zzyyyyy{{zyxxx{}~~~~~~~~~}}}||}}{yxyyzz|}}||}}}}}}}}~~~~~~}}{{|}}}{zyyyxxwusqrtrqqsrrstw{||zwsqpnnnommkkmsz}vqlklmmmkkkjjlpvz|vtsssss~~~~~~~~}}~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||}}}}~~~~~~~~~~~~~~ ~|}}~|{~{vsty~{yvsrquyz}}}{{{{zzxxxz{{xwwy}~~~~}}}}~{xxz{}~|~}|||}}}}}~}}~~~~~}}{|{{}~}|yzzxxywusqstrqqsrssu{{||zvroooooomlkjnxzsnljkllljjjiimrv|{vsrrrrr~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||}}}}~~~~~~~~~~~~~~ ~||~}~ ~~}~|wuvy~}{xurruz{}~}~~}}||{{zzzzz{{{xwvw{~~~~}}}}}||yxy{}~}|||}}}}}}}}~~~~}}|{|~|yzzxxywurpqrrqqsrstx{{{{xuqoooooomlknt{~xpmkjkllljjjjjmry~~yurrrrrr~~~~~~~~~~~~~~~~~~~~}}}{~~~~}}}}}}}~~~}}}}}}}{||||||||||}}~~~~~~~~~~~|~{||~~~~~~|~}xvwy}~}{wuvy{y{}{xz~~~~~~~~~~~~~~~~|{{z{zx{|{zyxxwy|}}~~}}~{zz|~}}|~}}}}}}}~~~~~~}~|{zzyxxwuqooprrrrrsw{||zyvspooooonlkknw~|upllkkllmkkkijptz~{xusrqqqs~~~~~~~~~~~~~~~~~~~~}}}{}}}}}}}}}}}~~~}}}}}}}{||||||||||}}~~~~~~~~~~~~~~~ ||~}~~ ~|~}usu{|~|yyz|{xyyywx~~~~~~~~~~~~~~~~~~}}}}|{y|}}|zyxwxz|~~}||}}~|{zz~~}}}}}}}}~~~~~~~~}|~~|{zzyxxwuqooprrrrsuy{|{ywtrpooooonlknr{~yspllllmmmkkkjkpv}zvsrqqqsu~~~~~~~~~~~~~~~~~~~~~~~~~~||~~~~~~~~~~~~~~}}}}}}||||||||||||}}}}~~~~~~~~~~ |||~} ~z}|vsx{}}~}}}}zxxxxy}~~~~~~~~~~~~~~~~~~~~}{|}}||zzzzyy{}~}}~~|zz ~~}~~~~~~~~~}~~|~|zyzzyxvtonoprrrstw|||zvvsqpooooonmlnv|uqomllmmmmkjjjnqw|ytsrrpqvy~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~}}}}}}||||||||||||}}}}~~~~~~~~~~~ {{~ y|~zvx|~|}zyxyz{|~~~~~~~~~~~~~~~~}}|}~~}~}|{z{{z{{}~}}}}}~~}{{ ~~}~~~~~~~~~}}}~ }|zzzyxvtonoprrrsw{}}{yussqpooooomknrzxsqomllmmmmkjjkosz~|wsrrrrtx{~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}||{{||||||||}}}}~~~~~~~~~~~~} }| xx~yy||z|~~zwxyz{}}}}}}~~~~~~~~~~~~~||||~}}~~||||||z{||}|||{}~}}||} ~~~~}}~~~~}||||zyxwtpopqqrrvy|~|{xurrqonnnmmklov~}urpnllmmmmmmiimqx}{vsrrtvx|~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}||||||||||||}}}}}}~~~~~~~~~~~} | || uv}{{}}zy~~~|zxyz{}}~~||}}}}}}}}~~~~~}}}}~}}~~~~}}||{{{{{{{{z|||}||}}|}~~~}}~~~~} |yywtpopqrrtx{}~{ywtrqppnnnmmklqz~ztromllmmmmmmiimry~yutssvy}~~~~~~~~~}}}}~~~~||}}}}}}}}||||||||||||}}}}}}~~}}~~~~~~~~~~ ~zz}~~usy}{~|{z{{zyxz{}}~~|z||||||}}~~~~~~}}}~~~~~}}}~}|||||z|{{zz{{{{}~}|}~~}~~~ ~{xwspooqqswy}}|ywusrpooooonmlms|~xsqolllllmmmmkjns{}yvtrsw|~~~~~~~~~}}}}~~~~~~}}}}}}}}||||||||||||}}}}}}~~}}~~~~~~~~~~ ~ }z{}}}~usy~~{z{~~|{{zz{}}}~~}||||||||~~}}~~~~}}|}~~~~~~}~}}}|}}}}|{zzzz{{}~~|}~}|| |wtqpqrsuz|~}{xvsrpqonnmmmklov~~xqomlllllmmmmjjou|}wttsv{~~}~~}}~~~~~~~~~~}}}}}}||||||||||}}}}~}}~~~~~~}}}}}}~~~ {{{|~}svy~|z}~~}}~}}{z{{|}|zz||}}~~~~~~~~}~~~~~~~~}}}}}~~}|||~~|{{yzz{{|~~~~}}} ysrrsqty}~}{zvsrqqppnnmkkjkqz}wqonlmmmmlllkkkou~{trtux|~}~~}}}}~~~~~~~~~~}}}}}}||||||||||}}}}~~~~~~~~~}}}}}}~~~ }z{|tqu|~|z{}~~|{z|}~|{{|}{zz||||}}~~~~~~}~~~~~~~~}~~~~~}||||}~~}|{{{{|~}}}~}}}~yusqqv{~|ywsrpqqppnnmkkjmt|}wponlmmmmlllkkkrxzutvy{}~~~~~~}}}}~~~~~~~||||||||||||||||||}~~~~~~~}}}}}|}}}}~ ~z{} xxy~}{}}}{zxxx|~}}}~{zzz{{{{}~~~~~~~~~~~~~~~}}~~~|~}~~~|||}}|~~}{z~|}~ xtrrz}{yvqppqqpponnllknw~woommmmmmmmljimsz~ytuxz~~{~~~~~}}}}~~~~~~~||||||||||||||||||}~~~~}}}}}|}}}}~}xy}~~~~}}}{zxvtuy|} |{zz{{{{{}~~~~}}}}~~~~~~}}||}~~~}~}}|}}~||||}~|z|~|{}~ ||{z{~zwtpppqqpponnljinw{tonlmmmmmmmljinuz~}xux|{x~~~~~~~~}}}}~~}}}|||||||||||||{{|||~~~~}}}}}||}}~~} ~|} ~~}~~}|}|{ywtttvx~{zzzz{||}~~~~~~~~~~~~~~}{{y||}~~~~~}}~~|{{{|~~}}|~~~{|~~ } }{|zyvrooqqqooonmkhjpz|unnlmmmnnnmkikow}{vvy}~wv~~~~~~}}}}}}}}||||||||||||||}}|||~~~~}||}}}}}}~~ }}}~~~|{{{yvursrtw|{{{||||}~~~~~~~~~~~~~}|{|}~~~~~~~|{zz|~~~}|||}}}~}~{|~ ~zyyyxyxwsqoqqqooonmkklt}{rmmlmmmnnnmjhkrx}{xy}~xtu~~~~~~}}}}}}|}||||||||||||||||||~~}} }|||}~ ~|~~~~}{zzzwvtsqqqtx| }{|}|}}~~~~~~~~~~~~~}}}~~~~~~~}}{xz{|}~~|{|}{{|}||~ }{vs||tv}{tqpqpomnmmkjmszqmmlmmmnnmliiltz}~zy{~}tsu~~~~~~}}}}}}|}||||||||||||||||||~~}} ||}~~ ~|}~}~}|zyxxuutrsrqsv{|{||}}~~~~~~~~~~~~~~~~~}}}}||}~~}}{yy}~}{|}~}~}}~~~}||~ ~vqq|}wy|wsqqpomnmmkinwxpmmlmmmnnmliinu{~~||~~xssw~~}}~~}}}{||||||||||||||||}}}}~~~~~ }|~ }} ~|||}}|zwvxvtuusssrsvx||{|}}}~~~~~~~~}}~}}}}|}|{zxz{|}{zz|~{||{|~~~}{} }uos}~~vv|zsrpppnmkkjpy~wpmmllkmmmkjhinv|~}zqru|~~}}~~}}}{||||||||||||||||}}}}~~~~~ ~ ~z} }{}}}}|zzyvtsuvutttuvxz|~}}~}~~~~~~~}}}~}||{|{{{{|{yzz}~}{zz|~{|||~~~~~}~ vqqy}|z~|||~|yspppnmkkkqz{uommllkmmmmlgipx}~~~}vqty}~~~~~~~~}}||||||||||||}}}}}}}}}}~~~~~~ ~|}~~~}{{zzxuvvwwvxuvyy{{} ~}~~~~~~~~}~|{zz{z{{}~~~}|{|}~{xz}|z{|}~~~~}|~ }tuy|~vpppnmljkr}|rlmmllllmmljhkty~}vrtx|}~~~~~~}}||||||||||||}}}}}}}}}}~~~~~~~ ~~~| }|{{|||yxvuuvxz{{{||}}~~~~~}{zyyzz{|~~}~~{xz}|yy{|~ ~~~~~}|} ||~~uppnmljkr}|tnmmllllmmlihltz~zutvz{}~~~~~~~~~~~~}}}}|}{{||||||||}}}}}}}}~~~~~~~~} ~~~~}|{zz|}}zxuttuyz||~}~ ~~~~~~~|{yyxy{}}~zw{}~zwyy{~}}~~||}~ }|} zroonkkms~}uollllkmmmmjjmw}~}~~wtuxy{~~~~~~~~~~~~~~~~}}|}}||||||||}}}}}}}}}}~~~~~~} ~ }zxxxz||{xusrsvz||~~~~~~}|{zzyz|}}~~ ~~|{}~~zwyy{~}}}}~~}||} }uponkkmv~}tnllllkmmmmjiow}~}~ysuvxy{~~~~~~~~~}~~~~~~~~~~}|||||||||||}}~~~~}}}}~~~~~~~~~~~~~ {wvuuvy{{{wusssuy{}~~| ~~}}~}||{{{|}~~~ ~~||}}xvwy|~}|||~~}}{{} zspnkjnx|umllkkmmnljijqy|~}}~}xuxyyy{~~~~~~~~~}~~~~~~~~~~}|||||||||||}}~~~~}}}}~~~~~~~~ ~~~~}~{xrooruy{{{yvsssuy{}}}~|{| ~~~}}|||{{||}}~~|}~yxyy|~~~}}~~}}||} {wtqpsz|slllkkmmlkjhks{}~}}~zwvyzyy{~~~~~~~~~~~~~~~~~~~~~}}}|||||||||||}}}}}}~~~~~~}}~~~~ ~~~~~}| |}|xqnoty|}}zzvwvwyz||}|{|} ~}}}}||{{{{} ~}}}|}|zwxx{~~~~~~|}~ }{yy~~{rnkkklmmlkjimv|~~~{wuxzzxxz~~~~~~~~~~~~~~~~~~~~~}}}|||||||||||}}}}}}~~~~~~}}~~~~~~~~~~~|~xrorvz~~~~|||||}~~}|{z|}~ ~~}}}||}}}~ ~||}}|zwxz}~~~} ~ ~{rnkkklmmlkihmw}~|}{wwyyywwz~}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}||||~~~~~~~~~~~~}}~~~~~~~~~~~~~~~}}}}}~~|}}vpqvy|}~~~}~~}|{{||{| ~}||{{{||} ~||}}|zxy{~~~~|} ~ yrnkkklnnlkgipx~}}}zwyyyxvvz~}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}~~}}~~~~~~~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~}}}}}~~~{~}usux{|~~~~~}|z{{||{|}~}|{|{{{{{~}zz|}~||zxyz}}}~~~~ ~ yqlkkkllllkikqy}~}}|yxyyxwvvz~}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}~~}}~~~~}~~~~~}}}}}}~~~~}}~~~~~~}~~~~~~~~~~}}}}}}}}}}~}z}zsrvyz}~~~}|{{{{{zzz|~}~}|z{{{z{{| ~|zyz|~~|z{yzzz{}~~|} yux|}~ yqmkkllllkjilt{}}}|yyzzxvvvy}~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}~~}}~~~~}}~~~~}}}}}}~~~~}}~~~~~~}~~~~~~~~~~}}}}}}}}}||~{z}xtuxy{}{|}}~}||{{{zzz|~}~~}|{zzz{|}~}zyz|~}}{|{zyy}~ }tnpx}~~}~~ }tnllllllkjimu|}|yy|xtty}~~~~~}}}~~~~~~~~~~|}}}}}}~~~~||}|}}}}}}}}~~}}}}}}|}~~}}}}}}}}}}~~~~}}~~~~~~~~~~~~~~||}}||||}}}~~xy~~ww{yy}zy||}~~}}}||{{zyz||~}|{zz{z~|zy}~~~~~}|zy{~~zrnntz~~}}~~ ysonmmmmkihnw~~}{yy}ytty~~~~~~}}}~~~~~~~~~~~~|}}}}}}~~~~~~}|}}}}}}}}~~}}}}}}{}}}}}}}}}}}}}~~~~}}~~~~~~}}~~~~~~~~~~}}||||}}}~~|xz~~~{z|~|z|}~~~}}}||{{zyz|}~}|||{~ }zy|}||~~~~~ ~usu{{}}y{~~~zsponmmkiipx~~}~zzz~ytty~~}}}}~~~~~~~~~~~~~~|}}}}}}~~~~~~~~~~~~}}}}~~~~}}~~}}}}}}}}}}}}|||~~~~~~~}}~~~~}}}}}}}}~~}}}}||}}~{xz~ ~{}}}~~}}|||||{zy{~~~~}}}|~ }{||}}~~~~~ }sw}~~||~zzz}~~}~|vronmjhhqz~}zwxz{wttx|~}}}}~~~~~~~~~~~~~~|}}}}}}~~~~~~~~~~~~}}}}~~~~}}~~}}}}}}}}}}}}|||~~~~~~~}}}}}}~~~~}}||}}~~}}}}}}}}}}|xx}|~}~~}||||{{{|~~}~~}}}|~~ |||}}~~}}~ uw|~~yy~zyyyyz{~}}~xtqnkglv{}{x{}wustx|~~~~~~~~}}~~~~~~}}}}}}}}~~~~~~~~~~~~}}}}}}}}~~}}~~}}}}}}~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}~}|xz}~ ~~~~}}{zyz~~~~~}|}}}}} }|}}~~}~||yw{{{xz zwvwyyyyy|~}{}~ }|~ztolkmu}}zx|xrqssw|~~~~~~~~}}~~~~~~}}}}}}}}~~~~~~~~~~~~}}}}}}}}~~}}~~}}}}}}}}}}~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}zy{}|{}~~~~}}{z|}}|{|||{}}}|| ~~}}~~|} }wzzwu{|vuvwvvvvuqqsz~{~{umjpx~|y{}vttspty}}~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}~~}}}}}}}}~~~~~~~~~~~~~~}}}}}}}|}}}}}}}}}}}}}}}}}}~zxy}}{|}~~~~~~}|{{~~~~{zy{||~~~~~~ ~~~|}~~~ywtrrz}vtttutttuokrz ~~~zqnty}||y}ysrrqoty~~~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~}}}}}~~}}}}}}}}~~~~~~~~~~~~~~}}}}}}}|}}}}}}}}}}}}}}}}|||{vx|~}|}~~~~}}~~~||}}~~~~}{||{|{}~~~~~~ ~~ ~y{|~~{uqqryzutttuuvtnlt|~~ ~|vsuz}~||{vsssnnuz~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}}||}}}}}}~~~~~~~~~~~~~~}}~~}}}}}}}}}}}}||||||}yxz|}||~}}~~~|}~}}~~}}|z{zz{||~~~~~}~~ {wy|}~zposz{xuttuuttojq{~~~~ |y~zuw{~~}~{vssurlksx~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}}}}}}}}~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}~~||||||}}|~~{zz{~}{{|~~~~~}~~}}~}}|{{{zz|~~~}~~ ~ ~sprz{wtttuuuqkr{}~~~~} yty}|xy|~~|vssssqkmv{~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~}zzzxyz|~~zvy}{z{|}~}~~|}}~{z{~~|||}~|}~~~~~~ xssz{wuutturlly~~~~~~~ zpsx}}zz|~~|xusqrrmjmw}~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~}zyywwxy|~}zz|{z|}}}||~~~~{{{zz{|||||~~}}|}~~~~~~ ~ }wuyyvtttttojr}~~~~~~~ {qpt{|{{}~|xtqqqqrqsx}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~}}}}~~~~}}~~~~~~}~~}yxxvwwxy|zx||y{{zzy}~}}zzxyzz{{||}}}|}~}|||}~~~~~~ ~ zxyyustutrjlx}~~~~~~~|sqrx}}|}~|xxxwustyxz~~~~~~~~}}~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}~~~~}}~~~~~~}~~}yxxusstvx{|~}||}}zxxz|~~~|||{zyxxz{||}~~}}}~~}||||}~~~~~}~~~~ ~ ywyxtsssrnirz}~~~~~~~{qpquz~~}|}~|xwy}~~ ~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|zzvtqrrvwy}~~||{zz}~~~||{xxxy{{{{|}}~}}~~~~}|||}|||}~~||}}}}}}~~~~~~~~}~~}~xvxwusssqkku}~~~~~~~~}~~uoquw|}}}{xuw{~ ~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{zzwtqqqrvy||~|{z|~}}}}{yxwwx{{{{||~~}}}~~}~}||{|{{{|||||||}}||}~~~~~~~~~~ ~~~}~~|}ytrxwvtssqjnz~~~~~~~~~~ ~vrswy|}}}~zxyz ~~}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}~~~~~~~~~~~~~~~~~~}}}}}~~~~~~~~~~~~~~|xvusrqqsv{}~~|~~{{|~}zyvwyyxxyz{{{||}~~~~|}}~~~~}{{{{{{zz{{{{||||{|}}}}~~||}}~~~ ~}}{xuuuwwuusojoz~~~~~~~~~ ~yy{|~{y|~~~}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}~~~~~~~~~~~~~~|xvttsrrtx{|{{z{{|~~}|yxwwwxzyyyz{{{||}~~|||~~~~}{{{{{{zz{{{{{{{{{{||}}}}}}}}}}} ~{vvuutvvvutpkqz}~~~~~~~~ ~{{~ ~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~}}~~~~~~~~}~~~~~~~~~~~~~~~~|zwwwtsstz|||z{{||}|{}~zxvwxyzyzzyz{}}||}}~~}}}}}||zzzzzzzzzz{{{{{{{{{{{{{{{|||~ ~~~~ }{|}~~}{xuutuvwwvsqnqz}~}} }|} ~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~~~~~~~~~~~~}~~~~~~~~~~~~~~~~|zwxwuttvy|||z{{|||~~}{yvwxyzzyxyz{{|}}}}~}}|{{{{zzzzzzzzzz{{{{{{{{{{{{{{{|||~ ~~~~~~~}~}}}}{yutttuvvsolnx ~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~|{xyyxvwy|||||||||{|~}||zwyzzyyxyyz{|~}}}}~}~}}{{{{{||zzzzzzzz{{{{{{||||||||||||}~~}}}~~~~~~~~~zwtsttopoov ~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~|{xxxxxy{{||||||}|~zxxyyzzz{{{|zzz{}}{{{{{{|}}}{{{||||{{zzzzzz{{{{{{||||||||||||}}~}|}}~~~~~~ |zwusvwz~ ~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~}}}}~~~~~~~~~~~}~~~~}}~~~~~~~~~~~~~~~~~~~}~~}}~~{zyxxwyz{{||{||||}}yttvw{{{|}~}|zzzz|{|zz{||}~}}{{z{{{||{{{{z{{{{{{{|}|||}}}~~}}}}}~~}}}}}}~~~~~~~~~~~~}}~ ~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}~~~~~~~~~~~~}~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~}|zyyyyy{{||{||{}{vttw{||~~~}{zzzz{yzz{z||{}}}|{z{{{||{{{{z{{{||||}}|||}}}}}~~}}}~~}}}}}}}~~~~~~~~~~~~~~ }|z~~ ~~~~}}}}}~~~~~}~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}~}{yyzyyyzz|||{{|~}zwuuvz|}{}}|{xxxyyzxy{{||||||zzz{{{||{{{{{||}}}}}}}||}}}}~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~~~ ~{zzyy| ~~~~}}}}}~~~~~~~~~}~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}~}{yyzyyzzz{{{{|~~|zwuuux{{{{{{zxxxyyzz{yyzz{{{{zzz{{{||{{{{{||}}}}}}}|}}}}}~~~~~~~~~~~~}}}}}}||~~~~~~~~~~ }{| ~~~~}}~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|z{{zzzzz{{zz|~{zz|zwuvvvy|||}|yyvvwxzz{zzzz{{{{{z{{zz||||{{{|}~}}~~}}}}}}~~~~~~~~~}}}}~~~~}}}}}}~~~~~~ ~ ~~~~}}~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~|z{{|{zzz{||~||{{yxvqrv|}|||{yywwwwzz|{zzz{{{{{{zyzz{{||{{{|{|||~~~~}}}}}}~~~~~~~~~~~~~~~~~}}}}}}~~~~~~ }}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~}}~}|{{~}|{z{{~|zyzzywvtqw|~||zxwywwuwxy{{zzy{{{{{{{zz{zz{{}}}~|{}}}}}}}}}}~~~~~~~~~~~~~~}}}}}}}}}}~~~~~ ~~~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}~~}|{{{z||{||z{{zyyyxvqpv|}{yvvwyxxuuuxzzzzyzz{{{{{zz{{{{{{{|}|{||||||}}~~~~~~~~~~~~~~~~}}}}}}}}}}~~~~~~~~~~ }}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|~~|||{{{{{z} }|}|{zxwspotx{zyvvvwwxyyxwxxyzzy{{{{{{{||}}}|||}}}}}}||}}}}~~~~~~~~~~~~~~~|}}|}}}~~}}}}}}||~~~~~~}} ~~~ ~~}}}}}}~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|~}{{{{|}~||~|ywuqnnrvyyyvvvuwy{{{ywwyzzy{{{{{{{||}}}|||{{{{||}}~~}}~~~~~~~~~~~~~~~}}|}}}||}}}}}}~~~~~~~~ }~~~ }}}}}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~||}~~~~~~~~~~~~~~~~~~}}~~~~~~~~~}|{z{z}}{||~}yxwtrpquyyxwuuwy{}}||zyyyzxzzz||{|}}}}||||{{{{zz{{}}}}|~~~}}}}}}~~~~~}}}}}}}}|}}}}}}}~~~~~~~ ~~~~~}}~~~~ }}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}~~~~~~~~~~~~~~~~~~~~~~~~~~~|{z{~~}|{||~~zyxusppsuxxwwvwy{|}||z{yyywxyyzz{|}}}}{{{|||||{{{{{{{{{|||}}}}}}~~~~}}}}}}}}|}}}}}}}}}~~~~~ ~}~~}}}}~~~~ }}}}}}~~~~~~~~~~~~~~~~~~~~~~~}~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~|{{~|{{{|}~}ywwsooqsuwxvwxxyy||{||zyyxwwxyyyz{{}}{{z|{{{{zzzzzzyyzz{{{|||}}~~~~~~~}}}}}}}}}}}}}}}||}}~~~~ }}~~}}|}}~ }}}}}}~~~~~~~~~~~~~~~~~~~~~~~}~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~|{|{{{|}~~zyxtpopoqsuvyzzyy{{{||zzzxwwxyyyz{{|||||{{{{{zzzyyyzzzz{{{|||}}}}~~~~~~}}}}}}}}}}}}}}~~~~~~~~~~~ }}}}|||}}~ ~~~~~~~~~~~~~~~~~}~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~|}~|{||}|~~{xurqponoqsvwzyzyz}}}}{{zyyyyyzzyz||||||zz{zyyzyxxzzzzzz||||||||}}~~~~~}}}}}||||||}}}}}}~~~~~~~~ ~}{z{{|||~~~~ ~~ ~~~~~~~~~~~~~~}~~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~}~|{{||}|~~{vtrononnprsvyz|zxyy{{{{zzzyyyyyyz||||||||{zyyyxyyyyzz||||||||||{|||}~~~~}}}}||||||}}}}}}~~~~~~~~~~~~~~ ~|{{{|||~~~~ }|~ klmnooonooortuvvwxzzzzwurpqqppppqqrssuxyzzyyyy||}}~}~~}}}}}}}}{{||||||||||||}}~~~ |sjd^[]_ada_][^cehhe``bglsz {qf]YX]bjtz~{urppmifaadgknpquyz|wpmjjjjjjjkknqqtuuuuuvvuwvwyxupf[XTV[^choy~~|xvtomjggcdfgghfhjjmprtttttsrrqpoqutuuz|zvplhbbdca\Z]dknttsrrsuwz~ mooppporssttuvwwxzzzzxvtrpqqppppqqrsuvvxzzzzzz{{||{}~~}~~}}}}}}}}}}||}|~}||||||}}~~~ }wpgc_^^`bb]ZYX[_dghfc`acfmu~ {si_\Z[bhpw}}{wtrpmnifcbbejmppqsv{|zupkihhhhhhiklnortuuuuuvvuwvwyxupg]VUW\^cgpw~~{wtoljihgeegiiijjkjmprttttsrppqpoqsrpsw{||ytqmhdccb`\Z]dlsuusrrsuwz~ moqqssrstutuvwxxy{{{ywusqpppppqqsssstuuwyyy{}}||||}}}~}~~~~||~~~~}}||}}~~~}|}}~~~~~~ }vmg`]^`cda^[XVY]fmojea``emt{ vmfcaadhot{yusqnmnjiihedehpyzwz~}{wrnjhhhjjiiijlnosvwvvvvwwwwwxxyupg]WUVZ^dhpw}|ztrojghhgghhjjjkklnnpstsuuropppoprrqrtxz||wsqkgecca_[]aelsutsstuvxy}nonprrqrtuvwxyzz{|{{yvvsqppppprrssuuuwwxyyy{{{||||}}}~}~~~~~~~~~~}}~~~}|}}~~~~ wnc^__dddb`YTT[epuvneaabfkqw {smgbbeilostpnmllllmmkigglw ynqyzvqkihjiikkjjjkmoqrtvwwwwwwwwwxyzxsj`XTW[`cgnu|~|yurnlihhhhffgkmmmnonllpsvtssroooomoqportxz{yvrlhca`adaacdhnstrqqtuuwznnmnpqstuuuvwx{||{ywvurqqqppqrrttvxxxxxxzz{{y{}}}}}~~~~~~~~}}~~~~~~~~~ ~yohfbeffc^\WUWamv|{rkca`chnu{ }vrjeefikopoomllkknonnjijov}xqqw~|wrnkhhiikklkiiknoqruvxxxxxxxvxxyyyukbZWVZ`chms|~~|xsnjihgheefgijnoooonljkpsssuuroomnppommptxxzxspmida_aebaejnsuurpqtuwwy}nnnopqtuuvwxz{}~}}{zxvutrqqqrrrsuvwxxxxxyyzzzzy{}}}}}}~~~~~~~~~~~~ zslhdcdec`^ZX^iv|~wpic``dkry yrkdbehjlnonlllllppppnorsxzwtonv||vrpkhjlmoopnkijlpptuxyyyxxxxyxyyz{ytlcZWY]_bglsy~}ywsonliggfddefiklnoooolijmpsssssromlnppommruxxuspmkfbabeefimpuxvsqpqtvvtuy jlnopqsuvxxzz}~~}{zxvutssqqqqrrsuuwxxyyyz{{yyz|}}|||}~~~~~~~ ztnhecca`^]\]fr{zukeb^bgms{ |unlgdfjkjiikkkmorttrtuvx{|xqmmppijtusnjjklmoprspkhikmoswxyxxxxyyyyzzz{zunf_ZZ\_bglqwz{}|zwrokigeecbbdegimopqponmljhlortsrsronlnpqnmnqtutrqnnkgdabffkpsvxzwrpqsuvtstxjlnopqsuvyz|~~~~}|{zxvutssqqqqrruvwxxxyyyz{{{{z|}}|||}}~~~~~ ~xrlfb``ba`^_dpy}{vrlda`fkqy ~xqnigikjhiklllnqtvuttxyyyzvonprokjjnniiloqonrttpkhikmoswyzzzyyyyyyzzz{zvnfa^[]_bgjowz{zvsqmiggecbceeefilnoqsponmjhhkpstsrsspnlnpromnoppnlkkkhdcdehkrw{~~{usrstuwtsrvmmopopqwx{}}~}}{yvutsqqqqppqstuvxyzzz{{zzzz{{}}yz|}|}~~}}~~{tnie`\]``^^ckv}|umga`bgpv|}|wqljgklihknmnpruvvwv|~zwwwwxzvkhiinsooqvytmnqrpkiijmprwyzyyyxyyyyzzzzyxohd`^^acfkpsxytrnlhgddb_abdfgimmqqrrpmmnjiilrvuuttspnlmopmkkkkjijhfecbbegkou{~~xtsrtvxwtqpu|mmmooprwy|~}}{yvutsssrrrrsttuvxyzz{zz{{||{{yyz||}}~~~}xqje_]]^__`cir{xqkebagmtz~yrmjgghkjkkmnrtwyzz{ysuz~}vqoieeiosu{{tqrrpkiijlorw{|||zzzz{{{{zzzxrkd`^^acfhlpsspkjfdcbbdedfhikmnnnnppnlkllkjnrvuuttspnlnqpkhgfffededccbbehlrx}}{wsprtvwurqprz llnooruw{~~~~~||{yvvusrrqqpqqsuvvwwzzzzzzzzzzz{{{|}~}~~~~}xrmgc``_```afnx|vnh``chou|ysnjfehjjklpsuxzz|z|}~vqx~yvtolkjijnpx{wqpvwupkghkmprw|}||{{{z||{{|{{xrkda``bcegjnpoigegdbabbcgihjnoonmnoomnnomkkosuuuuuspmmopoicbeea`adcdeeffhmry}|{zuqqssuurqqppxlllnqtvx{{}}~~{{zwvutrrrqqqrstuvwxxzzzzzzz{{zz{zz{|}}~} {vrmhdb_\Z``bekr{~xrlc_bflrz~xtnjfehjjklqty{|{zxvvvvw}|xsqpoonpt{xtsx}}ytlhhklorw|}||||{{zz{{yyxvplgc``adghikjjhbacddcegfgijknoonnoqqpnnonlmqtvvvwvurpoprmgaabb`_`abcccdghmry}|zxtqrssuttsrqqt{kmmoqswxyz{|~~~|zyxwvutrqqrrstuvuvxxwxxx{{z|zz{{{{||}}~~{uplgec`^]]__bgqz{vof`bekqw}xvqjfdfhjkmtx}|}~{vvuv||zyy{vtvstw|wqt|~}xrlhhjloqv|}}}}{|||zywwwvtsojea``cijjgggecacddihijkkklnonmmmpmmprronnruvttvvspnopplf`aa^^__abccehhjnsx|{xvrprstvtqrrqqsy~lnmoruwxyz{|}}{zyxxwuttqqqqqqrssvwxxyyzzyyyzzz||||||}}~~{vojfeec`__`ceinv|~|voga`dhnu{}{uqnhfghkkpvzz~~~~~|xvssqopqruxzxuv}~}xrlhhjnpty|}}}~}}}|zywvutspmidbaadefedggebceggjjkjjjiknonmlllllprrpoosvxuuvvtqnoppkebba^^\_adefijjkosy{zxvrprstvtqooppqwzymmmotuvxxz{{||}{xwwvvusrppqqrsstxxxxyyzzzzzzzz{{{{||~~}}wqlieeecbabefhks{}vohbaaglrz~|wtpmifegklqvz{ ytruvwtrqmknoosuuxzyvw~~xqokiklnqv{}~~~~~|{ywussttqnhebbbeffgggghhggjkjlmjijkknmmllnmnopoqrortvttruvtqmnqokc``ccbaacdefijlmqtxzywtqqruuvsqpppppuutmnoqsttuwxzzzzzywwuutsrqqqqqrrstwwxxyyyzzz{{zz{{{{}}~~}|wplifeeeeefghlqx~zsmhbcfkpw}|ytrpnkhehlpptx{}wsqmnpqppnmmmpuy{zyzwvz~xqmiiknprw{}~~~~|{zxvstssrolhgcaadgghhhhiijjmnmnnkjijjkkkjlmnmlprssstwwutuvwtqopqokfbbdddbbefhjjijnrvyyxurqqruwtpnppppoqqpnoqqrqrswxyyyxxxvvutsrqppppqssttuvwwwxyz{{{{}}~~~~~~~~}wpjhhghhhiiklpw~{uojfceipt{}|zvppppmifgjnoswz~~zuppnmnoonnnmpux{{xuu|~xpkiikntuy|~~~~}|zxvvtutsrmjijfbbdhklmljllnoqrrpnljeggijkknmmnnprtttwyzwtuvvtpopqolfddeddcbdhjlllkosxxxvrqppsvxwspnponnnoonoqqrrstuwxxyxwwuussrqqppppqssttuvvwxyyz{{{{{{}}~~~~~~~~~ysnjiiklllmmmotz|vpkfceimswzywtqppomkgdgloquz~~xsomoqpmmmooqw{ {wvz~xpljklorwz|~~~}|{yxvvtutspolkhgcdhmptutqpqrsrttqmkjhhhijiigiklnprsuvyzxutuvusoopqolfffdcbbbdhjmmnlpsvxvurqppsvxwtqonmlkknnppqqrssuvwwvwwwwvuttsrrqqqqrrsttuvwxyzyz||z{{z{{|||}~~}~}{tnjijknopoponrv|~yrmhddglouwwvsqoomlkiehknosw}|vrplkjfgklnqu{{vuz~wpkklmoqv{}~~}{zzywutrqonkjhgghotw{zxvtrsvwwvsnlijjjhiiiggjlnqssvvxyyustvuqoorrpledbcbabcehknnmmosvwutrqoqsvwwtqomkjjjlmppooqrrtvwwvvvssttsssrrqqqqrrsttuuvwxyyzyyzyxuuussux||{zzyzz|}~~wpjihiloprqqpnrw|zuoiecekorstsqpoomnljfeinpqw}~}|{xspljhhiknosy }vrw}~wpkklmoqw|~~~~}}|zyxvtspooljihinu|}zvtvxzzvspmlhgghiiikjjmnqsstvz{yustvwrporrpgddb```acehknonmpswutspprstuvvtqomkjhhklnoooopqsuvwuttuuuuttssrrqqrrssttuvvwxyy{|ywtrokjhilpwwusrpqrrux|~~zslhghjkooooooqu||vqjdadimopqqppppnpokhginpptz~}~|xuqmjiiimnrw} zvwwpkkjkoqv}~}|zyxvuqpnoljhhkt{{wvw{|zxtpnljihiigiiillnoqsuxy|yutuwvrrrssme`bbaa_`cfhknnnnquvtrqqqstuvxwtomljihhjknoppprstuvutttuuuussqqrrrrsssttuvvwwxyy{{xuqljihfefjnmifcbdehloswz|~~~}yqjgeccdimpppoprw|~xrkecceilmpppqqqqrrmhfilpqsx|}}{wtqmljjjnrw| }uv}|vplllmoqv}~~}{zxwvrqopmkjjnv~}yy{||ywurqnlihfffghhjlpqqqrv{{yuwxwvrrrssme`a````adgilnnklquvtqppqsttuvvqnomljhhjkklnopqqqssssttssrrrrrrrrssssststuvvvxyz{yxvrmljigecbccbbbb`adeehou{}~}}~~|xsmfc````foqrqpqw|xrnfacfkjjkmoprsstspjghknpqtz|{ytpqlkklmqw{~ {tu|~wpllklnrx}~~~}||zxvusqoqomllqw}}{z{zyxwtrpmjigffiihgijnpqrsw{|zvuxwurrrrqia^_]^`bdfhkmnmlmrsuttrprtttuwwrnmmkifehkklmnpqqqssssrrssrrrrrrrrrsssttvvvvvwxyz{yxvrnmkjhfdcbcdbaabdddccflqw{}}~~~~zupkhd_]\`ekpsrqrtz{uojeacehjkmopsuxxvslihjnoqtxyxsnlljkkmqvz~ wsuy{uokkklosw|~|{zxvtrpqomlkqwz}|yuuustsrppolhgfhjiiihhkmopqswzywstvwuqqrroh`]_]^`cegilonmlmqrrqqqqsvwuwyxtollkiedfhmnqqppqqrsttttssrrsrrrrrrsttuuuuwwyyxyz{{{xurrrrnjgggfcfffffdeba`cjtxz||}~~~}wqleb_][\^blnqsrtwz~wqkeceggijmpqsx{{yvojhhoppsuurmkkjkllnqx |srwxupljklnrw|~}}}|zyvtsrqonnqsvyz|zwppqmkkmkmmkifdceiiihgggjoppswz|zvtuutrrsrne^\\[`bcehkoqnkilpqsrpppsuttuxxqmnnlhgdefmnppppqqrsssssrrssrqrrrrsstuuuuuxxwxxyz{{{zzxxyyurmlijmqsuttqmhbbbcksvwxz|}}~~|vojdb_^]`aeimprsuy|~xrleacdhjjmqtw{}}{xslhhloppplifhhjlllnqx ysrsvtoljjknrx}~}}}}|zzxvusrpnpoqtvwvqmhfecbadgjnkhddceijjhjlljjjotxz|wstuutrrsrne^\\[_befloopnkjmopppppqsutstzysnmmkgeccelmmmmonpqqqqppqqppqqqqrsstuuvvvvwxxxyz{|{{{{||||wuonnquxy{{{ytmecbcehkoruxyz{{~}~~xokgfcaaabdglorsuz~unhb`dghikrvy~~~{vohgknqlieebdehkkmnrx ywxxupmkijotx}~~}}}||}||{zxvtqnpnnnpsusof```^]^cfjllkgdcgjklmpponmknqw{{xuuvvussrqog]Z[_bgiikopomjjmpqssqqsuvvtsxxqnmlihebefklmmlmnpqqqqppqqrrqqqqqrttuvvvwwxxxxyz{|{{||||{{zxvvvxyz|{|||ytokhdb`bdgijijlqv|}~~ysnigdbabfglnpquy| ~vrlfcchjknswz~{yrjfhlnjebbceehlmnpru| }xuzzupkiijnsx}~~~~{xxy||}|zwusqnnnmnpqqlda`aaabgknppmhdcfikopsuronlkou{{wtuvvusstsme\Y[afgkkloqplhijnprsqqsuvvruyvqnljjiebdbkjjklmoqrrqqppqqrrrrsstuuuttvvwwxxxxyz{{||{|||||||{{||{{||||{zvsqmieabd``]_bdgnwz~{vqljhebaejlqsvw|~ {slfdegjlpv{~}yslfehlkc`acfhmoonoty~|srwzupllmnnqx}}xtpqwy{|}|zvspnonnnqrnkfggjmnrtuvupkfbbeilosw{xrnjint{{vtuvwtrstrlc[YV_hlmmoprnjggjmqtspqtvxwstxupolkjhedefjijkmnnpppqqppqqrrrrsttuuuuuvvxxxxxxyz{||||~}}}}||||{{||||||{zyyurmkhgda^\\]^`gpx}}xrmkhgghkptxyz} }umfcdgjosw}~xtoigilkhcbfknrsqmou| yroovyupllklnqx}~xrieemty||zwspoommkmoppooqvxyyzzzzwrlfbcegjmqx{|vofejszzvtuvvsrstsnf_]\ahlnooppmighjostrstvwwutvxuomlkjhfefhjjjkmnppqqqqqqrrrrrrsttuuuuuvvxxxxzzyz{{}}||}}||||{{{{{{||zz||zyxusomigea`````enw}|wqmkkiimsy|~ |wphefhloszzvqkijknifdiotvxsmmqw} ~vooqwyvokkjknsy~|rh`\]ent{{wsqponlmnopqppqv|}||~~~|zskfcdfhjlsx~|vnfadnwxvuvwwtuutroiba^agkoqssqmigikqsststwxxuuxyvolllkidfhijjjkmnppqqqqqqrrrrrrstuuwwwwvvxxyyxyzz||||||}}||||{{{{{{{{zz|||z{yxwtqnkjhfda^bkt}~ytnmjjkqvz~ }yskffgjosx~~{vojhjmljjqw{|{uommr||usrquxvokkjknsy~zpha]_fntxwsommonlmoppqrruz}~~~~~|xqjgdehikmqv{{vld`bltxvuvwwtuuvtpifebejnpsssqnigkmrvurstwxxuruxuoljjkigilmijjjlmoqppppppppqrsstuvwxxxxwwxxzzxxxy{}||{{zz{{zy|zyyyy{{{{||{{zz|||{wusqmga^agr|~xrkjklqw| |vpheehmrx~|wsliimnmnu}~ytomnrx{uuwuvvvnlkmlotz|riccdhorssolllkklmnoppqquz}~~}vngcdefgjmrvyyulc_bkuxvuvwvutvvsnigihhloprtrnjgfjnquuttuxxwsquwrnkkjhfgjkmijkklmnoppppppqqqrssttuvxxyywwxxzz{zy{{}||||}}{{zyxyxxyyzz{{||||||{{{{zzxvqha^`jt| |wupmklmrw~ ~zupgedgjov|~yuvtmjimopqxztollrx|wtz}zvvwplkknnsx}zsjggklnpppnnmmllnnpqqqqqsw}~zsmebcehijnswyytmf`ajsvtuvxvutvurnjijkknprsutnjiimrtussuvxxvrswxsnkkjhfgjoqijjjklnppqpqqqrqqrssuuwxyyyyxxyyyzzz{{{{||||}}{{yyyxyyyz{y{{{{||||{{{{{{|zukb^ajt|}wpnlllnqtz~ |wpjfdeimr{|ustuokkmopry}zvqkjovzvw{|xtttqnlmmosy|uqmmnnmoppmmomomnoqqqqppsx}~~~~}ysmhcdegjlpsy|ysmfbckrttuwxwvuuuqnklmmnpqsttqmlkmqvwvstvvxxtptyysnlkkiegilqijkklklmnppqqqqqrsvvwxwxyyyyyyyyxyzz{{{{||||zzzzyxwvuuuwwxzz{{||||||||{{|yvnd`ags{~ztplkkllmqw} }xurlfdeimrw}~zumlorokjjlorw}|vqnmqw~}z|}xusrrsrnlllnry{vrsssnmoppnnommnoppqqqqqtx|~~~~~|xqkgceghkosu{|ytoieenqrtuwxwvvvvqlijmpqsttuusomnotwxvtuvvwwsrtyxrnlkkihjlothijllmmnmoqqrrrrsuwwvvxxyyxy{{{{{{{{||||}}|{{{yyxxwwwwuutuwy{|{{{{||||||{{xpfaaeqz~}wqnkhhiijotz }xsrmgddfiptz}}ysmefmqnjhllnsv{}vromrx~z{{vsopqtpmmmlmqw{xssvvtommpppqqomnoopprrssuz|~~}}zvoiecdghlosz}|wrlighorttvxwwvwwwrnlmoqsututvspoorvywsstuuwwttvxvqonljjjknqwhijllnppqrrrrrsstvwwwwyyzzyz||||{{||}}|z{{{z{{yyyyyywwuuvxwyy{}}}}||||{{{{ysja_clu{}wrkgggfgilrw} zuqoniffhhnsxxuoidcegnnnnpoqstz}xsolou} ~|zxurqrsqmjlklnuz|yvsuwzzvppprrrtqomnpppqrrssty|~}|zvojeddfhkpty{zvpmkjnsvuuwxxwvwwvsnlmoqstvvuvsqpsuxzvutuxxxxttxzwolkljjjmprrijjllmopqqqrrsttuuxxy{{zzz{{||{{||||||||{{y{yyzzzzzzzzyywwxxxy{{||}}|||zz{ztmd`cjtz~wsmgccbdgiqu} zwtpnnkihiilprqlgdba`cioqrv{{xwz~|xrmnqw {urqstpljkjkmpsutppty{zurqttuvupnmnnoqqsqqruy}~{xuojedceimquzzxronnprvyxvxzzyyxxxrljmprtwwuvwurswy}{wuuvxyzyuuxytnkkkkkkllnmijlmlmopppqstuvvvwxxy{{{||||||||||||||{{yyyzyyyyyyzzzzyyyyyyy{{{||||{{|zz{zwqkbaenw}|smga`aceiow| |xsqpnolhgiijlkjgda_ceimquz}~}|{wspnrx ||}xrquwvpljkjiilpqpmlqvz{vsvwzxvrpnnprrrrrsrtvy|~~{wrnieefiknrxzyvqonnpuxzwxz{{yyyyxrklnprtuvvwwutvy|}{wuwxxyywuw{yrmkjjjkkkljjjklmnopppqrtvvvvwxyyz||||||||||||||||{zzxxwwxyyyyyzzzzyyyyzz{|||{|{{{{{{{{{ztoheektz~~ysje``_cjqv} |tqpponmkhghhiieadcdcjlnptx}yw|xqpsy ~ysrw{yqkjjkjknnmlilru{{ywx{|xxspopqrrrprttuwy|~~{ywqlhddfgjnrvvtqqqppruzzxwy{zyyyzzsnnqrsuuvuvvtsvy{}zwwyyxzyxuvz{smjiiijjjjhjjklmnopppqstvvvvwxyyz|||||}}||||||||{{zyxxyyxyyyyyzzzzzzyyzzyzzz{|{{{{{{{{{zwrmgcfnuy{{zwqhc`aciqx{~ }{vtqppnnpqlhgghhfbcbbdjnrtvvyvqsttx}zwx| }yww{xrkjiihjkkjijmswxxywx{|xvrnnpqssrprttuwy|~~}}ztpkfeegikosurqppooosx}|yyz{zyyz|xrmnrtuvvvwwvtux{}}zwwyyz{{yvy|zrmkhiijjikjkjkmnnoppqstuvuustuvy{{{}{|}~}}||||{{{zzyxxyyyyyyyyyyxyzzzzzzz{{{{{{{{{{{{{{{ytqjebflqtutqmhecciov{ }zwusqqppnnpqpgeefgfdccbdhlpswwqijlmprv{}| ~{yz|wqmkjkkkmkmkkouxzxxwyz{xvtppqrssrrrttuxz}~}|zxsnkhghkmmopppppppootz~{zz{{zz{{xplotuuvwwwwvuvy|~zwxxy||}zv||xqljjkjgfikilijjknopprstutsolkmpsvvvwy|}~~~~~||{{{zyy{{yyyyyyxxwwwxzzzzzzz{{{{{{{{{{{{{{{|zumf`cfkmlkhdbbbgms|}|yvtssqppppqrstpjffghgeffb_`fmsuwvpnknprv} }{z~wqmkklllllnopqux{zyxyzzwvrppqrttrrrttuux}~~{wqnigghkmponnooooonpu|~{zzzzzz||wolrvvuvwxyyttvy}}ywxyz||{wv||xplkkjhfgiknpjjijloqrrstusqmhgfgjnnoqrvvy||{{||{{zzzzyyxxyyyyyyxxxxxxzzzz{{{{{{{{zzy{||||||wqhb_bccddcaabfmqx~~~~~}}}}{ywwtsprpnnooqsuvurkhhiiiijjd_[aluwxwqmnqvz~ |z~ yrmkjlmmlqsx}}xxzzxxz{zvtqpppsutqqrttuvy~~zuqmjhhilljhjkmnpomorw|~}|zzzzz|~{tontvstwwxwvqsw|~|xwyy{||ytu}}yqmjjjggghkqvjjkkloqrrstusqkfdceeggghklmpvxz{}}{{zzzzyyyyyyyyxxxxyy{{zzzz{{{{{{{{zzy{||||}}xrkdca__``aaabfltz~|zxwy}~}{xwtsqqpponnnnnlijjhgghijjlorvxywrkgghikkpokaVXeouvwrnot{ ~} xpmknprqpt{yyyyxxz{{xurrrrstsqqrttuwz}zuolighjigdbehjlmnprvxz}}|||{{|}}zsnouwuvxxxwtqsw||yyzy{{{wqsz}yqmlkkigghltyhjjlnprrrsuxvuplhegfffddffghlpvyz{{yyyzzyxxxxxxxxxyyzzzzzz{{{{zzzzzz{{{|||||{zysnjgc_\_^^^abglu{}yrkgfhpx}{xuommlkhffffedccdcbccdc`````bfkrvyywrlgfijhjqtqgZYdouwtqonrv}}z |vpmlnsx{|}~{xtuvuwxx{|zwsrrrssrqppprruy|~}xsmifgiifd``cfjmooqvxz{}}|}}||}~}ztpquuuwxxywuqrv}{xzz{{|yrmqz}womlmkjhfgmv}hjjlmoppqrtvvuroljjigggdddedeglprvwxyyyyxxxxxxxxxxyyzzzzzz{{{{zzzzzz{{z{|||||{zwsokgb`_^[[]^cgow~~}xnc\\]bhnrolhda`_]]]]\\[ZWWXWYZ\]^^]]___bgjmnpqpkjhffdegmsodairuwurpnnprx~ ~| zvpmlot| |tohfimuwwzzzvrqqqrrqpooprswx{~{vqlhffhhec]]beknnruuxz~}|}}||}~~{xtrvxuwxxyvspqv}{xyy{{{xqlqz{uomlmmkjiglv}ikjlnoppqqstuvurpppmmkjihgecdefilpruwxwwwwwwwwwwvvyyzzz{{x{{{{zzzzzzzzzzz{||}}{yvspnkhfc_\]\^bhmt{~}ype_YY[^beheb^\[XWYXWWVVUSTTRSVWWXYUXVX[_adggfgimkigdb_`dimrpmrwvvtqpooorx|z|~}~zsmlkow~~slgijltuyz{wsqpprsqponnnqsux{}zuoighjkkgcaabfknqqtvwy~}|~|{|~zusvvvwyy{tnlnu|{xyz{{yvnkpyxqonnnkifehlu{ikkmnoppqqrsstttuuvuusrqnmkhfdccfjnprtvvuuuuuuuuvvwwyyyz{z{{{{zzzzzzzzzz{|~~}}|{ywvtqmifc`_]\\^bgnqqne^[[[]_`__][ZYXY[\^``aa_^]]\^`abca^\[ZZZ\_behikijgb`ceggdhqvswxwwurponnory}~~{|z{|xy~ulefjompsy|yurrrstsokknortwx{~~{vqligghjjieeeimqrssuvxz~}|~|{|~{xvvwwyzzxsmlnu|{xyz{{xrjipwvqonnnopollptyhjkmmnnoqqqrrtuuuuwwxywwurpkgecabcfgkqstttsuuuuuuvwwwwwyzzzzzz{{zzzzzz{{|||}}|zwxwvsljgdc`b_\\^dihd__]^\\\\\\^_aabdegllkkkigedbdggijifda^]]\Z\_`dgihd`bhmoi]\luvyyxwvtpnmnosy|}~~|zz}y} }thdejlpu|}ytrsrsvspmlmoqtvyyyxsrplggikllkhjlpvwvutuwx{~}}~|{|~~{zyxxyzyxsmlmt|zyzzzzxogipttonmnoprromosvikkmmnnoqqrtsuuuttwwxyxxxxvqliecba`_ahnqrrstttttuvxxxxwyzz||{{{{zzzzzz{{{{|}}|zxwsromnnlie`\[^]\\]____^^^^^ehmnpqqrtuutrplihgefggimonnmjiedbb`_``abceknnog\Vetwxxwutrrqollr{}xv||}~ytx vpkkptvz{wsrsrsromllmprsuvvtsqnmkkkklmmnnotw{{xtrtvy~~}}~||~~}{zxxyzzysmmnt|~zy{{zzwogirvtqrqprsuxwtqrtjllnmnopqrttvvttttwwwwxxxxwwsojgb`^\`dkopprrrrssssuuuvwxxy{||zz{zzzzzyywwvw||}{yvtssonnnmjb^\[]^\[[\]^__`adgmtwy}}~~~{{yurnlihgghikpuuvvtrnmjhgdfdadhnqnklojeioruxwtqlonhfgpz{wt{~~{~ wrv~ |vruz{wsqrssromnqryxuroqpnlkkhigjlnoqsv|~|wuuvy{~|}}}~}yzz{{yyrmlmr{zy{{zzvqgjruvtsrqpsuz{xtttkmmopppqqrttttttttwwwwxxxxxxwsqmjfa]]bhnppqqqqrrrrsstuvwwxz{zyz{zzzzzwpiikpuy||yusqqpqqmgdbba_]\][[\^]^]`aadgnu{|yuromjihiiimouy|}||zvqmifdcfhnsrjghnw{~{upqtronpmmlosy~}{} |vy}}z|zy{zvspnnnoot}xooqpnljihijknpqsuy{|{xsorvy|}}}~}yyz{||ysnkls}{z{{{{umejswtrsrqpsu}~{wuumonopprrssuuuuuuvvwwwwxxxxxxwvsrpjc[Y^fopprrqqppqqssrtuvvxz{zzzzzzzytmd`bdinrvutrpqpnlke`[XZYWXVYYY]ceeeccefgkqwz~~zupmkkkjhhiimry}}yunhdbinrsrkhhk|~wlhgmurroqv~ }wry |~ywtqljmoxxpnopqpkjhghilnppsu{~~{uqprvy|}}}}~zz|}||xrolmt~~{{{{{{thdjrusrrrooqx}}|yvunpprttttttuuuuuuvvwwwwxxxxxxwvustnd\Z^empppppprrrrrrqstuuwxyxxzz{{zwphea^_adimnlklmlljhaZUTVVWUTTX]dkpqqmjgdbceilpux||zvrolkkkkjklnrv{~~~|zwqmgglsutsslhpy}rnsz}{vuz~} zpov~~~ysrqqrv} |tomnnopqpmlkiiklnpqsvzzzwspptwz~~~~}{|}~||xromnt~~{{{{{ypgdjqssrrrqqqx{{zxwvqprrttstttwwvvttuuuuwwxxxvwwusttqme]XZcmpoonppqqqqqqqqrrttuvwwxxyxxtmgc^``abeegggfgged_[TPORVWZY[`elrx{{wnhe_]]`eijlpqtqpnmmkkklmpqty|~}ytroomtz~{{|wy |rv} {yysqw~}w{}{sppq{wmnmoopprrrpnmjjlmnoqsvz{wtqqrtx|~~|{{}}{vsonot}|yz{{yunebjturrssrrrvzyyyvvpsuuuutuvvwwvvvvvvvwwwxxxvvvussspld\UXbiooopppooooppppppsttuwwxx{ywtohc_aabdddcca_``^][WOMLPRTX]ckry}}tkda]^]^aefgijlnmmmllmoqtwy}}|{ywuqnmpu}|v{ |vttz xpnqy}ztnkruonmmlmnrtttrpmkjlmnprtwzyuqqqrtx|~~|}}}}zvsonot}~|{{{{yukbdmssrrqqrruyyxyyvvpsvvuvvvxxxxxxxxvwvwxwwwxxwwvvuuqnh_XY]djmmnnnoonnooppppqrtuuwxxxxxuqnjhdcbabdb__]^]ZXVUPMJHMU]dnwvlfddfed_^_bdfhklmnoopqtvxzzzwwsqppnnqz ~rw |yxvv{wolry}xnhn~|tnmmnnoopruvurpolllopqrtw|zvsppsux}~}~{}}||yvsomnu}}{z{}|ztjdgousqqopprvy{zyxxwvwvvwxzzyyxxxxwwwwzzzxxxxxwwvvuuspke^Z]_dilnoooonnoopppppqrrtvxxxxywtpnlkigdacddb`_[XVUSQPOOQ]gq{ wmhfjprojgb`adddhknsutuutsrrrqqppnmmpy{ }|y}~qhljf_`hz~yspnnnnonptwyxtqnmnnopqrux{yvsqqsux}~{||||yvuqmnu}}|{||zyrjfluusqqoppruxyzyxvwwwxxwwyyzzywwwwxzzzzzzyyyxxxwvvvspmha^^^_aehknoommnnpppppprsuvwyxyxxvtroonljhhid`^\VTTTTUWXZ^it|~xoffjsvurnme_^`bgimqrsqqonmomlonopomq{ {te]Z_it|}xyuqprromlmrvyywtqnnnnopprx|}{xwtttvy~}{{{||ywtrmqw}~}|~}|xtlhrywqppppqqsxxyzzyywwxxyyyyzzywwwz{zzzzzzyyyxxxwvwwutrkgcbbaa`agjlnmmnnnnooooqrsuvwwxzzxvvtrpnmigcb_][XWY[\_aeipx~zskfhov|zusokghiiihjlihhghknmllnprqrx{} }|rnu~xvtwvtsuvsmlmrwz{ytqnnnnpqstw{|zxwuuvxz}{}}}|zwurptz}~}|{}}ytlkvyvpppppqqqruyzzzzxxxxyyzzzzyyyyyyxxxxzyyyyzyxywwuwwuqmiiea`__bcfjmonnnnnnnnqqrtuwxz{{zxxtpmlhfc^\[Y\^_bcfinty|}|tnffjqw|{wpmpsqkljiijjjooponjiptvxz| ~{|wr|vtw~}xrpsqmlnsx{{wtqnmmoqqssvwwvuuuwuw||{|}~|zvurpu{}~}|}~}{ros||tpppoopppqvzzzzzyyzz{{||{{{{yyyyzzzzzyzzz{zyywwuxxyxtqnjgda_]^bglmnnoopppppprtuwwyzytqnjgea][[YZ[^acgknru{~}wpheejpwuxz{~wsrpmkihffhlmjhhpw{} wu~~~~}~}yx||zwt{uqrqnnoquxyvtqnmlmmmkknonlmmopuw{|{~~~|ywvtsy}~}}{tpx~{soppqqrrqsvzzz||zzyyzz{{{yzzzzyzzyx{{{{{zz{{yyxxwwxxwwvrnjfb`__bgkmonoppoprttuwvssqnkfd`[YWX[]^`behlqux{~yskegjkqsz ~zslddd_]bjlnnrv{|~ volov ~}yxuz|xtrrppqrvzytpnmmmjkkknmmonooortuy{z|~}zzyvv{~~~~|wv~zrppppqsttu{|{{{{zz{{|||||z{{zzz{{zy{{{{{{{zzzzzyzzyyyywutqnjd`^_cglmnoooopqrsttslida^ZYWWWYZ^chjmpuz{}|upifkosuz }zsjfgjkhhijkrz|tuzw||{~|||~}|}~|{|yvssrrsuxzzurlkjjjklmnmlllkmmmlnsuwxz{{zzy~~~yux|tqppqruuz~}zz{{yy{{{{{{zz{{{{zz||{{zzzz||{{|{{{zzzz{{{{xvtqlfa__cilmnopoqssutojd^ZXWVVX[^_bhnruy{~|yroru}|zrioma\ahlqyxqm{{xz}~~ ~~}|yyyyzzwttstx}~ysomnnkkklnprrponlkijjijorw|~z{{{z~xsw{qpqrsstw||y{{{||{{{{{{zzzzzz|||}||||||||{{{{||{{{{{{{{|zxvqkda_^`cefghloppolhb][[\^^^cgkoruy{~{ww~ wttj`]`is|}unt yy{{z} {yyzy{}|zwxvwuuux|zspoljjkknrtvwyyyyvtsssokjlntz~ |{|~}}tov~ytsrstuux}~|{|||{{{{||||{{{{zz{{||||{}}}||}}||||{{||||zz||{ztokgd`_^^_``dgihec_]Z[]`bgimrvy{~~z| |ww{slfguxpt |zzzxxz} zyy{z{}~}{xyxwvststtqoklloqoprvz}~~}{yyy{ztnihkq{}{|~ zqnt}}yvuttvvvz~~|{||}{{||{{||{{{{zz{{||||{}}}}}}}||||{{|||||||||{ytplhe`^^\\\^ab`__^__bfjotx||~}~ {| }vzxnn ~y{yusuy~}yx{ {zy{z{|~|zyxxxuronppoorstttstx|~wnhhpz ~~zokr||wusuuvvvz~~|{|}{{||||||{{{{{{||||{{{{}}~~}}||||||}}}}||||zzyxwsnjea]]][\\^`__`bglswy|}} |}}whfs yz{wutvy|}zxw}wz{yz{||yzxwtqnlknoqqruuuvwxz} |rjhp| zqnw~|xuuvvwvwy~~|~~~~{{||{{{{{{{{{{||||||}}}}}}}}||||||}}}}|||||||{ywvrpmkgba^^`befjnrxz|~ |{|| ~tmt |yyzyz|~|{{}ywwxz}~zuqqonkjjmprrrsvxxyyz{ {pfhr~ |uu{}xwwwwywwy~|ywusq{{{{zzzzyyz|zz{{{{}}}}~~}|}}}}}}||}}{{||||||||{{||ywspniedehknrv~~|{{} |tot |{zyy{ }vuvw{zvsqpmmnoruutuwxy||}}}}~ zkdiu ~z{zwvuxxvw{{rjiii{{{{zzzzyyz{}}||}}}}}}~~}|}}}}}}||}}zz||||||||{{||{zxzxvsrqsvx{}~~~ ~zz~}pko} ~vsru{ zxsrqqsrqsuvwwx{zzz{}}}}}}~ uhejx }zxw{{yxy{skgjljj||{{zzyyyyzz||}}||~~~~}}}}||||||||zz{{zz||{|}}}}||z|~~|z{|}~ {{ |omr~ vliku|y{sqrrtutwyyzz}}||}~~ ~peeq }{{}}|zxvojlmkllozzzz{{zzzzzzzz{{{{~~~~}}}}||||||{{yyyxzz{{zz}}~~}}}~~ |sqsy tgbehnvzzzvv|}zwyz{|}|{{{}~}|}~~~~~ {megq~ ~|}}~ztlinrrpqruyyyyyyyyzzzzyyz{|}~~~~}}}}}}||}}{{||zzzzzzyz||}}}~~~~~~ |vy}xhhnkhnv~~zxxxy{}{z~|||{{||}~}||~pgdis }tklnprtuwx{xxxxyyyyzzzzzzz{|}}}~~~~}}}}}}||||{{{{zzzzzzyz||}}}~}}}|}}~~tpvyqrusonr{|{yuuwwuttrrv|~~||}|}||||}~}|}~ yjcenz {pkmosvy{|~xxxxxxyyyyzz{{||}}~~}~~~}}||||||{{zz{yzzzz{|{{|||}~|{{|{{{||~ ~ztnr{ ¡ {|{wsqt||xwsnqvvtpmqx}|}}|}}|{}}}} pgcjx vljlpsvz}~xxxxxxyyyyzz{{||}~~~}~~~}}||||||{{||{yzzzz{||||||}~~~|||{z{{||~ {tt¢¢{{{~}{{wrlknsuusqqt{zxxz|||{}}}}}}}~ znikx wlkmosw{~~wwwwwwxxyyzzzz{|~~~~~~}}}}}||}{zyzzzz{{zzzzz{||}~~~}{{{{{{|}}~~ |yy~ |qeampsvxywuv|zxyz||z{{~|{||ypnv xnlnprvzwwwwwwxxyyzzzzz{||~~~}}}}}||}{zyzzzzzzzzzzz{||}~~}{{{zz{|}}~~ } ~yqbOViotwvusqv ~{z|{}}||zzxwx{}vpt ymkpruy|xxwwxxyxyyzzzz{{||}}~}~~~~}}}}}}||{{{yywwwxyzzz{||~~}}{zz{{z{|}} } |ylU73Whptvwwst||zz{zwuttutuw|wpt~ {nlpsuy~ xxxxxxyxyyzzzz{{{{}}~}~~~~}}}}}}||{{|{zxxxxyzzz{{{~~}{zz{{z{|}} }ucC,/Vknnrwyupv}x{~}zyvutux~wsv ~smqtuzxxzzyyxxxyyz{{}}}}}}~~~~~~}}~~}}}}|zzz{yxxwwyyzz{{}~~}}|{{{z{{|}~~ ¡taJ<F`qrnorvusuz}ztvx{~~}{vtsvzusw smqtvy~sxxxxxxxxyyzzzz||}}}}~~~~~~}}~~}}}}|zzzyxxxyyyyyy{{|}}~~}}|{{{zz{|}~~ ~~ }ul[JNgwyuqotvutw}{y}~}}ywvvw{utx xpqvvx xmxxxxwwzzzzz{||{|||}}}}}}~~~~~~}}||||{zyyyyyyyyzz{{|}}~~~}|}|}{zz{{|}}}~ ~xvwx}{tkaVO\v{vppuwtx~~~~~|ywvvz~wv{}pruvx~rixxxxyyzzz{z|||{|||}}}}~~~~~~}}||||{zyyxxyyyyzz{{|}}~~}|}|}{{{{{||}}~~zx~yxxy {wrmga`k{|{wpirywx} ~{ywvvz~vu| pquvw~ ynjxxxxyzzz{{{{||||||~~~~~~~~}}}|{|}}}}||{||||zzz{{{{~~}}~~~~{{}}||{{||}}~~wom| zurqroorwyuuuogjsxy|~~{yyy} }vv~snrvy~ }pikxxxxyzzz{{{{||||||{{~~~~~~~|}~~~~}||{|}}|zzz{{{{||||||~~~~~~~~}}||}}||}}~xndgyz~ ¡ ¢¡¡¡ ~{z{zxqllmknstmhkpw ~{yzz{ }wyxprtv}}zshgjyyxyyzzz{{{{||||}}}}~~~~~~}}~~~~~~||{{}}{{zz||}}}}}}}}|}}||}~~}}}}||||||}}~|qd_fw}{||y¡ ¡¢¢¥¥ }xnbafknqrpjfju}|{|~{xzvrtuvy ~{vqnooomlfeflzzyz{|||}}}}||||}}}}}}~~~~}}||}}||||{{||||||{||{z|}}}}}}||||}}~~~~~zshb`ehjknzorz¨¦¦§¢¡¡ ¡ ¡£¢ ~}ytqnicbglornhen|~}} zx}wqtvutvspnjihhfghhhgfecehzzyz{|}}||}}||||}}}}}}~}}~~}}||||{{{{zz{{{{||}}}}}}}}|~~~}~~~|{vmfabdehin{tjlw¡¤®©¥ £¡~{vux|yla^bjoojfkuyz{~ ~yz~ }stvsonieccdehilnoponkhhffzzyz{|}}}}}}||||}}}}}}~}}~~}}}}}}}}{{{{{{||||}}}}}}}}|~~~~}~~~|ukeehfeefgo|}trv|¡¢«§¢ ¢~|zvx{ync^`hmkgmy ~y{tsrligdedehlouy}~xqlmlnn{{{{{}}}~~}}}}}}|||~~~~~~~}~~~~~~~~~~}{||||{{||||}}}}}}}}}}~~~~~~~xofbehddedgp{{utz¨«¦£§¥¡ }yyy|}wkb]blmlmt{ |{{vnlhgdghkkntz zrmoqvx{{{{{}}}~~}}}}~~~~|~~~~~~~~~~~~~~~~~}{||||{{{{{{||}}}}}}}}~~~~~~|xpfeffgggccirvtz ¡¤¤¡ ~~~}xqe^elqroqu| |{{xnkikkmmqv|~rnnt{ ||{}|}}~}}}}~~~~~~}~~~}~~~~~~~}}}}|z||||||{{z{||||}}|}~~}~}}}}}yshdfgikkibbjs~ |y|¢¢¡ ~||}zqcfjoojelx~ zz}mifhmqrv~yqnrx~ zzz{|}}~~~}}}}~~~~~}}|z{{{{zzzz|||||||||}~~~~~~~~~}}}|zshccfilmkgbbit {~ }{z{}zurqrogjv }|{pffhkty~ ~tpps{ {{||}~}~~~~~~~~~~~|~}}}}|zzzzzyyzz{{{{||}}~~~~}}}|||}}}}{uicaehmqpnibclu~ ~|{y{{|}~}{{{urw}|{|uhfknrz yqqsy ||}}{|}~~~~~~~}}~~~~~~~~}}}}|zzz{{{{{{||{{{{z|~~~~}}}}~~~}}}}}zrjddcekpsqngbcir| }z~ ¢ ~|{z{{|}x~ ywx |{|zlfinr{ uprt|}}}}}}~~~~~~}}||}}||~~~~}}{{{{yzzzz{{||||{{{|~~~~~}}|}}|ztjddeeiossqlfaelx xnu|~{|§¬¦ ~||{{|}y|z{} ~}}~mdfknu {rnqx~~~~~~~~~~~~}}||||||}}~~~}|{{{{yzzzz{{||||{{{|}}}}}}}}}}~~}}}{ukdcegimqttpkhgip| |giu||~ |{|§«¬©£ ~}}}}}~ ~{ ~~ qjiilruoqv{~~~~~~~~||}||||||~~~}|}|{{{z{{{{zz{{{{{{||zz||}}~~~~~}|ulcaehlnqvwsnjghlsl`lvvxwx| ~¨ª¥¢~~}}~ yx{~ ykjklr{|spty~~~~~~~~~~}|||||~~}}|||{z{{{{zz{{{{{{||}}||}}}~~~}wmd`dhknsvxwqlhgks{ qacpvwwx| }tsw|{lklmpxxqrv{~~~}|||{}}||}}|||}~~~~}|}}{{z||{zzzz{{{|}}}}}}}}|}~~}}~}zshbbhkmsyz|wqlihnt{~vf`mtwxz~ ~~ {totz qkonnw |trtw} ~~~}}}}}||||}}|||}~~~~}}~~}|||{{z|{zzz{{{{{|||}}}}}}|}~{tkcafikqwyzytmihlpw{ ~yk`m~ |tkchu vlknps|xqrvz~~~~~~||{{{{||||||||}~~~~||~~|}||||||zz{{{{{{||{{|||}}}}}~~~~zpgbeilpw{||yunihnuxz ~q`i xslb[gz pjkptzvqsw}~~~~||}|{{{{{{|||||||}~~~~~~~~~~~~~~}}|}}}}}||{{{{||||||{{{{{|}}}}~~~~wlbchjnty{}{yuqmmnmjm~ wee{ }wqlcRPgz unmnqu}ursx~~~|~~~~{{{{{{{|||||||}}}~~~~~~~~~~~~||}}||}}||||||{{{{||||||}}~~~|vkegikov{}}}{wspplc_i zkew ~vmbOLZp }olnpt{|ssu{~~~~}|}|{|{{{{{{|||||||}}}~~~~~~}}||}}}}}}||||||{{{{|||||}}}~~}{vnhjkmsx}~{{zxuspg^_m {mbq ~zwv{zrnotz~ smnprw~xssv| ~~~~}|||{{{{{{{{||{{{{|}}}}}}}}}~~}}}}}}}}}}||||||{{||{{}}|}||}}~~|ztpknmpw|~~}}zwsld]_n |tpgg ujcgqz~|||~{} {omops{tqsv}~~~~}|{{z{{{{{{{||{{{{|}}}}}}}}}~~~~}}}}}}}}}}}}||||||{{||{{{{z|||}}~~{ytpmnory|~}zwpgZXfu {rl`e| |qc^]eoz}yx|~||~ rllnpv}{ssuz}}}}||||}}||{{zz{{{{{{zz{{}}||}}|}}}}~~~~~~~~~~~~}}}}}}}}}}||{{zz{{zzzyyz||z||||yplmknry~~zsk]Z]js} {unbo } |qe^Y\emt{}zyyy{~}|~ znnlmqxwsru}}}}}{{||}}||{{zz{{{{{{|||||||||||}}}}~~~~~~~~~~~~~~}}}}}}}}}}}}||||{{||zyyz{{yyz{yunkkjjnx|~{tkb[\aemx }~~ywz}~~ }re^\]`ipwyzzyvvx{|{z} ~qmnmqu|tstx}{{{{{{y{{{{{{zzzzzz||{{}}}}}}zz}}}}}}}}~~~~~~~~~~~~}}}}}}}}}~~~~~}}||}}}}{zy{zywvxywrlggghmu{~ypiedcecags yvv{}yyy| ~{tib`_^fmqtx|{zxx{{|{~ ynnnou{ ~uuvy|}{{{{{y{{{{{{{zzzzzzzz{{{{||{{}}}}}}}}}}~~~~~~~~~~||}}}}}}}}}~~~~~}}||}}||z{{zzyvuvvuqjfcfghowxphdejlgb`bk} {vw}zwx|~ }{tmheeb``_`aiq{{tnmqtxy~ rmopqx}{tuu{xn{{yyzzzzzzyyzzyyyyzzzz{{{{{{{{{{}}}}}}}}}}}}}}}}}}||}}}}}}||~}}}}}}||{zzzyyyyvvtsrmgabchehryzridhnsrqh`^f} |z|~|z{~ zkRFN\eca`a``eluwrnhb][dou~ |qnppuyywwyzok{{{{zzyyyyyyzyyyyyyyyyzz{{{{{{{{}}}}}}}}}}}}}}}}}}||||||}}~~~~~}}~~}}||{zzzyyxxwwtspkebcgiihjswrklqx|{wqhch ~}}|~~ueYX`egebabeecdfllhd^YUOU]^fr wqsruy}|vwwz|rmk{{zzyyyyyyzzxxwwxxyyxxzz{|||||{{}}}}}}||}}}}}}}}}}|||||||~}}}}}}~~~~~|}|{yxxyyyyxwurohcadjmlgekrqpru{{rmq ~{~}wqs}|vnidcgkjeacdfeaXSONTSOQdy uttty|~|wxx{tmmnzzyyxxyyyyyyxwyyyyyyyyzzz{||||}}}}}}}}~~~~~~}}~~}}|||||||~}}~~~~~~~~~|}|{yzzzzzzxwvtokfchnrrojkqqompyztv yrrw||tporw{{{||wogcdhpojdb`_ZSKGFF@KJLWer~ |tssty}yvyy} xpmpsyyyywwwwxxwyxxyyzzzzxyzz{{||||}}}}}}~~}}}}||||{{}}||||||}}~~}|}|{{{{||||zxxwvsnjgfjqturnkmnnihr}|wx } } zpjgggjpuuqnjjnpsqpsx|}sjbbadnpleb\YTL@:68=EC@HUfu vqrtv{~~wvyyvonquvxxwwwwwwxxwyxxyyyyyyxyzzzzzz||||}}}}~~}}}}||||{{}}||||||}}~~}|||{{{{{||||zxxwurnjeehotutpkjjjdcmzzvv}| rcn} |vy vliigefginturmhcdghgis|ukgcabjrqjc]ZUOIB<>GMNRWPMcq}~ustvx}~~}wxy{{qmnty|wwvvwwwwwwwwwwyyxxyyzzzzzyzz{{||}}}}}}}}}}}{{{{{||}}{{}}}}}}~|zzyyy||||{{{{{yxvspkdcenswxuplkjgcjwytu~~ vfXYs~ srv| yqoppljijjmtvvnieb`_`fs}xmhebcmzzrkd`]YVZ\ZYYTY_H,@_t yppruz|}~ |wxy|}toonu}uuvvwwwwwwwwwwwwxxyyzzzzzxyy{{||}}}}}}|||||}}}||{{{{}}}}}}~zwvuux{{{|{{{{{{zvsolfcbhnrwvtpkjgcet}~xvsv}~}{|zujaTN[y xqssy ~wustvwupnklswwvrnjfffkv}rlfbfszxsked`^]^cfe_^_Y=,1ATi| xqqruz|}~yvyz~xqpqu|ttvvwwwwvvxxwwvvwwyyzzzzzzyyz{||||{|}{}|}}}}}}||||||}}}}}~~~~~}{wsqsvz|~|}||{z{yyvrmgbcjosuvtroljdaiqropsv} yoinqlf`TKLc{ vnnruy|~|yxtu}vpnoquvuutqpnnqv~{rld_cpwwtoiccbbcgjlkd[M:8FOZprooovz~}zwxy ztpqrxvvvvvvwwxxxxwwxxyyyyzzzzzzzz{|{{||~~}|}|}}|{||}}||||}}}}}~~|zwsqqsw{|~~~~|{zyxvrmecfnqvxxwvpmkf_biiimrx }nggkid^VOGMl| {tqruyzyyzywyx{ zqorvyxwwwvsqqtz}smfbht{{uqjhhgeddccb^TD:?Xw~rqoov|}yyyz }ursuwuuvvuuuvwwxxxxxxyyyyyyyz||||{|||~}~~||}||||{{{{{{{|||||||}~{wrqqty}~|{wvttqoiefjpuvxyxvtqokghjmlosz ulihhc[RNHEPu zvruxzyz{yyzx| zspsz|zywwxvssw} |tkednx|ytqmllhbbcb_a`TKHPixuxxwz {spoqx~~ ~{zy{vrquvzuuvvuuuvwwxxxxxxwwyyyyyz{{{{{|||}|||||}|{|{{{{{{{{{{||||~~{wrqprx{|{vtokighihfeinsvwvxxyxurprtuwqnqxvnhdb^WPKCCXy }wtwyy{{||{z} ysqv}~}|zzyxuvz}yqkefowysnklmmjebcfcbaZSPXpzzwtrnlvxqppry~||zzy{ytrsw{uuuuuuuvwwwxxxwwxxxxxxyyzzyyz{{z{{{{{{|z{{z|||{{{{||{}~~}~~~~|ytnnov{zwqid`_^\]`chntuxxxxy{xwspty||sosx unjfa[SLG@Db{ |vuxyzz~|{}~wppv|zwwy~uokeht|ztnihjjmgdefdab_YX_t yuqlfcrxoppry}}}{yzx{}ztrsvxttuuuuuvwwwxxxwwwwxxxxyyzzyyyzzy{{{{{{|z{{z|||{{{{||{}~~}~~~|vqnorxwsjeaceb_^_elrvxxxxxy{ywtqsy|{rnrv |xqh_YQG@<Ghz |yy{{{~|{}}zxrpqx~zxz~~toicht|zulhgimkhhihfdb___ds yphdb^bu wqrsu{}}{{zxxxvqsuyttuuuuuvxxyyyywwxxxxyyyyzzzzzz{|{{{{{{{{{{{{{{{{||||||~~}~~ztqpptuofdhmpmfdfnsvyyxxyyzzyxtstx|zroqu{ }tjaZWME?CRp| ~{||||~|~|yurpqz~|~ |smgcju}|wplhklkmnnkgeca`bdp~ xnd^]\[dzursrt|~~ }{}}xuussux| vvvvuuuvwwxxwwwwxxxxyyyyzzzz{{{|{{{{{{{{{{{{{{{{}}||||}}}uqmloqnhimuzyrkinuwyyxxyyyy{yvttwzyspqrwypg^YVOIDFTr |||||~}|~|uqps~ zrlcdlx}|xtplkknooomkkheddflq} }ujb_`^\ispont|~~ ~|~~xsqqrtx stuutuuvuuvwwwxxyyyyxxyyyyzz{{{{zz{{{{||{{{{{{{{||||||}}|~tmikliggrz|ywtruzzyxyyyyzzzzyxuuxxuqqrw zreZXYRNIL\w }||||}|}{tqss} wplghr{~|xxupomlmpnmopnjhilmms|~xrh_`bbaas|roqqv~} }{~}xqossu|~stuuvvvvvvwxyyxxyyyyxxyyyyyyzzzzzz{{zz{{{{{{{{{{||||~~}}~~~yokkgcaeow}|{zzz{{zyyyzzzzzzzyvvxytpnqw} r_UTWWTR[gy ~~}|{{urqt} }rmifjs}|yxxvtponnrsrqpqpnpsttwx|}yzwoe`_bdb`l|rooow~} }}~zuqoquy~rsttuuwwwwvxyywwxxxxwwxxxyyyzzzzzzzzzzzz||{{{{{{{{{}~~~~~~~~~~~|wqlgb^_isz~~~}|{{{yyy{{zz{{{ywvvwuros| yfWVZ\\Y_k| ~~}~|zz}vqpw ynmjflw}}{ywwxsqppstrpqrqpquxxtpsuussojc^^begfiwzrqppx~|~~~vsnort{oqtttuvwwwvxwwwwxxxxwwxxwyyyzzzzzzzz{zzz{{{{{{{{}}{}~~~~~~~~~yrkfa^dpz~~~}|zzz{zz{{zz{{{yywwxyyy}}rcZ[^_`fr} }{z~wqpv~ |qlljhpy|yxyyzxvtqtvtnmlkjjnppnlllmmnolia\]cghho} xqpppx~~~{tpnoswoqrssvwvwwyyyyyyxxxxxxyyyyzzzzzzyyyyyzzyzzyy{{||}}|||}~~~~~~~xpkfdhpz}~}}{yz{{{{{||{{{{{zxxw{|~ wl^X]bkt{ ~{yz~zrlkmr{ |smkmkikoqvyz{}}}}|xyxumf_]]]_ehgfegjmpmkgd`_ahijmsy|}pppoqx~}~~~~yropqt|noprruwvxxyyyyyyxxxxxxxxxxyyzzzzzz{{yzzyyyyy{{||}}|||}~~~~~~~~~~{zuqprx~~}|{z{{{{{{{{{{{{{zxxy|~ wja`ky}} ~{yz~|qicacdht~ vljkjiehlkjlorwy}}yvrle\VTTTV[dhjmnppqnmiea`cfhiighlqvvrmllmoy~~}vqpqsv}mmopqttuvwwyyyyyyywwxxxxwwxxyyzzyyyy{{{{{{{{||{{||}}}}}}}}||}}~~~~~~~~|||}}|}|||{z||}}|{{|||||||yz| }{y |y{}}~ zyy~~ticbcbabkv }rhghiiejllgecbeipvvrmhc^WROPSY_lv{zzzxxspkedbcddefebbbfhhhhhknw~}{sqqsuymmopprstvwwyyyyyyywwxxyyyyxxwwxxzz{{{{{{{{{{||||||}}}}}}}}}}}}}}~~~~~~~~~}||{zzz{{|}}|||||{z{||}|zz~ } }|~{pjjiieb`cm| {skjjhhfhiiigeecdbccdcb`]\YYZ^hy }|wsppokikmopmkigdacccccgqxzy~}||xruusu{~mmnoqrsuvwyyyyyyyyxxxy{{yyyyzz{{{{zz{{zzzzzz||||||||||||}}}}}}}}~~}}{{}{}}}~}|}}|{zz||{{{{||||}}||}}{| ~|||} ~{~~ ~ysrqpnlhaacp||{z xnjlkgfhjijihggebbbbabccefediv~{xvvvtooty~}{ytnhdeddeekoru}zzwrssux{nnnopqrsvwyyyyyyyyxxxyyyyyyyzz{{{{||||{{{{||||||||||{{{{||||}}}}||}}}}}{||}~~~}|}}|{zz||{{||||||||||}}~ ~|z} ~|~{} }xtvxwtstoe`_flihims{{vomnmkggfefgecbcbddfdbagnsv{}}|ywz }umkigeefjlq{|{ytrssuy~ ~~nonopqrswxzzyyyyyyyyzzzzyyyyzz{{||||{z{{{{||||||{{{{{{{{{|||||||}}}}{{{{|||||}}}{{}}||||||||||||{{||||~~~~{| }~ {zqptvrptyvnfaaded`bflqolmnnjkiidcddddccddfhhedmw| }~}vrpkhefhiiszzvssssvz {zlnnooqrsvwwxyyyyyyzzzzzzyyyyzz||~~~~}{{{{{||||||{{{{zzzz{|||||||{{{{{{{{zz|||}||{{{{||||||||||}}||}}||~~ ~ {{snoqroptx{ulbaddeeffegiklkklpssoigfddddeeegmun_Ycw }ytnkjjggny{yxussrsw|}nnnnnpqrvxyxzzyyzz{{{{{{{{||}}}}~~}||||||||{{{{{{{{zz{{z|}}|{|||||{{{zzz{{{}}{{}}{{{{||||||}}~~}}}}}~ |}unklooorvyyuleehijjecccegfdejnvupjfefeebbcbejjcWIVu }zvsnlhjr{ {xtsttsuz~} nnnnnpqrtvwyyyyyzz{{{{{{{{||}}}}~~}||||||||||}}||{{{{{{z|{{z{{{{{{zyyzzz{{{zz{{{{||||||{{{{||||}}}}}}}~ {zxrlklmlnqvyysmhgikmponjihgfedfhjkkgggfeca_]][]]ZTRXe ¢¢ {yvqmiosw}wvtsttsv{zoonnnpqsvvwxyyyyzzzz{{|||||||}~~~~~~~~|{{{{{||}}||||{||{{{||{{{{{{{{zzzzzzzzzzzz{{{{|||||z{{||||~}||}}}~ wrolnppolmmty{snkhhlppuwupolhfcccbcghijihhie]ZXYYXY_aev ¢ {zwplprty~~wutssssx|} oonnnpqsvvwxyyyyzzzz{{{{{{||}~~~~~~~~|{{{{{||||||||{||{{{{{zzzzzzzzzzzzzzzzzzzzzz{{{{{{{yzz{{||~~}}}}}~ {snopqpmiiiowxrmihinqtvutsqokebaaacdglopqrsrjeddb`agilv¢ ~~wnrvttv|}ussqssvz |ppoonpqtvvwxxyxxyyyy{{{{|{}}~~~}|||}|{{{}}||}}}}zzzyyyyyxxxxzzzzzzyyzzzzzzzzyyz{zz{{{{z|||}}~~~~} |sqrtspnihgnyxrkgcgpssstsqomjedbbacehmqrsvtutropmllonqz¡¤¢¡ zw}~~zty|vurv|ztuttttx|| ppppnpqtvvwxxyyyyyzz{{{{{~~~~~~~~}}||}|{zz}}||}}}}||zyyyyyxxxxwwxxyyyyzzzzzzzzzzz{{{{{{{z|||~~} zqstuusqnhejsuoieclruvutsomkfdcbdegkmoqstwvtrqsvtssuwvx¤¦¤¢¡¡¡ vnr{~{z yvuu} ystuuuv| | ppoonrsuwwwwwwxzzzzzz{}}~~~~~~~~|}}||||||||}}}}}{zyyyywyyxxxxxxxxyyzzyyyyz|{{{{{{||{{{{||}~~~}}~~ {tsyxusuphfkqqlgdentwxvuqokjgedefhlqqrrruuqpnpw{{xvvvspr}¥¥¥£¢¢¢¡¡vjiu~}|ywxzzuvvvux||~ ppppqqsuuuwwwwwxyyzzz{||}~}}|}}||||||||}}}}{zzyyyywwwxxxxxxxxyyyyyyyyyz{{{{{{||{{{{||}~~~~~ {xyxxwwrljptrmjdgqvwzzwrpiiifeeinvywuuusrmhgkry{vsttpmmt¢¥¥¥£¢¢¢¢ qgm{}{xsu{ vtwuuvyy{zppqqprqttuxwxxvwxx{{{||}}}}}}~~~|}}~~||{{||}}}}{{{{zzywxxwwvvwwwyyyxxyyzz{{{{||||{{||}}}}}}~~~~~ zvwyzvonpsqmhflvxuxxtqmjiiggflq|}xxuronidbluxutrrrnkq¢¥¦¥¤¤£ siky~zyx{|uvwuvvxwz~xqqqqrsttttutvvvwxxyyz{|}}}}}}}}}~~~~~~~~}}||||}}}}}}}}{{{{zzywxxxxxxxxwyyyxxyyzzzz{{||||||||}}}}}}~~~~} }~}wrpqsplhfpzzxyxuojjllllklpwz{yvurrpkfelttstsssoor|£¦¦¥¤¥¤¢|qgju~~z~{}xsuvuvwy{yqnrrqrssttstuuwwwxyyzzz{||||}}||}~~~~~}}}}}}||{{}}}}~~}}{{|z{{yyxxyyzzzzyxyyxxxxyyyyzz{{{{z{{{{|}}}}}~~~~~ vqqrrolhgs~z{zvrmmmpooonkmorstxtssolklpsqtutrrssy ¢¦¦¦¤¤¥¢ zrmnw}}z}}}xuvvwwx ytmigkqqqrsstttuuvwwwxyyyyxyzzzzzz{{{{{{{{}}}}}}||}}~~~~~~}}||}|{{zzyyyyzzzzzyyyxxxxyyzzzz{{{{z{{{{|}}}}}~~~~} xrrrqnkgfs}~~zussstrrqpljjjjkopqqqqplloqtutuussw¢¥§§¥¥¤ zrmntz~~z}}xuvvvv{}vtxwpjdbcejnrrssrrttttvvwwwwyyxxwwxxyzyyzzy{zzz{||}}~~}}}~~~~}}}{yzzzzxxxxzzzzzzzzyyyyyyz{{{||||||||{|}}}}~~~~ zvuupnjgdp}|zuttuutqswtomhgfgjmmmpnmlnrvtustuw~ ¡¥¨§¥¥¢ ~wolquz}y{ zvuuuux~vnkligfebbeimuuttttttttttuuuuvvwwwwwwwwxxyyxzzz{|||}}|||~}}~~~~}}}|zzzzzzzyyzzzzyyzzyyyyyyz{{{||||||||{|}}}}~~~~ |{|xrjfs}zwuuuttromu~{wpkgefggilljjkmpuvvwx}£¥§§¦¢{sqsy{x{~wtuuuwzxqponoqrsrrtxzttttttttssuuuuttvvwwwwwwwwvwwwwwyy{|||}}||||}}~~~~}}}{{{zzzzzzzzyyyyyyzzzzzz{{{{{{{{{{z{|||}~~~~}}| |~ you{xuuuwvtrmhn{{snjc``dfgiljknpruwz £¦¦¦ysqvyvu|}}~|vvvuuwzwprtwwwz|~ ttssttttttuuttttuuuuuuuuvvwxxxxxxxz{{{{{|||||}|}~~}}}{{{zzzzzzzzyyyyyyzzzzzz{{{{{{{{{{z{|||}}}}}~~~~}}~ yustuwvsmgfo|}xpdbbabcehikmoqs|¢¥¦¦{ttqnkjkhiiinstsvvuuy{ztsuwz{~ttssrsttuuuuuuvvuuttstuuuwwwvvwwxxyyzz{{{{||||}}}~~~~~~~~||{{{zyyy{{{{zzzzz{zz{{{{|z{{{{{|{{||||~~}}}}}}~} |z{yxtqlhehozysjhgc``afgghimr{ ¤§§¡yqlifebaa__bgmpqrtuvx~}vuvvx~ zttssrsttuuuuuuttttsssttutvvvvvwwxxyyzz{{{{||||}}}~~~~~~||{{{zy{{{{{{zz{{{|{{|||||{{{{{{|||||||~~~~}}}}} |umjhhmttnkjnokjiiggffghkrx}£¥£~~~||{xxwvtooifedccb`]\_`cgmpsuuxz{ww{ ssttttttuuuuttsssssstuuvvvvvvvvvvvxxyyyz{{{{{{{|}}~~~~~}}{z{{zzzz{{zz{{z{{{||{{{|||{{{}||}}{{||~~~~}}|~ |tpqqomjmrssqtrnjgeddhmrv| ¡}}|zyyzyxvuttsqolhgfeddcceffbaaadgkntw{} rrssttttuuuuttttssssttuvvvvvwwwwxxyyyyyz{{{{{{{|}}~~~~~}}}|||{{{{{{{{||{|||||{{||||}}{}||||{{{{~~~~}}|~ ~{wrjgddfinrwz}|yyxzyyzzzz{{xywvtrpliidcacefhhgfdddefhlu ssssssssttutssttssrsrsttvvwwwwwwxxyyzz{{{{||zzz|}}~~~~}}}}~~}|||||||{{}|||||||{{||}}~~}}}}}}||||~~~~}|~ ~unhddfijlory|{zyxxxxyxx{}~}yvuspliea`cgghjjigfcccfkv ttttuuuuvvutttttssrsuuvwwwwwxxxxxxyyyyyy{{|||||}}}~~~~}}}}}}|{{{||||}}}|||||||||||}}~~}}}}}}||||~~~~~}}zoigghihhklnoqvvxxy{}~zwwurpnkjieeimoomkifccedem|ssttuuuuuuutttttssrtuvwwwwwwxyzzzzyyzzzz{{|||||}|~~~}}}|}}||||}}}}}}||||{{}~~~~~}}}}}{{{{{||||{{{|{} ~~ {phjiijllggjmossw|~yxxyywyyvuojjlmqqookjfcddbepuuuuuuuuuuutttttsstuvvwxxxyyxyzz{{zzzzzz{{|||||}|~~~~~}|}}||||}}}}}}||||||}~~~~~}}}}}{{{{{||||{{{|~ |} {rlihillheeghlqu~ }tppoqrrpppnjgddcbfrvvuuvvuuvvvutssstttvxwyzzyzz{{{{||{{{{zz{{zz{{}}}}~~}||||}}}}}}}}||||}}~~~~~}||||{{{{|||||||||} ~{~ zqkfgjmkjfcffgjnrv{ wpmrvyyuqponjgffecix uuuuuuuuvvvutssstttvwxzz{zyyzzzz||||{{zz{{{{||}}}}~~~}||||}}}}}}}}~~||}}}}}}}}|{||{{||}}|||||||}{z{ zpjijmqpnfcccdehhimr{|rmmt}}ytpnljihffgmx ttvuttttvvvussrrsuvvwyz{||||}}}}}}}}||||}}}}}}~~~~~~~~~~}}}}{{||}}}}}}~~}}}|}~~~}|}|||||{|||||||{~ ywy~ }qffgorokffcbdfdacehow xpnrzyqlknnligghnxwwvuvvvvvvuvttsstvwwwyz{||}}~~}}}}}}}}||}}}}}}~~~~~~~~~~}}}}||||}}}}}}~~}|{}~~|||{{{zz{|||||||{~yx{ uhehmqtqkfa`dikjfdejr~wppw xnklpqpkhhjmuwwxxwwwwwwvvvvuutuvwxxz{}~~|}}}}||}}||}}}}}}~~~~~~~~~~}}||}}}}|}~~~~~~~~}}||}|}|{{{zyxyzz{}}||}zw{ ~whdfkqsqlfeimsvurnqv~|vqt{~tmkptytmiijlt~xxxxwwwwwwvvwwwwvwyz{{{|}~~~~}}||}}}}}}}}}}~~~~~~~~}}~~}}}}|}~~~~~~}}~~}||{zzzyxwxyz{||||} yxz ~ zngefnvxsmhjov wppxyrllovzwpjhgglv xxwwwuwwwwwwxxxxvwz{|~}~~~~~}}~~}|||}}~~~~~~~~~~~~~~~~}~~~~~}}}}}}}}|{{{{yyyxxxwyzz{{||xuz}|~~ujcdny|xsnjmv~wppv~}wrqpuy{xqjggehq~ xxxxyxxxwwwwxxyywxz{|~}~~~~~}}~~}|||}}}}}}~~~~~~~~}|}}~~~}}}}}}}}|{{xxwwxwwwvxzz{{|||uv}z| {nbajx~|tljmt} zspt{}yxsruy{voiecchuzzzzyxxxxxwwxxyyzyy{{|~~}~~}}}}}}}}}}||}~~~~~}}}}}}}}}|||{zzyxxwuxxwwxyxz{||wx| }{|{j^\fu|{uoons} wrry~|xtvxwrjfebem}zzzzyxxxxxxxyyzzzyz{{|~~}~~}}}}}}}}}}}~~~~~~~}}}}}}}}~~~~|}||zyyyxxwuvvvvvwxzz{~ ~uvz ~ ~n_\^gq{|vpns}{uru{zwwxxrkjgejw{{yyxxxxyyyyyyzzz|||}}}}}~~}}}}}}~~}}~~~~~~~}}}|||||||{zywxxwuuvvuuvvxyy{~xuy }h^YZcu}zuqnt}}vrru{xvxvsomiefq{ {{yyxxxxyyzz{{zz{|}}}}}}}}}~~~~~~~~~}}}||||||{zzywxwvuuvvuuvwwxxz}~vx} {h\WXct}~ytor}ysmov{}|wvuqnkgfis~ \ No newline at end of file
diff --git a/media/test/data/bali_640x360_P422.yuv b/media/test/data/bali_640x360_P422.yuv deleted file mode 100644 index a7616ca..0000000 --- a/media/test/data/bali_640x360_P422.yuv +++ /dev/null
@@ -1 +0,0 @@ -DEHKLLMNNPPPPQSQSRRRSSRRQSQQPONMLJKJGGFCAA>>:;:;;88888899989::988:9767755544211100..--++)'))(())((((''(((('())))**++,,**++,,,,,,,,++++*)))))))))))((''&&&&&&&&&&&&&&&&&&''&&&&&&&&&&'''''''(()+,,-/0224557778899999999::9999::8876677:>EJQYahlquwxxwuqmjhhghjlnnoooopopoqstwy|~~~~|xrldYNA4.,)++++,*+++.4:BOZenx~ }wmgXH;/)%$$#$%%'*.178>CLS^emt{|zuoiaYSORQTTQSLD=60'%#$$""#"""!!#!"! #%&(+07=DKPW^cglrwxyxwuvvuuuok_WKFA??@@@@@@????==:99975543100/039<CNV]flqty{|zzvrmd]OE92,+)((''''&((()))+*-01236778=>?CFFJNU\bkptxz{}~}{yzvttx{ ~|zxutty |xpfXJ=4/.-,038=DNZbksx|~}{yvwwzz|ypdXOHEA><;FIILLLMNNPPPPQRQTSRRSSRRRRQQPONMLJJHHHEAA@=<;<<:8:::::89999:::98888876665544211100..--++*())(())((((''(((('())))**++,,,,++,,,,,,,,++++*)))))))))))((''&&&&&&&&&&&&&&&&&&''&&&&&&&&&&'''''''(()+,-/01124557888899999999::9999::999878;=DKQY_gosyz|||ztqljkklmooppoomlkjjjkkorwz~~~~wsk`UH<2+*++++++,,+,17?JVamw} {tk^P?3*&$$$%&(,/258=AEJRZcjr|zundZSOMTbpfVNE=6)#$###""#"""!!#""!!"$&+-4=CJPV\belquyz{zwuvvuuuskaVKEB??@@@@@@????>><:::755431000116<CMU]flqty{|zzwvof^RG;3.+)((''''&'(()))+,..0035689;=ABDGKOX^dkpty}~~}|{wsrrvyz} }{yyxy~ {umbRD92/...038=BMXbksx||zwwtssvxzxnbWNIFA><;HHIKLLNOOOPPQQQRQSRRRRRRQPPPNMLKJJHGEDA@@>=<;:8:9899899:9999::999988776655332100//..-,,++*(((((((((((((())))))))**,,,,,,,,--,,,,,,,,,,+*++**))))))((''''''''&&&&(''('%'&''&&&&&&''&&''''(((())*,..013345558888999988999999999:99778;>CGOU^bjqv{}}~{xvpnllklmnoommkigeca^adjmrw{|vqi^QB6.*+***++++++05=EQ_jr{ }xncTE6-&$$%&',/148=>BDHLU^eowzsh`VPN]euvo`fys@$!######""""!"""##$$*.39AGMS[`ejpsx{}|xvvuvvvsoj`TMD@????@@????@>==<;::755421/0..28;BMT\hnrux|~}|{vqicVJ>4.+)))((''(((())*+,-01134589=>@AEJMSZ_fkrv{ }|zwtrppquvy} ~~{yzz{}yrj\N>51-,,.037<CMW`irx|}{xwtrooruxwk^SNIFA>;:IJJKMLNOOOPPQQQRSRRRQQPPPPOOMLKKIIGFDCA@?><;;:9977999:::998:::999988776643332100......-,+*))(())(((((((())))))))**,,,,,,,,--,,,,,,,,,,+*++***)))))((('''''''&&&&(((('&'''&&&&&&&''&&''''(((()))+-/01223555468899998899999999;:9977::?DJRY^flsx|~}|yvsnljjihhiffefd`^ZXUUX\aeluz~|vmbWI;4,*,******++.28ANXcoy~}tj[M=0)$$$%+.168;?BDCFKOXdmu~}wncWOWl`ilpkdw^I+!$####""""!"""#"%(.3;@FMRY_cgnquy}|{yvvvwwwuph`TKDB????@@????>===<;::755411./..27;BJT\fnruxwz~}|wslcZN?81+())((''))(())*+-.013334679>>DINSY_aimtx} ~|yvtqoooppsux~~}{||~ {vofXI;3//0..025:BMU^gpw|~~~~|{xvtqmllnrv{ wj]RKGEB?<;HIJJLLMNOOOOQQRRRQRRQQPPPOPOLJJJHGGECA@?>=<;:9::888889::::::::99888865544322210000//----,+)))(((((((((((()))))))****,,,,,,,,,,,,,,,,,,,+**++*())*())))((''''''''''''''''''''&&''''''''((((((***+--.1112455678888999999::9899998769:;@ELSY`fmsvy{yvwspkhedd__^^^^_]ZWTRONOSX_fpz}xql\OB8/+*****+++,-28?IWamw{vm_RB4*%%%(.249<@CDEDFINU_iqzyri]U\ijqYIRiqcRWG! !$##"""#"""""&(-4;?EKSW]bdintyz||{xwwwwxwsoj_TJD???>>??????>>==<:997543211///06:@KS\ekrvwy{}{zzwqg\OB81-+))((''((')(**+,-/0232479:?DHNRY`dgmrv{ ~}xvtplkjjkmot{~}}}~ zslbRC82/..../26;BMWaiqwz~~}}{wusrmiiimqv| vgZNHGEC@><JJKJLLNNOOPPOOPRRQPPQQPPPOOMLJJJHGEDCA@?>=<;;:::99889:::::::::99888876543322100000////--,+*))((((())(((((())))++**++,,,,,,,,,,--,,,,,,,+++++,*))))))))((''''((((''''''''''''''((''''''(((())**++--.0103333567888999999::9899887778:;@EJPYaiouvvvtqolfc_ZYYXXZZXXYYXUOMJGEHPYcjs{}vmcWI<2.-****+++,,07<FS`ku~ yrdWG6-))).158:?BDFEFFLNRYdow~ {tk`\djkeWLQi[ONV\YT&""$$$##$"""$(-4:@EKQW]^bhlqvyz||zzwwwwvutpj`SICA==>>>>>>>>>>=<;:9975432311../38?IR\fmptwz{{}|zvqi_RG:2.)))((('((*)*++,,-./13369;@FKPUZ_dhmqvz} zxvtokhgeegjms{~~~}xri^O@71.-../226;CMXaiquz}~~~~~}{zywsmmkgggjnqx ~uh[NIGDC@?@KKKMMMMOOOPPPPRSQQPPPPOOPOMMKKIIHFEDBA?>==<;;;998899::::::::::9988887655321111111100/.--,,++******))**(())******++,,-------,,,--,,--,,,,--++,,**++))))(((((((((((('''((((('&&&''('''((((((()**++,,,-/01233466799888888::88986655678:>BGNW_gmqoomkhgb^XWTSRUUWWXXVVUPKFB><AGQ]fow {si^PB60,,*+++*++,-49BO[ht||tj]O>1++/479<>BCEFFGGILPX`jr{ |ulb^`S[NUTEOSJFDcjdZ%!$$$$$$#$&)038?ELQV[^`eimqstwzzzywvvwxxuqk`RJC?<<==>>>>>>==;;<:7786432210./.37>HQ[clqtvxz}~|zxtkaRG=40,)((((())+++,,,-../146:?EIORW\afinsux~ }{wspnkihdbcgint{~ }wqeYK=61-..-.148=FOX`iqv|}}}|}|}ywwsnkjgfccejqw~}teZRJHGDB@?JILLMLPNOOPPPPOPQQPPPPOOOMMMKJJHFDCB@?>====<;;::8899::::::::::9988887655321111111100/.--,,++******))**))))******++---------,----..---,,,--++,,++++))))(((((((((((('''((((('&%&''('''((((((()**++,,,-/1112335568888888899888755455669=AEKS^ehhffedb]ZURSRQRRRVVWWVSPMGA=879AJWaks} |voeYG:2.,*+++*+++,17?KXdq||umbRA4//269=?BDGFGGHHHKPV[emu~|xnc`UNG:C@59:>@CKT]S##$%%$$$&&*.5;?GLQXZ]`cfgkoqsuyyzyvuvuvvuph^QHA=<<<<======<<;:98777543220//..28=FRZckqvuw{}}||{tlbWK<41-)(*****)+++,,,.//258=BEKPTY^chmpsv|~}yuqoljgda_`cgjnty} }vncWJ<73.//./158>FO[cjpw{}~}||{zxurojgfcb``dipv~ ~tgYQKHGDBA@KKNNMMOOOOPPPQQQQOPPPPOOLLMLMJHFECBA???=>=<<<<;;;;::::::;;;;::::8877744432221122220000..-,,,**))******(*)))***++**,,,-----..........----,,,+++++++)))))())((((''))((((((((((((((''((((((((()**++,,+,--021122445677887777776655443568:>CJRY_bdb``^\ZWTRPPQSTTUUVVSPMGC;5124;EP\hqy~}wpj^PA6/+****(**++/7<FT`mxypeYJ;4369;?DFGGHHIIIHILQV_gqz}umd`TUWD;2CZ/-0AQEML8"%%%%$$'*04;AFLRV[^_bdeijjlqtvxxxssutttrmi_QH@<;:;;;;:;;;;;:98876534222100./19=DNZaiquvxz||||ytlcZM@81-+)))*(+++,,,-///26:>DHNSX^cikosu|}|wtpjhfca^]_cgimou{~ |ulaTF;740/0.026=BHR\dipx|}}~}|{wuqolgd_\\\\aiov} |sg\QJIFECA@LLNNMMOOOOPPPQQQRQPPPPOMLLKLJHHFDB@@??>?>=<<<<;;;;::::::;;;;::::8877744432221122110000....,,**********(**))***++**,,,,----............--,,,+++++++***)))))(((())))((((((((((((((''((((((((()**++,,+,--..00112445778877776655443333469=BINUWVWZ[[[ZZVUSSSSTUUWXVUROIC=60--27@KVcmv}zsmcVI;2-***+*)*++.29DQ^gt}|sk_QC97:=?CEEGGGGIIIIIIINW`jsyvhkpd]][MEBWY(+4<;7=DK'%%%%&(*05:@GLRW\]`bddgeefilpsvvvssutuurmf\PG@<999:;;:;;;;;:9::765342220//./27<DNXbiquvxz||||ysnf[NC82+,,++*)+++,-..0139=CHMQW\`fjnqvz|~}wskgea`\\\^behknptz~~~}~ |ulaTF=864200249>FLU]emsw|}|}|zwurmjeb_[YYXZ`jmv~ |sg]RLJFEC@>MMNNNNOOOOPPPQQQQQQQOONNMMKHIGDBA@@@????>=<;<<<<<<;;;;::::;;::98997765432233221110.011////-,,,,,++******++++****++++,,----//-.//.-....---,-,++++****)((())))(((()))((((((((((((((((((()))))))***,,++,,--///33454556666664455443211257;?EKOUVUY[[\[ZWWUVVVVWWXWVSOKF=71,*+.3;FP]gqx~{wpi]PA3.**)**))**,29@KWdny|tmeWJB?>@CFGGIIJJJJHHFFEGMWcmw|}skei`ed`VA<C:1*2.,0<Ab2%%&&(-06:@FLSXZ^_bcdddbacdjmrtsttssssspme[PD=988889:;:::::998875542221/00/016;EMXaipuvxyz{||yxqh_SD:3.--++,,,,-../139=AFLPV[aejnrvy| |{sjgb][YZ]_cehkmpsvz|}}}}~ |uk`SF>98541147=DJSYafnuz}~||{{xspjga_ZXSRSV^hmw}sh\RMHGDCA@MMNNPPOOOOPPQRQQQQNNMMMMLLJHGEBA@???@@>=>=;<<<<<:9;;;;::::99::98777765432233221110.0111///-,,,++**++****++++****++++,,--..---.//.-....-----,++++++**))))))))(((()))((((((((((((())(((()))))))*****++,,--/0100143556666664444332100146:>DINQUVZ[[\\\YYXXXXWWZXWTRMGA94.*)+-07@KWalu}~}ytmdUG:0+*********07=ES`jw~ |woh`TJEBDEGHHIIJJKKHGBBBBEP[gttoiflgegeXJC@G9123,9<EIcK%%(*,18;AGLRVY^_aacde`]\\^dhnqtutsssssojdZLA=;887789988888776665553221/0/..26;DNYbipuvxyz{|||xri_SI<71//----..//137;AFKOU[_ejnruw|~ ~zumg`[XYZ\_behkloqtw{{}}|~~|uj_RE?:965547;AHPV\cjsx}|||zwsmh`[XURPOOS\fmw {sh^RMHFCB@?MMOOQQQPOOOOPPOOPPONLLKIJIGFDBAB@@????@@===<====<<;;::::::888898776665443333222022022200/..-,,,,,,++**,,+*****++++,,--.........///....-,---,,,++++****))))))))(())))))))(((((((((((((()))')))***+++++,,---.//1231455665542433121./2569>DHLQUX\^^]^][YYYYXWYXYTSOIB=6.+(()*.4;FR^hqy~}{vph\N@4-*)**)****.6:BP\hq| }{undZQKFFFHHIIIJJJIGD><;=@JVctihigcfd`^B3LSX<@RSD<:CHXfU<$,08=BEKQW[]dfeee`\\WUVY]djqtvtsstrplibWJ@;6666788887777666654331//10//-/14:CMV_gotwwy{{{{|yslaVJ=740/////./025:@EKQW\`ekmrty{~~~~~zvqh_YVVY\_cfikmnoqtw{{{{{}{tj^RIA<;:888;>ELSY`fmtz|~}}|{wtolc]VSOMIHLQZemx~yqj_TNHECA?>MMOOPPPOOOOOPPOONNNMLKKJIGFDBA@?@@????@@?>=<====<<;;:::::::99:877766654444333223222211000/.-,,--,,++++,,,+****++++,,--.........///....-,---,,,++++*+**))))))))(((())))))(((((((((((((()))')))***++++++,,..-./0101222333322321110./2569?EJNTXZ\__``^\[[[[YXY[XVQLE=6/*))))),07ALXbnu}~~}ysmcUI90*)))()****07>IWdnw~|wqkaXPIGGHHIJJJJKJE@<969;ES^pookie`]YSMIJLS``__XKA8+;Rnn5(7<BFNYY[`liTMVa[[aRPPUZaipuutstrponh`UI?856656776666665544332220///.../04:CMV_gotwywy{{{|ysmcYL@:62111111348=DIPT\`fimruy|~}}}}|ytld]XWY\^bfikmooprvy|z{{||{tj^PGC?>=;;>@CJQX]ejqv{~~}}}{vrmf`YRMJECCHQ\eq| ~wsi_TMGDB@@>NNOOOONNOOONPPQPNNLLJJIHGFCA@@@@?>?????>??>===<<<<<<::;;::;;:9887776565233443344332221110//--,..--,,,,,+******++++,,--........00//....,---,,,,,,*,+*))))))))))))))(())(((((((((())(((())**))))*)****+++++,,-/////0111122122110/.01149<@GLRVZ^^_`aa`^]]][YY[ZXSNGB:3+()))))*.3;FS^itz~{wog[N>3*)))******/5<DS`lv||xuog\RKHHHIKKKKJHEA<82216AP_aniRhdZTRGQQJQYabaa`]U?4<I[rr:6>ILcmhcmwJOKTZ[YWJIKNV`iquwuwtssqkh^SF<643344554444555553221200/.,.././4:CLU^gnswwzxyz{||todZQC;663222327:@FMUY`ekotwz|~~}}~}}{xtphd[YZZ]afilooqrrtvy|}|||{ {rj\PIDBAA?AAFIQV]bhouy}~~}{xtpjc[SLFC?=?DO[gs~ }yskaUMEBA?>>NNOOOONNOOOOPPONLLKKJJHGDB@@@>>>>>?????>???===<<<<<<::;;::9988887776565565334455332221110///.-....--,,,+++****++++,,--........00//...-,---,,,,,,,,,*))))))))))))))(())(((((((((())(((())**)))))())))++++*+,---.//011111101100//.0126:=CGOTY\^`abcca___\[[ZZZUNKC=5.*'(((((),07AMXdnv}}~}yslaTE7-*))))****-39BM[fq{}zupi`WPMJJIKKKJIFA=84/-,1;QirsmM]aWUONPMGBNVb`XUX]T=9BT^qZLO[m~wjh[Ybmpwtd\P@AJT_ksuwvvtssqkh^SF<6422332222333333432210/-..,--,,-38@HS\hoswxxyyz{{{tni]SG>8765556:?DJOW^diotxz~}||}}~~}}~zywsqngb\YY]acglmprrrsux{}}zz{|{rj\RNHFFFEGIKOW^cflrx|~~~}{{ytmf^XOGA=9:<FR\hw {vrkaUMEA@>>>OONNOOOOPPOPNMNLKJIHFFFDBB@@??>>==>>??@@>=>>====<<<<;:<<;;;;888777665555555554554432222210000./0.--,,-,,,,,,+***++,,------//0001....-,--..---,,,,,++****))(((((((((())**))(())(())(((())))))))))**++++*))*---....//0111100///0./0137=AFLPVZ^`bdeddabaa_\[ZYYTOF?9/*('('&&''*.5<HR]gqz}}zvpfYL=3,****))**,16=IWcmw~|zvulg^UOKMMMNMJGB>:41+**.9Pq~xe`\GYYUQRNLMHQVYLL\_KGA*FL]TO[ntosmhmlepzqj\F=KUalswwxxvtsqng]PD:310011112211100111/0/----,-,+,,028>GR\enrvwwy{{{zzwrk`VH@<96689;BGLS[ahmru}~|}}|}~}~}}zzywurpmgb]\]_beinoqrrstvy||}zz{}|rh]TPMKLLKLNRX^beknsx}~|}||ywrle]QH@:777>GSbny {wslbVLFA?>:9OONNOOOOOOMNMLKJJIIHFCCA??====>>>>>>??@@>=>>==>>==<<<;<<;;;;988766665555555565554444442211100/0/..--.,-,,,,,+***++,,-----.//0000....----...---,,,,++****))))(((((((())))))(())(())(((())))))))))**((((**)*,,-....//00000////--/01459?CINTZ^`cefffeda``_[ZZZWQLB<3-*('('&&&')-18CMYcmw|~}||xql_SD6-**(())***.49DR_jr{~~{yupjaXQMLLLLKGC>:5/+('+/5Ts||{o_VHFVVQU[RKT\[jRIZdeZGBFKOJXf_lmqo{lgl_[pfgKU@HWdnswwxxxwvsng]PD:20//000000//000000/..-,,,,++*+-.17>GQ[entxxxxyz|zzwqkbXNE?=98<@DHOW^eipty~ |{{z{}|~~~~|zyxvvutsrpnhc__`adgjnpqrsvvxz}|{zz{}{sj_XTSPPPQRVZ\bfjoqvz~~}|||yvnh`YND<8447>IVdpz }yxqlbVLE@>=:9ONONOONNMLKMLKJHIHEDCB@?;;<<:<<<==>>>>?@>>>>====??==<<<<<;;::877777766667754576655554443211100100/.-,-----,,,+++++++,,--.//////....---.....-,,-,++********(*((''(((())))(((('((())))))))(((((())))))**)))))*++,,.///0/00..//.../166=AGLRX]`cefgijgfca_[ZZXWVPF=8/*('(('&&&''+.5=IT^iqy|~{||}xpe[J;0+)(())**)+28BLXdow}|{{zvsne[TNLJLJGD@;50)($"'+6Ybtwxo`SLJZ[TUa_STZ\^]ST^ldTJM^NQ`ikurfko[IGTV]RXSQRMKVdntwwwvwwvskh\PC92.----.-..////.....-..,,,,,,*+-/17>GPZenquxxxxy|{{xsng\OGB?<=BHMRWaglrw|}}zzyyyz{{{{|{zwrsssrsrpnifb``deiloqrsuvwz|}|{zz{}{slb\XXWUUVX[^agloruy|~~}|{{ytnf^SJ@83137@LXgs} }|xurkbVLC?<:99ONNNMMMMLKKKJIGFFEECA>>=;;:::;<<==>>>>?@>>>>====>>==<<<<<<;::99977776666777788665555444322211110//..------,,,+++++++,,--.../////...---.....-,,-,++********))(((((((())))((((''(())))))))((((((**))))**)))))*++,,+-/./...--//./.137;BGLRV]_cfgikkjgfca_][[YWRIC;4-(''(&'('&'((,19BNXcmvz|~{||{wrj`PB5,)(())**)*/5<GUcksz~}~}}{{yuph_VQMJIIEA<70*&#" $*;_afkqmeLFU[\VTX[YOXRQZX^`gibR@VNDe~ynf_V^K:8=QVHPSGFGKVdouwxxwyywuli\PC90-++++,---....-----,--,,,,++**,-17>GPZdmquxxxxwzzzwtoh^RJE@CFKPW\ahmtx} ~~}}zzxxwyzzzzzxwusqqqqrrpokfbbcdfiloqrsvvwz}}|{zz{} {slb^ZYXXXZ[]`gkprsx}~~~}|{{ytle[QE;52239BP_jv{xxvslcVLC?;999OOONMMNLKKJIHGFFDCB@=<<;77998:<<>=>>>>??>>??>>>>==>><<>>=;;;9887777766665678666646555554322102220/.//,-,------++,,++----../0....///.....-/-,--,+****))))**))))**(())))))))(*))(())))))))(((((())))))++++++**+++,----....../000227;?FLQU\`acgjjmmigfca^[[ZWSOG=70+((('&&'&&())*/4<GT^hrx{|{{}|ytmeWH:0))))))*)).28BN\how|}|}||{zxtmbYRLIIFB;63+&#! ##(;MUhfc\Y2@Z[WR]QPZVZUP^[W^`jeW4ONNoordbgXZWM8=GCQWD:6?M[gpvxxyyxxvtni_OB80*++,+++++**+-,,+***)+,,**+)++--07>FPZclqwwxxxxz{{xvpi`VNGFHNRY]dipuz }}{yvvuttuuwvvvwurqqpqqrsqokhdccdgiloqrsvvxz|}}{zz{~}vkda^\[\\_abeinsww{}}}}|{{zwrleYNB94224<FTcnz |yxxvtneXMC?;99:OOOMLLKJKKIHGFEEB?>;:::978999;;;;=>>????>>==>>>>==>>====;<;;:9887777666666778966675555543331121100010---------,,--,,----....-...///.......-,,,,+****))(())**))**))))))))))))))(())))))))(((((())))))++++,,**++++++--....../00237;@EKQVZadeikkllijifc`^[YXVPI@91-*)(('&&'&'&&()-27ANWbnuz{|{{}{wqi^N?4+*())))))*05=JXalt{|}~}{{|zuof_UNJFC=72,'#!$""#>`^RV_^\URRUZ\]T`\LKZ_^gSJ[c`\VFWKRg_dcgeeedQ4:HECB=/9CP[hqwxxxxxxvtnj_PA70****)*+**++,***))))))))(()**+,.27=EQ[cjruwxxxxx{{yupjbXQMLRV\`flsw}|zywtrpoprtsrrtvutppopqrrrmjebbcdgiloqrswwxz}|{{zz|}}vkdb_][^^acfjorvy|}}{{{{yvpkaUJ=73128>KXds} ~zyxwvtneXLD=:89:OOMMMMKIHGGGEDDA>=;:9887678:;;;;;<====>=>>>>>>>><>=====<<<;;::9877776677777777888876545543332211011//..-----,,------------...-./../.----..-,++,+**))(())(&(&)(**))))))))))))))(())(((())(((((((()))***++++++++++++----....00228<AGKQUZ_dfhkmlnnlijda^]ZWWSOE<5.**)((''''''(('',/4=HS^jqwzyz||{yrpdUD8/+)(((((()/3;GT`grx{}}}}|wsk`UOEB>94+)$!!#! /eg[FLd]VSQUURDN_iRBNXTW^VY[[XUUSPBHW]h^fcfk^<2EC=DE71:CQ\hqvxxxxvvvtoi^O@6.)''''())))(*)***)((((%%'()**+--16>ENWbkqtvxxxyz{{xvrnc[VSUX^dhms{~~~}ywwurnmllmklmpstrqonoqrrqrliec`befhlnoruwwxz}{{zzy{}}vleb_^^_`cfhlotvy|}}yzzyvtmg^OD;6433:@O]iv {wywvvtogYMC=989>OOMMLKIHGFEECBB?;:998787678:;;;;;<=====>>>>>>>====<<==>>==<<:;:9888877777777778866787655554333331110//..--++,,------------....//../.----..-,++,+**))(())()**)&))))))*)))))))))(())(((())(((((((()))***++++++++++++,,--.../0158<BGLQWZ^cgilmpqpqmkgc`^]ZWTOH@93,)*)((''''''(('')-18BNXenuxz{{zzzvqgZI=/*)(((((((-17?MXdowz}~~}|zuof[PGA:4/)&"!!! !7YghZ`]c_HMWTROQ^lT5@IKGS[^\WOFEEBHKJV`N]``e[QGDTG9((3:CQ\hqvxxvvvvusoi^O@6.)''''''(((())**)(''''%%'()**,//38>EQWbkqtvxwwwy{{{wunia]\]`glpsw}~}|zyutpokjiihjkmpstrqonoqrrrpkhca`bbdgjlmotwwy{|{zzzy}~}vledbaaabehkosvz{}~~}|yzzyvsleYME;6435<DSamy }ywvvvvtldYMC=89;@NMLKJGIHGFDB@B==:987766667889;::::<<<<<<==>>>>>>==>>==>><<====;;:9999788889999886788766665443333332100..--,,,,,,,,------........///....--.-,,,,++))))))))*/28-')))))**))))))))(((())))(((((((((()))*+*+,,*++,,++++,,,-//00159>DIKQW[_ceikmoppprmhea_\ZXWSLC<5/)('''''''&''&&(''*/5<IUaisx{zz{|{xskaQ@3+((('''''+.6=IUamuy|}}yrj]SE>6/,&# !! !#Ohmhcab`_SPSX\ZdTEC=DLP[`VPV?BEBEFHPQPOZW\_TIOU\O,.49CP\hquvvuuuuvtnh]N@4,('&((%%''&&))(()&%%%%&&&'(*,.1148?FQWaiosvxuuwxyzzyvsmgdcdhlrv{}}{zxuuqomkgdcfhjmorttrqmopqrqpmkgeaabbcfiimoquyyzzzzzyy| }vkffdccdfjkmouwy{}}}}|z{{zwqkcXMA95237?IVdo{ |xuuuwvtph[OB<7:=EMLKJJHGFECCB@=9966655444666678:::9:<<<<<==>>>>>>>>>>>>>>=<====;;::999888889999998777776665544333332100/---,,--++,,,,,,--........///....----,,,,+)))))))))*6CC4'())))**))))))))))(())((''(((((((())*++++,,*++,,++++,,,-//0258>CGMRV[`dfikmoqqppokfc`][XUTOF?81-*('''''''&''&&''')-28CR\fpuy||{||yuodVF6,((('''''),3;CQ]juy|zum_QF<3-*%"!!! !5Wmy\^ba^YXJKPZb[]YMBHFGKYWPPYC4:KORJHHFJLPPPLN\hVO)-5:DO\grvttuutvurmi]N@4+'&%%%%%&&&&()(('&%%&&&&()*,.03459?FQXajosvxuuwxy{{zxvrmjiiorw|~}}zxwuspplieaabfhknqrttqpnopqqpomiebaabccefhjnruyxz{{{zyy|}vkffddddfilnsuwy{{{zzyyz{yvpjbWI>85348@M\iuyvtttuvtog\QE<9=CJONLIGFFEDA?=<9666654434665557788::;<<<==>>====>>??>>>>??>>==<<<;::::::::99::::998899757776555555332200..--++++*+++++,,--------../..////.---,,,,+**))))**))2:9-)((((((((())))))(((()'(((((((())((+++,,+,-,,+,,,,,,,,.,..0237<CGMRW\_ehjmnpprrponkgc^[ZWTPJC93,)))''&&''&''''&(''&+/5@MXcntxzz{||{xsj\L<0)'&&'''()-29BNYenw|~~wqgVH;1-*%$#!!##\c_uQ>TJXdUA=O@LTSPNQ:;HMKHHNJUY\^behhaJBKFDBMNLYfVW+,2:EQ]gqssrrrstusni^OA4,(%%$$$$$$&&&'(('&%%'''()+-./1247;@FOV`hosuwuwxx{}}|yxvsqqrwy~~|{{xwuqpnkgc_acfhjlpsstsqpoooopnmkgca_``_acefhlqtx{{{|zwxy} |tlgfeeeefjknqrtyz{{zyyyxxxsmg`UG=9767;DR`lxxussttttqg]QE=>BGNLKJGGFDCB@>;97553333323455667988::9:;;==>>==>>??????@>??>>==<<<<;:;;<;::::::::998876778876666655332210..--++++*+++,,,,--------../..////.---,,,++**))))))))**++)((((((((()))))))(&)()))(((((())))+++,,+,-,,+,----,,,././047:@EIPV[`dgilnqrrropnlhda][YVRLE<6/+*))('&&''&''''+5</&(-2;GT_kty{{{|||{unaQB5+'&&'''()-1:ANYantz~zsiZJ;/(%&%$!!"7uYKB<;KOVaT?12;@?EBGJA8FIKEIEN[^`bdeecZ>=ICEB^NCP[\J(+3<EQ]gqssrrrstusmh^O@6,)%%&&$$$$%%%&&&&&%%$%')+,/01346:>CIOXaiosvxxwvxy{}~}{zwwwy}~|{zxusoolgb_^_acgjlorssutqpoooopnokeba_^]_``ddglqtwxy{|zwxy~{rlgeeeefgghmpqsvxzzyxyyxxvslf_QG=:99:=FTbmyxussttttqg]QD>AHLPLLIGFECA@><965432212223344557888999:;;<=>>>>??????@@?????>?>====<:;;<<:::::9::99998677777568774433211/---,,+++,,,,----------.......////.--,,,+***)))((((((+5B<,'''(((((((((((()((((((((((((())++++++,++,,,,,------./..0159>DHNRY_behklopssoommkgb_\YXSOI@81,*))*''('''''''&+4-'''+.7COZhpxz|zz||~yqfWE7,('&'&'(,/49BKUaksz}}vl_O;-$$###$#%<kL;453=LLbbaUB='799:BA=GKJLPTX]_`adbc^T<5@CEEjmMICDH105<FR[gqsttssttttmg^PA5-)%%%%$$%%$$%%%%%%&%'))*,.023579:?EKQYahosvxwuuwz}}~~ }{zwvqnnkeb``abdhjlppruutrrpopppnlmjfb_^]]]\^`bgjpsv{{{{zyvz~{rjfeedcbabfikosvwxwwwxywwtpjd]PD=:88<BJXer| |wtsssttsqi_RGCFJPVKIGFGDC?<:9754321101002244444788889:;;<=>>>>??????@@????@@?>====;<<;;;;::::9::99997679887877775432210.---+++++++++,,,,------......./---,--,,++**))))(((())'61**'((((((((((((((()(((((((((()))*+++++++++,,,,,----..////137:AFMSX\`dijmnprqqpnllhd`][XWQKC;5.+)(()))'&''''&&'%$%'('*.5?LVcowz|{zz||ztl\K</('&'&'*+06:AJT`jry}yqbR@1(#$$$$$'BnY>6;58AQbioqhP=F>46CGFPNMNVY[^`abb`]XTR5EGGBNaMJU]K.16=FR]iqsttssttttlh]OA5-)%%%%$$%%%$%%%%%%%&'*+.002457:<<AEKQYahosuyxwvx{~ }|yvsrmlifcaabbcfgkoqrtuutrpooooonlkhd`^]\\ZZ[\acinsv{{}|ywy| |siedda`_^`cfjosuvvuuuuwwwtpkcZNE><;;?FQ^ivzwtsssttsqiaUKFJPVZKIGEEC@;9865422100////11333356897889;;=>=>????@@@@A@@@AA@@?>=====<<<:;<:;<;:::99:97788888877775533210.--,,++++****,,,,,,----....,./.---,--,,****))))((((**'&(((())((((''''(((((()'((')))******+++++++++,,,----....000036:>DJLT[_aeijmonooonkjjfb^\[XTOG?70+*)))*2/((''''''&&&%$&#(-1;HUamuy||zz|{zun`P?2*'%&()*/39=AIS]fov}{tiZF6,&$##%(([kX<6<BAIYentwtVKHCADLPMGCLTXZ]``cba]XTNF>BACDKZTPLL9117>HR^jrtttusttsrli_QB5-)%'''%%&&%%%%&&%&''(,..01356:<>@CHMR\bhosvxyxxy{~ }|yvtpliidb`aabcefilnqstutrqooooponlieb_][[Z[[]]^bglrvz{|}zwz} ~vnhdcb_`\\`ehknsuuuuuusuuusoj`YOD?>>?EKVbmvyvsrrttvurleVOJMT[_HHFCA?<98655222100////01213356777889;;=>=>????@@@@A@@@AAAA?>>>====<<<;;;<<;:::99999999888877775544320.--,,+++++**+,,,,,,----....,./.--.---,,****))))(((((())''((((''(('''''((((()'((')))******+++++++++,,,--..//..000136:AFLSW\`cgikmnlljjiihhc_\[[VSLC;4-**)))**)((''''''%&&&$&%(+.7CQ]hry|}{{||{vofWD3)'%&(*.26:>CJR\elu{|ypaO>0'###$%)DKF8??=>JZdoswr[TNA>ABMKEPTYZ[^``a_\XTMGB:8MB@GUZZVE,247>JT^jrtttussssrli^PB7/+('''%%&&&&%%&&&'(*++.03457:;>@BEKQT\bhosvxwwvw} |{vsokhfb_`bbaceehklpqstutrqoonnomnlieb^\ZYYYXYZ\`glrvz{}{xwz} zslgdb`^^\\`eiloqttssssstvsrnj`XNGC@?BIO[dpzyurrsttuusmd[SOQY`dHEDB@=984553101100..../11123566699::;;==>>????@@AAAA?A@@A@???>==<<======<<;:::::::999977997766555310..-,++++++**++++++---.--+--/--,,--..,,++**))))((('''((((''''''''''''''''''''(((((())**+++,,,,,,,--,----.0300../00047<CINRW[_cgiklkiigggfeca]\[YUQI?80,))))(((((('&''&&&&&&%%&')05?O[epwz{z||||xri[K;-&'))-157;?DKOYbkrz~}uhVE6+&"$#$(@NJ>799<JUbqtwtlNCB97>FFPRW[]^_a`^[WRLF@852TC;GPYX\@0269@KVaksuuttstttrlh_QA60,)('''%''&&&$&&'(*,-.0357:;==BDFHOSX^djosvwyyy{ ~}ytqnjhc_^`_aeefghilkoqrtttsqoonmnmlieb^[[YXYXWWY\`flqvy|}{{{z~ }wqjfc_^^\]^afinprtsssrrstttrog`VNHECCFKU_gr{ ~zvtstuuutrke_WVX^diGEC@=9864332200000..../11123566699::;;==>>????@@AAAAABBBA@???>==<<======<<;;::::9999::77887766553210..-,++++++**++++++,,-.---./---------,,++**))))((('''''((''''''''''''''''''''(((((())**,++,,,,,,,--,---../0..../11469?DKPTX[_cghiihffeedca`^^\[XSNE=4.*)))))((((('&''&&&&&&&&&'+.2<IVclwz{zyy{|zuk^N?2*))+/379>@EKOWahpx~~wn_N=/'##$%&BuwD&)3:HTcpvxunK;./27.;VYZ]^`aa][VRLFA93.-7?EQOOL_M955:AJUbluwvuuuututlh_QA60,)(''''''''&'&'(*,-.03579<=?ADFHJQUZ`djosuvxz{ ~~yvrliea`]^`_accfghillopqsusqpppnmnmkgea]ZYWVWTSTTY\djqw{||zyyz~ zupfc`^]\]]^afjlprsrppooprrrpme]TMJGFFJNXblv}yvtstuvuusnja[Y\`fkFCA=:86743111./0//.....011344568::99;;==>>????@BAA@@BBAA@@????>>========<<;;;;99:998998877666644111/---,++**+++++++++++++-----,.--------,*)**)*)))((('((((''&&&&''''&&&&&&&&&&&&''(('(**+*++,,,,,,-----.--..--...//0368;@HNSTY\_behecbaabb``__`^^[WRKA:2,*****))((((''''''&&&&&&%%).3:DR`lswzzzyy{ztmcSC4+**-257<?CFJNU]gnv~ysfVC3'%$##"5id,#'/:CSbntusk`6%%*+7LYZ[_`_a_YUQKE=71/.338HPQOUNRW45:ALYcmuwyxuutwtrok`RB91-***(''''''''()+-/012468;=?ACFIKNSW[aflptvwxz}|ytolgd`\\^``acegffgikmnprtusqqoonomkifda]YVUTSTSTUX\dkpux{zxxxz~~ysleb`^]\[]^bejmpqrrnnnorssuqmg_WOMIGINU\fp{}xusttvvwwuqld]]_djoCA<:986543111...//.....0111245688899;;==>>???@@BAABBBBAA@@??????========<<;;;;;;:998998877666644111/---,++**+++++++++++++,----.---------,*)**)*)))((('((((''&&&&''''&&&&&&&&&&&&''(('(**+*++,,,,,,-----..../..///001269>DINSW[^^bedb`_][\\]]_```][VOF>6.******)))(((''''''&&&&&&%%(+28BO\iqvy{zxxxzvogXF8/--/38;>@DGJMT\dlv}}vl]J8+&#"" 3L="&/48DRantwxZ=$21?MUZ[[_`a`]YVQLE?80,-/20.?MQRTMII65:AMZcmuwwwvuwwtspi`RB91-**))(((((())),,-013468:;?ACEJMORW[_cglquvwy{~ }ytnjgc`\[\^``acfffgjklnppsvusqqooolljfd`][WVUSRQPQRS[aiouy|ywxxz~}vpga_^]\ZZ\_dhjmopppnnnorsttqkf^VPNLKMRXbkt} }xvtuuvvwwurngaabgmr@=;:8865432200..--..../111225677::::<<==<>??AAAAAAAABB@@@@??????>>>>====<;;;;;99:9988788666655554110-,-,****++++++,,++,,,,,,--,,,,,,,,,,,,))()))((((''&'((((&&&&&&&&%%&&&&&&''''''((()**++++,,,,,,----.///00..////2048;AGKOUW\]_bba^\\[[Z[]]`aa`^YTLD;3,******)))('&''''((''''&&&&'*/5?NXeovxy{zwxwtoi[M>411269>@AEGJMSZbhszxreS?0(#$.+10&$&226CQ`mty|iIJ[[YXXY\_`__]YUNJD=70--,.--.^SRONORI47<AOYdnvxwwuuvvwuokbRD:2-**))***'(()*,-.014579:<?BEHILPQSZ\beimpsvy{{unida^\Z[]]__bddefgiklopqttsrqpoonnkifb^[YVUTQPPOOOSYaiovxzzyxyz~{vmb^^][[[Z]adiknqsqpoomorsttpkd]VQQOOQV]fow |yvutvvvxxvuqjecehls=::86555432200.---..../111435677999:<<==>???AAAABBAABBA@@@??????>>>>====;;;;;;:::9987888877755435322/.-,****++++++,,++--,,++,,,,,,,,,,++++)))+**((((''&&'''''&&&&&&&%%%%%%%%&&&&''(((*****+,--,,,,----.///00//00/0336:?DIMSVZ\]_aa^\ZZYYXZ]_bbb`]XRI?61,******))*))'((('((''''&&&&'*-3;HT_msxyyxwwwurjaQD7337:<?ACBEHLOV`gpz}tkYF5+&&=6&%%!%+/6CQ`mtxzzxrme][Z\^_`_\YTPJD=5.,+,----7uXQNLJS?/6;DQ[cmuwwuvvvvwupj_RD:3.++*)**+*++,,--/024689<>@DGJKNRUW[_cgknstvx{~}uoid^[YYZZ[^__acdefgiknoqqttrqqpoonnjhfb]XWTSRONNMLNQWahpwzzzzz{{ ztlb^[ZYYZZ]afjjmqpqonnmoqrssold]XSPPSUZaks{ |yvuuuvvwwwvqjhgikos;:876644322000.-....../12233566699:;<<==>>?@BBBBBBBBBB@@???>>>??==>>==<<;:;:::9999888989::99:8997975520+*)**+-+-,,,-..-,,,,)++,+,,,,,,,,+*)**))(((((''&&&&&&&&%%%%%%%%%%%%%%&&&&(((())*****+,,,,,,--...///////001235:>CGKOUXZ[_`_^]ZYYYYZ^accdb`\VPE<6/+******))**))))('((''&&'''''(+09CP]iryzywwvwxvmeWH=679;>ACCDFJMMS^flu|zo^M<0'&#"$%$$$*/5AQ^mv{||{vnhc]Z^_`a]YUOID=6-+*+-....<jfPQNT^X87>DP[fouwuvwwvvwuoh`QE:3.-,++,,,,-,+,-.123569;>@DGIKNQTW[^beimnrsvx{ ytmd_ZXWWWYZZ]^_adefgjmopprsssqpoponmhhea[VTRQPNMLIKMOV_hptyxxwxxzyria]ZXXXYZ]afimproonnonquvtsojc]XTRSUY]gmu~ ~~|yxuwuvvwwwtroiginqu:9876654321111.-....../12233566699:;<<==>>?@BBBBBBBBBB@@???>>>??==<<===;:;;:::9999889:=>@@??@ABBBA>><83.+***./011121110/...--+,+,,,,,,,,+*))*))(((((''&&&&&&&&%%%%%%%%%%%%%%&&&&(((())*****+,,,,,---...///////001358<BFKORWX\^^^_]ZWVVVVY\accdba\VMC;4-+**++**))**))))('(('''''''''')-4>LXgpuxxwvuvwtqhZPB::;=?ADCEFJJLPZbjrz~zqgVC4(&##$25!#)-5AN^luz}{xplf`^_`_]YRNIB:5/,+*+,--..2QbSSSObgB7<FS^gpuwvuvvvvvspi]QE:3..-,,,,,,,+,-./14688:<>ADHJMPSVY\_cdhmqstww{ |wmg`[WUUVWYZZ\]_adeggkmopprsssrpoponmigc^YURPNMLKKIGIMT]fmsvyxwwz|xof^ZYVWWXY]ahjmmqponoopquvtsojb\ZXVUW]biqy~}}|yxusuvvwwwtrojhioqu98766544321110.-.....//123345677:::;==>>>>@ABBCCCCBBBAA@@@?>??????<===<<:99:::9998889;@DFHHHHIIKJJHIFA:3.,-/145679:::8854110/-+*---,,,,+***)((((((((&&&&&&%%&&%%%%%%$$%%%%%%%%&&((''))*****+,,,,--..-.....//0011368:>EKOTWX[[\^__\[ZXWYX\`cdefc^[TKA92,*+++++++++*))))))))''((((((&&),2:GUbmtvxxuuuxvskbSH=;=?ACDEEFFJMPW]fnw~~wm[I7+##!#*-##'.5@O\itz}~{yumfc`_][WQLIB92-+,++,,-//115FWQKNWV<8=GT^hrxwxuwwwwvsnh_PF:42...-,,,,++-/12335::<>@CFILORTV[^acgkprvwxy||yrkdZTUUTTUVXZZ[^_ceghlmnooqssrqpoqpqmjea[XTQPMKHGEDEFKQZflswxxywy|~wnf\XWWVWX[^bgjlmqromnnoprtsrojb\YYWW[`flt|~}|{||yvsuwvvwxwvromklpsw8766553332110..-.....//1234556779:;<==>>??@ABBCCCCBBBA@@@@?>>>========<;:;;::999879:=AFJMOOOPRSSRRSROIC931258;>@ADDDECB?<:9730/,--,,,,,+*****)))((((&&&&&&%%&&%%%%%%$$%%%%%%%%&&''''))*****+,,,,--....../////01357<@EKOSV[\\]_^]`^\[ZZZ]`acdedc]YPG>70+++++++++++*)))))))))'((((((&&'+07BP\iquwxvttvvsmgYNB=?@BDDEFGFHKOSYahr{{scP?0'$"!#""#'-2@MZitx~~{yungb_][WRMFA:2-+***+,-/00242LWKII[N68@JU`jtxxwwwwwwwtoj^OE<50....-,,,..-/12335:;=>ADHJMPRVX[^beimquvx{|}|wtng`WSURSSRTVYZ[^_adgjlmooprssrqpopomkgb_YUQOMKHFDCAACHOYclrwxwwx{}~wnf]YYXVWY\_bhknpoopnoorstutspib\YYY[]cipw ~|{x{}}ywuvwvwwxxwtqmkknsw6666553332110/-,..////0234676778:::<<<==>??@CCCCCCBBB@@>>??>==========<<;:::9989::;=@EINRVWWXY[[ZZZ\YTKC<:;>ACIKMPSTRONJJIE@=81/-,-+++*,**))*))(('''''&&&%%%%%%%$$$$$$$$%%%%%%%%''''()**++++-----..//////1/112248<?DIPRVZ[]^abcc`_^\\\]`baadeca]VMD;4.++**+++++,++****)+))((''''''&&()/5?LXforuuuutvwtpk_SHA@BCEFFFGGGIMMR[dmv}}tiXE5($"$#$##&,4@MZhsz}||yulec^\WQLFA:2-****++,-.//..3YTPIJMV=:AKV_lwyyxuuvxxvsnh]OD=62//....-,..-/023568:<?AFIJMQSVX\_dfjnrvx{|}~ }xrnhaZWSQPORRRSYZ[\_achjlnppsssrrooopolgda\WRONKJGCA><<?ELV_ipx{ywwy yne]XXXWXZ^`chkmnppppopqruutrnib]ZXZ^bgmv|~|zxxx||zwvuuwxyyywtqmkmqtv65666543321100.-0011113534677789:::;<<==>??@BBBBAAAAAA@?==?>========<<;;:::99979::<=AGKPSXZZ[\^^___a_\UMECDIMPVX[^_`b__]YVRNGB=62/.-,,,+++))*))(''''''''&%%%%%%%$$$$$$$$%%%%%%%%'''''(**++++----./////0/00011258=ADINRW[]^abdgffdb_^^^_accddec_\RJA91+++**+++++,++********((''''''(((),2;FS`kruwuuuuwvqngYNGCBDFGFGFFFHIILT^hqy~xo`M<.'#%#$$$(/9AN\grwz{{xtle_\UQKC?82,*****+,,-.//..2LdXJLLX=9AKWbmvyyxwuvxxvsmg]OC<622/110/.,../1023568:<@CFIKNRSVY]_dfkoquy||xtpic]WSQOOOPPRUVY[\^`chjlnppqrrqqqqqpomie_ZTNMKIFC@?;::<AJU_hovwwuwy~ xqg`ZZZY\[_cfhkooppoooqrsurtrmhb][Z[^djqx~zxttx{{xyvwwyyyyxwtqmknpru774455433211110..122113457777899::;;==??@>@AB@CCDDBBA@?>>=>>====>>>=;;:::99987889:;=BGKORVZZ[\]^adbab`]WPNOSX\_acejjhgffc_]XVQIA<52-++-++***)(((''''&&''&%%%$$$$####$$$$$$%%%%%%%&''''))++,,--../////000/001269=BFJNTVZ]]acehjkieec`__`acefdca[WMD>7/+,,++,,,,++,,++****++)())((((((&'*/7DQ]iruuuustxwtpk_TJCDDGEGFGGFDFEFMWcnx~{sdSA1'$$%&&',58DNZgrvwyyurme]WQJE?80+)))++*+--..//001CfVRNPV8=CMXcnuxzxxwwxxvtmg]PD<62322222/-./02213579:=@CGJMORUXY^acglprvz~ |xtpkc\WSPOOONPQSTVW[^_cdegmooopqrqqqqrpomhc^YSNKHCB@=;977:@KT_ipuwwuwz}zrid]ZXZ]\_aehlooqqqqprsuutsplgb\\\^_fmsz}yvstx|zxyxwyxxzzxwurmkmqtu775433432111110//022333568997788::;;:<???@@AAAAABBBA@A>=>===<<<<===<;;::9998778779<>@DILORUXWX[\_bbdfdc`[WZ^^ceggikkmikkigec_YUOH@92-,,**)**)(((''''&&%%%%%%$$$$##$$$$$$$$%%%%%%%&''''))++,,--..//////0011248;?DHLPTV^_bdeijkmllifeca`bbddedd^YSJA92.+,,++,,,,++,,++****+++)))(((((('&)-4@MYdnsvuutuwxwsofYOHDDFFFFGFDA?>BFP]jt||vjZF6+%%&&).17>FN[grvwxxurmcXRKD>80))*))+++,--..//000?baQLOLE<BNYeovyzxxwwxxvtmg]PD<843222221-.//0213579:=@CGJMPSVXZ^acglprv{ |ytqkc^WSQPOOPRSUUWYZ[^abdhjmooopqtsqqqrpolic\WPLFC@=<986568>JU`ipuwwuxy ypfb^\\\]^_bfinpqrrqrrtuvvvtqkea]\]_bhow~|xsqvy{zzxwwvxxxxxwurommnqs6654443210121121113334446577899:99::<;=???@A@AAA@@>@@@?>=<=>==<<==<<;:9999887666569;?BFJMPSUUTSTX]aceegec``bcfegiiiillkliigdcb`[ULE=5.,**)**)(((&&''&%%%%%%%$$######$$###$%%%%%%$%''''()*+,,--..///////01567<@EIMSWY\_dhjmnpprqojihdcabccbca`[TMC<5/.,,,,+,,,,,-,,++++++++****(())((((*-2<HUdmsvvvvvvyywti_SIEDEGGFEEB>;;<@LXdrzwp_L;.&&')-05;@IP\gqvwwwspk_UMF?5.+())**++,,,-..//1114NeRKOU^M?Q[gqwzzxxwwwwusmh\OE=854332220//////12479;>ACHKLNSUWZ^bcgmqtx~}ytqkf_XTROOORRUXXZZ]^_bcegjkmnopqrtssrrspnnid]VQKDA>;:762248?JV`kqvyxvx{ xogb`^]_``cehjnqrrrssuvuxvvsric^]\^bflsy{vtrtx||xxxwwxxyyxwtqmkmort6654443210122221114455556577989:::;;<=>????@A@AA@@A@??>>=<<=<<==<<;;::99989966654589<?BFHKJJIJJIOTX]_ehhhedegfeedddceefggfgihhheb[SLA60*))**))(('&''&%%%%%%%$$###########$%%%%%%%&''''()*+,,--..///////0469;AGLQRXZ`dgimoqsswuromjfecacdcbb`[UOF<51/.,,,,,,,,,,-,,++,,,,++++**(())((((),19DR_jqvutttvyzzumbXMGFFGGFEC?;878<FTbmvzseTB2)')+049?EMT]hqwxwwroi\PH@8/+))))**++,,,-..//1122=ghRPIZaJM[hsxzyxwwwwwusmh\OE=85434322000000004679;>ACHKMORTXY]^cglptx~ }ytpjd]XVTUSUXXZ\]_``bcdhiikmoopqpqsrsrrsrpomf`ZTMC=:97531028@KWakrvwxvx{ xogb``___adfiloqrssttvvwxvvrpic^\[^bgow}|xsppsy{{yxxyyxxyyxwtqomnprt55554442232233333455556666679;;;;;<<<>>>????AB@@@@@?>>====>=;;;;::::::998877555544557:9<>=>>:99;CIQW_dhihgfeeba\WTTX\\]]]`adfhjkmj`VLA4-*)**(()('&&&&&%%%%%%$$$$#"####"$##$%&&&&&'''((***+,.----//0001247<?DKPTVY_bgjmoqrvuyxwtqmieddddccb_YVQFA92,,,,,,----,,..----..-,----+**())))))),27?N]iqtuutuvwyywpg\QIGGHEGEC=74449AO_ky ~wkZH9.*-04:>CJQX_hqxxyvqoh\OD90*)))))****+,--...//103;RdTTGNNRQZgtz|zzyvvvvtqng\OF>:7665553110000013567:=ACGJMORSVXY`bhjnsx}|yvphc\XVWWVY[]abceegjklnnpqrssqqrssrrrqtrrqnje_XQJB<65532027@KValsvwwvz} yohdb`_^_bgfjnrqssstuxxxzwurlfa]]]`ejry |wqmpuy{{xwvvvxxwwyxutpoprsu55554444334344445555556666679;;;;<<<=?>>????@A@@@@?=====<;<<::;;::::::998776655433331456653300029@HQY]begefb_YTOHFFFHKLPQTY\afkmpnleYL>1,+++*))(('&&&&%%%%%%$$"""#####"$$$$%&&&&&''(((***+,.----//001249<AFKPUY\cginnrtvxz{|{wtrmjgfdccb`^YVOH@82-,,,,,,------..----....--...,+*))))))),/5>KYensuutuvwyyxtj_UMHFGFEC@:51024=JZgv~yqaN>30139>BHNSZ_hqvxyxsnh\L>4-()*)))**+++,--...//014=TNQOKQLBM]jv||{yxvvvvtqnh_RF>:7665533111100002467:=@BEHJMPSUY[^`eilrx~ ~{wtpjd^]XZ[[]`bdeiillnpqqrrvvvvttstsrrrtuttusokf^XPF@;8653238@KValsvwvx{~xohec`__`cefjnrsuttuvwyyywuqle^]]^`fnv||wplosy{{xxwvvxxwwxxutqpoqsu66554444444455555555665566899:::;;;=====??????????>><;<<=;=;;;::::::99887654543211/..0110--.,+,.58AJQY\]aaa\UMC:556878:;AFKQYcjlrvtmcUJ:1-.,+*))'&&&&&&&%%%$$$##""########$%%%&&'''(((**+,,----./001348?BHNQY]acfjmrsvwz||yzzvrnljeddbba]YRMIA:3-+,,,,------........//./012210--*)))))),/3;IWcmrutttvvx{wuoeWMIGFEC@=72-,.28EUbq| {vjYH:778>BGKOSZbjqw{yxtnh]M>3,))))****,,*,....0/0023ORY\RPRTKA[kuz|zywvwwvurmg^QF>97665432221100111345:=>ADGILPSUWY^_cfjqw~ }{xtojea__]^abdehijknprtuvwvxxwvvvttrrrstuvvwutrmi`\SHA:743126@LWbltxwvxz~woib`_^`bdghlprttuuwyzzyzwtnic^]]_chqx yrmkotxzzwvuvvxxxvxyvurpqutt6655444444445555555566668899::::;;;===>>??????>>>>==<;;;<<<;::::::::99886644442211/.....,,+,++,.04:DINRVYYXRI>40..-,+,.027?GPZcjquwskaUH<751/+)(&&&&&&&&%%%$$$##""########$%%%%&'''(()**+,----.//02358=DKNS[^cfhlnotwxz|{}yxwsplihecb`]ZXRKF?92-++,,,,------..//....///0124443.-,+**))),-2:FSaksutttuuwyzxoe]QKGFDB>:50,**06AR_my ~woaSC<;>BGKNSW\djqw{zyvqj^O?3,*())****,,,-,,...01123?bocRJKHPSaluz|zywvwwvurnh^QF>976654322211001113457:=>CEGKMPSWW[\_djpx~}zxtoieaa_`aacdfiikmmnqtwvxxzzyxxxutrrrsuvyyzzyvsphbZQJA;86538@LYdntwyxz{~wngbba`_adgimqtttuuwy{{yxvrmfa^]]`cjt| zqllotxzzwvuvwxxxvxyxurprvvu65554444555555555555557788:::;;;<<==><=???>>>>>>>===;;;;::;::9::9:;988976554321100//..-,++++++**+/5;AGLLONJC93-*))******+.4<FOZcmrwusj`RMD=:53-*(&&&&''&&%$#$$#"#"#####$$$$$$$%%&&''()))+,--..///0258<AJPT[_cgjmprsuwwz||yxwqonkgd`b^[XSOKD=61.-,,,,----.-..//./....//022446540/--,,*))*,09BP\gpttttttvxzyqi_VLGFC@<83.+().5?LZiv }tk[LCACGJNQSV\cirw{zzwqi^PA4.******++,,....//..0022MnaAKHJAMU`mv||{xvvwwvvtoh^QG?:855554411220011133556;>AEEHJMPTVW\]bhow} ~~}{yvrmhdbbcbacddfghklnoqsvxxyz{}yyxusrrrsuw{~||z}|xqkcZRIA:6448AMXdosvzyy~ wmfb^]\]_cfinrstuuuvxyyzwtpld_]^^cgow| wpkipuwyywwwwxwwxvxzywsopruu65554433555555444466668889:::;;;<<<<>=>???>>=====<<<::::<8::;999899988765544220/....--,+******))**-4:=@CA@:4,+*)****))))),.2;GP[eluvtqiaZROJE@:3,(''*++*(&%$$$#""######$$$$$$$%%&&''())*+,--./000148;BGMTZ`deknqqtwwxy{yvtspmkhhdb_]XURMGB;51---,,,,----....///.0000//1135456520.-,,,**,-18BMZensuttttvxywrj_VLGEB>951-)(),3:FVdr}{qbUMHIKMOQSX\cgptvwwwsmbSE>;,*++**++--...../..0246VcJBJGKIHW`mv||{zwwxxwvtoh_RH@:8555544442201111333568;>ADFIKNQSUX\bhpw} ~}{|ytplgcbcccc`cdefhijmopqrtwy{||zzxusrrrsvy{~}~|yrkdZRH?7248AMZfovxyy{ wngb^\[]_bejorsvvuvxz|{xwsnha^\\^bhpw~tljjotwyywwwvwvvwxz{ywtstuvw556677555556766677777788:::::;;;==<===>>>>==<<<<<<;;;;;;99<;::888777655555223///.,----++))))*)(()(),1569840,*(((****))))))+-39EP[equvurlgb^YQMF>80,-23322-'%$$##########$$%%%%%''&(())*+,---.//0147:?FMSZ^dgjlnpqrvwwwusromkifb_\[XTRNHD?730----,,----../-.0/../111122224467641/----++++.15>JUdlrttsssvwxwslaWLFBA<83/,)''*28CRaoz }wl_UONONQRUX[`fjrwwwwtmbTINJ-)))**++---...////016QlG7<9?IIJVamw{|{yxwyxwvuqlaRG@<96655433333111133443689<>AEGJMNPRT[`hqy~}|zyvrnieba`aaaaab`abdegghjnrsuxzzzxvtrrrrtvwy{z~~{qkbZQE<549DP[fpwyyyz~ypje\[ZZ\`djnsuuvvxyz{{xvqkd_[YYZ`hs{}skhjnsxyxwwuuvwxvx{{zwstuuvx666677556666766677777788:::::;<<==<===>>>>==<<<<;;:::::::999998887776555543210/..,,,,,++*)))))(())(*,-00-*))(())))))(())))*+-28CP\ipuwutpllfa[TKE?:;?@A??71,%$##########$$%%%%%'''((***+,-...//037:>CJPW^cfklmopstttsrpnkifda]\ZXSPMID@;62/.----,,----.../0////011112222335652//..--,,,,-03;GS`jrtsrsstvwwslbXNF@?950,,)'').6?L[iu}~}sg]UOOPUUWZ\^cglrvvvsk_RPGB/*****++----../////25a]98IQOOKUclx}~}yxxxxzxuqlbTG@<966554333332246887755679<>ADFHJLNPX^gov~ ~~}{ywvsmgda`^]^]\Z[Z[Z\]`cbdfimquvwwtsqqrrrtvvxzx|~~zrjaYOE<9;FQ]hqxzzz} ztlf^XWY[bhmrtttuvxyz{yutnh`[ZYX[bkt|{rliknsyyxwvwvutuvx{{zwtsuvuw77757755776676558867889989::9<==;>>?<<====<<==;;:999999999988898965555332322/..,-,,++*****))(('(('&'(())''(('())('))((((((((*.4=GS^hrwyvuuspnhb]VQNOPQSSOHA7,%#!""$$###$%%%%&&'''''()*,,,-..011258=BIOV\afikmoprutrqniiffc]ZYVTPLJGEB>820...-------------.001111334422211344220000.-,.---/39EP_gqtssrstvvusndYNEA=84.++)'').3:GWgpz}~zoe\VTSUVXZY[\ciosvutmaXS7>7+,,++,,+,--../000136UwgIJITTQOYdpx}}|yyxzyzyyslcUG@<96644432213469=<=<<;86579<=@CDFIJPU]gry ~}{zxvvpleb^\YYUVUTSSSSRTTUXZ]afknnqqqrrrrrrrsssuxxz}xribVKB>?JS_jqy{{z~ }wpg`]Y\^cjpsuttuwxz{zwsqjd]YWWW\dmw~{pkikmsyyxwuuvvwwvw{{{vtsvutt776777667777666677778899:9;;:;;;:;;<>>====<<;;;;999999998888888877664422120/..-+,,+++**)**))(('(('&'(('''''''())('))((((''(((+.6=HR]hrvyxxwutqnhfa`acdffd[SH:+$$""#####$%%%%&&''''())*,,,-./11147:AFMSY`dgjlnpqssolhfc`\\YURNKHEEA><750/.-..------------.0111121334443322452221110/-....-/28CP\gorssrrrtttrmdYNE@<72.+)('''+17AQ`lw}~ ~uh`YWVWWWWUWX^fmtuurhZXL7@:,,,++,,+,,-../00022>X_\TWC@NUUXfpx}}|yyxz{zyyslcUI@:866444333389<CFEFEDB>:7778;<=@CGJPY`iry ~{zzxvtnic_YTSPNMLJIJKLLLLMPQTX]ehknqpqqqprrrrrqqqruvz~|xpg[TIABKT`krwyy{} {tme`^^afkpsutuwxzyzxurnf`ZVWUX]eowwohgjmsyyxwvuvvvvwy{{zvrrttss7768988869887777667878::::;;;;<<<<<<<<<=<<<<<<::9988998888887777555521111110---,+***))))))((''''('''''''&&''''(((())))''''(('(,24>GQ[cjosvvuttssopoqruvvrjeWI9)"!!""$$$%$$%%&&&&'()*+,,,,-/012369=CIOV\afilnopsqlkfb\ZVRPLJHGCA=:9842/....------....----00012233555555445544322210////-..028BMXeosrqqqqstsqmeZOE>;60,''((')+.5?LZft|}~ ynf]YXXWURRPV\bjrvuraXY@6<2,+,,-------../01003;[f^Z`[;HTXVhpz}}{yxxyyyyztmcUJ@96545543246;?EJNPPROKD>967779<>AFKRZclsy }||zxuqmg_YSPMGFFEDEDDDEFGGIJMRY^dgjloqrrsrrronkklmmoqv|{tlb[PJFNWblsy{{} }vqjfeefmpstuuwxyyyzwsnhb]WRSSX]goywlefiouyywvuuvvvvuwzzzvtrqqqp888899997:889977668878::::;;;;<<<<<<<<<<<<<<::::88888877778757745555111100.--,,,+**))(('((''&&&&'&''''''&&''''((((((''''''(('(*+05=GNT]cfilnqrtuwxz|~ zsfVF5(!!""$$$%$$%%&&&&()*),----/002358;AFLSY`ehjlmnoqnhc\YTPNHDA?><;8764200///..------....0000011123345555555566444322000/0/-/.028@LVcorrqropqrqqmeZOE=84/+(()(()+-3:EVdpz}|}{qi`[XWUQQOLOT^irutvbQK?882.,,--------.//000125Mk_WdV8;PWWdqz}}{yxxyyy{ytmcUJ@:7755554459@GMSX[\[YUNB;85667:>@FOV]fnv}~~~||zxuqke^TLGCABBAABAAAABCCDFKPW]`cjmqppqqrttplieebbfhntz}yog_WPOQYbltz{{}~zuqkkjkpsuuvvxxyzzyurlf^URPRRW]hr| ~wmecjpvyywuuuvvvvuvxxxvsrpqpn9999999999888877877789::::::;<<<<<<<==<;<<;:::887776777777667546545311200/.-,+++**))''''&&(((())&%%&''''&&''''''''''''''''&('''')+4;BHNRXZ]bdhnuw}|reUG5&#!"$$$$$$%%%%'(****,-../011358;=BGNV\beiklooomia\TNHEA?=<96534321////----..........//00112333455566775566554433220011../137>JT`kpqroonpqppme[PC;51-*''()()*,/7BP_lw~}} ~umc^ZUQMIGGHP[jqvtkRBKNF>60,,--,,----/0//10/25H[[[i[76PTVdrz~zyyyyzzxxsmdUJ@:75564446;@HPX^bghfb_VKB964358@FKSZdkry }|~}zxvrjbZMGC>?>>@?@@@???A@CCGNT[afimoqrqqssqpje]YUVZ]ckswz}{rld[XUY^gouz}}~ |{{xxtpqqtuvvvwwy{zywtoic[TPOOPU\gq~ ~skffkqwzywtuvvvuuuvwwwsqnnnpn9999999999888877887789::::::;<<<<<<<==<;::::997766666566765555655431110//..,,****)(''''&&&''''((&%%&''''&&''''''''''''''''('''&&%(*059<CDHKOT\dnv| |ocRA.$"#$$$$$$%%&&'(*+++--../013467;?DJQX^dgjlonmmh`ZPJCA=:86544311200////----..........//00123344556677777777665533321000///137=HS_jpsspnmnorolg\QE:4/+)('()()*+/5<JZgs|~|||~xpg_YTPHECCGPZfptfM>D\\M?60,,--,,----.///00.2=RY^cdTIQQOVgqz~~zyyyyzzywsmfXKA:75543448=ENU^flqrple]PE<6538<BKPV`hou} ~}~~|zvrjbVMFA?=>=?<>==><=?>?CGNT[aeioppqpqsqqoic[QJHMTZckquzyvoia]\]bgov{{{zyzz{{zxvxwwwwwxyx{zywsmf_WPPNLOU^it~zriffjqwzxwuuvvvuuuvwwvrpmnmkj888899999987887788889:::::::;<<<<<<<<<;:::::987766665455556566555211000/.-++**)*)))('&&%&%%%&&%%&%%&''''&&&&''''''''''&&&&(&%%&%%%%'*/11238@IPYcjsz xo]L8'##$$$$$$$%(((*))*+--/.0013578=AGOUZaehknolljc\TIC=96654444312200000/-----...//....//1112334455667777777766554333021100//48;ER^iprqnmkmnrpmh]QE:3.+)(&())*+*.2;FVeqz~|z{~zrj\VPKB?==DMXfphI7B^i`QD80-.--,,..---./0./13A]]baUSYXSPYdq{~}{yxyzzzywtneWKA;8444446;AJT]fotyyzsj`UG=747;BJPV_fosz ~~}}||}{wqh_UJB?><<<=<>=;;<==?@DGNV[agjlnnoqqrrplg_WKC<?GQYahmuuvqkgdbadjry}~|}{{}{}}}{{zzzyyxyzz|{zuoiaZSNLJLOU_kvzqhdejrwzxvuuuvvvvuvwwwsoolljh99999999998788778888::::::::;<<<<<<<<<;:::9987776666445544553334521100.---*++*()))((&&''&%%%&&%%&%%&''''&&&&''''''''''&&&&''%%&%%%$$&())*)*0<CKT]flrvyz| ~tdS?*!!$$$$$$%&((()*+,,-../0013689>DJRX]bgjmnmmjd]ULB9874433333311100000/-----...//..../011233355557788777777765554322110110048;CNZioqqnllompqog^SG;2-*)(()))++*,39BP_lv}|zz{~{vj[SLE?:9:AKWbpkN9LjocRE90-...--....-./00123FbibSMjbRUSWfr{~}{yxxyyyzwtneWKA:7443346<BMVblu{}ulbUI<77;BHOV]flsy }}~|}~}{wqh_TJC?=<;;;<<;;;<<<<=BGNV[`fkkmpqpqromje\RJ?76=EPX`flswrmiifgimtz}~{ywsuw|}~{{zzxyzz|{ytmg]VNHIHIOU`mwxmebhlrwxywvvvvvvvuvwwvsomhhge:::::::9998777778888:99999;;::::;;<<<<:;:;99887765555455544443343201/---+,*)((''&&'&'&%$%%%%%%%%&&%%&&''&&&&&&&'&&''''''&&%'&&&&$$%%%%%&%&'+05?FNTZaeilpx~ yiXD-"$####$%&'())*++-,-../012469>AIRV\bfhknnnmg`XPF;7444443333212100000/....-.--//..001111334555579999999988776655442211111148;@LWdmpponkmnnqold\NA5-))*)*))**+,18=KZht{~~}zyy|~}wj_RHA<557?IT`jqJ?auqgVG<4/.,,....--./00003;Zz{eHN\\HIRYdqz|yxvxxyyxuoeXJ@84332137<EPZdpy~umaUI?<>AHPW_emrx} ~~}}~~~~|wqj_SI@;<;:999<<;;;;<9=AGOV]cilnoooononmgb[OG:304;ENV`fnqsojjkloru{}~ |vrniltx}~|{{{{{{}{xqkd\TLHFFIMWdp| ~ukcaglrwyywuuuuuvtvwvvuqnkjheb::::;;:99987777788889:99::;;;;::;;;;;;:9:8776676555444544444432221/0/.,,,*(((('''&'&'&$$%%%%%%%%$$%%&&&&&&&&&&&'&&''''''&&%'&&&&$$%%%%%$%$&&&*09@EJMNSZbnyym\D-$#####$%&'())***,---/0/1369<BGLTY^egjlnnmgc\UJB;7444443333212100000/....----....001112334555679999::::98877665442222111348;@KValpponlnnnppmg_RC6.***)*****)*/6;IXery~}zxxz}|wl_RF=6148=FR^kd=BgvofXJ=5.,++,...--.//../0Cn{v^ONYT>NYcer{~}{yxvxyzyxuodWJ@84311248@HP\gs|~ul`THAAFIOW[cjpx}~~}{|~~~~{wqh^PH@;:99999::99999:=AHPY^dilnooonnomjdaWOB61./3;FQ[cjnqroonprtw{~~zrkebemrx|~||{{{{{}{vniaXOICDEGMXfr}|qjbbgltyyywuuuuuuuwwvtspmkifeb;;;;;;;;::9887888888:8999::::9:::999;:888877765454445444234441220//0.-++**)((((('&'&%&&%&&%%%%%%$$$%&&''''&&%%&'%%&&&&&&&&&&&&&&%%%%$$%%$$$$$$',158;=CKWiv zn[C,##$$$$$%&'())*,,,---/0/136:>DKQX\afjjjnmid`XOF>96545444444333321000///.-----...0001133445567789988999998877665443322322136=BIT_iqrpnkmooprpkcWI<0+++))*++,+-/6;GVbnw}~{xxz}~~yobSD82036<EQ]jaDFowrh]L@5/---,,--....-.02<_klrXGNNZSQRgjt|~~{yxwwyzxwuogYJ@84211249=GR_jw~uk`TLGEIPVZ`gnv|~~||||~{wqi^PF?986857777787768<BGQZ_gimnmmnmmnjgc[RH=6.+-05=FS\eloqqqqtuxz|~} ~wla\Y^fksy~~}|||}|||yslf\SJEBABFO[ht{pfacfkszyxwuttvvvvvvuspnjhgea^;;;;::;;::987888888899999::;9:;;9999:877756666434465444323444111/.-.-,++**)(('(('&&&%&&%&&%%%%%%$$$%&&''''&&%%&'&&&&&&&&&&&&&&&&%%%%$$$$####"""$&',./6BQdsylX>)##$$$$$%&'())*,,,--.001369?DJNU[_ffgknnlhcZSJC<665554444443333210000//.-----...0001133355556789999999998767665543322231247<AIR]fnrpnlnooprrlf[M?4,,)+)*+++*,05<FUalv}~{xxy|}~{reSB72036:DO[g[ERutoj_NA6/-+++,--......03KjjulLGFHMLNRons{}~{yxwwwyxwtngYJ@84212239>HUbmy ~uk`TLHMSXY\dmt{ ~~~||}{~{vpg\PD=;76667777767767>CJSY_fjmomnmmnmkf_YPE:2.++-18ALVajmtttuxy{}~{sg[SPX`kqvz~~}}}}}||zwrkcZQGB??CHQ]jwxnf_bgltxyxvtttuuuuuuuspnjhfda];;;:::;;:9987788889998999;;;::::99988877655554555544333323554100....,+++**)'''''&&&%&%%$$%%%$$$$$%&%%&')'&&&''''''''&&''''&&%%%%#%$$$$####""#"$$#%&',4>N`q}ykW<'##$$$$%&'()**+++,,.12249=BFKQT[adghimnlif`WNG?8766544444442222110000///.------.0001123455557779999::9999888866554433332458=BIQZckpqommooqrvqjbQC6.))+*+**,+.3:?GS`js|}yyx{|~{sgWF:3149:BNYeTK^ztqk`PC70,,,+,,,----.0/3Rqzw_FEL>>LUUeiq|~{yxwwxxyxuofYJ@9412114;AIWcozxm`UQQSVX[agpw} ~~~}{{}~~}|upf[OB<864456766554457;BKSZ`gjllmmlkmlic]VMA8/,**,06?JT^imsvwwz{~~ zpeXKJP\dkrw{}~~~}}}{{xuog^XMD?==@GS_l{ uld`chntxxwtssssssuuttqpnjfec`[;;:;::;;:98777888888899989;;::99::8988664455544444333322222221//--,,+****)('''''&%&%&%$$$%%%$$$$$%%'(++--+('('''''''%%&&&&$$$%%%%$$$$$####""#"###$%&+1>M_p}ykW>'##%%$$%&'()**+,,-./0259?DHMQV[]deijkjjjic[SJA97566544444442222221/000.--------.0001123456657778999::999999776655443333346:>CIQ[dkpqonnoqstvtnfZJ<0****+*+,/26<BKT`kt|~zxx{|~|vl[K=5025:BMWdPSgvurmcSD;2-,,+,,,--,--.08lzuubNED>EQQTffq|}{yxwwxxyxtneXJ@9401/15;CMYfr~|pdYVVXZ\]dirx~ ~~~|||}~~|ytof[OC:644444555444457<DMU\bhkllmmlkiifa[TI@30+))+04:EP\fmrvy{| xmcUJJNWahpwz}~~}~~~|{ytle^RHA<:>BKWco||ujb`chntxwuttsrrrsttsqromjfc`][;::;;;;;9:877788877788889:::::989987776655433333443333221100//.-,+**+++*((''''&&&%%&%%%$$$$$#$$$$%&',03442-+)()))&&&%%$$$$$$#%$$&$####$#$##""#""! $')/:J\my zkXA)"$#$%%&'()))*+*,.-0259AEJNSV]_cdfhkkkkjf_VMD>86565544433332222110../0.------...0001112346667889989::::9999776665553333567:?DIQZcjoqponqrstvuqkaQB4**++,+.036;AEMValu}~z{{|~yoaOA71148ALWdLLjvvqleVF;2-++*+,,--,-,00IzrUK^]TPWLMQPiir~}{zywyyyyxtnfXJ?8411/27?FP[hv rg]XXY\]`elrz~}~|~~}~|yuoeYMA9522232233443348=ENV]ejkmmmnllkie^ZPE:2+))*,/38EO[cjrwy}~ ~uj`VKGMT^irx{}~}}{zwqld[OD=::;BLWgs}sga^bgotvwurnqqrrssssrqnljfa^[Y;::;;;;;:9877788787788889::::9778877776544333333222211111100//.-,+*****)((''''&&&%%&%$%$$$$$#$$$%$&'+05:=9722100.,(&$$##$$$$#%$$%#####$###"#"#"""!"%(/9GVjy|n]J1$%#$%%&'()))*+,-.036:@FKOSX\_acfjkkkjjic\SJB;64655544433333333110/.//-------...0001112345677889989:::::988877665553355668<@EIQXaiprrpoqrstvvtneWF8.-.,+.157;@DJQX`js}|{{z}{reUD82148AITcKFivvrlfWH=2-,,*+,,-----1DtwmZ8OOMSWQRROcjs}}{zyxwyyyxuneVI>843125;AIS^lxth`[[\]]bflrv| ~}}|||}~}zxtndZNA9521112211111149>GPY`gjmmmmmllkhc]WMB7/*)))+/3:FO[cipv{~ }vl`SKJMT^hpv{}~}}|{zvpjbVKC=::;BNZiuxpe`^bhntvvtrpqqqqrrrrqpmjgca^ZX;9;::;;;::89878788877899999888887766665522221111222210000/00//..,+**))))))'&%%''%%&&%%%$$$##%%$$##$&+.37;;:6777765/*%$$$$$$$####"#####""""""""!!!!#$)-6BRcs~~scP8'&$$'''(((**++,./25=CHMQTY]_bdhkkjmkhie^VLD?97665566553333332211///..-------../10011123455678899899999987899764434345676:>@FKPX`iqstrppqsuvxvpg\K;2----169=BEJMT[ajs| ~{z}~xk[J;3347>ISaJJjvtrofXI<4-,,*+,,-----.KxzqJ7UNLIMSMRWchr{~{zyxxyyywtmeYK=752548=ELXcp|}tib^\]]_aejpvy }}}}}~}~}yxumcYK?7321000000000/6:@GQYahmlmmmmlkie`ZUK?5-+++**.4?IS\cjqu{ ~vl_RJKQVajqy|~~~|||{xsne\QG?:89=DO_ly}vlc^^bgnuutsrqppppqqqqomkgfc`\WV;9;:9:;;99998777888778889998886677665543222211112111100/0///..--+,**))))('%%%%&&%%&&%%%$$$########$%(*.166456699872-'$$$$$$$####"#####""""""""!! !#&+2<M]o{vfU=*&%'+-,())**++-/26<BIMSU[^_befhjjjiigc_XOF@965665566553333332211///..-------../1001112345567887789999998789976653554568:<@CGKPU`hosttqppsuvxwslaP@6/..18<?BEJPRW]cks{ v{xo`O>5347>GR^IHlvtsofXJ=4-,,*+,,------;`pjQ8[WNLPYUMTc`fv}~}zyxxyyywtofXK@96689>ELS]ht~|rhc`_^]]^`djov}~}}}~~~}}yvrmcYK?731000....../04:BKSZcjmlllmmlkic]WPE<3-+),+-09BLU]cjrv{ }vl_RNNSZbksy|~~}~}||{wrkf\QG<888=FSco{ |sic^^biovvtspopqqqqqqqomjgdb^ZWW::<<::::;9::9887888888889977765555445543222211222200//////./.--,++*)('''''%%%%%##%%%$$$$%%#"######$$%'(*++-///26661.(%##""""####$$#""""""""!!!"""""#%)0:JZkxwi[F/&%*/23,)))*+,-28;AGKQUZ^`cdfhiiiigfdaYPHA<764555555554433322200//.-.-------..0011112325666788888899889888887775445567:<=AEJMQX_gouuusrrtvtvwsndUG:1027<@EHMPTX[_diq{~s^j|yz|seTD8336<FP_JGkvsqnfXK>5.,,)*,,,,,,-+2LenaPn|iQO^^NLa^Vk|zyyyyyxxvofYJ?;;=ACHLTZbmxznga`_]ZXY\bipw}~|}}|}~~~~}{ztqkcWI=630/...---,,-/5<CLU]djmklkllmjhe]WOD91,**,+/5@HRX^ckrw} |vl`SQRU[elty}~}|}|||{wpibXMA9688>FXdq~{qf`\^cjrvwtsqpqrrrssqoljlge^\ZYY::<<::;;;99988878888888888777655443344332111112222/////.///..--,++)*('''''''''%##%%$$$$$%%#"######$$$$$&&&')**+///+)&%##""""####$$#"""""""! !!"""""#%)/8EWftznaP6)).59;3*))*+.16;@FLQVZ\adcfihhiihgc_YRKB<7655555555554433322200//...---..--..0011112325666688889977899888777765655668:=ADHILQW_emtwusssrttvvtphYJ>859<ADINRVY\_dhlu|wnky~|p{}ujXI:337=EO^KHarsrnfZK>5.,,+,,,,,,,,,-4FSkkuteb`aZNBSfkz}{zyyyyyxxsmf\PDCACGJNRZajr|uic_\ZURSTX`fnw~|}{{}~~~~~|yvuqkbVI=63/.---,,,+++/4<CMW_gjmnlklllieb]WOD91,+++/5?GMU\diotx}~ |vlaUQSZ`gov{~~}|{}}|{upiaTI=7669?J\juynd[[^ciqvwsrqqrrrrssrokjhfb]\]^^::::;;;;:99877878877887787776643333322221111110011000///.,..,,,,*))))'&&''&&%%$$%%$$$$$$$$#########$$$%%&&%%&&'%(*''%$""""""""""""##!!"""" !!!"$'+4>Qboy|sgWB-)1;?A7,**,-169=CJQVX^bfeggjhiihfa]VOLD<97754545566444443221100//...-------../01111233456667766889989887766665666676:=@CEIJMQW\emsvwuspssssuuqj_PC<<>AEJNRX[^aegkpvz xwujfqf[hszyo^M=4249CP[PO_qsrkeZK>6/,*++++++,,--+++8YrlgdpdWVTS]hu{}{{zyzyxxxvoi_UMJJLMPTYahov~zre]ZVRMJMOS]fr|~|||}~~~~}~{xxupibTI>52.----,++++*05>GOYahknlmkkkljgcZULB8/+++04=GNV]binswy| |ulaVRU\biqx|~~~~}~~}xume^RE:6648AM\ly~vl`YY^elsvwusqrqqrrrrrolhgc`\\^`b;;::;;;;:98777878877777787775433111111110000001100.-////--.+,,,,)()'''&&&&&&%%$$$$$$$$$$$$##########$$%%%%$$%%$##%&%$#""""""""""""""!! !! !"#$)/9K]lw~tk\J3),4<?4))*,/38<CGNSX\adfghhhjigda[VOHD>955655545544444443221100//.-.-------../0111123344566776677887788776666655688:=@BEEILMQVZdlswwvtrrrrtssqlbWNEBDJMQU[_dfikpsvz}vut|dPD?@D^^XohJ8049?KZNM^lrrmeZK>6/,*++++++,,,,,,+5M]chkcDHMLS`ir{}{yywxyyyxuqkc\XRQPTV[`fnu|xmcYSLHDBEMU^iv}~||}}~~}|{xwtpibTI>52----,,++++,19@HP[bhlmkllkkkieaXSJ@5/*+-3;EPU]ciorwz|| |ui^WUX_fmty}~~|}}}||xukf\PB84459DP_o{ }uh^YY^elswwusrrqqssssrolheb^^^_bd<;;;::::98988677887766776566421111110000/11100////00/.--,,-,,+,,))('&&%%&&%%%%%$"#########$$""""######$$$$$$$$$"""$$$#""""""""""""""!! !"##'-5CVhs}{raR>.+/75/*+,.05;>DJPW\`cfiijjjiec_ZTOGB<765456544554445443222110/..---,--,,--...011112333555566777788898866666665679<=@CEFILKMOTYcltvwvtrrrrrrsqoh^TLHJOSY^bhknstvyz}mYZqhU[bi^PxVf`_T<:?YnbKGN_prleZK>5.-+,,,,,,,,,++,2;PVWbfQO]IELVeqz~~|xwxyz{{{yvphc^[YWZ^cgmryvk_QLD=8;@MYeq{~~}~}|{zxuupibTI=30--,,++**++-3:BJT\dhkjljjkjigc_XOF<3-)-2;FOV]djprvz{}~}vmbWSZaipx{}~~~}}}|}wuicWJ?62229DTbp}zqh]WX_fmtvtutqrsrsstsqnkfca_`_adf<;;;::::988877768877666655443111111100000000//0///0/.---,,,++*))))(''%%%&&%%%%%$"######"##$$""""######$$$$$$$$"$#"$$###"""""""""""""!! !!"##%)4@N`mz}ufYI5,+-,+*+-047>CGNUZ^bdgiijjjfa\VQJD@;7665663344554445442222110/..---,--,,--../01111233355456766778878876666666789==?BEHIKLMOPS[bkpuzyusqqqqsutsne[TPQUZ_ejquxvX@PZRP@X`^dlNJA@GKhrICLcsqkf[K>5.-+,,,,,,,,-/Hn}tcfjV[JN\Xeqz|}{xwzzy{{{zxvoga_][_chmsx}|uiZL@7339CP\hv}~~~}zyxxwspibTI=30--,,++**++-39CKT\djkkjjjihhea[VMB81,+07BKS]fjoty}~}}~ }vj_XW]emqy{}~~~|}xrh^UH;3122;FXgs|tkc[WX_fmtvurqqqrrqrrqpmjeca`aadef;;;:::999877776655665556422221001100////..-----//...--+++++)))*)(('&&&&%%%$$$$$$$$$$$###########$$""####$$""##""########""""""""!!!! !!""""$(.8GWgsz|xnaR>-*((**,-058>ELRY\`cdhijihd_YTNHB>976335444455553344332211000/....,,--.---../001202344555555556666887666447679:<>ABFHJJLNLNQQYbhptz{vtqqpprtvvqkd]XW[]djqw|~jVQZVZ\cfib\EDLQD@LhwmYGZwsole[K=6/--,,,,,,++-/[~vx}xiiwkRIV_Xdq|~~|yxwwy{{|{zwojeb`bdimry} {qeVF;404=HUamx }|{xxwwvrkbVJ=3.,,---,,+**/3:DNXaeiiiiiijieb\WQH>5.-/5>HT\dkov{~}|}~ ~uj^Z\`hosy{}~~~|xph\SE80-,0<J\iwyumbXTWahosvurprrsrqtsroliecccddefh<<;:::9988777766556655542222100///10////..----/./.----++++*+))))(('&&&&%%%$$$$$$$$$$############$$""####$$""##""##""""""!!!!!!!!!!!! !!!"""#&*3?P_mv|zsj[M6,+*+,/046;AHMTY]adgjmkjd^ZRJEA>9554454444455553344332211000/..--,---..--.0/0012023335555555566667775656679;=>@ABEGIKLMMMOPSV^gnuzzxusqsssuwwtpjd_^`diou~pVIIW_mrklnYGQCFGIIKTb^O[tnmic[L>6/,,,,,,,,+++,.>Yhpkfd_nkPO^^Tdpy~~|yxxxzz{|{yvojeccfimqvz} {qeTD8218BM[fr}}|{xxwwvqlbVI<3.,,---,,+**.5<GNXafiiiiiiihe`\XOE92/04<DOYbinsy~~}|}~ ~uka\_cipvz{}~|uph\O@5.++3>O_ly yrk^VSXaipuvsrqrrrrssssolieddeffghg::;;;;99877766656655444200111110/0./..//..--------,,,,,+**(*)'&&((('%%&%&&%%$$##$$$$$$$$####"""###""##""""""###"""!!!!!!!!!!!!!!!!!! ! !! !#$'.8GWfpz|wocTC2,--0136:@EKRUY]aeijjjhaYSLGB=:674445555444555433333321100//..--,,----..//./11111112445555554455555445568<<>@ABFIIJLMMNONOSX[ckrwxxtsrrqsvxxxuojfdehlqw^;BX]Wgag}pVd\KDCGOU[VOUijnlcZL>70,,,,,,++,,,,6SKNVWaPIWQNXR[Xepz}}{xwxyzz{|{{xsjfcffjotwz} zqdSC725<ER]jv}|yvvxxvqldWI;2-++,,.-++),/6=GR\bfhihhiiige`ZTMC8127;DKV^ekrv|~~}{} ~uk`Y_dkqw|~}~~{wodYL=4-+,5@Rap yphZVSWaiortsooqqqrrrsrmkheffgiihge::;;;;9987776665552211210/121221221/..//..------++))+++)***)()(('''&%%%$&&%%$$##$$####$$####"""###""##""""""""""""!!!!!!!!!!!!!!!!!! ! !! !"#%*2?O`jt}}wl\O@222568:?CGMQW[_bfhijgb[TMFA=:7654444444444444543333321100/...--..----..//./111111334444555544555545568:<=@BCDFHJJKLNNOONOSTY^hnuxwusrrqsvzzzwuojfijmt{u^VYP3]s|wj\TWRSMMYWQNU^fd`YMA7.,,,,,,++++*,2A]aPZ[Q]T:BEJT^eqz}|{xwxyyy{|{{vojfehhlpuux{| zqdSC837>IVaoy ~~}|yvvxxvqldWI;2-++,,-,+++-29AJT\bfhihhiiigd_YQJ@725=BJS\bhnsw{|}{zz{ ~tia^`hlsy|~}~~{wodYJ;4-,/6CXdt xmeYURXajptrrqpqqstssrpmkjijjhhhfd^<<;;::998766665533331011021146674310//..--,,,+,,,,++**++)))'(())&&%%%%%#%%%%################""""$$""""""""####""!!!!!!!!!!!! !! ! ! "!!""#&,9EUcnx}zreYK;669;>>CHLRTX]adfhigc]UPJB=8766654344445544554433333211//......-------..../11001243334455554555443688:<>@BDEGGIJKLMNNOONPOQSYbjqyvttsrqsvy|}ywrmijinuz kSyyob]LEV^YUQOMOPRYa\QC71--,,,,,+,,-*-5ZgWTaVcT:GLSX^hu|}}zzywwxz{{{xslgcccfiloruyy} zrdTE:58AN[gr{~~{zyvvwxwslcVH;2-++++****+.4;FNV\cfghgghhhhd_WOG<59<BHPX_dinrvyywvxyz }ujbabjpuz}}}~~{vndWG:3,*0;HZjxvlbUQRYajqvursqppqrssqomllkljjhddaZ<;:::::8876655643321//001/1458;<:8531100....-+--,,++****))(''(((&&%%%%%$%%%%$$####$$########""""##""""""""####""!!!!!!!! !!!!!""%%)2<IZhr{}vmcWG<;=?BDIMRVX]^beehhf`YSKF=976554664444455665544333333110///....-------..../110012243344444434445578;<<?ACDFFHIIJKLMNNOOOOPORV]dluxvtsrqsvwz}{ysnjjmqsv{ |{|{sg\NGV[XZPLJMOLLY^VH=4.+,,,,,,,,,,-5E]\G[VWOFWVG>Pdmx~}zzyxwyzzzxuojdbbbbgkkosuxz}ypeTE96:CP^jv ~~~}{zyvwyxwslcVH;1+*********06=GOW_fhiggghhgfb]WOG?<@DIQX\bhllruvvsrswz} ~uk``flrxz|}}~~~zskbSD81,+1;M_n{ |si^SPR[bjrttrrrrrrsttsqonlllljhf`[V;;99999876665343221///.//2278<>BB@=9554211/....---++*)))))((''('&&%%%%$$%%%%&&%%$$%%$$##########""""""""""""##""""!!!!!! !!!!""#%&*3BN^ht}zsjbUGABCHLPTWZ]_bdefgeb\WOGB;85455556655445566555544442200000/..----..----///011011212222222423435678<=>ABDEEEHIHIKLLMOOMNONNMOPU^grvvtststtwy|}zuoljllmpsz|{{}~{tk^RNUOPUVVMGKOSSTWRG;4+..-,,,---,..05:=QNQEDOPC>Pkgw|{xwwyyyzyxutlib[]^aeigmpquwy{~}wlbRF89=HTbny~~~}|{yxvxyxwslbVG90+))))**))+18@IRZafgihhhhggeaZUNFBAEJPY[_dhkmprqomorvz~ ~rgbbgmtyz{}~}xri]NB7.+,3?Obryqg[QPR[clquvtsrssttttrrppolkkkfc\UP;;:9998876554333221///./.16:;@CFIFDA><;855431120..,,+***))((''('&&%%%%$$%%%%&&%%$$$$$$##########""""""""""""##""""!!!!!! !!"#"""#%'/:GVcnx|wrjaSHHKNRVZ]`abdefedc`\UNF>966666666655555566665555443310000/..----..----///0//01011233545555555789<=AACCEGHHIIIJLLMNNNMLMLKHIJOWblsvusqrttwy{|zuolihfgglpy|yz|~~~yqj]UURRNNLNIDGOPNJMPJD9----,,,---,--.3=PWPLCISQCAOmiw|zxwwxyyzxwtslg_YXZ`fknolnnorvy|}{ukaPD89@JVdq|~~~}|yvvvxyxwslbVG90+)))))))),29AJS[bfghihhhgfb_YUMIDGJRX^_bejllmlkiilpuy| ~rhcfjpuz{|}~|wqh]N@6.+*6CTdtwndVOOT[dlttsssrrrrsttssqpoonmhc_WNI<<::99888554544121/..../036:=AFJMMKHGEB??<<;87765411.,))))(''&&&%%%%&&%%&&%%%%%%%%$$##$$########""#####$""##$$""!!!! !! !$$##!!$&+2?M\ht~|vrkdYTRVX\`ccefffifda_XQKC<6687667777776656668976553333100000/-./....-/./-/01224455566866786688;>???ACDFFHHIIJJKKKLJKKKMLKJHFCDIS^iqtvtsssuwxy{zuqkfd`_behpw{ ~yyxz{xsnha^[YVTMMJIJFCGKGCHPMLH/).-,,+-,----,06FXQQIQUGA=>Jky|{zwvuwzzwuurldZWWY^gmcF@Dcknquz~{uj_PC9=DO]hu}~~~~~~~~}~{zwwvvwxxvrkaSF80,(('''(()-4;BKS\cfghhiiiifc`[XRLKLQV\_ceiiijihdddintx{ tkggmsvy{|}}}~~xofXK?5-+.8FXjw~ulbWPNS^gmttssqqrrtttuuutspoolf`ZQIH<<;:98875554332210...../036:?DHLRRPNMKIHFDDC?@@><;9952-+*))'&&&&%$%%&&%%&&%%%%%%%%%%$$$$#########"#####%$$#$$$$"!!!! #"##"!$&'.8FXepz}{wrle`^^]adffijiiigc_[TMF>999886677777777888:89976643301000000//0/0//00.144456668::<=>=>@@ABBDDFFFFHIJIIIKKKKLLLLKKKKLKLJHGCB@?CLYdqtvtssttwyyyxvpib\XWZ\`krx|yywvtnecaZYWVYWMKLJE><CGGGIPSS\L%-.,-,+,,-.--.4LMWf[ST@<7:Iizz{xvvuwyysrurjd\XZ[jhM=D@8]iejsyzxsh]L@;?GT`mw~~}}}}}~}{ywvvvwxxvqjaSF81+(('''((*.4=FNW^eghghiiiifca^ZVPORW[^aeghhifb_^_cimtx{ |sjiiotz|{||}~}wofXH=4--1;L\n}~th^RNPT]elrussrrsstttuuuutqpmhc[SMHJ;;:98777543332101/--.....26;?EILRSSRQOOMKLKJIIJIFDB@=951.*)('('&%%%%&&&&&&&&%&&&%%%%%$%%$##$$$######$%$%%%%%%%$#""!! !! !!!!$&(-5@Q`lv}|xtplkhhhkkkkkjjieb^YRKD?;9999777788988889;<>??>>:64221/110/21132134577;<<<?BBCGGIJLLMOOPRRTSRTTSSSSRRSSRTRPPONNLJKKIIIFCB>;:9=IUcotusssstwwyzwtpe\SPNPS\cmqy|xwwwpf]YVRQMNVQFGFC;;?CGHKMOMOQ]@#*,-,-,,,-,--0<<Wc`^S=:3AZp{~yxwwvvusjhppje`\\aZ\UE@7Jcaajswvvph[M@<BJWdoz}}}}}}|~~|zxvtutvwwuqj`TG80+((''&'((06@IOZaeghhhhhjhhfb`\XVVVZ]`dfeedb_YXX\bhotxz~ ~wmklqty{|{z|~~|upeXI=4..5>N_o}{qf[QOQY`iqttqqqqrssttuwwutrolg`XNHEJ::8877775442220/..------/247=BGKQRRRSRPONMNPPPQROPLGDA=940+)('''&&&&&&&&&&&&%&&&%%%%%%%%%%#$$$$###$$%%'%%%%%%%$#""!! !!!!!! !!!"%').4>JZfq{|ywtussqpooppoojgd^UNHA<;999988889989;;=BFIJMNKIC@853223333467:;=<?@DFFIKPPPRUWWY[Z]aaacdgffeehgeeeeeddba_]\[XTQNLJHGDB?<:855<DR^jtvtssstvwxwwtmcZMGAELU^iov~~zxvwxskc]XSMJMRQJFDDAACCGLOQNLNJ^iL*&--,-,,-,-/07>`k__K:96Maqy}ywwwuvwp_V_hiea\aS=EH?KX`YW[fryxuneXL?@EN\it|}}}}}}|~}|zyutrrsutqoh^RE80+((''&'),19CKS[cghhhhhhiihhfea][[[\_acec`]YWTRW^ekqtxz~ xpmntxz{|{|}~~~}wqgZK=4//3?Rcrzod[QNSY`jqssssrprstuuvwwwwspme^ULEDP888877766431111/.---,,,,,.059?CGLPQQQQQPPOQSTTVVSUSSPKGA<52-)('(''%%&&&&&(''%&&&%%&&&&%%%%%%%%&&$$%&&&&'&%%%$$$$##"" !!! !!"""$$&+/4;DQ`js|~|zzzxvxvvurtrrnid]UNF?;:;;::9999::::=AGPTY\_`^[UMA654549;=@CEFIMMOSVVZZ\^_bedghiknmorssttuvuvvwwvvtuttrpooligd_[VOKGD@=87434<GP]irttssstvxvyvtndVLA:>GR[enu~ zxvuwvolf^XRVWTQKHDBACBDDILNTSLMJDl\9+---,/2///1@\rm_[C98>Xf]p|zxvvvuvo]]b^`[XZU[I^F@D]VMQblqurspgYKBBHS_lv~}}}}}}}~}{xvurqqrsrpmh]QE82+)((&'()-4<ENW^dhiihhiihjjigfc^\\]_`bcdc]URONPW_gmruy{~ zrqqvz|{zz{}~}|xpfZL>5229DVgvxnbYQNS[bkqrrqqqqqrrtvwvvwwuqoe]TJEHU8887776534311/00.---,,,,++.269>BGHJJLLMLNPPRSSWYZXXXVSPLGB:4/+)(''&%&&&&&('''(((''&&''&&&&%%&&&&&&''&&'''&%%$$$$$$$"! !!! !"""#%%(+/27>JWblu}~||||z|zzywwuusmg]UNE?=<;;<<;;;:9;;@EMV^eknprqoj`QC;:=@CHIMQTVXZ[]cdgikmnprrqttuwyzyx{|~}~~~~}|}}}|zzzywtpjf_XPJB=94348>FR^jsuussstwxwxxtmcP@228BNXaju} |xvvwxxuqjha^XSPPKGFFGEEDFHIOYSDBBJ`h6)+--,..11;OhhVWO999J`eS]{{yuuutoh^c_VWUPOLOb\]]EOR_gsmtp}uqdWJACJVcox~}||}}}}}{{zyvrqqqqrrpmh]QD70,)(()'(+.6>IQX`fhiihhiihjjjihfa]]]_aa_]YTQMJLPW`hmrwz{~ ~uttx{{|zz{}~~~~yrh[MA833;GXhxxnbWRRW_fkqrroopqqrrtvwzzyxwtpi`TKGJ\99976553331///....,+++,,+++./379=ABEGGHIKNNNPPUWXYZZWWVSQIC>84.+*'''&&&'((('((((''''''''')%%&'''()(''&''''%%$$$$%%$$"! !!!!!!""!"$&(*,/26<BNWdmtx|{}}}}~~}}{{yxvsld[RIB?>>><<=<<;=>AGQV_isz}{qcUHEHKOSX[_chhlmnqrstuwwy|z{{{|~~ }zuoibZQLF@<>ACIR^hquutsssvvxyyxy{{|^/-?IS^jt| |ywxvxyzxxricWVQRNMIKHFHEFGIMVLCADB?UK$*/,//1<`SKQP\SN9;;Q_jketzztswqeb^ZRPSQJJFDRSdaZJNOWbjih_WlbUICEOZhp{~}|||||||{zywtponnqrqolg]PC70+)(((')+18BJT[bgiiihhiiijjiigd___^^_^YWPLIGGKQXbjpuvz|} xuux{||z{}}}~~|xqh^QD;44>K]m|xmaWTT[agmqsspopqrsrsuwz|{zwuohaULIPb8876544200////..--,+++++***++-/1488:==@CCEFHJJPSUWYZ[XYXXRMIB;62.+('''')))(())))((''''''')''''(())))('((''&%$$$$%%##"! !!!!!!""!"%'(*+.25:@ENXdjqsuy{~}~~|{yyuld\TMFC>>=<<<=<?DIOX`kv}qfYSV\`dgloqstvwwy{{|}}~|}}}{{ |xtmf`YVRONNPV_irtttssstvvw £ z(3EP]gu|zywwxy{zvkiaQRXYRRNKIJJIJOQTQQA6DA<;WT,(121<OgoWb^YTM7<AS^dquwxwvtvrdYOR[SNMMJKQ_[ZZJS_^_\I[aQ^laVHGJR\hu}~|||||||||{zxuromnmpppnjf\OC70+)(((').4<GMU`fhhihhhiiijjihea]]]\\YVQLIEBBFLSZbjpuvz|}|yyz{||z{}}}~~|zsh_RF=66?O`p~wl`VUV^djprsspopqqrtuuwzz{zxvrle[QNZi7765333110//..----+++*))**))**(+--/1325:;<@ADEHLORTVXYYYZXVRNGA;51.,)(****)())(()(((((''(())))))**+)))'((('&&&%%&&$$#""" !!""""$%%&()-.046;@HOW_fimpsw{{}~}~}{upicZSMGB?@>=>?BHOX`kt~|reacgkptuwz|}}||}}~~}}}}{z{{{{}~ |zvsolgd_\\_fksuvuqsrstw£¡¢¥«®¨h,>OZiu|{ywwxzzzwnkcODT`XYQLMKLQRUUPJBOFE<=:BcXJVF0^ysrnW>QUQ:;JY_VivrpvxvvriURW]THJJJLRTMT[L[UV\U@`g^_nbRGHKValv~~|{{yy{{{{|{zwsponllooonje[OA80+*)((().6>HQXbgijiiiiiijligida^ZYWXUOHEA=>CIPW]fkqtxz}~~ |}||{{{{||~~}{{rh^SG=89BSds}vj_VRW_enrutspopqrqvuvx{}~~|zwqjcYXbr7766433110//..----+++*))**))))'*))),+-.10258<=@CEIMQUVWX[Z[YXSLD?:61.-,+++*)++)))))))((((())****))****()(('&&&%%&&$$$#""" !!""""$%%&'+-.0469<@HNUZ`ddhnpsxz||~}|ztnha[UMGEC@@DINV`hp|wmknrsuw|~~~~~}}}}}}||||||zzz{{{}~~ }zwwtpnmrtwwwspnnx¡¡¨¬°±®48IZiu~|yxxxzz{{yrbNCPSS^XQQIMUVWQN><MJC::;:Dc^Rlc\pifZ<P[T==@FLBX{oxxzxrbVcbRIMKJJMRT^][VSJD[hgd^`s\QKJKXcny~}|{{yy{{{{|zyurommllooonje[O@70+*)(()+18BKT\dhijihiihhhigeb`[XXXSNIDB>:;?DLSZagmsvy{}~~~~ }}}}{{{{||}}}|zwri_UJ@:;EWfv ui^VR[bhosttspopqqsvuwz{}~~yunh`bmy775543120/..--,,,,++++***)))'''())))**))*-..0359:?CGMSVY[]__]ZVPJC?:631,,,+**++,+++++****)+,*,,,++**++))**('&&&'''%%$$##"! !!!!""#%$%((,/0479:>@DJPQTW\cginqvx{~}zvohaXVQKFGKRXagpzxssvwyzz{}}~}~}{||{{||zzzzzxyzzz}}~~ }}}}~|xsv ¢¦«®¯®«A5GYft||zwxwz{|{si`SSR\hbWTLNWXTRK85IC=;:7:>Thf^D,`UUoVRQZI<>KUH;V}zZTabfd_]ZasiGGJJILLTXXPMINQdc`SR_cnmNIP^hs{~|{zzyz{{{{{ywsqnllklooomjd[PB70+*)*))/4<EMW_eikkhgghggfddb`[YWRPKFC=876<BHPU[`hotwz{}~}}~ }}|{{z{|}~~~}}zyvpi`UMB;=J[jy ~tg]WY]flstutsonpqpsvuwz|}ytnjnv55443211..--,,++++****)))(((((('''''(()))())*,.1258>CHOUW\^`^^\ZUOID@=731/-++,+,,,,,++++++,,,---,,,,,,+***)'''''''%%$$##"! !!!!"""#$%((*,/257:>BDDDEHLSY^bfimovyz |yvpid`ZVUWZahnyywwxxywz{{z{{yxyyyxxuuttrrrstuvvwwzz}}}~~~~||~~ ¡¦ªª¨¦¥T1HXgu||xyyz|~}|xrd_VWadb[RPSPRVPK7.38;;88>CLbqkDVvkYTZm_P@>?QJA@B:9<=<<<==GOQe\BEJKKKHOPUSPQRVWP=MOTTabWJVakv~~zzzzy{{{{{{ywspmllkloopnjd[MA60++*)+,27AJRYagjkkjigfffdbb^ZVSOLGB=77337<BHPW^ekouy|}}~}}~ }|{{z{}~~~~}|zxuohaYPE?AL]l{~tg^YYahovvtsponpqqrvuwz{~ }yutx5554200/.--,-,++**(((())''''((('''((''(''())****,./5:AFNUX\^`a_^[XROJEA<;80--++----,++,,,,-...----..,,+*)))(''''''%%$$$$"! !!""###$$%'')+.1369=>>??@DGLQW\`cgimpuz~}yuroieddfkpx }xtuwuvvuuvuuwtrromkjjhgggeffdfiklmpqrstuuuutuuttuvvwxy|}~ £¤¤¢s3DXgu}yzyyyz|}}wsic][ddecXXXYVVSC62/7=<:7>KSxx]HPhpjgXUQQ@A?<7>BDFGGFEGFFMTF?BBCMPNKKJIIOVWYSEEO68FUUGCXMXdmx}zyyyy{z{{{zxvsomkkklmonlhbYK?40,+,,.18@GNU\djljjhhhgffc_^ZUQLHB>:4/136:@DJQY`hmswz}~~~|~~}{{z{}}~~}}zyvrngbZPGBEPao} }rh_Z[ckswwvspmnqqqrtvwwy~ ||334211//.,,++,++**((((((''''((''''''&&''')))******,.19?DLOVZ]_aa_ZXXUQKGB>730..-...-,,----..........---,+++*''''''&&%%$$#" !!""###$$%'')*-0377;<<<<?CGKPVY]]`dfjmrw{ }}xusqprt|zurqsrqrqoollgeeeaa^]YXWUUVWWWZ\`abdhhiijjjjihhjjihiknortwz|~ ¢¡|AFXgu~|||zz{z}}{v_a^V_^ddZWV^_QWC9329;;978@HE5=IKCAloiUG>:CA=GDB=<;;88599@TNF@?EJJLKIFHNYb[UMFJUWNEKJEWNU\eqz~{yyyyxyz{{{zxtqmmkkklopnlhbXJ?4///0259=DJSZ`gkkjjigfffea_ZVRKDA;60.,/25<AFMT\cinswz}~ }|zzz{}}~~}}zywtohd\SJEHRcr }re]\ahmuxwuspoppprqrtuvy~ 220000..,++*))****(('''''''''(((((''''&'))))********-06:@GNSZ\\[^_]\[XVSMIC931...0.//..//////////.//---,+++*))((((&&&&%%$#!!!! !!! !!"#$$$%%%&()*+./369<===AEGLPTVXXYX[_chouz }|zy{}~sqpmmjjhdc`\[ZXTRPOMJHHGECDHHIKMRRVXZ[^```]^\[]\^_\[[]]aejnrux{~ }¡¢|y{px\LXhv}qbozzy|}~i[WXZ^dhYHJNK=KG7C::;95669;D;7?GAMSSvdP?<CCJH?F8Jr}wsnVT=MjPJCFJHGEFCDGOXVONKFDGGEWIQYSS_lt{}}zyxyyzzy{{zyvtqnjjkllnqomgaUH@64579<@EHLRV^dillkkhfgddba\WRLE<72-))*.27<CHOX_gjpuwz}~}{||z|~~~~~}zywsoid]VMHKVfu |sf^]cirwxwuqpqqppqrrrrsv|110011/,*)**(())))((''''''''''''&&''''&'))))))********-28?EJNSWYZ]_`a``_YSLD:200.//00///000000000/00...-,,,+))''((&&&&%%$#!!!! !! !!"####$%$$$%%%&(+,+.057;<=@@EIJMOSTTUTUUW[^fmu} ~zrkhfdb][XUSPKHFB?>=;8776789>ACEHKLPSUWYWWVUVTSRQQPPOOOOQTX[aejovz} p}~~|n|sLYgv iamcz wjb\VTa\FCHQRHRb]L::;6565649E@6Xh_UNTYOF;?<@AEI=4c{nWdjMKLHJIKMJFCBBAAHLMMKE=?@@KPEWX\_blu|}}zxwwxyyzzzyxzzrkkhgkklookf`UJ@::;?BDHNRVY]bgkmmiihhffd`]XSNH>5/,)))*.27<CHOY`eksuwz~}|||}~~~~~}{xvrmie_XOLNXhv{qg`agouzzwurqqqppqrrrrrty~211/00/++++*)(((((''''''&&&&&%&&%&''''(())(())******))+,.4:@EINSXY^`abbde^ULA610/.000001221123311111////..-,++*)))(('&&&%$""!! !!! !#&()('&%#$%%'$&)*+,/259<?BEGIKNMPRUUWWVWWX_cjoz |qf^]ZURNIC?=:9521223022259=?BFHJMPSUUWWWWVVWSPONKHD?>::>BEJQSY_dipw} ws{r|{{ZZguwpqroxkoh]Xfc][FGOZcmf;MG:78678659BDEZ`TOHIJ@@=?BNZcH<2Wzm]MPXRNJMNJGFLMFAA;;?DGIIFIJLJOO?XXRbhox~~}|ywvvwwvyxzwvohoz~sljorjig\SLHCEGJORTWY[`fhjklkkjhgfda_[VQJA8/+(()((,38;BLSZagmrwz{ }}{}}~~~~}|{xuqmjfaYSMQ\lz|rfcgisy{zvvsspooopqrronquz 1//.--+*+***((''''&&&&&&%&&&&%&&%&''''(())**++********+,,,059?EIMSY^bdfiif^TH<3101/0111122110222111100//..-,++*)))(('&&&%$""!! !! "$',/00/-+'&%$&&'(*+/359=@CFIKOPPQRRTVXZ\]]^aejrx |l\QNLF?;9531./0....-/2336<>BEHKMPRTWWWWXXWVTSRNJFD>:41/..28=BIOU[`gnvz~}zvy{nsmlyv~vztp}pxcagjcU\[\PPOIZdX9MK=8878;76=?GcTN7<68=FDAACKW]CF<a\be[@?JDNZVPHFFHC=>;8=AGIIJLLNOQXZXYXZepy}}|{xwuvtyxssvpfJL[p`X\SGK[^YQLHJLQVXZ[^cfkmnmnljihgecb`\TMG=4-*(()((,27=ELS[ahntxy| |{zz}}~~~~}}{xsolifa[URU_m}{sifimtz{zwussrpoopqppnkmqv{0/..--+)*)(()('&&&&&%%%%&&&&&&%%&&''(())))))********++,,---.06<AFLQX^dglmke[NB711222122233333222222210/.------+***(('&&&%$$"! ! ! "&+/4<>>>960,)(')))*.169=BFGJLOPRTTTTVZ\`deeefiqu{ zhREA=9731000../----..048<?BFIJLORRVXXZZZZXXWSSMKFB;60---++,017;AHRY`fmpvum¡´¼½¾¾¾»°}yqtzY{kzmcuxwj\fjkheb`RTUKEOb\+(=><;9;@E<98:FC@G?:987L@ACEJUSFI_]SZeRRX9BhrhUIFDABB?:7;@CGHIKMPRRUX[[X`foz~}|zwvwuuyt_`whbkrnna`JGFINNEYb\UPPQVY[]`deimopomkijhhgee`]ZTLD;1*)))())-49?FNU\cjpuw{}~ ~{yyz}}}~~~}{yvspliea\WUXbp~{slkmrw{|zxtssoonnpqqnkhhlpv~/...-+*)()('((&&&&%%%%%%%%%%%%%%%&''(())))))********++,,,,,-,.29>CKQY`hmrqkcUH:32222223344444333333321/.....--+***(('&&&%$"!! "%*/39@GKKKGC>70+)*,-/157=AFJLOORTUVVXX\`ehlmnnnprx} {iQD<77353//....----0036:>BDHJNQQRVWXYYZZZXWUURNJF@951-,,,++,,-05;DLT[beuykom¡¶»¹»½½½¼»»´vpihjk_]`vwy{kUVrnUZjmjll`]TQTNJSvu<413;<<:@KG847?FDLE;<9;J<ACEIUKH=MieZVJVO;nnniXLE@>=>A<8:<@CDGIJKLNQV[[X]gqu|}xxtsvvmUsmld]^inf^CDNPQJDH]le`ZUTWZ\^adfiloqqomkijhhgee`[XQJB90)()))*,08;AIOW^ejpvx{} }{yz{{{|}}}zyvsqnjgd`\WW]es{spnquy}}{wsssponnnnnkihijmqz --,,++('('''&&&%%%%%%%%%%%%%&&&&&&''(())))**********,+,,--..--.27:?JR]hmqsodXK=42222123444443333443212311/00..,,++))('''&%%#"!!! !"#$(/5=GMRVY[VRJC<60./0449=CFJLORRUUWZZ[\`bfmrtsqqsuy~ o]RLD<84541--//---.2268>BEIJLOPSSVWYYZ[ZZYWUTPLHB=61.++,,,,,,-148?INTYm}]5c ¸¾¼»¿¿¾½¼¾¶³º¤|_Rgcspgwz{zlW\dXL[cfb]agWHGNMLZmhJ:9ID><8DA9673EYSA39<<IMBDECJVED9Mwpk2B@Auyng`RNF??=>><;:>BDCDFGFHJPSWY[]gqwxzyvtslrnmorfG6@daHKKLLHFL\kpkhc]YWYZ_achikopqpnljhhhgfdc`ZWOG?5-)'''+,04:ADKRZ^emrvy{}~ ~|zyxy{{{|}}yvsplihfc`\[Y^gu{uqruy{~}zxtsrppoooopmjgfflpw}--,,+)''''&&&%$$$$$$$$$$$$$$%%&&&&''(())))********,,,,----../1/.24;DLXbjsvqiYL=53323234444556666444323331/000.--,,))))('&%%#"!!! !"$(-5>HRW^degfaYQGA;5457:?CGJLNQTVYYZ[[\`ejnrvvvttvyy~ ~zyyz ylc\WPID:71./..---0168:@DHIMNOPSSVWYY[[ZZXXURPJE?94/-,,+,+,-.036<BHNTT ~kyn]px{\]¶½¾½¼½¾¿¾¹¦ls³¸°¡wIZhuz|{{{scRWbOQQ_QAAKX^QNLIOLQX>8FEA<5COQ987<I`JLPNUOCDBRLJTBF<VxkD><<qywqefXHFFDB@=:9;=@CDFGHIJMQRVXWWhuxw{yyvsn`dmjoji_Y_OJKKHDSeklnnmkfa\Z]^bbeglmmooomkjhhhfecb^YUME=4,)'''+/38;BELTZ`hmrvy{}~ ~|zyxy{{{{yywtrojggec`\[Zakv{uqtw{}}zvusrppoooopmjgffinu{ ,,,+*(''&&&%%%$$$$$$$$$$$$$$$$$$&&''(())))******++--------..1311238>GT`jstqj[M<54433245544666644555434331/011/.---*)**))'&$""""" !#&+3;GPXahnoqqlg]TLF?<:=ADHJKPQSVX\[\\\`chnruvvvuvvx{~ ~yvvslou|wqmia[VOH?93/-.-/038=@BFIJLNPQSTVWYZ[[ZXVUTRMIB<72/-,----./028=@FKOTq ¤rYghuwvvo»¾¾¾²°¼¾¿½¸v§º¯ Q<H^ov~xtwzoTAsgZY\^H=?;<MaPMNMKBKOD@@A=8BID89689>6OVO@KCFC^YLQAE:nyM0=Bprwxteh^JGGDC@:78:<>ACEFHHJPUUUXQXmvzxxsHZspgZafeqo_TLJNNLOflkllmnnkhd_\]]_`ccgiikmkkjjhgfedc`]XRKC:1,)'&(,04;>BGMV^bhptwz|}~~~}~ }{ywxy{{{zyxurnjgefdc`^\\dny{usuy|}~}ywttsppoopoolifceintx~++**)'''$%%%$$$#########$$$$$$$$&&''(())))****++,,-------...1311238>GT`jrusiZJ=4343335665566665555543433211010.---,++*))'&%###""!#(-5@IT^fouyzxrlf_UQMGEGJIJNOPTUX[^]___aeknotvvusstuy| ~~~{wsplgc^agq~xxuqnkd^VL@91./158<?BDHJKMNQRSTVWXYZZXVUTRNID?93/.---,,-.148:?DHKP_¡¢£{xm`yu{ah|£¹¾½º¬·½½½¬°¥|iOTOnzoY^dlmleje]XTL?:9>EITRMKKKTMKEAB?><CE?=;:;A:;KLIA@EFJWXPJCB>zV12?b_fvvsk^YQFFFCB>;98;=>BDGHKLOW][YYdmvywuq;Qsd>[okvwNCSOJEQbihgjjlnoomgc_\\\\^\\^adfhjjjiggfecb`\WPIA80+('(+.38<?AGNV]dkptwz|}~~~}~ |zxwxy{{zyxwrolhfdecc``^_hp{ |vtu{|~}{yutsrppooponkgfceinpw~)'))'&%%%%%%$$##""""########$$$$&&&'(()))))*++,,++----......141/238>GU_jtyrh[H;33356556666666666666655554432110.,.-,+*+*'&%$##"!!!!#%*18BMW`hry}{ysoic^ZWTSNNPTSSRTWY[_``aacfikorrtsrrrtvy}~~{xtplgc_XQORYeq||}||yxtnibYNB;65:=@@DGJLLMNOPRTVWXZXVWSROMID>:630..//..1358<?CEKRR{¢ij¥ hZT^f|¶«»¼±¥¯¤ ²£} }^^tq_YLKShi]iniq^XR::;=?8JPOFLFESKEDHJLSB8<?;:;=PMSHGPLAD@a]RQAD@Jh27;MZ]gtupj_UNJFBCCB><<>@BEFIJOPRU[^Y\bnvxwttqusdQdovmK?GKJGWljihiklknnmjhb^\ZZWTRSUY\_fhjjiggfeca^\WNG?5.(&&*,059<@DHOV_fmquz|{|}}||~ {yyxxyyyxxvspmhebcecda`aelt {vwxz}{yvsrqoooopolkgebdglpv} *)(('&%%%%%%$#!!"""""####"""##$$&&&''()))*)*++,,++----....../321238>FS^itxsh[J;54456556666666666666655554432110110-++**('&&%$$"!!!##$'*19CMWbjrvyxvpqlhed^\[YWZ\ZZY[\_aeffffhijlpqrsrrqqqty}~}yvrmhd_[YTGABDMYgv}{}~~}zvskdYNC??@CDFIKLLMNOPQTVWYXURSNMMID?:842/////1258:>@CFHJN^wW\¤§tbYadIZ¾®²µ©®nq¡}egagf]cdYTWf\Teiic\bR;<>D=<FRNJRQCINOQMNRZE<>E=8;:H[fLKPCCBG`RUXAF=W=>CGPXKNqupg_WNJCACC@=<>@ACFMLNVYWZZ[]`hrxyvupoqpnnryfJBJJCAJbefhhhhklmlkie_[YXTOMJLKPY_cgijigfedba]ZUNG>4+'&(+.059>@EKRXbgnswz|{|}}||~~{yxwwxxxyyvspjfb`adddaabgox xvuw{~zwusrqoooopolkfcbdfkpv} )(''%$$$##""""!!""""""##"!!!!"$$&&&''()))*++++,,----....----/133128>HT^juxrh]L=56665576666666666666666665444221000/.,+*)'&'&%$#"""""#&+2:CMW`gnopoomljjjhgcb`^^__a_cefhlkjiijjlnqtsrqonpqvz~|zxvqke_[WRNJG@>@BFP`qzuy{~~}yuoeYPIHGGGJJJJLNNNQSUVVSQNMJIGD@:6440012357:<>@BEGIJPSvYQx~wf_( }½»©}¬®w[e>Z zUY\]twm_\]ZSVmhghVHE9=MSF@>Td\`u[FLNNGCUA=@=CA779DWS?5CBDC;@O\O>D?;9@JMUSLYjnqjZOMHB@BB?<:=AACFSUS]bba`_^bjsxxwsprpkHTxvFFMNFLgjfdfeegijkkklf`\VUQOGC@CDNV]dhiihgfcdca]ZUME<3,(')+/25:?AFLR[dipux{{||||{}}~}{ywxwxxxwvtqlhc_^^cdcbaejqyxsuw{}}|xutrrqnnooqpljgcbeglsx(&&&%$$$""""""!!"""""""""!!!!"$$&&&''())**++++,,--......----.022128>DO[epvtkaP@77765556666666666666666665444223200/.,+*)'&'&%$#"""""#&+2;AKU\ceddcacfijjjhgecdceffhklmopnmmmnnnqstsrrppqvz|zzzwupkf]WQJHDDA@>>=>BJZl}|rgkqv{~}{tle[SNKIIIJJMMNNPRSROKKIFFDC@;863322579;<>ACDFJLMQQd cQfZ*#x¼»´¥]|bVk{lp¤fC\_czvmdb\haV\X=9?DKWYD9BBc\WbYKFC@:LT38>IJ<435>FJHDAG@E7@GT=@<=:H]X[]?EOM^^_ZO??@ABBB@=>AACFTYW_cecbabgltxwtsl`\rlf_TMCL[dsmhedcbcehjkkkhc]YROJC:76<DMV]fhiihffccb`\XSKB80,(')+/25:?BGLR]flrux{{||{{{}}|zxvvvwxxvttoke`^]]`cbbcfms{~xsuw{}}|xurqqpnnoopoljfdeeiorz&%&&$$$$$#!! !!!!!!!!""""##"!$$''&''()*++,,------....--....0/01016<DLXfnstmbSH=6566557786666666666677775544442111//-,+*(''&%%#"""!"%&+29AHPU[\XTRV\afjlkiggfhhhlllnprrrpoopprttwxtrrsrsxyyvvsoje_YRLD@?@@?===;=AHVgywhXZaipw{{uof\UQNJIJLLNNOPPNHFDBEDBA>;7557879<>?BDFHKLMOPQT£¢puPW|{vs{ @*#hº¹¶²¦ `dvi`}}pcODfr}|cacSRX\R3>KS^kF/05MSMQJIFDDDW;88=@732548DD;>?BAS@AF@=BJRHVgQQW@F@0OOMP@;?DFHB?>?@BBDHQYZ_fgddefinuyxuqpc]jgJHSNMYesnieccbcceeiihgd_ZRLG@9215:CLV]dfhhfeedca^[XQIB7/*('),046<BCHOW_hmrwyz|||{{{{} ~}|ywvwvwwwvurmic]Y\[_accdhpx~ytrsw{||{xurppnnnnonmljhgefinq}&%$$###!! !! !! !!!!!!!!""##$$''''())*++,,------....--....///0004:@KWbjqrofYND<786555555665555555566665554443111/,,+*)(''&%%$#""!"#%(06>CKQSSOJIOTZaeghghhgijjmnpqqqrrqqquuw{{{zwttuvvwutrolga[SKD@>>?>=><<;<=AGRcxucOJOXbkrz yvod[SOKKLLMMMMHFCA@ABCBA=;<:9;>AABCDFHJLNOSUQSc¡qY~t^Md}{vqu2**Mµ´±°«rijmohu|mdtvb>>X¡uf[UKMX_^GKBFZUD329>5:NIIC>;FS:589<F;2228@NF=?7NYQYK9BCQAHVUORN7@20IKY@6:>CFIFA??AACGHU\YZchceagmr{wsj^gg^^RGF::KS_rmiecbadddhgggf`]UMD=5-,.3<FNX_dfgfeeca``^[TOH?7/*('),069;?CHOYaiouwyz|||{{{{~ }}zywuvwxwwutqlfa[XWZ_abcflqy~vqpruxz|ywtrppnnnnnmkjjhgeehmq}&$$$###! ! !!!"$$%%''())))*++,,--......-.////....//0027<FQ\fkppibWMC<97556345555555444455466653323311/-++*(((''%%$$"""##%).3:>DHKKHGFIOV[^`adcegijjkmopqqsrstvwxz{{||ywvvtrppolie^VLD?@?=>====;;;;<?FPaswdM;@ENYcmx ~yuk_VPMLMLMLFC?;::=ACCCCCCCDDCDDDGJKLNOQOQRUUp£¡JLnlPOqxwwpp3105£®§¨¤xj`TYWfjcmlhS8?T¥ tI<^ob[TNLFCCKRD3@D9::8>G43BKK?3A>?713059DC><Pe`i_;=<ANQIEOHMM;<F=C@?817?A@@AA@@AAHIJMMLR`hgfZ_pedcTKDEJEOOJ@BNCIdumgdabddcefhhfc^XPF<3+))/5>IQY`eggeddcb`_\XUPG>4-)((*,058:=BIRZbjpvvxy{{|{zz{}}|zwutuvvvutroje_ZUWY]`adhptz}tppptvxzxurqqonnnmmnlkkiifghms~$!##"! !"!"$$%%''())))*++,,--......-.////....////029AKW`ekpnh`ULF>;:86644445555444455466665323321/-,,+*((((&%%$"""##%&+039>@CBBCGINSWYX[\\]_aadgikmpqtstuxyy|||||yzxuqnlifc_XPGB?>>==><=<<::9:;=CM^p|jS715=HO^ju} ~{vpi[SOMLKHB?;;;>AEFGGIIJJJJJJJJLMNOQRRRRSWVv£n:IcxXNYrxzx`854.z¤¢¤~mdXNB?D]jhcJ<EQ~¢cCN|}vmd`SJPYXZPG<:FG:;=EA?ADUPBA705=6,036:?UbYVN<;=D^XHDFHQ]WLJMKM@>@;<?>:;>@A@>??CFGHITahk\FNN`kZOT]T4/@HCAFGGTrrlheddeccdeihe`[UK@5-())/6>IQ[bfhgedcbba_\XTOE=3-)((*,058:=AJS[bjpvvxy{{|{zz{~~|{yxvtuvvxwvtnic\XTTX\`aejqx~ yollnpvwzxurqonnnnmmmmmkjjjhjov"!!! ! !!!"!""#%%''())))*++,,--..//......00......,//15;DNYchopkd^UNGC@=:85331444444445566666544332//-,++)()''&&&$$##""$&'-15;:<;=?CEJOQTSSQQRTY]`adgjmprruwy{}~{xtqmid`_YRLE><<<<<<<;::999999;>IYm~q\>*+/7?KVdnv} yri]SLKGDB=<;>CGHJMNNPRSTSPOOOOOPQRSRSUVVV|£j6Ua{}dRLfu}V9788I£ }wl`ULFI\c\ZGAFJrzZsyli\ZV]lqyvjb[TUVUU\hPC?;FNI;019G7--/48Kf`URP=>CJA=>JMFDMNI>ALVVVSWF:=A=:>AACEGEAMRQ^SOY\IERZZdY[TC:>9EG<0<Rpwrooihncjhbbfeea[SH<1(('*/6?JS[cefgfdbaa`_^YRMD;2,**)+-1578<@IS\dlqvvxzz{{{zyz}{xxvuvvvvuusqmh_ZVRSW[_bgmu{}tniilpuwyvurqonmmmlnnnmmlkkiiox"!!! !!"!"##%%'(())))*++,,-...///.....//......,-..15=HQ[dhmmhd\XQLGE?<96334444444455666654443311/-,++))(''&&&%$###!"%$(-243578:>CHLMMKKLLMOQW[adgjmprrxyz|~{xsokf^ZWSMF?::;;;;;;::99887777:=DRey yhL0*),09CMZfqy~vmbUKFA@CCCEHKNQTUUUVWXWUTSSQQRPTSQUTUTZs,<Xq}hMGLfu|uJ:;<<:gwrtmeYPPTZUQQIFGGf}|]NQTMOUNRnps pbZMFKXONF;GO@2.37<8/-..3<?C?KVJMI;78;;:<>>A=8:;<CHJMP@ADA>?AABDCCFJJIMRPHCPQBEJITS@?B@AKQfktyxunftxwdX]T^^cfea]UF;1((')07@KU\dfggfdbaa`_\XRMD;2,)))+-1578<AJV_flqvxyzz{{{zyz~}{xvutuuuuvvtplg_XSOQU[_eiov|zohdgkpuwwusqqonmmmlnnnnnmmkkms|!! !!"#$&&'(()**+,+,,.../////.....----//.-,+,-,17=GQ\diiifd_ZUQNID@:653333334444554443332210/.-,,+*)('&&&&$$$#""#$$(-0132689>ADFHHGJLNQUZ[_cfknrrsw{|~}|ywqngc]XPMGB=;:::9::99998876666679>K_r}nW;))')-4=IS]hox{uvxpaTIEDGIJMRUY[]^]YWXZ]ZXVVUTSRSTRSSV]b}+9N`{ycNKDIPRIA=>>=<9nnmjb_WPPSQJMPIJGEJv~[AIOMLJE>5?[ajte[NRWHJL=?:52892550-*'*1;?>TL=74774395358;;:98==;:BQDA?EMGCA=??BGIR^Z]dTV^\J=RZQ:@LGEPapvsswtssmpieJSAH]cegea[SG;1+&'*19BKT]dfggfcbaa_^[WRJC92-*+),.0468=CLW`gmrvxzzz{z{{z}~|xvvuuuuuvuuoje]VQOQW^bfkqy}~vmeeejnswwtqoqponmmnmnpppnmnlpu~!!!! !"#$&&'(()**+,,,,.../////.....------,,.,,--047@FR[^cefeb`[XUSMHE=643222344445544433344100/-,++**('((&&%#%$$##$$$'+,-3789=??@CEFMRU[^`dgiklnquwzz}||zwvsojd]WPICA?>;;88889988887765555548:FUj{ vdG-(&()),5>HU]gpv}|pe_f{xpeZONPVZbilnooplf_ZY[_^ZVVVTTRPOPPOOY 318VnpSJPHA<;=@AAA@>7Er~sedaXZRKNNJKMLFGGJLYu z{wF:ABCAA@>;6/2Vvb`UQSI=D98849?=2.1/.-)<A98<86347;<AC:43662<BCG<99;=FFB>8<?@A>AECBIIYQjuMPipraIA@HIH=ThnpqrjZpfl^UR@@LU]chedb[SH:1+'(,2:CLU]dfggfbbaa_^\XRJB:3,++),.0158=ENW`iptvxz{{{zzzz}zxvvvuuuuuvuuoje]VQOQW^bglrzzrha`bgnsvvsqoooonoonmnpoonmnntz!###! "##$%%()()+++,--,..../.././/-----,,,,,,,-,,,.18@HQVZ^`abaa`]ZVPJE@:53322333344334333320/00.-+++*(''''%&$%##$$$##%'*,/37;>@?@CIMVZ]aiknnoopsvxwywvwutqnjc^XQJC<98;<::876567776666542333356=L`s |pW9'%%'((,/7@KVajojdonSj}th_]clv}~}}unghhie_YVSRPNLKLJIGL E.6Og\GIRJEB?@@@?>=26FQqnc]ZWVPOMIHFKLIIPRWZXix|mC;<;899:9763/;f~saSNICEKJ=26J;4,34/+*3?=DE953496<BD<7655434334577::>JVI60,38:<E?;=@FZ\QCL[gb]HAHJE=YmmmprtZClvtFW`WGIP[fghgb[RH:1+)*/4;DNW`dfggfdba`_^\YRIB:0*()*-11479>HSZckquxy{|||zyyy| zxwvuuvvvvuusmgc\SONSY_cgmt| ymd]\`gotuusqonponnnnnnnmmnnqqv} !#%%" ! !#$%%()()+++,--,......././/----,,,,**++,+,,,,16?FLOSX[]^`_`_\WSNHA:532233334433433321///0/-,,-+))'''&&$$#$%$$%%%'+059=ADFJKOU[^cfjnrrstvuwwwtsqomkif`\VPKB:756889::9867566655554331111237BVjy yeF,%'%')*06;DSbmhk]dxqxyxyrsrw }~xpg[QOLLGGECCCAzY.5A`VCHMJHEBCCBA@?^n^BXlg_XSRNOMKGHMMKMLTVWVXZooUB8;:576421.--Dj|eTNJDAIJ/),/32<:,*19,'4?94343413400334331000149<:=CLD/).375BM;?UXQSV`mZ@DPb]KD@B[njnpmpvweEb¨Kd]LBJQ`hhigb\SH:1+)*.3=GOY`dfggfdba`_^ZWQJB:322337;;<<@EMT\emswyy{||}{xxx{ ~{ywvtsvvvvuusmhbZQNOSY`dipw{rh^ZZ^ekrvusqopnonnnnnmmmmnnrrz#$&'% !"#$%%&'()))***,..-.--..-.///.,,--,,+++++*,+,**+-/4;?DIMQVY\]_`b^]UNLB>7312233343333221111//..,,-**)''&&&%$#&%%%%)+/39;@EHNQVWX_fjmnquxzzyxwurpkkgeb^YVQMFA;74446578788665544444443211000023;LburY=)%$%%(.1:GS[^Yacfoqqkiopu~~||xy}{l[LA>>B><9;Xq4/=X[IKMKKTFDCE@>:_jg`ecZSPLPOMGHJLMRMM[VKQTdl]QE;2247641.,*-He|zeV]WQSOD::249?<007:+(+44644438:EOD=7542/--0.038:?>E>5/1:?@?85EURYW_mj[LNLZmS]etulstwtyvUcODS[TAHWfgghhc[RG92-+,27@JSZaegggfecba_^[XTOJD@?BGHMOPPPSSX]ciqvzz{{{{zxyyy}}ywwuuuwwvvvvrlg_XNLOTY`elsxypdYRU[cltwtspnmoonnnnlkkkknoot{#$&'&" !"#$&&''()))***,..-------.///-,,--,,))++))**+***+,.36<@DGMPVX\_`a`_ZRKE=512233333333221111//..,,+**)''''&%$%$%(*+/49?DHLRVY\`deiptvwxyzyyxtpnigd`[WQNJHC>84311124455766543333333332111//-/015CXl~|jQ;&%)45.0<KNGGGONNRMQTWVUZhruyvvuw{r[H>889759~Q=?Q_PMNLIJFCDB@>7UvgVZ`YPMRNHEEJHKNNNYYSHJSq]IC43789952-***0J`|nadcsk\XSMD738DHI@7+/355326GLIKF67:864:A=8/+-7>AM]A2?CHBHG=GMQRU^gd]UVU`egIWorpmo~hib[\[YMDI[`H;FOffijfa]SJ<2-,/38BKSZaeggggecdcddeeb_^YUXX[`ehillnnppstwz||{zzzxwxxz~~{ywuvvwwvvvvrlg_WMKNTYafnu{uk^SMQYdmsvsrpnoqonnnnlkkkkkmns| !#&'&# "#$%&''('(*+***,-/----....00.,,,*++*))**)))***)++++,.259=@EJQV[]__`a\UNG;52222223322210000....---+**(((''&&')-/38;BHMQT[_bfikpstwy{{zyxurolid`[XRLHD>;95211100000033333322222422222210.----.4<LezueO9)6SUJGT\ZQPQSTY_PJFEFG?FILU^gp{xyw{aH93467H}jKGWcUOSMEFCBBB<7FrymZTXZVPIFEFGIGHHIKOPIFJQeA52>C>;720)()**5Ld}wturcbXLKLB<MNF>7;:75427<<:>??;:758DEK;.112>JOiqO/=RNLTQR_sZLRZgQ]R;=\fYOT`\^ZYb^VPQID@LJ@ENA<H:1^kkjhb\TI;1--/5<EMT\dgighffhjlnqrqtsppptyz}~}|{vuy}|zxwwwwxxwwutrle^TMJLS\ciqx}~vgWMIOYbltutrpnnnnnnnmkjiefiknu "$%%" !!"#%%&&'(()****,-..,---....//.,,,*++*))**))))****++***+-257=BGKRY\_bba]TLF;5222123321010000....--,++*(((((++.14:>EINUX]bhjmpstvxzz{yxutqojfa^XROIC=:623210/////00/02222112222243200000/.-,,-.16EZpyiO:CXWQRK]mpbeh]e`YdcRKE>DDEFGDI\mzYA7<GLh}rnngXLOMDDB?A?<;b~si^XUQMHDFIJJHGFILEFEGMM~ymF<7BGD@80(##%')*8P`xujuiqxdikYJW^QC<97840/025668:89GOG9CC7**+1Nhkmd_HBGCTPOS\mcL]fZMG9CNj[CcieXIIC??ELF77?MYRMGKQ[4Ogkijhb\TH>51139AHPX_cfijlnpqsvz} {z| |zxwwwwxxwwutqke^SKINS\dkry|rcQIGPZeotutrpnnnnnnnmkigbdfhnv"$$" !!"#$&'''()*))++--------.....--,,,*++***))(()))*****))**++//48<BGNU[addd]UME93311222200011//....---*+*)()*,/58<BGMPT[_dhmprtwyxxxwvtrpmkgb^YTOJF>;511000..////////./1111112211132111/...-,++,./2<Pg|xrww|~ufQ??Pcuy\OWX_nwtaICCCDFCACDGK\q~~} yc`ny ~qXLMIGCA?=ELVxmb]]ZRIHGHHIHFEFHE<=ESYltmiQ@>BFDB=3&! !$'*+>Q`qtniu~tvpebcZ\G;<;961345369585D;;?;7;65-/9^pqeV]UTOODLSV]WSN\KEPHBZL^m]_`VM+.953674=MQcl_UGDOecihikkgc^VI?8354;FKTYbgnprvvz} |{yxxwwwwwwutrkd\RIHMS\elrzym^MEGS]hqtusrpnnnnnnomkheccdhox !!"#$&'''()*))++--------.....-,,,,*++***))))))**))**++****++-04:@GOU[beif]VJB8322122100011//....--,++*)*,148?CGNT[\bhkoruwyyxvusqplkigb^YTOKEA:62/--./--.-...../.../00000011000221110..,-,++++-/7E[rtq~{qXMQdZ`QJPele~|\QHEDDGFCAEFEGIV_ ~|~ XhXJLKGC@A==Co¡§}ukgdcaaMH]f`WIFEEC929H\hx_`XGDCEB?>8-"!#'*+,/:M\jwxw}~zuj^OUH<DHOXT=:@<67B42>?CFZU<>31547Uid\V\[Q@;Cajh^\ZUFDL?AMXHVh^\NEK?G;?6?CH\ehxoNL=Aeffijkkgc^VLA:679?FOW`hotw| |{yxxwwwwwwutpjaYPHGLS\elt{xiVIEJT^jrtusrpnnnnnnomkhebbchqy !!!"#$%''()()**,,,-..,........-,+***++***))))((((((()(())*'))++-29?GNU_dgjd\RKB:55431200010..//.---.-,,.168?FJOT[_dilmpsuwvvtromihea`]YUPLFB;710.----.---.-----.-..//./0000//00121122/.++,+**))),0:Ndxz| }zlX]nbVOew|n`OEAFFEHEFHJLJA?Snq{y{q ]HJKEA?;::J}£§ek~unlkjiix\F>713Kekrnea]MHDDA??=2(%'),,,,-:L]gpv{uoaQbVNWUXUX>5ECEBD16A:FOKFC0.;9,4JZSNP\UF=FKTLQILQUGEGJMVWHIXZM@EOIG8?C89DXny}ysR4=Qhgggijlhb^WNE=8;<CLU_gqy} |{zwwwwwwwwusog`YOGFLT]fmv~whTHELW`kruurrpommnnnomjgdaachq}! !!$#$%''()()*+,,,-../........-,+******))))))((((((''(()))())++*+06>EPY_gkic[SMEA=876521010/.//----+-/137<CIOU\aejmpsssstrpomjeb^ZWSRNKEA;641,,,,,--,-,,,------+-....-.////////010000/.++++**))()*2@Teos yvv ~~\Rksnf]o|umg[WPEEJRKEDCEIKGGJ^rwzxycGIIEBCOG8Q¦©£^,X{yxvzz¡ydM>6/8Selsnga^UKA?A?@>81*.///-+*+:P[gjl¡{kceebXTDVM<>B?IA0=;AF?GN:.?VD-5IT[JAJ<<LLHP5:<@FWVFEEON\f_M85GYKSL67RY?DJfp_jrOCWcfghhjjlhd_XOF><>BMV_is}~{yy{~ |{zxwwwwwwwusog_UJFEKR[clw ufSIJR[emrssqppommllonmjgd``cis~!!""$$%&&')*(**,,,,-,-........,+++++**))))))))''((''''''))())()))),19@IR\dikib\WQKGC>=;9533100...-,-//47:?GMTZ`cgmprrtsqqljhea\XTOLHEA=9510.-.++,+++++++++,,--,,,,------.........//.//..,+**)))))(*+5FZf|~qbphioWXeX\ggd__qtlgbZMDFJPJEECDGIKLLOx u}y{wvqDLKIIC?>7Z¥ª¥+4Mr}y{¡ t]H;4/9MYbghc[VTOA>??AB>813310/-+*+5L_hdk¢¤¤¤¢~`VhqfUOaVE<HW>2<:DORK>/&4GWE2;GUcVCED5;F[S<737:EDHB1:AFab8<Y_XL[PKSnh`c`jnNV_]`fegefijkkheaXQHBCHNYbkuxrlfb``bdgkorx{|~ |{zxwxxwwwwurmf\PGDDJPZcmwufVOPW_gostrpnponnnnnnmjgea_djt!!""$%%&&')*+(*+,,,-,,........,++++++)))))))))(''(''''''((())(((((*.3:BLT^dhjhc^XVRNKFC?=:8632/.../0369>CHPV]agjmptrpnljfb^[USNKFA=9311-,,--,++*++++++++++,,,,++++,,,,,,--.......//...--,+**))))))(+5Mpzyuqmii^\vso}wtfZ]bTKTbuuimmgVDAIHJGDEEFFHILMuzvvzKGHFFEA>5c¡§§¥W2=?FI>Aj ufZS;11>LXZ]^`XTPMC=>BDGB?966431.-)*+5Rb_]nxixsh`[WZKGV`=94;AGFH89>H<E>9<HQYN<;_C=FZ^E1.66<HE@=EHHT^enkgeieimlfPUi`fl`ekiiecfgggkkkjfaZSLKNRZdoy{rj`XRJFEEGKLR[_glnsvz}}~~ |{zxwxxwwwwurne[OEAAGQ[eoywh\RTZclsttrqpponnnnnnmjgebadlv !!"$%&&'&'()**++-.----,-.......-,,+++(**)(((((((''((''&&&&((((&&')**/4;DMW\dhhgdd`[YWRQMIECA:6522237:>DHNTY_ehkmnonmjfa^XUPLJE>;730/.-,,++,-++**++++++++++++++++++++,,,,,,,,--------,,,,,+**))))((+0a}oeivodi^f{zym]f`^|ucdlt| yudUKLLGDEDFFJMMJi[r {yut\DIHHCA<:r£¨©¦t0<;974/P~yqeXPG5:BLRRTVXUOKHD<9AJKFD@;97651.+.17@V^[bkt~ ~uiUPUTUddLB7522;><?@4779<;:=A:60-:;>7gZ3-0>NA6<JPRLcxwrpnkjikii`Vhs`Zqqmihfeffghjmmljida\YY\cju~~ti`UKA>;<;;:<>BJOWagosvxzz{} ~}{ywwxxwwwvvqkdZQFABHR\hqyvj^YZ_gnuvtrpppnnnnnnnmjfdbabn{ !""$%&&'&'()**,,,--.--,-......-,++******)(((((((''((''&&&&((((&&&(**+/3<GNV]bdfedcc`^YWSSPMJEA;988==AFKQW]`bhjkihhea\YTPJF>:731//-++,,,,++-,++**++++++++++++++++++++,,,,,,,,,,------,,,,,++*))))*-3Gzqzwtwiikgju`MK[\egebd_g{}££xUCHHEGHHIHIIG`/9Ocqywuwh@GFEA@=Ey¢¥ª¦19;;;949fy{zvqg_XPLGGHIJNPNPRNJIF=4@NNKECA?:7642247:7CW]_dlpqsusvwrjhjfVIMUZQVXN12,9E?<IJ29<881/9KD00794<9=Q+,1692HemRPJqzvronkkjjknmqqu]Jmrnigffffghikklmmljijkpv|}vme]VOGA>=;<<<>?BFMV]fmsvwwvwy||~ ~}{ywxxxwwvutnjc[PE@CHQ\hq{vj_[]dkquwupppnnnnnnnnmjfdbaep~!##$$$&%&('()++,,,,,-........---,,+****))))))((((''(((('&((''((''''(*)+05=ELRX^acdcedaa^][[XTQLHEECDIMPV[_bdgihgda^[SOJE@:61--,,+++++,,,,,,,,,,++****++**************++,,,,--,,----,,++**++,,)+**.7Qdxdf ppffibOMLNMZgm_TOL^hpj¥¨¡xU@IFHIGIJHHHT@;;<Ig}{uvwxzBAFJC>>V~£¦¦}.9=<<:54Jgkljf_VTPNKJKJLNONNNNMKGA5APRPIFFE?<96:;;8799LX]`ejjptvxpZGFHJIDDGQJFHG>;C26<?<99EC=9:;B<<<35<17;?`SK?<<F[ebSOY{xurnkjjiijmnqrsmktpkigffgggihkprtvuvxz} ~{xrmhb\YRME?;:<==>?ACIRYbhptwvvvwyzz{|~ ~|zxxxxxvvwvsojcXNE?BHR]kt}wh__bjqvwwtrqpnnnonnoomjfeacfr !"#$$$&'(('()+,,,,,,-........---,,+****))))))((((''((((((((''((''''''('*,4;@GNRVY\^^_aa`a__^\ZVSQPPPTWZ^_acgffb^[WSLGA;50-++**)+*+,++,,,,,,,,,,++****++**************++,,,,--,,,,,,++++****++)+,+0Nsysly }|vsyydUP[[K`gdb][V`cd~¯³«¤G@FILMMLIDBGD4>>?E\yztwy{N?EGD@?h£}.68;:8617S__\][TMIIHFJNNNMMMLKLLLF@JRSVGDHIFD@<=BFB@>>DTXZZ[__ehnpne[TLB;<<GSPV]]Z8-4857;7BC;>B01;CSL8@769<OYID:8Xut\QLp{xsomkiiihilnqpnputokigffggijkpty}}zurnlifc`^]WQKC@<;;=>?@@EKV^dlsyxyywxyyyyz{} ~|zxwwxxvvvusniaWMD?AHT_lv}rg``hmuwyxvrqpnnnonnnnlihgdejv!"!"#$%'&'())**+,,,,,-......--..-,,+****))((((((((''((((((((''''''''(())'',07<CFJNQRVY]_^_`_a``a^][[\^__acdecb^ZTNID<52-+**))*)++**++*,,,,,,,,,,,,+++++++)************++--,,--,,++++++++*)****)+-/I~x|~xwwvzvpkffoghYFcpWWfcdefk°¬¯²¬®«¡wC<HKJGJEAABmk.:>@@@Sruowx|aCHHEADy q-6899:973>QVUWVRNJIIGGJMMNLMNJJHIKN\e`UEEEGLLJFDSbSCCB=OWUTPKGIOSY[Z\XK::ILROKIRXK=:.5?9:8>B:86.(*,44(9757?H<B>;<h}UM_|urpmkjgghiintoovtspmjhhghjkosx| ~vplgfc_`a_]]ZUOHA><<==>?@BEOW^hpv{}|{xxzyyyz|~~{zxwwxxvvvuqmg^ULC?AIUamxyoeacjouxzxsqqponnoomnmkjihefmy "#"##$%'&'())*+,,-,,,-......--..-,,+****))((((((((''((((((((''''''''''((''().26:@CGILMSUWX\_aadedcaaa`adeca`]YRMIC;2-*)()'****++++++,,,,,,,,,,,,,,+++++++,++**********+,------,,++++++++*))))))+,?yj|{|yur|}ypvy~vj\QXkgSGdwyzy¨ª³¹´·¹±h7IHGFFCCC=W}S4@?=@BNptqxz~xHHLFBGb,699::8521<KQQSQNMLGGGHMJLKMMIGEEKSWWVMJFGILQRPMQ^]PMC9HVRMD=9666:999510:FNQA=ADD8?><HI>9?DE95535/$2<=CA6;CD>:878ENMEMho}|xupnkhgghiorquuuspnlllnopsv{|ungc`a_^^^_^\[YTMG?<:;<<=>@DIQ[ajv|~{yyzzz{{ }{yxwwxxvvusplf]TJA>BJUcp{xmc^bipuxyvsppoonlmnnomkjhgfgp} !!#$$$%%&'(())*+++,--......//-----,,+****))('''((((''((((((((((((''''&&&(''((()-036:=AAGKLORVW\^bbcccccaaa_[WQMHA;3.)'(())((*+*++**++,---------------,+++,,********+++++,----,,--++++++***((()))*7kqXowz~|nvspwphcVc}oG4Xv|z £®·º»»»¹BBELJDBCD>F~} =8?>>ECawsy~ ONIFAJ^+677999730-8GNOOMKKIGGFKKMKMMJHFCFSMKNRSIIIJQVW[aiYLJB9EVSJB>@EH@:2//--07CE7223:;>C>DMK?=9BH96864479AC943<A=;9::=<748>@BJOXcnmliggiililtvuttqrrtvvx|~umgb^[[]]^^^]\\ZXRMH@;:::;==@EIS[foy ~}{yzz{{} {ywvwwvwvuuupke\RG@?CKYgs}ui]]ajouxxusqppnmlmmoonlljigjs~ !!#$$$%%&'(())*++,,-.......//-----,,+****))('''((((''(((((((())((''''&&&''''''(+*,.0156;?@CGLPTZ]^_````]\ZVQLGA:3.+)'()((())*++**++,,,---------------,+++,,++******+++++,----,,--,,,,++***())))*,GohWYqu|}{qulpmXEdsjRG]ft¨µ»»¼»ºµ]7>AEBCC?@Cd~~f1@@>CHqxxz~{z[@EGHJQ07:9:9863/.0;EMONMKIGGGEKMKKJHEFCDXQLNSTHIIKMU_rwod]KB=M[RHGMQRPH?30/00022//0325;;F;;EB938>A=7979:>>82363BO=6589:<;9=CB9:BEHR]aagihjootyzyyxvxz|~{skc^]\[XZ\\\]]\\\YVRKE=;:9:;<<>EJR_gq| |{z{|| ~zxvvwwvvvuttoid[OF@?EMZft~ |re\Y^gnswvrqpppnmlmmonnmljihmv !""$$%%&&''(****++*-............---,,+**))))((((((((((((((((''''''((''&&''''''&&((((++*-1468>BGLOQUVVYYWURNKE@83))''((')(**)**,,,+--,-......----------,,++,,,,+*++++++,,--------,-,,..06+)++**)*,5Y^cw^k~x||{{qmvoxu\AVp}ucY]b{£¯º¼»»º¸§p06=BADEDDCN~}H1?BKm~|{e?CCEP@3;;:::9863/.0=JLMLLLJJGIKMIDCAEDEEX[MOTSBHIKMN[mmlgfI?D[^RPVWWVK@82.*)-.../../147984852.1;A>;;69:;<981::144728BIG>75>CDFC>9?PX[Y\dmqtvy}~}}}~{pg]WXY[\\]]\\\\\\[[WUPJD<:99:;<<@GKS]kv }|z{|{ }zwuuvvvvvvtrmgaYOE?=EQ^kvxn_VU[blsvvtqponmkklmoppomkjjoz !""$$%%&&''())**++*-............---,,+**))))((((((((((((((((((''''((''&&''''''&&(((())((+,.257:>BDIJJMNKHGC@:2,)((''(('))*+***,,----,-......----------,,,,,,,,,+,,++++,,------------..4S@(++**)*2HRnqim~ {{vprmM?UBbxx|nX_j¨²»»»ºº¦t47<??CECCCFcyzD-C\~|} zIFAFLI5<9::::9542.+4ELONNKIJLLMKGLPRPKDAE]ONTS?EHJMNQ^b`^^MKR]]Y\_\WPE=86-&'*++-----0125@A><<FGHBF?<79779345631/26137:9?<:><?EKJGA7=Ni`H]x|z} xlcVNLPTXY[[\\[[[[\\[YWUPJC;:989;<?BEMWajs|{z{{| |yvttvvuuuutrmg`XKA<=EQ_lxwl]SSZcmtwusqpomlkklmopqpolkmt} !!""$$%$%'((*****++,.......//------,+,,**)())''''((((((((''''''''''((('''''''''''))))))))))*,/0357:?AAABA=;62,)'&''(()))))*++,-,+,,-,-.....//////----------,,,,,,,,,,,,,,-----------++,/1-*****+,AX_{x~RJTettwsupbT>LutrX_o¥®´¹»º¹£i17:<>AA@@FCDm}{};2g} VKDDK}P59:;<;;:941.,/@KPPLJHHLLMPQQQPMG:01aVMQM4=CFGJV]ba^^bbddddc`ZRE<977.'')**+++,,.0019=AHKFFOOKEB<E>FORF?961433453538CF=@A9:@GKIG?7QaV^t wndVMFFLSVYYYZZZ[[[[[[ZZWTOIA::989;<=AJX^alw}zzy{}|xvttuuuuuutqlf]UI@:=EQ_mz ~wi\QSZenuwvtrqonlkklmnoppnmkmu !!!""$$%$%'((*++**++........//------,+++**)())''''((((((((((''''''''((((''''''''''(((())))***)*,-..13677762/,)((((''(())))*+++--,,--..//../0//////-----------,,,,,,,,,,,,,------..---.-,-,,,**++-8OQyTb[OJMi|ty|zyoH?^xxunScq¦©¬°·ºµN/9:>>=@A>ABBK{}zx@w}{mNLEI|U59;;<;;9743.-,9IOSVWWWWVSQY[XOB3,(,]^OPJ,/49?T[`e_`jlmhiiij`TF?::992)(,*++***)*-..2=IKMPNXOILHAFMQVNB/264//1460/8K@;85NTIA99@DGA9=Q[Tk~}}}|yxoj`VLFDGKQTXXWXXXYYYYZZZZVRLFA::989;<?ALTW`lw}zy{{} ~ywvusuuuutttqke\TI@;>FRan{ }sfXPS[gqvwusrponlklmmnpqppnmqy !!!"#$#$%&('()++++,,..////////----,,-,+***))))((((((((((((''''&&''''((((''''''''''(((((()))*))******,,----+*)('&&((((()))**+,,,-....//./001111/////-..//..--------,,,,,,,,----..........--++++,-0JKP bMHEJSh®©¡ vvopkoO:;Hb~iV\y¥§§©³©|729<=<<?A@B>?BQ{{vw|w||{ROGIx`7<;;<==:841/+-8Xmvuoja]YV_eb\K4&%%'TcLLH-..0T`fbaexqpigjkml\IB?<===6/12210.)(%'+../9ALOIV`QJF?GKLEEJF1#093-./0,/?Mx_C<EPTSL=9:==A8<MXj |wsrrqssqoke\TJFEHLRTXXXXYYZZZZYY[ZVQMGA<989:<=>DIPYboy}zxzz} }xxvstuuutttspjd[SG?=>HTcp{qcWQW`jrvvurrponmmmmmnqqqmmot~ !!!!"#$$$%%&'())))++,,--..////..----,,,+****))))((((((((((((((''&')(''((((''''''''''(((((((()*))**))))))*****))(()(((((()))*+,,,-.//////./0011111/00//..//..--------,,......--....//......--,,++-.5VIhKGEXr¡§¬§¢}okmsr]??83Gmw~Y]p¤¡O4;;:::8;:<?>BMOd {ywy|x~ ~SIHOpj:<;=<=<85400..[}soi[WUTZ]]XE,&)(*HlULJ/,/Tkkkgl{yojhegkme\RJEA@B?<9768630*##$(,-./8AFQ[`RKLTQZc_WQI5!+<<1.00.+*<veI?JPPQN<7BDB:CWh |{vqliiiklnnmhc\SJFFIMQUYYXXYYZZZZYYYXVQKE@:98:;<=AFKT\dqz|yzz{ }xwutussttttrojd\RG?>CMYerzobVSX`jsywurqponmmmmmnpqpomm} !!""""#$%%%&&'((')***+,,,,....//.-----,+*)**(())))))))))))(((((()*,)&(''(((((('''(''''((((((()()))))****))())))((*))(())(())*+,....//.//1111111111221111/////.------..----,-...-............------./?]nnnG>a¨¦¡££«ªª©wx|zb[KHCDJqy}o^jwlD:=:<=ACC?=<>BHJRmxuv| zv{y t}hEIQgu<9:<=;:944/.+K{~zvrpe_XQJHCDGB400-.AlZML01Ejmrox|vnifchlomh^XTPOIA=<9<986,%"#$&*..-.2D^eh\PVXdlinlidZG8846/--4<?EYvz oQDLTUR>9=DB8=Wo{xsokhgfghjllmoic\SKGFIMRSXXXXXYZZZZ[Y[XUPJD>:989:;<AGLS\gs| zyyw{ }yvtrstttttsomgc[QG?@FP\huxm`UT\dntwttqponmmmmmmnqqpmp !!""""!"%%%&&'(()****+,,,,....//.-----+**)**))))))))))))))(((()))).9+'''((((((''''''''((((((()()))))****))()))))))))()))))**+-,,//./01001111111111222211/////.------..----,-..............//..----./EtzeCRBX¨¥¤£¦¨¥£¯®«¢ wlj`e[eW_w{{efr{tJB:A>==BIJOJDB??HQRq{wuw|sqy{wknwbs{ECM^wz@68;;962110,8q}ywuuk`VSTMEA:;;<9644>hZPP39fjqt}zroqslcftxofe_XQJE@>@=<7*$$$$&--.,),Gdeg^[[dqy }{~wa<04.+6FFD?CLL^`\s|}{[FGLFG@7?@=A[sypnjhgggghjlnmmkf]UNGHKORUVVXXXXYZZZZZZXUPJD>:989:;<BGNV_jt}}yxxx| yvtsrsstssromfaYOD?AGR^jwvj]TT\entwtsppollnnmmmnprmw !!""####"#$$%'''(())**+,,---....//.---,,,+****))))))))))))))((((((((''''''(((((((())((''(((())))))))))))))))))))))))())*)**++,-.,-..00111122222212111111110/////...-....-----..-....////......----,,./DmLB@EQ¨ª© ¡¡¬¯¯¥ ¢vnaenihfepl`nv}yT.4>D>?@?@@IEDM>:AFOIx}vsyzuttpWa}wm{UBEdr ~A77998432/0.G{yptpom`ps[:F@:;;;:8=]\PSA^jnx~zz}|n__^iwtmeaYXUQKGDB>2'#"$#*1..+*+K^_aT\Ya`m{usyzzrjT504>?=978>Qlkb]]csi[NJFOPI?=@>A[xrlkihgffhjlmmnkc\VNLIKOSTVVWWXXYYYYZZYWUPHB=:87:::=CIPXakv~{vvwy} zwsrrsrrrqppme^XNC?CJValy~tg\TW^gqvvtromlmmmmmmmomo !!""####"#%%&&'((())**+,,---....//.---,,,+****))))))))))))))((((((((''''''(((((((())((''(((())))))))**))))))))))))))()++**++,,..-///00111122222212422222110/////........-----.......////.......---,,./=TGCDTt³²£¦¨¢¤¥£ª§¥¢¡qdcdimgfkuyt|`kv}z|j<,29?>?@???BFHJA<>AJMLy}uvzxl[V\wcCCO ¡^?78977744566M~q~P;DD<;:<<;S\NWeqs xg`_^`bb_ab]XUSROMHF<)$#%$%.2/.++2N\_WGQ[]]amzywwurhijZJF>.6;5+*4T~g]SNPVNLM>8=>EZ~sigefhfijkmmmjf_XPMKMQSTVVVVXXYYYYZZXVSNGA=::9::;>DJQYcmw~}yvvwy} {utssttssrqpme^VLC?DKXcn{ |rfZSY`kswvtpponlmnnllmmr !"""""##$$$$''('(((***++,,--......--..,,++,+++**))))**))))))))))(((((((((('&((((''(((((('''(('(())))))))))))))******)(****,-,--../00/.002011333322243333331100000/..//..--.----..-................---+//>FFCNq¦²±®±²¶·¯¬£~vi[ojdehnvz{jfr{{x_1,01:CDDA>=<9CJH::=HMCR|y||kHQTk~mJCDv ¡sv?:;;<>?@>CB2V }opkU27?D?;<=<J_]gpo} rcbdfceb^UNTTURRPPNKC/%#$$&+442/-5DP^ZGCIW]\\ar~yqhimievhH:-0064./4R a^r|viXKGCEGBA@Amqjhhfgijlmlolg`YRMLNQSTVVVVVXZZZ[ZZZWSMF@=;::9;>>DLQZeoy~ |wvvx{} |wstrrrsssppmd]TJCAENZer}|qcXSZbksuuspnnmnlmmkmlv !"""""##%%%%''(((((***++,,--......----,,++,+++**))))**))))))))))(((((((((('&((((((()(((('''(('(())))))))))))))******+*****----..//00/0012322333355334433331100000/////.......-...-................---+.09AEOp°µµª¯³µ¹¸³ {zjbi`acst|~RbpxuX,(+17<@AB@>><;AA<=;AKH9Z}{{EIV_xtqQDEWvJBBCDFDC@==92n©¥¢~ |yg[IA7548?@;<:Cahkqv e`ghjjhdd^M;=LRPPQPMI>*""%'*056425EQPRF@FHP[\_[bnhfcXQRYciK301./2/,-8orv xM=FB?CELbsxmjgighjlnnojg`YROMNQTUVVVVVXZZ[\\ZYVSMF@<:;;:<@CHMT\gpz|wwwx{} }xtsssrttsqpmd]SGAAGP^juzn`UV\dmsuuspnmlmllkngw"!""!!""######$$$$''''(((***++,,--......--,,,,,,,+++*)))))**))))****))))(((((((('&''''(())((((''''''(())))))))))))))******+*++,.,--..//01111112332443344444433332211000000//..........................----,,,/<DUt ¥³´·¡¡«¥©©§¨£|xqggemnqt SRjutR'(+.86:IDA?=<:9=:=;?GMDF{{y |GOXzyaFCDi_DA?=?@=9:7R`B ®¢qhiZQC864324>C?<AWkntz igffimpmkf\B00>LRQRSRJ7&""'+,3659:@KROD7<AIPTWW]`cY\\H=72:GWO6//21/..I_gx{y{|~wkobT>CHEEGEPPS^cgghkllmolga]TOMNRUVVVUWYYYZ\]\\ZURLFA>===<>BGLPW`irz~yvxxy|} }xtqsrsttrqljd]QE?BHT`kxxm^VU]fotusqpnnmjikjh~"!!!""""######$$$$''''(((***++,,--......--,,,,,,,+++*)))))**))))****))))(((((((('&''''''''((((''''''(())))))))))))))******+*++,---..//001111123234444444554433332211000000//....................--..------,,,/?Qo®¦¥²p~¦¤¬© zsljckqut} ©nJejhL*+&,.>MKJBAA?<;>;;=>DHFFw~{{}uJRv yrNFCKsrLE@?=>=<::5GdX©~f[PMMC6643126>IHBE`osxsqkkjklpspjX9./3BOQRRPA-%$'+-0;?BABFGHLB89>HOQNVYYXOC3,.1334BOO:7618;6N=<Spuyzub]kqq m>LE>HHKMNQUV_dilnnnkgb_VQOOSVVVVUWYYYZ\]]]YWRLFA@?>?@BEJNR[bkt{ }yvwwy| ~~yssrstsssrnhaZQE?BKXcny wl\SW_hptusqpnlkmjil""####"#%%$$$$$%%%''''(((***++,,----....--,,--,,,+++*)))******))**)))))((((('''''&(('''''((((('''&''(())))))))***********+***,---..//0010011132455665444665444332222111000//...........-.---......--,,,,----,0Ce¡©«pr¥®®¬¬§}pifnrs{©®G_^]G..3.-6G:>FCB@<>=<<<=BKLHzrz|{ lMc{yndk}_EDBLcpTFEA>===;:814Zj¥yvmeWLCBA74334:CKSSSEGOUX\s |zqmlopvwqiS4-005HRTQH8-*+,.2>IMNOONGEF:8:>BJOOTSI@-+A`z£¦¥¥£~k]WF2=@5?Yosyzj\ayJCG<EKEUcXVUSWZ_ekkmmg_XTRQUVUVVUVXYYZ\[[[XUOLFBA@@@BDHLRV\dmu|{wvwv{| ~~~~~yusssstsrqlf_XOE>DMZeq} ~tgZSW_irtuspollkljj#######$%%$$%%%&&&''''(((***++,,----------,,,,,,,+++*)))******)))))))))((((('''''&(('''''((((('')'''(())))))))***********+***,--..//00111133333355666655665444332222111000////../......-.-,,------,,++,,----,0Jx£©²¶ªs{xtvsiqw|©°£RUWV@*+AC20037DA@@=?<==;;?DJG{vj|z`Rfmc^^^k~H?CAABGHDB?=>;95225Td¥|vi_UTLD?834;?GRVY[XYYMIGIOU]izxihry~zmF.,0317IOI</.-.12GY[Z]^YTOG<76<AFJMNK=2)7Xy¦¶»»»»º»ºµ®ªh/1540?Xku}{ws|mbAJEHMNnuhbd[VVW]]ddda[UTSUVUVVTUXYYZ[]\\XUQLGCBABBDFKOTX_fov|}yvuwx{| ~~~~~zuqqrssssplf^VMC@EQ]gs~|sfYT[ckrrtropmkjkk$#"#%%$%&&%%%%%&''(((((()+++,,,,.......-,,,,,+++++++**********))****))))((((''''&&&&&&&&''''''''&&(((((()))))))*****++++++,,,,--////112233433444456666665666554433221010000//.-/+.--.-----,,,,,,,,,,,,,,----+0[ §¬¨rwvwz{rx}p®¥XNPJ5(*7C/-0007CE=A><;;9;@EKH|~sj}wzZ_d[\]\ewo?@A@?BFDB>=;98400;Ub||pskXUPE997<CJOTVYX[]\`ZJDDKS\i|waZu p6-026628DA1,0221<Waahqm`WPF627@IJLH<3,0>Zz¤»¾¾¿¾¾¾¾½º·´°ª}9(024?Mdtw||LHHOSPZtkjie`]YYZ]^]YWUUUUUUUVWWXZZ[\\[YUSMIEDDEEGJNRV[bhpu}{wuvvx{~}{{}}~{zvssttssqojd^UKCBIT_jw{reWS[bkrvvqolkihk&$#$%%$%&&%%%&%&''((((())+++,,,,.......---,,,,++++++**********))****))))((((''''&&&&&&&&''''''''&&(((((()))))))*****++++++,,,,--////1122334334444566666656665544332210100/0//./.8/,,------,,,,,,,,,,,,,,--,++2g £¦¤¥¡£yy|~ywtv|{a3LPKE/'(,.,,+.16;FA?C=?<<;<AFEysmo|{~rSZRUWYfvU??=@CCCC==864100=R_y{plj]JJA457@JNSTYZ[\^_bbOEFLR[i{{bm\--13775353++1449T_emx|ueYJ>64@MONH6.,=HWv¦¹¾¾¿¾¾¾¾¾¼º·³°®¥8.4348Uhg~z}~{hGDNNNOyxkhjhgojd_][\YWXYWUVVUVXYZ[\]]\ZXURNIGGGHHJNRVZ^dlrw}}zuuvxz|}|}~~}}{wssttssqojd]TKCCJVbkyzpcYT[dmtuspnmkij(&$%%%%%&&&&''''(())))*+**++,,,,,,,,---,++,,,,++++************))))))(())((((''''&&&&&&&&''''''''&&(((((()))))))***++++,,,,,,----//0002223333344455565566665555553322110//...../MK0----,,,,,,++++++++++++,+,*+5h{ ¢¡¢ §¦¢xv{}|yvokojW;]dNMPN@+')+-/;1.027JD==>@>?;;?@Djxsnu|| ^XWTT[jz}xx{M>@ABCGH=:8431.0<R\xtqmd]KH=875?MSY[\aa`abc]JKNSTZduw £ A//0258:51--.049M^epz pYB:99DQWQB//5CMc¤¹¿¾¾¾¾¾¾¾½»º¸µ±¬¨ r018G5>XVou{|{cn>HGOPo~tmlhdedbb_[WUVWXXVVVUUWXY^`][ZZYXSNJIHHJKMQRV\`fmuz{vtuvwy} }}{}~}zwusssssqnicZPIDGO[cq{xmbVV]fnuwsqolml~'&$%%%''&&&&''''(()))*++**++,,,,,,,,---,++,,,,++++************))))))(())((((''''&&%%%%%%&&&&&'''&&(((((()))))))***++++,,,,,,----//0002223333344455565566665566553322110//.....,),.------,,,,++++++++++++,++++6f{££ ¢§zvuyyxrme\WQJPNNMPK<+'*),0.+,344459>>@@?=;=A?_vnln ~{}pQPU^dq|xsl}kB?CBCFD=:7521/0;MZtz~xsh\YSF>?:5;PWY_dfqslf]XWVLLQYao a130146674421036I\eow|l:7:;<<>@=70/7?Ef¶¾½¾¾¾¾¾¾¾»»º¶³§U0LW@0CBaYm}xFM~[?EKNV`^URJIIJLMRQMKJOSWWUVTWYVT[dee^Y[_]VPKIJIKLORUY^chov|~ztsuvx{} ~~~}zxutssssqnicZPFFHP\es}vl_VX_gpuvsrpkn ~t~''&&''''''''((''(((()***)*++,,,,,,,,,,,,++++***,++**********))))))(((())((((''&&&&&&&&&&%%%%&'''&&''(((()))))))*))**+++,,,,,----.0/102223433444555665566665555543311110///.-..-..----,,,,,,,++*)**********)*)6jyz{}wpbgaPEKQONOQLJ<'&()+-*)*,+/31619>>A>=?>?Puonlr|~|UASdpyzreY`xy\CDB?DH>:74310/:L` maVTSFBB>8<SX[ahnvywrvgbfegixnD6667986234453E`flu|b27765567530047Bu¬º½¾¾¿¾½¿½¾¼º¸µ²«¡~MKUT19TjX^}~~zywMENNeWSTRPUYVXWVQONMOWUTVX[\[TWZ\b^ZZ`ddf]OLKLLMQTX\`fjpv}~|yuttwx| }~~}|yuvttsqpmgaZPFEIS^hv|tj]WYairvvtqlm |jn ''''''''''''(((((()))***)*++,,,,++++++++++++*,++******++****))))((((''((((((''&&&&&&&&&&%%%%%&''&&''(((())))))))))**+++,,-,,--../00002223444554555665566665555543311110///.----..----,,,,,,,++*)**********++/6kzztniajcK9COOQOPOH8'(()***)*,+.5;JG9:@@=><9;Bzwwuw~qXahv{wn\G>HZeWECEC@@9731/0-;I`cGSTVHDGCCHRX_hjnwf^elgmrutw~wpV=:9898543258:Yttuxb675443020..266Eq´»»½¾¼¼¸´¶ºº¹³®¤ZRLUR14MO=h||xnTKLLMNPMONLDLXUMECCEGVRQ[YSYWceemkhhfmoh^QMLNNORUX\bhnsz~~zvtstwx|~~~}yusrrpnomgaZPIHMW`kx{si\W\ckrwwqpl |ddt((((((((''(((((()))))****)*,,,,,+*++++*****)(+*)))**))))))))))))((((''''''''&&&&%%%%%%%%%%%%&&&'&&&'((''))))))))))**++++,-,,..//./1011112344554555666666555555432211000/...---------,,,,++++***************0@>h }yqqpmlgcTJKKMNQNJH7'((())+(*.,+,7P=83<:??=<9;l~YitxzxjWD97:KaTB?CCCA841/..<KZvRGGWXMHCBLLV[\eu {`SMby tit|vsmhaS@<;98766656:Uw~{ c:863200/-.049<JUo¤¬³¶±§¨®°¯¨XIJGWI07E4Kty}wep>LP^c]_`VRHZhhlke[SUgRGX_Zcia[gmlc`Xkl^SOOOPPRUVZ^bgnuz}}yvusvxx| ~}yusrrpomjg_YPIJQ[epyzqf\X^fnuvwrp~ {d^iw((((((((''(((((()))))****)*,,,,,*+++++*****)(+*)))))))))))))))))((((''''''''&&&&%%%%%%%%%%%%&&&&&&&'((''(())()))))**++++,-,-,,--./1011112333555555666666555555332211000/..---,--,,,,,,,,++++***************,.0a wywvrme^UNLIKNNNKI:)((())**+---,/3+0VDB=?@>;=\}qrwwytgTC8548LZODAFDD=51//.<Mezs4@LNPLA<?BSX]dp tum]Rbx|{nrtrlfc[YPDA>=:97779:Mv g<75631/.-/39;CU_fks{}w~ZDDIKQP<<Mm}wx~~l_CMYccekhYP\flmmnlga`XPJM[Z]ZVOVbeUHA]dWQQOPPPRVXZ^diov{~}yvsuwxz} ~|xusqqqomjg_XOILS]gp{ ypf\\bipwvuqy zd^`jv((((((((((()))))********++++++++++++++******))(())))))))))))))))((('&&&(''&&&&&&%%%%$$%%%%%%%%%%&&&'''&''('((())))**++,,,-,,-...000110111233445556565565465554442120//.-------,,,,++++++**++****+,(*))****))),Z}}xvtmd]RE>BGMMKHH>-(')*(()*-.,+(),.:CP;?=><>J~ duxvpcO>777:EV\O?>B?@:40./:Kgh.6QdTHB>>AE[ad{vx{p~¤¢¢ ~rmhgd_ZZTOIB@>=;98:4[w{ |{jD6643/../15:?Ph}~ywma_becdlx|{ncY?@7<7EZBAbvxu|dL`OMijgg`^[_hjmlnnkha^__WYZXUW\]ajhB08WoWQQPQQQSVY\`djpw|}yttvxy|}xutttqomje^VMIOV`iu~xpd]]dksvuou~g__enz((((((((((()))))********+++++++++++*********))(())))))))))))))))((('&&&%%%&&&&&&%%%%$$%%%%%%%%%%&&&'''&''''((())))**++,,,----.//001110111233445556565565554444322100/.--------,,,,******))**))**/2))))(())))),Pm}xwwtoe_UD@CJNIKHF=-(')*'(((*,++.-3+).8<>?<:<@ inwup`LB88<DZf_TMDB@?;51//;H[}Y2Srx`LEA?@AOfnws¥«¥¦¦®°ª¥{rie`]ZVRNKEA==>??>;Z ~sw~{xnQ8753300158?BVv}iZVROMT[^`d`]\ZbtYB?=B4=AK7Kegnz h&bqSZ]dklfhcbhopppkgd__\ZXYYYZTT\^X6(1OaURRPQQRTXZ\`fkrx}}zvsuxy|}xtssspnlie^VMLQXakv~wod^`gnswtt iccbfq~))(((())))))))****)*********++,,++******++))))((((((((((((((''''&&''&&&&&&&%%%%%%%$$$$%%$$$$$%%%&%&&&&&'''''(((()))*)+,,,,,,-...////01/11222445555654444433333201///..----,,,,++**++++***********)))'''''''()+Ik~ } qtyslhaSLHGF<@IGE</(()*)((**+,+-,--*,/54=<7;Lruum[K<87D_lrh]WOF@B?810-8D_rPNwoSJHAA>NUe| µ²²²¬¥ ®³®¤}slec^YWXTMHB@@BCB@Cbxw|}yvo[@:;;<7257<@E[xqjc\VSMGFHHLXgsoLGI>G8>EDG?HWay8EIXVQVckb]lmqtqmhd`][ZYXXYZWZ]\VE?LWYUQPQPRRUY[_chltx|}zxwvz{} ~zwssssonlgd\TMNS[cmx ~wme`bhovuq keebbhv***)))))**))******)***********++))***)))**))))((((((((((((((''''&&''&&&&&&&%%%%%%%$$$$%%%%%%%%%%%$$$&&&&''''''(()))))+,,,,,,-...////0010122233555554444433333310////..----,,++++))****))))))))))*))&'''''''()+<sz~z~nv}qqqqocYQOMHF@AJEC>0'()*)('+**+,*)*)++-.17:@Kkurtm[ICCCJWeok`UPPIAA@:5/2C[qyNczzeG=E>NVP]y¥±³µ³°«¤ª¦¡¢tnjda\bpg\QHEDDDDDP|{z~wui_\Y]\_P48;>ABWo ywupcca]\[^afmpfBA=@BC;LAIG?RgvpePPWKJVOKWqrrtqniea^\[[YY[\\\\\XVZ[XUUSQPPRRUX\_cinsw||yvwx{|} zwssqqonkfbZSNPU]eq| ~wmdcgirwr| rhifbbjx ))****(****())++**))******++******))))((((((((((((((((((((''((''&&&&&&%%&&'%%%%%$$$$$$$$$$$$$%%%&%%&&&&&&&''''''''))(*,,++,,-...//00111012222244555544332233220./..-----,+++**))))))))('(((())))))''''''''&(),2kz}}sgd~q^_^`gdQQSUMLNNJFE?1'')))))(+(),*+*+,-,,047Uzz|{vqssu}jY[ZUNGFGA72?Njxl\tL@>JULRm¨±³°¬¨££¤¢£smkhmmjsjcZSKHHICBN}}wriiotzzve=68:>BNazzx{{zsfdjg`\_fgjnZ[[X96:>MKCFDO`y|[rTGRK6GMelprtsqoke_][[ZZZ\\[\][Z\ZWWUSQPPNRRUY]_cjnrv|~zxxy{|~ ~yvrqqqonlgc\RPTY_hr| ~wmfegmuv{oiiidbeoz))))))(****+++++**))********))**)))))))(((((((((((((((((((''''''&&&&&&%%&&%%%%%%$$$$$$$$$$$$$%%%%%%%%%%&&&''''''''))(*,,++,,-...//00//1012222244444444222233310//.------++++**))((((((''&&&&''((''''''''''&()/3N |winnftaOOPTV_]OMONKJKHGE?1''))*#*+**'&(+++*+,++.AnzuRPRXRMGEJC64Jmw{£ hiZO@Mf«°°²¥ ¡¬¤ £¤¤¢i[dovqnf_SJHKKGB@]xuxxqvsP46;??DSiy}qlqusqmgcgf_[_cbgb\`seB7;=>FJQG;Nx{d{\IO>+@T]dqqrssolfa]\\[[[[Z[[\\\\ZXUUQPPPOPRUY]_chlrvy~~|yy{{|~ ~zvsqqqonlgb[RPTY`is~ }wmfgjqu{tjnliecgq~**))))*++***++++**))******))))))))))))((''((((((((((((((((''&&&&&&&&&&&%%%%%%%%%$$$$$$$$#$$$$%%%%%%%%%&&%%''''''''(())**)+,-,--.000011101222223333333322112322///.-----,++****))(('''&''''''''&&''&&''''''&(,1;Ek qv[NLTXTGWb[ONNKJKFC=/%'(*,Im8#(,,))+,,././2T ~{z `VXXZ\UJFGB:>a¤¥¤ _@O¦®°¯¢ ¢¤¥¦¥ [<J`z~sleZJFHLKGADouz }~wa55:<=@HUflkcgif```[Y\][Z[_`_JQ\g`I:C?7ACFRGHex }RJT=-GA05eussqnjfa^][ZZZ[[Z[\[[\[ZVSRMMNNORUZ]`cejquz~~|zz|{}~ }yussqppnlg`XTPW\clu~|wmggntzvllmliffju********+*****++**))++****)))))))(((((((''((((((((((((((((''&&&&&&&&&&&%%%%%%%%%$$$$$$$$#$$$$%%%$$%%%%&%%%&&&&&&''((())))+,-,--.////1110122222332233221111110////------,++**))))''&&&&''&&&&&&&&''%%''&&''')18EYru} ~{z|¡pWOQ]cZXUii_LFLKJGC:-$&'))9=(&'(()(*/0-0/-=a~|}rW[Z]_`YMDDA@n¤ª¦s ¥`lª¬¬©Y<AJr}pibTJHFDEHAP} ~{tg>257<<AFR\][^`\VYYTOSTWXZ]WEBLU_la9;631=Y[eWVp cIJE8=@4=ftttrnifa^][ZZ[[Y[[\][[ZYUSPOOLOQRUZ^adgjptx}|{|||} |xtssqppnif`XRQW\dnv|wmiimu |lmonmkhhnw**********++++******))***)**))))))(('''''''(((((((''(('''&&&&&%%$$%%%%%%%&%%%%%%%%$$$$##$$$$$$$$%%%%&&&&$$%%&&&'''''(()))+++,,-....011111113221123212233000000..----,,,+*+**)))(''&%&&&&&&%%&&%%%%&&''(&')*+7F[u{tdmqtxullkls¢¤¡z^WY]aba^fglp_[SMHHB7*'''((&'''''&'((*+-/,5Ldx~|} |rdac\[_]VJB@?^h[q¡¦¨}¥©¤ WACJa{qjbQGBAFHD@a xtohH//28;>@CJRTWXWUTSPNQSTWYP:+<HNOSM@G?94@WJQON[p{eKCE@38E^pttsqnie`]\[[\\[ZZ[ZZ[ZZWSPNMNNOPSVZ^`chjorw|~|{|~ |xusrqpomje_XSRV^enx |vnkko nmnomonilqy**********++++******))**))))))))))(('''''''((((((('''(''&&&&&&%%%%%%%%%%%&%%%%%%%%$$$$##$$$$$$$$%%%%%%%%$$$%&&&'''''(()))+++,,-....011111113223321110000//////----,,,,+*)**)))((&&%%&&&%%%$$%%$$&&%&&%&(**-5EYnqa`[X^cd\SX[\]s¡`UWeiidfdghrzqmhe]KA0&&''''&'''''&'((*++,/:Vht~yyy ujgffd`^\ZQF=;[pfa\e£¢{gMNf¦£zoeUHDUthjyyrkifQ/,./58;=BIMPQQPPQONPRSQC00>?8;>:?CC=>=AJGIX[J^kuXFIRD<NYlttsqnid_]\[[[[[ZZ[\\ZXYURONMNNNOSVY\`cfhmrv{}}~~ }yrrqqpomid^XUUX^gpx zsnml qnqpopomjlt{++++++****++++******))))))((()((((('&&'''''(((((((''''((&&%%%%%%%%%%%%%%%%%%%%$$$$$$$$##$$$$$$$$$$$$$$$$$$$%&&&'''''(((()+++,,-...-/11110002112210/10/0...//.---,,,++++)))))(('&%%%%$$$$$$$$$$$$%%&&%'&&*.4BZrzjb^VWY\YOCKQTZmgUR[ibdjhcdjtpumplwxgD%'&*'('&''('&)()(*+.7EVft|~xxvpb]ZQH??}|m\_`_cx¢¦zs|`Tyª£vk]LDK]x~|xtnfa^V4-.)*.38<BFJMMMNMNOKH?3,/7BF7?FF@KOLI>BMI@LRT_bt xKAJZaPZopqssqnje_][ZZZZZZZZZZZXWTPNMLLMMNRUX\acdgjquz~}~ ~{vsqqppnlhd]XSV\`hry yrnkwulnmnpppmot|++++++****++++******)))))(((((((((('&&'''''(((((((''''''&&%%%%%%%%%%%%%%%%%%%%$$$$$$$$##$$$$$$$$$$$$$$$$$$$%&&&'%%'''((()+++,,-.../01111000211111021//.---..-,,,,++++**)(()(((&&%%%%%%##########%%%%%&'',1@Vu|uf^WYfdWQLEIVU\dx{|lZ]`kgiknlbhopkopshmr|d<),,)())(''''((**-;NZbx| |¡~h]\YPH=unZ`ppknp¦¡~s~~ph¥yocRDCHnyyskf^[ZV@,+('))+/479<????<81,*+.35EU>6;?EIK?BQNJ<LLNUV_hyr<=ERb[`opqssqnje_\[ZZZZZZZZ[\YWURONJIIKLNRUY]`bdgipty} ~zvrqqppnlhd\XUWZajry~wqkq{mqqrrtvvwx}++++++++++++**********))(((((())(())''('''''((((((''''''&&%%$$$$&&%%%%%%$$$$$$$$$$$$$$##$$$$##$$$$$$%%$$$$$%%&&&&&'''())*+++++-...00001101110011110.-----,-.-,++++**)++)((''&&'&%$##$$$$######$$%%%%&&(+1=UrzvsgUQ]qtc]]QIJ[bcspqSPWlumpmpnmmsmojrtmfsr~`7%(+*++*(19-)(().;MZc }}}lHXbfw¥¦¬ª¦wa^ZSLIj}r[s|yz¡£yop|~vuzqj`PCT}s unhaZWQNF0)(('('&')*))(('&')++.168=EJND>:=<HXUHI:0NMECRdj}{dNBBCMV`nrrsrqmid^\[ZZZZZZZZYYWUTQOMJJIKLOSVY[`ceeknry~ ~yurppppokhd]WUZ]bkt| }wqo qrvz||~,,,,,,,,++++**********))(((((((((((('''(''''((((((''''''&&%%$$$$%%%%%%%%$$$$$$$$$$$$$$##$$$$%%$$%%$$$$$$$$$$%%&&&&'''())*+++++-...//./00/011110000/.-----,,,,,++*****)()(('&&&&&$$###"####""""##%$$&''(-6Nlzuqxsnvwwuib\G/@jos}n}XGNYuqsumks vggmv~vflyv a7$(+++*0>0')))-;NZi{zvz`5OYfqx¥°°±©¡nc^\PIcv]u zx~}xtqmt¢ª¦¡~|vqldXEg | wngb]UOIC:.(&'&&&&&'((((')****,.16:=CDLHG<59NG<;@G:3AIHTdwve\VkhQV]fqrsrqmid^[ZZZZZZZZZYYWURONKJJJKLOSVY^_bbdhlqx} }yurppoomjfb\WUY_fmu| {vp {y,,++++++++++**++****))))''''''''''''''''''''(((((())((''&&&&%$%&%%%%%%%%%%%%$$$$$$##$$$$$$$$$$$$%%##$$$$$$$$$$&&&&&&&()))*,,,,--......//./0000////..-,++,,,,,,,+*))))(((''&&%%%$##""""""""""""##$$%%%(+5Fdyxsw||zz{vtj_UE<JdryuqlMO]ipszxwrmqtqjgnyz}jozuzxZ.+'*(()))+++.@PYp}~zwusy\GJRZk±®¯©td`ZTNat^v phtxkt¤ª¨ZPYkztqpneXEg ~wh[SPOJB9.'$&$%%$%&%%''(')('')-./792367?FRD564JOJY>3+1?P[cb^jjVWird\[`lqsqpmid^[ZZZYYZZYYYYVTQMKJHHJKMPSVZ\^`bdgkqv{ ~ysrqponliea\XV\`fmt{ {p~ ,,++++++++++********))))''''''''''''''''''''(((((())((''&&&&&&&&%%%%%%%%%%%%$$$$$$##$$$$$$%%##$$%%$#$$$$$$$$$$&&&&&&&()))*,,,,--......//./00//////..-,+++++++++*)))(((('&&&%%%$$##""##!!!!!!!!""$$%%%*0Dbxwqv|~|zxtph\PHQaj}~jwzWQWqymr{qqquytwuop~yto_urbmrN3((()**+++4GT^x {ptx|}vpmqzW:O]s°¬®§]IMNQNN_jP\ {jkox§«§£{SJJGY}sonnofPA] { tbG:4342.(%%#%%%$$$%$%%%&%'(*,/015?53/*+.2>^R?@QJ<QG-+4AZ_RTIOtrvnSQ]hprqpmid^[ZXXWWXXYYXXUSPLJHHHIKMPSVZ\^`acfjpuz |wsrqponliea\XX\`hov} ~z|,,++++++++********))))((((''''''''''''''(('''((())((((''&&&&&&''&&&&&&%%%%%%%%%%#####$$$$$%%###$%%%%%%$$$$$$$$&&&%&&(())(*,,,,------..////..---------,++**++***))))(((&&&&&$$$####!!!!!!!!""""##$%%$(/>Wsxqtz||z{zzrjf[NIWiv}piKNPcnnhikptuqfuxyposvkeX`plWmdg?2')))(*5ALYc}}|vnpstvrv}{urppz WQ`r®²°©C=DEDKPP[T:=ds{ {xy|h_aix¢~UFHOktrskhkoaHFSlx{rnyy~}cB40.,*(('&$#$$$%%$%&&%$%'',/1022473-(&&)*.E_PXYZYVI,+-9QPTL\y}}zSQOXcppqpmhc]ZXWWWWXVXXWWUQOKJGFFHJLORUY[^_`aeinty |vsrqonmkhe_[XX^cjry~ || ,,++++++++********))))((((''''''''''''''((''(((())))((''&&''&&''&&&&&&%%%%%%%%%%$$$$$%%%$$%%##$%%%%%%%$$$$$$$$&&&%&&(())*+,,,,------..////..---------,++****)))((((''''&&&%$$$#"##!!!!!!!!!"""###%'',8Lktrrw{|zz|yxqlbVNJ[vxu}}yYGGSinokekfjsmhl~rosohigGjnTnqhft?MD)(,*)2<LYf zyy{uljnnjkknr zqtrnve`vª¯¨wiQB==@PYZ[ V8ASgw{vrpopspjls~}T=>N{~cS\`befUHBFWswqi`jpuuS401/,*(('%$#$$$%%$%%$&(+-.12463220.(&%%')+<SXNVT\WH?/2@JORXR\lsyvmYMWnrqqmhc]ZXWWWWWWXWWWTPNIGFEEGILORUY[]]_aeikqw} {vsrqonmkhc^[XZ`eltz~ }++++++++********)))))(''))((('''(('''''''''((())))((((((''((''&&''&&&&&%&&%%%%%%$$$$$%%%&&&&%%%%%%&&$$%%%%%%%%&&%&&'())**+++----....--//--------.,,,,+*)))(())(((('&&&%%$$%$##""##!!!!!!!!!""#""$%(+5Gasqsv{|yyy{xusj`UJJcu}z{|nMEL]hiijebdgjmluwrcxoiGPw_Ekdf[hhnvZ:+++4?KW`~vywvsljkhcdifinquwx|uqnnrxlz¢wawQ=7@Xnph``9@aormjikpnjkw¤}{wdHA=MryM/3AQa_PJKUozrlb[dppkJ,/0/+*(('&&%$%%%&%('()+02121574.+*)%&')+)+7AFQQUMVgCA@JMRSMXV_]fk{toaKVirrpmgc]YWWWVVWWWWWVSOMHFDBCEHKNRUXZ]^^`dhkpv| zurqqommjhd_XZ_agou| ++++++++********))))((''))((('''((''''''''(((()*))(((((((((('''''''&&&&&&&%%%%%%%%$$#%&&&&&&%%%%%%&&%%&&%%%%%%&&%&&'())***++----....--..--------,++++*))))(())(((('&&&%%$$##"""!""!!!!!!!!!!"#$$%'*3C[noov{{wruwvwutg]UJIis|z|}bIGP`acfjffhcUNVYXcgQ]YWQVD>co]dWg]hfYVI9/3>JVZ~}zvuvrpoliecba^_agecby}qlnmlu}sRS[Q>:Jk rc{pLQkvynifbejioy¦zvqqkM=:<Kcu pD/,/?cg^Y[hsf]fmj_F*-0-+*('&&&%$%%"#%))**/4568>FCD<+'(&&&()))07?IY]U]cP@ELFMWk\SWS^kyK@]Lfkfqomgc]YWVVVVWWWWVURPKFEBABDGKNRUXZ\]^^afjou{ }ytprqommjhc^WY_diqw} ++++++******)))))))(''''(('((((((((((((('((((())))((((((((('&&((''''&&&&''%%%%&&&&&&&&&&&&&&%%%%%%%%&&%%%%&&&&&&&'((()()**++----..----.---------,+*****)))((((((((&&&&%$###"""!! !"!!""#$$'0B\nlmq{{tqrvxxywof\RDBn xkxxyqVFHQbhjmfbUTNDKHHEFJE>887;BAF^ZJpa~tpnaTKHA;>JPVy}ytqqqssohe_[ZWYZaaWHIqupoifkwu|[OVN?<Lvo_sNceezzwmgb]bfej£ xleh^HB=;GZgv {`<,,3GivnYXf qlmeU>)*,,**('&%%$#$$#"$&%&,:BEEDGGB=5)&&&&&()'*/3:DDJWd`ZVF@;@LWXZ\>B[T28fQN]irpmhb\XVTTUWWWWWUTQNJEA@@ACFJMQTXYZ[\]`chmrz|~ }xsqpqpmljga]Z[`djqx} ++++++******)))))))(''''(('(((((((((((((((((())))))((((((((())((((''&&&&'''%&&''''''''''&&&&&&%%%%%%&&%%%%&&&&&&&'((()()**++,,,,..----.---------,+**)))))(''''''''&&%%$###""""! !"!!!"#$%,<VqphntytmmprswxvnbVM=:hrrwrwykNCHUgilvdWGDGAJPKKJJDGA>;;@F?O]:k{ ~tcTMIEA@FJQrzvsronprnie_[YONT`^RF2Gjmnjhhnv|c@KNNEHo¢j\o y51Lwwkd^[^c\f|o\al\FB@DMWakqwvunU8+-;Rht~wd^dx{i_S4&)-,*)''&%%$$$$#""#$(/:BDC?:8760'''&'((''*16:=8GQX[YVOIKCLYZRPF6Q\MVjYJYrspnhb\XVTTTSVVUUTSPLJEA@?@BEJMQTXYXZZ\^aejqw}~ |vsqqqpmljga]Z]afks{ |y{~,,+++++*******)))))('()(''((''))))(())(((((())))))**)(((()*((()))('''&''''''''((''''''''''''&&''&&&&&&&&%%%%%%''''(((())*+++++,,------------,,+++***)))))(''''&&&&&%##$#"""!""!!!! !!!!!!!!###&)7OmrikqvslijkoqvwtkcVK?;jtyrpxwbKHPag_`hWJFDICHNKLKMECB@>?>E@AJD:<w}rbUMFDCCBFLgwusqnlnongb[X[SNNCLOI9,FjjfjhjowzG<BIIGm¢££¡sa]C+={wh_\YXZSu¡p[PY\M;:BIRW[adfjkaM7,7I\inv~vjy z}k\J,%(***('&%$%%$###"""!&/7:<?>==;81('''(('(&*1;=79BGI[^[\GFADKKIV`R@YRcn^J\stpmgb\WUTSTTTTTTSRNKGC@??@AEILPTWXYZZZ\^bgntx}~ |vrqpppoljga]Z\`hnu{ ~|yyxy| ,,,,,,,+******)))))('()(''(((())))(())((((()))))))****))()*((()))''''&''''''''((''((((((''''&&''&&&&&&&&%%%%%%''''(((())*+++++,,------------,,+++***)))))(''''&&&&&%####""!!!!! !!!!!!!!!!!!#"#)0FftmkptsmkhijmpsurkcVJ>6f~quxz\CHQek_]_PDFILGMQOMJJDBA@A?@BDCCF@nzkYNKECBBADG_ xvtqnjikjga^ZVPG>63;=867VmggffgowqF:7;_£¦¤¦u`eyK1@zsbZXVVNTyjONYP;19BKQWY\_cfe\J60=Qcikku|~z|wdT>*&'))*('&%$%%$###"""#*3:=?A>>;630+&''((((&)-2319<:DU[RW\D?DQTU^i]Lbwx~gDXktomfb\WUSRSSTTTTSRNKEC@??@AEILPTVWXYYYY\`dkqx}~ {urqpqonkhe`\[^cimv} |yxxwwx{}**************)())))(((())))))))))))))))))******++**,,,+****))))))((((((''''))))))))((''((''''((&&''''&&&&&&&&'''')))***)*,,,,,,,,,,..--,,,,,+**))))))((('&&&&&&%%%$##"""!!! !! !!! "$#$(/B^qmjottlhfdadintsskbUH81Uvnszm[HKSda\]ZLDJONNORSRKGGEFILA<>@?CEDWrgUJGCBAB@>@\ysopokhhhea]YTPH91155556<iligcehm}X02As¢¥§¤ m\vq`7>w}shVRUTTMq yn^OZaT54;GNTX\^abc`YF9?KZdgfglphysi^T?,())())'%%$#####""!#%09<=>?@<831/*''((((&&(-.1347:<JZZJSOC=@IHLPLZmwnidEWmuunic[XSRQPPSTSSRQOJEB>=>@BFIMNRUVWXXWWX]bhnuz| {urqppoljfc_\]_ciqx} }yvvuvvwy{{**************)((())(((())))))))))))))))))***+**++--,,,+**++*)))))(((((((((())))))))((((((((''''''''''&&&&&&&&''''((*****+,,,,,,,,,,--,,,+,,,+**))))))))('&&&&&&%%%$$$"""!!! !! !! "(5%&-?Wrtlntunieb_\_inswqjaTG6-JtrrvaOJLO]YSROCFMONKLPY[PHHIFHLPNBA@@CDA }n_QICA>;=<9;T }qopnhfgec_\VUPD533113356Mplhfecltt>,O~ £¢}cZw`v|r:6oskXNORRQWwmcXTVZUB?DKRW[]_`caZRHELR\bdgkpy}sng_ZU@,*'(())'%%$####!""!#,7<=>?@?<8530+('((((''&+.135557;CMOBFUZ>AO\TVY\bMS\JRivwple_WRPPQPQSSSRQNHDA>=>@AEIMOSUVWWWVVW[_fmrw} ytrqppoljfc_\]`fkry~ }xwtuvwwwxz{{ ****++******))(''()))())))))))))(())**++*+++++,,++,,,,,,,,,,*)))))(()))))))))))))))))))))))))(''''''''&&&&&&&&''''((()++**,,,,,,,,,,,,,,,+--++*)))))(((('&%%%%%%$$$#%%"""! !! !! "%(,>Vounqsvojea]WV]iqvvrk`SF58[|prsl\QFGPTLDACDGMKKJLN]\OFDBA@@RVD=A>D>>ivmd[TD;888779B~~rlljhdc_XVWVRL<..1/./0275drja`cgis_9^|nNR^CavsG7rxlaGGGMNNdwh^ORTPLSUPSVY\^__bdb`RJNW]`dlolp|oieb[YS=+(&''(&&%%$###$"!!"&1;==?@A@>;864-&$&&''%$&(,/0256777<IFFKPGCFNMUdbZTVVOEIezqlf^XRPPQOQQQQRQLHC@>=>@DGJMOSUVVVUTUVY]cjpv| ~xrpqqpnljeb^]]ahntz }zwwvuwwwxyyz{|****++******))(''())))))))))))))(())**++++++++,,++,,,,,,,,,,*)))))))))))))))))))))))))))))))))((''''''&&&&&&&&''''((()**)+,,,,,,,,,,,,,,,+++++*)))))((((&&%%%%%%$$$#$$"""! !! ""'-<Vpunnswpifb\TPP^krxvog]PD56Mwtmps^LTEDLHEA?DEGFIIIIPVVOEEF?>=;9D??>=A@JtlgdTA;6764468b~smjgec_\QLNMIE6**/.,./018Oojedcabfp[c{zmRFFCBW~upU:vtk\BEGLKRkvui^TSTVXC<PZY[Z]`bcdfebVP^giglxl]w|nga_]VUR?+(('('&&%%$##$#"!!!)5<>>?@A@?=<85+$"##$$##%&*.10379<;:9889CMLLQ[UX_aWEI_dKOqtmd]XSQPOQQQQQPOLHC@>=?ACFINOSTUVVUTTUW\bkpw| }wrpqqpnljda^]^biqu{ |wwvvuvvwwwxxz{}********))*))))'()))))))))))****))**))**+,*,++,,,,----..,,,-+*++**++****))**++****))))))))))))))''''''''''''''(())(())***+++++--,,,,,,,,++*)***)))((('((&%%%%%%%$$$#"""""!! !! #')9TovpnswskeaZTMJO]iotsme[NC75I]kfhqSEGC=BEGCBDEHFGIIIKQQNF@BBCED>;B==>?@:m{rkfcM@D=411235;rrieeeb`ZQKNOKE6-**--.//.7@inifb`\`jiiwxlT>FBAKX}xjbBztfW@BHJLVdge\TSTVVE4.8Vg``_ceddggg\_imlr{r^`|le_]\WQPN>+%&''''&%%%$$$!"!"&1:=?@@@CB@?>;4,$"""$$""$%'*./15:===71.+.6HVXPRYYiiLIbbZLmukc\XXZROPPPQQQNJFA>=>@CCFJMPRSRUWTSSUW[bjpv| {trppppnljc`]]_eipu| zyywwwwvwwwwvxyz{))******)))))))())))))))))))******++**+++,++++,---------,,-,,+++++++++****++++++++**))**))))))))((((''''''''''(())(())))*+++,,----,,,,,,++*)))))**((('&&&%%%%%&$$$##"""""! !!#%+7Vpyolqvvoga\UMDEO]gnqskbWJ>424`~hgmoJFC>:@CGEDDEDGHHIIFKOQTXdnnqokcURU_P79Wxqjg_OB@93212312Hrjcba_^YQLIMNMG;.*.1/./237Joni_\[]bmsw{|uk\A?BDMS`rn__MuseSAGIJMZ]ZXVTSUR@0.4C_jmnkggfgiieckortzg[W`z{lc]YXYRPMJ;*&%&(''&%%%$#"""!#-9<>AA@@B@@?>;5("####$##$%%%(*.1379862,++.<R`Z[_alfaPIFSJfrne\W_^^YOPPPPNKHDA>=>@CFILNQQQQSTSQQTX\ckqx~ ztqppppnkic`]]`flsv| }yxyxxwwvwwwxxxzz{(())))))(((())))))))))))))))****+++++++++++,,,,--...------..,,,,++++++**++++**,,,,+***++*)**))))((((((''''''''(())**))****++,,,,--,,,,,,**+***)))*)))&%%&%$$##%%$"!"##""!!! !! !#'5Qnxrnpuuslc]VLA=DP]forpi^WJ<411f|khokEE?99=BEFFEEDFIECFCCLixvwiU_qyY7?snke[M?621110./13Xkc`^]\XRMJIJMNNG9+*-/-/343RrmaWXY\amzvpjbM<@?B]`gbYZXRmm`NHJMLORRTTUSQM=303@Sgqtxzvokilljmry}rZIZPWffc^[XYTMJHD5'$&&')('%%%$#"# #)7=>=@@@@AA@@?<3& ##"#%$$#$$%'(),-.*+*+*)+-7P\RZcdba[\i[K[soi_c[@V_SOOOMLHGC@==>ABDGLOQQQRTRQPQSX\cksy} ~ysppppomjhca]^bgnsy} {{xxxxyyxvwwwxxwwz|(())))))(((())))))))))))))))****++++++++++,,,,,.......------,,,,++++++++,,,,++,,++*+++++*)**))))((((((''''''''(())**))****++,,,,,,,,,,,,**))*)))(((((&&&&%$$##%%$$""##""!!! !!#$(7LkwmmsuvuqiaZOA:=DO^hmnlf^UH<43>tvhgncAC@:9?ECDEDCAEHKFBBHh£ªª¡|wx{hWg{T/`ole[K=62231/./12<dc_[YXVSPLKKKKOMJB61-+-31+/Zf^XU]]_fowjaYC=<Facb`]ZPTdd{dUGHMPQRTUUUQOH?411;N^mwtrwxurqqqprstu]GVODQZ^_[XUURLHE?2&$'''''&%%%$##"#(3<=>?@@@@AA@@?<2% ##"#&&##$$&.))&%&***+*++,.3FJ[__X`dZ_YLVqrbZE)4NNLOOOMLHEB?==>ABFILOQQRSRQPPQSX_dlsy} ~ztppppomigb`]^bhnty~ ywyzyxxyxwwwwvwwwxz|))**))))(())))))))))*)))))******++++,,+++,---.-.//......-.-,-,--,,,,,,,,--,,++,,,,+++++++*++****))((((''(((())*******(******,,,,+,,,,,,,*)()))**('&&&%%%%%%%%%&'**%$#""!""! "# !!%(0Ijwmhntxtsnf\SC76?HQ^hoplg\QF:46P~rabl_ABA:6?EBBBCC@CHQJ?H^¥¥qZb}w;FrkeZO@622233/.155>^`WVUTSRNLMMKLLKKH</),/-&'4_b_[X[^deqn^R=;@^_UZZ\WPGPQk`PCJJMNRX\`]ZNE;313EYfpvuqprqmjloonnlpKOUGFQ[]\ZVUTNJFB:-##$%&''%%%%$##$'29;<>??@AAAA@@?:/#! !"""#&#!"%)+))++**++++,*,//1DVmigf_fjXRVngh`N+1FOPNNNLKGDA>==>ABDGMOPPRRQPPPORY`gou{~ ~ysoooonlhda`_adjpv| |{yzxyxxwwxxxxxxxwx{~(())(((((())))))))))*)))))**++**++++,,,,+,---..///......-.,,-,--,,----,,++,,+,,,,,,,,,,++*++****))((((''(((())*******(******,,,,+,,,,,,,*()+))))(&&&&%%%%%%%%%&*00&#"!"!""! !!!!#%&1FdyogkrxvvrjaWJ807AJT`iprme[OD81<apefnZ@D@96<CDAACCBDEHEKd ¤¬§}vtj e3pje\RC2.,,***+,/0/;V\WSPPONMLMKMKJIHE@5.1.)$):Z^abca]^hhaH:BUZW[`aYRLIDGNXMFLQOORU[a_VL@9307J_kotvsnmlgeijgg_\ZGZNIHR]^\ZVURJFC@5*$$$$''&'&&&%##&08;;<?@?@AAAA@@?:/##%!!""""##%'(***++**++++++./38@TbhmoideUMWije^\XUONMNNNLJFDA>==>ABGJMNPPRRPPOONSZ`hpu{~ }uqoooonlhda``bflrx~ ~y{yyxxxxwwwwwwwwwwy|(((())(()))))*****))+*++**++++,,,,++,,,,,,..--//..........--....,,..------,,,,,,,,,,,,,,,,++**++(((((((())))))))**++**+++++++++,,,,,,,,+**))))(('&%%%%%%$$$$$$&&)(%#""!!""!!!!!! !"$)-@auskkrvxxtoe\M>01:DPZbjqricXLC71EulgjqVAF>99?CDCBBCACEDIa£¦¨ª¬ª¡vyz|Edlf`WOGGFEEFEFJLWZTHLRSRONMLKKKKKHGDDD>73.*'#%>YYW[\]]^]bUIRYRTWVUSMJG@4;EADIMLLQTW\\VI<701=P`lptvtnkhgfiige_[NURGHMZaa^[USMGD@=1($##%%%&&&&&$""+59:<>>??@BBAA@@?:/##$#""!###""%((**++++***+,.148=BDGTfggmiNL^jjd`ZTLLNMNNNMKGC@>=>?ADFIMMPPPPPPNOOUZbiqx} ztpoooomkgecbbchnuz~yyxywxxxxwxxwwwwwwxz}(((())(())))*+******+*+++*++,,,,,,++,,----..-.//..........--....,-,,,,----,,,,--.-,,,,,,,,+++*++*((((((())))))****++**+++++++++,,,,,,,,+**))))((&&%%%%%%%#$$$$##%$"#""!!""!!!!""!!#%(1?[vskkqvvvvof\QB4/5>HU^fnqng_UI=67V{lfiqT?E>99BEEDCCCBCEHYp¤¬«ª®£¡riQd_]]\YYVTUTSTTRWWY[SNMNKLKLKJIIJHEEBA>:73-(&'&CWPPW\YWZ`bUUWYUTRPKGBB?43>HCEIJLOQUXWQF=722>N]hpxxqkiifffghgXD8FMJHR^`a\XUPHDA=9,%#"#$$%&&&&%##&08;<>@???@BBAA@@=;/# ""!!###""%'**********+/069?<=<?DKQ_qmIGXY_de[SVQNNNNNMLHE?==>?BFHKMMPPPPOONOQV\biqz~ |uqnooooljgebcefjqw{ wy|yyzxxwwxxxwwvvwwxz~ ''(((((())))))++++,,+++++*++,,,,,,,,----..--//////..................----..----,,------++,,++**,,+***))))**))))**********++++++++,,,,+***))((''''&%%%%%$$$$$$$$$$&$####"!""!!!!""#"%)1=XsqjlprrsrmdYNC5/07BMVbiopke[OC75Be~lhflO@C@87BGECCEFC@@Nk§ªª¬¢££¤¢}zRYXWUTOOPPOOOOPPRV[[UPIIFEEFEEFHHHHEA?>:853,'&(.PZUSUTSUY]bWRRNPNKFA=?A7.3=AADFKMNRWYQF:834>KWelrslhhgfffghicN6@FEIX^^\XUSKEB?;5)######$%%$$###)29=>?@@??@A@@@??>:.#!!""!!"""""$&+++)******,04666689<CFEHUTFG\R]ef[W\SMNMNNMKGD?==?@DGHKMMNNNOPOOPQV_ckrw} ~ytpoppnnkigeecghlsx} zszzxyyyyxxwwwwwxzyww{''(((((())))))++++,,,,,,,,------,,------....////....................----..----..------,,,,,,++,,+***))))**))))**********++++++++,,++****))((''''%%%%%%$$$$$$$$$$%$####"!""!!!!!!"#'-7Povjknprrqg`VL?2,.2<GR[fmooi`XK?21Iq}lgjlL@CC:8@DEDEGGC?C^}£®°®°®©«tv£¡£`QUSRQQQQQRRQQPPQSTSVQMKGECB@A@?CCBB@>>:9630*%'*2R_XPRURPTa[DICEFEB><>>5/,-:@AEJLMNNPOC9743:HU^glmlihfffe_XVLOULCFOZ^\ZVSOHC><93'$#####$%%$$###)3:=>?@@@?@@@@@??>:.#!!""!!""""!"$(**)******,/3444569@GIIIMC@AUhonfbcgSLMMNMLJFC@??@ADGHKMMNNOPRTTTWY_fktz} ~ ytpooonnljgedehiov{sx{{yyyxxxxwwwwwwwxxy|''((((()))**++++++,,,,,,+-----------......////00//..............................------,,,,,,,,,,+***))********++****++**++++++++,+******))''&&&&&&$$$$$$$%$$$#$$$%#"""#""""!!!##%(*7Kjypilmppme\QD91++.8@KVajpple\QG;35Y{ylgknQ@EC<7?DEEFHGB?No ¡¯¹º»¸ª§|¢sMUXXVWVSRSSRPRRUUUUWPNJHFFEECBBAAAA@>><;:63.)(+*5T][XSQOR[aJ;??CB?;8873/--9DIKLNPOOQO?652.5DQWY]dihkkfgi\OW8LYKGNY]\ZVSQKD@<:91&####$"#$$####")4=????@A@AA@@@@@?<-#! """"""""!"#'')*+*+))++-102479669=@ED?CFAKfg`_\ZUNMNNMLJFCA??ABEHIJLLNNPRVWZZ]aaemt{~~|wronmmnmjhgdefjlpw}zux|}zyxxxxwwwwwvuuxxy~''((((()))**++++++,,,,,,,...--------.....///////................................--...-,,,,,,,,,,+*************++********++++++++,+******)(''&&&&&&%%$$$$$%$$$#$$$$#"""#"!!"!""##'.7EfyrkknmlkcXK?4,))-3:GP[fmqpkbXMA516^{y~mein`?CC>8>EEEFIHDHaw¤±¹»¼º²¯©ª¬® ¥\XXXVTTTPQQQPPQSUXYYTPMFFEEDDEDCC@@A@>>=;;950-+,.7UUWWRQPTZUB;;>>=;8751//2>HLMMNPPOQUG(/0.2<EOSV^bbegihkid_QQTOOWZ[XWTOJD@?;95-%""$#"##$$####")6>A@AAAAABA@@@@@@>/#! """"""""!"##$$()()**++,/135653668=?>CCC=I_ff\ZTUOLNNMLJFCA?@BCEHJLLLOORUYZ_aegglpw{~~ytpnnmmnmjhfefgjmty~utz|{{zxxxxwwwwwvvvxxy~ (())**)))******++++---------......--/......0////.../............///////.////////-./.....-,,,,,++,,,,++++++++*********+,,,,++***+++++**))('''&&%%$$%%%%%%%%$$$$$$$$########"""#$'+1=Zxrjjqpph]TH8-(%&)-5?IT^fnome]RF;20>gwvkbelvK?D@::EDDFGFL`t£±µ¶º»¶¹»º»º®¦¢jZVRRQSSTPPNMMRTRTUUSQNKHFEDCDCCCBBBA@@>===9841...AWVQPQPOMUV;5;<=:8432019GMNMNNNNNQSSG=)(/5;CJMV[_bdfgfcabUVTKPY[[YUQMEA<;:60'$$$$$$##"#""""#'2>ACCB@??@BAAAA@?=3%! "!!!!!!!!!!"""&))**++*+/1324468;;:9==BGMR[fba\VPTQNNMJFB@@BBCFIKMLMMQUZ\`ekoppptw}{wronmmmlkjgffgilqw{ ywx{}}zyxxwwwwwwwwwwwxz(())**))****++++,----.------......../...//./////../-............///////.........-./.....-,,,,,++,,,,++++++++*********+,,,,++**++++++**))('''&&%%$$%%%%%%%%$$$$$$$$########""""%(-8Mrxpmqtri_SF5)'%%&)09CNWbkpmh`YO@2/0Cmxxmccnzr=A?:8ACBEAG_v ¤¯®°¹¼¼¹³°µ´µ©©¡wUTYesyyyyum^NLLNNORRPNKHFEDECCCCBBA@@@?>==:9863..,@SPONNKKMQR:69:8652202=KPONNNNNNPUUTT929:;=AFRWZ[_b`a_\_YSNKOXZYXSPJ?<9750*%$$$$$$######""#&/;@ACB@?@AAABBA@?<5)!!!!!!!!!!!!!!"""$&(**(()*,/0124547755699;BJSblbZRPOSPMMJFCACCCDHJLMLNORU[afkrvwxxxy}{ysommmmmlkifgggimrx| yy{|||zyxxwwwwwwwwwwwx{))))******+++++,,----.-............././0//00//////..........................................-,,,,,,,+++++++*****++++,,,,,,++*++++++*****)'&&&&%%$$%%%%%%%%$$$$$$###########"#$'+4GivnjqvvncVF5'$$$%&)1:EPZdlpkf]RG:.-4Usxxlcahw~U:@76?ACDAQu¥¦¬¹¾¼³³²³±¬¤¨¦^bt~~}|{xxutk`YXWURROLJGDEFFDDDBCA@??@>=<;:98754.-/?OLMLJIGIPL66965310/5CMRRNOMMLLMPSW\VF;9<;=@IPVWZZZY[ZYUPLLSXZVRNLC9840,(%$$$$$$$$$$###""#$+8@ABCBAAA@@AA>??<7*!""!!!!!""""""##""$&+*)**)****/2244010/57<@EJ[fjaYTOPPNLKGCABAEFIKLMMMNQU]aipx|}}{zz~~ytommmlmmkjihghhjnty~yyy|}{{zyxxvvwwwwwwvvvy}))))****+++++,,,-..................../00//00//////......................//..................-,,,,,,,+++++++*****++++,,,,,,+++++++++*****)'&&&&%%$$%%%%%%%%$$$$$$#########$#"#%(0=^ysinvvqgZL6($#$$%&+3;FQ[ennjaZMA3--8\xws~pachtu@=:5<@ACLf ¤²¶·´±¯¬nt{zvwvwtssstqomjhdb]YTRNLEDDBABA@AA@?==<;:9875430--@MGGFFFFKQF686530//8IQSSQOMMKKLLPRYWXL;69<=CJMRTVTTTUVQMMSXYVSOLIB81/+%#$$$$$$%%$$%#"$""##&2=?@AACCA?=@BB@?>8,""""!!!!""""""##""$$+)*+,*(((+0//0-**1403879>OWaWQZYQPMKIFC@@CEEHLMKMMLOT[cjsz~~~ {wrnmmmlllkjhhghhjouz}zxz{}{zzxwwvvvvwwwwvvvy}))))***++++,,,,,-.......////....--//000000//////................//--00////////....///.......-,----,,++*,++++,,,,,,,,,,,,,,+,,,,,++++***)*)&&&&%%$$$$$$%%$$$$$$##########$$##%),6Nswljruqk_O:*$!""#$'+3=HR^dkje]RH=1,5Hk}}sbchte5968?CB[|¤£©«¯³´©£tgtuutrqroonnnllifb`__^\\\XUTQMJHEA==>===<:98765210..<KHEFCBBGRA6872111=LTWWSQPOKKKKMQRSNTSE<=?@CHORUTONNLPPPPRRPNKID>6/.+&%$$$$$$$$$$$$$$####$+8>@@@@BA>>?>?=<;5+"!!""!""""""!!####$$'((*))(+./,+)*))*-17<=CGNW[aotbUONMKGDB@AEHHIKMMMMLORZbkv|~| ysomkkklkkkigfghimqv{ |yy{|{{yxxwvvuuvvvvwwxz~))))**+*,,,,,,---...../0////////..//000000//////....////........//--//////////....///.......-,--,,,,,,,,++++,,,,,,,,,,,,,,,,,,,,++++***)('&&&&%%$$$$$$%%$$$$$$##$$$$$$$$$$#$(+2Ek|okpttncUB-#""""#$'-5=HR]fjjbXMB705Gi|~}v}wbdfp~H776=AHj£¤¢¦¬®±¦kooqqpmmkjkjiihdbe`__ZYXXXWTQSRSRRPG?==;::98776422...=DBACBBDIT>664024BOSVWSSPOLJJLLMQSMNTSLB?@BDIMNMMJDDEGHFDDCEFC@91,+)'&%%$$%%$$$$%%%%%%%%#%,6<???@???@><:83*#""!!"!""""""""####$$&'''&*,09<3(('&')1=CEMPQUVY_py^XNKLIA@BBCFGIKMNNNMNOU^gr{~ {wrnlkkklkkkigfghimqv{|zyz|||{yxwvvvuuvvvvwwxz~))))++,,,,,-----....../0////////0000000000//00//..////..........00////////....//......////...---,,,,,,-+++++,,,,,,,,--,,,-,,,,,,++++**))('''&&%%%%%%$$$$$$$$$$!#$$$$$$$$#$$$(.<`tlqvyqhZN5$!!##!#%'.5<GQ[dieaVJA96?]~zv{{hbbl~t195<@Ov¢¡¤ ¤¥¤¡ {txu{ikklmjkigghffda[V[\ZXZXTQPPOMMMMNQSRGA<;9:89886551/0/2FGEEBDEIRP633216GQTTUURROMKKKKKMMPONOLD62:?AEFGHFBCCCB?CFIJHFA<0+(')'''&&%%$%$%''&&&$$%%%&*07;>?=?@?<84-&#!"#!"" """""""""""#&&%&'''(')4:/(&%''+1;BEJNQSSWXYj~]^oPHGC@@@BEFIKLLNNMNMQWcmv}~}}~ }wtqlkkkkkkkjhfffgimqv{|yy||}{zxwwuuuuuuvuvuuxz )))))*,,,-----......../1////////00000000000/000/..////..........//////////..-///......////...---,,,,,,-+++++,,,,,,,,--,,--,,,,,,++++**))('''&&%%%%%%$$$$$$$$$$$$$$$$$$$$%%$&)3Oyqpw}zpdTC-#!""##$&(+3<GR\bfd`UKDCL`{zwvz|ia`j|Z+26>Uz¥¦¥§¢ ¢uiba\nv_gghhfecbdeb___]ZXZXVQOMIIIKKKLKKMNMGC?<:99875554110.9HEBEDFFJOF22437HRUTTSSSQOMKJKLKKMMKKJ?0%',29;CGFD@@A@BFJIHFC@6/*'''&''''&&&%$%'('''&$%%%%%&*-38:=>;61*$""%$"#$%!""""""""##"#$$%%&&'(&'(&'('&&%'*26>DGMNPSZcnaN^\ICA??@BFHIKLLNNMNJOS]iqy~}|} |wsnmlkkkkkkjhffffhlqv{{vxyzz{ywvwuuuuuuuuvuuy{****,-,,,-......./////00//11////0000000000000011//////.0////..//..//////........////////.....-......,,,,,,,,,,,,----,,-,--..--,,,,++**))((&&&&%%%%%%%%$$%$$$$$%$$$$$$$%%%%%'+;fwqr{yk^M:(#"!"#"#&(*1:EOZ`cc`VOQ\m}xxv|}k`^dzC+2<Tz§©ª©¥¡ ~wlghiaq}]bfhfdb_dbb`^][YVVVTQOKIGGEFHHIJJJKMOOHC<97765665333/0CKGFGGCFIM?112:GRTUSSRSQPNMLLLKKJIGFD:,#$$%)-6<=><>AABFIECA:4,(('*++*()(''&&&&&&'&''&$&%$###"%+.231-'"""#$%""###"""""#"$%%&&)+'&&$&&%%''&&''(()),.18=EJPW`jvu\Y\j^G@AA?AFHIKKKLNMKJLPWemv|~~~}z~ {wsnkkkkjjkjhggggghlqv| ~zyxy{{zxvvutusuuvvvuwvx} ****,-,,,,....////////00////////000000000001001100//////////..//..//////........////////.....-......,,,,,,,,,,,,----,,,-....--,,,,++**)(''&&&&%%%%%%%%$$$$$$$$!$$$$$$$%%%%&),?qtryufYF1$#"""#"#%'*19DOZ`cdaZ]gq~y~j`\^vz6/5Ow §©©¨§¢|yuvsfagkr}u~lV_cb`\\_]\[\XVSSSQPMKHFDAABAACDDDEGIIJGB<8665666533227KOGGGGJGHL=028FPTTUSRPOPNMLKKKKJIFEB8*###%&'&)+/:<<@DDEFB5-*+---*++*)*)((''&&&&&&&%%&&%$$$#"##"$$""!""!##"""###"""""#$$),15975.)'((%%''&&''(((*+*,/7DMTX[\[^hiWPJC?>?ACFHIKKKKLMKJJOVbitz~~~}z zvpliklljjjigfffgijnrw{ }yxxy{{{yvvutstuuvvvuuwy~**+++,--+,..//0000001111111100110011001111111111111100//////.../..////////....//..00////.....----..-,,,,,,,,------..,,,.....----,,+***)(((&&&&&&%%%%%%$$%$$$####$$$$$$$$%%%+.@outyp_Q=+$###"#"#%(,08CNX^ac__ku} | |m_[\t {nv~b21Dp ¥¨¨¨¨£wuugelqj`eZpp]qyUSWYYYZZVWVTSSNOPLLKIFDA?=<;<?@A@CEFFECCA<756446555662;KICDCHHHIN=16FPTUTTUSPONNMMLKKIIGE?5%##$$%&''(*-2;BFDA;2--,---,-,*+*((((((&&%'%%%%%%%$####""""""""##""#"#""###""####%*18AGIHD<3*$#%%&&&&'')*++)+,0;ELJOPV]c^UNJEB@?@ACFHHJKKMMKJIJOT]hry~} }wsnjijjjjjjhgffggilptx| {xwyz{zyxvvuttstuvvuuvwz~**+++,--./..//0000001111111100110011001111111100111100//////.../..////////....0000//..//.....----..-,,,,,,,,------..........----,,+***)((('''&&&%%%%%%$$%$$$####$$$$$$$$%%',/>juszzeWI3'$#####"#$'*-6ALV]`ba_n| xx{}}o]Z[gf_i{|H)>d}¤¦¨¦¢u|vprpoleahhdgpeN^Ic YMQSTUSUUSRRPNHIKJJGEB@=;98999;=<?BCCEEBB@<764456666643>IEDCDEDFHTC6BOTUTSQQOONNMLKKKIIGE?2$%#$$%&''(*)4@BB?7.+-./,..--,,++(''''''&&((('%%%%$####""""""""##"""#!#""##$$##"%*4?JSWWVRLA3'$#%&&&&)),/-,.36;<?HKP]llg_YQNGC??@ACFHHJKKKKJIHILR]gpx~ zvpkjjhjjjjjhgffghjlptx| }zwwyz{zyxvvuttstuvvtvwx} **+++,....-.//11000000001111001100111111111000//00000000////////....//////............//..--....,---,,,,,,,,------..--......----,,++**)(((((('&&&&&&%$%%$$%$######$$%%$$%%'-3>eusv|paP=+$#%$######&)-6@KV\adebn uwx{~o\YZfcgpzx|=-Vs ¥§¥£z{wqjihjegjhqw{rTHQW~bIOONKPMSRNLJHDEHEDDCA?<6544366779>BBBAAA@@<963465654468=FEDDEECELWG@MSUSSQPOOOMMMLKKJJHF>1$$#$%%((''(*+4960+-..--..-.-.-**($#&'*'&'())*'&&$$$##""!######"""##%$"!"!!""""#(2@LVaihgaZPB3*$$%%&&(*05:=A=8L\lvuvvsngaZQMHD@?ABDGIIJJJKKIIIIJP[grz ~xrnjjiijjjjihhgfhjjmrvz~ }yvvyz{zxwuutsrrtuvvvvvw~**+++,....-.00112222220011110011111111111110000000000000////////....//////............--..--..--,---,,,,,,,,------..--......----,,++**)(((('%%&&&&&&%$%%$$%$######$$%%$$%&)/6Aa~usuxkZJ5&"$%$$$####&)-5?IS\adgnz}kpwvxzr`]^dguz{y}x/>hz£¢¡ zvpggfhgegox|xwob\Wg]CKJFFIJTUMGFD@?BA?@?@>:63310034459>@@???>>>>843556665467@HFDDFEEHOXKHPUURQPOOOMMLLLLKKIG?3$$#$%%''&'(*('*'(,/.///.0.-/./-'%##&'*(''(((&''&%$%$#"""######""##)52%!!!!""""$,8DS_ktxtoh\N?0*%%%').0/4<HY[Puwsvvutqmf]TLFB?AACEGIIJJJKKIIHHJP[grz {vpkiiiiiijiihhggjkkosxz }yvvxzzyxwutssrrtuvvvvwz~,*+--,--..-.11002233222222221111001111112200122211110/00////////////////////.........---..-,--,,....,,,,--------------......------**)))(((''((''&&&&%%%%%%%$$$$$##$$$$$%&'+1:B\|uqqqlV?-&$#%$%$###$&).2;EQZbhmvz_Omjippsda`fs~xvy a3[rpgaaehgb^fowpovxxovo8DGBAA?AEEDAC>;=<;<=:853211/./11159<=>>>>===;8556776888:<DHFGEFFFHMTNHQUSQPOONMMMMLKKJKFA5&%$%%%&'&'))(&%$(+-//...00//01/'""#%',*(((''''''%%%%$$##"""""""###&-*$"!!!!!""%/=KZiv~zrg\M>1)'),04<JTMDOO_~|vvuutqmf^VMFB?DABDGIIIJKIIHHGGHOYgr| ~xtnjhhiihhjjihggfikmpty| ~{xwvxzyxwvtssssstuuuuvy| ,*+-..--../011112233222222221111001111112211333311110/00////////////////////.........---..-,--,,----,,,,--------------......----,,**)))(((''''''&&&&%%%%%%%$$$$$##$$$$$%%(.4;FWwuqqonS7'%%#%%%$###$&)-39CPZcmt{|ub<Gkppsqwhdcfs|wvPCetp\`aghc`]cly}ukl{||L9BC<;;<@><<=;998778641//./-,-///269:;>>>====;877777997:<=DGFDDFEEHLPNHNPPONMLMMMMLJKJIGC9&$$%%%&''())(&%$%+../..22/0355/' !#',*(((((''''&&&&%%$$""""""#$##!""#"#!!!!""&0?LYj{zqf\MA8436ASalk[]bgy}wttuusnibWNEBBABDFGIIIJKIIHHGGKR]is~ |vqljhhiihhjjihgggiknquz} }yxvwyzyxwvsssssstuuuvwy|---.....//01112222222221121122111211222233333333330000//////////................//--.-,,,,,,,,,,------------------....--./....--,,+))))))(''&&''''&&%%%%%%%%%%%%"$$$%%%%'+05<IXmxkhkmY2$%%$%%%%%%%&&(+28AM\hu|~|{wp\;(?fyrqtzjdcbmt{ }{z|uIRmym_\`a^adcgt}pktxtwj-9><867:::867553266510/-,,,(*-,,/1467:<<<;;<=977899<DKA>:?EGGEDEEFGIOEAKNKLMLKKKKKKJHGGE=+#%%&&'')*,)(&%$%,.///111688:80& "'-.,*&)()'''''&%%%&$$$$$############""!!""%.=L]m}umh^TPIFHTbfekeelsvyx|zvuwtrqkcYPHCAADEGHHIIJJJJGGFGLS^it ~xrnjihhhhhhhhhggghjkprv{~ }zwwyzyzvvutsttttttttsvx| ,-./..../001122222223321121122111211222233333322000000//////////................//,,,-,,,,,,,,,,------------------....--./....----+))))))('''&'''''&&%%%%%%%%%%%%$$$%&%%)-33=JYgrngfid@$%%$%%%%%&&%%(*.6ANat|yumaJ4.6Yxsry{laagirywwzzdK]pxyvlhddgaY\binv |rllnqsv>1475455564322210231/..,,,,('))+,/02389:<<<<<;:89<=?YbHB@>BIIFFGFFHGHJBCHJKLKJJJJKKJHGGD@5&%%&&(***+*'%%$%,.//.048=>;=;3' "*012/-,++)''))&%%%%&$$$$$$##########""!!""'.<K^n}yupkf`WWX]fg_[SO]dRJkqmrvuuqmd\RJCBCDEGIIIIHHHHGGFGLS]jv ztqmkihhhhjjjjhgggijmqty{~ |ywvxzywwvutsssssstttvvz ........000111222233332223333322333333333333333322110000/////////////..............-,,,,,,,,,,--,,----------------....--.///..--,,+)*****)(())('((('&%&&%%%%%%%%%$%%&%%%*/16=MZfonhdcfV+!%$$$$%%%&'(().6FXo}xofUE?69Tupl||kcbeiv usuvyzbL`pprv|{rjgihhmlkmryxz~ymaVKKGMK.03111112310..//-//---++,,'$))'(,,03588::9:;:::;:=??BCCBADFILIFHGDFIJH@>FHJLIFGIJKJHFEC@9)$$&$(++++*(%%%&+.00.1:=@B@=:4(%-24785/-.*&&%&''%%%%$$$#%#%$%%##""""##"""#%*4GYj}~{zwmlgY[W\f^F@F5<`v|wssntog\SJDCCBEGHIIIIIIIHHEHMT_mx}ytoljhhhiiiihhhgggiknrtx{~~zxvuwxxwvuutsssssssuuvy{........00011122223322222333332233333333333333334411000000///////////.............-,,,,,,,,,,,,,,,----------------....--/.////--,,+)*****)(())))((('&%&&%%%%%%%%$'+)'%&(+,/5>LZfoqkgcfcA"$#%$%%%%%&))+3DTl {tj_VPIDFRabf~zncahk~{ssuvw|yOQgljkoustkgjklot|}yq^MD;1224---.0011.///,*++++-,,,++++(%&&'(*+.135787567779::>BDFHJFFFFHJLMKJIHKKJG@@DHJIDDGIJIHGEB?9+$$%&(+,,,+)&%%%*.00/6>@DCA?>3( !"'07<<99852.(('%&&%%%%$$$#"#$%%%%#####$$$$$#$(2GYj| {un]\cieZPVO?Kj}~vthC`qhaUMFEEEGHHIJJIIGGFFEHMT`nx |wsmkihhhhhiihhhgggiknruy{ }zxvuwxxwvuutsssssssuuvy|--..///-/011112222222222233333223333334222333333332211111000/.//////..--..........-+,,,,,,,,,,,,,,------------------............,,,+++++****))))(()('&%%%%%%&&&'*8C7'&'&)-06?L[fotpigbeZ/$##$&$$&'(,3=Ocx wld`YWRNOXadn{wngfhpyknrprw{WAWbfacegwnalos{~kWD86444471*()/.----,,*)))*****(''''%#$&&''*,/1345533333788<BFIJIJJHILLPRQPOQNKHIHCABEFBBCGGHIHF@?9,%%%'(+-..-*(%##)-..1:@EIEBB=1$#%+2:=?=:8642+*)%$%$###%%#"$$%%%%$%&%&''())('),2EVgy |pdkoji[]h[5Adv|wrbcpjaXNFEEGIIIJJJIIGGDDFGKUamx |xrljhhhhggiihhhggijlosvz} ~{yxwvxxwvtttsrrrssssuuuy~ --..//0/111111333333333334444433333333343333333333222221100000//////..--........,,++,,,,,,,,,,,,,,++----------------............++++++++++++))))(()()'&&&&&&'''(-FZL00+')-28ANZenrsliccfQ)##%%$#((.9K`tyjcaa`\XVUYhuvsxrhfiounnnopwyY8KW\^]Z[nyjwyr} oW>9448?;697.'(*,--,,+*('''((''&'&%%%#$$#$&%'),./10222222478>CIKLLLMONNQTWXXXVROKJIGBAA?=?BCEFGFD@<5*&&'()*,,.-*('&%(-.00<CEIIEB:+! "%07;;;<;:741.,+'#%%###$$#"""!!!##$$$%&'())+,.18FYjz yrh^OG>40031:AMmyuvvsokc[QIFFHJJJJJJJJHHFFEGKUamw |vpkihhhhggiihhhggijlosvz~{wvuvxxwvtttsrrrrsssuuw{~ --////0011111133333333333333333344443333443333333322441000000000................,,,,,,,,,,,,,,,,,,++,,--------------......//..--++++++**++**))))((()((((''((((()+?UP<;-)*.2:EP[dmorlhdbegC#"%&$%+.;Oi|xcXX^a`_]ZWWe{{ozsggfmtxyvpomoruz\36KVVXXR\ntxrbk~z}iP@98865889;?5('''*,*)(('%%%$&%%%%$$$$$$"!$%$&(*.,-///0111499?EGMNOOOQTUVWYY]ZYXURNJIIGDB><=ABABCB>8/*''&')**+,,*(((%'+/03@BGJJH@2' &5:<=>=93/--.--*(&$$#!!!#"!! !"$$%())*.-,.04EZp~}}uj`WOE7.(&#&*6:@NYiottnf^SLHGIIJJJJJIIIHFFEFLWalx {voljihhhgghhhhhghjknotw{ }|xwtvxwvvtssrrrrrrsstuy} ..//0000111111333333333333333355444433334433333333334410000000..................,,,,,,,,,,,,,,,,,,++,,--------------.....///..----++++**++**))))(((((((((()))))**2IXK:-*+/6>GR^gkkflmc`cif;"$$%),5Ih |oXJNPX^bb``\Zc}~{ujjhkmouxrmnnruw^1.7KQPQMNX^ZRFBi}{uzvkP;56:987768;<;.&%%&''%%%$$$$#$##$##"""""!!##$%&)+,,----.0147;>FJKNPQQSVWZ[\Y[]\ZYUSONNKIGDB>>@@AA?<5/*''&'((*+++*(((''+..4BBGIHF?-# )5;=?>72.+)&$(,,*&$#"#""!#"!! !!#$%'*)*,,05;9ESd{x|yskfZRB9.((+09DFLONQ\kkicWOJIIJLJJJJIIHGEEEFLWany zumjhhggggghhhhhghjmorux~ }zxwuvxwvutssrrrrrrsssux} ////000022221133333333333333444455553333444444443344432011000000..........----------+++++,,,++,,++,,,,,,,,,,,,------.....///..--++,,,,**++**))**))(())'')+**,,+,-09LT<--/3:BLU`hjga`khbbcoc/$(+/>^~ w]B5<ELRX\_adbbds{nkjijlrtpkklouu_2,.9HKHNKJLBB?4;dxzpbN;4579;;:968:;;6(%$$%%%$$##""""""""""""""!!!#$%&&(+---,--/.;A=BEILORSTUUUX\^]^^]]\XUSSTRQPIFD@=;>=<94.*))(((((*++****(')*.4AFHHEB7) "#,6=><940,*('$$'(*($###$$"!"""! !$%'(+--/36<Qhnjp{ytlf\RE732249?GEDGIKWZY]aTIHKMIKLIJIIHFEDEFLW`nx zsligggggggghhhhhijmosxx }zyvvwxwuttssrqssssrstw| ////002222112233333333333333444455555333444444443344431121000000..........----,,,,,,+++++,,,++,,++,,,,,,,,,,,,------......//..--++,,,,,*+++*))**))(())**+*-.////111/GA1128?HP[cije[QZmgbagqT$#),2HlyiI.)24;DJRXZ]adfhuqigfhlmkiikloqv_,)+.4FHEGF>65IF53Vd]H<55689;;::979:;=-%%#$%%$$##"""""" !!!! #$&&%&(*+***,.49;<CHNORRRSSTUW\]]^bb`^YVXYXXWWUMIC=978730,+)))(((*++*****(*,/5AFFEC=/# !%-6<?;71.+)&%##"#$&#"$))%%#"###"#%')*,022/1=\u~zy|~|wpic\PFA:768:??ADFINKNHJ^eSUZHFJJJJJGFEDDHOXboz }vrmigffffggghhhhhjlprtw|~|ywuvwxwuttssttrrrrrstw| 00112233332222223343234444444444555555334444445555443322322200000.......--,...--,,,,+++++,+,+++,*,,,,,------..------..........----..--,+,,,+****))*))++-..00121100119B=57>ENT]fjjbUKHakecdnq>!(*6Qy}~sW9&(+,15<AHPTZ_dlr~z|shdbgmqmkijkoop^+)+**7FIEC>3,131*1DC843247:::;;;78:;<6(('&%&''%$""##""!!!!!! ! "#$%(***,,,.367;<BGLORQQQSVVZZ\[`aca^\]^]Y\^WSNID<75640..-,*)())**++++,+,-17@DEDA7+!""#(.4;>:4/*)&%#""!"$!%&)))&$#"$%%%&&*++,./-.;KXctxws|{y| |xrib\VOB:>:=@CEGHLNUQVZJHVS_i\MIJJKIGECDHQXdoy {vqmhgfeeeggghghhimmpqtv }{yxyyyutssrrrrrrqqssuv{001122333322222233432344444444445555554444444455554433323333200000......------,,,,,,+++++++,,+++*,,,,,------,,------......--..--....--,+,,,+****))+++--/0122334444444>JC9BLT]fklj`UE8Hiidbita*#+:Z|}xwc7%'*),,059@EMS[frx yxse``aipofgkmnor],(*-129FA@<6**)('(,/023598::9:;;9779<;-(*+,())*)('%#$$""""!! "#$%&)))*,,,/3689<AEJORRPPTVX[\\_^`ab`___\[[[ZWINH<74220/.-,+)))**+++++-,-0;@DED?5& !"$).39;950+'&$#""""##$%(***$%###$&('*++++*+3APWB=9<:Zlr~|us{~xpkg_UNDB@@CFKKMLLQNH?KKLOXglbIIKIHFDCEIP\epy zuqmgffeffgghfgiiilnqruv ~{yxyywttssrrqqqpqqssux~ 001222332222333333433444444455555555555555444444556644444222100000/.....------,,,,,,++++++,-++++,,+,,,--,,,,,,------....--------------,++++***++**,,//1233456656567788DLDHRY`fmmi[P>05VpiccjqH$)8Z~{xs\.&&)((+,039?FKXiwyzud^\]clphggilorZ-'*-5;9RH=:92+%'$&&)-.27@=989:998668<<7-+-/,,,...,)%#$##""" "##$&'())),,/373139>AHMPQOQSUVXZ]][[adcbb_^_ab\RTRG;753320-.-+++,,++,,./003;@CCA;1$!!%'(-26:93-)(&%$""$%#$%()),-(%$$$&''()**)(+/8ETU=5*'/BXk}}ukhqx~xsleaYRKCBFFFIPQSWTLIPNORYeV]]MKJJGEDEJS]fqy ~xsokggedffggghghikmprtvx ~|xxzxwtsssrrqqrqqqstsy222222332222333333433444444455555555555566555555556655554422111100/.....------,,,,,,,+++++,-++++,,,-,,--,,,,,,------....--------------,+++**,,++,-/03367668899888:;;;;>JURT\cjrmcWJ8.,?fmebelf;$5Qw|vq^6&'))'()*.38>GWlyu|zvh^\[^chggegmprZ+'*-5<Cc[B793,,(%(()*,.5<;557887876577:2++-,++,-./,*)(&&$"! "##$&'('((()-110/05:>AGNRTPRSSUUWXX[\]a_`__[[YY[VSNG@674210---++,,,,--.1333;>AA?9/$!"(+,/19<ED>3+'&$&&#$#&))(*..-,)%$%'*((((((+264<1*''*5CZn~|qjeiqx}xsokeYRLJIOOQUY\\VQWRKNTWZUIPXKDEEEDFNT]grx }xsokffedffggghiijlnpruv} ~|yxzuwtrssrrqqqqqqrst{333333331344333322444555443345555555556677555555556666554432221111/...----,,,,----,,,,,,++++++++,,--,,--------------....----..--....--,+,,,,---.1222559;::;;;<;;<<===@CFOVZ\fnvnaSF5.,3Mpifcem]/*Dj{upeA&%')'&))*/48AZryoszvna][^efffcfjlkZ-%&,17Jg[N8//,++((*++*,.13224577685596582*)+,,++,,+,+)(''&$ ""#$&''''''((*(),/28>AKQQRTSRSSSTTRUXZ\Y[^]]dc`ZRQQMI887211/0-------//34548=>><6+##$&)/19M`lpk_RA3+*&"""&-64688830-,*++*))))&%*+'%%'''(2C\p|pc\`hry}ztpkhbbca`_b_^][bmhY[^]YWP@8OVeZGFFGLU`isz}wrnjgfedfffghhiijmorsty ~}|zyzvvsqrrrrrqppqqrsu{333333333444333322444555444456555555556677655555556666554432221111/...------,,----,,,,,,++++++++,,--,,--------------....----..--....--,+,,,,-../24758;>?=>>>@@@AAABBCDFJLSVYZjvn_OD5/,-9ZtiedjlZ.2YwvpiO*')'(&)*)+/5D`wsjn~ztoqf^\\`bffcgmlm\.&&++0MgYRQ9**++,,-,,-.---//14587556875@P+*-,,+++*+++,*('&$""""##$$%%''((*/98<FLRSTPTXUTQOQOQSUVW^ab_bfc]UTRQM:5;9400....--/025684489;60'##%%+6F[s zm`TB.%$,9AFGFEACB<4442221000/-*'$$$$$&(4H_u{qaWY`jsy|wqjghddda`afkllc[WXW`efaa[LUk\JEFKOXbisz| }{wrnjfeedffffffiijmorsu| ~|zyxvvsqrrrrrqppqqrsv}2222223344465544334444445555554466666666556666666666766544442111110/..----,,,,----,,++,,+,,,-,+,----,,+,..--------....------....//.--,*+,,--/0125789<>?ABACDCDCDDDEEEGHKQY_^\cpkZL?3/.-0AipldfmsY-GlwumY5&()'))*,-1;Sqxljr||tomqm`][_acfgijlm_.%*,.1Pe]WUV:+'(((()*-./-,,-.0445314433KxR!//.---,++*++*('&%" ! !!#$$#"&&&(,37=EJOSWSSPROQNQOPRTRZ`efghddZN>QNY@0;=7220000100168862452*&#%&(4J^pzkT1):EMIA?EFFEA;:8:::<<:863,&! """%'7Ndw}ncUSYckt{ zzyxxwmlnmuyupklqosrikjkno]NTOGCMUZbirx{~ }yupnjggfffeggfgijkmprvv ~|zyxvvsqqqqqrrqqppqrx2222333344465544444444445555554455556666556666666666666543443222110/..----,,,,----,,++,,+,,,-,+,-----,,-..--------....------....//../.,-..//113689<>?ABDEFGGGGGGHHIIIKJNU]dggadg[L?2/.-04TpkidhruJ<azwpc=%'&'()*+-0A`xvklx~xsmlovd]\[aceffikl]/&*,-3Sg[Y[XN9*'&&')(-00/.++-012311699Ie6&.00/11..-,++('&%$$%%"!" ! !! !#""')-/7?DFINQLNPRPMJLNQRRSW]effhgdZUVKIZN67=:62111111278864230(%%',Cax wO0:BMB><@LJID?967:;<<9998-$ !$$$*?Si}tdSKPZeov| {zwuqsxzyxtufadkjpqong_TWgbfTLT\ejsvz} ~|xtomifeeefeggfgijlnqqtw ~|zzyvtsqqqqqqqooppqry3333444444454455554444555555555544456666455677665666666654443322110/..------------,,,,,,,,,,,,,,------,,--,,----------------....////010...013559:;?ACDDEGIIJJLKKLLLKLNOSY`gmpmd\UK?62////;_phedjtk@UwysiF(''))()*-3Fi{rkn|zqmkdavk\]\`cdcchjj]0'*+-3XhZYYVRJ>,&&'''+23/..,,-../24>FPaxw+13234321/-+((%#"####" !! !#$&)-3:@DEFHHGJLNONNMMPOQU\`feijgd[RMJZeXF?<:7320112366876332+'')AilB?FLEDMITYUOMIB==>AA>>?<2($#%%&'.BXmyeNBHR_ipx ~yvutuqtwwkfc]Z`^bfb[a\Z=AQd]^[_emrvzz| }zvsnlieeeefffffgijnosst{ ~|yxyusrpqqqpppoopppu{ 333344444454445555444455555555554445666645567766566666665444332211//..------------,,,,,,,,,,,,,,------,,--,,----..----------......0335400023378<>?CEFEHJMNNNOPPOPPPOQRRUZahnospbRC>7543//1Cjpfeely^Nt|voP+&(**((*-2Jmyolrxnlgbdopb`^^acabeih\0'+,06_e[]][POL@.('&'(.2/-,,*+,,-7BNYix[-4566321/-+*)(&#"""! !#$$&(-269>ABDEEINKOPPOPMOSV\l_aoljc[QLYe_UOIA98852212567798662,(9b{R<FIJJKNSVXX\aa^UUQONMGA4&##%&((.AWmudI9=JW`kv}}~~tjada^`h]aYUWafaWZa ACW_[`diosvxy| ~|yurmkheeeeffeefgijnosst| ~|yywusrpppqqppooppqv| 33444444554444445544444455444455555566666677666666667664554433331100..----,,---,----,,,,,,,,,,,,,,--..,,,-,,----......------..//..014577667799>@ABFGKMMOOQQSUUTTTTSSSSUV\ciqqqoe[PC<8542105Pqieffru]qysY0&&)*)),.5Rqvmmu~qeggiiith_^\_``cdghZ0&,-1:ee^`^[OMNQD-)('%'//+)+**+/8GWbn{6$/3444400--.-,)&$!! !"""#%)-.16:>@BCEMSRQNMIHMQU[ejoplmj`SNK`cUPPNI?8743334478:<<<949Z ^>IIJHLMRTZ^_bcd]Z^]ZXTE0%##%%&&-ASi}|pbE/1>JXelw xjaZY^\WWW\^^_jeXi}LZa^ZYforsvz| ~~~~}zxtqkkgedeeedeeghilnpsss ~|zxvuqqpppoooooonopw} 44444444554444445555444444444455555566666677776666667634554433331100/.----,,---,----,,,,--,,,,,,----..,,,,,,..--......--------//134468;<<<<<;;=@DDHJLNOQRSUWXYXYXXXXXXZ[`fkpsqoeZQPKA8433208^qgdhnvuxxa3(*(**+,.<Zxsmls~vjiiiijpma]\^cabfhfY0+.34?fd^]^ZMJPTSC,'&$$&*.*-317AKWhp|x(/244310/,--++)%#! "!#&(*,,-279<@BGMPQIBHKNSY__ejglni\TVMRdXSOPPMHA80/13666666<8=YzlFFIILMOQSVY\^__\\[\[XQB,$"##$$&)7McxynX=,)/@LYet~yvtdefeZZ^jjg_LHNk tSPLQYgnsuuw| ~||}}{xtqkieedddddfffgilnpqtu ~zywtsqqpoooooooonnrx 44444444554444444434444444554455555566666677777777778AC;6644443231000/....,,---,------------,,----..--------......------....--//13558;=AABB@==@CFHKNPRTUXY[[[\]\\Z[[\\^_bhnsvtqg]TLQUPB5211/>enhfhqy~yi>%'*())*0Bdyolkqukhgiigflc_]^bbdgfgV31114Gfc]__YFFNSTRC+&$##'6=69?EOX`jx U*,.10/...-,++*'$"""%'*+++,/37:@JKQQNJOLNPSW^^^acaaZVYSUc^QONNNLHE>72.+//02345OspsrHAHIJLNQSSSVWYWW\^^\XUE,!! "$%$'4Jbw~uiR3(56>DO^o| ztneZWVX^djdedRL[{}of[Valonw} {|{{{yxtokheddccddeeeghloqrty |yvvusqpqppoonnnnlot{ 44444444554444444434444444444455555566666677777777778DWN96544432310000....-,--------------..------..--------......------....--//23579;?DDEFEBACEIKNQSTVX[]^_````_`````bdgkpsvtrk`WMHDHMKA4101Gkkdchp} zpV0(*(+-17Jiumkhozojhhiieceha`_^bdeefU31112Pga_c`WEFKPQRP?)###%1DNDCMSZbiy ;/.00//./.-++*'$"! !#%&&*,,-/37?DEKMNKIJMOQSWZ^^a`b^YUSUc]QPNNNLHEDC>830.,+//DiwU=AYx uPDHIIILMNORSURRTWYXVRPC.!! "$%$&2E]r}sfR/&7;?AMany yskcYSQPKOVX^_^_v}WU[TU[`cp~~|{zz{|ywsnjgdddccddeeegimpstu~|yxvtrqqpoooonnnnnpu| 44443455444444444444555544334455665566776677776666667:GN<555444331//00...---..-...,,--..------..---...........------------....//1469;>BHJMMLIGGJLNPSWY[^_bbcddddccddeeffjnquvwrlcZQJCBBFIID:10Pofcbfr~~ykWC536:BJYoqjgenumkijjheecmda`_bdddeS0,*,2[idaghWIKMOOOOL9&$%(0AVXKHRXagr{%(.320//.+)***(&# !""$&'*-/24:ADGPTYYZ[]^emvustpkf`_ZPWf`SPOOMKIFDBBC=:421+2\~ mK*()=cvPAIJKKKMOONNNKKPSSONMKC.!!""$%%(2H]r{obM-)+/6?Malu| yskfYUQPR]_]\bgo{ ¢¥¤WLNSQJG@;ALVl~~zyyz{zyvsmhedddddddeeegjmqstu}xwuspoppppnnnnmnopu~ 44443455444444444444555544444455555566776677776666666657:3554443310000....--..-..-..-.//------..---...........------------....//357:<@DIMORRPMJLNQSUXZ_abeegffhhiihhiiiinqtuvurlcZTMFB@>=DMNE?>\ldccis}yrf\UV[`dlrnigdlxoijihfbdbmlfecdfddcR0)**<gidfegOLOMNNMNNK8((.6:K\ZLQY]dmw~ h(//./-+)(())('$!!""#%%),/112FWdlqrvxzvrttvtrtsstrmaUXeaTONNNLIFDBCBDFIA0/Aoz{~ }a7%()%2W} sIDIJKKKMNNMMIFKLNNNOKIB-!!""$%%(3I_t~wn^F(+3/*3M_iqx|}xpieb[U[\^\]agq iSHVaain\MJHGGDBJf||zyz{zyxurmhddddddddeeegjmqsuw|zxwuspopooonmnnmnopv 44444444444444445555554454554455456666677766666655665563455543333222110./.---.............//..........////..--------..--------00368;?CFKNRVXWSOPQTWZ[^bcfehijkkkkkjkllmnrsssutpkb]TNKDC@><:=CEEOghddfoy||wysppprropnhefbk{pgfffeccbmvjfeadcdbQ,**,IlgfhgfKKONNONLLKH82599=SZTOSZ_iq|C*,,***''&&&&&%# !"!&'*/9Nahjjijpyyvustsqooponprrtsqm`UQOONLIGECBBEJQ``P[{{||~m8&(%'/NwhCDIJKKJKNMMLFFJKLLKJIG>.! "#$$%(3K`w ~{tl\G.&1)(:P[elqw{~ {smijkhaZ^cgkpxB.0;M]`nqtfVXchimmh^RVgy{xyxzzyxupkfdbbddddddeegknqss{~|yvtrqpoooomlllmmosy 4433444444445544555544445455445545666667776666665566555553554333332211110/..-../00//////..//..........////..--------....------/0/28;@DHLQVY[[ZUSTUZ\^begjjlmmnnnnnmnpprsuvvvtsoib]UPMHEB@=9856;CRfeceisyuxvttuxvsolfbbebi}pijhjhiijo{nihcceb^N,*+/XlhfjnaELPPOPNLKIGFB?<=;=E9<KV^fnr|,!&(''&&&%%$$#"! !#"'?Zfgdcdegrxxutvtsromkkllnpprv{}t\PLNNLHFECBCDHSamrz~{}}}~y],$%&$$/NtZ9DHLMMLMNMLKFHKLMMKJIG>.#!"""%(8Pfz~~{tiX>+,0,.EQZ^gkquz|}~|yutspngefhkqx\2DL\ba]nsm£©¦ogmvwxzywxsojedbbdddddefgimooru~}{yvtrqpoooollllmmosz5544443333444444444555444444445555775556666666666666665555542244332111000//../....//////////////......////..../---..--..------/0146:@DHLQX\^a`ZXYZ\``dhijlmnooooooqqrrstwwvutqmfa\WQMJHDA?;96213<Wjcfeorghgfjkjifgiffed`fxsomiifeghh}rhfdaga\N,*)<gkhilmSBKOPQQONLIGCB@B>?9;54BQZbglus $'&'%%$$""!! "7[lica`acirzwuuwvtsrpnnlllkkloptzwZLONLKGECCCCJT`kt}y{{xuoJ)&&%&&.Nt zO7BFJLLMLLLKHEIKKMKJHFD>.# !!!%,<Tk~}yrgS8"'18=LQZ\`fknqrwy|~~~|wxwuqqqojnszA'7EQ[]QUb}³¼»º¹±¢~pwt|zzzyvsnkgcbbdddccdegilnqss }zxvtrpooonmllllnlot|5544443333444444444555444444445555665556666666666666665555454444332111000//..///..////0/////////....../////.....--..--..----..00146:>AHLPV\`cda^\]_abfhilnppqoooooqqrsuwxxvtsokfa\VSPMJFD@=:74111GjeacilXKRUTTNJKWhfa_\Zdxpjidgjjgdexxkhfchg[J,'.PnihlojEELPSTRQOMIEC><@E>;9=9?TV[`gmv~X&&$$$$%%""!! 4Zkje``bcclvyuvuxxxxusqpnnlljjjklnszy\ONLKGECBBEJRant}ywwsp`9,,(&'(/OsvI7BFJLLMLJJICEIJIIIHIGE?-$##!"#%,@XmzrdP5""&2BMPVY\aeikmqtxz|||{{|{wtttrptx~71<ES_VQMn ¶¾º¶·£«´fXp{yvrmgccbccccccdehjmoqsu }zwutrpooonmllllmmpu} 554444444433334444554444444444446666555677666666666666555344444444321100000////.//////////////////////.////.//.//.....---------/1358;@EJOT[aejiea]_bdfjljmnnpnnnnnqrtwyzxwvtrnid`\WTROLHFB@<97312:^ia`bkX04;:62/7Nhd\ZWS]xrigbdhjhilr}mfcbjo\G'(@hkijpp]?HNSTUSQPNIEA?=<>EB=8@ARdQYafku}=&((%&%$##""!! "Eloe^\`ddfjxzwvwxy{zyxvurqpqmkkkiijlr{z[PLJFECDCDITant~zvsstqrZ7/0,(''2Qu~p=3AHKJLMLJIABGIIIJIHIHF>,"#%#$%'/C[q }ypaM2 !%9KPTVUX\^cegklsvxy{{{}~zzxxuvy| 33<DT[LEX´½½«¨¤¬¹² w_nxwvokgecbbbcccccegjmoqsw {xwusrpoooomkklllnqv 554444444433334444554444444444446666555677666666666666555344444444322210000///////////////////////////.////./////.....--------/01357:=BFMS[aejmjfcbdehkkjmnnpnnnopqsvx{{xwtsqmgd`\XVSPMJGDB>:74213Nj`]`gg5'*++,.9Re_WVPMVxshggcekejkntiffmv`C&BdjhilpqNBKPUVVTROLGEC?=9:@JJ?9>E[XPX`bju{- ()'''%%%""!! /]nk_^`cegjqzwuvwxy{}}}}ywvtrqonmkjfhgksyqULJFECCCDJTblr|zrppqnnmaE1//++5Uw ~~i2#5EJKNMLG?=CIKJJJIHIHF<*"##"%%&0G_w }wo`H-#%,BPTWWWYZZ]\`dfkqtvxyy{}}}}|xz}~ y77;AMRJDo¦·¾¶| ´º¸¶~uvutokgecbaabbbbcegjmost| }zvtrrqpnnkkkklllnpw 445543555544444444333353444455556655555656665566667777664444443344343311111100////////0000//./00////00.///0000/..-....----..../134679<AELRZ`fknnjgfgiiijiklnpooorswx{|yxxwvsnige_\ZXSQOKIFB?:8622/?cc^afjG&(*)+/;Te[WRMNTtwfghiiifhlp~mkjhuzpgihhlpnphCGMQUWURRNIFEA>=;859FI?:9BIPPV^fmu~w%"(((&%%$! !""Bjnb^`cefjpw{wtvxw{|~}~~}zxuvwtrqojgfeffmsz|iPJGFCECGLUbrxyupmnpoopomaG2-,7Vy~~d0#$*;FHJH?53<CIKLKKJHHE8%""""$$&3Md{ }vm]C)"*:LUWZ]\\[ZYTUZdhlqttuwx~~}}~~~rG9@CKICM©¶½¶{Y©²ª¡£uqrokfbbaaabbccdfhkoqqr~~zwtsqoooonmlkklljmpx 555543455544444444333344444455444455555665555566667777664444444444222211111100////////0000//./00////00.///0000/..-....----..//0135789<AEKQX^djppmjgfhhijlknoqpqswy|}~{yzywrmjfc_\[XTRPMIFB?;962304[g`_bhW(%)),0<VfZUQOPUrxheeebekhimzskjio|zxoloppq\AHNPSUTROLGC@><:95328CG>15=CFQ[^enu{k%&&""#" ! #Nnl`_eddirwz|wtsuxz{{|}}||xtuxvtttrpkigefhmszx`KHDCDDHMXfrxwrqnmonnqromk`TB?\z|~y[(""#!*8AA7/9@BFGIKLJIHD6%""""$$(:Qi{ xmZ?&%1DRVY[^__ZYWTSY]ekmoompsx|}}|mVCFEIE@a¨²·¸ª|¦ronhfdbbbbaacddfglnqrs~zwusqooonmlkkklmknry 65554345555555444433443333445544555566666655445566756666545544443333211111110000//////0000//0000..//00//////00/..-....----..000135689<AEIQU[biptrnjggfhikloqvyy|}~{zwqmjfb_\[XUROLJGC?<973213Li^]aca0$()+0>XdWSQRTXirfabcaailrpqujffhq{zyuonprnNDJNORRROKJEA><:75331/2@C:37<?DT\acnu| X"$!! "#$&# -Yokedhhinrusvwustuvyzyz{yrjiihquqprvwrolhfilpswrSIFFBEIP]krwvonmnpmprqojeiooko|~}wS$!#$#"%#+*(/:?EGIIJEFF?1%#$""$%-?VmylX:&*;JU[ZZ]__]ZWUSV[`eikljkowz|||}~mVOLGIBAl¥««¨¡ ¡skidbbbabbbcceggjnqss }zwsqonnmllkkjjjkkns{ 655555665555554444554433334455545555666666555555666766666555444433333311111100000/////0000//0000////00//////00/..-....----..00013568:=?CHNTZ`gnrusnjighhinqv| ~{zwqnkfb_\[XUROLJGC?<973200@c`X]be@%((*/A[bVRQTVVdsg`ccchmmokgqsh^_cimrx{xttm]GIKMNOOOKKJEA>;9653321/2<=437AGRYYaemuy M "%(+-% 7amhdgimmrxunprrqposvvwyzphcehmnopnmkkrxwoihjlmrtsdMHDCGKUcotvtnmmonkntrpljhimr{{~|sK$!$$"$$$'*+).:AEHIKMLE<0&$$##&'0DYq whT4%0BLRYZ[^``_\WUSTY^afhiknpuquz|}n[ZZTQHDq¦¥£jedabbabbbcceghkppsv~}zvsqnnmlllkkjjkjlnt~ 55665566645555444455443333345544555555556666666666666666665554443333332211001111/////0//00//0000///////////////..-....----..//123579:=?CGLSX_flqwurokighiot{ }zwrolhdb^[[YUSOLJGC?;8621./4SdZW[cS&%(+2C]aXRQVYX`ricjjnlkmkllcgie`]cinquzzn^OMNMMNNMLKJIEB?=96522/.../6A?7>GNWaU]dlt{A !!!! !%(/4-Aemhehjknvwpiknrponoqttuvkcaaagjmnqnlifju{slljjlnpqmWFCCHNXeptxqnopqnjmrroljjhkt}|}}rC#$$"##"%%',.**4=BGHGHE</%$$$$&)2G^u tgS1&6DKOV[\_^]_\YVTTX\`caceikppmuw|pdgcZVQOu¦¥£¡ |fbca`abaaccegilqquy ~{xuqnmmllkjkkjhkkmow 9999886676555533445544333333444455555555666666666666667666664444333333221122111100///0//////000000/////////////..-....----..//013579:=?CGLPW\ciorutpmifhltx| {vspnlhdb^[ZWTQNKIHC?;86220/1Cb[XZ`]2%+.0@Z]VSUWXY`smdhpqqnnqmnk_]bidfgklngaUQSSTQQOOMKIHGEB?>;762100//,-7FE?CHNidU_glqx}~8 """""""" !#"'/8HZhhhkmmqwvligillmmopqqtr][[[_`ekopnoqmjditwqokkmlnnreIDBIQ[hqwunnpppmfirrpnkljiq{z|{k;$&%$%%%#%',12+$(6ADGFB7+#$%$$&*6Mcz|reL.&7EKOSYY\^]][WUUUX\^abddccikjorx||rnkge`\\|¤¢z¢}kcbbbabaaccegilqrt{ ~{xupnmllljjkkjkjjmqx 9899998777665554444444333333444444446666556666666666666665664444433322221111111111//////--//0000110000//00/////..-....----.../013579;>@BFJOTY_eiptwsplggmwz} ~zvusplifca^[YVROMJHFC>:86330//5Y`WX\aC&),0A\]WVVYYY_sqijkmqljjlnjjdXYYR=:>BFRSWYYYVSOOKJGFDB@?<;9740/.----/8FKFFCSrcY`iisz}2! !!"####"$$%$$""(%5HMRZdjootxwiceddgggknpnojRQXW[\_cfhlpsutnjdkyyspllmlorpUDCJQ]muxrnoppnibhqspmmmmlu}|{yh7%''%%'(&'*./41*$%-:DC@3*$$$$'',9Pg|yn_A)(<EJNRVY[]]\\[[ZZ[]_`a``_beedhlpx{}|wsspkgfe ~sVoyydaccabaabcegjmpru}~ {wtpnmkkkkjkkhijkmsz ;::::::877665554444444333333444444445555556666666666666665554444443321221111110011//////--//0000..//////00/////..-....----.../013579;>@BDHLRV\agmrvvspljnuy{~|zyuroljgeb`^[ZWROMJHFC>:86330..1Ib\Y[`T((+0B[]WUUWWX\ovlnquyqoqrkike]\ZL==@HNTXY[[YVSQOMIDCBA>>=<:8520...//159CLIB2F^g]^djsy2! "$#$$$$$$$%%%$%5SlbNJWblqx{obaacaaccbgklnaAELRXY]bcdgmquuwsmiit~yqnkmmmprcHEJUarvxropppnb_gqupmkjhlr|~}}ze1&&'%%&(())&%)24,&%%.;>1&$%%('(-?VoynZ8(0?GJNQTY\][]][[[[]^^_`_^]^bcbcgjovy}}}{uttrsqopv{|o`ababaabcegjmqst }zuspmmkkkkjkkijilns| <;::::::98887766444444333444333344556655555555666655666655444444333322221111000000////....//////00//0//////.....--....-------.012479:=?AEGKNRW]bjpuwvtmlovx{{~~|z|{{zxvqolkiecb_\YWTROMJEEA=977321-./9]aYY_a6&+0C[^YYYZ[[_kwqnmpy{xtwohef^WTG<>BHMRVZ\[ZWUSPLHDA@><<;:8764311/015;?;;JMFAB]g_`dkry/"&#$$%&##!$$%#*FhuqdLAJ`t|yg_b^_acccaaeikT?CJHOTWZaa`dintyzwtqloy{rmmmmkmmiPILWfvxuqqrqpm\^fptqmkkiks}{}xa,'('(('(*)'&'(-00'(()*//*)'&(%&0F\syjU1'5EKJLPRTXZ\\\\[\]]\]``_`]^^[Y\`elotwyz}~xrstuwutv ¬¬¢z{¡xdb``aabacehknrtt {wvuqnmlkjjjkkkkjkow =<;;;;::98887766544444333444333344556655555555666655666655444433333322221111000000////....//////00/////////.....--....-------.012479:=?ADEILOSY^dkrvwwrprsvwz{z{{zzzywuroljheba^[YUROMKHEB?;976411../2Ma[Y[bO'+/=X`\\\^^`agwuoqwwvxrkheUJOA:=@CKQUXYYYWVRPMJEC@>=;::::86532334<=;;=<>S]Zcijcddmsy~+('&%&&$#%$!(Onxi`c^K<ATntdY[]^bbbfe_`hdO=?ICGHNUVUWZ`hoty|{xuqlt|tlllkjhhhXKR[mwvqpqrqpeW[fosplkkjmt}||wY)'(())*))('''(*-4.(()****))*)))3Kat xfL-,<GJKLNQRVXYZZ\\\\^^\Z^]Z]__\YZ\^biostwywqnpuuuuvw ³¶®£{ |rabaa```bfhknrtv~ {wtqpmmlkjjjkkkkkmrw ==>=<<<;;;99887765444444334433445555665555666666665566665544333333321111111111////....---.//00//00/////...........//..//.-....012468:=?ACEGJNPV[_fjntwurqrtvxyxxyyxxwutonlihfb_]ZWSPNLIFC?>:97422/.--.=^^YY`a;(-=Xe`_^_ciigs}ut| ywwunj_QJPE:=@CGNQVVWVUURPMIFDA@><:::::7544579=<<;=6,3L^fginndbjqw{ &!)')'&%$#"5qyrke_]XN?>QRVYYY\^bbeeeefWD=<BHEHFMQOQT_hosstvyywups}xolkkjhif[RTapvtprssrm^VYbnrnjjjimt~}~uT'()(()*)(('()'),21)'(*+*+,,0--/;Si|}scC+/>IKKMPPQRUWXYZZ\]_^]]]]\\]^ZWXY[[`ippquqghmosvxwz®³« ¡~f`_``a`adhmprtw }{wsqnmlkkkkkkkllkmqx @@?===<<;;99887777555544334433445555665555666666665566665544333333321111111100////....---.////--00/////...........//..//.-.....02468:=?ACEILMNRV[`glpvxxtrtvvvwwuuvvutsnmkgfe`_]XUQNLJGDB?=;:8641/.---2Q`[Z^bR*,9Udcddelppir~z}|v|unf`UK=:9<?AFHMRSTSTSRPMJGEB@>=;:;:7544456;>=<;;3/+*<Yfgjrthcdktz~,"))'''#@rvnjjkijfZA8IMEOUVY\acdeg^C4E?AQKEHHILNS]flrttstuxyxvx}}umjlhffcaWZiuwsprstphVTXalrnjkijnu~|}rK$')))()((***)*')03-''(****-0339G\s |r`A+3BJKKMPPPPRTVYYYZ\___]\\[Y_^[TUY_`ZbjjmodZ`gnswy| ¨©~o_`__`_cfkmppuv ~yurplllkkkkkkklllory A@ACB@?><<;:::88776666554455444444445566665566555566665554443333222222011110//////....---.--...........---............//.......02458:;>@BDFILMQUX]dhlquxtrttuuusssrtrroljhfdb_[YVSPLJHFCB?>:87641/-,,,-A_]YZ^b=)7Qffiknqutpt|pmuslj_XH789:<=AEKPQPPQPONOKFB@?>=;;988543237=>><<81...+0Jchhqqogfmsx}(!'+('"3suokkmmloo[C8<ODJUXZ[\^^[L94586EMIBIJJLT\cgimprqppuxzzz}}unhjgcaab\`pzxqqqrslbPPW`jpkjljjqy|~qC&''(''()*))**)(*-150'(())*,234>Rfz }yo]:+9EIMOOOPOOPRRVWYZ[^^_`]YXXW][TSUYed_dehk]V^fmtz{¡ yda`aaadgiloswz |ywtqpomkjkkjjjjjjkns} BAA@?@?>>;;:::88776666554455444444445566666666555566666644443333222222011110//////....------...........---............//....../123468;=@CEEGJLPSVZ]cglquuqqqpprrppqpmmljheca^ZXVTQMKHFEBA><:76420.,+++-3O`VUY`W,2Jaklmqvxvqqsmtlnvieh^XWB8::99:=AHNPMKLNOONLHC?<;:8778854325:?@><<71//0/.+5Sjjjurhejr{v$('(&jwmhjprttxrZG<7GIESWYZZXE657:81:DB@BDHIQ]dgfghginnprvxwvyzuojhea_`a_dswvsqrrpiYNOU`ilijljjq||m<%'()))*+*+**++*++/471&'()),18<I\r wjV4,:DIMOOPRQONOPRUYZ]^_a`]YWUU\_]YSONaggfgfZYagrv|~ ¢tfeddegilmpsw~~|vsqmllmlkijjjjjjlou FFEDDCA???<:998877777755333344443344555566666655555566555544333322211101111000////....--,,,,--..//------........--...........//112356;;>>@CEGINQTWY^chnnoommnnoommmkkjhgeba^[YTRQNKIHECA>>;86631/.,,++,.<\XST\_A)=Tflos{zvolgin[bqxg[OSSB::;9977;CJMKHFJKMLKEB<::88887643237>BB@><5111100/,'@]flmpmjmvy~O#(&Qzqkjpuw{}`VG;3;B;?IUZO5)6;9=@=;;>BDGIQZchifabc`_eikoqstsrqqjd`_^`_chwxtturqodRMNT_fhjmkikr|}m@2.*++++****+,--,--08</&(),..9ASgy ~udH-+:CGKNOPRSRONNORSX[]___^ZVTQZ`c_ZWTU_giie[_hnuz~¤¤¢~}lhkiikmnrsuw~~{wurolllkjikiiiijjmpw HHFECCBBA?>><<:988777655433344443344665566666655555555555544333322211111111000////....--,,,,--....------........--...........//0123578:;=?BEGHKORVXY]begikkkiijjiiihhfedb_^[WUSPNLJHFCA?=<886420,+++++,,2M_VQW[W,0=P^houztmh_WQJZr sfe`I=9;99778>CIHEEFHIHGC@<:85545653357=CDBB=94222010../,+Dcjmpqmpsv~ w $"B{qhjovy|w\VVI9257876;8,05679:>>>EHLQTZ[aijfa^cf_Z\_abfoqmlnqjd`_^^]alvtqqrqoo`LKOU]cekmjjnu}rRIE<0,,+*++*+..,+--.2:92)-8>7=Mbt ueJ/'0>FJKNQPPOONMNNORX]]^_^[WTSW_fhdcbejklnjcipv|¢£ vpnmooqrswxy ~~{wurpmlljiihiiiijjmpx MMIHGFDBCA@?=<;;8986556666554444444455555555556644444445544333332222222221//////......,,,,,,--,.---...------------..--..........0023668;<>@BEHHMORVXZ\`bdeddbcegfeefecba^[ZXURPOLJHHEAA>;;86421.++++++,-,<[YRU[bE&19EW`ksoha\XUIH`| {uujN<9<:87658=@A@BCCCCDA?<96430//146;?BEECB=6323311210320(3Rgmmjt}wtvzj7 4uujjnvw~u[TUVN>423565432788::99CJNPT\_`^ejhe`cghec_]YPP`omjlqkb]\]^\^ovqopqrqlRLLOSX\ellgimv ylc[SD4-+++++++,++++,.4;=033:>DZn{uj][WLEADFHNONONLJD@ILTZZYX\\[WY^flooprsstqsrqsw{ }wustvwwxwz|~~~|ywtqpnlljjjiiihhkklqy NNLKIHEDECCA?=<<:996556666553333444455555555554444444445533333332222222221//////......,,,,,,--,.--,---------------..--..........00234779:<>?ACGJMQSUUVX\\^]]^_acba``_`^]ZWVTROOKIGFCA?><;98521.,++++++-,-1G_WQZd_0(0:MX[ced^XYZRJNlztpoorbQA<54421/14554678;<><:;964332158?BEFFEB>9311222255555450/=Zinny}{ueRT8'/ltihlow~|gWWZ[XRA3468<3/1666:>?BJMOTWW[__bfgfedeehea[YRE@Qfjjkpi`[[\\\eyzppqsqpbJHKNPU\eolikp{ ypd[I6,+++*++,++++*,/5>735=BQiz}tj\]affeWJFGLLJKGA.*>BFVXXY\`_]^emrsstvyyxxwvxz}{wyzzz|| ~~~{ywrqpnlkiiiiiihhjklq{ ONNMKJHFEDCB@?>=;;977677555555444444444444444444333334544333333322222211110///....----,,,,,,,,,,--,---------------------.....-.-001445789:;=?AFGKLOPQQSVWXXXXZ[]]\]\\\ZXURRPOLJJGECAA?;9764320,,,,**,,,,,-7U[SU]eO&,4@MSUYXTRVYXUR]lmkfhibXK8-.-+*(('''''*-148<ACDDEGGC@AEFGGGFEA;40000155766655633.0Ibgs{|xX $+kvlnqoryscW\\[^\UA556530-/4DIFFXdTQVZZY^^bded```dcdc\WTM?:CUbggkg_ZY[]]n~wpprqqsXFEKNQW]ejlmrxsk^L4-,,,-,,-+++++-16:;;BLZr|sh`]bfcbbYGOMG>9=?9#/3.GXZ\_cdcfkrvwyyyyz|~}|} ~~~zxvrppnlkiihfhiiiikms} PONNMKJHGEDB@>>=<;987677555555444444444444444444333334544333333322222211110///....----,,,,,,,,,,,,,,,,,,----------------.....-..//03345789:;=@DEIJLNMOOPQRRRUWYZYXYXXWVTQONMLJIHFDB@>;977531/.,,,,++,,,,,,/C_WSXch>*-3@LPLLLMNRVYVTgkkhcbb^[J+('&%##!!""#$&)09ENQRPOONMJIIHGGFCA<7100001567666445554105I^owyye0*izmnmnrxw\XZ[\]a__G65531-.04FJILelSSSRSW]]acb_\\\]_^ZYQNC:7=O`g`ij`\\\[ax~urpsqqlLBEIMQW^djnuyvk[D0-.----,,++++,/4;;=@Nbv{pe_DFciea^Scmg_VL=@;9<@KY_acefhnrvyz||||}~}~ ~~~|zwurppnlkiihfffhhijov TSRPNNMKIGFDCB>==;9:8677466655444444444444444443333345442222331133222211000///..--,,,,,,,,,,,+++++++,,--------------------........013355778:=@DDFHIKMMMMNNOOPSTSTTTSSRROMMKIIGGFD@?<<:75421.-,,,--,,,,,,,-.5TaVX_fa0+/6ALJFGHGHMVUSXiopcZ\daWC'%%#!!""##$')/:KUTRPOOONJJIIGDDCA=7300001446777;895653560.07?EB5,jxnnrqpzz]XZ\_aa`aO@74430/--26>ABJNQWXUTUZ]__ZZ\[]]YTQMGA<66=O\^V`lb]]]\l~uqqrsp_DBFHLOV_cgr| tgV=//......++,+-.07<@ERj|pdcP=^gbeehhhhlolbODKQQYafgknrsx{~~~~|}~ ¡¢£¡£¢ ~~~~~~}~{yvtqonllkihgfeeggijpw UTSSPOOMKIFFCB@>=;:8977765665544444444443344443233333332222211111122221100/////.--,,,,,,,,++++++****++++,,++,,------------......//01115577:<<>ACDFFHJJLMLNNNNPQPQPPONLLJJHFFEDCB@?<::86420/.--,+----,,,,,,..?a`WX_gO(*.6BMJGIEDHKMTW^mqf_`de^T9!$"! """$',1?P[YXYVRUQNJGFFDA@?<9400001244668<><9557754410/-.+1evmkoqpw{cY[^^dffP>8;84422101347:BKPX[YZYY]]]]ZZ[\\YTPMI=8656=KSTQYhe[]]aw}vtutsqR?BFGKOV_hou}sbM5---....,,,++-,27K\dtylcbcZ^eccefiiggjooppokdgmrsuuyz~|{zxy{|} £¦ª««««¬®°ªª©£ ~~|~||}}{yvtqomllkihffefggijqz XWUSRRQOMKHIEC@>=;;9977765554444333333333322333344222211111100110120110011//...---,,++++++******))**++**++*+++,,,,--,,----..-.////01333356799;=?ACEEFHIIKKLMNNMMMKKJHGFDDBCCB@A@><;967410/.---,+----,,-,++--4PeYWX`g=#,/6CKJFDCBEFHQWerjihb]X\S2($!! !"%)/=S^^``\[[TQKFBAA?>778821001233478;@A=8875544320-,-6lwljonnwzh\\[]`aW:(.7;9520036435:?GPUXWTVYY^^]\Z[]^^ZTRQG;7524:FMQSVdf]^[fzwvtstvkK=BEJQU\ky~ym]C2.,//00...++-..4Caw ug`_aabacddggeehnrrvy~}||~~~|{xwtrsttt~¦¬¯±´´´¶·º»»º´³²®©¥~zzy{{{|} ~~}}~~zxurqnmkjiihffffggijs|YXWWUTSPNMIHECA?><<:877765554444333322222211222233222211111100110120000000//...---,,++**********))****))+++*++,,,,--,,--....-.//001122223668::<=??ACDEGGIIIJJJLLIGFFDCDA@A@@>=><<9864520//..--,,----,,-,+,,-/;ZaZUYed3&,/9CKHEECAAAHPZkuwp^PU`bJ( !!!!#%+6N`bedb_YOHA8500277569842111233567:>=:8875544533,-Bmumjmqqvyga\`^aX:))-07:952028755:?HORX[WOOVZaa^\Z[^]^ZXVWNB<655<FOUSR^g^Z\mxvustsqvtSAGLMP_s}~sfW>149;;<862-*+022C_y |ob_baabbdeeeggjkprtx} zxwttpqonnqu«±²·¹¹¹»¼¾½½¼»º¹·°¬¡ ~|ywvuvvvwy{~ }}~|yvsrnmlkkjihffhhhhimt} [[ZXXXTQONLIGDB@?=<;88875555323333332222221122000111111111//00001110//0/-/..--.---+++*))))**)((((()()*+++++++,,,,,,,----..--..0000000012445788:<<=>ABDDEEFGGGGFEGDCCCAAA?===:<=;9864332///..------,,---------3Hg`WU\g_)&&/5@HGFDA@@BIT_nro`PT]^W;!!""$'.D]ccdc\ND7,(#!#'(+.15:<943347679;==@:897565445307b}xmgkqsuw`Z\_abS-&(-/36477418;899>FOTUWWTPPRW^___\\^\[ZZ\\XK?<89DNTUVV[daW_v}yvtprrjjwxXEIMVcw|qcSEGLPPPMHC@81214Ed|xk`_cbcaabcfghjossu{ ~yvspohfimpmn²³¸¼¼½½¾¾¾½¼»»»»¶°§{yxvsrorpprrvw{~ ~~{xwsplllljiiihfiihhilu[[[[YYVTRNMJGEB@@>=<998755553333333322222211220001111111110000000000100//..-,,,++++++)))))**)((((()(')*****+++++,,,,----..--..00110000124446889:=>=>?ABBCDEEDDDCCB@>>===<<;;;:::77522221///.----..--.-...-,,--3ShZWY^iQ#%*,3>FHDCB@<AIRfppk`[^]]T+!!"%*6QdbceZF0,)%$"##$$&(*/9;844447;==?BBE@;:86766750Ku}tkimqvzua[_Z_gK((+.-1038:7<FE>;>>GMVXTSSSPQSQUZ^^]ZZYYYX^d`OCB>?FRUUUVXa`[i|wususj`bkuxXFKXo}~zzxsid_bfffb]VQJA846Ml|rdb`bcbb`abdglptw{| ~}}}|xsnjff_hmpko¯´¸»¾¾¾½¾¾½¾½¼»»»ºµ¬¦zz}~zxvtromkhhjjljoy}v{ ~~zwupnkjiijiiihfhhhhjpw[[\\[YWVSQNLJHFD@>=<:9765542223322221102222222111100000011000000//0/....---,,,+*++**))(())))(())((()()))*****++++,,,----------/1..0011223333667799<>>@@AAAAAA@??=>><;;::::999866555221110000----.....--------./8Wc\XYcd@!%)+1;FIB@@>=@EUjsrmkihieD!""#'.?^iedbL3*+)%#!#""#&(+/79666679=@BCFHGB?;8766543I~yrmilqsyud[^]aaD$)++-.047:=9>JF<9=ELPTXURRRNMONPSV[ZXZZ[VTZa]NHFCCDLRUUVV_`ar|xwvtvtd]`glssVO^s }qotvy}zxy{}|zwrkf\VKFK[q |xkaadbaccehiknquy}} ~zvvwvvusmhb\_dacgm¡«³¸»½½½¾½¶¾¿¾½½½½¼´ª styyyz{zxurpkgffijhdemnw{ww|{wtrpllkkjjiighhhiikq| ¡¡^^\\[YWVTROLJHEECA=<:976654222331111111111111100//000000110/////./..--..,,,,--**++**))((((((''(('''()))))))******+++**,-----..--//001122445566779:;<>>>@?????>>><;:9998888778866553221110000/-..-,---,-------./0?_cXVYba.#''+.7CGFBB>;<DXv}sjgbnt]/ !$'1Hhmml]7*-)'%#"!#%#$&*-46666679=@DFHHGC@<985441:suonqstyyb^`]agI%%*/9:78>>?A>=FD?=@FMNLMLKNQMLNOOQRSVWW[\VTUWUNKKGEEHNUUVV\dj}|vusuuuua]bbekso_i{xkhmtrx {tlf\\_m{ }tf]^_bdcfgklquw{ wnjjkljd[WTQRUSW`¡ª¶³µ»¸¸¼¾¼½½¿½½½½¼¹¦}imqtuvvwvuronkidipopkfltsjekpuv }wurqllkkjiiighiiiikq{ £¤]]\\[YXWTRPNLIGDB?<;:987643322111100111100000000////....//..........----,,,,--++**)((()(&'((&&''&&'((((())))()****++++,,,,--,.--0000112222223456778;<=====<<<<<;::::9988776888655533210000000///..--,,.---...---1Fd^UV]hV%&'&(+3?IGA??<:C^qjjbapsqM##'3NmpqnP--,)$""!!"#""$),0456678:=@DHJHGA>=875110^xtqtuz|~gY_]bgU&,)-4=GEHLOIGDEIEFEDEKLIFCEJKIIKNRTQQRUYY\\URTQOPOKHFELSVWUYds{wrsvvvwr_[__agkqtt yecholm{ |wolqx{o`Z_`^bgjnqsv|}~wz}|{sfZZXTPMWPGQ^jl£¨µ¹»²¬¾¾¿¾¾½½¼½·¢rYcilppqttttrqpnkgjonoppmqvm\[`emz ~xurolkjlhhiiihhhhjou}£¢^^]][YYWVSQOLJHDB?<;:987643322221100////../000//....//////------/.--,,,,,,,,,,****((((('&'(('&''&&&'((((((()**))**++++,,,,,-,,.-..00112222223456779:;<<===<<;;97777777776654446533332111000000////..//..--...--,.3PgYRXbeF#%$#$(.;EIF@?>>Hcpncekhkf8!$(1PopokG.,)&#" !!"#"!#(+-14679:<>?AEIJIA=>>:50/[yxsqtx|{~pV\^`h^,),,0:CDEOQSXSMQOCINMGFGEDCCDIIHGMNTUTUVXZ^_YTSQOOQOLHDHQVWX[fuztrsstuwr_[\``ago|wdbdljbq} {m^[^^^einrwz| {ytquy|vvld[T^aFDl§²·¸¸¹¾¦w»¾¾¿¾¾¾½²]RW^cgkloqtssrqqroloromkkloooaORV`t }xtpmlkihhhiiihhhkpw ^^]][ZYWTSQONJHEC@<;:976644310110000//.....0////..........----,,--++,-,,+++***))))('')''('''&&&&&&''''''((((()))(***,,++,,,,---.111122113333335556798:::::8:::888777666666544443333222221111000////.//.-..--.----/6[bRQWab9"##""',6BHGCBB?PgbXZ]gknZ("%-Ffmjf@+,'&#! !$%##"$(+-/336:<==>AFIKC??A<25`wrlstxz||u[Z]]dg:++,-.6=AGOTXfbWICDMLIGEB@ABBCGIGHJNSVWWYZZ]a]YUSRRSNMHDEMUXZ[fsvqswxvuvq`[^]`dgstbccii^j{ whZTX^bhkos{~}zzzug\armnge^_qsIZ¬±´¹»»½¾¼»µ¬¸°³¼¾¾¾¿¾º§uHEORX\`dkoppprrqsvvrrxtlgginrriRAMWo ~zwrnllihhhhhhhhimqz ^]]][ZYWUTQNLIGEDB=<:9766433211100..//.....0//..,,........----,,,,+++,++***)))))(('&'(''('&&&&&&&&'''''''''((((()***++++,,,,--./111122113333335556778899999888777666666666444443333222221111000/////00/...--.-,,+--=b\TUZ_W-!"!#"(2BJJHCBAX_OLOW_hkC#)9YhibA,*&$" !$$#!!#$'+-0369<==>ADHIGCA?8Lpvmmlpvvyzs_Y]\^eI)-,11059@HOWakhWJGJNNJEA@>>??BDFHILMOSVXYXZ[]^\XXWWTONJEDGQWYZervsvzyxuwr_XY\_dkx~qbacfg]h{~vh^WUajghov}}}ytonj]SIennpuhU\jLo¦´¸»½¿¿¾»´³·º»½¾¾¿¿½»°dFKSQOQUZ^bgjmprqrruyyrswqppoqwvshKEYp |yupnlkhhhhhhhhimr{ ^^^^[ZZXVSQOMJJGEB@>;96664331111//.-----....--..,,--..---,,,-,,,++++++++***)**))))(''&&&&&%%%%%%%%$%'&''(('&''))((******+,,,.../00112111223334446655777787777777666666777766664333333322222211100000//..----,,,,,,+-GdWRU[cV,!"! "'0<MROKGKa]QJKMYnc.%1Rd`V?,&""""!!#%%##""$(),.2449:<>?ABAB>;H_tqljiputvvq`Y\]^f_)+/0325888?HXil_UMMPSUSME==<<<?CFHJLMMQSVYYYWX\[ZYXXTPNIECDMSW[ennpruvsuvng\X]_hs oaaabeal{~vlb^_gossy}}{wutskqo_YpsmklsjZX[Wuª¸º¼¾¾¼¸±ªµº¼¾¾½¾¿»´rQKTTQMNNNSV\`dilooppsyvwwssmiprpoaBKp ~{xusoljhhhggghjms~[[[[[ZYWVSQOMJHEDB?=;97764331111//.-------------,,------,,,,,,+++++***))))))))))))'&&&&&&&%%%%%%%%$%'&''(('&''))((******+,,,.../00112111223334445555777787666667777666777765664333333322222211000000//..----,,,,)***0QfUPV\b[-! &+9HQTMIQe\NNPSetK!$/NaYUK;'""""!!#%%$##"$')*,/14668;;;<;7Icrsmfgjopu{wmaZY\^`d@-,/02589736AR\UKHPRUUWZUL@=;::=?DGLLMMORVYYYXYZZXUUUSOLIDABHOVXbjjjlpsqsrke_YZ`o{oaaaekiq yrnjhny~ }zwuvrrronpqrnoqmkhfYS_c|©·º½½»º¶¯¥°¸»½¾¿»¶©~UMRRQOLMNOOOQV[_cgkpqqrt||wqsxqj`mvxwn\ay}} }{wtqmlhgggghhimu~ZZZZZYYWTSQOMJHEDB?=;:9764322000//.-----------,,,,,,,,,,,,,,+**(++++))((((('''(((('&&&%%$$$$$$$$$$#$&&&&&&&&''(())))**++,,,,../000112222233323444466666677666667777666667766554333333333221110////////.-----,,++,,+()5_bSPU\fa2 "(*7CRTOJ[i[PKQZpk5 ,Jc]TMG*""""!"$%%&%#$$',+**.//1269841Owpmgdfimppwpmc\YZ]^`Q1//2258<;:8<=KE=;EQWVXZ\ZQF>;;;;=BFLNNMPPUZZYY[\YUQOOLKJHDCACKRW`gghjmompmdcb\Zdssb`bhru} {yxsrx||zywsqpomppkjknpqmiejc^c_v¥µº¼¾¼º¹µ£¬¸»¼¹±eJLSRQQQPPOOOMMQTZ^agipqrtssusv{|tq}py}y||}~ }{wrpnjihfgggkox\\\\ZYXVUTQONKIFC@?=;:9764322000//------,,,,,,,,,,,,,,,,,,++***())**)()))))(((((((&&&&%%$$$$$$$$$$#$&&&&&&&&''(())))****++,,-./000112222233333565566666666666666777666667766554333333333221110///////../----,,++,,))'):d`USV_id4!"##$%+3>MSPSgnZNNSatX#(>afYMB*""""!"$%&'%&%%(0/*))**+++,.,NrqkhgkooopviXea\ZZYUX4..366679;78@?B;19EORPRUSQJD=999;=BFLNPRTTUWYZ[\\YRLKIIGHEDCAAFKT]ddehklkmj_`hd`hwub_dkv{~~ ~}~xuqmmrsppqonmlklonnncY`ed`ZU`´¹»º¸¸¸´®°©²º¸¬QLMRRRPOQPPNMMNLNQUY^chlnrqprvx zy zx{{{|}} }{zwsomjhggghkpz}YYYYYXWUSRPNLKIFFD?==:8764332000/.---,,,,,,,--,,--++,,++******((**))))))))(((((('&$&%%%%%%%%$$$$$$"#%%%%&&''&&(())))***+,,,--//00000123323444456666666555766666676666677666655543333333322221100//../...------++,++('''BkbTRW_hc8!#$$$()*6FQTXliYPTXhsA)3FOJ</%!! !!"%&'&$(&(+/,('&(&$$!*Lrnmhhlmnmpr[Khf`\ZYQYL*.1699788:75786328DKMKKMMHEB>:88:>AELNQTWXVVXZ\]\XPMJLJFEGDEEDAFMW`abehiikfZ]ghjp} wd_fpz }{voga\_chklpqroonllnmlU@MX_[H@Y°¶¹¶¯µ·«¯±¶¶ªNOQOQQSRRSQPNNLOONNSUX\bfimoqt| vvyzzzz{~ ~zuqolkjihjls| }|YYYYYXWUSRPNLKIFFDA?<<8764332000.----,++++****++,,++**))))))))(())))(()(((''''&&&&&%%%%%%%%%$$$$$$#$%%%%&&''''(())))***+,,..-//00000122233454456666666556666666665666677666655543333333322221100//......----,,++,+++(%#)HmaYWW_ggC!$%&%%$'/;JP\teWUW_rj2,,()%# !! !!#$')./,,(&+)('%$""$%WtmollnokmrrF>hle\XZPXW0+159:<<;>;55463.39DIHHFFEDBA>:88:<?DGLOSXYVVXZ[\XVPJKKHGFFFGGGFCIPX]`dgijjcX[cinw yd_ft}|}uhXLIORT\`bcfmpoonnjeN=R_^V>3Np¥´¶¯°¦²°± oMOT]WSTXWURQQPOOOOOPQUX[`hq}¡¢ wuyzzzz{|~ ~~}xvtpnkihilt w}WWXXWWUTTSPNMKHEEA?=<;98532220//-,---,+*++++++++****)))))*))))))(((('''((('''&&&%%%%$$$$$$%%$$$$$$%%%%%%&&''(((((()))*++,,,--/////00122244455566666666666555555556566556666666433333332221/111//00......,,,,++,,+,*,,(%%*KmbXVW]diK##$$$$"! &2?FkwcXWUevP#''%#"! !!! !##%.9@<:/(++)&%#"#!=spklonmposk:+`kid\[UK[A)/49::=@@=86542-149@DDA=?ABAB?<:98;=BEJLQVVTTVXYZVTOJKLKHHHHJKJHDDIMV^ceefheWXdmt}h_jv |zm[N\a\]^^ZZXY`ejmmmeWG=GJKNB?M^¯²R¤}³·ª v\MNRXQTYa]TUVVTQPRRPNOPR]n~¢£¢ £~xvxxzz{{z|~ ~}{wutrollkny }rxWWWWUUTTRQPNLJHEDA?=<;98652220//-,,--+*+************))))))))))))(''''''(((((&&&&%%%%$$$$$$$$$$$$$$%%%%%%&&''(((((()))*++,,,--///11001222444555666666666655555555565665566666664333333322210011////....--,,,,+++++++,,)&#(/Mm`VUX^fl[&"$%#" !)3HlqbWVZhp2&#$#!! !!! !$"(.3782/++-*'%#"#-hnkjjmpllta.5_kigc`bJ[Q++<>;:=?D>7559830258<@=:7:>>>?>=:97:<?DIMOSSQQSUVWTRNJKKKKIGJMMLIFACIOW\acffcYZepz}h_ly ~|xy~ ~wod[mm[RWVY`\XXY^dffc\UF51GQORZ]z¢ª_s²µ¨vVIILOOOSVVROTUTUTRQQRQNMa~¥«°¯ª§¡ywxxzz{{{{|}~~|zywtromnqzvoxVVVVTTTRQONLJHGECAA><:87543320//-,++++**********))))))))((((((((''''''''''''&&&%%%$$$###""$#$$$$$$%%%%&'''''((((()))**++,--.-.000022223433555666666666665555556666666666666666443322111111000/0.//.-..-,++++++)))*,--+)%$'-MobUTW]dl_4!$#"! !%*Epn`YY`rU#%"""! "$(-/2761,-./,)%$""XvmggllmukO3Cahjjhd^L\[5.:@?@@BB>835:@B<4379976567::<><<::78;>CIOQQQONOQTUSQLLKKLLKKMNNNLGDABFOT\acec\[iv ~jbp{|wqosy| xsssrqigjhiicWPQW[ZVTUUOCCTSVY]get¤ £¯¯¦|WJHIMLLRUQOPPRUWWUQRTOTf¦²¹¼º¸°©¥ ¡{v{zzz{{{zy|~}}~~}}||ywrqppu} {rnw UUTTSSSPPNLKIGEDBA@=<;7574331///-,++++**********))))))))((((((((''''''''''&&%%%%%%$$$###""##$$$$$$%%%%&'''''((((())**+++,-....00002222344445666666666666555555666666666666664444332211111100///.//.-..,,++++++*)()+++*(&(+./PndXTWZ^ffC"" !Gwm^VZeq:!##"! #&-2111,+,-12.*'$#<sjjgikpt_<;ZegjfggaMV_F29E?DCEC<;756>FEA955564332599;;<<;:99<AEJPRTRMLMOPQQMLKKKLLNNOOOOMGDA@BGKRYaec[^o| |jcq~~ypggmsv{~ ~vrpppqstqoc]aSEEPWTNHINPMKQQPTSfa`|¥¨©¬ªmLHHMNMMNPPSTTSRSTRQQPUnª·»½¿¼ºµ¨ ¤£¡ |u{{zz{{yyzy}{{|}~~}}}}{zwtrrxxnmx TTRRSQPOONLIHFDCA?><;:866532100/-,,+++*)****))))((((((((((((((((''''''&&%%%%%%$$$$$%$#########$$$$%%$%%&'''''()))))**++,,-.-./0101223345554555557766666656665566666655555555444433221111100/..--/.-,--,+++****)((**+-,)'+,.0/HkcVSTX]_fQ/!!#Uzg][^j`%"# "! !! #(*,*++),,-461,(%2lnffjijp]<HgfaabcccWRVI65=;=C??=97532:BDB<543230312589;=>=;:<@DJMPSVURPONNMLKKKLLMNNPQPOOMHFCA@@CHRZaa`fx|y{ {jes~vl`_dkosw| }|zwtqqqpqqn\9489<DINMKKKMIJFMRRPllj §¨©¨UFGHJJILLOKKPOMMOPPQOXw¸»½¾¾¾¾¼´¤ ¤ ¦¥vz|{zzzzyxy|~wwy}}|}}}}}||yxtrz}uonz RRQQPOONMMKHFECBA?=<;976653210/.-,,,++*)****))))((((((((((((((((''''''&&%%%%&&%%$$$$##########$$$$%%$%%&''&&'())))**+++,,-.../0101223345555555557766666656665566666655555555554433221111000/.---..-,++*)))))''*)))*+-,)),,-013Egj]TRSY^dbF$!!!"'fxb[]`nJ""! !&(*++***--.120,,/cpjedhkn_M`jhdaacb[_[J?47899;<:97565108ABB=6310//012237:;=>>=?DHKMPRTSSQPNMKKKKKLLNPPQTSPOLIFCB@>?@GQU[bm|}vqu {jiv ~}xma]`eimqv}~|{zwvtpqqpqnM)5688<CDJPSOKHKMVZZV^_l¨ª§¤wDHIIKJILLMJJLKJLMLMMTs¯¸¼¼¾¾¾¾¾¼¹³¤ ¥¥¡|z}{z}|zzxx{}vsuwz{|}}||||{ywtzyqon{ QQPPOOMMMKHFECBAA?;;986654320///-,-+,,****))))((((((((((((((''''((''&&&&%%%%%%$$$$$$##########$$$$%%%%$%'''''()))***++,,-,-./01123223455555555445577666666665555555555555544333333111100////...-,,,+,,++****))))())-/0.+*+/3477Bcn^SQSU]cj[1 $$3lrb^[am0 "!""$(*,*,+++./012/,.`oghgijphdjkhe``acb]VH8205768>@<84364107AFDB:430.013454679<>AABEFHKNOOQSRPNLJIIKKMMOPSSUSPNLIFDD@AA>AEKTarznkvyjjw }zvkd_^_ehmsy |}~~~}zywvuutuvkR/.:<>CGKGGHOPQXXV\`aeidm§©¦ mAFJILKJIJIJJJJJJIKJSl®·¼½¾¾¾¾¾¾¾¾¹µ®¥£¡££z~}{{{{yzxxz~sptuww|{|~~~}}{yv{ uoon{ OONNMMLLLJGFDB@@@=;::76643220///-,++,,****))))((((((((((((((''''((''&&&&%%%%%%$$$$$$##########$$$$%%%%%''''''()))**++,,,,-..001132223455555555555566666666665555555555555544333333111100/.....--,,,+++**))))))(()))-/21/-.147:=>>UjeXSRVX_ihN% %((;qr]_akb "!!!$(*,,.---./0110,UrfcdhkpnjhgefdaabcdfY51333459AGC94451104>EFA:51/.013566779<>BBCEFJJLLLNOQSOMLIIJJLMPRTTUSOMJHFDCAAA@>?BO[p ~zqhltymlx |{|vkb_\[_`fmu~ {vqpqou|~ ~|{|{{{yxy{wgJ@AFP[^XKMX\]^\Zadefkkc{§§¥cCHJIJKJIHHIIJJHHIIOfª·º¾¾½¾¾¾¾¾¾¾¾º¶«§£¡¢¢{{|{{{zxxy{}nlnqttxy{|}|||{zy{}uoon}LLLKKKIIJGDBCA@>>;:87755421000/...,+,,****))))(((((((((())((''''''''&&'&&&&%%%%%$$$$########$$$$$$%%%&''((''()*****++,,---//0012433334455555665555555555666655555555555444333333221110///..--,--**++**)(((''''(((()-1442/236:<BEA>Idm\SQQUZfma9"%',,Htjc``pF! "!%&+-/20..000221LrhbcejppigdddddcefdjbE1255348:DX]93332103:EKF;53.,/13567879;>BCDFFIGJJKLNORRRNKIHHJJMOSTURMKIGDCB@@@A@@?FSiz ~}|ujcivyomy ~||zufefed^^ait zvmbaees{|}|}~~yphdehiidW[`a``bcfgjkohbl§©¤SCFKKIJIHFGGGHJGGHI]£²¹¼½¾¾¾¾¾¾¾¾¾¾½¸³¥ zyzz{zyxxyy~pjllqrtvy|~|z|||z}|tnon|KKKIJJHHGHFDBA?=<::877663210000.--,+,,****))))(((((((((())((''''''''&&'&&&&%%%%%$$##########$$$$$$%%%&''(())))****++,,,---/00112433334455555665555555555666655555555555444333333221110///.---,,+**))**)(((''''''(()-167524:<>BFJBC@?VkbWQPS[_gn^4$+,-.Oxj__`l4 "$&&)-1232244441IsmfffinphedddddeegddYC7534437;?CLN723322128CHF=830../0156669;<@BDFFGFGHHIMPPQNLKIHHHHJNPSTPMKIGDCA?@@CDCAAHYjt|~}|~|wnbbkw{omy}z||yulhjhe`^^iv }woeWNQXmvy{}~}wqssqnjdW_a`__`eeghlogcg{¥¥£JEKJIHFHGGGGGGGIHFLn¯¸¼¿¾¾¾¾¾¾¾¾¾¾¾½»´¬¦¡ {yzyyzzyyz{qjggnqpsvxz|z{{}{~ ytnno}IIIIHHGEDDCBB@?><;98866543000//-.--,,+*)))**))((((((''))))))))))((''''&&%%%%%%%%$$$$$$$$$$%%%%$$%%&&%&'())**))*,++,,....///011223344446456666666666666664455545564444433422222221100//...-,,++*)))))''&&''(('&%%&')+16:976>?BHLMJIC?<PbeZRRUX\_inR1)))))SsbZ`j[ "$$&***.024545775=ikhhkklqgdeghhcdeih^G@B332227FD@732121111466:@C<;6..//1234569;<=ACDEFFGEGHILNNMMKIHHGGILNPROLJIGEDC@@@BDDDDEIS`ktxz{{|vqh^`jw}on{|{{{|{skjjhebcn~ ztf^MCDVmswz|~}~tppmmcX\___`abdfgeckgdiu ¤ G?GIKIFFFFFFFHEGFFZ¨·¼¾¾¾¾¾¾¾¾¾¾¾¾¾½¹²¬§¤ zyyxxxxzzz{qgehhmoou{|}}{{{z~}yrnmnGGGGEEEDCCA@@?=<:987755443000//----,,+*)))**))(((((((())))))))))((''''&&%%%%%%%%$$$$$$$$$$%%%%$$%%&&%&'())**))*,+,,-....//011122334444566666666666666644445554334544443334222222100//..---,++*))))((''&&&&''&&%%&'(*/7<=;8<BHJLLJIED?=GXje[XXXY[bhfR*"()(Ztd]blL&$&**+-0378:;987fjgikjophcfggghfgilWA?B7353337CKE731///0124699799951.././025789;<>ABCEEEEEDGGHHIIHHFFGGGJOQROLJHFEDC@@@@CEEEEGHP[dmty{wqfb^_ky {oo{|{{{{}}ypoljiffq }ztgZTKHPctvvy{}~wponj[_`a``cacefgc^efhnqF@FGHGHEEEEEFEEDFOt²º½¾¾¾¾¾¾¾¾¾¾¾¾¾½¹²¬§£ {yyyyyyyywy}rgeggjnnsvyz|{{{z |xpmlsEEEEDDBA@@A@><;::876745422100/...-,,,,+*******))))))))******))))((''((&&&&%%&&%%%%$$%%%%%%%%%%%%&&''''()))*****,,,+,././/111111233443444555555556655554345555334443333332333111111//....-,++**)()(('''&%'%&&&&%%%%&*19?BB==FKKLJJHEB??A;I`jdYYYWY]`kgA&&&(bvdbdj:!(+*,--145688?jkgiigltkdfghhiill_H:BE>3153016A[R520/..01348<:966642-,---/04798;==?AABBCCDCCCCCCCDEDEFFGGJKNQLIGFEECA@?@CDGGFFIGMV_gosqgSX_bn{|qp{~|{yxyy}}tonnkilv ~~ysha[WQQZhsuwxz}wpljb]`abbbacdfgd^Ydfemrzx@BFEDHGGECDFDDCEJd¬¸º½¾¾¾¾¾¾¾¾¾¾¾¾½¼¶²¬§¢ }{{zzyxxwvw}sddgefkloswx{{{{z{yutw~BBBBBBAA@@@?<;::9765554322210/...-**+++*******))))))))******))))))((((&&&&%%&&%%%%$$%%%%%%%%%%%%&&''''))))*****,,,,,//.//111111134443455555555556655554445554334443333222233211110/..-.-,,+**)((((('&&%%%$%%%%%%&'(+/:BGHC=FNOPNJJEB?=<:69Mhi]XVTUY]ciV2!$,`vccbg1"*,0321378<8=iqjhjhiolfgjjgjnn[B9:?DI;3442016?PI30////276769<<9853/++,,--/159:;==>@@@@>?A@??=>>@?AACDDDDEHJJIHFDEECA@?@CDEFGFHGGJOX`fcVIO^en| |pqz}~}yyzzz|wtqonlny~~~{xriea[WW]isuwwz} xpgc``abccccdfffa]]hhhkruu<BEFIGFGDCDECCBEW¥´»½½¾¾¾¾¾¾¾¾¾¾¾¾¾º¶²®©¢~}{~ ~{{zz{zxwvw}wgefgejlopsuy{{}| @@AA@@??=>><::998744343322110/..++,+,++)))******++****++++++******('(('&&&&&&&&&&&%%%&&&%%%%%%&&&&&&''')******++,---.///0011202224445566666655555555555545543333333333222122111000..----,+**)))((('&&%%%&%$$$$$%$&&*.8ELPIDGRTVRMIDA><<992.8VebWUSVX\^ggK+'craeh]'*,27=CKLI@IjsmjmiipnfhiikngZL;9:=?@;642200467;71/11/037:97:@A=:53/++,----/2669;;<?=;;:;=><;;=>>??@ACCBADFFGGFDCBB@?@?@BCCDDEEEFEGJQUQJEGWfs~ympw{}{zyyyz|~wqqoor} z{z~ ~|}}}|uqmida`cbisvww{|ypebbcdccccccfdcc]^ijjmrstr>AEDECCEDDDEDBDKm®¶º½¾¾¾¾¾¾¾¾¾¾¾¾¾¾º¸µ®« ~zy~~{zzzyxxwvu|zhehgdejnnqtvy|}| @@@@????<<;:99887666533322110/..,+++++*)))******++****++++++****))*((('&&&&&&&&&&&&&&&&&%%%%&&'''''''()*))++++,,,--..//0001111222344556666665555555555553454333333333322122211100/.--,-,++*))(((('&&&%$$&&$$%&$#$$&)-8EOTRMKSXVSOKGC?;:732/.,=XgaYTSVX[`gbB&$^oaeeV:85=@ILQXcrrlmljkppjiklojSEFGHCABA>84420//36763.-.11149=;7;BD@=71/+,,----/14678:;=:889<<<;;;:::===>?ABCCDDEDCA@@@AA??@@AABBBBDCDDCDEFDEEShu tmqxxzzzyyyz{|xurqqw {zy{~~ywxzyzwxpnlmmlhmtwwx{} unjfbfeddcccccdb`_floljpxyoCBGDEDACDDDEDBCW ¦³·¼¾¾¾¾¾¾¾¾¾¾¾¾¾¾½½¹¸²« ~vuyyyzzyxwwuvyzgeedefiklnrvy}}|????>>==>;;;9788767643321100.-..++++*+++********++****,,,,,,,,+**,+(**((&&''''''''''''((''&%&'''(()))))***+,,,....../0/00010022223445555666655665566554444443333332122221101000/..--,,+++*))((('''&&&%$$$&$#####$#$&+6ALVZVQSXYXTNKHD?<8310./,,Dah^VTTTX_chcE,^i]chP+7<ET]djosjjlmmppllmmgP<BMQOHILIA:5432.-055540/,+253389989@DCB<51---,,.//134468898777;::98989::9;=<=@ABDABBB@?@@>>????@@AAAAABBBCCCBBBETiv{oiovvwxxxyzuuvyvtrx{xxx| xpptwuwvusrsutqlktwxx{ }uspkfcefffdccbc`[]cgkqnoru{pFCCEEEBABCDEFCJj¬µ»¼¾¿¾¾¾¾¾¾¾¾¾¾¾¾¾¾½»¹³¨£¢ ¡vuwxyzzywwwvxz}heecfdghknrvy}{} <<<<<<;;<9888755656532211100.---++++************++***+,,,,,,,,,,+**)((((''''''''''''''((''''''''(()))))*+++,,,.../../0000010022223445555666655665566554444443333332122111100000/.--,,++***)(('''''&%%%$$$$$$#####$$%)0:FQYXQQXYXXSOLHD?;752/.-/*1HeiYSSVXY\dibVjqcdbJ8K_eggntlhjlmppkml]=-5I[]TOMEGG=74230/,0:=621.--03668::89>AEB@93/--,,-.0222455567778989:988888889;;=?@ABBBB?>??>>>>>>?@@AAA???@AAB@BAJ[ky{{|}yrkipttvwwwyxrglzxvsy|yyy| xoimmopsvvuuwvutpmrwz{ ytpnijgffecccca]Z]cgmuuuomppLDDDEDACCCDDEFV£²¸»½¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¼»¸¶µ±ª¦£zuwxyzzywwwwwz}hedcdeghimqtz|{} <<;;;;::998876776655211100..-,++**************++**++--,,------,,,+****))((('''((((((((((((''(((())))))*+,,+,----..////00011102222344445556665555555555444444333311110000000////.--,-,++***))(('''&&%$#$$##"#""""###$&-6CMSTPNUZ[[YTMIGB?<732/+,,,+3Ui_ZWWWYYZbjqutponligeblrjhgjnqrnj\>")3G]_ZRMB8EE;5220/./7HL<31//..3789;867<BFCA<71/-,,-/1324435555666668777799887788;=>?@@???>?>=<====>?@AAA@@>>>@@?BIWcrzwvzyqj^blqsuuvwwwqcWn}yx{}zz{| |sb_dghlnpsuxxvtstttuz~ {wxusoigfedcbb_^Z^fmtwvuplivpQGFFFDCBCDEEAJdª´¹½½½¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾»°«¦zuwyzyyywwwvwz}lccdddfhjmrs{z|~ ::::;;::99776666554431110/..-,++,,************++**++----......,,++++++)))((((())))))))))))))(((())))))*+,,,,------////000111022223444455566655555555555444443333111100000////....-,+++***))(('''&&%%$$####""!!""""!#$)3>GLIFIOY\^]XQNLGC?:74/+*)+(*+<Ye_UZUVWZaefjjjgefb`jspihkntwo\8'-6/Ifg`]WO76JC851///..2:9421.0111567;857=BEEC=82.+,,-/024543444445554566779988777788;<==??>===<<====>?AAAA??>?>>?AIT_mwvtvupeXOUafkoqtuywn[;2m}{~}zz|xiYY]`dfglrvwwvvuutwz| ~zyzuromgddcb__^]ajsywvupmko~YDGFEBBBCDEEANs¬¶¹¼½¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾½¾¾¾¾½µ¬ xnpsuwwyyyywwwvwz}kccbddfhjmqtz||~ {99:::9::8888666644442011/..--,++****)))***+++,,+++,,--........-,+,+*++))**)))))()'((((((**))))(((()*++++,,--..--..//0000001101222233334555666655554455333444222211110.//00//--...-,++**)))((''&&&%%$#"##""""!!!!!!""#'09CEA?BMWZ__^WUPKGC?90('('&&&'#*C[_YWSRUWY\`bccdcclwqiglptyh?!#((9Neidcc[L42A9420//.,-/1/011/15412579888=EGFD@;50,++../1344433333344456677667755557788:;>>=<<<<<<==<?@@@AA@@><==AES_isvrrqldUB>FPX\bgmstthYVLf~~}q_QSUZ]`fjrttuuwvvyyz}~||zuupkhfda_][\eipsussnjigr|cGFFFDBCCDCDBR~ ¶º½½¾¿¾¾¾¾¾¾¾¾¾¾¾¾¾¾¾½½¾¾¾¾½µ¥scdeinsxyzzyxwvwwy~ncbbdfegjlou{|} yumd99::99998888655532222000...--,++))++**)*+++++,,+,,,,---........,--,+,,+*++***)))*(**))))**))**))))*+++++,,--..--..//0000001112332222333455444455554444333444222211110.//....----++++**)))((''&&&%%$$##""!!!!!!!!!!""#'-8BGD>BHPW]`_^\ZTNKE?.'&&%((('&$$(@]bTOTWWZ^_acedluspkorvxe;%&(&'=]iiec_UI,+86310/.-*+,./00002672236::89>BDEEA<61,++...0133333222244444456466655555677:;<==9::;;;<=<?@?@??C@>==?GRZgrtqtrkcU>029?DIQZ`fmnh_ehr zmVMOPTW]dkpqsttvwxyz|}}~}|yttnjgc`^^\ajpppqqqmihfiroPLJJEBBBBABEU £°·»½½¾¿¾¾¾¾¾¾¾¾¾¾¾¾¾¾½¼¼¼¾½¼¹©hY_dehhkqxyyyywvvvy~sfccgiijlnqv|}~ ~ztldYQP8899997788875555322220/....--,++++++***++++++,----............--..-+,,,++++*))****))****+)***+*(*++,++++,,--..--..//0000/1111222223322333345445555334433453222220011/././....-,,,++**))((((&&&%%&%$##"""!!!!!!!!! !"#&+4AFFDGIOUZ_bca_\WSM>.))%$$$&(*((($*G]]UY]`acchhmsplkmqswkN4,+&'*C^ibWRYSJ-(76000/--*,0/11121389757:<><;;@CEEB?;50,+,,-.0112332200223344444444554234679;<;978888:;<>==??BBBA?<>CP[fqussng_R@2//0369@FOW_b`[^V]t ylVJHJLR[gnnoprtwyxx{}~~~~{xvrnjd`^[]ehjmpqonligddfkWTPICBABBBAEX¤°¶º¼¼¾¾¾¾¾¾¾¾¾¾¾¾¾¾½¼»ºº½½»· \PW]`dghijrwxxxvvwx|yqprssssttw}~wpg`XNGDHN8888887788755555322220/....--,++++++***++++++,----......//////....-.,,,,,+++**++++**++**+,***+++*++,++++,,--..--..//000000111222223322333333443333554433330002220000../....---,+++**))(')&'&&%%%$$##""!! !! !#&+4?HJJKNPSZ_eeffb^XM4)+(&$%'&&+-..+&%*:KWgljkmnkurmmosuwlSI52<=12LcdZSLMMG0%65/00.---/201212369=;988;<==>@?@B@?=92.,,,-.0112332210113344443333444233568:;989888879:;;;<=@ABA?=@NYcopqsoh\N<.-,-/00047:CINPWYRZi ~o[HDEHQcikkmprxywwy{{{|}}~~}{yurnje[PT\efgnonnligddb`XRPFDBBAAABDX¥±·¹¼¾½¾¾¾¾¾¾¾¾¾¾¾¾¾¼º¸·»»½¹±\EPY\^bfgihltxyyzz| ~~~~ysg_VNEEFJLPS88777777765555553222200///.--,,,,,++++*,,,,,,,..--//....00////......--..,,,,+++*++++****,,++++,,**,,,,+-,,--..--..//000000011112222222343333333333333333320.0111000/......--,,+++**))(&&''&&%%$$$##""!!! !! !! !"%+3=EILOQQT[`fkjfdc[H1,/,()))')*,,-/.01128AQX`cfrroortuteQPO;;HOJO[`XURKKOF1$3500/-.-16855324469?>=;;<@@BA><?B@@=;4/----/1122222222222244321122332121156775677777888899::<=?@=@HWanqqrog\P>0.-,--//011248<BHIKPXj~|y|~s\HBCJ[cffhmryywwyzzzzz{|~~~{zwusole_ahd_dmoonlhfbaa_[NNHFCBBBB>DY¦°·¹»½½¾¾¾¾¾¾¾¾¾¾¾¾¼»¹¶µ·º»¹FEOV\^`ddgiiow{{| }xuqncVLFBA@CHMMPT77777768765555553222200///.---,,,,++*)*,++,,--....--..//00////..----....----,+++,,**++++,,++++,,,,,,,,+--,--..--..//00000001110122222233333333332233333333210011//......----++++))))(''&'&&%%$$$##""!! !! !! !#(.6>EIKPSTW_djnliebV>.-,***--(**,,,**-.0110..((drllkhim`ILNPDCNRQY^ZY][UUTR<'/6830.--17=<:63335;@AACA?@BBA<:<>==<:51.---/11112222222222331111113321//112443345555667799::<=>?>FP]krnolf\N=0.,,,,-////00101359;>AK`f`dhkpqsvz} vcG?HWcfdekpzyvwyz{yxxxz{|}~}||zvsrooplknkkoooljhfbab`\KLOGCBBBBABV£®¶¹¼½¾¾¾¾¾¾¾¾¾¾¾¾¾¼º¸³¯µ»»·§pHLQY\^bdgjjjms{ uld`ZOE@<=>A@CHLNOR77777777665544333222201000/...-,------............11110000000000--..-.0...--..----,,,,,,,,--,,,,,,,,--,.....-...000000000.11110022221111345433222222221111110.00--------,,+,++****))((((&%&%%%$$##! !! "$*08@EHKPSVZbfkonie^O0*)))**-1,,,,--*%#$',0016.Zvjjkolh\UJIJIEKROX]a`aaZX_^ZK/)4A@5.-0:ACB<64469;?CDEHDBABC<78999:8840--,.00111122112222221000002211//01001122444466668989;===@JVdnlnje[M;3/,+,,--..//00//00221236@EEIMSU[achotyz yeIGVdeacisyyvvyyzywtqvwz|}}}~}}{yvsrnnoonmlmnjggebaab]OKSPDBDCBCDRw®¶º¼½¼½½½¾¾¾¾¾¾¿¿¼»»µ«´¸·®o_cdddgjjkmnnry} vkaUME>98:;<>@@DFILLL77777777665544333222201000/.....------....,,...../11110000000000/...-.0.......----,,,,,,,,--,,,,,,------....-...000000000.//00//11111111233133222222221111111/00....----,,+,+**)**))((((''%%$$$###!!!! !$',4=CFGHKQUZafhlkieZB-(*++**((++++,,,'#"%%),2;Zulikjke[MLEGE95JPQW`_[__ZZ[_VS:+/DM9//3@DEE@99:88<>?BDHID??::7555589983.-,.00111122112200000000000011///1001111233444566879899:ANYhmmje\N>520.+,,--..//00//00221220/04468<@GHNV\diouy}|hSX_dbdowxwvvyywvpnnotwz{}}~~~}|zyvtrqqpnnmljfdbaa`a`VMMPHCCCCBDKo¬¶¹¼½¼¼¼¼½½½½¼¼»»¸·±¦¦«¯®¤xzwwvvwwwvyy}} }xngZOE>;:7879<=?@ADFGJJJ77777766665544333222310/00/...----..-------/..//0000000000111100000000//00////..------,,,,,,----....../-..--..//00//////////00//00001122211212222211100000000000//..----,+++**)))))('''''&%$$#""!!""!! !#)/6>EFFFINUY`ehjiigW7.-,-)**))((((*++)(''%&*(IpjjfgecPAKMIDB@@FNXbd\Zb_WQR\YOD:36=2./08?EFB;>@:889:;?HKF?:75654467;;852/--//0011112222////0000////00//./0011112222123466777759CP^hjhf]P?6421.,,,,,./....//00110222000/0//0026<CIOU\bjnrvx{}~qeabfnvwxvuuutrmhggjmswyz|~~}~~|ywuvstrppomifdda___a[KIJLIECD@AGm©²¹ºº¼¼ºº¹¸µ³±°®«¨¦£¡ ¤£ {vqiaZMC=<===<:989<???CDDDDDD77777766666554333222310/000///..--..-------/00//0000000000110000000000//00////..------,,,,,,,,--....../.......////////////0000//0000112221021122111110000000////....----+++**))))))(''''&&$$##""!!""!! !&)/6>DGGHGKPX]`ddeebI-++)+'))))(((((***))'&&'>hfgfgdcN@DMML><JNIW_c__`d`TTZ]aOGB;48879114<CCBB?:99778;FLME=74435567:<><830-//00001122220///////////..//./0011111101111233444448DQ^ggd_UC54410/-,,,,--,,..../011022211100.-..,../37<@GMSY^dhlpsx sghqy|{xwwvqlgd\TV`gmrwxz}~}~}|}{yxvutsrqomjfba___`]PHJIHDACCGQj£°±±±±¬ª©§¤¤¡ }ypjcZOGB;9;;;<==<<:9:=?A@CDDCCAA887766776666553332222210110/////------........////000000111100000000//////////..------,,----,+--......./////................////00001122221121110000//000///..--....---,++*****)))('''(&%$$#$##"""!!!! #%)/6?FHHHHKOTY[^_b`W9'*)'((((()(((()+++*(),$Cl`bbdceP;B<:KG>=EDSda]]ab_c^ab`bTHDHVJ>BG9,05;?BGB:766336AJMGB:53355679>?A>:51..//0011235510////00..////////0000000000000011222238CQ^cb`XJ;4430///-,,,,,,,--//0111111121000/.-----..--.13;?DHLRW^dipuwwrlmsxwwyvspja]XMHQZ]dmrrw}~~~~~}~}|{ywvutusplhfa_``__[NLJEEHPV^jv ¤¦¦¦¤£ ~|zvrnia\ULF>;:9:89;<<==<<;:<>>BBABC@@@?777777776666553332222210000/////------......//////000000111100000000000000////..------,,--------.......///////..............////00001111221111110000//000///..--..--,,,*+*,+**))))''''&%$$####""""!! !#%)/4<BGHIIIPUVWZYZWF-''''((((((((((++,,,)&&=md[[afhS>@;76@F=556Pcccddda_`debcXLDGJA;@A:1.028ADE?634012:BEFB;7445579;=?AA>:51/0011112344420/////...////////////0////00////////16AMZa]ZRE9121////..,,,,,,--//0111111121120/..-,-----./../0036:>DIMSV[^^^afjklljgd]UQMMOIEQ^fjmpu{||||}}{yyvvttqmjhecaa```TLNQX_ks| }wmfdaYQKHA<;;;;;:;<<;<<==<<:;<=??BBBB@@?@776678776655442211223310////////--....//..////..00000011111111111111111100////....--------------....////////................/////0//0000221111000000//00/....-..--,,++++++**)(*(''''&&%$###"#""!!!! !"%()-3;ADFHJLORSUUSM?4)''''))(((())))++-++)*0ll`_adfV8AD<239@DBKEI_baffbc_cddgeguK@>?=>@A90/.7EED@821.-/29@BB?;6543368:<?BB?:62011001234452200//..../......--..////////......../3>IW[VSLA70///...--,,,,,-.../1000001111100/.-.-,---..0..//0/00/1568<>ACEHMPTVVXUSNECCFKF?DR[aflov}||}||}|{yxwwsqomjgebcacb\_kq{ wl_UJE@?A?B@:<<;<;<<<<<<=<<=:9<<==?BCCB@BD776678776655443322223100//00////......////////..00000011111111111111111100////....--------------....////////................/////0//0000111111000000//00/....-,,,,+++++++***((*'''''&%%$#"""""""!!! "#&),04<ADFGJLNPRRSOG7-'''('))(((())))++*,+(,ekhhfcgW7AHG8-/7:=>D?Peegjecddb_aebfdGBBDEEEE>3/.279AE>3/.-./29>A?976645578;>@@@>:611100123444422210/....--....----.................1<FSVRNI@6-,,,-..--,,,,,-.../10////0011100/.-..--.//.0///000////./0../10468=>?BAA>;;;>BB>?ENSZbenu}~}}|}~|{yxyyvtqonkjgilrvx|~ zri^TJC?@@?BA=<<;<<<<;=<<<;;;<;<<==@CDDEEFF66776866775554332222210/..///////0////....////////0000111111111111111111//.////...--,,-------------.00//////..........------....///////////000000000/////....-,,,,++++++**)())((''&&&%%%$##""!""!!!! !$&+.27?BHIIJLNOPQQMH:,'&&&'()((((**))*))*,-]legdbfV,<FRQJ=337;55:Rfqnme^abc_]af[ROKKJHJIHB80/125@GD:1-----16<;;755544568==>?@=97321011244554444410...--...-------..//..--..----,09BMUPLF?5-,,,,,,,,,,++,-...///00000011110///......////0000000000........./0/001111367:;;==AGKPW_hry~}|}~}|{{{zvvtsqonotz }~{tmia\PB@@?B@><<<==;;;;;;<;<:<=???@BEFFGGHG77776866776554332222210/////////-/////....////////0000111111111111111110///./.....--,,-------------.00//////..........------....///////////000000000/////....-,,,,++++++**('))((''&&%%%$$##""!""!!!! !"#&(,039AFIIIJLNOPPNID4('&&&'((((')))*(((()-[ndge`h[-2>GTYXQF8:<98?^ltrlhje^`aaff\SROLNONLLH?3/237=CF?4/---,-/477765554679<=>@A?<9621011234555577522///.-...-------......--..-----.7AJROJF?5-******++++++,-...///00000011110000////..////0000000000..--........../////13366778;<?ENYcnw}~~}|zy{{zywuvwrrz~ |xsoh`QB=<><;;<<<<;;;;;;;=>??@BBBCEGGGHHHG88777766665544322222211000///000//////..////....////0001111111110/./1100///.--.,--,,,,----,,,,,,....////////........--...--.........///.//000000000000///....---,,++,,+**)''''(('&&%%%$$#""!"!!! "" #%(),15<CIJIHIJKNMLLE?.'&%&'((('')()(((()*(Qmcedac]./7=FPWXYM729:?<\trrpcYa^[`fhe]WSQQRQPNLJC933/18=CA81-**+*,.135445678879<>@A>=:7520/1213455689643100.-...,,,,,++,,-----,,,----,,5@FQNIE@7.(())))****,,--..--..////0000111000//////////00000000000/...-..--.../////0001222344556;IWcmx}~zyxz{{{{||uolhffkmpqs{thnz |z{{} }wsh]M@?<;::;;<<;;:;;;>@@AABCDFFFHGGGFFF6677996666554432222221100////010//////..//////....//0000111111110/./00//.-..---,--,,,-----,,,,,,....////..///.....------.--.............//000000000000///....---,,+++++*)'''''&&&&&%%$$$#""!"!!! !! !#%(++.37>EJJHEDFGIKJGC;+&&&''())'')('((()+%Oibde`ea0,38=GNUYXP4/9AEHlttto`X`[Z_chg`YVTTQPPQPLGA:53127=@:2,***)**-012357888779<>??>=:752112133467876542000/..-,,,,,++++,,,,-,,,++,,--1<ENMHE@94*())))**))+++,++--..////00001111110/////////00000000000/....--..///000//00011223443333:GVblv}}~~{vsvz~ wpic[UPMHHMQ`]_dnsqlgs £¢¡¡¡¡}ytrssx{~ ~~~~yqj]L@===<;;:<<<;<>>ACCDDEFGGGGGFFEEEE7777666655554432222210000000//0///////-///////...../0000111111000///.......-------,,,,,,+,,,,,,,--..////00////....,.....----..........,-..//////0000/////....---,,++++*(((''''%%&&%%$$$##""!!!!! %'*+,049@FIIHEBBFGGFE?5)&&&''((((''(()****Tm`bea`c1*566=DIPWXT>==><KlpstUV`cabeeegc]YXWSPPQQMLF>81/138<<3,+*)))*,/13589:9:7778:>@AA@=:642012334567535422110..---,,++++++,,++,,++**)).8>FKGDA:5.''(''&((())))()++--./../0010001221111//////////00//11//..--.......///1/00/1112344442249CS`ksy}|wqotx} ~woh`XSNLFCDDDEM]XROTZ_gn |toiddeimsw|~}~~~}~~~zrgZF><;;<<><>>?ABCEFGGFFFFFFFFEEECCD6677776655554432220000000000///.//////-///0000......///001////00////.....---,,,++++++++++,,,,,,,--..////00////.....-....----........,,-...//////0000/////....---,,++**)(((''''&%&%%%$$###""!!!!! "$',-/39<@FIIGB??BEFEA=/(&&&&&''''''())*+._kaab_dd5&29;>>BJOPPI>;<ACTmqrne[Ycaekmkhe^][YVRPSROMJE=30/24993,+*))))+.01489:;<97779=@BBA=::6520013345655555432110/////--,+++,,,,,,++**)),2:@HHC@<6/('''''''())))()**++,--.../100001111110/////////00//00//..---------.112311111123555522247CQ^ipz}|zvpoquy}}}}~{ri\TLIIKLONLJHHIKQQOKIKT_o{sme`\YVVX[beipsv{~~~|{{|}}||~~|wodWF===@AA?@@ACFGFFGGGGFFFFFFEEEFCC6666665544554432210000////000010//////.---............//////////..--------,,,+++++++**,,++,,,,,,--....////////....-,-...----........---/...///////////../...-,,,,,++)))(''''''&&%%%$$####""!! !! "$%),.27;@CHHGFA>>BCECB:,'%%&&''((()')(*)3el^`_]`j?"/4:<@B@CHJJ?8;A@<Zqqsomi]eghlppida^^\WSPQPONKGC:3005473.+*))((*,-045789:86777:=ABB><;:752111224567797553442213330/.,,,----++**+)))*-6<DJA?:51*'&'''%%''&%&''(****+++./..001111221110//....////00..00-----------04455322012456766333347?NZeov|~{zxunkloux{||zzyxtsqnmlg^VNJJIIKMLNNJGGHJLKHFGKNZm{}|{}yzvqjb[UMGDBEGOUY^_deilqtyxy}~}|{{{zz{{|}||{|}|tkbUI@AABCCDEEFFFGGGGFGHHGGFFFFFFGG6666665544555422210000////000010//////.---............/////////...------,,,,+++*****++,,++,,,,,,--..//////////...-----------........---....//////////////...-,,,,,++)))(''''''&&%%$$$##"#""!! !#%(+/38<BEGIHFCA>>ABDB?7)'%%&&''((((**+':ki^__]boG -238:DGKFEHG=>Qd[DOjlmgjiegkmpqnief`\ZWTQOMLLKGF>7114363.-+))(()*+-13379:766677:=>>><<;975211123435579:76753333330/....--,,++*****)(+19@FC>:53.(''''%%%%%&%%%&')))*)*,,-..011112222210/....////..//..----------.156884320235677883333246?JYcmux~}{yvrmjkorv{||zxwusqniida\XRPNKKKKJIFCCDFECCB@ADOZ[YYYYYVQNFA=98779?EJRV\___aceimpqvz}~|{zzzz{{{|||{{|~yskaWKCDEEEDDEEEEEEFFEFFFFFFFEEGFGE6654556644333322102211000///////000.......-.//..----/.........----,,------,,+*******))*+,,,,----+,--..//00..........--,-----------..............///...///.------++++)))(''&&&&&&$$%%$#$$#"!! #$(+-18<DGJLLJGB?ABCEFB=2&('&&&''''(')))Gog`[^abjU!.:<==;EGXOLJHB=EOWWXdjhgiiabiqrojchhd_]ZVTQLIKIFDA:614675/,,++))))*,-015988855569:;<>===;97543002222568997995442101100///.++-*****))()-5;@C>;73/,)%$&&%%%$%%%%%&''''()**,---./012322220000.0///.--..--,,,,,,**--/1468534422345896666521134=IV`irv}~zyvrljfkotx|{}{zzwusqmie_[XTQMLJGEB@?=@>><<;;==?????><<9:::89::9:<BHMSX[^`^]_^behlpux}~|yyyxxzz{y{{|{||~|ytldZMFFDFFEECCCCCEEEEDEEFGFHHGFHF4433332233323311211100///.......//......-.--..--,,,,,---/-----,,,,,,----++,,+*))))***)*+,,,,,,,,+,--..////..........--,-----------------......//....//....------+*++)))(''&&&&&&%%%%$#$$""!! "%*,.29AFKPOLJGDBABEFIF<,('&&&&((')%(')Vlc_\bhglS2JLLNSUJGJNKIA<D@BGQ^aeghji_gjnplbejgbac^WTPLHGFEDB=834675/++++****))++0269995534457:=>>>=;97532111333335888876421/0000000/.-,,**))))())-6;@>:641.*&$&&$$%$%%%%%&''''''))++,,../02222221111////..------,,--,+,,,-/11344123434456866666411344<FR[eov{}}ywtrkdgjlquy{{||{yxwtrokgc`\WVTQNJFA<:88887677::::9;;;<=>><9;;99;?EJOS[^`_`_\ZY[`ejqvz{{yyxxzz|{y{{||||}~}ytmc[NEFFEDCDCBBBCCCCDEFGGIIHHFGF54224322322211110000////////......//..----++,--,+-,,,,,,-+***+++,,++,,,,++++++**)))))))*++++++++,,,,..----..........---------------,------....//--...0--------,,+*****('''&&&&&&&&%$####"""" !"%()-/4;AHMPPLIFEB@BBDJG8**'$$&('''&'$7fmc]_chhlZ&+CKJNMOTSKING@9NaVZWR]dilkkqnlnpob_eggebb_WSPLHCBACB?:30254.,,++++,,+**+-/3578652113688;<=>>>>;955312331024425775422001123431/--***))))(((18;=:6640.+)%%%%%%%%&&&&&&&&''(()***,,-.01000011111/00////.-,,,+**+*+++-.0111112333223455566766644559ANYbkqx~}xurniefgjmqvy{{{zzzxxvtpligfca]XSMIFDA>::6777677899<<==<<=<:9889<@FJPUZ]```][USTZ]dhpv{{zzxyyy{{{|}|}{{|}}|xtkb[OEDEDCCBAAACCCDEFFHHJJIHFFD33222222221110010000//......----....--,,,,++*,,+,++++++++++++,,,++**++++********)))))))*++,,++++,,,,..--,-..........----------------..----......--....--------,,,+****('''&&&&&&&&%$$$$#"" "#&)),.28>DGJKKIFEBCBCBFB0(+'&'&'''(*%@pmbb`ffhoL#*0EQJGF@BLQFKJ?<FT[dWVcinonlopklpeW_jjhica_XRRMIDA>>=<93/011,++++++,,+,+,,/.3565531/057789;<>@@?=:76334300011114445332222234320--,**)))(&&'*38:<865430,*(&%%%%%%%%%&&&&&&''()**,,-//0000011111000////.-,,++++**+++,-...../01221233355666555334437?KT]emty~|vrpnlgbegjoux{{zzzzyywtrnlkkihd`[XUPMHFA;877666777::;;;;::887779<BGLQW[]__][WQMMTY`gptwzzwwxyyzzy{|||{zy{}}xribYNEDCCCABAACCDEFFJKJJJIGFED1132222211111/.//........-------,.,,++*,+++,+++*****++**++++**,,*)****++****(******+*)****++,,,,,,++------......--..--------------,,------.......-..-,--------,,++++))((''&&&&&&&&%$$$###""" #$'*,-.26;@BFIJIGGDAAA@?8-*,('%''&(''<pkddegjlh=+/3?ZLCFG<PRNKIGFLQ_f`X_gmoonpqopof_ejhgiffa]XRNGB=;:;:74110-,++**)*,+--,*,./01345420037589::<>A@?<:8697531//.//122444433355431/.-,,*)(*)'%''-2499856720-+'%%%%$$$$$%%%%&$%%'(**,,........---/0000..0..-,,++++++**++,,,,----01111233444433332232126:BNWbjqx{zurmmkfbbekqtvzxyzyxxvttolmmmljiggb^ZVQLE?986656777789::998866569>BFMSX\^^^]YUNIHJS[ckqwz{xuvvwxwwyz{}}z{{{zvpi`XPFDCB@@ABCDDDGHHJLKJHGECA1110111100/.//-..-------,+++,,,,-,++++*+)*++***)))******++**))++))**))))))))()((''(**)****++,,,,++++,,------------..--------------,,--,,,,--,,,,.,-.-,------,,,,++++))((''''&&&&&&&%%%$$#""" !$%)*,-.16;?CEGJJIHEBBA@9.*++('()(((*>kmfcfkmog5&.10=XXKNKJLMRSNDFFPQhieafltolknnknl`bdffefghf_ZVRJB=;:866:;83/-,*(()**,-./...../124642223568999<??>=><:;9753221/0132334433344321/.-,,+*)))('&&),15::997661/,'$%$$$$##$$$$$$&&'(**,,--......---/.....///.---,,++++**++,,,,--..////0112222211112210/0235>JV^gotyyvrpmlhcaejlptwxyyxvsqomjiijjkjmmkie`ZSKD<87666667777776666555768>DINTZ\\^^]UQJFBGOXaiotxxxvttuvvuuy{{zxyyzxvqibYPECABBCDDEGHIJIKKJJHFCAB000000//0/////0.----+,,,,*,,,,,,,+**))*)())))))*))*)****++**))))(('(((('(()))((((((())********++++++,,,,,,,,,,------..--,,------,,++++**+++++++++++-----,,+*,,+,,,****(('''''%''&&&&%%$#$#"" "%(),+.147>BEFHIKJIHDDC?0*)*(&(*&)(*)dpgeejjoW*(0/4DV\LKHILKBIRKE?IY_YX[`jpskjjelnlg_aegc_afhe_]YUNGB=:879=D?51-+)((((+,--//..--.//3345521136779;<<<????>><9866533334444542300000.-..-,+*)))('&'(,27::;;9841.*$#####""""##$%$&()*,,,,-....--,,,---.././/....--,,,,++,,,,,,,,----..//01000000//11/.////15;FO\ckry{wtpmiheaagjoruwwtqqnkihfdddhjijljgd_YQH>987665566775544445555569?ELQWY[^_]ZSMIC@IPX`gntyywurrstrrruy{yyyz|xvpjd\SJAACDFEHJJJJIIKJIFFCA?////....//.././---,,+,,,,*,,++))(*))(()('((('())***)))))**))))))(('((((&''(('&'''''())**********++++,,,,,,,,,,--------,,,,------,,++++**+++++++++++++,,+,,+*,,++++****((''''&&''&&&&&&$#$#"" "&)*,.148>CEFIJKKJJIFFF9++**)'&&'((#TojijnpoO,43.-4CJNOQMSSSOLKDBGG[^UK\biqommjgmomifdba``_cfb^ZYVPKD?<<=<<B=53/)((((()+,//./.-----/2466321135689;;=??@@@@??=<;:998888755410....,.-..-,+*)))(''&'(-147:;97630,($####""""###$%'&'(*,,,-....--,,,-----////....--,,,,--,,,,,,,,----..///00000//..00....../128DNX`hpuzywoljgeb`agnqssuqomiedb`[\^`cfhihd_YPG>988665566775544445544568>CJNTZ\]_][WPJEDDJQZbhpswxvtrpqqqrruz{zyyz{zwsog_XMFECGHIJJJJIIIIIFDA=<....--...--,-.--,,))****+((((((('''''''''''(((''''(((())**(((())(((('&&&&&&&&&&&''&'))****++))))))+++++++,,*++,-,,,,+*++*,,+,,,,****++++**++++++******+*+,++++++******))))('&'((''%%&&%%$$!! $(+,/137;AGLMNLJKJLKJJC.&)'%'''(('&Mqhciopi?"300/14=GRZ^XVXYUMIB6<HbcZYhqmrrqmnmiijkgda_b`_^]\USUSQMIC@@C?8:9561(&''((())*./00.,+++,,-03101/025788:;=???@@AAB>@@@?>>=;;8542/-,---,-.,,+**)))('&&&%&+/24688441.,'"#######$$$$%&'(++,,--..,,++,,--......////..,,,,------..--------..//000011//..//..--....017?IS\emswwvpjhhd_^^flnpppnlgba^[WTSSX[`ea_ZWME=986665566775566556666669<AGMSWZ\_]\YSNHFEHMT]ekpuwxxvspmnopqsx{zyyy{zwtqjc[UMIHIHIJIGIGHGFC@=;9....-----,,--,,,,+**))((('''((((''''''''(('(''''''(((())**))(((()(((&&&&&&&&&&&&''&'(())))**(())))+++++++,+**+,-,,+,**+,,+**,,,,****++++***+++********+**,++++++******))))('''((''%%&&%%$$"" "%),/147;=BHNRQOMLKKMLH7*&&('&'&&($Ntiejjle2#120.306EIOWYVVVWVMD;08F^RU[akjruqmprnjiggdb`]^^YUTNMPRNLMF??A<763551+'''(((()*-.//.-,,++*+,-.00/0035778::;<>@@@@AABBBAAA?=;87531//.--,,,+++**)))''&&&%$&(+.025442/-)%#######$$%%%&'(++,,,,--,,++,,--......////.-,,,,------......----.///0000111/0000/.--..//0122;EP[ainvyupmjie_Z]bhknqrplfb^YUQNLOOSXYWUPJB9786666677887777889999:::;AFKQVY[\]^ZUQKGFHMRXahlpuwyywrmmoonorv{zzz{||yxtolc\RKIIIIHHGEEDC?=;76....,+-,,,--,,,,+****(''(&&&&&''''&&''''''&&%%''''(((())(())((('''''%%%%%%%%%%%%&&&&&&''(''''())))*)*+++*++++++,++++****))+++*++**********))))**************++++******)))))((((((('%%%%%$$""!! $&)-037;>@CHMPQONNLLMK?-)(('*('&%&TvjefjdgC#.210.-/9HORXYY^dZTMG>1<HZZTW^hjmqqswvqiaafd_ZY^_VPNIHJNMMMIDB@<854774-(%&'()))),/..-...+)(('))*--.014566899;<=>??A=>?@@BBA=::8664210//-,+**+*))(('&%%$#$$$%),.1234/.+&#"$##$#%%&&&''(****++++,,,,----.-......//.-,,,+---------.-------.///00011001100/.00//00230149AJV]fkrxwqpjec]ZZ^cglmonlgb[VSPIGGHJMMKFC=88:9887788888888:<<<;;;;<;?CHMRXZZ\^[WRJIGILQUZagmqtxyxvrmonpqrsv{{{{|}{xsohaVNIHGGDDCDA?;:752,,,,--,*++++******((*)))&%%%&&&&&&%%&&&&''&&%%''''&&(())((((('((''&&%%%%%%%%%%%%&&&&&&'''&&&'())))*)))++*******+***++***))****))))))))))))(()))*))))))))**))**********)))))(((((((((((&%$$""!! $(*-037?CDEHIJLMOMMLJB2''''(*)%%$StkafhiiM&.10..-+/>MWPUVY__][TH>7CQ^^UWc_[htstwyzqffki`[Z[\ZRKIHJJJKJJFDA<875654-(%&'())))+-,,-....*''&$%(*+-/25568988;<<<===;::;>>><9::::8732100.,+***+))((&&%%##$$##%&*,.11/.-*%%$##$$%%&&&''(****++++,,,,--...-......//.-,,,+,,,,-----.-------.////00000011100/1111222223236>GQYajpuxrnhc^[VY^`ekmnnnhc\VSNHDA@CCB@==:;<;::::;;;;;;;;<=======>==BDIPSWXZ\YWTNJKJJNSW\dgkqtxyxvromqrrrty|{{|~}wqlaULIHEBCB?=;97633++,,,,,+****))))(((&&&&&&%%%%%%%%%%%%%%%%%%%%%&&''&&&'((((((((''&&%%%#%%%%%%%%%%%%&&&&%&&&&&'))(''*())()))))))****++*)))))))(((())))(()))((())))))(((())))))))))))**)))))))((())((''((&$$%$#"!!! %'*/27:@EGFGEFIKNNMJD7,*((()*+$#A{m_^edj_%+1038/++-8DLORSUZ[^]QJB=HRWYQRZRTbrstx}|wusoh`\YVUTPHHIHGGEFJGDA>976543.('''(((')*++++-//.,*'%"$'(+.046776679=<<<=<:976798877:;<<;76320/.,*****))(('&&&#$$$$$#$'*-.10/.,)&$$$%%%%&&''(())**++,,----..///,---...//.-,,++**++++,----,----....//00001112221133334455434467<DLT_eltvsmhc_YUTX^cgkmmmje]XRNHE?<:<>>?>>=>>???<<=>????@@????>>>=<>BHLPTVXYZWUPLKHINRUY_dhnrvyzyvrqqqrssvy~ ~yri_QGFC?==;9644330+++,,+******))(((((&&&&&&%$$$$%%%%%%%%%%%%%%%%&&''&&%&((''((((''&&%%$%%%%%%%%%%%%%&&&&%%&&&&'''''')((('(()))))))(())))))))))((((((''''(((((())))))(((((())))))))))**)))))))((())(('''''%%%$###!! %),/4:=BGIIHFEGJNONNG>3))))*)((-qwi`dijb5%.1122-**,5BLNNSW[]\ZNF<8HVY[[S`dY[bmptswwwunjaZTQPPLFGHIFDDEIJHCB?;98851+((()))*)')*++,...,-,(%$$'*.26887668:;=<<<;:9664666577;9897521//.-,,,,,**))('''&$$$$$#$%(*.01//0-)(&%%%%%&&''(()))*++,,----..-------......-,,++**++++,---..........//001122123333344455777877778;@IQYdhpttmha[ZSRV]`dkmmlid^XUOLHC?@@@AAA@@@AAA@@?@AAAA@@??==>><;;<?BGMSUVXYYVRMLJLNRTX\afkqsvwzywtrqqtvxx} wocVGC@<;:964332/.**))****))(())(('''&&&&&%$##$$$$$$%%%%%%%%%%%%%%&&&&&%&&(&'(('&&''&&%%%%%%%%%%%%%%%%%%$$%&''''''''((((((((*((((((((())))((((((((''''''(((((((((((((((())(((((())****))))))*)()))((''(('%&&%$##""! "%(,/4;?AEHIGFEFLNONMNG7'')**)((]{mceiph@!.20/.,*)+-7AFJRRTX^c^OEA=M_ffjnwq_YS`edkrxyumjd[VSPOJDEFHFDDDHHGFFFC>:852+)))+*+,,((')+,-.-,.,*)'&&(+.59854799<<=<::9755545555653666210///.-,/..,++,****'%$$$#$$$%),.21121.+(%#%''&&&'(((()*++++,,,,....----....,-..,+++****++,,--/.....//..//00223221243345556677888888779>EMU]emsrmib][WSV\_cgkijiec[XTOLGEDBCCDCBCCBBAAAABBBB@@?=<;;;;99:;@EKPRWYZZXSPOLOQSXY\achlqsvy{{xusuuuwz| zqfYHA<98755211.,,**))((**))(())((''&&&&%%$$###$$$$$%%%%%%&&%%%%%%&&&&&%''(&''''&&''&&%%%%%%%%%%%%%%%%%%$$%&''''''''((((((((('''''''''((((''''''''&&&&&&''''((((((''((((''(((((())****)))))))(()))((''(('%&&&%$#""! $&(,.37;<?ADA@AEKLMMLHC4(('))*%UwolhnrpW&+/3/..,+**+8@KORRUVVY^QCCO^gkjgmjVYa^ZWWcortplni_YVRNHC@CEGFEFGEFHIIFD?;94.**-**.11.+(')+,,,,-+,+,))()-164345688;:988764222333332222210//-----....,--****(&%%%$$$$%(*.100210.+(&%&&&&&'(((()*++++,,,,......--//..,---++++****++,,--/...//////00112343233334455666778999888998;AIQ[bnrsole`[[YXY_cgikjjhe^[XSPJGEDEFECCCBBAAAABBBBA@?=<;::9999:>EHNRUW[ZYVSQQRTVX[]`bbhmpswx||zxusvx||~ ~yri[J>8754220/.-+,**))(())))(()('''&&&%%$$$$$$$$%%$$$$%%%%%%%%&&&&%%'''''''&''&&&&''&%%%&%%%%%$$$$$$%%%%%%%&&&'&''(('''%'('&&&&&&&''&(''''''&&&%%%%%%&&&&&%%&&'''''''''''''((((()(((**))(()))(())))'(((((('''&$###! $%*,-1467:<=;>@BIJLJJF>2*'()(%Syocagso]$'//.--++***,/BKMRSSQPU[ZLDNbkooerj_[heaaMWglomkjgc^]XNGB?@ACEFDDEHIIHFDB@=7/,,//03682.*'(())+,+,--..++--000011236676544111012222100.//////..--..//11/.,*))*)('&&#%$$%)-1323330/,)&%%%&'''(((**++,,,,,-...../..////..-+**++*))++++++,.//01111222222233333444556666666887799888777?FOZciqusnje`^[YX\`eghklid`\WUPKHHGFEECCBA??@@BBAAA@?=<;:987889<@DJOTWY[[YVTVVXY[\]_ddghlprtx||{xuvxz} ~|yriZI;5531/.-,++++))))(())))(()('''&&&%%$$$$$$$$%%$$$$%%%%%%%%%%&&%%'''''''&''&&&&''&%%%&%%%%%$$$$$$%%%%%%%&&&'&''(('''%%%%%&&&&&&&&&'''''%%&&%$$$$%%%%%%%&&%%&&&&''&&&&&&''''((&'((**))(()))))))))'(((((('''&#$##! "%(+-023347:;>BDFIKKJFB6*'))&G|o`_fqqe7 ,0.---++***-=GDGQTWROQYYRJSbjq^ovbeoha`bRP\dhjhd_`b^YQGB@?@BCCDDEFFFFEDB@=7/-/13425420+)((((***,/12200..--//../02241111011012222100///////...-..//11/..,,,*)('&&$%$$$'+-2555410-+(&%%&''''((**++,,,,-....../00110000/-+++++**+,,,,+,.//011112222222222223434566655557877998887666<CNXaiqtvtqjgb`][Z]dgkjhhfc^YVTPKHGFEDCBAAABBBBBBA@?=<;:977889<AEJOQUWZ[[[Y[[Z[\_acceffhknqtx{|zwxx{|{{yuqg^M=200.-,,+++++(((((())))(''''''&&%%%$$$$$$##%%%%$%&%%%&&%%$$%%%%&&'''''%((''&&''''&&%%%%%%$$%%$$$$$$%%&&&&&&'&''&&''%%%%%%&&&&&&&&&&&&%&%%%%$$$$$$$$$$$$$$%%%%&&&%&&&&&&'''''''())))))***)(())('''(('&&&('$%#""" #')./12237:=@BCFHJMLGA4)(*'6uvkkjpqj8%-.----++))*0FCBIQSQPOSRMXY[`k]Knovnigd^YYVRYabe`\][[\SKFA?>?@CFFEDCEECBA@<72.04872-121-,*))()(),/255442..,..--///.///////00133332111..//0000..--00000/.-,,*)('&$%%$$$$(+.2455211.+)'%%''((()++++----........001111200/,,----,,--,,,,..//1111111122111101223444554444566688888888659BLV_jsx|zwrkgc`ZX]adghhjhd`\YVQLIGFEDC@??BDCDECBA@?;;;:89:::<?CHNSWZ\^^____^^_bdffffcbehkorvz||zz{}~{yvtsni^QA2..-+,,-++**(((((())))('''&&''&%%%$$$$$$##%%%%%&&%%%&&&&''%%%%&&''''&'((''&&&&''&&%%%%%%$$%%$$$$$$%%&&&&&&'&''&&&&%%%%%%%%%%%%%%%%%%&$$$$$##############$$##$$$$%%%%&&&&&'&''()))))))))(((((('))(('&&&'&'%$##"" ""'),.01358<BCDDFJLOLGA2((,,l|kkqrnlA#*------++*))-/9FLQPPPPKLPW^`[b^W_eproiiec][X]`aa]\ZUXXSOLE?<=>?CCBAAEFB@?ABA90.1550-/211/.,)))++/-0358830.,,,--...-.--..-0/013333332200001111../000000/.,++*)(''&%%$$$$%),0244221/-,('%%%((()++++------......000000001011/---..--,,----./00000011110011012223444455444466777777774448@LWaju|~}zvpie_YW[]cffijifb\ZVQLIGGFEBBDFGFGGECB@@?=<;9:::;=ADHNSWZ\__``aa``acegggfc``bdjosx}||zz{~}xvtrqmh_VF6---+++,,,++''(((((())('&&&&&'&%%%$$$$##$$%%&&&&&&&&''&&&&&&&&&&''%%%&((''(('''''&&&$&&&%%%%%%%%%%%%&&''&&&&%%&&%%&&$$%%%%$$%%%%%$$$$#####""""""!"""""""####$$##$%%%%%%&&&&&'(((''))))))))))(())))''&((&&'%%$"" !##')*+.039<@BFJMNNOOJFA1()*[wlmvsoP #,,,,,,,,,**+15:@HORONKHKKUadXQZeigackh[Xda___\__\ZYSQORRNHB@><;;=<<=?====?DKA1**.0.//33201/,*-0110214883/+*)),+,,,,,-....///02123234433221100//..00110/.,,++*(''&&%$%#$#&'++/0111//-+)'''((())*+++,,,+++,...-0000//01001100..--..--,,----,..../00////000011222233443333466666777654447@LWclv||vrie\USX\aeiijifb_[VRMIIHGGFGHJJJJGEDCC@>=<;;==??AEHMRVZ^`accbbabedeffffc`][\djnsuy{{zz{~~~ywuronlicYM=.***+++,+))(((((((())('''&&&&&%%%$$$$##$$%%&&&&&&&&''&&&&&&&&&&&&%%%&''''((''''&%&&&%&&%%%%%%%%%%%%&&''&&&&&&&&%%%%###%%%$$$$$$$####"##"""!!!"""!!!!!!!""!!""""#$$$%%%%%&&&('''''))))))))))))))))****''''&$$#!!!!"$')*+-/16>BFIMPRRTRJF?.()9zrinwwqT! "&,,++,++,,***/05>FILLIJLMLXbcYEHST_fldda_`bike^^_^]ZRPQSRLEBA><87788989999;BPI1*),-022421/0//023445222330-*)**)+++----.////0/0100023443333221100..//00/./--,+*)()(&&%%$#$$$'(*-./0100/.,)'(()(()**)+,,....//.-////..,-////....-...--,,,,,,,-..-.//////0000001100002322222333334554444469@LYcpv{{}}wqh_VPQW\aehjjlgda\VRMLKJJJKLLMMLIGFDC@?=<<<==??CGHMRVY]__cddebddeeffffc]]ZX[aflpsyz{{yz~~}xvrpmnlje^TC1+)*)**+*(('''())('))('''''&&%%%%%%%%$$$$%%&&&&&&''''''&&&&&&%%''&&''''''''&&&&''%%&%%%$$%&%%&&%%%%%&&&%%''&&%%%%$$$$"###$$$$##""""""!!!!! !!!!!! ! !!!!!##""!"##$$$%%%%%&'&&(((((())))))))))))****((((&%$$""!!!$()*++./5=AEJSUTTVQJE;*)(Tuqmsyte* "!%*,,***+,.,+++*3=BHKGHHFDFP_SFKSQTbgeijf^_\bjdZX[\\ZSPTVSQLCB?;:7544545688:AE@8,)+,.334422145665676310//,+*)(((()+----.0/011////0001331223333110/,-/../-.,-,+++*))(&$$$$$#$$$'(+,,/0221/-+*)))******+,..00/0/-./////....////......-,,,++++,,--....//////00////////00122211111123334434578ANYenwz{|yuoeXPPQTZbfgjkjgdb]XRPOMOMNOONNLIHFED@@?>=<>>>>AEINQU[]]acefffgffggffdb_[URQV^dhkrvz|z{|zzxuqrllkif_XJ:,)+*))))(('''())('))('''''&&%%%%%%$#$$$%%%&&&&&&&&&&''&&&&&&%%''&&''''''''&&&&&&&&&%%%%%%&%%&&%%%%%&&&%%%%&&%%%%$$$$#"""####""!!!!!! !!! !!! !$$!!!"##$%$$%%&&&'))(((((())))))))********(((('%$$##""#&())*+..48>HKRVUURKHD9)(,ksjqxsi7$"#'*++**)+,./,*,+5AGGIIF@?;=FRJ?IRYkhfhhceg\P]gaXTWXZZVQUSMMJCC@==:755534688<>>><0))+-0144434688875431/---,+*)((((()*,../0/011///////01113234445320/,+../----,++**++(''%$&$$$$#&&(*+.133220//,**++++,,,.0000/0110.////....////....//-,,,----..--....//111100//////////002211222223334454576:CNYcjsyyyurj_VOSRW\bgmklkieb\XWRQPPOPPNNJHGEDDB@@??=??AACEGNQU[]^bdgihghggggfeda]XRONORYaglptx{zyzzxxwsmmligd\SC4-*)((''((~~~~~~}}||}}~~~~~~~~~~}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~}|{zzzzyyyyyyyyyz} |{zzzzz|} ~~~~~~~~~~~~}{||{}~~~~~~~}}} ~}}}}||{{{{||{}||}~~||{|~~~}{xwyz~~}|}~~~~}|~~~~~~}}||}}~~~~~~~~~}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~}|{zzzzyyyyyyyyyz} ~~~|{zzzzz{}~ ~ ~~~~~}{||{}}}~~~~~}}} ~}}}}||{{{{|||}}}}~}{{{|~~~~~}zxwyz~~}|}~~~~~}{~~~~~~}}||}}~~~~~~~~~}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~}|{zzzzyyyyyyyyyz}~~~|{zzzzz{}~ {}~~~~~}{||{}}}~~~~~}}}~ ~}}}}||{{{{||}~~~}~~|yy{|~~~~~~}{zyz{~~}|}~~~~}{y~~~~~~}}||}}~~~~~~~~~}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~}|{zzzzyyyyyyyyyz} ~}~|{zzzz{{}~{tv}~~~~}{||{}||~~~~~}}}}~ ~}}}}||{{{{||}~~~}~~~}|yy{|~~~}}}zzyz|~~}|}~~~~}|z~~~~}}{{}~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~}||zyyzzyyyyyyyy{} ~~}}~{{z{{zz{~ yssv~~~~~}|}}|}|||}~~~}}}}~ ~~}}}|{zz{|}}~~}|{yz{|~~~}}~}}zzyz|}~~|~~~~~~~zy~~}}}}||}~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}||zyyzzyyyyyyyyz} ~||}|{z{{{{|~yttu} ~~~~~}|}}}}|||}~~~~}|}~ ~~}}}|{{{{|~~~|zyyz{|~~~}}~|{zz{|}~~|~~~~~~|yy~~||}}}}~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}||z{{zzyyyyyyyyy| }|{}}{z{{{{{}~yvuvz~~}|}}}}|||}~~~~}|}} ~~}}}|{{{|}|{xwxy{|~~}|}~|{zy{|}}}|~~~~~~{yy~~||}}}}~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~}||z{{zzyyyyyyyyy{} |{{}}{z{{{{z}~yxwwz|~~~}|}}}}|||}~~~~}}}} ~~}}}|{{{|~~~{zyxwy{|~~}{}}|zy{|~|||~~~~~~{yy~~}}|{}}}~}}~~~~~}}}||}}}}}}}}}}~~~~}}~~~~~~~~~~~~~~~~~~~~}}|{{{yyyyyyxxxxyz| ~|{{|~|{zzzz{}~}|{z{~|{}}}}}}|}~~}}}}|~ }~~}||{}~~|yxwwxz|}}}|{| ~|{{{|}~{z}~~~~~{yx~}}|{z||}~~~~~~~}}}||}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}||{{zyyyyyxxxxyz| }|{{||{zzzz{}~~~|{|yz}~~|{}}}}}}|}~~~}}||} }~~}|}}~~~~{yxvvxz|}}}|{| ~|{{{}~|zz|~~~~~~|yx}}||{z||}~~~~}}}}||}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~}}||{{zzyyyyxxxxyz{~ |{{{|}{zzzzz|~}|}~}}|~}|~~~~|{}}}}}}}~~~~}}||} }~~~}~~~|}}zywvvxz|}}}|{| ~}{||}~{yz|~~~~~~}{y}}||zz||}~~~~}}}|||}}}}}}}}}}~~~~~~}}~~~~~~~~~~~~~~~~~~}}||{{zzyyyyxxxxyz{~ ~|{{{|}{yyzzz{~~|{|~~~}}}~~}}~~|{}}}}}}}~~~}}|{} }~~~~~|}}zxwuvxz|}}}|{| ~}|||}|yxz|~~}{z~~{|{{{{|~}}}}}|{}}}}}}}}}}}}}}}~~~~~~}}~~}}}}}}~~~~~~}}||||{{zyyyxxxxy{|~ }{z{{}~~{zzzz{{}~|}}~~~}}}~~}||~}|||}}}}}}~~~|{{}~ ~~|}~~}zyvuuw{|}}||z| ~}}~~~}zxxz}~~|z~}{{{{{{}~}}}}||{}}}}}}}}}}}}}}}~~~~~~}}~~}}}}}}~~~~~~~~}}||||{{zyyyxxxxy{|~ |{{{{|~|{{zz{{|~~||~~~~~~~{ ~|{|~}|||}}}}}}~~~~}{{|~ ~}~~~}zxvuuw{}~}||z| ~~~|xvv{~~}{~}}{{{zz{}~}}}}||{}}}}}}}}}}}}}}}~~~~~~}}~~}}}}}}~~~~~~}}}}||}|{{zyyyxxxxy{} |{|{{{}|{{zz{{|}~}}~~~}{| ~}{~~}|||}}}}}}~~~~}{{| ~}~~~}zxuttw{~}||z| ~|wvw{~}{~}}z{zzz{}~}}}|||{}}}}}}}}}}}}}}}~~~~~~}}~~}}}}}}~~~~~~}}}}||}}{{zyyyxxxxy{~ ~|{{{{{|}{zzz{{|}~}}}}~~}zz~}~}|||}}}}}}~~~~}{z| ~~~~~}~~~}zxuttw{~}||z| }|xvx{~}~~}{{zzz{}~~~~}}}|{{{{|||||||}}}}}}}}}}~~}}}}}}~~}}}}}}}}}}}}}}}}|||zzyxxxxwxy| ||||||||~~|zzz{{|}}~~~~~~~}|{y~}xx{}}|~|{{{|||}|~}}~~}}{|~ ~~~~~~}~~~~{xusstw{|}}|{{}zwvx|~~~~~z~}|{{{{z{~~~~}~}|{{{{|||||||}}}}}}}}}}~~}}}}||}~}}}}}}}}}}}}}}}}|||zzyxxxxxxz~ ~||||||||~~|{zz{{|}}~~~~~}}~~~}|z{y{}|spqtxz|~|{||||}}|~}}}}||{|~ ~}}~~~}~~~~zwtrstw{}~}|{{} ~zwvy}~~~~z~}|{{{{{|~~~~}~}|{{{{|||||||}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}|||zzyxxywxy{ ~||||||||}~~}zzz{{{|}}}~}|}~~}|zxyzx|~xspprtw|~}|}}||}}|~}}}}||{|~~}||}}~}~~}}yvsrrsw{}~}|{{} }yvwy~~~~ {~~}|{{||{}~~~~~~}|{{{{|||||||}}}}}}}}}}||}}}}}|||}}}}}}}}}}}}}}}}|||zzzxxzxy{| |||||||||{}~}zzz{{z{~~~~}~||}~~}}|zxxyvyzvssrtx|~~}}}||}}|~}}}}||z{~}|||||~}~~||xurqqrw{~}|{{} }xvwy}~~~ |~}||{{|}|}~~~~~~}|{zz{{||||||||}}}}}}}}}}}}|||||}||||}}}}}}}}}}||{|{{zzyxxwy|} ~|||||||||}~~{{{||{}~ ~}}~||}~|{{zyywvv}}wtuwz}~~}|||||}}}}}}}}|{{{}}|{z{|}~~}}}zxusqqrvz}~}|z|~ {xvxy}~~~ |~}|{{{|}}}~~~~}||{zz{{{{||||||}}}}}}}}}}~~||||||||||}}}}}}}}}}||{|{|zzyxxwy} ~|||||||||}}~~|{{||z|}~ ~}~~~~~~~|zz{zzwwvy}}xwxz{}~~}||||}}}}}}}}|{{z{}~{{zy{}~~~}}}zxurqprvz}~}|{|~ {xvxz~~~ |}}|{|||}}}~~~~||{zzz||{{||||||}}}}}}}}}}~~~~||||||||}}}}}}}}}}||{{{{zzyxyx{~}{||||||||}}~|{{||{|}~~~}~~|zz{{zxxvw{~zyz{|||}~}||||}}}}}}}}|{{yz}~|yyxy{~~~}}|ywtqppsvz}~|{{}~ {xvx{~~~ |~~}||{}}|}}}~~~~||zyzz{{zz||||||}}}}}}}}}}~~||||||||}}}}}}}}}}||{{{{zzyxzx{ }{||||||||}}~|{{||z{|~}~}~~}}{z||zxxvwz}}x{|}|z{}~}||||}}}}}}}}|{{yz}~}||yxwy{~~~}}{ywtqoprvz}~{z|}~ {xvx|~~~ |~~}}|}|~~}~}~~~~}|{z{{{{{{{{{{||||}}~~~~}~~~||||||||}}}}}}}}}}{{{{{{zzyyyz}}||||||}}|}|}}~}y{||z||}}~~~}z{~}}~~}|||{{ywwzyuy~{z{~~~}}}}}}}}}||zyzyyz}}|{zwvwx|}~}~~}|xuspnpruy}|{|~ }zwvy}~~ ~~~}}|}}~~~~~~~}|{{{{{{{{{{{||||||}}}}}}|~~~~||||||||}}}}}}}}||{{{{{{zzyyy{~}||||||}}|||}}~}{||||}}}}~~||wuw}|{|}~~~}}zuvvuy{z{~~~}}}}}}}}}||zzyxxy|~|{zxwvwx|~~~~}{xuspnoquy~|{|~ }zwwz~ ~~~}}|}}~~~~~~~~}|{{{{{{{{{{{||||||}}||||{}~~~|||||||||}}}}}}}}||{{{{zzzzyyz|}||||||}}|||}}~~}}||}~}~~|ztsw}~}||}~~|tsvvy~{z{~~~}~~}}}}}}{{yyxxwx{}~{zywvvwz}~~|zwurpnnpuy}|{|~ |zwx{~~~}}|}}~~~~~~~}|{{{{{{{{{{{}}||||}}||||{|}}}{||||||||}}}}}}}}{{{{{{yyzzyy{}}||||||}}|||}}~}{||~}~~}ztuw~~~}}}tswxz|~{z{~~~}~~}}}}}}zzzywwvwz}}{zywvvwz}{zwtrpnnpuy{}|{|~ {zwx{~~~}~}~~~~~}|{{{zz{{{{{{||}}}}}}}}}}|}||||||}}}}}}}}}}||||{{{{{{zzzzxz|}}||||||||}}}}}~~||}~}~~|{xx{}}~}vsuwz}}|z|}|}}}}~~}|zyyxwutvy}~~|zxwvuuvy~|zusqpnnpuz|}|{|~}zwvy|~~~}~~~~~~}|z{{zz{{{{{{||}}}}}}}}}}}}||||||}}}}}}}}}}||||{{{{{{zzzzxz|~~|{||||||||}}}}}~~}|}~~~~||{{}~~ysrvy|}{z|}|}}}}}}}{zxxxvusux}}|{zxvvtuvy~}yusqpnnpuz}}|{|~|yvvy|~~~}~~~~}|{z{{zz{{{{{{||||}}}}}}}}}}||||||}}}}}}}}}}||||{{{{{{zzzzxz|~}{z|||||||{|}}}}~~}~~~~}}{}} }yspsw{|z{|~}|}}}}}}|zyxwwutrtx}}|zywvusuvz~|yutrpnnpuz}}|{|~~zwuuy|~~}}}~~~~}|{z{{zz{{{{{{||{{}}}}}}}}}}||||||}}}}}}}}}}||||{{{{{{zzzzxz|~~}|{||||||||}}}}}~~}~~}}||}|xsopv{|z||~}|}}}}||{zxwwvusrsw|}|zxvutsuv{~|yuurpnnpuz}}|{|~~}yvuty|~~~~~~}|{{{{{{{{{{z|||||}}||||}}}}}}}}}}~~~~~~}}}}|||z{{{{zzzyyyy{}~~~}||{{||||||}}}}}~~~|||~}~{xtomu|}{{|}|||||||{yxwwvtsprv{~}}}}~~}|zxvvusstw|~|yvtsqompty}}||}~~~~}{xusuy}}~~~~~}|{{{{{{{{{{z|||}}}}||||}}}}}}}}}}~~~~~~}}}}|||z{{{{zzzyyyy}~~}}~|||{{||||||}}}}}~~|z{{|~~|zwtnlt|}{{|~|{|||||{zyxvvusropuz~~}{{|}}}{yxvutsstw|~|yutsrompty}}||}~~~~|yvssvz~~~~~}|{{{{{{{{{{z|||}}}}||||}}}}}}}}}}~~~~~~}}}}|||z{{{{zzzyyyz}~}}~}{||{{||||||}}}}}~~{xy||~}{ywuoks|}{{|}{z||||{{zywvtrppnouz}~~|zz{|}|zxxvtsrrtw}~|xttrqonpty}}||}~~}}{xtrsv{~~~~~~}|{{{{{{{{{{z|||}}}}||||}}}}}}}}}}~~~~~~}}}}|||z{{{{zzzyyyz~~}}~}{||{{||||||}}}}}~~{xz||~ |yzwwqjq{|{{|}{z||||{{zywvsronlnsy}~~}}{yyz|||zwxvtsrrtw}~|wtsrpnnqty}}||}~~}}ywspsv{~~}||{{zz{{{{{{||}}}}}}}}}}}}~~}}||~~~~~~}}}}||{{zzzzyyyxxz{}}~|{{{{{||||||}}}}~~~|z{|} }{zyzxslqz|z{}~{z{{|{{{zxwvsqnmlmry}~|zyxxz{}|yxwtrqpqsx}~|wtsrqonpty}}||~}|zxtpqrw}}}~}~}||{{||||{{||}}}}}}}}}}}}~~}}}}~~~~~~}}}}||{{zzzzyyyxxz|}}|{{{{{||||||}}}}~~~}|~}~ |{zyxxunpy|z{}~{z{{{{{zyxvtrplkjlqx}~~}zyxwwy{|{zywtrqpqtx~~|vtsrqonpuz}}||~~}{yvsoprw}~}}~~} ~}}}}~~~~}}||}}}}}}}}}}}}~~}}}}~~~~~~}}}}||{{zzzzyyyxy{~|{{{||||||||}}}}~~~~}}}~ ~|zyyxxvpnw{z{}~{z{{{{{zyxutqnkjikpv|~}zxwvvvx{||zywtrqpqux~~|vtsrrpnpv{}}||~}|zxtqnorw}}||}~} ~~}~~~~||}}}}}}}}}}}}~~}}~~~~~~~~}}}}||{{zzzzyyyxy|~}{{{}}||||||}}}}~~~~~~~~~~{zzyxvtqov{z{}~{z{{{{zzyxtspmjiginu|~|zyvutuxz}|yywtrqpquz~|vtsrrpnpv{}}||~}|yxsomnrw}}||}~~}~ }}|||}}}}}}}~~}}~~~~~~~~~~~|||||{{zzzzyyyxz}}|{{{||||||||}}}}}}}~~~~{{zzyxwvpnuyy|}}{z{{yyzxxvtqomkifiov|~{xwtsstxz{zzyvtrqpqtyzvtssrqpqw||||}~~|zxvrokntx~}z|}~ ~}||}}}}}}}~~}}~~~~~~~~~~~|||||{{zzzzyyyz| }|{{{||||||||}}}}}}~}|}~~}{{zzyxwwrouyz{}}{z{{yyzxxvtqoljhfiov|~{xvtstuxz{zyxvtrqpqtzzwuttsqrsx||||}~~|zxurokouz~}y{}~ ~~}}}}}}}}~~}}~~~~~~~~~~~|||||{{yyyyyyy{~ |{{{{||||||||}}}}}}}~}|}~}~|{{zzyxwwvruyy}~}{z{{{{{zxvuroljgfiov|~{xvtutuxz{zyxvtrqpqu{zwutusrstx||||}~~|zwtqnkou{{y{}~ ~~~|}}}}}}~~}}~~~~~~~~~~~|||||{{yyxxzyy{ ||{{{||||||||}}}}}}}~}~~~~|{{zzyxwwvsuxy|~}{z{{{{{zxwuroliffiov|~{ywuvuvxz{zyxwurqpqu{{xvuutrtvx||||}~~|zwtpnkov}yy|~~ ~}}}}}}}~~~~~~~~}}~~~~}}||{{zzyyxyyz| }{{{{{||||||||}}}}}}~}|~~|{zzyxxwwwwtsxz|}{z{{{{zzywurolifeflu|~|yxuvvwxz{zyxwurqrsw|zxvvvstuvy}{{{|}yvqolkov}}zx{~~} ~}}}}}}~~~~~~~~~~~~~~}}||{{zzyyxzy| ~|zz{{{||||||||}}}}}}~~||~~|zzzyxxwwwwssx{}}{z{{{{zzywurolifeflu|~|yyuwwxz{}}|{ywutsty~{xwwvtvuvz|{{{|}yvqnkkqw}}yw{~~} ~}}}}}}~~~~~~~~~~~~~~}}||{{zzyyxz{~ ~{z{|||||||||||}}}}}}~~{{~|{zzzyxxwwwwssy{|~}{z{{{{zzywurolifeflu|~~}{zwxyz|}~}{zxwwx{~}zxxwvutvy{{{{|}yvqnkkrx~}xwz~~} ~}}}}}}~~~~~~~~~~~~}}||{{zzyyxy| ~|{yz{}}||||||||}}}}}}~~{{~~{{zzzyxxwwwwtsw|}~}{z{{{{zzywurolhfeflu|~~~|zxxz{~}|{yyz}{yxxvusuy{{{{|}yvqmjjrz}ww{~~}~}}} }} ~~}}}}}}~~~~~~~~~~}}|{{{zzyyz| ~||{z{{{{{{{{{{||}}||}}|}}||}|{zzzyyxxvxwtrvz~|{yz{||{zyxusoligfglu|~~~}{{{}}|}}}zxywutvz||||}~}yupljksz~{wuz~~~|}}~}zxx{{}~~~~~~~~~~~~~~~~}}|{{{zzy{|~ }{{{{{{zz{{{{{{||}}|||||}}||} |{zzzyyxwwxwurtz}|{yz{||{zyxutpmjgghlu|~~~}}}}~~~|zywutvz|||}~~}yupkjmt{~~zwvz~~|}}~}ywxxyy| ~~~~~~~~}}|{{{zzz|} }zzz{{{{zz{{{{{{||}}|||||}~}{{| }{zzzyyxwxxxtqry||{yz{||{zyxutqnjhgimu|~~~~}~~}zxvuwz|||}~~~}yupllou|}yvvz~~~~|}}}~|xvxxyy{~ ~~~~~~~~}}|{{{zz{|~ ~{yzz{{{{zzzzzzzz||}}||{{|}~||{| }{zzzyyxwwyxsnrx||{yz{||{zyxutqnlihimu{~}}}~ |xvvxz|||}~~~~~}yupklpv}}yuwz~~~~~~~~~}}||}}~{xxxyy{{|~ ~~~~~~}}|{{{zzz} |zxxxzz{{zz{{{{{{||}}||{{{|}~}||{| }|{zzyyxxxyvsnpw||{zzz}}|{zyvtrokhhinv|~}}~~~~~}ywuwy|||}}}~{wtojkpw}}yux{~~~~~~~~~}}||}|}}yxxzzz{{|}} ~~~~~~}}|{{{zz{} ~}zyxxxzz{{zz{{{{{{{{{{{{{{{||~}|{{| }|{zzyywxxzzuoqw|}|z{{}}}|{yxtspliijnv{~~~}~~~}|~}~~~~zwuwy|||}}}~}{wsnjkqy}}yux{~~~~~~~~~}}||||{{xxy{{{||}}~ ~~~~~~~~}}|{{{zz{~ ~~|}|zxwyzzzzzzzz{{{{zz{{zzzz{{{|}}|{{z{~ }|{zzyywwx{}xsrw|}}{||}}|~}|yvspnkjjnv{}~}~}{zyzz{|~}}|~{wuwy|||}}~~|~~~~~}{wrmkmtz~}yux{~~~~~~~~~}}||}|{zyyy{{{||}|~ }}~~~~~~}}|{{{zz| }}|zzzywyzzzzyyzz{{{{zzzz{{zz{{z{}}|{{zz}~ }|{zzyywxxz|zusw|~}|||}}|~zxtqoljlpw|~~~~}~~~}ywvuvwx{~||{}}wuwy|||}}~~|}~~~~~}{wrmkmu{~}yux{~~~~~}}}}}}}|~~}}|{z{z{{{}}}}~~}~~~~~~}}||{{{{{{} ~|zzyywwwyzzzzzz{{{{{zyzzyzzzz{{z{~~}}{{{{|~ ~~{zyyxxwwxx{{utx|}~}|||}~{yurommmry}~~~|{ywvtrrsuy~~}|||}}wvxz|||~~~~~}~}{urmjmv|~~|xux|~~~~~}}}}}}}|}}||||||||||}}}}~~~~ ~~~~~~~}}||{{{{{|} }{zywwwwwwwyzzzzzz{{{{zzyzzxyyzz{{z{}~}|{{{{|~ |}}{yyyxxwwwwyzvux{}~}|||}~{wsqpootz~}zywutsqpqty~~{zz|zwxz|||~~~~~~~}zuqmknw~~|wux|~~~~~}}}}}}}|}}||}}{{{{||}}}}}}}~~ ~}}~~~~}}||{{{{{|~|zxwvwwwwwwwyzzzzzz{{{{yyyy{yyyzz{{z{|~~}|{{{{{} }{}}{yyyxxwwvvwwwvwz}~}|||}}xusqrsw{~~zyxwvusqpqty~~{vxz~|xyz|||~~~~~~~~~~}yupnlpx~~|wux|~~~~~~~~~}}}}}}}|||{{||||||}}}}}}|||}|}~ }}~~~~}}||{{{{{|~{zyxwvwwwwwwwyzzzzzzzz{{xxxy{zyyzz{{z{|}~||{{{{z| ~~zy}~{zyyxxwwvuvvwvvz}~}|||}~xvvuuvy|~yxxwuusqpquz~~zvvx}|xyz|||~~~~}}~~~~}xtponqy~~|wux|~~~~~}}||||||||||||||||||||||||||}|{{}~ }}~~~}}}||{{zz{}~{xwvwwvvxxwxxyzzzzzzzy{{yyyzzzyyyyyz{{|}~}{||zz{|~~~~}v{~|zyyxxxwvuuvxwvy}~}||}~ {yxxzz{~~yxxwuutrqruz~}yutw{}zyz{{{|}}}~~~~~|yuqoosz~|wux|}~~~~}}||||||||||||||||||||||||||}|{{{|~}}~~~}}}||{{zz{}~zwvvwwvvwwwxyyzzzzzzzyzzyyzyyyxxyyyz{{|}~|{||{{||~}}}zw||zyyxxxwwvvvyvvy}~}||}~~ |{zz|||~~yxxwuutsstv{~~yusvz~{yz{{{|}}}~}~}yuqnot||vux|}~~~~}}||||||||||||||||||||||||||}}|{||}||~~~}}}||{{zz{}|ywvvvvvvxxwxyyzzzzzzzyyyyyyyyyxxyyyz{{|}~~|{||{{||||}{xx}{yyxxxwxyxvvtvy}~}|{||}~}}|~~~~zyxwuutsstw{{usty~|zz{{{|}}}~}|}~yuqnou||vux|}~~~~}}||||||||||||||||||||||||||}}|||}}~~~{{~~~}}}||{{zz{}{xvvwwwxxxxwxyyzzzzzzzyyyyyyyyyxxyyyz{{|}~}|{||||||{{}{yz}{yyxxxwxzyvttvy}~}|{||}~}~~~|zxxvvusttw|}wttx|}{z{{{|}}}~}{|~~|ytomou}zuuw|}}}}}||||||||||}}}}||||||||||}}}}~~}}{}~}|{} ~yz}~}}}}{zzz{z|~zxwwwwwwwxxxxyyzzzzzzzzzzyyxxyyxxyyyyzz|}~|{|||}|}}|z|{x{}{yxxxwxz~zussvz}}|zzz||~~~~{yxxwvvsuuy~}yutw|~z{{{{z{}}~~}{y{~~|xtpnpv}~~xttz}~|}}}}||||||||||}}}}||||||||||~~~~~~}}|~~}|{||yz}~}}}}|{zz{|~}zwwwwwwwwxxyxyyzzzzzzzzzzyyxxyyxxyyyyzz}~~|{|||~}~}|z|{x{}{yxxxwx|zuttv{}}|zzz||}~~~~{yxxxvwuvv{~}yutw{~{{{{{z{}}}~}{xy|~|xtpoqw}~}xstz}~|}||||||||||||||||}}}}}||||}}~~~~~~~~~~}|~|yz}~}}}}|{zz{}|yxwwwwwwwxxyxyyzzzzzzzzyyyyxxyyxxyyyy{{}~|{|}}~~}|z|{x{}{yxxxwy}zuttv{}}|zz{|||~~|yxxxvwwxw{~}zvtw{~|{{{{z{}}}~~{wvy||wtporx~~~|wrtz}~|||||||||||||||{{{|}}}}{{||~~~~~~~~~~~}}yz}~}}}}|{z{{~ |ywvwwwwwwxxyxyyzzzzzzzzzyyyxxyyxxyyyy||~}{|}}~}|z|{x{}{yxxxwy~yutrv{}}|zz{|||}~~~|zyyywwxwx|}zvuwz~~||{{{z{}}}~}wvy}|wtppsy~~~|vrtz}~|~}}||{{|||||||||||}~~~||||{||}}}}~~~~~~ ~z{}~}}}||{||} |wwwwwwwwwxxyxzz{{{{{{zyzyyyxxxxxxyyyyz|~}|}|}~|z{zv}}{yxxxxz}ytrrtz}~|zzz{{|}~|zyyyxxwxy}}{wuvz}}|{{{z{}}}}xvx{}}~~~{vsopty~{uruz}~{~~}||||{{||||||||||}}}}}}{||}}}}~~~~~~ ~{{}~}}}|||}} {wwwwwwwwwxxyxzz{{{{{{zyzzyyxxxxxxyyyy{|~}|~}|{zyw~}{yxxxy{~}xtsrtz~|zzz{{|}~|{yyzyxxx{~}{xvwx{~~}|{{{{|}}}}yvx{}}~~~{wsppuz~~{uruz}}z~~~||||{{{||||||||||~~}}~|||}}}}~~~~~~~~ {{}~}}}||}~ ~zxwwwwwwwwxxyxzz{{{{{{zyzzyyxxxxxxyyyy|}~~|~||yxx~}{yxxxy|~|wuurtz~}{{{{{|}~}|{{{zyxy{}{xwwx{|{}||{{{|}}}}~zwx{}}~~~{wsppuz~~~~zuruz}{x}|{{{{{{||||||||||}~~}||}}}}~~~~~~~~ {{}~}}}|}}~ }yywwwwwwwwxxyxzz{{{{{{zzzzyyxxxxxxyyyy|}~~} ||yxx~}{yxxxy|}vutqtz~~{{|{{|}~}|{{{zxyy{}{xwwy{{{||{||{z{}}}{xy{}}~~~{vrqqw|~~~}zuruz}{w~~~~~~}}{{{{{{||}}}}}}}}||~}|||~~~~~~~~~~~~~ |z|}~}|}}~ |yxxxwwwwxxxxyyzz{{{{{{zzzzyyyyyyyyyyyz|~~|{xvv}}{zyyyyz~~xutqtz~~}}}|z{|~~~~~{z{{{zyyz|~|{yxxyyz{{{{{||{|{}~~{wy{}~~~{vsqrx|}~}}~~|yssu{~}zu~~~~~~~~~~}}||||||}}}}}}}}}}||~~}|||~~~~~~~~~~~~~~|}~}|}~|zxwxxwwwwxxxxyyzz{{{{{{zzzzyyyyxxxxyxyz|~ ||yvv}}{zzyyxx||wvrtz~~~|{{|~}|~~~|{{{{zyyz}~}{zzzyy{z{{{{||||{}~~|wy{~~~~{vsqsx|}~}}~~|yssv|}zu~~~~~~~~~~}}}}||||||}}}}}}}}||}~~~~}|||~~~~~~~~~~~~~~~}}~ {ywvwwwwwwxxxyyyzz{{{{{{zzzzyyyywwwwyyy{|}|yvv|}{zzyyxxx|~zxsuz~}|{|~~z|~~~}{{{{zyyz}}{{{{{{{yy{{{||||{}~~|yz|~~~{wsqsx|}~~~~~}ytsw|}yv~~~~~~}}}}}}}}}}}}||}}}}}}}}|||}~~~~~}|||~~~~~~~~~~~~~ ~~}} ~zxwwvvwwwwxxxyyyzz{{{{{{zzzzyyyyxxxxyyz{~ ~}}yvv|}{zzyyxwvy~|xtv|~}|{|~{z|~~~}{{{{zyyz}}{||{|||zz{{{{{||{}~~|{{|~~~{wsqsx|}~~~}ytsx~~|yw~~~~~~~~~}}}{||}}}}}}}}}}||||||}}}}||}}}}}}~~~~~~~~ }~~~ {xwwvvvwwxxxxyzy{zz{{{{{{zzzzyyxxyyyyyy{} ~~}}vu{}{zzzzzyvw}}xtu{~~}||}}|{{~}{{{{zyy{}}|{|}}~}{zz{{{zz{{z|~||||}}~~~{wsruy}}~~~}xttw|{xw~~~~~~~~~}}}{||}}}}}}}}}}||||||}}}}}}}}}}}}~~~~~~~~ ~~~~~ |xxwwvvvwwxxxxzz{{{{{{{{{{zzzzyyxxyyyyyz|~ ~~wuz}{zzzz||~~xsu{~}||~}{{{{~}{{{{zyy{}}|{|}~~}{yz{{{zz{{z|~||||}~~~~{wsrvy}}~~~}xttw||xw~~~~~~~~~}}}{||}}}}}}}}}}||||||}}}}}}}}}}}}~~~~~~~~ }}}}~ ~zwxxvvvvwwxxyyzz{{{{{{{{{{zzzzyyyyyyyyy{|~ ~}zvy}{zzzz|~ysu{~ ~||~~|z{|~~}{{{{zyy|}}|{|}~~}{xzz{{zz{{z|~||||}~~~{wsrwz~}~~~}xttx||yx~~~~~~~~~~}}}{||}}}}}}}}}}||||||}}}}~~}}}}}}~~~~~~~~}}}}~~{xwwwuvvvwwxxyyz{{{||{{{{{{zzzzyyyyyyyy{{} ~}}yu{}{zzzz{}xru{~ ||~~|z{|~}{{{{zyy|}}z{|}~~}{xyz{{zz{{z|~||||}~~~{wssw{~}~~~}xutx||zx~~~~~~~~}}~~}|||}}}}}}}}}}||||}}}}}}}}~~}}~~~~~~~~}~~~~ }~~~|yxwwwvwwwwwxxyyzz{{{{{{{{{{zzzzzzzzyyyy|~ ~~|{yz~}|{zzzxyyx|{vrv{ }}}{{|~|}}{yyz{~~{z|}~~~}{xyz{{zz{{z|~{{|}}|~~|wstx{}~||||wuty}~}}~~~~~~~~~~~~}}~~}|||}}}}}}}}}}||||}}}}}}}}~~}}~~~~~~~~~~}~~~~~ {xwvwwwwwwwwxxyyzz{{{{{{{{{{zzzzzzzzyyz|} ~~}||}|{zzzxxwyyxsrv{~}}}{{|}{||{yyz{~~{|}}~~~}{xyz{{zz{{z|~}{{|}~|~~}xttx|}~||||wuty~~~~~}}~~}|||}}}}}}}}}}}}}}}}}}}}}}~~}}~~}}~~~~~~}~~~~ |zxwvwwwwwwwwxxyyzz{{{{{{{{{{zzzzzzzzy{{~~ ~}~}|{zzz{{|}zxssv{}~}}}{{|}{{{{zyz{~~}}~~~~~}{xyz{{zz{{z|~}{{|}~|~~}wttx|}~||||wuuy~~~~~}}~~}|||}}}}~~~~}}~~~~}}}}}}}}~~}}~~}}}}~~~~}~~~~~ ~|ywvvwwwwwwwwxxyyzz{{{{{{{{{{zzzzzzzzz|}~ ~}}|{zzz{{|}{xuvv{}~}}}{{}~|z{{{zyz{~~}}~~~}{xyz{{zz{{z|~~{{|}~}~~}wtty|}~||||wuuz~~~~}}}}||~~~~~~~~}}~~}}}}}}}}}}~~}}}}~~~~~~~~~ |yxuvvvvwwwwwwxxyyzz{{||{{{{{{{{{{zzz{{}~~~}|{{{zy|}|xvuw{~~}||{{}~}{{{zzyz|~~~~~}yxxy{{{{zz{|~{{|~~}}|wtvz|~}}}}~|wtv{~~~~~}}}}~~~~~~~~}}}}}}}}}}}}}}~~}}}}~~~~~~~~ }yxvvvvvwwwwwwxxyyzz{{||{{{{||{{{{zzz}}~~~~~|{{{yxyz{vtuy|~}||~|{{}~}|{{zzy{}~~}|yxxyzz{{{{||~}{{|~~}}|wtuy|~}}}}~|wtv{~ }}~~~~~~~~~~~~}}}}}}}}}}}}~~}}}}~~~~~~~~ ~zwwvvvvwwwwwwxxyyzz{{||||||~~}}}}}}|~~~~~~|{{{yyzz{usu{~~}{{}}{{{}~}||{zzy{}~~~|{yxxyzz{{||||~}{{|~~}}|vtuz|~}}}}~|xuv{~ }}~~~~~~~~~~~~}}}}}}}}}}}}~~}}}}~~~~~~~~ |yvvvvvvwwwwwwxxyyzz{{||}}}}}}~~}}}~~~{{{{{yyz{vuxz}~|zz}}z{|~~~}|||zzy{}~}}|{yxxyyy{{||||~~|{{|~~}}|vtuz|~}}}}~}xuxz~~~~~~~~~~~~}}}}}}}}}}||}}}~~|~~~~~~~~~~~~~}zwuuuuuvvvwwxyyyyy{{{{}}}}~~}~~}{}~~~~~~~{z{zzyy{vvz{}~}zzy}~~}{{}~}|{{zz{|~~||{zyyyyz{|||{|}|||~~}}{wuuz}}}}~}~yvx{~~~~} ~~~~~~~~~~}}}}}}}}}}||}}}~~|~~~~~~~~~~~~{xuuuuuuvvwy{zzzyyy{}{} }|}~~~~~~}z{{{yyzww{{}~}zyy}~}{z}~}|{{zz{|~~|{z{yyyyz{|||{|}|||~~}}{vuv{}}}}~~~ywy|~~~~} ~}~~~~~~~~~}}}}}}}}}}||}}|}}~~~~~~~~~~~~~~~yvtttttuvvy}~}|zzz|~~ ~|}~~~~~~~}{{{{yzxy||}~~{zy~~|zz}~}|{{zzz|~~{{z{yyyyz{|||{|~|||~~}}{utx|}}}}~~~yxy|~~~~~|}}~~~~~~~~}}}}~~~~}}||}}{}|~~~~~~~~~~~}}~~~ytsttssuvx|~|{|} }~}~~~~~~z{||{y{{}}~~ |z|~~{y{}~}|{{zzz|~}{zy{yyyyz{|||{|~|||~~}}{utx|}}}}~~~yxy|~~~~~| ~~}}~~~}~~~~~~}}}}}}|||||}}}~~~~~~~~~~~~ }xtstsrrsv{ ~}~~}~~~}z{{{zz{{}}~ ~~{zz}~}||z{y{}~{z{zyyxzxz||||{}~~||}}~}}zuuy{}}}}}~zy{|}~ | ~}}~~~}~~~~~~}}}}}}||||}}}}~~~~~~~~~~~~ }xustsssuy ~~~z{|}yyy{}}~{zz}~}||z{z{}~~|{{zyyxzxz||||{|}~||}}~}}zsuz|}}}}}~{y{|}~| }~~~}~~~~~~}}}}}}||||}}}}~~~~~~~~~~~~ |yvtsttw{ ~|~zxz}}~~{zz}~}||z{z{}~}{z{zyyxzxz||||z{|~||}}~}}ztvz|}}}}}~{y{|}~|}~~~}~~~~~~}}}}}}||||}}}}~~~~~~~~~~~~ |xsstty~ }~~|y}}~{zz~~}||z{z{}~}{z{zyyxzxz||||z{|~~||}}~}} zuwz|}}}}}~{y{|}~{ ~~~}|~~|~~~|||||||||}}}~~~~~~~~~~~~~~ ~xuuvz ~~~~~~~~}{|}}{z{~~~}|zz{{|~}|zzzyyyyz{{{{zz|}}{||{|{~ytvz|}}}}}|z|}~~~}}~{~~}|}}|~~~|||||||||}}}~~~~~~~~~~~~~~ ~yxy} ~~~~~~}~~~~}|}~~~{z|~~~}|zz{{}~}zzzyyyyz{{{{zz|}}{||{|{~ytvz|}}}}~}{}}~~~}}~|~~ }}}||||~~~|||||||||}}}~~~~~~~~~~~~~~~~}} ~}} ~~~~~~~~}|~~}}}}}~|}~{z|~~~}|zyz{}~}zzzyyyyz{{{{zz|}~~}{||{|{~ytvz|}}}}~~{}}~~~}}}~}~~}~}}||||~~~|||||||||}}}~~~~~~~~~~~~~~~||}}~ ~~~~~~~~|||}}|~}}~~||~{z|~~~}|zy{|}~~~}zzzyyyyz{{{{zz|}}}}{||{|{~ytvz|}}}}~{|}~~~}}}~}~~~}}}}}}}}||||||||||}}~~~~~~~~~~~~~~~~||}}~ ~ |~~~}~~~}}|{}}||~~}zz}~~~~~}}}||}~~}~~~~}zzzzyyzz{{{{{{z{~~||||{||}ztvz|||||~~|}~~~}{{|~~~~~~ }}}}}}}}||||||||||}}~~~~~~~~~~~~||}} ~~|}~zz~~~~~~~|~~~~}{{||||~~}z{~~~~}~}|}~}}|~~~~}zzzzyyzz{{{{{{z{}}||||{||}ztvz|||||~~~~~}{z|}~~~~}}}}}}}}||||||||||}}~~~~~~~~~~~~~~~~~~} |||||||~}}}|z}~}}}}z{~||||}~}~ }|z{~~~~~~~||{}~~~}zzzzyyzz{{{{{{z{}}||||{||}ztvz|||||~~~~~}{z{|~~~~~}} }|}}}}}}||||||||||}}~~~~~~~~~~~~~~~~~}~ ~}{{zz||}~~{{{~~~~z| ~}}}}y{~}|}~}}}}~~|{z{~~~~~~||{|~~~}zzzzyyzz{{{{{{z{||||||{||}ztvz|||||~~~~~}{yz|}}}~~~~}} ~{|||||{{{{{zz||||||}}~~~~~}}~~~~ |{zyyyyz|}}{{{{}}~}~}~}|~}~ ~~}~}zz|}~~~}{{|~}{{||~~|{zyzzyyy{{{{{{{yy{{||||{||}zuw{|||{}~~~~~~|zxz{}~~~~~}} }{||||{{{{{{{{{{{{||}}~~~~~}}~~{yyxxyyz|} ~|||{|}~~ |z{~~~}~~~}}z{{}}~~{{|~~}}~~}{{}}~}|{zyyyyyy{{{{{zzyyzz{{{{{||}zuw{|||{}~~~~~|zxy{}~~~~~}}}~|z{{||{{{zzz{{{{{{|||}~~~~ ~~~{zyxx{{|~~ ~}}||}~ ~~{xz}~}~~~~~~~~~}}{{|}|~|{|~}~}~~}{{~}}}|{zyxxyyy{{{zzyyyyzz{{{{{||}zvx{|||{}~~~~}zxxz|}~~~~~~}}|}}|zzz||{{{zzz{{{{{{{||}~~~~~~ }}|{{{||}~~~~}~ ~}~~~}|||~}~~~~~~}}}|}|}|~~}{|}~~~{~~~}{{~~}}|{zyxxyyy{{{yyyyyyyy{{{{{||}zwy{|||{}~~~}zxxy{}~~~~~~~~~~~}}}} zyzz||{{{zzzzz{{{{{|||}}~~ }}||~~~~~~ }~~~}}}~~~}~~~}}~~~|}~~~}z{|}~}|~}||~}|{zxwwwyzz{{zzzzyyzz{{{{|{{|~~zwy{}||{}~~~}{xvx{|~~~~~~~~~||}}}} zyzz||{{{zzzzz{{{{{{||}}~~ ~}}~~~~~ ~}~~}}z}}{}~~}|}|}|{||}}~~||}|}}~~~~~}z{}~~~~~}}}}|{zxwwwyzz{{zzzzyyzz{{{{|{{|~~zwy{}||{}~~~}{xvx{|~~~~~~~}}||}}}} {yzz||{{{zzzzzzzzz{{||}}~~ ~|{{~ ~~~~~~~}~~~~}wz}{~~||}~||{z{{{}~}}}|{{z{}}}~~~~~~}z{~~~~~}~~~|{zxwwwyzz{{zzzzyyzz{{{{|{{|~~zxz{}||{}~~}{xwy|}~~~~~~~||||}}}} {yzz||{{zzzzzzzzzzz{{|}}~~ ~}yxx| ~~~}}~~~}vx}~~}{}}}{|{{z{|~}~}|{yyz|||}~~}z{~~~}}~~}~~~|{zxwwwyzz{{zzzzyyzz|||||{{|~~zy{{}||{}~~}{xxy|~~~}~~~~~~~~}}||||}}{yz{{{{{{{{{yyzzyyz{|}||~~ ~}{xvuz ~~}{xz}~}}|~~~}wtw|~}{}}|z{zzz{{{}~~}~{{{yyyyxz|}}}~~~~z{|~~}}~}||~}{yxxxxxz{||zzzxyyyy{{z|||||~|yx{||{{{}~}zwxz}~~}~~~~~~~~}}||||}} {yz{{{{{zzzzzzyyzzz{{|||~~ {xvtsuz |wuvz{{z{}~{|}{uty}}~~{||}|{|{zz{|||~~|||{yzy{yzxwz|}}}}~~~~zz}~|}}~}||~}{yxxxxxz{{{zzzxyyyy{{{|}}||~~|yx{||{{{}}zwx{~~~~~~~~~~~~}}||||}}{yz{{{{{zzzzyyzzyyz{z{||~~ {xusrruz {trtxxwxyzzvyzxy||||z{z|{||||zzzyz|||~}{z|{yyy{zzxx{|}~~}~~~~zz}~~{{|~}||~}{yxxxxxz{{{zzzxyyyy{{|}}}||}}|yy||{{{{}}zwx{}~~~~~~~~~~~}}||||}}{yzz{{{{yyyyzzzzzzz{z{||~~ }wtrqqquz ~ytssvvvwxz~|utx~~{zyyx{}z{{zzzyyyyzz|}||{{{yyz|{{zz}~~}~~~~zz}~ }zz|}}||~}{yxxxxxz{zzzzzxyyyy{{|}}}||}}|yy||{{{{}}zwx{}~~~~~~~~}}||||}} ~{{{{zzzyyyyzzzzzzzyz{{||}~ xtppqqquz~ |wtstvutuvy~zwx{xxwyz|{{zxyzxwyyxxz|||}{{z{|~}}|}~~}~~~zz}}|{{}~~|{}~}{yyyyyyyzzzyyzzyyxz{||}}{zz}}{yz{|z{{{}~~}zwx{~~~~~~~~~~~}}||||}}~ }||{zzzyyyyzzzzzzzyz{{||}~ |urqqrrrvy}} {vutuvussuy}~}yy}}xwvx{|{{yxzz{z{zxxy||{||{{{}~}}~~~}~}|{{}~}|}~~~}{yyyyyyyzzzyyyyyyxz{||}}{z{}}{zz{|{{{{}~~}zwx{~~~~~~~~~~}}||||}}} ~}||{zzyyyyzzzzzzyxyz{||}~ }wsrqqrrsvx}{} ~ywvtuvvtstw|}||z}~}{ywvxz|{zxwz{}}}|zzy|||}||{|}~~~~~}}~~~~~~~~}||{|}~~}}~}}}{yyyyyyyzzzyyxxyyxz{||}}{{{||{z{||{{{{}~~~~}zwxz~~~~~~~~}}}}||||}}}~}}{zzyyyyzzzzzzxxyy{||}~ ztrrrrsssux~}{{} }zwxtuuvutuwz||||~~}z{xvxy{{yvvy|~|~~{{{}{z{{|||}~~~~}~~}}~~}}~~|y}{z||}~~}}~~~}{yyyyyyyzzzyyxxxyxz{||}}|{{{|{z{||{{{{}~~~~}zwxz~~~~~~~~~~~}}}}}}}|||||~ ~|{yyyyyyyyyyyyyyzzz|}}~~ |wsssssrstux~{z|~ ~zyyvtuvussuyz|}~{{zyvvz{zxvtv|~~zz|zyzz{{}}~~~~}}|{}~~~}~|yvy{zz{~~~~~}}~~|zxxxxxxzzzzyyxxwxyzz{{~~}|{|||{z{|z||{}}}~~}}~~yxxz~~~~~~~~~~~}}}}}}}|||||~}yyyyyyyyyyyyyyzzz|}}~~ |xurssssrstux}|z|~~ ~{{zwtuvsrsuwx|{yzwuvxyxwussuy{}}}{zzzyy{||}}~}}|{{{|~~}}|usx{zz|~~~~~}}}}|zyyxxxxzzzzyyxxwxyzz{{~~}{z||{{z{|z|{{~}|~~}}~~yxy{~~~~~~~~}}}}}}}}}}|||||}~ {zyyyyyyyyyyyyzzz|}}~~ ~{xvtsttttstuvx|~{|}}~ ~{{ywtturqruvy||yxwvtwywusqpqtvy{|}|{zyy|||}}}|{|{|}~~~}~{sry{z{}~~~~~|||||zyyyyzz{{zzyyxxwxyzz{{~~}zy|{{{{||z|{|~{{}~}}}~~{yz|~~~~~~~~||}}}||||||||||}} ~{{{yyyyyyyyyyzzz|}}~~~~ ~zxxvuuuuttstuvx|}}}~~} |{zywtturrstvy}|yxwuuvvuttqqprtwz{||}}||}}}~~~}{|}}~~~~~}|~~~}~}|try{z|}~~~~~{{|||zzzzzzz||zzyyxxwxyzz{{~~~zy{{{z|}|z|{}|y{|~}}}~~{z{|~~~~~~~~}}}}}||||||||||}}~ }{zyyyyyyyyyyyz{{|~}~~~ ~zxvvwwvvvuutuvvw|}~~ |{zzwttvtttvwy~{ywvtttsrrsrrtttuvz{}}~~}}~~||}~~}}}|~||~~~~~~~}|ss{{z|}~~~|{|||{yyyxyy{{zyyyyxwxyzz{{~~|{z{{{{{|{{{|~{z|~~~~~}}~~|z{}~~~~~~~~~~}}}}|||||||||||}}}~ |zyyyyyyyyyyyz{{|}}~~~ }|xwvwxyxvvuuuuvvw{ }~ ~~}{{{xvvwutuwz{|~}zyuuttsppoquuuvvutx{}~~~}|}~}}}{}}}~~~~~~~|{su||{|}~|{|||{yyyxyy{{zyyyyxwxyzz{{~~|zy{z{{{|{{{}~zz}~~~~~}}}}{z|}~~~~~~~~~~}}}}|||||||||||}}|} }{yyyyyyyyyyyz{{{|}~ ~|zywuvwyyywwvvvvwwx{~~~|| ~~}}|{||zxwwtutv{|yy|}|zxtsuusnlmqwwvvwvsvz~~~}||~~~~~~}~~~~}~|z|}zuw~}||}~~}}}|yyyxyy{{zyyyyxwxyzz{{~~|zyzz{z{|{{{}~xy|}~~}}}}zy}~~~~~~~~~}}}}}|||||||||||}}|}}~~ ~{yyyyyyyyyyyz{{{|}~}zyvvvwwyyyxxwwwwxxy{ ~~|yyy{}~ }}}}|{~{yvutuuvy{wvy{zxwtstttpklqvxwvxwtvz~}}||~~~|||}~~{xz}zxz{z|}~}|zzyxyy{{zyyyyxwxyzz{{~~|yxzzzz{|{{{}|wx|}~~}}||zy}~~~~~~~~~~~~~~~|||||||{||||||||}}}}}} |yyxxxxwwyyyyzz{|~ ~}{yxwwwxxxxxwwwwwwwxy{~}wuwxxx{}~ ~}|wuuvtwxxsqvxyywustwwpiipvyxxyxwvy|~~{|~}~~~|zz|}}||{|}|{|}{z{}|}~ ~|{yyyz{{yxxxxwwxz{zz|~{yyzzy{{{{{{|zvy|~~}|xxy}~~~~~~~~~~~~~~~~~|||||||{||||||||}}}}}}}}~zyxxxxwwyyyyzz{} |{yzxxyyyyxxyyxxxxxxxyy{}|ustuwvxz{}~ ~}xvvvtvvupnruxzwuttvwoffmtxxyyyxwwz||~}}z|~~~~~~}||{z{|}|}}{{z{||~~~|{ ~}~~{yyz{{yxxxxwwxyzzz|~{yyyyy{{{{{{|zvy|~~}{wvy}~~~~~~~~~~~~~}}~~|||||||{||||||||}}}}}}}}}|zzzzzyyyyyyzz|~ }|{zxyxxyyyyyyyyxxxxxxxyy{}}{wtsrtuvwx{||}~~} }xuvvtrrrnknsw{yvtttuoecjswxzzzyxvx{{{}{zz}}}}~~}{zz{{|~~{zz{|||||}} ~~~~{zz{{yxxyxwwxyzzz|~{yxxyyz{{{{{|~yvy|~~}{wvy}~~~~~~~~~~~~~}}~~|||||||{||||||||}}}}}}~~~ ~zzzzzyyyyyyzz}{{zyyyyyzzxxzzzzyyyyyyxzy{| ~|{vrpqtutvy{|{{{} }yvwvupopmknrw{|zutssoeckrwxz||{ywvxyw{~|y{}~{||~|zxxz{{}}{xyz||{|}~~~|{{{yxxyxwwxxyzz|~{xxwwyz{{{{{|~xvy|~~~zwwy}~~~~~~~~~~~~~~~~~||||||{{{{||||||}}}}~~}}}~}{zzzzzyyyz{| }|zz{yzzzzyyyyzzzzyyyyyyz{z{{}wpmotuqtyy{{z|~~ywwuupkmnkmqw}}yuttsohgkswy{||{zxwwwtx{z{~~{||}{xuvxz{}|xwx{|~|~~~}{zyyywwxxwxyzyz|~|xwwwy{{{zz|}~zvx|~~{vux}~~~~~~~~~~~~~~~~~||||||{{{{||}}}}}}}}~~}}~} ~||zzzyyyz}~ ~~{{z{|{{{zzyyzzzzzzyyyyzz{{z{|~||zrnkoutoqwy|{{}~~|xwvuokkollmu{ysrrtspkkmtwy{||{{xxwvtw~}z{~~|{{}|xwwxz||yyzz{}~ ~}|{yyywwyxwxyzyz||xwwwyz{{zz|}{wy|~~yuvy}~~~~~~~~~~~~~~~~~||||||{{{{||}}}}}}}}~~~~~~~||{{{|~ }||||{{||||{{{{{zz{{zzyyyy{{{{{|~~xrlinsrnpux|{{}~}ywwunmipomnrwtnnpturnlotwy{||{{yxwvsuy~|}|{{}}{|yyz{{x{}~} }|~~~~|zyyyyxwwxyzyz||xwvwyz{{zz|}|xz|~~yuvy}~~~~~~~~~~~~~~~~~||||||{{{{||}}}}}}}}~~~ |||} ~|{z{{|||}|||{{{{{{{||zzyyyyyy{{{}~{qjhkpropuy}}}}~zxwtmlhnpnnqsplkptvsomquwyz||{{yxwvsqu|~~|{{}}}|zz{{y{~ ~}|~}}~~|{yyyywwwxyzyz||xwvvyz{{zz|}|y||~~yuvy}~~~~~}}~~~~~~}}||||||{{{{||}}}}}}~~~~ ~}} }}|{{{|{}}|}||||{{{{{{{{{{yyyyzz{{|~|rljlmrttwz}}}}|~zxvrmiglqssromllquvspnruwxz{}{zyyxvtppuz~}}|}}}~~}yx{{{|~}}}|{|}~~~{yyxxvvwyyyz{}|xwvvxzzzzz{}~z|}~~~|yvwz~}}~~~~~~~~}}||||||{{{{||}}}}~~~~~~ |{{z{||}}}}|}||||{{||||||||{{{{|||||~~}zqlhkmsxzz|~}~}|{xvqkhflqvvrpmlnqtvurpruwxy{|{zyxxvvrrrvz{zz}~~~~~~}yx|}~~~~ ~}|z{|}~|yxwwvvwyyy{{}|xvvvxzzzzz{}|}}~~~|yvw|~~}}~~~~~~}}||||||{{{{||}}}}~~~~~~ |zz{{|}}}}}}|}||||||||||||||{{{{|||||~}{yslhikr{}~~~~}|| |yvoheelrvvtqnnnqruusrtuwwwz{}|zwvwxvttvwxww|~}}}zz}~~~~}|zy{{|~zvwwvvvxxyz|}|xuvvxzzzzz{}|}}~~~|yvx}}}}}~~~~~~~~}}||||||{{{{||}}}}~~~~~ }zz{}}}~~~~}}|}||||}}||||||||{{{{||||}~ zumhhjr|~~}{| |yuogcelsvvvspoprqsvtuuuvvvz{}yvvxxxusuxxxw{~}|}|{~~}~~}|zyyx{|{vvvvvvxxyz{}}xtvvxzzzzz{}|~}~~~|yvy}}}}}~~~~~~}}}}||||{{z|||}}}}~~ |{zz{|~~~~~}}}}|||||||||||||||||||}}||~ ~{slklu~~}}~}}z| |ytnf`emrtwwtqqrsrtuusstuttyz}}xttuwxwstwwwx|~~}||zz~~~~~}|| ~~~|{zzyz||xwvvvvxxy{z}}xttwyzzzzz{~~}~~~~~~~}{ywy|}~~~~~~~~~}}}}||||{{z|||}}}}~~ |z{z{||~~~~~}}}}|||||||||||||||||||}}|| }zuopw}}||~}|~}z{}ztnf`fnstwwutstsstuvttutqswx||wrruvywstvvwx{}~|||zyyxwy|~}|} }}~}{zzxwz}|zxvvvvxxyzz}}xttwyzzzzz{}}~~~~~~~}{ywy|~~~~~~~}}}}||||{{z|||}}}}~~~~~ ~|{{|{}}}~~~~~}}}}|||||||||||||||||||}}}} }ztsw}~}{{{|~~|z||y{ztnfbfmsvxxwwuuttuvwvvutqruvzzwrqsuxvttvvwwz|}~~}}}zy{yvvuy|~}|~ {{|}zyyxwy{~~{yvvvvxxyz{~}xttwyzzzzz{}{|~~~~~~~}{ywy|~~~~~~}}}}||||{{z|||}}}}~~~~~~~ ~{|{{|||}}}}~~~}}}}|||||||||||||||||||}}~~ ~yux}~|zyy{}~|z|{xz ztnedemsuwwwwwvuutvvwxwvqruwxxuppruwttttvwxy{{}~~~}~~{{}zyvrrw|}|~ |zzz{{yyxxxz}}yvvvvxxyz{~|wstwyzzzzz{|y{|~~~~~~}{ywy|~~~~~~}}||||||||||{|||~~~~~~~~~~~}~~|{{{|}}}~~~~~~}}|||{||||||||||||||{|}}~~| ~yy{~~~~zyyzz}~~}{xz ztniefnruvvxxxutttwxyzzxtruwwvrporttrrrtuvxzyz|}}}}~}~~}{vrot}}~|yyyyzxxxvvz}xvttwxxxy{~zustwyzyyxz}~{xz}}|||zxwy| ~~~~~~}}||||||||||||||~~~~~~~~~~~}}}}|{{{{}}}~~~~~~~}}|||{||||||||||||||{|~~~~} ~zy}}zzyyy{~}zwyztojfgnruvvxyxvutuwxy{|zvtvxwuponpqqportuwyzyy{}~}}~~|wstx~~{xxxxyxxxvvy{~|xuuwxxxy{~zurtwyzyyxz}}ywz}}|||zvx{ ~~~~~~}}||||||||||||||~~~~~~~~~~~~}|||~~{{z|||}}~~~~~~~~}}|||{||||||||||||||{|}}}}~ |y{|zyyyxz}~|wuxztojfgnruvwwxxuttvwxy|}ywuwxwtpnnooonorsuwyzyz{}~~~|yuty~zwwvwxwxxuvy{}}xuuwxxxx{~ytqtwyzyyxz}}xwz|~}}|yvy ~~~~~~}}||||||||||||||~~~~~~~~}}~~}||{|}~~}|||{}}}}}~~~~~~~~}}|||{||||||||||||||{|}}||~|~{{{zzyywxz~}|vsxztokggnruvvwxwtssuxxy||ywvxyvspnnnmmlnrtuxy{yz{|~~~~}vrt~}{vvvvxwxxvvxz|~zwuvwwwx{~}xtqtwyzyyxz}}vvz}~}||yx| ~~~~~~~~}}||||||||||||~~~~~~~~~~~~~~~~~}}||}|||||||}}}~~~~~~~~~~~~}}|{||||||||}}|||||||||}z} |{~{|{yyvwy~~~|zurw|unkhhnsstuxywsrsuxyy||zxxyxvsomnmkhkmqtuxz{{{z{|}~~}~~yutz|yuuuvvvvuvwwyz|~|ywwxxxx{}xsrtwxyyyx{~}uu{}~~}|{x{~~~~~~~~}}||||||||||||~~~~~~~~~~~~~~~~~|}}||||||}}}}~~~~~~~~~~~~}}|{|||||||||||||||||||}}|{|yx~~zz}|{|zywwx||~{ytrw }umkginstuvxxvpmoqqtvyzzwwxxuromlhfgimqtvy{||{{{{|~}{||{xw~|wuttuvvvuuvwxy|}yvvwwwx{~}xsqtwxyyyx{|tu{}~~}}|{~~~~~~~~}}||||||||||||~~~~~~~~~~~~~~~~~|}}||||||}}}}~~~~~~~~~~~~}}|{||||||||{{|||||||||} ~{}xx{}zssy|yuuz~~}~~|~}zxwvyz{zwtqw }umkhjnstuxxwrkghkknrtwxvvwwtqokhddggkruwz{|}|{{z{ ~}zxz|}~}}}zwz{wtsstvvvuuvwyy{~{wwwwwx{~|wrqtwyyyyx{ysu|~~}||~~~~~~~~~}}||||||||||||~~~~~~~~~~~~~~~~~|}}||||||}}}}~~~~~~~~~~~~}}|{||||||||{{|||||||{|}}z{wvxzxsswzvqqu~~}{z|}{zxwxz{yvspw~vnkiinsuvwxumecceilpruuuuvwtqnjeeeigksvy{|}}|{z{| ~|wttwxy{{|~}xz~yvsrrsvvvuuvvxyz~}wvvvwx{~|urqtwz{yyx{~}wrv|~~~|{|~~}}}}}|}}||||||}}}}}}~~~~~~~~~~~~~~~~}}}}||||||||~~~~~~~~~~~~}}}|||||||{{zz{{{{|||||}|z}zsttuvttvtqooov~|zyx{~~|zwvvxyzwrqx }wokihlruxxwsh`^^^cjoqsqrrttrqmhdddiimrx|~~~}{z{}}{}}ywvvxvvtvz}}yx}|yvssrsuuvutuwwxz}~ywwwwx|~~zuqptwz{yxx{}vsx}~~}{|~~~~}}}}}}}}||||||}}}}~~~~~~~~~~~~~~~~~~}}}}||||||||~~~~~~~~~~~~~~}}}|||||||{{zz{{{{||||{|}}xy{xrtvvwvxurnoonqw~|xvv{~~}zxvvxyywrry}vpmiimrvxxwobYWYTYfoqsqrrrqonkgeffjkmpuy|~~}zzz}}zz|~~zyywy{zxvz{|ywz|xussrsuuvutuwwxy|~~{xwwwx|~~zuqqtwz{yxx{}uty}~~~|{}~~}}}}}}}|||||||}}}}~~~~~~~~~~~~~~~~~~~~}}}}||||||||~~~~~~~~~~~~~~}}}|||||||{{{{{{{{{{{{z{|zwxzwruy{{}~yrnoonnt}}xvvz}~}{xwwxyyxuuz }wqnjimsvxvvobUPQMVdorsqppommjkihggjlnosvz}~|zyz}~{z|}~{ywx}~{{}}|wvy}|wtrrrsuuuttuwwxx|}|ywvww{~~zuqqtwz{yxx{~{vty}~~}{|~~~~~~}}}}~~}|||||||}}}}~~~~~~~~~~}}~~~~~~~~}}}}||||||||~~}}~~~~~~~~~~}}}|||||||{{{{{{{{zzzzz{}zwwzxru{}}{sooonns}~xuv{}~}{yxxxyxywwz }yrmjimsvwuvqeRKKMXenstqomliigjjihfimopsuz{}}{yyz}}{|}||ywv{~~}~wuvx{~~|wtqqrsuuustuwwxx{}}}ywuvwz~~ztqqtwz{yxx{~{vty}~~|z}}}}}~~~~}}}}}}}|||||||}}|~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}}}{||||||}}}}~~~~~~~~~~}|}|||||||{{zz{{{{{{{{{|}yvuzvorxy{}tqoopos|~vuv{}~}{zxwxxwwwxz}tmjkosuuuvqhSKLS]hqttrpnifeegiigefmqqsuwz{{yxxz|~}|}|{| ~{yvz}}ztrsx}}~~|vtqqrstttstvvvwwz||}~ywuvx{~yrpquxyyyyx{{uty}}~}{~~}}}}~~~~}}}}}}}|||||||}}|~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}}}{||||||}}}}~~~~~~~~~~}|}|||||||{{{{{{{{{{{{{|}ytswumotsvz}|tqpponr{~wuv{}~}{zxwxxwwvxz~wplmqsuvuuqkYSSZckqtsrpojgeefffefgmrrsuwyzzxwvy|}}}~}|~|wx|~~zvsrvvy}|vsqqrstttstvvvuvy{|}ywvwx{~xrpquxyyyyx{ytty}}}||~}}}}}~~~~~~}}}}}}}|||||||}}|~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}}}{||||||}}}}~~~~~~~~~~}|}|||||||{z{{{{{{{{{{{||xqorqlmrssw{ztqqqooq{yvw{|}}~}|{yxxxvvuwy|~{}ysopstuvttpk`ZZagmqsqonnmjhfecccdhnsttuxyzywvtw{}}}~~ zx{|~~{xvspsw|~{urqqrstttstvvvvvyz|}ywwwx{~xrpquxyyyyx{~ysuz}}}|~}}}}}}~~~~~~}}}}}}||||||||}}|~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}}}{||||||}}}}~~~~~~~~~~}|}|||||||{z{{{{{{{{{{{||wokmmlmqrqu{ztqqpqoq{zww||}}~}}{zxxxuuuvwz~~z}{urrvvuvtrojb^]dinqpnlkmomkiecab`fouuuvxyywusrtx}}}~~ }yz{~~}|yopsy}~zurqqrstttstvvvvvxz||zwvvx{~wrpquxyyyyx{ztuz}}||~||}}}}~~~~~~~~~~~~}}}}}}}}}}~~}~}}~~~~~~~~~~~~~~~}}}||||||||}}}}}}}}~~}}||||{|}}|{||||||{{{{|}|wohhklosqosz{uprssqt{|wx||||~~||{ywvuttvww||{}yvuvututrnieaagnpomkhgjnqolgd_[[dpvwwxxxwtonprw|~~~~}}z{|}~~ztpqw|~~~ }yurpqssssstuvvuuuvx{}|wvvx|}vqqrvxxxwxx{zttz~~|~}}}||}}~~~~~~~~~~~~}}}}}}}}}}~~~~}}~~~~~~~~~~~~~~~}}}||||||||}}}}}}}}~~}}||||{|}}|||||||||||||}}woihjknsoorx{vqruusv}|xy|||||}}|{zxwsrtuvwz{{}{xvvvvwurnjgdfkppnjgddglqpnif`[\epvxxxxvsqlkorw{~~~} ||}}~~{wtnsu{~~ |xtrpqssssstvvvuuuvx{}|wvvx}|upprwxxxxxy{~~ytuz}}~}|}}}||}}~~~~~~~~~~~~}}}}}}}}}}}}~~}}~~~~~~~~~~~~~~~}}}||||||||}}}}}}}}~~}}||||{|}}||||||||}}}}|}}xojjjkmpmnpv|xssvvtv|~|yz||||{|}|zywvsstuvvx||}~~|ywwvwwwtoligjnpolhcaadiopnjgb^_gqvxxxxurnihnsvz~~~~~~~ }|~~~yrqoux{|}~ |xtqpqssssstvvutttuwz||wvvx}~{uposwxxxwwy{~~xtvz|}~{y{||||}}~~~~~~~~~~~~}}}}}}}}}}}}~~}}~~~~~~~~~~~~~~~}}}||||||||}}}}}}}}~~}}||||{|}}||||||||}}}}|}~xqlkjkmnlknu{zusvvtx}~}yz||||||||zxwvutstuvw{|}~}}zxxwuuwtomkjlppokf`^]`fmomjgca_hruxxxxwrkegmtvz}~}}~~~~}|~~~ysprtwwwz{ |xtqpqssssstvvusttuwz}|wvvx}}{uqoswxxxvwy{wsux|~}yx{||||}}~~~~}}}}}}~~}}}}}}}}}}~~~~~~~~~~~~~~~~}~~~~~~}}}|}}||||}}}}}}}}~~~~}}}}}}{{{||||||||||||||||}~zrljkkmlhhks{zwuuuvy}~}z{|}|z|{{{zywvvsrruux|~~}}}{zxxuuwtpnlmnqrpkd^YY]cjomidb^_fptvvxxuoidfovwxyzz{~~~~~~}~{yqoqrrty}|wtrqrrrrrstvvutsstwy|{wuvy|~ztppswyxxxwy{~|urvx{{vy{{{{{||~~~~}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~~~~~}~~~~~~}}}|}}||||}}}}}}}}~~~~}}}}||{{{||||||||||||||||}zrlkkmnjefiqyzywuvwy}~}{{|}|z|z{zzyxvvtsrttvz}}}|{xxyyxuollnpqqokd\VW]djmkid`]`fortuwwtmgbemtvxzyzz}}}}~ ~}~~ztqqsv{{wsqprrrrrstvvutsstwz}{vtvy|~ztppswyxxwwy{~{vsvx|yuy{{{{{zz~~~~}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~~~~~}~~~~~~}}}|}}||||}}}}}}}}~~~~}}}}||{{{||||||||||||||||}zrljlllgdfjqy|zxwxxz}}|||{||z{zzz{yywvussstty}}}|{yyyyyvnjmoqqqokcZVW^figecba`bgnqrtuurlfadkruwzz{|{yy{} ~}~yvux{~{wrpoqrrrrstvvutsstw{~zusuy|~ytppswyxxwwy|~zvtux~|wuy{{{{{zz~~~~}}}}}}||}}}}}}}}}}~~~~~~~~~~~~~~~~}~~~~~~}}}|}}||||}}}}}}}}~~~~}}}}{{{{{||||||||||||||||}zskhklkfdfjqy~zzxyyz}}|||{||zzy{{|{zywwsrssty~}}|{yyyyyunjmppqqokbZVX_ed`^^_abdhmpqsttqjebdjquwzz{}|yuw{ ~}{yxz|~~{vqpoprrrrstvvutsstw{~zusuy|}ytppswyxxwwy{}zvsuxzsty{{{{{{{}}~~}}}}||}}}}}}}}||}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}{{||}}}}}}~~}~~~}}||||zz{{|||}}}||||}}}}}~~{rkhkmkedgmr{}{yzzz|}|{{|||{{zzyzzzzywtsrrsw~~}|||yxyyxunklopppmibZWYbc_XYZ\^cfjmpqrstqkgcciouwz{|}|yvuy~~}~|}}~~zupoopqqssstvvusssrv{~{vsuz}|wrnpsxyyyvvy{~ystv|~wrty{{{zzz|}}~~}}}}||}}}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}||||}}}}}}}}}~~~}}||||{{{{|||}}}}}}}~~}}~~~|qlijkiedips||{zzz|}|{{||||{zzy{{zyywtrrrsv}~}|{wwxxxuplloooonic]Z_ie[QTTV\ehknpqrrrpjfbdiotvyz|~}}|{}~ ~~~~~~~~~yupnopqqssstvvusssrw|zusuy||wroqtxxxxvvy{}yssw~{sruz|{{zzz}}}}}}}}}||}}}}}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}||||}}}}}}}}}}||||{{{{|||}}}}}}}~~}}~~}rkijkieeksu|}|||z|}|{{||||{{zz{{zyywsqqrsv{~~{vuvwxupmlkklmmke^^gnjZLOOPXbgjlmpqrqoiebcimrvz{|}}~~}~~~~~~}ytpoopqqssstuuussstx|ytsuy||wqpqtwwwwvvy{|wstxyrrv{}{{zzz~}}||}}}}||}}}}}}}}~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}{{{{}}||}}}}}}||||||{{|||}}}}}}}~~}}~~}tmhikjefltw|}}}z|}|{{||||{{zz{z|{ywtppqsu{|vttuxuqmjfejmolg`akok[IJJMS_gighnpqqnhcachkpw{||{|~ ~~~~}}~~~~~~~}ytonopqqssstttussstx}ytsux{|wppruwwwwvvy{{vsu{~xrrx|}{{zzz}}}}}}}}||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}||||||}}}}}}}}}}||||||||||}}}}}}}}~~~~~}tkhhijfhotx|~~}~|||{{{{|}}{{{zz{||zxrpoorsx~}wrstvwsoiecfjlkhcfjlh\PJGHP[egcbiopolgc`adhmv{|{{} ~}~~}}}~~~~~~~~|wsonoppqssstuuussstw}~xttvy{{vopruwwvvvvy{{wsv}{vrrw{{zyz{{}}}}~~}}||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}|||||}}}}}}}}}}}||||||||||}}}}}}}}~~~~~wliiijilpu{~~}}}~~}|{{{|~~|||{{{{{xvqomorrv}}xrrruvtohccfhhfhfikmi_YNKJPYec]]emnokfb_^aelw||{{| ~~}||~~~~~~~~~~|wrpmmnpqssstuuussstw}~xttvy{zuopruwwvvvvy{{vtx}xusrv{{zyz{|}}}}}}}}||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}|||||}}}}}}}}}}}||||||||||}}}}~~~~~~~~{oijkjnptx}~}||}~|{{{}~||{{{yzzwtpnmnqqtz~xrqqtvsohccghg_adjlmhaXPNLOXfaWXajmnkfb_]_blu|}{{} ||}~~~~~~~~~~|xspmlmpqssstuuussstw}~wstvy{ztopsvwwvvvvy{{wt{{wurrw{{zyz{~}}}}||}}||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}|||||}}}}}}}}}}}||||||||||}}}}~~~~~~~~~rmkkkpswz}~~|||}}|{{{}~}}{zzyyzvspnlmoqrx~}xrpqsusogcfhie[Z_immhbVRNNQZgbUT^gmnjeb^]`bmu}}|{} }}~~~~~~~~~~}{xronmnpqssstuuussstw}~vstvy{ytopsvwwvvvvy{{wv}}zwtqsx{{zyz{||||||||||||}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}||||||||||||}}}}}}{{}}}}~~}}~~~~}}~~uomlnuwy{~}{|}}~~|{{|}}}}|{{zywtrqmjkoqqv|}{vqnorsroheegkg\UYdljg`VSTV[ah`VPZekkhb`^\`dmv||{| }}}~~~~~~~~~}{wsollmprrrssttssrrux~|vssuz{xsopruwvuuuvx{ywzzwvrosyzzzyz|||||||||||||}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}||||||||||||}}}}}}||}}}}~~~~~~~~}}vrpnpwy{}~|}~~}z{}}}}}|{{yxvsqoljknppsx|}}zxspopqqqogceini]UW^gfe^WV[^`eh`XOVcig^ZY[\_dnu{~|{{~ ~}}~~~}}~~~~}{wsolkloqrrssttssrrvz~|vssuy{wrnoruvvuuuvx{xx|}xuusqw{{{{{{~||||||||||||}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}||||||||||||}}||}}~~}}}}~~~~~~~~}}~ }xuustx{|~}|}~|}}}}}}|{zyxvsqnmkkmopquxxywvqpnnppqogbelpl_VWYbcd^XY_`_ae^ZPS`hcVTV\\_dnty~}|{}~ }}}~~~}}}}~~}{wsolkloqrrssttssrrvz{vssuyzvqnoruvvutuvxzwz{xwywv{~~~~~}||||||||||||}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}||||||||||||}}{{}}}}~~~~~~~~~~}}}}zwwuvx{~}{|~~ ~~|}}}}|{zyyvsqnmllmopqruuutsqpmnppqofbelombVUX_bb^\\``\]`\YPS^hcTOU\^`dntx~|{|}~ |}}~~~||||~~~}{wsoljknprrssttssrrvz{vssuxyvqmnruvvutuvxxw|zxwz{| ||||||||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}{{||||}}||||||||}}}}}}}}~~~~~~~~}|| ~||yyy|~~}{|~|}|~}||zyxvspnnnmklorsrrqpqsqmknopogbeljidXQV\bca_ab_[X[WSPT^fcSJOZ\`emsx~~}||}|~}{}~~}||||}~~{yvsojklopqrtttrssqrvzzsrsuyyupnoruuttstvxxy}~{{~||||||||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}||||||||||||||||}}}}}}}}~~~~~~~~~}}}{z|~~}{} ~}}~}||zyxvromnnnjknqrrrpqoqtollnnmhdfhe``ZRU[ab`_ac`[WUSOQV]cbTKNY]ahoty}~~~}~}~~~|}~~}||||}~~{yvsojjkopqrtttrssqrvz~zsrsuxxtonpruuttsuvwy{ ||||||||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}||||||||||||||}}}}}}}}~~~~~~~~~~~z}~}|~ ~~}||zyxvromlmmkjnprrssqnorqlklnmieghc][[TWZa`^]`ee_ZWTOSW]baVLOX_djquz}}~~}~ ~~~~}~}||||}~}zxvsojjknoqrtttrssrsw{~ysrsuwwtonpruuttsuvvz~ ||||||||}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}{{||||{{||||||||}}}}}}}}~~~~~~~~~~ }z}~}|~~}||zywvrnljllkimprstuqnppqjjkpniegic[YZVW]b`\W\gkfaYTQWZ^baWMOX_fmtvy}}~~}~ }~}||||}~}zwvsojiknoqrtttrsssuy}~ysrsuxwsonpruutsstuwz ||}}||||}}}}~~~~~~}}~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}{{||{{{{zz||||}}}}}}}}~~~~~~~~~~~~{z~~}~ ~~|{zxwtrnjklljjlorrturmonkhmnonkghicYY[Y\`a^VPZinjbZVX]]_cdZOR[cjpvy{}}~~~~ ~}~~}|||||~|{xurniijnpqrtttrrrruy}~yrqsuwvqnnpsutsstsvy~ ||}}||||}}}}~~~~~~}}~~}}}}}}~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}||||{{{{{{||||}}}}}}}}~~~~~~~~~~~ zz~~~~ }}zxwtqmjjklkjkorrttrmonikoopoliijd[Y\]_b`[QP\ioiaZZ`gdcef]UY^gntwz|}}~~~~ ~~~}|||||~~{zwurniijnpqrtttrrrruy}~xrqsuwtpnnpsutssssuz }}}}||||}}}}~~~~~~}}}}}}}}}}~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~||{{{{}}||||}}}}}}}}~~~~~~~~~~ zz~}}~~ }|zxvsqmjhjjkjkoqrtsqnmkkoqpppljjkf]Z\`ac`WNT_koja\]dmlghg`Y\bjqwyz|}}}}~~~}|||||~~{zwurniijnpqrtttrrrsvz~}wrqsuvtpmnpsutsrstu{ ~}~~}}||||}}}}~~~~~~}}||}}}}}}~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}}}||{{{{||||||}}}}}}}}~~~~~~~~~~ z{~||~~~~ ~|zxvspljgiillknqrttqmmknrrrppljkle^\^abb]QLWdmojd__eoojkha[_clrwyz|}}|| ~~~}|||||~}zyvurniijnpqrtttrrrtwz}vrqsuvtpmmpsussrsuw| ~|~|{{}}}}|||||}}}~~~~~~}}}}}}}}}}~~}}}}~~~~~~~~~~~~~~~~}}~~~~}}||{{|{{||||||||}~~}}~~~~~~~~~~~ }yy||{|zzz}~~~~{zwqnmighjmnmnqrttrnlmqrppopljkke]\`deaYNQ\fnnjfcafooiji`Y\dmtxzz|}}}}~ }~}||{{{~~|zywvsokjknoqrtttrrrsw{|uqqsuvromnqsutsssvy{y{||y{}}}}|||||}}}~~~~~~}}}}}}}}}}~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}||{{|{{||||||||}~~}}~~~~~~~~~~~ }yz}}{{zyyyz{}~}}|{wqnmjhikopporstttqnorqpoopnlnkbZ]bfe_VPV`hmligdbgpmffhaXZcnvxzz|}}}}~~ ~}~}||{{{~~{zxwvspljknoqrtttrrrsx|{uqqsuvqomnrsttsssv{}yuvxz{y|}}}}|||||}}}~~~~~~}}}}}}}}}}}}||||~~~~~~~~~~~~~~~~~~~~~~}}||{{|{{||||||||}~~~~~~~~~~~~~~ {{}|zyxwwwwy{||~}}{xqnmkiikoqsqrssstsqrsqqqqppnol_Y]dhe]UR\fknligebhpkcbfb[\dpvy{z|}}}}~~~~~~}||{{{~~}{yxvvspljknoqrtttrrrty|zuqqsuupmlnrsttsrrx~~|wrrtwz{y|~~}}}}|||||}}}~~~~~~}}}}}}}}}}||||||~~~~~~~~}}~~~~~~~~~~~~}}||{{|{{||||||||}~~~~~~~~~~~~|{~}|zxxwvwwy{|} ~}}{xqnmkijkoqssuttsusstspqrsrqpol`Z_fhe]TWahnpmigedioibaec^`gpvy{z|}}}}~}}{||~ ~}||{{{~~|zxwvvspljknoqrtttrrruy|zuqqsuupllnrstsrrr{{vsnnswz{y}~||}}}}||}~~~~~~~~~~~~~}}}}||||}}}}~~~~~~~~~~~~~~~~~~~~~}}}||||||||}}}}}}}~~~~~~~~~~~~~ ~~~|~|xxxxvvuw{|| }|||yrnmkkklpruuvtttttuvtqrtsrrqpi`[ajmd[U[dimnkgddglmicdfcadjquy||}~~~~~}||{|~ ~~}{zzz{}~}{zxxwvtqmklmoqsssrrrruz}yspqrttplloqtssrqt|~xtqpmnrxyz{}|}}}}}}}|~~~~~~~~~~~~~~}}}}||||}}}}~~~~~~~~~~~~~~~~~~~~~}}}||||||||}}}}}}~~~~~~~~~~~~~~}|}zxxxxvvuwz{| }|||zrnmmmlmqswwvtuuttvusrruusrqpi_\dmne[X_fkmmjgdehllidfgecglqv{|}}~~~~~|||z|~ ~}|{zz{}~~}{zxxwvtqnllmoqsssrrrrvz}~wrpqrttpllortssrrw~~xroommnryyz|~|z}}}}}}}}~~~~~~~~~~~~~~}}}}||||}}}}~~~~~~~~~~~~~~~~~~~~}}}}||||||||}}}}}}~~~~~~~~~~~~~~ }{|zxxxxvvuvz|}~ }|}|zrnmoonortxxwvvvttvtsrsuusrqpj_^fnog^]dilnmjfefikkhhhhfeimquz|}}~~~~~||{z|~ ~~|{zz{}~~~|zxxwvuqnlmnpqsssrrrrvz~~wrpqrtsommpstssrsy~xtponmlmsyz{}~{z~~}}}}}}~~~~~~~~~~~~~~}}}}||||}}}}~~~~~~~~~~~~~~~~~~~~}}}}||||||||}}}}}}~~~~~~~~~~~~~~ ~|{{yxxxxvvtvy{|~~}|{{{snloqqrruyyxwvvttusrrtvusrqqj``gnqiccfkmnlifegiiigjjhfgjmqvy{}}~~~}}|{{z{~}~}{zz{}~~~|zxxwwuqnlmnpqsssrrrrvz~~uppqrtronnpstsrqu{ztqqponlmtzz{~}zy~~~~}}|~~~~~~~~~~~~~~~}}}}||||}}}}}}}}}}~~~~~~~~~~~~}}}}}}}}||}}}}}}}}~~~~~~~~~~~ ~|z{yxxxxvvuvxz|}}}z{|ytomprstuw{{zxwuttvtrruuttrqrmedjoolghknonigfeghggimkigjloqvz||}~}~~}}{{z{}~~}~}{{{{|~}|{zxxwvupnmmoqqrssrrrtx|~vqpqstqnlmpsssrrxytrponnmlnswy{~{zy~~~~}}|~~~~~~~~~~~~~~~}}}}||||}}}}}}}}}}}}}}~~~~~~~~~~~~}}}}}}}}||}}}}}}}}~~~~~~~~~~ }{yzxwwxxvvvvvutu{ }z{{zvompsuwx{{{|zwvuuvuttttsrrqrnhgmponjkmpokeedcdfccjmljiknpswz||}}}~}{{{zy{}~~~~~ ~~~|{{{|~}|{zxxwvuqnmmoqqrssrrruz}|tqpqstqmlmpsssrsz}ztqqponlklotyy{}zyy~~~~~}|~~~~~~~~~~~~~~~}}}}||||}}}}}}}}}}}}}}~~~~~~~~~~~~}}}}}}}}||}}}}}}}}~~~~~~~~~~|ywxxwwxxvwwvtomny }zyzzwqnqsuwz}}}}|xwvvvuvvssqppoqojjoppmlmpqohbacbabadjmlkkmoqux{||}|}}}zzzzyz}~~~}}~}{{{|}|{{zxxwvuqnmnpqqrssrrruz}{tqpqstpllmpsssru{zsppooonkjmpvz{}}zxx~~~~~~}~~~~~~~~~~~~~~~}}}}||||}}}}}}}}}}||||~~~~~~~~}}}}}}}}}}}}||}}}}}}}}~~~~~~~~~~ {xvwxvvxxwxxxslijt }zyzzytqptuw{}~}zywwvvuussqponpolmpqqmnoqqpf_`aaba_djnnkloqrux{|}}}}}|zzzzyz}~~~~~}{{{||{{{zxxwvuqnmnpqqrssrrruz}~ztqpqstollmpssssw~xqooooonlkmqxz|~|yww~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}~~~}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~~~~ |ywwvvvwyxyxxqmjjr}yzz{yvsqtvy|~yxwvvutsssponloqoorrommprqmd__aaa`_flonmnpprvx|}}}}}|{{{zzz{}~~~~}~ ~}{zz{||{zzzxxwvuromnqqrssrqqruz}~ysqpqqqnkkmqsstu{tpoooomlkjntwy|~yxww~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}~~~}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~~~ ~} |ywwvvvwyzyxwqmjjq |||{yxvsux{|~zyxwvvttrronmkorqqsromnpsqnd__acb`djlommnpqsvz|}}}}}|{{{zz{{}~~~~}~ ~~}{zz{||{zzzxxwvuromnqqrssrqqruz}}xtppqqqnkknqssuw}|ronnnnmlkjnuwy|~yxww~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~{ywwvvvwxyxwuqmllp} }{zwuvwz|~|{zxvvutrqnmmmpssttspnorsrnfbbcdccglnplloqrtwz|~}}}}|{{{zz{{}~~~~}~ }~|zzz{||{zzzxxwvuromnqqrssrqqsvz}~~|wtqpqqqnkknqssvzxponnnnmlkjnuxz}~yxww~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~ ~} {ywwvvvwxyvuuromnoz~}{zwwwz{~|{zywwvttpmmnppsuvvupoprtsmhddcddegkopmloqrtx{}}}}}}|{{{zz{{}~~~~}~~~~~~|yzz{||{zzzxxwvuromnqqrssrqqswz}~~|wsppqqqnklorrrx}unmoommmlkjnuy{~~yxww~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~}}}}}}}}}}}}}}}}|~~~~~~~~~}}~~~~ }}{ywwvvuwxxuttsqpmn{ ~|zxwxy{}~~{yyyxvuutqnnprstvvwvsqprutpidddeeehmpomnprsuz|}}}}|||{zzzzz{}~~}~~~~~~~~{yxx{||{zzyyxwwtqonnpqssrqpqtx{}|||vrppppomllorsuz~rnlnommlkkkntxz{yxvv~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~}}}}}}}}}}}}}}}}|~~~~~~~~~}}~~~~ ~|~|ywwvvvwwvtrsvvsop{ ~}|zxyyyyy|~~|zzyxwvvurpprtutvvwwusrsuvrkfeeeefioqpnpqstv{|}}}}|||{zzzzz{}~~}~~~~~|{yy{||{zzyyxwvtqonnpqssrqpqty|}||zuqppppomllortw|{ollmnmmlkkkntx{{xwvv~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~}}}}}}}}}}}}}}}}|~~~~~~~~~}}~~~~ }|~}ywwvvwvvurrrxzuss} ~{zzzzyzz|}~}|{zyxwvwvsrrsuvvwwyyxvstvvsnjhgfegipqppprtuw{|}}}}|||{zzzzz{}~~}~~~}~|zz{||{zzyyxwutqonnpqssrqpqty|}||ytpopponmlmpstw|wlllmnmmlkkkovz|~zxvvv~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~}}}}}}}}}}}}}}}}|~~~~~~~~~}}~~~~}{~~}ywwvvwvvtqqrz}ywx~ ~}{zzy{{{{}~}|}|{yxxwwwuttuvxxxy{zzwstwwtpkjiffgipqpqqruuw|||}}}|||{zzzzz{}~~}~~}~~}|{zz{||{zzyyxwutronnpqssrqpqty|}||xspoppommlnpsvy~~tlkmmnmmlkkjov{~~zwvvv~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~}}}}}}||}}~~}~~~}}~~~~ }|}~}xwwvwwvuropsy}{|} ~}|zzyzz|||}~}~~|{zyxxxvuvvvwwyy{|}{wvvwwtomjhgikqqqqsvvwz||}}}}}|{z{zzzz{|~~~~}}{zzz|{{z{{zxvtttsqoopqrrrrorv{}}|zuroooonmlmnprv{{pllmmmlkkjilqx{}yuutv~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~}}}}}}||}}~~~~~~~~}}~~~ ~{|~ ~~ywxwxwutrpoqwz|~}{z{{{{|||}~~~~|{zzyxxwwwwwxxzz|}}|xwvwwvspmkijmrrsruwwy{||}}}}}|{z{zzzz{|~~~~~{{|{{{}|{z{{zxvtttsrpopqrrrrprw{|||xtqoooonmlmnprx}~vnllmmmlkkjilsx||xuutv~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~}}}}}}||}}~~~~~~~~}}~~ |{~ ~}|xxxxxvsspmotw}~{z{{|||||}~~~~|{zzyxyxxxwxxxz{|}}|yxvwwwurpnlmnsttuvyyy|||}}}}}|{z{zz{z{|~~~~~}|xz{}}{|||{{{zxvtsssrpopqrrrrrtx{}|zwrpoooonmlmnpty|smllmmmlkkjjmsx|~{vuutv~~~~~~~~~~~~}}~~~~~~}}}}}}}}}}}}}}}}~~~~}}}}}}}}}}~~~~~~~~}}}}}}||}}~~~~~~~~}}~~ ~{{~ ~}~{xxyyyvtqomnrx|}{{{|||||}~~~~|{zzyxyyyxwxxxzz|}}|{zvwwxvsrqppqtuuwx{{z}||}}}}}|{{{z{{z{|~~~~~~|zuvz~~||}|{zzzxvtrrsrpopqrrrrrux}{{zuqooooonmlmnpu|yqmklmmmlkkjjmsy|}zuuutv~~~~~~~~~~||~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~}}}}}}}}}}}}}}}}~~~~~~~~}}~~~|||xxzzyxuqommrw|}||~~~~~~~~~|}{zyzyzyyxxwwxy{}~~}|{wwvvxxvtsqsuuvwz||{}~~}}}}}|||{zzz{{}~}}}|{xuuy~~}{{zzzzxvtrrssqpprrrqqruy|{{ytqoooooomkmnqu{yqmkllmlkkjiinty}|xvvvuv~~~~~~~~~~}}~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~}}}}}}}}}}}}~~}}~~~~~~~~~~~~ }||{zzzzyvspnlqv{~}zw~~~||{zyzyzyzxxwwxz{}~~~}{zxwwzzxwutuvvyy{}}{}}}}}}}}|||{z{{{|~~~~~|||yvvz~}{zzzzxvtrrssqpprrrqqrw{|{zwspooooonmklorx}~wolkllmlkkjiinty}|xvvvvv~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~}}}}}}}}}}}}~~}}~~~~~~~~~~~ }|}}~|y{z|zwupomqv{}~{w~~|{zzyyxyyzyxwwxz{}~~~}{|zzyz{zywwwxy{|}}}z|||}}}||||||{||{|~~~||}|yxz}}{zzzzxvtrrssqpprrrqqsx{}zywsonoooomlkkns||tnkkllmlkkjiintx|{wuuuuu~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~}}}}}}}}}}}}}}}}~~~~~~~~~~~ |}~~~{y{|}{yvrporwz|}|{}~|{zzyyxyyzyxwwyz|}~~~}{{z{{z|{yzyyz|~~||z{||}}}|||||}|||{}~~~||||{yz}}{zzzzxvtrrssqpprrrqqty{|yxvromoooomlkjms}{rmlkllmlkkjiintx|{wutttt~~~~~~~~~~~~}}}}~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||}}~~~~~~~~~~~~~~~~ ||}}~}zz}}{zwtqorvz{|{|~|{zzywwyy{zyxwx|}~~~}|||||||||{z{~|z{yyz|}}||}}}}}}}}}~~~~~~}}{{|z{~zyyyxxwusqrtrqqsrrrrvz||zxtrpnnnommkijnu}yrmklmmmkkkjjjouz~}yuttttt~~~~~~~~~~~~}}}}~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}||}}~~~~~~~~~~~~~~~~ {||~~}}yy}~|{yvroruyyy{}~|{zzywxyy{{yxwy{|~~~~~~~~}}|}|{z|~~{yzyyz|}}||}}}}}}~~~~~~~~}}{{{||}zyyyxxwusqrtrqqsrrssvz||zxsqpnnnommkjlpxxqlklmmmkkkjjlpv{}xtttttt~~~~~~~~~~~~}}}}~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~~~~~~~~~~~~||}~~}~zwx|}|zwsqrtwxx{}~|{zzyyyyy{{zyxxx{}~~~~~~~~~}}}|||}|yxyyzz|}}||}}}}}}}}~~~~~~}}{{|}}}{zyyyxxwusqrtrqqsrrstw{||zwsqpnnnommkklrz}vqlklmmmkkkjjlpvz|vtsssss~~~~~~~~~~~~}}}}~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}~~}}~~~~~~~~~~~~~||~~~~}|~{wvw|}{xtrqtuwy|~ ~|{zzyyyyy{{zyxxx{}~~~~~~~~}}~~|yxxzyzz|}}||}}}}}}}}~~~~}}}}||}~~}{zyyyxxwusqrtrqqsrrrtw|||ywropnnnommkknt||upkklmmmkkkjjmqwz|vtsssss~~~~~~~~}}~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||}}}}~~~~~~~~~~~~~~ ~|}}~ |{~{vtty~{yusrruxz}}}{{{zzzxxxz{zxwwy}~~~~}}}}~{xxy{|}{}}|||}}}}}~}}~~~~~}}{|{{}~}|yzzxxywusqstrqqsrrsuz{||zvroooooomlkjnw~ztnljkllljjjiimrv|{vsrrrrr~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||}}}}~~~~~~~~~~~~~~ }|~}~||~{vtux~|zwtrquy{~~~}}||{{zzxxxz{{xvvx|~~~}}}}}}}yxy{|~}~}|||}}}}}}}}~~~~~}}|{{{}}|yzzxxywusqrsrqqsrssv{{{{yvqoooooomlkkqyyrnljkllljjjiilrw}{usrrrrr~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||}}}}~~~~~~~~~~~~~~ ~||~}~ ~~}~|wuvy~}{xurquy{}~~~~}}||{{zzzzz{{{xwvw{~~~~}}}}}||yxy{}~~}|||}}}}}}}}~~~~}}|{{~~|yzzxxywurpqrrqqsrstw{{{{xuqoooooomlkmt{~xpmkjkllljjjjjlry~~yurrrrrr~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}}}||||||}}}}~~~~~~~~ }{|~}~ ~~}~}xuvy~~|yvsruz|}~}|}}}}}}{{zzzzz{{{xwvwz}~~}}}}}||yyy|~~}|||}}}}}|~~~~~~ }}|{}~|yzzxxywuqpqrrqqsrsuy{{zzxtqoooooomlknt{}wpmkjkllljjjjjmsz~}xurqqqqq~~~~~~~~~~~~~~~~~~~~}}}{~~~~}}}}}}}~~~}}}}}}}{||||||||||}}~~~~~~~~~~~|~~{||}~~ ~~~|~}xvwy}~}zwtux{z{}{y{~~~~~~~~~~~~~~~~~~|{{z{zx{|{yxwxxz}}}~~}}~{yz{~~~}}|~}}}}}}}~~~~~~~}~|zzzyxxwuqooprrrrrsv{||zyvspooooonlkknv~|uplkkkllmkkkijotz~{xusrqqqr~~~~~~~~~~~~~~~~~~~~}}}{}}}}}}}}}}}~~~}}}}}}}{||||||||||}}~~~~~~~~~~}{||~~~~~~|~}wuvy}~}|xuwz{yz{ywx~~~~~~~~~~~~~~~~|||z{zx{||{yxwvx{}~~}||}}~|{zz}~~}|}}}}}}}}~~~~~~~~}~|{zzyxxwuqooprrrrstx{||zxuspooooonlkkpy{tollkkllmkkkijpu{{wtrqqqqt~~~~~~~~~~~~~~~~~~~~}}}{}}}}}}}}}}}~~~}}}}}}}{||||||||||}}~~~~~~~~~~~~~~~~||~}~~ ~|~}usuz|~|yxy{{xyyywx~~~~~~~~~~~~~~~~~~}}}||{y|}}|yyxwxz|~~}||}}~|{zz~~}}}}}}}}}~~~~~~~~}|~~|{zzyxxwuqooprrrrsuy{|{ywtrpooooonlkmqz~yspllkkllmkkkjkpv|zvsrqqqrt~~~~~~~~~~~~~~~~~~~~}}}{||||}}}}}}}~~~}}}}}}}{||||||||||}}~~~~~~~~~~~~~~ }|}}~ ~|~|tru{|~}xy{}{xyxwwx}~~~~~~~~~~~~~~~~~~~}}}|z}~|{{zywxz{}}}||}}}{zz~}}}}}}}~~~~~~}}}}|{{zzxxwuqooprrrrtvz|}{wvtqpooooonlkot|}wrollmlnnmkkkjkpv}zusrqqqsv~~~~~~~~~~~~~~~~~~~~~~~~~~||}}}}~~~~~~~~~~}}}}}}||||||||||||}}}}~~~~~~~~~~ |||~} ~z}|usw{|}~||||yxxxwx}~~~~~~~~~~~~~~~~~~~~~}{|}}||zzyyyy{}~~}}~~~~|zz ~~}~~~~~~~~~}~~|~~|zyzzyxvtonoprrrstv{||zvvsqpooooonmlnu~|uqomllmmmmkjjjmqw|ytsrrpqux~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~}}}}}}||||||||||||}}}}~~~~~~~~~~~ }{|~~ ~z}}vtx|}}~~~}zwxxxy}~~~~~~~~~~~~~~~~~~~~~|}~~}}z{{zyy{|}~~}}~~~~|{{ ~~}~~~~~~~~~}}}|~ }{yzzyxvtonoprrrsux|}{yvusqpooooomllox{tqomllmmmmkjjjnrx}~xsrrrprwz~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~}}}}}}||||||||||||}}}}~~~~~~~~~~~ {{~ y|~yux|~|}zxxyy{|~~~~~~~~~~~~~~~~}}|}~~}~||{z{{z{{}~}}}}}~~}{{ ~~}~~~~~~~~~}}}~ }|zzzyxvtonoprrrswz}}{yussqpooooomknqzxsqomllmmmmkjjkosz~|wsrrrqtx{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}||||||||||||}}}}~~~~~~~~}}~ |{ y|~{vx|~|}yxyy{||~~~~~~~~~~~~~~~~}||}~~~~}|{{{{z{z|~~~}}}}}~~}|| ~~}~~~~~~~~~}||~ }}{{zyxvtonoprrrsx{~}zxussqpooooolkns|wspomllmmmmkjjkosz|vsrrqsuy|~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}||{{||||||||}}}}~~~~~~~~~~~~} || xy}xx||z|~~ywxyz{}}}}}}~~~~~~~~~~~~~||||~}}~~|||||{z{||}|||{}~}}||} ~~~~}}~~~~}||~{{zyxwtpopqqrrux|~|zxurrqonnnmmklnu~}urpnllmmmmmliilpw|{vsrrsuw{~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}||||||||||||}}}}~~~~~~~~~~~~}~ |~ ww~~zz}}z{~~{xxyz{}}}}}}}}~~~~~~~~~~~}}}}~}}~~}}}}||zz{{|{{|z||}}||} ~}~~~~}}~~~~}} ~}yyxwtpopqqrswz}~|zxtrqponnnmmklpx|uqpnllmmmmmmiimry~zvsrstwy}~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}||||||||||||}}}}}}~~~~~~~~~~~} | }| uv}zz}}zy~~~|zxyz{}}~~||}}}}}}}}~~~~~}}}}~}}~~~~}}||{{{{{{{{z|||}||}}|}~~~}}~~~~}~ {yywtpopqrrtx{}~{ywtrqppnnnmmklqz~ztromllmmmmmmiimry~yutssuy|}}~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}||}}||||||||}}}}||~~~~~~~~~~}~~|}{{~~tu|{{~~zx|~~{yxyz{}}~~{{||||}}}}~~~~~~~~~~}}~~~~}}||{{{{{{z{z{{||||}}|}~~}} }yywtpopqrrty|}~{yvurqpponnmmjlqz}ysqnlllmmmmmmiimrz~yuutsvz~~~~~~~~~~}}}}~~~~||}}}}}}}}||||||||||||}}}}}}~~}}~~~~~~~~~~ ~~zz}~ ~usy|{~|zy{{yyxz{}}~~|z||||||}}~~~~~~}}}~~~~~}}}~}|||||z|{{zz{{{{}}}|}~~}~ }zxwspooqqsvy}}|ywusrpooooonmklr{~xsqolllllmmmmkjns{~yvtrsv|~~~~~~~~~}}}}~~~~||}}}}}}}}||||||||||||}}}}}}~~}}~~~~~~~~~~ zz{}~~usy}|~|{zz~{zzxz{}}}~}{||||||||~~~~~~~~}}|}~~~~}}}~}|||||{{z{zzz{{{}~}~|~}}|| ~ywspooqrswz}}|xvtsqpoonnnmllnt~~xrpmlllllmmmmjint|}wttrtx}~~~~~~~~~}}}}~~~~~~}}}}}}}}||||||||||||}}}}}}~~}}~~~~~~~~~~ ~ |z{}}}~usy~}~{z{~~|{{zz{}}}~~}||||||||~~}}~~~~}}|}~~~~~~}~}}}|}}||{{zzzz{{}~~|}~~}||{wtqpqrsty{~}{xvsrpqonnmmmklov~~xqomlllllmmmmjjou|}wttsvz~~~~~~~~~~}}}}~~~~~~}}}}}}}}||||||||||||}}}}}}~~}}~~~~~~~~~~ ~~ ~{{|}}~usy~{z{~~}}}{{zz{}}}~~}{|||}}||~~}}~~~~}}}~~~~~~~}~}}||}}}|{zzzzz{}~}|}~~|}} }vsrqqssuz}~|zwusqpqpnnmmlklpw}xqomlllllmmmmijov}}wtttw|~}~~}}~~~~~~~~~~}}}}}}||||||||||}}}}~~}}~~~~~~}}}}}}~~~~{{{|~}svy~|z|~~}}~~||{z{{|}~|zz||}}~~~~~~~~~~~~~~~~~}}}}}~~}|||~}{z{yzz{{|~~~~~}}} xsrrrqtx|~}{zvsrqqppnnmkkjkqy}wqonlmmmmlllkjkou~{trtux|~}~~}}}}~~~~~~~~~~}}}}}}||||||||||}}}}~~~~~~~~~}}}}}}~~~ {{{|~ssw}~|z|~}||~~~}|{{z{~ |zz||}}}}~~~~~~}~~~~~~~~}}}~~~~||||~~~}{{zzz{{|~~~}~~}}~ |urrrquz~|zxurqqqppnnmkkjlr{}wponlmmmmlllkkkpvztruvy}~}~~}}}}~~~~~~~~~~}}}}}}||||||||||}}}}~~~~~~~~~}}}}}}~~~ |z{|tqu|~|z{}~~|{z|}~|{{{|{zz||||}}~~~~~~}~~~~~~~~}~~~~~|||||}~~}{{{{{|~}}}~}}}~ xtsqqv{~|ywsrpqqppnnmkkjmt|}wponlmmmmlllkkkrxzutvx{}~~}~~~~}}~~~~~~~~~~}}}}}}||||||||||}}}}~~~~~~~~~}}}}}}~~~ ~z{|tsv|~|z{|~~{{yz|}|||~|{{||{{||~~~~~~}~~~~~~~~~}}}||}~~|{|{{|~||~}|}}~ |wsrsw}~{xvsqpqqppnnmkkjmu}}wponlmmmmlllkkkryxttwz{}}~~~~~}}}}~~~~~~~||||||||||||||||||}~~~~~~~~}}}}}|}}}}~ ~z{} wwx}}{|}}{zxxx|~}}}}~{zzz{{{{}~~~~~~~~~~~~~~~}}~~~|}}~~}|||}}|}~}{z ~|}~ ~wtrry}{yvqppqqpponnllknw~woommmmmmmmljilsz~ytuwz}~~{~~~~~}}}}~~~~~~~||||||||||||||||||}~~~~}}}}}|}}}}~ }xy~ {zz~}|}}|{ywvv{~~}~~{{zz{{{{|~~~~~}}}}~}}~~~}}}}~}~}~}}}~}||||}~~{z} }{}~ zyttt|~zxupppqqpponnlkjnv|upnlmmmmmmmljimt{~xuvy{~}z~~~~~}}}}~~~~~~~||||||||||||||||||}~~~~}}}}}|}}}}~}xy}}}~~}}}{zxvtuy|}~{{zz{{{{{}~~~~}}}}~~~~~~}}||}~~~}~}}|}}~||||}~|z||{}~ }{yyy~zwtpppqqpponnljinv{tonlmmmmmmmljinuz~}xux|~{x~~~~~~}}}}~~~~~~~||||||||||||||||||}~~~~}}}}}|}}}}~ ~yy{~~~~}}}{zwusty{|}{zz{{{{{|~~~~~~~~~~~~~|{||}~~~~~}}|}|~||{{|~|z|} ~|{}~ }~}|~~~zwtpppqqpponnljinx|unnlmmmmmmmljjnuz~|vtx|{w~~~~~~~~}}}}~~}}}|||||||||||||{{|||~~~~}}}}}||}}~~} }{| ~~}~~}|}|{ywtttwy~ {zzzz{||}~~~~~~~~~~~~~~}{{y||}~~~~~~}|~~|{{{|~~|}|~~~{|~~ } }|}{yvrooqqqooonmkhjoy|unnlmmmnnnmkiknv|{vuy}~xv~~~~~~}}}}}}}}}||||||||||||||||||~~~~}}}}}}}}}~~~ ~~}}~~}|}|zxvsssuw| |zyz{{||}~~~~~~~~~~~~~~}{{y||}~~~~~~~~~|{{z|~}||{}~~~~{|~ ~}zxyzxvrooqqqooonmkijr{|tnnlmmmnnnmkikox~{wwz}|vv~~~~~~}}}}}}}}||||||||||||||}}|||~~~~}||}}}}}}~~ }}}~~~|{{{yvursrtw|~zz{||||}~~~~~~~~~~~~~}|{{}~~~~~~~|{zz|~~~}|||}}}~}~{|~ {yxxxyxvspoqqqooonmkjkt}{rmmlmmmnnnmjhkqx}{xy|~ytu~~~~~~}}}}||}|||||||||||||||}}|||~~~~}{{}}~~}}~~~ ||~~~{zzzxvurrqsv{ }||||||}~~~~~~~~~~~~~~}|}}~~~~|zyy|~~|}}}||}||}~}~{|~ |vv|zywyxsqoqqqooonmkkmt~{qlllmmmnnnmjhksx}{xz~vrt~~~~~~}}}}}}|}||||||||||||||||||}~}} }|||}~~~ }|~~~~}{zzzwvtsqqqtx} }{|}|}}~~~~~~~~~~~~~}}}}~~~~~~}}{xz{|}~~|{|}{{|}||~ ~{wrz{tw|ysppqpomnmmkjmszqmmlmmmnnmliilty}~zxz~}tsu~~~~~~}}}}}}|}||||||||||||||||||~~}} }|||~~ ~|}~~~~|{zzywvtrqqqtw|~||}|}}~~~~~~~~~~~~~}~~~~~~}}~~}}{xy{~{|}}{|~||}~~}||~ zwrv}wv||urpqpomnmmkjmuyqmmlmmmnnmliimtz~~zz|{trv~~~~~~}}}}}}|}||||||||||||||||||~~}} ~||}~~ ~|}~}~}|zyxxuutrrrqsv{|{||}}~~~~~~~~~~~~~~~~~}}}}||}~~}}{yy}~}{|}~|}}|~~~}||~ ~wrq{{wz|vsqqpomnmmkinwxpmmlmmmnnmliinuz~~{|~~xssw~~~~~~}}}}}}|}||||||||||||||||||~~}} ||~~~~~ ~|}}}~|{yxwvsstsrrqrvz~ ~|{|}}~~~~~~~~~~~~~~~~~}}}|zz{}~~}}{yy}~|{{{}~}}}~~}||~ |tor} |vx{yrrqpomnmmkhnwxommlmmmnnmljiov|~||}wrsx~~}}~~}}}{||||||||||||||||}}}}~~~~~ }|~ }} ~|||}}|zwvxvttusssrsvx||{|}}}~~~~~~~~}}~}}}}|}|{zxz{|}{zz{~{||{|~~~~}{| |tos}~}vv|xsrpponmkkioy~wpmmllkmmmkjhinv|~}{rru{~~}}~~}}}{||||||||||||||||}}}}~~~~~ ~} ~{} |{}}}}|zwwvussutssstvx{~||}}}~~~~~~~~}|~~~|||}|{z{{yyy|~}{zz|~{||{}~~~}|} ~ups|~{~~xv|{trpppnmkkjpy}vommllkmmmkjginw|~~~wqru|~~}}~~}}}{||||||||||||||||}}}}~~~~~ ~ ~z} }{}}}}|zyxvtstvutttuvxz}}}}~}~~~~~~~}}}~}||{|{{{{|zyyz}~}{zz|~{|||~~~~~}~ vqrz~|z~{z||xspppnmkkkqz{uommllkmmmmlgiox}~~~}vqsx}~~}}~~}}}{||||||||||||||||}}}}~~~~~ {} }{~~}}|z{zvttuvuutuvvyz{~}||~}~~~~~~~~|}}||{|{|{z{{}|y{z|}~{zz|~{||}~~~~~}}~ xqqx|}|~~}|zspppnmkkkq{{tnmmllkmmmmlgipx|}~{upty}~~~~~~~~}}||||||||||||}}}}}}}}}}~~~~~~ ~|}~}~}{{zzxtuvwwvwuvxy{{} }}~~~~~~~~}}~|{zz{z{{|}~~~|{{|}~{xz}|z{|}~~~~}|~ |stx|~}upppnmljkr||rlmmllllmmljhjsx}~~wssw{}~~~~~~}}||||||||||||}}}}}}}}}}~~~~~~ ~}}~}{{{{yvwwvvvxvxzz||}~}}~~}|{zzz{zz|~~~~~}|}~~{xz}|zz{|~}~~~}|~ ww{~ yqppnmljkr}|rlmmllllmmlihktz~|vstx|}~~~~~~}}||||||||||||}}}}}}}}}}~~~~~~~ ~~~|}|{{||{xxvuuvxyz{{||}}~~~}}~{zyyzz{|~}}~~{xz}|yy{|~ ~~~~~}|} {{}}tppnmljkr}|tnmmllllmmlihltz~zutuy{}~~~~~~}}||||||||||||}}}}}}}}}}~~~~~~} ~~~~~| }{{{||}zxvuuvx{{{|}}}~~~~~~~|{yyyzz{}}~~{xz}|xy{{~ ~~~}||} }~~}vppnmljkr}|tnmmllllmmmihluz~~~zutvz{|~~~~~~~~~~~~}}}}|}{{||||||||}}}}}}}}~~~~~~~~} ~~~~}|{zz|}}zxuttuyz||}}~ ~~~~~~~|{yyxy{}}~zw{}~zwyy{~}}~~||}~ }|} yqoonkkls~}uollllkmmmmjimw|~}~~wtuxy{}~~~~~~~~~~~~}}}}||||||||||||}}}}}}}}~~~~~~~~} ~~ |{yy{}|zxussuxz||~}~~~~~~}|{zzxy{}}~{x{}~zwyy{~|}~~~||} }|} {roonkkmt~}tnllllkmmmmjjnw}~}~}utuxy{~~~~~~~~~~~~~~~~}}|}}||||||||}}}}}}}}}}~~~~~~} ~ ~zyxxz||{xusrsvz||~~~ ~~~~~}|{zzxz|}}~~ ~~|z|~~zwyy{~}}}}~~}||} ~ }tponkkmu~}tnllllkmmmmjiow}~}~zsuvxy{~~~~~~~~~~~~~~~~}~}}}||||||||}}}}}}}}||~~~~~~} ~~ |ywwwz{{|xurqstz||~~~~~}|{zzz{|}}~~~~|}~~zwyy{ ~~~||~~|||} ~wqomkkmw~}smllllkmmmljipw}~}~xruwxy{~~~~~~~~~}~~~~~~~~~~}|||||||||||}}~~~~}}}}~~~~~~~~~~~~~ |xwuuvy{{{wusrsuy{}~~}~~}}~}||{{{|}~~~~~||}}xvwy|~}|||~~}}{{} yromjjmx|tmllkklmnljijqx|~~}~}xtwxyy{~~~~~~~~~}~~~~~~~~~~}|||||||||||}}~~~~}}}}~~~~~~~~ ~~~~~ ytsstvy{{{xusssuy{}~~}|~}}}||{{{|}}}~~~}}~}ywwy|~~~|||~~}}||} {vromlox|tmllkkmmmljijrz|~}}~|xvxyyy{~~~~~~~~~}~~~~~~~~~~}|||||||||||}}~~~~}}}}~~~~~~~~ ~~~~}~{xrooruy{{{yvsssuy{}}}~|{|~~~}}|||{{||}} ~~|}~yxyy|~~~}}~~}}||} zvsporz|slllkkmmlkjhks{}~}}~zwvyzyy{~~~~~~~~~}~~~~~~~~~~}|||||||||||}}~~~~}}}}~~~~~~~~~ ~~~~}} ~zysooquy{{{yvtssvy{}}}~|z|}~~~~}|||{{|||~}~~}}~}zxyy|~~~~~~~}}}}} ~ywsrs{|slllkkmmlkjhls{~~}}~yvwy{yy{~~~~~~~~~~~~~~~~~~~~~}}}|||||||||||}}}}}}~~~~~~}}~~~~ ~~~~~}|||{wpnoty|||yyuvuvyz||}|{|} ~}}}}||{{{{|~ ~}}}|}|zwxx{~~~~~~~|}} |ywx}~{rmkkklmmlkjimu|~~~~|xuxzzxxz~~~~~~~~~~~~~~~~~~~~~}}}|||||||||||}}}}}}~~~~~~}}~~~~ ~~~~~~~|~}~zronsy|~~{{yxyyzz||~~|{{}~ ~}}}}}||{|}~}}}|~|zwxx{~~~|~ ~|{~{rnkkklmmlkjhmv|~~~{wvxyyxxz~~~~~~~~~~~~~~~~~~~~~}}}|||||||||||}}}}}}~~~~~~}}~~~~~~~~~~~~|}wqorwz~~}}{{{{{|}}}|{z|}~ ~~}}}||}}}}~~||}}|zwxz}~~~} ~ ~~{rnkkklmmlkihmw}~|}{wwyyywwz~~~~~~~~~~~~~~~~~~~~~~~}}}|||||||||||}}}}}}~~~~~~}}~~~~~}}~~~~~{ ysoquy}}}}}}~~}|{z{|}~ ~~}}}{{{|}~~||}}|zwxz}~~~~~~~ ~~ ~{rnkkklmmlkiimw}~{|zwwzxxvvz~}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}||||~~~~~~~~~~~~}}~~~~~~~~~~~~~~~}}}}}~~|}|uoqvy|}~~~~}~}|{{{|{|~}||{{{||} ~||}~}|zxy{~~~~|} ~ yrnkkklnnlkgiox~|}}zwxyyxvvz~}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~}}}}}~}|xrrux{|}}~~~}{{{||{|~ ~}|||{{{||~ }||}}|zxyz}~~~}~ yrmkkklmmlkhjpx~}}|yxyyyxvvz~}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}~~}}~~~~~~~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~}}}}}~~~{~|tsux{|~~~~~}|z{{||{|}~}|{|{{{{{~}{z|}~||zxyz}}~~~~~ ~ yqlkkkllllkikqy}~}}|yxyyxwvvz~}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}||~~~~~~~~~~~~~~}}||~~~~~~~~~~}}~~~~~~~~}}~}}}}}~~z|}uptwz{~~~~~|{z{{||{||~~~}|{|{{{{{ |{yz|}}{|zxyy||}~~~|} }|~~~~ yplkkkllllkikqy}~}~|xxyyxvvvz~}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}~~}}~~~~}~~~~~}}}}}}~~~~}}~~~~~~}~~~~~~~~~~}}}}}}}}}}~|z}yrrvyz}~~}|{{{{{zzz|~}~}|z{{{z{{| ~|zyz|~~|z{xyzz{}~~|} {vy|}~ ypmkkllllkjiksz~}}}|yyyywvvvy}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}~~}}~~~~}~~~~~}}}}}}~~~~}}~~~~~~}~~~~~~~~~~}}}}}}}}}}}z{{truyz|~~~}}~}|{{{{{zzz|~~}~~}|{zy{{||} }zyz|~~|{zz{zzz}}~ {trw|}~~~~~ {rnllllllkjimu|}~~|yy{}zwuuy}~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}~~}}~~~~}}~~~~}}}}}}~~~~}}~~~~~~}~~~~~~~~~~}}}}}}}}}||~zz}~wsuxy{}||}}~}||{{{zzz|~}~~}|{zzz{|}~}zyz|~}|{|{zyy}~ uopx}~~~}~~ }snllllllkjimu|}|yy|~xtty}~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}~~}}~~~~}}~~}}}}}}~~~~}}~~~~~~}~~~~~~~~~~}}}}}}}}}|||y{yuvwy{|z{|}~}}{{{zzz|~}~}{{zyz|}~~{yz|~~~{}{zyy~~ zsmow}~}~~ vollllllkjinv}}|yy|ytty}~~~~~}}}~~~~~~~~~~|}}}}}}~~~~||}|}}}}}}}}~~}}}}}}|}~~}}}}}}}}}}~~~~}}~~~~~~~~~~~~~~||}}||||}}}~~xy}vvzyy}zy||}~~}}}||{{zyz||~}|{zzzz~ |zy}~~~~}}|zxz~|snmry~~}}~} xrnmmlmmkihnw~~}{yy}ytty~~~~~~}}}~~~~~~~~~~~~|}}}}}}~~~~}|}|}}}}}}}}~~}}}}}}|}}}}}}}}}}}}}~~~~}}~~~~~~~~~~~~~~~~||}}||||}}}~~xx}zy|yy|~zy|}~~~}}}||{{zyz||~}||{{{{~ |zy}~}}~~~}||} ~vpnqw{~|{~~}}vonmnmmkihow~~}~zyy}ytty~~~~~~}}}~~~~~~~~~~~~|}}}}}}~~~~~~}|}}}}}}}}~~}}}}}}{}}}}}}}}}}}}}~~~~}}~~~~~~}}~~~~~~~~~~}}||||}}}~~{xz~}}zz|~|z|}~~~}}}||{{zyz|}~}|||{}~ }zy|}||~~~~~~ursyz}}yz~~ yroonmmkiipx~~}~zzz~ytty~~~~~~}}}~~~~~~~~~}}~|}}}}}}~~~~~~}|}}}}}}}}~~}}}}}}{}||}}}}}}}}}}~~~~}}~~~~~~}}~~~~~~~~~~}}||||}}}~~}wz} {y{~|z|~~~}}}||{{zyz|~~}||||~yy{|||~~~~tuz}}~zz||~|}{tpoommkiipy~~}}yzz~wtty~~}}}}~~~~~~~~~~~~~~|}}}}}}~~~~~~~}~~~~}}}}~~~~}}~~|}}}}}}}}}}}|||~~~~~~~}}~~~~}}}}}}}}~~}}}}||}}~zxz ~}{}~|}~~}}|||||{zy{}~~~}}}|}|z{|}}~~~~~ }sw}~~~{{~}{z{~~~}~~zuqommjhhqz~}~zwxy{wttx|~}}}}~~~~~~~~~~~~~~|}}}}}}~~~~~~~~~~~~}}}}~~~~}}~~}}}}}}}}}}}}|||~~~~~~~}}||||}}}}}}}}}}~~}}}}||}}~}yy| ~|~~}~~}|||||{zy|~~~}}}}|~ ~{||}}~}~ }sv{~|z}|yyy|~ }}~xtqomjgjs{~~~zwy|zvstx|~}}}}~~~~~~~~~~~~~~|}}}}}}~~~~~~~~~~~~}}}}~~~~}}~~}}}}}}}}}}}}|||~~~~~~~}}}}||~~~~}}||}}~~}}}}}}}}}}{xx~|~}~~}||||{{z{~~~}~~}}}|~~ |||}}~~}}~ tw|~~yy~{yyyyz{~}~}wspnkglu{~}{xz~~xustx|~}}}}~~~~~~~~~~~~~~|}}}}}}~~~~~~~~~~~~}}}}~~~~}}~~}}}}}}}}}}}}|||~~~~~~~}}}}~~}}}}}}}}}}~~}}}}~~}}}}xy|}}}~~}||||{{{}~~}~~}}}~~ |||}~~~~||~ vx{}{yx |yzyyzzz}~~|||wrokglv|~|zx|{ttssx{~~~~~~~~~}}~~~~~~}}}}}}}}~~~~~~~~~~~~}}}}}}}}~~}}~~}}}}}}~~~~}}}~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}}~}{xz} ~~~~}}{zyz~~~~~}|}}}}} ~}|}}~~}~||xw{{{xz |wvwyyyyy|~|}~ }|}ysoljlu}}zx|yrqssw|~~~~~~~~}}~~~~~~}}}}}}}}~~~~~~~~~~~~}}}}}}}}~~}}~~}}}}}}~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}|~~zx{}}~~~~~}}{zz|~~}}}}|}|}|}|| ~|}}|}}~~|| zvzzyv{wvvwwwwwwyxz{| }{|wqljnv}}yx~vsrsqv{~~~~~~~~}}~~~~~~}}}}}}}}~~~~~~~~~~~~}}}}}}}}~~}}~~}}}}}}}}}}~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}}}}|}|yy{~|{}~~~~}}{z|}}|{|||{}}}|| ~~}}~~|} |vzzwu{}vuvwvvvvurrsz~ }{~ztliox~|yz}vttspty~~~~~~~~}}~~~~~~}}}}}}}}~~~~~~~~~~~~}}}}}}}}~~}}~~}}}}}}}}}}~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}}}}}}}~~}~zxy|~|{|}~~~}}{z}~~~|{|{{z|{}}~|| ~||~~|}}wyyvszzvuvvuuuusons{ |~ }volqy~~{y|zurtroty}}~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}~~}}}}}}}}~~~~~~~~~~~~~~}}}}}}}|}}}}}}}}}}}}}}}}}}}yxz}}{|}~~~~~~}|{z~~~~}{zy{{|}~~}}~ }}~|}~~~ywurrz~wtttutttupkpy~ ~~~ypmty}||y}ysrrqoty~~~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}}}}}}~~}}}}}}}}~~~~~~~~~~~~~~}}}}}}}|}}}}}}}}}}}}}}}}||~{xx|}||}~~~~~~~~}|||~~~~}{zyz{}~~~~~ ~~~|}~}~~yurqrz|vtttututrmmw| ~{soty}|||}xssspoty~~~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~}}}}}~~}}}}}}}}~~~~~~~~~~~~~~}}}}}}}|}}}}}}}}}}}}}}}}|||~zvy|}|}~~~~}}~~~||}}~~~~}{|{{{{}~~~~~~ ~}~ ~z|}~~zuqqryzutttuuvtnlr{~~ |vruz}~||~{vsssnnuz~~~~~~}}}}~~~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}~~}}}}~~}}}}~~~~~~~~~~~~~~~~~~}}~~~~}|}}}}}}}}}}}}}}}}{{{~}wwz}~|}~~~~}}~~~|~~}}~~}}{|}|{z|~~~~}}~~ |wyz}}}}wqrry}yttttuuurmnx~~ |}}vsu{}}||~~yusttlnv{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}}||}}}}}}~~~~~~~~~~~~~~}}~~}}}}}}}}}}}}||||||{yx{}}||~}}~~~|}~}}~~}}|z{{z{{|~~~~~}~~ {wy{}}yposz{xuttuuttpjp{~~~~ |z~ytv{~~}}{vssurlksx~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}}}|}}}}}}~~~~~~~~~~~~~~}}~~}}}}}}}}}}}}||||||~~{xy|~}{|}~~~~~}~~~}}~}}}{{{zy|}}~~~~~}~~ {xz|~}qor{|wuttuutslmu|~~~~~ ~zx}{vx{~~~~xusstrlksx~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}}}}}}}}~~~~~~~~~~~~~~~~}}}}}}}}}}}}}}}}~~||||||}}|~}zyz|}{{|~~~~~}~~}}~}}|{{{zz|~~~~~}~~ ~ }sprz{wtttuuuqkqz}~~~~} yuz}|xx|~~|vssssqkmuz~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}}}}}}}}~~~~~~~~~~~~~~}}||}}}}}}}}}}}}~~||||zz|}}~|zyy}~{{|~~}}~~~}}}}||{|y{z}~~~~~~~ tps|{vtttuutpku~}~~~~~ yrw}{wy}~~zurssspkmv{~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~}}~~~~}}zzzxyz|~~yvz~{z{}}~}~~|}}~{z{}~{|{}~|}~~~~~~ wrs{{wuuttusmkx~~~~~~~ ypty~|yz|~}yusqrrmjlv|~~~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}zzzxyz{~}yx{|z{|}~~}}~~~|||{yz|}}||}~~~|}~~~~~ ~ yutz{vuuttupjlz~~~~~~~ zpqw}}z{}~~yvsrqrqmloy~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~}zyywwxy|~|yz}{z|}}}||~~~~{{{zz{|||||~~}}|}~~~~~~ ~ |wuyyvtttttojq|~~~~~~~ {qpu{|{{}~}xtqqqqqpqv|~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~}~}zyywvxy{{z{{z|}||z{~~}{zzz{}||||||||}~~~~~~ ~ wuxxwtttttnjs~~~~~~~~ {qorx}{||~~}zxtrqqsstv{~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~}}}}~~~~}}~~~~~~}~~}yxxvwwxy}~zx}|y{{z{y|~}}zzxyz{{{||}}}|}~~}|||}~~~~~ ~ ywyyustutrjkw}~~~~~~~|sprx}||}~}yxwvtrsxwx}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~}}}}~~~~}}~~~~~~}~~}yxxvuuwxz}}zy}|yyyyyz~~|{zzzyyxy{||}}~}|}~}|||}~~~~~~~ ~ zwyxtssssqimy}~~~~~~~{qpqv{~~}|}~zxxzzxvx|~~~~~~~~~}}~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}~~~~}}~~~~~~}~~}yxxussuvx{||{|~}zxxy{}~~|{|{zyxxz{||}~~}}}~~}||||}~~~~~}~~~~ ~ ywyxtsssrniqz}~~~~~~~{qpquz~~}|}~}xwy||| ~~~~~~~~}}~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}~~~~}}~~~~~~}}~}yxxussrtwy||~}}|}{xxz}}|{||zyxwxz{||}~~}}}~}||||}}}~}}}}~~~~~ ywwwtstspljsz}~~~~~~~~~ {rpquy}}}|}~{vwy} ~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{yyvtqrrvwy}~~||{zz|~~}||{xxxxz{{{|}}~}}~~~~}|||}|||}~~||}}}}}}~~~~~~~~}~~}~xvxwusssqkjt|~~~~~~~~~}~~ uoquw|}}}~|yuw{~ ~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|{zwtqrrtvy|}~}|{{{}~}|}|yxxwx{{{{|}~~}}~~}~}|||}|||}}}||}}~~}}~~~~~~~~~}~~|}}vtwvusssqjlw}~~~~~~~~~~~ ~topuw|}}}{xvw|~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{zzwtqqqrvy||~|{z|~}}}}{yxwwx{{{{||~~}}}~~}~}||{|{{{|||||||}}||}~~~~~~~~~~~~~}~~|}ztrxwvtssqjmz~~~~~~~~~~~ ~uqrvx|}}}~zxxy~ ~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~zxywtqpprvz||~~~zzz|~}||{yzywwy{{{{||~~}}}~}}~}||z{zzz{||||||||||}}~~~~~~~ }~}~~}~ ~wtrwvvtstqio{~~~~~~~~~~ ~wsuwy|}}}}zyy{ ~~}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}~~~~~~~~~~~~~~~~~~}}}}}~~~~~~~~~~~~~~{xvusrqqsv{}~~|~~{z|}}zzvwyyxxyz{{{||}~~~~|}}~~~~}{{{{{{zz{{{{||||{|}}}}~~||}}~~~ ~~}}|xutuwwuusojoz~~~~~~~~~ }wxz{~~~~~{y{~~~}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~}}}}}~~~~~~~~~~~~~~|xuusrqqtw{}}}{|}|{}~{yxvwxyxyyz{{{||}~~~~|||~~~~}{{{{{{zz{{{{||||{{||}}~~}}~~~~} ~~~ ~xwuuuvwuutpkpz}~~~~~~~}}~~{z}~ ~~}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}~~~~~~~~~~~~~~|xvttsrrtx{|{{z{{|~~~~}|yxwwwxzyyyz{{{||}~~|||~~~~}{{{{{{zz{{{{{{{{{{||}}}}}}}}}}} ~{vvuutvvvutpkpz}~~~~~~~~ ~{{~ ~~}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}~~~~~~~~~~~~~~|xwuusrrtx{|{{{}{}}}~}}{xxwwwwyyyy{{|{|||~~|}|}~~}}{{{{{{zz{{{{{{{{zz||}}||||}}}}} }~ ~ywuuuuuwvutqkqz}~~~~~~~~~} ~{{ ~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~}}~~~~~~~~}~~~~~~~~~~~~~~~~~|zwwvtssty|||z{{||}|{}~{xvwxxyyzzyz{|}||}}~}}}}}}||zzzzzzzzzz{{{{{{{{{{{{{{{|||~~ ~~~~ }|}~~~}zxuutuuwwvsqnqz}~}}~~ ~}||~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~~~~~~~~~~~}~~~~~~~~~~~~~~~~|zwwwussuz|||z{{|||}|}}zwuwxyzzyyyz{}}}}}}~~}}}||||zzzzzzzzzz{{{{{{{{{{{{{{{|||~~ ~~~~ ~{|}~~~|xvutuuvwwsploy~~~ ~|~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~~~~~~~~~~~~}~~~~~~~~~~~~~~~~|zwxwuttvy|||z{{|||~~}{xuwxyzzyxyz{{|}}}}~}}|{{{{zzzzzzzzzz{{{{{{{{{{{{{{{|||~ ~~~~~~~}~}}}}zxutttuvvsolnx~ ~ ~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~~~~~~~~~~}~~~~~~~~~~~~~~~~|zxyxvtuwy|||z{{||{|}}||zxwwxzzyyzz{z{||}}~}}|zz{{zzzzzzzzzz{{{{{{{{{{{{{{{|||~~~}}~~~~ }~~}}}|zutssvvuqolo{ ~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~|{xyywuvx{|||{||||{|~}||zwxyyyyxyyz{{}}}}}~}~}}{{{{{{{zzzzzzzz{{{{{{||||||||||||}~~}}}~~~~~~ }}~}yvtsttppnnt~ ~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|{xyxxwxz|||||||||{}|{{{yz{{yyyzyz{|}}|||||}~}}{{{||||zzzzzzzz{{{{{{||||||||||||}~}}}~~~~~~ {xustrqqsv{ ~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~|{xxxxxy{{||||||}|}zyyyyzzz{{{|zzz{}}{{{{{{|}}}{{{||||{{zzzzzz{{{{{{||||||||||||}}~}|}}~~~~~~ ~{yvtruuy} ~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}}~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~|{xxxxyz{{||||||}}zwvyyyz{{{|}{{{{|}{{{{{{||}}{{{{{||||zzzzzz{{{{{{||||||||||||}}}~}|}}~~~~~~ ~{xwvy{| ~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~}}}}~~~~~~~~~~~~}~~~~}}~~~~~~~~~~~~~~~~~~~}~~}}~~{zyxxwyz{{||{||||}}yttvwz{{||~}|zzzz|{|zz{||}~}|{{z{{{||{{{{z{{{{{{{|}|||}}}~~}}}}}~~}}}}}}~~~~~~~~~~~~||| ~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}~~~~~~~~~~~~}~~~~}}~~~~~~~~~~~~~~~~~~~}~~}}~~|{yxxxzz{{||{||{|}}zusuw{||}}~}{zzzz{z{zz{|||~}}{{z{{{||{{{{z{{{||||}}|||}}}}}~~}}}~~}}}}}}}~~~~~~~~~~~~~~~ }~ ~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}~~~~~~~~~~~~}~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~}|zyyyyy{{||{||{}~{vstw{||~~~}{zzzz{yzz{z||{}}}|{z{{{||{{{{z{{{||||}}|||}}}}}~~}}}~~}}}}}}}~~~~~~~~~~~~~~ ~|z~~ ~~~~}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}}}}~~~~~~~~~~~~}~~~~}}~~~~~~~~~~~~~~~~~~~~~~~}}~~}|{yyyxx{{||{{{|~~{xutwz||~}|}{zzzzzyzz{z||{|}}|{z{{{||{{{{{{{{||||}}}}}~~~}}~~}}}~~}}}}}}~~~~~~~~~~~~~~~ ~}|z{|}~ ~~~~}}}}}~~~~~}~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}~}{yyzyyxzz|||{{|~}zwuuvz|}|}}|{xxxyyzxy{{||||||zzz{{{||{{{{{|||}}}}}}||}}}}~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~~~ {zzyy|~ ~~~~}}}}}~~~~~~~}~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}~}{yyzyyzzz|||{{}}zwuuuy|{z||{zxxxyyzxyzz{{{{{{zzz{{{||{{{{{||}}}}}}}||}}}}~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~ }}|zyy} ~~~~}}}}}~~~~~~~~~}~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~}}}~}{yyzyyzzz{{{{|~~|zwuuux{{z{{{zxxxyyzz{yyzz{{{{zzz{{{||{{{{{||}}}}}}}|}}}}}~~~~~~~~~~~~}}}}}}||~~~~~~~~~~ |z{ ~~~~}}}}}~~}}}}~~~}~~~~~~~~~~~~~~}}}}}}~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~}~~}}}~}{yyzyyzzzzz{|~~}|zwutsw{}|{|{yxxxyyz{{yyzz||||zzz{{{||{{{{{||}}}}}}~}}}}}}~~~~~~~~~~}}}}}}}}~~~~~~~~~~ } ~~~~}}~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|zzzzzzzz{{zz||zz|zwuvuvy|||}|yyvvwxzz{yzzz{{{{{z{{zz||||{{{|}~}}~~~~}}}}}}~~~~~~~~~~}}}}~~~~}}}}}}~~~~~~ ~ ~~~~}}~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|z{{zzzzz{{z{~~|{zzyxvttvz}|}||yyvvwxzz|{zzz{{{{{{zzzz||||{{{||}}}~~}}}}}}~~~~~~~~~~~~~~~~~}}}}}}~~~~~~ ~ ~~~~}}~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~|z{{{zzzz{||}||{{yxvqrv{}|||{yywwwwzz|{zzz{{{{{{zyzz{{||{{{|{|||~~~~}}}}}}~~~~~~~~~~~~~~~~~}}}}}}~~~~~~ ~~~~}}~~~~~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~}}}}}}}}~~~~~~~~~~~~~~~~~~}}}}~~~~~~~~~~~~~|z{{}|{zz{|}}}{zzxxvqru|}|{zyyyxwvwyz|{zzz{{{{{{zyzzzz||{{{|{|||~~~~}}}}}}~~~~~~~~~~~~~~}}}}}}~~~~}~ ~~~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~~}}~}{{{~}|{z{{}|zyzzxwvtqv|~||zxwywwuwxy{{zzy{{{{{{{zz{zz{{}}}~|{}}}}}}}}}}~~~~~~~~~~~~~~}}}}}}}}}}~~~~~ ~~~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}~}|{{|{|{{|~~|{zyyzzxvsqw||zxwwywwuuvxzzzzy{{||{{{zz{zz{{|||}|{||||}}}}~~~~~~~~~~~~~~~~}}}}}}}}}}~~~~~~~~~~ ~~~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}}~~}|{{{z||{||z{zyyyyxvqpv|}{yvvwyxxuuuxzzzzyzz{{{{{zz{{{{{{{|}|{||||||}}~~~~~~~~~~~~~~~~}}}}}}}}}}~~~~~~~~~~ }}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~||}~~~~}|{{{{||z}|{||zywxytppu{|yyvvwyyxvuvwyyzzyzzyy{{{{z{||{{{{{||{||||{{}}~~~~~~~~~~~~~~}}}}}}}}}}~~}~~~~~}}~~ }}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}|~~|||{{{{{z} ~|}~~{{yxwspotx{zyvvvwwxyxxwxxyzzy{zz{{{{{|}}||||}}}}}}||||}}~~~~~~~~~~~~~~~|}}|}}}~~}}}}}}||~~~~~~}} ~~~ ~~}}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}~~|}}{{{{{{~}||~}|zxvroosxzyyvvvuwxzzywxxyzzy{{{{{{{||}}}|||||||}}}}~~}}~~~~~~~~~~~~~~~|}}|}}}}}}}}}}}||~~~~~~~~ ~~~~ ~}}}}}}~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|~}{{{{|}~||~|ywuqnnrvyyyvvvuwy{{zxwwyzzy{{{{{{{||}}}|||{{{{||}}~~}}~~~~~~~~~~~~~~~}}|}}}||}}}}}}~~~~~~~~ }~~~ }}}}}}~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{{{{|}{{}ywurooquyyyvvvvxy||{ywwyzzy{{{{{{{||}}}|||{{{{{{||}}}}~~~~~~~~~~~~~~~~}}|}}}||}}}}}}~~~~~~~~ ~~~~~~}~~~ }}}}}}~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~||}~~~~~~~~~~~~~~~~~~}}~~~~~~~~~}|{z{z}}{||~}yxvsqpquyyxwuuwyz}}|{yxyyzxzzz||{|}}}}||||{{{{zz{{}}}}|~~~}}}}}}~~~~~}}}}}}}}|}}}}}}}~~~~~~~ ~~~~~}}~~~~ }}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~||}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}|{z{{~~|{|}~}yxxuroptwxxwvuwz|}}|{zzyyyxyzz{{{|}}}}{{{|{{{{zz{{|||||}}}}}}}}}~~~~}}}}}}}}|}}}}}}}~~~~~~~ ~~~~~}}~~~~ }}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}~~~~~~~~~~~~~~~~~~~~~~~~~~~|{z{}~}|{||~~zyxusppsuxxwwvwy{|}||z{yyywxyyzz{|}}}}{{{|||||{{{{{{{{{|||}}}}}}~~~~}}}}}}}}|}}}}}}}}}~~~~~ ~}~~}}}}~~~~ }}}}}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|}~~~~~~~~~~~~~~~~~~~~~~~~~~}{z{~}|{{|}~zyxuspprtwxwwwwxz{}|{z{zyxwxxxzz{|}}}}z{{{||||{{zzzzzz{|||}}}}}}~~~~}}}}}}}}|}}}}}}}||~~~~~ ~|~~||}}~~~~~ }}}}}}~~~~~~~~~~~~~~~~~~~~~~~}~~~~~~~~}}~~}~~~~~~~~~~~~~~~~~~~~~~~~~|{{}~|{{{|}~|ywvsooqsuwxvwwxyy||{||zyyxwwxyyyz{{}}{{z|{{{{zzzzzzyyzz{{{|||}}~~~~~~~}}}}}}}}}}}}}}}||}}~~~~ ~}}}}}}|}}~ }}}}}}~~~~~~~~~~~~~~~~~~~~~~~}~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~|{{~|{{{|}~zxxtpopqsvwvxyyyy{{{||zyyxwwxyyyz{{}}{{{|{{{{zzzzzzzzzz{{{|||}}}}~~~~~}}}}}}}}}}}}}}}||}}~~~~ }}}}}}|}}~ }}}}}}~~~~~~~~~~~~~~~~~~~~~~~}~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~|{|{{{|}~~zyxtpopoqtuvyzzyy{{{||zyyxwwxyyyz{{|||||{{{{{zzzyyyzzzz{{{|||}}}}~~~~~~}}}}}}}}}}}}}}~~~~~~~~~~~ }}}}|||}}~ }}}}}}~~~~~~~~~~~~~~~~~~~~~~~}~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~}~|{|~{{{}~~{ywtqppmnqtuxyyyyzz{||z{{yxxxyyyz{{{{}}|z{{{{zzzyyyzzzz{{{|||}}||}}~~~}}}}}}}}}}}}}}~~~~~~~~~ }}||{{|}}~ ~~~~~~~~~~~~~~~~~}~~~~~~~~~~~~~~~~~~}}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~|}~~|{||}|~}zxurqpnnoqsvwzxyyz}}}|{{zyxyyyzzyz|||||{zz{zyyzyxxzzzzzz||||||||}}~~~~~}}}}}||||||}}}}}}~~~~~~~~ ~}{z{{|||~~~~ ~~ ~~~~~~~~~~~~~~~~~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~|~~}|{||}|~~zwtrnnonnpstwzz{yy|{||{{z{{yyyyyyz||||||{{{zzzzyyyzzzz{{||||||||||||}~~}}}}}||||||}}}}}}~~~~~~~~ }|z{{|||~~~~ }} ~~~~~~~~~~~~~~}~~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~}~|{{||}|~~{vtrnnonnprsvyz|zxyy{{{{zzzyyyyyyz||||||||{zyyyxyyyyzz||||||||||{|||}~~~~}}}}||||||}}}}}}~~~~~~~~~~~~~~ ~|{{{|||~~~~ }|~~~~~~~~~~~~~~~}~~}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}}~~|z{||}|~~{wsqqponnprsvxyz{xyy{{{{zyyyyyxxyz||||||||{zzyxxyyyyzz{|||||||||||{||~~~}}}}||||||}}}}}}~~~~~~~~~~~~~~~ }||||||~~~~ }{~klmnooonooortuvvwxzzzzwurpqqppppqqrssuxyzzyyyy||}}~}~~}}}}}}}}{{||||||||||||}}~~~ |sjd^[]_ada_][^cehhe``bglsz {qf]YX]bjtz~{urppmifaadgknprvyz|wpmjjjjjjjkknqqtuuuuuvvuwvwyxupf[XTV[^choy~~|xvtomjggcdfgghfhjjmprtttttsrrqpoqutuuz|zvplhbbdca\Z]dknttsrrsuwz~ kmnopppopppsuvwwwxzzzywurpqqppppqqrstvwyzzyyzz||}}}}~~}}}}}}}}||||||}|||||||}}~~~ yohc_\^_ac`]\Z]aehgda`beiqx zrh]YX[cisz~~zvspomhfaacfkmnknsy~~}ztnkiihhhhiklnpqtuuuuuvvuwvwyxupf\WTW\^chpx~~|xusnliggcdfhhigijjmprtttttsqqqpoqsrqswy~~zuplgbbcba[Y\ckottsrrsuwz~lnopppoqrrstuvwwxzzzzxvtrpqqppppqqrsuvvxzzyyzz{{||{}~~}~~}}}}}}}}}}||}|~|||||||}}~~~ }wogc_]^_bb]ZYX[_dggeb_acfmu~ {si^[Y[bhpx}~|xtrpmnifbbbejmonorw{|zvqkihhhhhhiklnortuuuuuvvuwvwyxupg\VUW\^cgpw~~{wtpljihgdegiiiijkjmprttttsrppqpoqsrprvz||ytqmhcbcb`\Z]ckruusrrsuwz~ npoppposttvuvwxxyzzzzxvtrpqqppppqqrssuvwzz{{zz{{||{|~~~~}~~}}}}}}}}}}||}}~~||||||}}~~~ |vngc__^`cb]ZXWZ_dhjhc`aaent} {ria][\bgnu||zurqommifdbbdinsvwwv{~~~{ytojhhhhhhhhilnortuuuuuvvuwvwyxupg]VUW[^dfow~~{wsnliiihffhiihjjkimprttttsqppqpnqsrptx{{{xtqlheddc_\Z\dmtvvsrrsuwz~ moqqrrrstutuvwxxy{{{ywusqpppppqqrrrstuuwyyy{}}|||||}}~}~~~~~||~~~~}}||}}~~~}|}}}}~~~~ }vmga]^_cda]ZXVY]elnida``emt| uleb``chot{yvtqomnjihgdcdhow}yx{}{xrnjhhhiiiiijlnosuwvvvvwwvwwxxyupg]VUVZ^dhow~|zurojghhgghhjjikjkmnpstsuuropppoprrqqtxz||wsqkgecca_[\`elsutssstvxy}nooqssqrtuuvwyyyy{{{yvvrqpppppqqsstttvvwyyy{||||||}}}~}~~~~}}~~~~~~}}||~~}|}}~~~~~~ ~vnd]\^acca_ZVUX`jqrld``aflrz zrhc``dhmrxwsqnllmjjjjfdeit~ {prz~~|xsplhhiijjiiijmoprtvvvvvwwwwwxyzvqg]WUWZ^dgow|~{wromighhffgijjjklmmmpsutttroooomprqprtxz{zwsniecaba_^_bflsusrrtuuxz~nonprrqrtuvwxyzz{|{{yvvrqppppprrssuuuwwxyyy{{{||||}}}~}~~~~~~~~~~}}}}~}|}}~~~~ wnb]^^cccb`YTTZdotunea`bfkqw {slfaaeilottpnmlllllljhfgkw ynqxzvqlihjiikkjjjkmoqrtvwwwwwwwwwxyzxri_XTW[_cgnu|~}zvsoljhhhhffgjlllmnnllpsvtssroooomoqportxz{yvrlhca`ac``bcgnstrqqtuuwz nnmnqqrsstuvwx{z{|{{ywvtqppppprrssssuwwxyyy{{{||||}}}~}~~~~~~~~~~~~~~}|}}~~~~ xnea`_dee`^XUT\hqwwngaaadkqw~ {sliccfjlortpnmlklkmmlkhhnv~wpq{~xuoljhhjjkkjjjknpqruwwwxxwwwwwxyyxskaYTV[`cgmt{~~~{xsnljgghhhgfgkmmmonlllpstsssronnpnpqoortyz{ytpmjdb`aeccdfjnrsrqqtuvxynnmnpqsttuuvwx{|~~|{ywvurqqqppqrrtsuwxxxxxzz{{z{}}}}}~~~~~~~~}}~~~~~~~~~~~ ~ynhebdefc^\WUV_lu{zqjba`ciou| |uqjeefikoppomllkkmnnnjhinv}xqqx}wsnkhhiijjkkiiknoqruvxxxxxxxvxxyyytkbZVVZ`chms|~~}ytojihghfefghimoooonljkpsssuuroomnppommptxxzxspmida_aebadimrturpqtuwwy}nnmnpqstuvuvwx|}~~|{xvutrqqqpprssuuwxxxxyyzz{{y{}}}}}~~~~~~~~}}~~~~~ yoigcdeec^\WUYdpx}|skda`bflt{ }wskecgijloonmllllopnolklow~~xrpt|~yuqlkiijlnmmkiilopqsuwxxxxxxxwxxzzyukc[XW[_bgms{~~~}xuqmihihgdefhjknooooljjkpsssttronmnppommquxxxvromhb`_cebdhkqtvsqpqtvwww|nnnnpqtuuvvwyz}~}}{zxvutrqqqrrrsuvwxxxxxyyzzzzy{}}}}}}~~~~~~~~~~~~~ zrkhdcdec`^YW]ht{}vohc``dkry xrkdbfhjknonlllllppoonnqrx{wtoow}|vrpkhjklooonkijlppstwxyyxxxxyxyyz{ytlcZWY]_bglsy~}zxtpoliggfedefijkmoooolijmpsssssromlnppommquxxuspmkfbaaeeehlotwvsqpqtvvuuy nnoppqtuuvxy||~~~{ywutsrqqqrrqrtuvwxxxxzyzzyyy{}}}}}}~~~~~~~~~~}} ztmhdbceb`^ZW`lx~~xqjd`_cjrx} yskfaceimmmmkllllqpppmotuy{ussojs{{vqojhjmnooqokiiknouvxyyyxxxxyxzz{{ytlc[XX\^afksy|~{vtpmjjhgedccefjlnnooooljjlpsssssromlnppommrvxxsqnkifa`cefhloqvywrppquvuqtx klnopqsuvwxzz}~~}{zxvutsrqqqqrrsuuwxxyyyz{{yyz|}}|||}~~~~~~} ztnheccb`^][\dpzztjda^bhnt{ {tmkfdeikjijkkkmnrssrstvxz}ysnnqphluvtokjjklnoqrpkhikmoswxyxxxxyyyyzzz{zume^ZZ\_aglrwz{}}{xspligffdbbcdfhmnppponmljhlortsrsronlnpqnmnqtvurqnmkgc`bffjoruxzwrpqruvtrtxjlnopqsuvyyz{~~~~}{zxvutssqqqqrrsuvwxxyyyz{{zzz|}}|||}~~~~~~~ ztnhdbba`^]\^ju|zumgb^aflsz |vomhehjkiiijllmpsuustvwx{{uoijoplhpspkijnpoprstpkhikmoswxyxxxxyyyyzzz{zvnf_ZZ\_bgkqwz{|zxupligeddcbbdehjnoprponmljhlpstsrsronlnpromnqstroolljfcacfhmrvy{zurprsuvtsrvjlnopqsuvyy{}~~~}|{zxvutssqqqqrruvwxxxyyyz{{{{z|}}|||}}~~~~~ ~xslfb``ba`^^cox}{vqkc``fkqy }wpnigikjhijkllnqtvuttwxyyzunlnqolikoniikoqonrttpkhikmoswyyzzyyyyyyzzz{zvnfa][]_bgjowz{zwtrniggecbcddefiknoqsponmjhhkpstsrsspnlnpromnoqqolkkkhdccegjqvz}}{usrstuwtsrvjlnopqsuwyz|~~~~}|{xvutssqqqqrruvwxxxyyyz{{yyz|}}|||}}~~~~~ }wpieb``a``_`eqz~{vrlea`eiow~ yrniehkjiilmlloquwvuuxzyxxwrpttmkjhllgjmppmmsttokhikmoswz{zzzzyyyyzzz{zvnfa^[^_bhinwz{yuqnkgeecbacffegimnppqponmjhgkqttsrstqnlnpqnmnnnnmjjiifdddfjlsz}~zurstuvwtsrtlmooopqvxz|}~}}{yvutsqqqqppqrtuvxyyzyz{zzyy{{}}yz|}|}~~}}~~{unie`]]``^^biu|{tlfa`bgpv|}{vpkigklihknmnpruvvvv{}zwwvvwyvkhiimqmnptvrmoqrpkiijmprwyzyyyxyyyyzzzzywohc`]^`cfkotxyusnligddb_abdffhlmpqrrpmmnjhhkrvutstspnlmopmklllkjjhffcbbegknt{~~xtrrtvwwtqpu}mmnpoprwy{~~~}}{ywutrrrrrpprttuvxyzzzzz{{{{{{{{y{|}|}~~~~zrlgb^\]___afnv~}unhbabhpv|~}wrljghjjijllmpsvvxxw|zuuyz{yslgfglqrqt|snqrpljijmprwz{yyyyzyyyzzzzzxoid`^^acfjnrvwspmjedccb`bceghjmmppqqomklkiilrvuuttspnlmopmihiigfgeedcbadgkrx|}wtqqtvxvsqqt{ mmmooprwy|~}}{yvutsssrrrrsttuvxyzz{zz{{||{{yyz||}}~~~}xqje_]\^__`chqz~wpjdbagnuz~xrmjgghkjjklmqtvxzzz~ysuz}}vqnheeiostz|tpqrpkiijlorw{|{{zzzzzz{{zzzxqjd`^^acfhlpttqljfdcbbdddeghjlmmnnppnlklljjnrvuuttspnlnppkhgfffeeedccbaehlrx}}{wspqtvwurqprz mmmnnprxz}~}}{ywvusssqqrrqstuvxy{{{||{{zz{{yy{||}~~~~|xphc^]]_```clu{xrkfb`flrx~~ysmjgghjimlnoruxz{{{zsuz~|toojfeglru{~xtrrrpigijkorw{|}}{{zz||{{z{{xslc`^^acfhlorqnjhedcbbdddgiklnoommppnllmmllnrvuuttspnloqpjfeeedcbddccacfilrx{~|{wsqttvvuqqpqyllnooqtw{~~~~~||{yvvusrrqqpqqsuvvwwzzzzzzzzzzz{{{|}~}~~~~~ysngc`__```aemw{umg``chou|ysnjfehjjklprtwyz|z|~wqw~~yurokjihjoqx|xrquvtojghklprw|}||{{{z||{{{{{xrkda__bcegjnpojhefdbabbcgihjmoonmmoommnomkkosuuuuuspmlopoicbeea`adcdddffhmrx||{zuqqssuurqqppxlllnorux{}~~~~||zxvvtsrrqqpqrsuvwxxzzzzzzz{{zz{{z{}~}~~{vnjfc^]\_``bhnx}woi`_bgns{~ysnieehjjklptxz{zywyzxus{{yupmlkjlmryyuqrxyvqkhhklorw|}|||{{{{{{{{{ywqkea``bdegjmnniecedcacccfhijnoonmnppnmmnmlkpsuvvvvtqnnpqnhbbdda``bcddddghmry}|{ytqqssutsrrrrw~lllnqsvx{{}}~~{{zwvutrrrqqprstuvwxxzzzzzzz{{zz{zz{|}}~} |wsmhdb^\Z``adjqz~xqkb_bflrz~xtnieehjjklqty{|zywvvvvw}{wrponnmot{xtsv||xtkhhklorw|}||||{{zz{{yyxvplfb``adghikkkhbacddbdfefhjknoonmoqqpmmnnllptvvvwvurpoprmgaabb`_`abcccdghmry}|zxtqrssuttsrqqu|llmoruvy{{}}~~zzywuusrrrqqqsstuvxyxzzz{{{{zzzzzzz{{|}~~~ytplgcc_[Z_`cfkt{xrmc_afjpy~ytnkhehjjklqtx|}|}zuvvtv||ytttqpopu|ysu{~}xsmihkmprw|}|||||{{{zzwwwvplgd``aehhhhghfcaddddghhikklnoonnpqqoooonlnrtwuuwwsponoplfaaaa_^aaabccefhmry}|zxurqssuurqqqqszklmoqswxyz{|~~~|zyxwvutrqqrrrtuvuvxxwxxx{{z|zz{{{{{||}~~|uqmgec`^\]__bgpyzune`bekqw}xvpjfdfhjkmsw||}}yuutu|}zyxzvtursv{wqt|~}xrlhhjloqv|}}}}{|||zywwwvtrnie```cijjgggecacddhhijkkklnonmmmpnmpqqonnruvttvvspnopplf`aa^^__aabcdggimsx|{xvrprstvtqrrqqsy~klmoqtwxyz{|~~{zzyxvvttqqqqqrsttuvxxxxxyzzz{zz{{||||}}~~~ysnjeec`^]]abeirz{vofaaejpv|~yupjgeghkknuyz}}zzyz}zwyyvrpqrtx{xsu|~}yskghjmprw|}}}}||||zywwvusroiea``chhieeeebbdeeiijjkkjlnonmlmnllorronosuwuuvvtqnoppkeaba^]]^addcfiijnty|{xvrprstvtqqqpprx~|kmmoruwxyz{|}}{zyxxwuttqqqqqqrssvwxxyyzzyyyzzz||||||}}~~|vpjfeec`___cdhmu|~|voga`dhnu{}zuqmhfghkkpvzy~}~~}}xvttroopruxzxuu}~}xrkghjnpty|}}}~}}}|zywvutspmidbaadffedffebceggjjjjjjiknonmlllllprrpoosvxuuvvtqnoppkebba^]\_adeehjjkosy{zxvrprstvtqooppqw{znomosuwxyz{|}}|zxwwvutsqqqppqrrsvxxxyyzzyyyzzz||}}||}}~~|ytmieeec`^_bdejqx|~|vohb`cgmu{~zupohdfhkkow{|~}}}|xutqqnmpqsuwzxux}~}wqmihjlosx|}}}~}~~|zywuusrplidcabedddeggebcfhikkkkiiijnonmkkllmqrrqpotwxttvvurnoppjd`aa]_\_accgikklotyzyxvrprstvtqooooqwxwmmmotuvxxz{{||}{xwwvvusrppqqrsstwxxxyyzzzzyzzz{{{{||~~}}wqlieeeca`adegksz|vohb`aglsz|wtpmieegklpvz{ zvtvxxurqnlnnortuxzxvw~~wqokiklnqv{}~~~~~|{ywussttqmhebbbeeeffggggfgjkjkmjijjjnmmllmmnopoqroqtvutruvtqmnqokc``bbb``bcdfijllqtxzyxuqqrtuvsqpppppuutmmnptuuvxy{{||{zwvvuvusqqqqqrrstxxxxyyyzzz{{zz{{{{}}~~}|wqljgfdcbacfgilt|~xpicaaglry~~{vspmjgehknquy{zupnqrtrqolkmoptxvx{zux~~xqnjikloqv{}~~~~}|{ywtsssspmhebbbefggggghhhhjkklmjijkkmmlklnmnnppsspruvutruvtqnoqokc`_cccaacdfgjjlmruxzytqqqruvuqopppppstsmnoqsttuwxzzzzzywwuutsrqqqqqrrstwwxxyyyzzz{{zz{{{{}}~~}|wplifeedddeghkpw~zslgbcfkpw}|ytrpnkhehlpptx{~wspmnpqppnmmmotxzyy{xuy~~xqmiiknprw{}~~~~|{zxvstssrolhfcaadgghhhhiijjlmmnnkjijjkkkjlmmmlprssrsvwuttvwtqopqokeaaddcbbeegijijnrvyyxurqqruwtpnppppoqrqmmprrtuvwxyyyyyxvvvvsrrqrrqqqrstvvxxxyyzzz{{zz{{{{~~~~}|vpkjfdedfeggimsy~~zrniacfjov}~{xsppokiefknotx{|vqqmmmnnnmmnmqvz|}yxuuz~xqmiiknpsx{}~~~~|{zwvssrrqnkggc`adgghhhijjkkmnmonljiiijjkjklomkpqrttuwwvtuvwtqopqokfccdddccegjkjjkosvyxwurqqruwtpoppppopponoqqrqrswxyyyxxxvvutsrqppppqrsttuvwwwxyz{{{{||}}~~~~~~~~~}wpjhgfgghhhjkov~~{tojeceiot{~|zvpppomifgjnoswz~zuppnmmnnnnnmotwzzxuu|~xpkiiknsuy|~~~~}|zxvvtttsrmjhieabdgjklkikkmnpqqpnljfggijkkmmmnmprtttwyywtuvvtpopqolfcceddcbdhjlkkkosxxxvrqppsvxvrpnponnnoonoqqrqstvxyyzyxwuuttrqqppppqssttuvwwxyyz{{{{||~~~~~~~~~{unjhhgiiijjlmpw{upjgceiosy|{xuppppnifeinorvz~{vqommoqomnonqvx}~ytty~xpkijknsvz|~~~~}|zxvvtttsqnkiifbbdimpqommnnpqrspmkjehhijjjlklmnprstuxyyvuvvvtpopqolfeedcccbdhjlllkptwxwvrqppsvxwsqnolkkknnnoqqrrstuwxxyxwwuussrqqppppqssttuvvwxyyz{{{{{{}}~~~~~~~~~ysnjiijkkklllnsy|vpkfceimswzywtqppomjgdgloquz~}xsomnqpnmmooqvz{vuz~xpljkkorwz|~~~}|{yxvvtutspoljhgccglostspopqrqstqmkjghhijiihiklnprsuvxyxutuvusoopqolfffdcbbbdhjmmnlpsvxvurqppsvxwtqonmlkknnnoqqrrstuvwwwwwwvvssqpqppppqssttuvvwyzyz{{{{{{||}}~~~~~~~wrljjjklmmnnmouz}wpkfdcgmrvyxutqppnlkhdgkoquz~}wrpnpqnljloory| zxwz~xplkklprw{|~~~||{yywwuttrqmkkhgcfknrtvurqrsttutqnljiiiijiigijknpqsuuz{xursvuroopqolfddcbbbbdhjmmnmnrvwvtrqppsvxwtrpnmkjjmmopqqrsstvwwvwwwwvuttrqrqqqqrrsttuvwxyzyz||z{{z{{|||}~~}~}|uojijkmnonponqv{~xqlgddglpuwwvsqoomlkiehknosw}|vrplmlggjlnpuz{vuz~wpkklmoqv{}~}}{yzxwutrqonkjhgfhnrvyywusrsuvwurnlijjjhiiiggjknqrsvvxyyustvuqooqrpledbcbabcehknnmmnsvwutrqopsvwwtqomkjjjlmppnnqrrtvwwvvvuuuusrsrrqqqqrrsttuuwxxyyzzzz{{yzzzz{|}}}|}|~~yrliijknpqpponrv|ysmiecfkotuvurqoollljegimosx~~}{wrpljgfgkloru{ {vsw}~wpkklmoqv{}~~~~}{{zyxvtrqonljiggjrx{||zwvstwxxvsoljihhhiiihhkmnqssuwyzyustvvroorrpjddbbbabcehknnnmosvvutqqoqsvvvtqomkjiilmppnnqrrtvwwvvvssttsrsrrqqqqrrsttuuvwxyyzyyzyyvvvttvy||{z{z{{}~~wpjihiloprqqpnrw|zuniecekorstsqpoommljeeimpqw}~}|{xspljghikmosx~ |vrv}~wpkklmoqw|~~~~}}|zyxvtspooljihhmt{~~|yvtuxyyvspmlhgghiiikjjmnqsstvz{yustvwrporrpgcdb```acehknonmpswutsppqstuvvtqomkjhhklppoopqqtvwwvuussttttsrrqqqqrrsttuuuvwxyzyzzxvrqqpprwzzyyywyyyz||voghghmpqqpqposw|{vpiebfjnprsqqpooonljfejopqu{||{zxsplijijloot{ vsx}~wpkklmoqw|~~}}|{zxvusqnoljihinw}}zwuwy{{vspmkighhiiijjikmqsstvz{yustwwsqorrphedb__`acehkoonmptwusrppstuuwwtqomkjhhjknoooopqsuvwuttuuuuttssrrqqrrssttuvvwxyy{{ywusolkijmqxxvttrstsvy}~~{tmhghjkoooooopt{|vqjdaeimopqqppppnonjhginpptz}}}{xtqmjiiimnqv} yuwwpkkjkoqv}~}|zyxvuqpnolihhjszzwvw{|zwtpmkjihiigiiiklnoqsuwy|yutuwvrrqrsnfabb``_`cfhknnnnquvtrqqqstuvxwtpmljihhjknooooprsuvvuttuuvvssrrrrqqrrssttuvvwxyy{|ywspmjifgjntsqokjikmquz}~~}wokhffhjnoooopqu|}wqjebdgknopppqppopokhginpqsy}}~{wuqlkiiinosx~ }wx~~vokkjkoqv}~}|{yxvuqpnpmjiiku||xwx||yxurpmkihggfgiiklopqsswz|zvuvwvrrrssmd`ba```adfiknnlmquvtqoppstuvvvrnmljihhjknoppprstuvutttuuuussqqrrrqsssttuvvwwxyy{{xuqljihfegkonjgdcdfimptx{|~~~~zrkhfdceimpppopqv|~xrjecceilmpppqppprqmhfilpqsx|}}{wtqlljjjnqv{ }uv}|vpkklmoqv}~~}{zxwvrqopmkjjmv~}yxz||ywurqnlihfffghhjlpqqqrv{{yuwxwvrrrssme`a````adgilnnklquvtqopqsttuvvqnomljhhjknoqqqrstuvusttuussrrqqrrssttsttuvvwwxyy{zxtpljigfedhjhgcb`bcfjlpty{|~~~~~~{vpidba`chmqpppptx|~xskfccfiklooprsssrsnheiloprw|||zusqnlkkjosx} ztt}|vqmmlmoqv}~}{zywvrqoqnkjjow~}zz||{ywurpljihggghhhjloprqtwz|wsvwwvrrrssme```__aaehjmnnlmquvtrqqrstsuwwqnonlkhhjkkmoopqqqstssttssrrrrrrrrssssststuvvvxyz{yxuqmkjhgeccddcbba`adffipv{}~}}~~}ytnfc````fnqqqppv{~xrmfacfkjjlmoprssssoighknpqtz|{ytqqlkkllpvz~ ztu}~wpllklnrx}~~~}||zxvusqoqomkkpw}}{z{zyxwtrpmjigffiihgijnpqrsw{|yuuxwurrrrqja^_]^`bcfhkmnmlmrsuttrprtttuwwqnmmkiffhkklmnpqqqssssssssrrrrrrrrsssstttuuvvwxyz{yxvrnljhfdbaaaaa``accddekpv|}|}~~~~{uqkfa]]`ciorrqqrx}ysngbadijjkmoprtuutpkhhknoqtyzzvqnnjkklmrw| |tsz~|unkkklnrw|~~}|{xwvtrpqomlkqw|{yxzyxvusrpmihgggiiihikmpqrswy{xuuwwurrrrpi`]^]^`cefiknnmlmrssrsqprvutvwxsnllkifdhjklmnpqqqssssrrssrrrrrrrrrsssttvvvvvwxyz{yxvrnmjigecbabca``bdcdccfmrx{}}~~~~{vqlie_]\`ekpsrqrty~{uoidacehjkmopstwwurkihjnoqtxyysnlljkkmpuy~ vrvz{unkkklnsw|~|{yxvtrpqomlkqwz}}yvvvtutsppolhgfhjiiihhkmopqswzywstvwuqqrroh`]_]^`cegilonmlmqrrqqqprvwuwyxtollkiedfhklmopqqqssssrrssrrrrrrrrqrssttvvvvwwxyz{yxvqnmmkigedcddb``ceedccfiouy{{}~~~xrmheb^\]`fkqsrqrtz|vokebdegjkmpquxyyvslihjmoptwxvrnkmkkknrv{ xtsyzupjjklosx}~~}{{ywurpqomllqvy{}{xtsrqrrqnmmkhgffiiihhgimopqsw{ywssvwuqprrog`]^]^_bdgjlonmlmprsrqqrtuvvwyxtollkidceglmpqppqqrsttttssrrsrrrrrrsttuuuuwwxxxyz{zzwtqqqpmifffeceddeecdbaadktxz||}~~~~xrmfc`^[\]akmqsrsvy~}wqkeceggijmpqsxzzxunjhhoppsuvsnlkjkllnqw~ {srxxupkjklnrw|~}}||zxvsrrpnmmqtwy{}{xrqrnllnlmmkjfddeiiihgggkoppswz{zvtuutqqsrne^\\[_abegjnpnkilpqsrpppsuttuxxqmnnlhfdefmnppppqqrsssssrrrrrqrrrrssttuuuuxxxxxyz{{{yvttvvqmihhgfilmllhhdb`bfpvyzz{}}}ztnhda^\\]afmoqrsvy|~xrkdadfhjjmprtx||zwpjhhmppqsrokiijlllnqx yrqtwupljjknrx}}}}}|zywussrpnooruxy{wrmkkhgfhikmkhddceijjihhhhlmotxz|yutuutrrsrne^\\[_adfjmoqnkilopqpppqststuyyrmmmlhfdcemnppppqqrsssssrrssrqrrrrsstuuuuuxxwxxyz{{{zywwxxtqlkhilortrrolgbaacltwwxz|}}~~|wpjdb_^\_`dhmpqrux{~xrleacdhjjmqsvz}}zxrkhhloppqmjghhjlllnqx yrqsvtoljjknrx}~}}}}|zzxuusrpnpoqtvwwrnigfdcadgjnkhddceijjhjkkijjotxz|wstuutrrsrne^\\[^aefloopnkjmopppppqstsstzysnmmkgeccemnooppqqrsrrrrqqrrqprrrrsttuuuuuwwxxxyz{{{z{yyzzwtnmjlosuxwwsqjbbbcjpsuvyz||}}|unica^_^abfinqstvy}~xrlfabcghjmqux|~~{xtlghjnppmkgefejmllnqx {tstvtnljijnry~~~}|}}|z{xwvtqpoonortvtmiddc`_`cgimkhedceikjhkmmljknswz|wstuutrrsrne^\\[`begknopnkkmooqqqqrtvvrsxxqmlljgebdflmmmmonpqqqqppqqppqqqqrsstuuvvvvwxxxyz{|{{{{||{{wunnmptwxzzzxsldbbcfimqsvyz{||~~ypkgeb```bdfkorsuy~}tmhb`dghikrvy~~}zvnhgknqmifebdehkklnrx ~xvwwuplkijosx}~~}}}||}|{{yxvtqnpnnnpsusnf```^]^cfilkkgdcfjklloonnlknqw{{xtuvvussrqog]Z[^bfhhknpomjjmppssqqsuvvtsxxqnmlihebefklmmmnnpqqqqppqqppqqqqqrttuuvvvvxxxxyz{|{{||{{{{xvrqqtwyy{{{zupifcbcdfilprrstw|~}~}vnifdcaaabeknpruw{}voicbcgiikquy}~{vpigjmnjgcdcdehlkmnrv} zxyyupkiijotx}~~~~}|{||||zxvtroonnmnqsrme`_`^^^bgjmnlhdcfiklnqqoomkmqv{{xuuvvusssrnf\Z\`cgjjloqpmijlnprsqqsuvvrtxwqnmkjiebedklmmlmnpqqqqppqqrrqqqqqrttuvvvwwxxxxyz{|{{||||{{zwuuuwyz|{|||xsnjgcb`bdgjkjkmrw|}~~zsnigdbaaefjmpptx{~ ~vqlfcchjkmrvy}{xqjfhlnjebbceehllnoru| }xvzzupkiijnsx}~~~~|yyz|||{ywusqnnnmnpqqld`_```aejmopmhdcfiknortqonlkou{{wtuvvusstsme\Y[aegkkloqplhijnprsqqsuvvquyvqnljjiebdblmmmlmnpqqqqppqqrrqqqqqstuuvvvxxxxxyyz{|{{}}{{yy{yxxvyz{}{{{{{uqkifc`abffffgimu{}~}wqlhecaabghlnotxz} }vslebehjlnsx{{ysjefjnjd`acefhlmnqsv} |vtyyupljjkmrx}~~||xwxx||~|{xusqnnnnopqpkdbddbcdjmpppkgdcehkpqswrpoljnt{{vtuvvussrqme[YZ`fhkllpqqkhjimorsqqsuvvuuyvqnkjihebcckjjklmoqrrqqppqqrrrrrrstuuttvvwwxxxxyz{{||{|||{{|{zz{{{{||||{zvsplid`acaa_`cehox{~{vqljgda`dikortv{} ~zslfcegjlpuz~}yskfehlkc`acehlnnnotx~|trwzupllmnmqx}}yurrwz{|}|zvspnonnnqrnkeffhjlorsutpkfbbeilorvzwrnjint{{vtuvwtrssrlcZYV_gkmmnpqojggjmpsspqtuxwstxupoljjhecdfkijklmnpqqqqppqqrrrrsttuuuuuvvwwxxxxyz{{||{|||||||{{{{{{||||{zwtrnjfceb^\Z[^`bluz~ztolihfdehmptvy{ |tmfcdgjmqv{~}xtlgfhlkf`adhjnpomou{}wqqwzuplllmorx}{vplmtx{{}{xurponnnnopokijlqstvwwxwqkfbcegjnrx{ysnhgmtzzvtuvwtrstslc[YW_imnmopqmiggjnruspruvwusuxupnlkjhedegjijklmnpppqqppqqrrrrsttuuuuuvvxxxxxxyz{||||~}}}}||||{{||||||{zxxtqljggc`][[\^`hqx}~xrmkhgggjnsvxy|~ }umfcdgjosw}~xtnhgilkhbbfjmqrpmou| yroovyupllklnqx}xsjffnuy||zwspoommlmoppnnotwxxyyyzwrlfbcegjmqx{|unfejszzvtuvvsrstsne^\[ahlnooppmighjostrrsvwwutvxuomlkjhfefhihjknomoppqqppqqrrrrsttuvvuuvvyyxxxxyz{|||}~}}}}{{||{{||||||{zz{urnljgda_]]\^`enw}|wqlkhggjnsw{|| }ungcegjnrx~~ytpjgjljhdcfkottqmot{ xqppvxupllkllpx}}|vofbbkry|}{vrqnmnmjmqqpqquyzzz{{zzwrlfccegilqw{~xpfchsyyvtuvvsrstsmf_]\bfjooopplhehjnrsqtuvwwutvxuomlkjhgeghjijkmnopqqqqqqrrrrrrsttuuuuuvvxxxxzzyz{{}}||}}||||{{{{{{||zz|{zywtrnlhfd`____`enw}|wqlkjhhlrx{}~ |vogefhknszzupkhjkmiechnsuwrlmrx} ~vooqwyvokkjkmry~|sia]]fou{{xtqpomlmmnpqppqv{}{{}}}{yskfcdehjlrx}|vnfaeoxxvtvwvttttrnha`^agjoprrqlifikprssstwxxuuxyvolllkideghjjjkmnppqqqqqqrrrrrrstuuvvvvvvxxyyyyzz|{||{{||{{{{zzzzzz{{zz||{yywuqnkhfdccb``dmv}zuomjjijpx|~ |xpifehkosw}{wrljiknjgekrwyxtnmnu|}uooqvyvokkjknsy~{qg`[]enuzzvqonnnlmoppqqqsx}}}}}zrkgcegikmrw}|vme`bmuwvuvwwtuuuspjed_bhlorssrnihjmrttsstwxxusvxuolkkkiegijjjjkmnppqqqqqqrrrrrrstuuwwwwvvxxyyxyzz||||||}}||||{{{{{{{{zz|||z{ywvroljiged`^bkt}~ytnmjjjouz~ }yrkffgjosx~}zunjhjmkiipvz|{uommr||urqquxvokkjknsy~zph`\^entyxsommonlmoppqrruz}~~~~~~|xqjgdehikmqv{{vld`bltwvuvwwtuuvtpifeadjmpsssqnigkmrutrstwxxuruxuoljjkigikmjjjkmnppqqqqqqrrrrrrstuuwwwwvvxxzzxxz{||||{{||{{{{zzzzzzzzzz|||{zyyyxsqnlkhea^ajt}~xsmlkkkrw{ ~yskgfgjosy~|wpkhjlmlksy{|zuokmtz{vtrquxvokkjknsy~zqia^`gotwtrnlmnmlnnopqppuz}~~~}{vpifdegiknpvz{uld_bmuyvuvwwtuuvtpigecejoqsssplihklrwurstwxxtqtwtoljjkifhkmijjjlmoqppppppppqrsstuvwxxxxwwxxzzwxxy{}||{{zz{{zy|zyyyy{{{{||{{zy||{zvtrplga^agr|~xrkjklpv{~ {uoheehmrx~|wrliimnlmu|~}ytolnrx{utvtuvvnlklkotz{ricbchorssolllkklmnoppqquz}~}}vngcdefgjmqvyyulc_bkuxvuvwvutvvsnighggloprtrnjgfjmquustuxxwsquwrnkkjhffijmijkjlmopppppppppqrssuvvwxxxxwwxxzzyyxy{}||{{zz{{zyzzyyzzzz{{||{{{{{{{{xwtsnga^bis{~|vpljklqw| ~zupheehlqw}~zwsmjimomov~~ytommrxyuvzvvvwnkjkmoty~{ricdfjoqrrolmmlllmopqqqqsx}~{tnfccefhimrvyytmd_bjtwttwxvutvvsoihiihmoprtsnjhgjosvuttuxxwsquwrnkkjhfgjmnijkklmnoppppppqqqrssttuvxxyywwxxzz{zy{{}||||||{{zyxyxxyyzz{{||||||{{{{zywupha^ajt{ |xupmkklqw} ~zupgedgjov|~zvvtmjimoopxztollrx}wty}yvvwpkjknnsx}zsjffjlnpppnnmmllmmpqqqqqsw}~zsmebcehijmswyytmf_ajsvtuvxvutvurnjijjjnprsutnjiilqtussuvxxvrrwxsnkkjhfgjoqijlllmnoppppppqqqrssuuvwxxyywwxxzzyyz{{}||}}~~{{zyyyxxzz{|{{||}}||||{{{{xwsja^`jt~zutolkmmsx~{uogedfinu{}yuvumkimopqy{toklrxyvty}zvvvqmlllmrx}{sjggklmpppnnmnlmooqrqqrrtx}}~}zrlebcegiknsxyyunha`iruuvxyvutvuqnkjjlknprsutnjjjnruursuwxxuqtxxsnkkjhfgjnpijjjklnppqpqqqrqqrssuuwxyyyyxxyyyzzz{{{{||||}}{{yyyxyyyz{z{{{{||||{{{{{{{zuka^ajt|}xqolllnptz~ |vpifdeims{|vttuokjmopry}zuqkjovzvvz}yuttqnlmmnsy~|tollmmmoppmmomnmnoqqqqppsx}~~~~}ysmgbcegjkosy{ysmfabjrttuwxwvuuuqnkkllmoqsttqmkklpvwvrsvvxxtptyysnlkkiegilqijjjklmooqpqqqrqqrttvvwxyyyyxxyyxxzz{{{{||||||{{yyvuuuvwzyzz{{||||||{{{{{ytkc^ait|~{uomlllmqu{ zvqjfdejnry~~zqortokkkmpsx}zvqlkow~zxyyyvstsrnlmmosy}uqnnpnmoppmmomnmooqrqqppty}~~~~|xrkgcdfhkmqrx|ytnfbckprtuwxwvvvuqmjlmnoqrsttqmlkorvxvtuvvxxtptyysnlkkieijlqijkkkklmnppqqqqqrsuuwxwxyyyyyyyyxxzz{{{{||||zzzzyxvvttuwxxzz{{||||||||{{|yumd`ags{~{uqmkkllmqw} }xurkfdeimrw}~{vnlorokjjlorw}|vqnmqw~}z{|xusrrsrnlllnry|vrrrsnmoppnnommnoppqqqqqtx|~~~~~|xqkgceghknstz|ytohddmprtuwxwvvvvqlijmpqrstuusommotwxvtuvvwwsrtyxrnlkkigjlnsijllljklnopqqrqqrtwwwwvwyyyyzzyyxzzz{{{{||||zzyyxxwvvvuvvwyy{{||||{{||{{}zxpd`afsz~~ysnjjkklmpw} ~ytrlfdegkqv|}yslkmqokijmosw}|vqmlqx~}z|}xtqqqtqnllknqy~yvrttqnmopponommooppqrrqquy}~~~~~|vqjfccfhkotw}|ysnjffossuvwxwvwwwqljinprtutvvsomnptwxvstvvwwrrtywqnlkkiijlpuhijllllmmoqqrrrrsuwwvvwxyyxy{{zzz{{{||||}}|{{{yyxxwwwwuutuwy{|{{{{||||||{{xpfaaeqz~~ysolhhiijnsy~ }ysrmgddfiptz~~zunfgmqnjhllnsv{}vrnlrx~~z{{vsopqtpmmmlmqw{xssuusommppoppnmnoopprrrruz|~~}}zvoiecdghlosy}|wrliggnrttvwwwvwwwqmkloqsututvspoorvxwsstuuwwssuxvqomljjjjmqwhijllmmnopqqrrsssuwwwwxxyyxy{{{{{{{{|||{|||z{{yyyyxxvvuuuvwyz|||||||||{{{{wpe__cnx|zunkihghijpu{ zuqpmheegins{|{vpjedinokimmosu{}wrolpv~zzyvtppqsqnklkmpv~yvtuwwupnnqpprqomnoppqrrssuz|~}~}zvoidcdfhlpsxz{wqlihjpstuxxwwvwwwrnlmoqsuuutvspopswzwstuuuxxttwywpnlljjjloqvhijllnooqrrrrrsstvwwwwyyzzyz||||{{||}}|z{{{z{{yyyyyywwuuvxwyy{}}}}||||{{{{xri`_clu{~wskgggfgikqw| zuqomiffhhnsyyvpjecegnnmmonpstz}xsolou} }{ywurqrsqmjlklnu{}zvsuwyyvppprqqsqomnpppqrrssty|~}|zvojeddfhkptxzzvpmkimruuuwxxwvwwvsnlmoqstuvuvsqpruxzvutuwwxxttxzwolkljjjmprshijlmnppqrrrssssuwwwxxyyzzyz||||{{}}~~}z{{{{{{yyzzyyxxuuuwwyyz{{}}||||zz{{zulc`djsz~}voieefefilsx~ ~xtrpniffhhlruvrmfbbdflmnoqssrsz}xsolou| |}{urrrtplkkjknsxywtqtw{{wpporsstqomnppqqrrssty|}|yvpkdcdehkpt{|xupmkjotwusvxxwvwwutnlmpqstvvuvsrpsuxzvuuvyyyyttvxvnllljjjkorqijjllmopqqqrrsttuuxxyzzyzzz{||{{|||||||{{{y{yyzzzzzzzzxxvwxxxy{{||}}||{zz{ztmc`cjtz~xtngdccdgipt| {wtpnnjhhiilprrmhdba`cinpquyywvz~|xrmmqw~ {urqstpljkjkmqtvtqptx{zurpssuvupnmnnoqqsqqruy}~{xuojeccehlquzzxsonmoquxwuwyzxxxxwrljmprtvwuvwtrrvx|{wuuvxyzyuuwytnkkkkkkllonijkllmooqqqrrtuuvvxxy{{{{{||||||||||||||zzy{yyyyyyyyyyxxxxyyxy{{|||||||zz{ztnf``hqx~}upjeaccdgksw~ ~zvspnnlhgiilopoiec`abdjprs{}yz~~zvqmnqw ytsrstpljkjjknrsrnnsx{zusquuvvspnmoopqrsrqsvy}~{xsniedcfjmqv{zwponmoswzxvy{zyyxxxrljmprtvwvvxvstxz}{wuvwxyzxuuxysmkkkkkkllmlijlllmopppqssuvvvwxxy{{{||||||||||||||{{yyyzyyyyyyzzzzyyyyyyy{{{||||{{|zz{zvpja`enw}|smgb`acehnv{ }xsqpnolhgiijmlkgda_bdhlqux}}{~zvronrw ||}xrqtvvpljkjiilpqpmlqvz{vsuvywvrpnmprrrrrsrtvy|~~{wrnieeehknrxzyvponmouxzwxz{{yyyyxrklnprtuvvwwutvy{}{wuwxxyywuwzyrmkjjjkkkljjijlmlmnopprsuvvvwwxxy{{{}}||{{||||||||zzyyyzyyxxxxzzzzyyyyzzz{{{||{{zz|zz{zwrldafnw|zslf_^acejpw} {vqqpnomihiiijihec`_cejorv|{{}|~ |ytpnry ~|}|wqqvwwpljkjhikopnjkpuz{wsxyzxvrpnooqrrrqsstvz|~{vqnifefilpsxzytqonoquy{wy{|{yyzzxrjlmprtvvvwussuz|}zwuwxxyxwux{yqmkjiikkklihjklmmooppqrtvvvvwxyyz||||||||||||||||{zzxxwwxyyyyyzzzzyyyyzz{|||{|{{{{{{{{{zsngdekuzysje`__ciou| }urpponmkhghhiiebdccbikmpsx}yx|wposy }xrqwzyqkjjkjkmnnlikqu{{xvx{{xwspopprrrqrttuwy|~~{ywqlhddfgjnrvwuqqqopruzzxwy{zyyyzysmmpqsuuvuvvtsvy{}zwwxyxzyxuvzzsmjiiijjjjhijklmnopppqrtvvvvwxyyz||||||||||||||||{zyyywwxyyyyyzzzzyyyyzzyzzz{|{{{{{{{{{zuoieejqx}}}|wqhc```fltx~ ~ytqppnnnmhhhhiieacccdjloqtw{wty{~ysrsz |vtw{xqkjjjhjmmkjilrvyyywx{|yvsoopqrrrprttuwy|~~|zvqkgdefhjnsvtspppppruz{xwy{zyyy{yrnmqstuvvvwvtsvz|}zwwyyyzzxvw{{qnkjiijjjjijjklmnopppqstvvvvwxyyz|||||}}||||||||{{zyxxyyxyyyyyzzzzyyyyzzyzzz{|{{{{{{{{{zwqlfdgnvz{{zwqhc``bhowz~ ~{vtqppnnppkhgghhfbcbbdjmqtvvzvqtuuz|xvw| }xvw{xrkjiihjkkjijmswxxywx{|xvrnnpqssrprttuwy|~~}}ztpkfeegikosurqppooosw||yyy{zyyz|xrmnrtuvvvwwvttx{}}zwwyyz{{yvy|zrmkhiijjikjkjklmnopppqstvuttuvwwz|||||}}||||||||{{zyxxxxxyyyyyzzzz{{yyzzz{{{{|{{{{{{{{{zxsngbdjqwyyyvojd_bfksz| ~|yttqppoopqmiffggfbcbabiorsvuvsoprptz~{x} }{ww{yqkjhiikjjjijmswxyywx{|ywrnnpqttrprttuwy|~}|{xrpjfeeghlprtrpppooosx}|zyz{zyy{|xqmnstuvvvxxvtvy{}~zwwyyz{{zwy|zrmliiijjhkjkjkmnnoppqrtuvuustuvx{{{}{|}~}}||||{{{zzyxxxxyyyyyyyyxyzzzzzzz{{{{{{{{{{{{{{{xspidbfmruvurniebchnu{~ ~{xvsqqppnnpqogeefgfcccadhmpswwqjjlmprv||| ~{xy{wqlkijkkljljkntxzxxwyz{xvtooqrssrqrttuxz}~}|zxsnkgggjllopppppppootz~}{zz{{zz{{xplotuuvwwwwvuvy|~zwxxy{||zv{|xqljjjjggikikijlmnoppqstutssqqsuxzzz||}~~~}}||{{{zzyyyzzyyyyxxwwxyzzzzzzz{{{{{{{{{{{{{{{zwqkd`djosrpmifccdkpx| }zwtsqqqppppqrogeefgfdccbafkosvvtmjkmpsw{} |{}wqmkklkkkkmkkouxzxxwyz{xvsppqrssrrrttuvy}~~|zxrnjhghkmnpoopppponou{~{zz{{zz{{xqlpuvuvwxxxuuvy}~zwxxy|||yw||xqlkjkigfhklnijjknopprstutspmlnqtwwwxz|}~~~~~||{{{zyy{{yyyyyyxxwwwxzzzzzzz{{{{{{{{{{{{{{{|ytle`cglnmliebbbflr{}}yvtssqppppqrsspiffghgeeea_agmsuwvpmkmprv| }|{~~wqmkklkkkkmnopux{yyxyzzwvrppqrttrrrttuux}~~{wqnigghkmponnooooonpu|~{zzzzzz||wolqvvuvwxyyttvy}}ywxyz||{wv||xplkkjhfgijnphijknopprstusrmjhjmpssuvx{||}}~~||{{{zyyzzyyyyyyyyxxvxzzzzzzz{{{{{{{{{{{{{{{|zvnha`cgiihfbbbbhnt}}~}zxussqnooppqqtupjgghiheffb^]emsuwvplknqtw} }|xy wqmkjkmmmmnpqrtx{zzxyzyvsrppqruurrrttuvy}~~{vrlifghkmonmmoooonmqv|~{zzyyzz||vomsutuvwxxwtsvy~}ywxy{||{uv}|wpljkihfgiloqijijloqqrstusqmhgfhkooprswwz||{{||{{zzzzyyxxyyyyyyxxwxxxzzzz{{{{{{{{zzy{|||||{wqha_bcdeedaabelpw~~~}{xxutqrpnnooprtuuqkhhiiihiic^\bluwxwqmmpux| ~{y|xrmkikmmlprvzzwxzzxxz{yvsqpppsutqqrttuvy~~~zuqmjghillkikkmnpomnqv|}|zzzzz{}{tontvstwwxwvqsw{~|xwyy{||ytu}}xqmjjjggghkpujjjjloqrrsturpkgfedgkkkmnqsvz{||||{{zzzzyyxxyyyyyyxxyyyyzzzz{{{{{{{{zzy{||||}}wqhb_baaaabbabfmsy~~~}}{{{{{wvuuuuurqqonnoommnoqtwwvrkggijjjlkg`W[fquvwrmnsx} yqmkjlmmlpt|yxyzxxz{yvtrqqqsutqqrttuwz}zuplihhjjjhghiklppmpsx{~}|{{zz{|~{tontvsuwxxwuqsw||yxyy{|{ytu|}yqmjjjhgghlsxjjkkloqrrsturpkfdcdeggghkmnqwyz{}}{{zzzzyyyyyyyyxxxxyy{{zzzz{{{{{{{{zzy{||||}}xrjdba__``aaabfltz~}{zy{~~}zxvussrrpooooonjkkihhijkkmorvxywrkgghikjonj`UXeouvwrnot{ ~ xpmkmoqposzyyyyxxz{zwurrrrstsqqrttuwz}zuolighjigecehjlmnoquxz}}|||{{|}}zsnnuwuvxxxwtqsw||yyyy{{{xrsz}yqmlkkigghltyjjljloqrrstutrkfdcfefffgijlosvyz~~{{zzzzyyyyyyyywwxxzzzzzzzz{{{{{{{{zzy{||||||yskdca__```aabfksy~~yuqrrz|zwtrponnmmkjiiiihfggdccdefhjosxyywrkffghjkoqmbVVdpvwvrnpt{ z} xpmkoqttsw}}{wzyxwxz|zxsrsssrtrqqrttux{~zuokhfhiifbadhjllmqswyz}}||||||}|zsmovxuvxwxwsqsw||yyzy{zzunrz}yqmlkligghkuyhjjkmprrrsuwvtokgdgfffddfghimqwy{|{yyyzzyxxxxxxxxxyyzzzzzz{{{{zzzzzz{{z|||||{zyrmifb_\_^^_abglu{~ztmihjry~|zwqoomlihhggfeddedcddec```aacglswyywrlgfhjhjqtpeYXdouwtqonrw~|z }vpmlnswyz{~|yvvwvwxx{|zwsrrrssrqppprruy|~}xsmifgiigd``cgjmnnqvxz{}}|}}||}~}zsoquvuwxxxwtqrv}{xzzz{{yrmqz}womllkjgfglv|hjjlnpqqrstwvuqmifhgggdddddehlruxyzyyyyyxxxxxxxxxxyyzzzzzz{{{{zzzzzz{{{|||||{zysnjhd`]^\\\_`ejtz{vld``dlvxwsojhgffcaaaa`_^^_^^_`aa^_____ciosuvuqlhfghginrsk][eouvurpnorx}|~ zvpmlnsz~}wqmloruww{{yurqqqrrrqppprrvy{~|wrmiffhhec^^bekmoortxz|}}|}}||}~}{uqquvuwxxywtqrv}{xyy{{|yrmqz|vomlmlkighmv}hjjlmoppqrtvvurnkiihggfdddddehmqswxxyyyyxxxxxxxxxxyyzzzzzz{{{{zzzzzz{{z{|||||{zvrnjfa_^][[]^chpx~~}xod]\]bhpspmieba`^^^^]]\[XXYXZ[\^^^]]___bgknoqrpkjhffefhnsob_hquwurpnnpsy~ }} zvpmlot||tohfinuwwzzzvrqqqrrqpooprswx{~{vqlhffhhec]]beknnquuxz~}|}}||}~~{xtrvwuwxxyvspqv}{xyy{{{xqlqz{uomlmmkjiglv}hjjllopppqsvvusomjjiffgdeedbefjmqtvwyyxxxxxxxxxxxxyyzzzzzz{{{{zzzzzz{{y{||||}{zxtplhda`^[\]_aemv~{vk`[[\aglnlgdb^^\ZY[[YYYXTTVTVVYZ[\\\]]abfgjlmnpkigfeccdiqogdlrwwurponnqx~~~~z} zvomlpt}|umfeinvvwz{zuqrrrssqonnprswx{~~zupkgffhhfd]]cflnostuwz}|}}||}~~|xsswyuwxxyvsopv}{xxx{{zwqlqz{uomlmkjihhlu|ikjlmoppqqstuvuqooolkjihgfdbdegimqsuwxwwwwwwwwwwvvyyzzz{{x{{{{zzzzzzzzzzz{||}|{yvromifdb^\]\^ciow|~yqf_YY[_cfigc_][YXYXWWVVUSSSRRUVWXYVXWY[_adggghjnkigec_`dhnqnkqvvvtqpooorx| {}~|~ztmljov~}slghiluuyz{xsqpprsqponnnqsux{}zuoighikkgc``bfknqqtvwy~}|~|{|~~ytsvwvwyyztolnu|{xyy{{yvokpyxronmnkifehlu{ikjlnoppqqsttutrrrrppnmlihgedddgjnqtuvvvvvvvvvvvvvxxyyz{{y{{{{zzzzzzzzzzz{}}}}{zwtqpnkgc_\]\\_bipvzyvlb]YY\^bcda_[YWVVXWXXWWWVWWUWYZ[\\WWVXZ\_behhfhjiheb`aadhlrtqswwwurpooopv{~|y||}|wploosy siehkmqswz{wtqpprsrommnnqsvx{~~|xsoigghjjhdbachmpqqtvwy~}|~|{|~{utvvvxyyztnlnu|{xyz{{xtkipxwronnnlkhgjnuzikklnoppqqrssttsttuttrqpmljgedcdfjoqstvvuuuuuuuuvvwwyyyz{z{{{{zzzzzzzzzz{|~~}}|{xwusplheb_^]\\^ciorrof^[ZZ]^```][ZXWXZ[\^^__]\[[[]_`ab`][ZYZZ\`cfhhjhjgb`bdffeiqvsvxwwurponnory}~||y{|yy|z{~}}~tkefjnmpsy|xurrrstsokknortwx{~~{vqligghjjieddhlpqrruvxz~}|~|{|~{wuvwwyyzxsmlnu|{xyz{{xrjipwvqonnnnonklptyiklnnoppqqqsstuuvvwwwutsppljgedceiloqrttuuuuuuuuvvwwxxxzzz{{{{zzzzzzzzzz{}||}}|{zwwurojgc`^][\\_djnnib\[\\]`_^_][[\Z[^^accddcbaa_`cdded`^][[Y\]abehjjieaaeggfacpvsxxxxvrponmprx|}zz ||{vx wmeejonpsy}yusrrstspkjmortwx{~~ztplighikkidfgkotsssuwy{~}|~|{|~|yvvxxyzzwrllnu|{xyz{{yqiipwuqonnnnppllpsyhjkmmnnoqqqrrtuuuuwwxxvvtqojgecabdgilqstttsuuuuuuvwwwwwyzzzzzz{{zzzzzz{{|||}~~}|zwxwurkifca_`^\\_ejie_^\]\\]]\\]^_``bcejjiiihfdcacffhihdb`]\\\Z\_aehjhd`bglmh]]muuyyxwvtpnmnosy|~|zz|x| {rgcfklpt{}ytrsrsvsplkmoqtvyyzytrplggiklljgikntvvutuwx{~}}~|{|~~{yxxxyzyxsmlmt|zyzzzzxogipttonmnnoqqnlosvjklnmnnoqqqsrtttuuvvwxxxwurmhecacddefnprssstttttuvxxxxwyzz{{zz{{zzzzzz{{|||}~|{xyxvsnlligdc^][]`baa^`^^\\\[\^`defgjkkpponnkifddehhjlmkhfca`_]__`begfb`ekoqk[Xiuxxxwusqqonnosy}{{}}|{ }y~ xledflqt{|xtrsrsuromkloqtvxxxvrpokhhjkmmljlpswwwtrtvx|~}}~|{}~~|zyxxyzyxsmmnt|zyzzzzxngiqtvqonopqsusnosuikkmmnnoqqrtsuuuttwwxyxxxxupkhdbbb``binqrrstttttuvxxxxwyzz||{{{{zzzzzz{{{{|}}|zxwsromnmkhe_\[]]]\^___^^^^]]cfklnooqrttsqpligfefggilommlhgdcaa__`abbcdjnooh[Vetwxxwutrqqommrz}yw||} }~{w{ ~tmiiosuz{wsrsrsspmlklprsuvvutqnmkkkklmmmmnsvzzxtrtvy~~}}~||~~}{yxxyzzxsmmnt|~zy{{zzwngirutqrpprsuxvrprthjikmnnpqqstsuttuuvvwxyyxywqojgdb`^\_ekpqqrsttttuvwwwwwyzz||||{{zzzzzz{{zz|}~|ywvssomnnljda][]][Z[^_`_^^]``hlpqsuuvwxwvtqmjigfhggimooopmmihec_^__^adinnllg]Wdrvwwxwtrsrnjip{}xu|~z| ~ uos| zvporsxz{vsrsrsqnlllnprruvurqpmmkjjklmmooqwx{}xusuwy~~}}~|}~~}|zxxyz{ysmlmt|~zy{{zzvpgjrwtrqpoqrtxxuqqtikkmlnopqrttuvttttwwwwxxxxwvrnifb`^\_dkopprrrrssstuuuvwxxy{||zz{zzzzzyyxxwx|}}{yvtssonnnmjb^\[]^\[Z\]^_^_`celruw{{|||zzyurnlihgghhjosttuspmligecec`cgmqnklmf`gpsuxwurnpohffoz{wt{~~{} vqu}|yuruz{wsqrssrommopvvtrprpnlkkhigjlnoqru{~}|wuuvxz~|}}}~~}yzyz{yyrmlmr{zy{{zzvqgjruvtrqppsuyzxsstjllnmoopqrttttttttwwwwxxxxyyvrkhda^\^binppqqrrsssstttvwwxy{{{zz{zzzz{{vrrquz|{xvtrqromlkjhb^]\^\\[[[\]`_`bfkry|~}zxtqnkihhhhjlrvwyyxuqoljfdeecgmrojiktvsspqtwvrkhklhhjt{|wt{~~}~ ~usyzvwz{wrrrtrpnoqw}xpoqpnmkjhigjlopqsw|~}zvssvy{|~}}~}yyz{|yyrmlmr|zy{{zzvpgjsvussrqpsu{|yuttklmooppqqrttttttttwwwwxxxxxxwsplie`]]bhnppqqqqrrrrsstuvwwxz{zyz{zzzzzxqkjlqvz}}zutqqpqqmhedca_]\][[\]\]\`aadgnu{|yuromjihiiilntx{|{{yuqmifdcfglrrkghmwz}ztprusomnllkmqx~}z{~ |vx|}{} yy{zvspoooonszxooqpnljihiikmpprux{|{xsorvy|}}}~}yyz{|{ysnkls}zy{{{{umejswtrsrqpsu}~zvuulmmoqopqqrttttttttwwwwxxxxxxxsrolga][`fnpprrqqrrrrrrsuvvwxyzzxz{zzzzyrkffhmrx{yvrpopopqmd`__`^\[[\[\^__^`aadgmsz~}ytqmmjihiijnovy|}}~|wqniebbgjpuqjggqy{}xpqqnorsqnnsw}~~ |wz{vx{z{zwsnkjlnqxuooqpnkjhiijlnpqsvy{{zwqorvy|~}}~}yyy||}ytojks}|{{{||uldirutrsrqpsu|}{wuumomopoqrrsuuuuuuuuwwwwxxxxxxwusroib[Y^fnpprrqqppqqssrtuvvxz{zyzzzzzyuneacejoswvtrpppnmlfa\Y[ZXYWYZY]acccbbdeglrx{~{vqmkkkjhhiimqx|}yunhdbglpssligjz~|umigmtrropu| ~{xsy{}ywtpkilnwzqnopqokjhghilnppsu{~}{uqprvy|}}}}~zy{}||xroklt~~{{{{{{ticjrusrrroorw}}|yvumonpqqssttuuuuuuvvwwwwxxxxxxwvtsqkc[Y^fnppqqpppprrrrrsuuuwxyyyzz{{zyrjc__aejmqsroopnmkid]XUWWVUTVWY^fhhhfdfeeglqtx}}xtpnmkkjihjjnsz~~|wsngdelqrrofbgp|lhjrxurosx ~pow}~yutqnlnt} wpomopqpkjhghjlnpqsvz}}ztppsvy|~~}}~~zz|}||xrolmt~~{{{{{{shdjrtsrrrpoqx||{yvunppqssttttuuuuuuvvwwwwxxxxxxwvustmd\Z^empppppprrrrrrqstuuwxyxxzz{{zwphea^_aejnomllmlljhaZUTVVVUTTW\cjnookigdbdfjmqwz}}zvrolkkkkjjkmqu{~~~|zwqmgfkqtsrrjfnw~}plqy|zusx~ ~ zonv~~ysrqqqt{ ~vpmnnopqpmljhiklnpqsvzzzxspptwz~~~~}{{}~||xrolnt~~{{{{{yqgdjqssrrrqqqx{{zxwvnpqsttuuuuuuuuuuvvwwwwxxxxxxwvutsnd\X]dmppppppqqpprrqrtttvvwxxzzzzzvngd`^^`bfjljikkjkif`YUTUWXWUVZ]entuuqmhdaaceimpuzzxtpomkkkkjlnnsw|~~|xvpkijouvuuuqnr{}rrx}yy~ |{ypov}~~yqqpqtzvommmmopqpmlkjjklnpqsv{{xvsoptw{~~}||~~}|xronot~~{{{{{xogdjqssrrrqqqx{{zwxvpprrttstttvvvvttuuuuwwxxxvwwutttqme]XZcmpoonppqqqqqqqqrrttuvwwxxyxxtmgc^__`befhghghhfe`\UQPSVWZXY_cjqwyzvmhe_^^afjknrsurpnmmkkkkmppsy|~}zusonlrw}}|yyzuw |ru| {yyrpv~~x|}{rppqz ynnmnnppqrqommjjlmnoqsvz{wtqprtx|~}|{{}}{vsonot}|yz{{yunebjsurrssrrqvzyyxvvqprrttttuuwwvvuuuvvvwwxxxvvvusttqme]WYbkpponppppppqqqqrrstuvwwxxyxwsmgc^``bceeeeedccba]YRONQTVXY^ciqv|ypie^[[]`fijmoononmmllklmpsvz}~|wsomopw|rv }|ytrx zwsy~ztqoq|~rmmmmopqsssqnnkjlmnoqsvzzvsqqrtx|~~|{{}}zvsonot}|yz{{yulcbjttrrrrrrrvyxyyvvprttuutuvvwwvvvvvvvwwwxxxvvvussspld\UXbiooopppooooppppppsttuwwxx{ywtnhc^aabdddcca```^][WONMPRTW\bipw{|sjd`\]\^bfghjklnmmmlllnpsvx}}}|zxuqnmpt| }uz }vtsz yqoqy}zuolq~wonmmlmnrtttrpmkjlmnprtwzyuqqqrtx|~~|}}}}zvsonot}~|{{{{yukbclssrrqqrrtxyxyyvvpsuuuuuuvvwwvvvvvvvvwwxxxvuuusrrpkc[UYaimoopppooooqqqqpprstuwwxx{zyvohca`aabccbb_]^^]\YUNJINQRW^enw}~ulda_`_^`cdeghinmmmllnoruwy||zywutommpv yw~ ~ ~zvuu{wnlqy}ysjjs|rnmmmlmnrtutsqmkjlmnpsuxzxtpqqrtx|~~|}}}}zvsonot}~{{{{{yulbensrrrqqrrvzyyyyvvpsvvuvuuwwxxwwwwvwvwxwwwxwvvvuttqmg^WX^ekmmnnnoonnooppppqrtuuwxxxxxuqmhfcbaabdb_^\^]ZYWUPLIINT\blu}vleccecb^^`cdfhllmnnnopsvxz{{xwtrqpnnpysw |xwuu{wnlry}yohm|}unmmnmnnoruutrpolklnoqrtw{yurppsux}~}~{}}||yvsomnu}}{z{}|ztjcfourqqopprvy{zyxxwrtvvvwxxxxxxxxxxwwwxywxxxxwwvvuuroia\Z]agjnonnoonnoopppppqsttwxxxxxvrnjhhgca`bbaa`^]YWUTPMKIMW_ir{ wmhfhjkid`]_cefhklnpqsuuuuvvvttqommnnt}}sz }yxww||njrxxpigt ztnmmnnoopsvxvspomllopqrvx{yvsqqsux}~~{||||yvtomnu}}{{||zysjehpusqqoppruy{zyxvvuwvvwxzzyyxxxxwwwwzzzxxxxxwwvvuuspkd^Z]_dilnoooonnoopppppqrrtvxxxxywspmkjifc`bcdb`_[XVUSQONMP[eoy~ wmhfjoqniea_adddiknsttuutssssqqppnmmpxy }|{x}~phmnjbbgyyspnmnnonptwyxtqnmnnopqrux{yvsqqsux}~{||||yvupmnu}}|{||zyrjfktusqqoppruxyzyxvwvvvvwwyzzzxxxxvvwwyzzyyyxxwwvvuuspje][]_chjlnnoonnooppppppqrsvxxxxzwupomkjhdcddba_]YVTTRQPQQU`kt }ulfgkpsrnje`_bdfgjnrusttsrpppoononmmqz {} skhb\Y]ky{ytonnnmnnptwyxurnnnnopqrvy|yvrqqsux}~{{{||yvvrmnu}~}}}}{xrjgmvusqqopprtwxzzxwxwwwwwwyyzzywwwwwyyzzzzyyyxxxwvvvsplg`]]^`bfilnoommnnpppppprstvwyxyxxvsqnnmkigghc`^\VTTSSSUVX\grz~wnffjrvtqmkc^^`cgimqssrrpononlonoonlq{~yre\X[er{~zyvroqqnmlmquxywtqnnnnopprx|}{wvtttvy~}{{{||ywtrmpv}~}|~}|xskhqxvqppppqqsxxyzyyywwxxxxyyzzywwwxyzzzzzzyyyxxxwvvvtrnic_^^_bbejmmnmmnnoopppprstvwxwxyywusqpomkiihc^]\VSTUUX[]`fpzyqgeiqvxuqoje`acghloponmjkklklnnopoos{ }xlfenu{{zuwuqruuqmlmrvyywtqnnnnopqrw{}{xvssuwy~}{||}|ywurmrx}~}|}}}xtlirxwqppppqqrvwyzzyywwxxyyyyzzywwwz{zzzzzzyyyxxxwvwwutqjfbaaaa`bgklnmmnnnnooooqrsuvwwxzzxvvtrpnmihdb_][XVXYZ]`cgnv~ysjfhov{ytrnjefghhhkmjiighjmlllnpqpqw{}~ }|qms|~ywtwussuvsmlmrwz{ytqnnnnpqrtw{|zxwttvxz}{}}}|zwurosy}~}|{}}ytljuyvpppppqqqsuyzzzzwwxxyyyyzzywwwyzzzzzzzyyyxxxwvwwvusliedc`_^_dgklmmnnnnnnnnpqstvwyyzzywusqpnlgeba]\\XYZ^_bcinuz~ztlfhnt{|wtqlklljkiijhghhglonmlnpstvy}} {ztnxvsuxyurstrmlmrwz{ytqnnnnprtuvzyxxwwwvx{~}{}}}|zwurptz|~}|z~}zsmmxzupppppqqpqtyzzzzxxxxyyyyzzyxxxyyxxxxzyyyyyyxxwwuwvtplhhd`___bdgjmnnnnnnnnnqqrtuwxz{{zxxtqnmifc_][Y\]^`adgkqwz||tnffkrx|zwplorojljiiijjnnponjipsuxy{ ~{{vq|vtv}|wrpsqmlnsx{{wtqnmmoqqstvxxwvvuwuw||{|}~|zvurpu{}~}||~}{ror{|tpppoopppqvyzzzzxxyyyy{{zzyzyyyyxxxxzyzzz{zyywwuxxwsqnjfba__`aeimnnnnnoooopprtuwxz{|zxtqljgcb`\[YZ\_begknvz~~~|ungehms{zxrptvtnkjhhjihlmoqpkhlrwy{~ } ~}{~yu{}usw{qorqmlnrvyzwtqnmmnpppprsqqppsuvx|{z|~~|yvvsqv|~~}|~~~{sou}|tpppooppprvzzz{{yyzz{{||{{{{yyyyzzzzzyzzz{zyywwuxxxwspmifc`_]_cglmnnoopppppprtuwwyzzurokhfb^\\YZZ]`bfjlqsz~~}woheejpxvxxy}~|uqpnkjihgginnjhhpw{} ~xw~~~}}|xy}}zvtz tprqmnnquxyvtqnmlmnnllopnlmmoquw{{z~~~|ywvtsx}~}}{tow}{soppqqrrqsvzzz||yyzz{{||||{{yyyyzzzzzy{{{|{zywwuxxxxvspmifb^\]`flmnnpppppppprtuwvxywqnjfda^ZZZXZ]_bdknquy}}xpidejouux}|xwurmkhdcdhjiiimu{} }tqw| ~~~~yvyzyzu|vrpqnnoqvxyutqnmlmlljjlmmkllnotvz}|~}ywwuuz~}~{usy|snppqqrrqsvzzz||zzyyzz{{{zzzyyyzzyx{{z{{zz{{yxwwwwxxwvuqmiea_^_chkmonoppoprstuvvttsplhea]ZXXZ\]^acfjosvz}xrjegjlrsy{wpjbcc__cjknptx}} unmqx ~}ywuz~zxsqqoopqvzytqommmjkkjmlmomnnortuy|{}~}zyxvv{~~~~|wu}zropppqsssty{{{{{zzyy{{{{{yzz{{yzzzy{{{{{zzzzyyyyxxxxxxvrpliea`_aeimnnoooopqrrtvvrplieb^\XVVX\^aceimruy{}ytmfejkms| |tkiga^bhknkmlqy}}wyytmls~}|}~{xvz~xtsrqqqrvzytpmkkkjkllnnmnmnnnqqrvxxz}~zzyww|~~{vw~{sqppprttwy||{{{{zz{{|||||z{{zzz{{zy{{{{{{{zzyyzyzzyyyywtspmic`^_cglmnoooopqrstttmjeb_[YWWVXZ]bfiknsyz|~{uohekmqtz yvphdfijihiiksz|uu~wty}|||{~~ |z|~}||yussrrrtxzyurlkjjjkllnmllllmmnmosuwx{{{zyy~~~yux|tqpppruuy}}zz{{zz{{||{{{yzz{{yz{zy{{{{{||{{{{{yyyzzyywvvrplf`]^aelmmoppoprtttrnieb_\VWWXX[]`ekmqtx|}}uqkgkswwy{znjhllefikjqy{rt }zx}}}||{~ }}|zyzywstrstwx{{upkjjjjkknnmmlkjkjjjlprtwx{{{|y~~xtxzrpppqsuv{~|yy{{yy{{{{{{zz{{{{yz|{z{zzzz||{{|{{{zzzzzzzzwuspke`__djlmoppoqssutpke_[YWVVXZ\^`flpswz}|xrnor{~{y}ohnlb^cikqyxqn| zxz}~~~ ~~}zyyyzzvtsstw|}ysolmmkkjknoqponlkjhijjkpsx}~z{{{z~xsw{qppqrstw||yz{{zz{{{{{{zzzzzz{{||||{{{{||{{{{||zzzz{{{{zxvsmgb_]aegijklorsssqmhaZXXXXX[_behlruy}}zsqx} ~pppbY\emrz{slw}xx{{|~ ||yxzz{yyywtttuy}}wrnmmlkkloqsuwutrponmmijlosx~ |z{|{|~wrxzqpqrttux|}y{{{||{{{{{{zzzzzz|||}||||||||{{{{||{{{{{{{{|zxupjc`^^adfghimpqqpmic][Z[]\\aeimpsxz}{vv} vssh^\`js|}umu yy{{z} |yyyx{}|zwxvvuuux|{tpoljjkknqtvwyxxxusrrrnjjlou{~ |{|~}~}tpv~ysrrstuux}~|{|||{{{{{{{{zz{{{{||}}||}}}}||{{||||||||{{{{|{xvrmfc_^^acdefkmoolid`\\\]_abginstx{|~zyy yuund_`fr{}vns~yz{{{}}xxyzz{}|zxxwwvvvxyxqpoljkmmnqtvyz|{ywwusspljjlqw} ~||~|snt|zutrtuuvy~{{|}}{{{{||||{{{{zz{{||||{}}}||||||||{{||||zz|{zytnjfc_^^_`aaehjifd_]Z[\_aeglptxy}~~z| {vvxohdft~xpt }{yzyyz}zyy{z{|~}{xyxwvsttutqoklknqnoquy|}~}|zxxxywrlhhlr{}{|~ zqnt}~yvustvvvy~~|{||}{{{{{{||{{{{zz{{||||{}}}||}}||||{{||||{{{{|{wrmieb`]]]^^adecb`^][]_bhmorvz}~~z}yx~~xomuxps{xzywvy{zyyzz{}~}{yyyxvtqprqonmopssppty|~|}}{~|toiglw~ }|} zplt}|xusttvvvz~~|{||~{{||{{||{{{{zz{{||||{}}}}}}}||||{{|||||||||{ysokgd`]]\\\^ab`__^^^adhnswz{}}}{{{tyxoo }y{yusvy~}zy}|zy{z{|~|zyxxxusonpponqrtttrsw{~~~|ulghq{ }~zokr||wusuuvvvz~~|{|}{{}}||||{{{{zz{{||||{}}}~~}}||||{{||||||}}|zztrniea^_\\\\_a___^`behmswz~|~ {}|zxll{ ~z|ztsrw|~|xuxzyyzy{}~{yywuurpnnpooprtttttuy}{skgnw zpkr{|wvtvvvvvz~~|{|~{{||||||{{{{{{||||{{{{}}~~}}||||||}}}}||||zzywvrmid`\\\[\\^`^^_aejqux{}}~ |}~~whgt ~yzzvtsvy|}zww ~wz{yz{||yzxwtqnllnopqruuuuvwz}zpiiq} zqmv}|xuuvvwvwy~~|}~~~{{||||||{{{{{{|||||||{}}~~}}||||||}}}}||||{{zxxtpmifb`]\\\^aaacflqvx{~} {|}~ ylhr |}|ywvvy|}zxv}}xxyyy{}{xtsrqnkijmprqruuuxxy{~ vlelx zrow~}xvvwwxvwy~}{|{|{{{||{{{{{{{{{{||||||}}}}}}}}||||||}}}}||||||{zyvuqoljfa`]]_adeilqwy{~ |{||}sls |yyyxy|~{zz |xwwxz}~zuqqonkjiloqrqsvwwyyz{ zofis {ttz~}xwwwwywwy~|yxvus{{||zzzz{{{{{{||||}}}}}}||}}||||||}}}}||||||}|ywwtqolidc__`cfgkpuz|}~~ |{zz~ tnt}zyzzz|} {wvxy}}xtpqnkkjlnqsrstwxxxyz{~ ~sgen{ |uv~}ywwxxyxwy~|wtrpm{{{{zzzzyyz|zz{{{{}}}}~~}|}}}}}}||}}{{||||||||zz{zwuqnkgcbdfilpu|} ~|{{} }tot {{zyy{{uuwx|{vsqpmlmnpstttvwy{{||||~ xjciw ~yz~zwvuxxvwz{tljji{{{{zzzzyyz{{{{{||}}}}~~}|}}}}}}||}}{{}}||||||{{||zxvtrnkjjlorwy~~}zz} }rmr }yxxy| zvuvwvuqqpnoqruvvvvxyz}}}}}}~ oefp~ }~ zxwvyxww{|ulghii{{{{zzzzyyz{}}||}}}}}}~~}|}}}}}}||}}zz||||||||{{||{ywywurppruwz|~~~ ~zz~}pko} ~wtsv{ }xxssqqrqprtuwwwzyzz{}}}}}}~ tgeky }zxwzzyxz|ulgikii{{{{zzzzyyz{||}}}}}}}}~~}|}}}}}}||}}{{{{||||||{{|||{{{zxuttvxz|~~~~ yx }pko} upoqy zqppstrsvvwxw{||z{|}}}}}}~ ykdhv |{yx{{zyxxnhflkll||{{zzyyyyzz||}}||~~~~~~}}}}||||||||zz{{zz||{|||}}||z|}}{yy{|~~ zz |olq~ uljlv}{} ~rpqrtttwxxzy}~}||}~~~~ }oeer }{{|||zxwqkkkkkln{{{{{{zzzzzz{{{{{{~~~~}}}}||||||{{yyxyzz{{{{}}~~||{}~~~|}~ |pnq| zohflu~}yusvwuuvwxxzzz{{}~}{|}~~shem{ }||}}|zwqjjopnoprzzzz{{zzzzzzzz{{{{~~~~}}}}||||||{{yyxxzz{{zz}}~~}}|~~~ |rpry sgbejqyzyyuu| |ywxyz{||{{{}~}|}~~~~~ylehs ~|}}~~}zumimqrpqqtzzyyzzyyz{zzzz{{||~~~~}}}}||||||zzyyzyzzzzyz}}}}~~}}~~~ |tru{ vgbeehq}{{|zw|{wz{}~}}||}}}}~~~~~ }ngen{ ~}~~ypijpstqrtvyyyyyyyyzzzzyyz{|}~~~~}}}}}}||}}{{{{zzzzzzyz||}}}~~~~~~~~ |vx{~ xhflignw~zyyxx{ |zy}|}|{{||}~}||~ogdju ~ulkmprttvwzyyyyyyyyzzzzzzz{|}~~~~}}}}}}||||{{{{zzzzzzyz||}}}~~~}}~~~~~tw~wjjqoklqz~yvwxxxxz||~|{}}|}||||}~}|}tgcgq xokmoruxy{|~xxxxyyyyzzzzzzz{|}}}~~~~}}}}}}||||{{{{zzzzzzyz||}}}~}}}|}}~~tqxxoptsnmqz}{xuuwwuttstw}}||}|}||||}~}|}~xicen{ |qkloruxz{~xxxxyyyyzzzz{{z{|}}}~~~~}}}}}}||{{{{zzzzzzzzyz||}}}~}}||}}~~}umr{ ¡ ztvvtonr{|{{wuututrposz~}|}||}}||}~}|~~~ {mddky ~wlkmptwy{}xxxxxxyyyyzz{{{|}}~~}~~~}}||||||{{zz{yzzzzz{{{|||}~}{{|{{{||~}yqms} ¡ y{zvrps{|xwsoqvuspmqx}|}}|}}|{}}}} ~ofcjx xnikoruy|}xxxxxxyyyyzz{{||}~~~}~~~}}||||||{{{{{yzzzz{||||||}~||{{{{{||~ ~{utx ~zxvvy|zwrlnuwupmqv}~{{|||||{}}}}~~ sieix }qjkmqtw{}xxxxxxyyyyzz{{||}~~~}~~~}}||||||{{||{yzzzz{||||||}~~~|||{z{{||~ ztt¡¢ zzz|~|{xsmknsvurpqt{zxyz|||{}}}}}}}~ xmhjx xmjmorvz}~xxxxxxyyyyzz{{||}~~~}~~~}}||||||{{zz{yzzzz{|}}|||}~~~||{zz{{||~ |vt} ||}|{{yohjnrtvutqsx |xwy{{|{}}||~}}|}~ |pllw tjknptx{~wwwwwwxxyyzzzz{|~~~~~~~}}}}}||}{zyzzzz{{zzzz{{||}~~~}{{{{{{|}}~~ |yx~ ~|qecnqsvxxvuv}ywyz{|z{{~|{||womv yokmoquywwwwwwxxyyzzzz{|}}~~~~~}}}}}||}{zyzzzzzzzzzzz{||}~~}{{{{{{|}}~~ |{} zqf[inquwxusuz|xz{}|y{{~~~}}zz{|~ {rou ~skmprtx}wwwwwwxxyyzzzzz{||~~~}}}}}||}{zyzzzzzzzzzzz{||}~~}{{{zz{|}}~~ }~~yqcQ[jotwvusqw ~}{z}{~~}}{{yxy{}upt znkortx{wwwwwwxxyyzzzzy{||~~~}}}}}||}{zyzzzzyyzzzzz{||}~~}{{{yy{|}}~~~~~xo^HGflsvvusqu~ }z{{{{yzxxvvwz}vpt tklqsvzxxwwxxyxyyzzzzz{||}}~}~~~~}}}}}}||{zzyywwwxyzzz{||~~}}{zzzzz{|}} }|ymX:6Zhpuvwvst|{zz{zwvttutuw| wpt~ |okortx} xxxxxxyxyyzzzz{{||}}~}~~~~}}}}}}||{{|zzxwwxyzzz{||~~}{zz{{z{|}} }|xjM/.Sinrtxxtrz |y{|xuutttuw}xqu wlnqtx| xxxxxxyxyyzzzz{{{{}}~}~~~~}}}}}}||{{|{zxxxxyzzz{{{~~}{zz{{z{|}} |udD+.Uknorwyupvz} }~|yxuutux~wrv slptuzxxyyxxyxyyzzzz{{zz}}~}~~~~}}}}}}||{{{y{yyyxyzzz{{{~~~}}{zz{{z{|}} ~s`C,1Ymonpwyvqt~ytx}} ~{{xttux~urv {qnruv{}xxzzyyxxxyyz{{}}||}}~~~~~~}}~~}}}}|zzz{yxxwwyyzz{{}~~}}|{{{z{{|}~~ ¡s`H8A_pqmosvurtz}ysvy{~~}{vtsvz usw tnpsvx~uxxyyxxxxyyzz{{||}}}}~~~~~~}}~~}}}}|zzzzyxxwwyyxy{{}~~}|}|{{{z{{|}~~ wjWIKbrupopttsuy}|xww{~~{vttvzusw ~qnsuw| |qxxxxxxxxyyzzzz||}}}}~~~~~~}}~~}}}}|zzzyxxxyyyyxy{{|}}~~}}|{{{zz{|}~~ ~vm[JNfvxtpotuutw}~zx}~}}ywuuv{utx yppuvx ymxxxxwwxxyzz{yy{{}}}}~~~~~~}}~~}}}}|zzzyxyyyyzzzz{{{|}~~~~}}|{{{zy{|}~~ ~}{|} zri[LPjx{wrqswusx}||~~}|xwvww{utxtorwwz wmxxxxwwyyzzz{{{{|||}}}}}}~~~~~~}}|||{{zyyyyyyyyzz{{|}}~~~}|}||{zzz{|}}}~ ~yvxx}|ulbWMYt~~zupquvsx~}}~~|ywvvy}vuz~pqtvw} sixxxxxxzzzzz|||{|||}}}}~~~~~~~~}}||||{zyyxxyyyyzz{{|}}~~~}|}|}{{{{{|}}}~ zxwx| }ztkbXUbz|xonvxvx}~|yxwvz~vu| wosuwz~qixxxxyyzzz{z|||{|||}}}}~~~~~~}}||||{zyyxxyyyyzz{{|}}~~}|}|}{{{{{||}}~{z }yxxy {wrme_^j{}{wpjsywx}~{ywvvz~vu| qpuvw}zojxxxxyyzz{{{|||{|||}}}}~~~~~~}}||||{zyyxxyyyyzz{{|}}~}|}|}{||{|}}}}~}wt~{xyz ~ywrmiedny|zxphoxwy}~~}{ywvvzwv} |nrvwx xmkxxxxyzzz{{{{||||||~~~~~~~~}}}|{|}}}}|{z{{{{zzz{{{{~~}}~~~~{{}}||{{||}}~~yqn| ~~ yuqppmmqw{vvuogjtxy|~~~{xxx|}vv~tnrvy} ~qjkxxxxyzzz{{{{||||||||~~~~~~~~~|{}~}}}||{||||zzz{{{{}}||}}~~||}}||||||}}~|sjiy ¡ zuttuspqtupqsphgovy}~~~yyyz~ }vv }porvz {mhkxxxxyzzz{{{{||||||{{~~~~~~~|}~~~~}||{|}}|zzz{{{{||||||~~~~~~~~}}||}}||}}~yoegy{ ¡ ¢¡¡ }zyzywqmnnlnsslgkqx~~{yzz{ }wxyoqtv| |uhgjxxxxyzzz{{{{||||||||~~~~~~}}}|}~~~~~||{|~~|zzz{{{{||}}||}}}}~~}}}}||}}||}}~|tjbhyxz}¡ ¡ ¢¢¡ }}}|ypiijjnssnhhnv ~{zzz{ |wz}upsuv}zzzyyuoggjyyxyyzzz{{{{||||}}}}~~~~~~}}~~~~~~||{{}}{{zz||}}}}}}}}|}}|}}~~}}}}||||||}}~|re_fx~}{y ¡¡¢¥¤ }xnbbgknrroifku}|{{}{xzwrstvx ~ytpqqqongefkyyxyy{{{||||||||}}}}}}~~~~}}~~~~}}||||||{{{{{{}}}}}}|}}|{}~~}}}}||||}}~~~~~~wma_gqust} }z¢¥¡¡¡ ¡¡¢¤¤ |wnfbdhkmpqlghs}|{}{y||rquvwz~xuroniikiigeecfkzzyz{|||}}}}||||}}}}}}~~~~~~}}||}}||||{{||||||{||{z|}}}}}}||||}}~~~~~{shb`fiklozqu}¨¥¤¥ ¡¡¡ ¡ ¡£¢ ~zuplgbchlornheo}~}}~ zx}wqsvuuwvspkjhhfghhhfeeceizzy{{|||{{}}||||}}}}~~~~~~}}}}}}||||{{{{{{{{{||{z{||}}}}||||~~~~~~~}yrgb_ceginz mms}¦§¡ª©£¡¡¡£¢ ~~{xssrnc^djospielx~|}}~ zx}vpuvuqrnkigffgfhihhiheddgzzyz{|}}||}}||||}}}}}}~~}}~~}}||||{{{{zz{{{{{|}}}}}}}}|}~~}~~~|{wnfaacehinzrjlx¢¥«®ª¥ £¡ ~{vtwzwj_^ckpojfkuyz{~~ ~yy~ ~tsvtqnjfdcdeghjlmnmmkggefzzyz{|}}||}}||||}}}}}}~}}~~}}}}||||{{{{{{||||}}}}}}}}|~~~}~~~}{siedceegfl{vlmw~£¦©¥£¢~|yww|~sf__glnjfly|||~ ~y{ yutsokhedceggkostvvqnjjiiizzyz{|}}}}}}||||}}}}}}~}}~~}}}}}}}}{{{{{{||||}}}}}}}}|~~~~}~~~|vmfegeeefgn|{rqv|¡«©£¡¡~|zvwzxlb^aimjfmy ~y{tssmjgddddgjmsw{|~~vpkmkmmzzyz{|}}~~}}||||}}}}}}~}}~~}}~~}}}}{{||{{}}||}}}}}}}}|~~~~}~~~zrgcfhfdeego}vtu} ««¤¤|zxvy}yqd^^gmkglw~y|~qspjfeehefintz}zrmmmpq{{z{{}}}~~}}}}}}|||~~~~~~~}~~~~~~~~~~~~}{||||{{||||}}}}}}}}}}~~~~~~~xpebehddedgp{zttz¦¬¨¢¦¤ |yxx{|via]clmkmt||z{ wnmigdfgjilqw zrmnptv{{{{{}}}~~}}}}}}|||~~~~~~~}~~~~~~~~~}{||||{{||{{||}}}}}}}}~~~~~~~}ukebegddddhq~~vuz§¨¢¤¦¤¡ ~||}~~{pg^_imnoqx| |{{rmkhgggjnpw~ vnlqtz}{{{{{}}}~~}}}}~~~~|~~~~~~~~~~~~~~~~~}{||||{{{{{{||}}}}}}}}~~~~~~|yqgeeegffccirvtz¢£¤¤¢ ~~~|woc^elqqorv| |{{yokijjlkoty~rmmsz{{{{{}}}~~}}}}~~|~~~~~~~~~~~~~~~~}{||||{{zzzz{{}}}}}}}}~~~~~~~~{wndegggghccirvuz ££ |}~{ui`dkqqont{ |{z umjgklmosy}qopw|||{||}}~}}}}~~~~}~}~~~}~~~~~~~}}}}|z||||||{{z{||||}}|}~~}~}}}}}ytidfgijjhbbjs~{x|¢¢¢ ~}|}~yoaejoojgny~zz~oifhlppt{zqnrx~ {{{||}}~~~}}}}~~~~~~~}~~}}|z{{{{{{{{z{{{|||||}~~~~}}}}}xofcfgjkkhbbjs ~y}¡ }|{|}xnlmnnibix~ ~{z~ xiegjotv| xqnsy zzz{|}}~~~}}}}~~~~~}}|z{{{{zzzz{||||||||}~~~~~~~~~}}}|{tidcfiklkgbbit z~ }{z{}~xsqpqmeiv }|{qfehksx|upps{ zzy{|}}~}}}}}}~~~~~~}}}}|z||zzzzyy{{{{zz{{|}~~~~~~~~~}}}{xodacfilnkgbbhu~ |~ |{z{||xtuusklu ||||mfgilv{|ropt| {{{{}~}~~~~~~~~~~~}~~~}|}}{zzzyyyyzz{{{{||}}~~~~}}}|||}}}}{vjbadglpomibcku~ ~|{y{{}}|{zzyspv~ |{|vhejmqyzqprx|||||}}~~~~~~~}}~~~~~~}~}}}}|zzz{{yy{{{{{{{{{|~~~~~~~~~|||}}}|wpgdaeimrpnibcjs} ~|zzz{|~{wyz{y||z|rghmovxpprz ||}}{|}~~~~~~~}}~~~~~~}~}}}}|zzz{{{{{{||{{{{z|~~~~}}}}~~~}}}}}zskedcejosqngbcir| ~{ ¡ ~|{z{{||x ywx |{||mfinqz vpqs{}}}}{|~~~~~~~~||}}}}~~~}}}}|zzz||{{{{||{{||{~~~~~}}||~~~~~}}|zqhddddkpsrnfbcir| {w}} ~ ¡¦~|{y{{|~w{xvy |{|uhdios~ ~spsu}}}}}|}~~~~~~}}||}}||~~~~}}{{{{yzzzz{{||||{{{|~~~~~~}}|}}|zulededhnrsqlfadkw~ xow|~{|¦«¥ ~||z{|}x{zz| }|ndeknt |rnqw~~~~}}~~~~~~}}||}}}}~~~~}|{{{{yzzzz{{||||{{{|}}}}~}}{}}}}{yqhcdffipttqlgdfmy yjqy}{|¤ª¥ ~}}|||}{~|} }}ykfgknyxpory~~~~~~~~~~~~}}||||||}}~~~}|{{{{yzzzz{{||||{{{|}}}}}}}}|}~~}}}{vldceghlqttpkhgho{ {gjv}}|{|¥ª¬ª£ ~}}}}}~ { ~~ sjhikq~uoquz~~~~~~~~~~}}}}||}}}}~~}}|{{{yzzzz{{||||{{{|||||||}}~~}}~~~~}zshcdffjnqutolhgjr}|feszxyz|~ |{| ©¬«¦¢ ~~~}}}~ }z~ ~mijknv}upqv{ ~~~~~~~~||}||||||~~~}|}|{{{z{{{{zz{{{{{{||zz||}}~~~~~~}|vmdaehjmpuwsnjghks~jamwvxwx| }~©«¦£~~}}~ zy|~ zkjjlqz|spsx ~~~~~~~~||}||||||~~~}}|||{z{{{{zz{{{{{{||{{||}}~~~}zqhbafimoswxrlighmvn`isuvwy{~ ¢¦£¡~~~ vuy} sjkkmtzrqty~~~~~~~~~~}|||||~~}}|||{z{{{{zz{{{{{{||}}||}}}~~~}xne`cgjmruxwqlhgjrz p`dqvwwx| }tsw||mkklowxqruz~~~~~~~~~~}|||||~~}|}}{z{{{{zz{{{{{{||||||}}}~~}~}vkb`dhkntwywqlhimu| sb`nuvwx{~ {srw}vjjlmq{vrsv|~~~}|||{}}||}}|||}~~~~}|}}{{z||{zzzz{{{|}}}}}}}}|}~~}}~}{sibagjmrxy|wqlihnt{ ve`ltvwy} ~~ ztpu{ rjnmnv }ursv|~~~}}}|{}}||}}|||}~~~~~~~~}|||{{z||{zz{{{{{|}}}}}}}}|}~~~~zxpf`chkmtyzztolhhnu{ }wh^mwz|} ~~{sklt| {ojnnpxzsrtx~~~~}}}}}||||}}|||}~~~~}}~~}|||{{z|{zzz{{{{{|||}}}}}}|}~{tlc`eijpvyzytmihkow{ }xj_m}~ |tkdjv wlknor{xqrvy~~~}}}}}{{||}}|||}~~~~}}~~}|{{{{z|{yzz{{{{{|{{}}}}}}|}~zricafjltyyzxslhglrt{ {mal~ }ukacr tklnpu}wqsv{~~~~~~||{{{{||||||||}~~~~||~~|}||||{|zy{{{{{{||{{|||}}}}}~~~~zqhbdilov{{|ytmhgmux{ }p`j ytla\j| pjkosyvqsw}~~~~}}}|{{{{||||||||}~~~~~~~~~~~~~||}}|}}}}}||{{{{||{|||{{{{{|}}}}~~~~}wmdbfjmqx{||yuojjnrru saf} |vpjbVZr {okkot{vrsw~~~~~||}|{{{{{{|||||||}~~~~~~~~~~~~~~}}|}}}}}||{{{{||||||{{{{{|}}}}~~~}wlbbgjnsx{}{yuqllnnkn~ vde{ |vpkbRQi| vnmmpt}ursx~~~~~||}|{{{{zz|||||||}}}}}}}~~~~~~~~|}||||||||{{{|||||{{|||}}}}}~~~~vk`chiotz}}zyurnnojgk yhfz ztneQL`w~ snmnrw~~tqsx ~~|~~~~{{{{{{{|||||||}}}~~~~~~~~~~}~||}}||}}||||||{{{{||||||}}~~~|vkdfijou{}}|zvsppmdai zjew }umbMJYp~ plmosz|srtz~~|}}}}{{{{{{{|||||||}}}~~~~~~~~~~||}}}}}}||||||{{{{||||}}}}~~~{vlfhjlov|~{{zvsqpk`]jzkdu ~uk`[]ly xnlopu{ zssu{~~~~}|}|{|{{{{{{|||||||}}}~~~~~~}}||}}}}}}||||||{{{{|||||}}}~~}{vmgiklrw}~{{ywtrph^^l{lbr {yx|ypjkqy~ smmpqv} xssv|~~}}~~~|{{{}{{{{{{|||||||}}}~~~~~~||||~~}}}}||||||{{{{||||}}}}}}~~}zvnijlmty}~||{zvtpe\_nxoco ~ |tprx~zxuux~ pmppsyxssv| ~~~~}|||{{{{{{{{||{{{{|}}}}}}}}}~~}}}}}}}}}}||||||{{||{{}}|}||}}~~|ztojmlov{~~~}|zwsme]_n }upfi vleis{~|||}z| |nmopszuqsv|~~~~}||{z{{{{{{{||{{{{|}}}}}}}}}~~~~~~}}}}}}}}}}||||||{{||{{||{}||}}~~|ytplnnrx|~~}zxsi_Zap yqmcd }rf_enx~~zz|~{|~ wnmnpt{sqtw~~~~~}|{{z{{{{{{{||{{{{|}}}}}}}}}~~~~}}}}}}}}}}}}||||||{{||{{{{z|||}}~~{ytpmnory|~}zwqgZXet zql`d| |qc^^gq{}yx|~{|~ smlnpv||ssuy~~~~}|{{zz{{{{{{||{{{{|}}}}}}}}}}}}}||}}}}}}}}}}||||||{{||{{{{z{||||}}zxtpnnnqy|~}}zuneXXht |toaex |rc^Zbkw~}~{xw|~}}} plmnpwyrsu{}}}}||||||{{{{zz{{{{{{zz{{}}||}}|}}}}~~~~~~~~}}~~}}}}}}}}}}||{{zz{{zzzyyz||z||||yqmmlnry~~ztl_Y\js~ ~yskap ~ |qe^Y]fov|~zyyy{~}|~ {nmlmqxwsru}}}}}||||}}||{{zz{{{{{{{{|||||||||}}}}~~~~~~~~~~~~}}}}}}}}}}||{{{{{{{{zyyz||zz{{zwokkjlry~~xpeZY^ir{ {sjr }z} |qd]Y[clqy}{zywwy~~|z} ulmlnqxusru~ }}}}{{||}}||{{zz{{{{{{|||||||||||}}}}~~~~~~~~~~~~~~}}}}}}}}}}}}||||{{||zyyz{{yyz{yunkkjjox|~|ulbZ[`fny }y|~zwz~ }rd][\aipwzzzyvvx||{z} rmnlpt{tstw }}}}{{||}}||{{zz{{{{{{||}}}}||{{|}}}}~}}~~~~~~~~~~~}}}}}}}}}}~~}}||{{||zyyzzzyyyzxtmjhhjmw|~~wohb\_aelv |wwz~{{| ~sf_^]_houxxzyvwwz|}{} {nmnmqu|~vtuy}{{{{{{y{{{{{{zzzzzz||{{}}}}}}zz}}}}}}}}~~~~~~~~~~~~}}}}}}}}}~~~~~}}||||}}{zy{zywvxywslggghmu{~zqjfcadcbhs yvvz}yyy} |uha`_^fnruy|zyxx{{|{~ ynmnnty~uuvy }}{{{{{yz{{{{{{zzzzzz{{{{||||{{zz}}}}}}}}~~~~~~~~~~}}}}}}}}}}}~~~~~}}~~}}||{zy{zywvwxvqkgeghktz}umfddfdbbfq yuw|{xxy| }ypic``^cehns|||xwy{{z|~ vmooov||tuvzu}{{{{{y{{{{{{{zzzzzzzz{{{{||{{||}}}}}}}}~~~~~~~~~~||}}}}}}}}}~~~~~}}||}}||z{{zzyvuvvuqjfcfgipxyqiddhjfb`cl} zvw}zwx{} }|vpjfeb`__`aiq{{uposvyz~ smopqw}{tuuz yo}{{{{{zy{{{{{{zzzzzzzz{{{{{{||}}}}}}}}}}~~~~~~~~~~||}}}}}}}}}~~~~~}}||}}{{z{{yzyvuvvtpiecdfgku}}vjecgmnib^`i} {vw~zwy} |xmdbceba````fq{}ypiehmsx| pnpprx~zsutzum{{yyzzzzzzyyzzyyyyzzzz{{{{{{{{{{}}}}}}}}}}}}}}}}}}||}}}}}}||~}}}}}}||{zzzyyyyvvttrngbbcgehrzzricflqpof_^f| |y{~|z{~ {mWKQ]eca`a`_enwysngb^^grw }pnpptx~ yvvx~ {pk{{yyzzzzyyzzzyyyyyzzzz{{{{{{{{{{}}}}}}}}}}}}}}}}}}||||||}}||~}}}}}}||{zzzyyxxvvtsqlfabdighowxqiglrwvrkc^e} |{}~|{{ xfMBL[eda`a``bhnqolf`\VY_jrw~zqnpquzvvvywnl{{{{zzyyyyyyzyyyyyyyyyzz{{{{{{{{}}}}}}}}}}}}}}}}}}||||||}}~~~~~}}~~}}||{zzzyyxxwwtspkebcfiihktwqjkpw{zupgag ~|~}|}~ueUR[bfdbaaddbdgllhe^ZUPU_aiuxqrqtx||vwwz}smk{{{{zzxxzzyyyyyxyyxxxxyy{{{{{{{{}}}}}}}}}}}}}}}}}}||{{{{}}~~~~~}}}}||{zzzyywwxxtsojebcgijiiquskkrz~}ztkfm ~}|~ugdikjhebbbffdadjkgb\XTQZXX^m} tqtsvz|uxxzzpmj{{zzyyyyyyzzxxwwxxyyxxzz{|||||{{}}}}}}||}}}}}}}}}}|||||||~}}}}}}~~~~~|}|{yxxyyxxxwurohcacilkgelsroqu{~yplp ~~}}vopzytlhccejidacegfaYTPOVTPTfztsttx|~|wxx{vmmmzzyyyyyyyyyyxxwwxxyyyyzz{|||||||}}}}}}||~~~~}}~~}}|||||||~}}}}~~~~~~~|}|{yxxyyyyxwvsoic`ejnnjgkppopt|}vqt |~|zw{}~zw{yphddhlkeabbdb]TPKKMRNM]q} ussty}~{wxw||qmopzzyyxxyyyyyyxwyyyyyyyyzzz{||||}}}}}}}}~~~~~~}}~~}}|||||||~}}~~~~~~~~~|}|{yzyyyzzxwvtojebgmqqnikqqomqyysu {ttz~}uqqtx|||}}~~xogcdhonicb``\UMIGHBLKLXgt |tssty}yvyx} ypmpryyxxwwyyyyxxwwyyzzyyzzzzy{||||}}}}}}}}~~}}}}}}}}}}|||||||~}}~~~~~~|}|{yzz{{yyxwurokgeinssplkpqoknv}{uw~ }tonrw}}zrmkotxyyyy}~umgccgpokea^[VNGE@>EJHGS^l{ {srrtywvyy~~uooqtyyyywwwwxxwyxxyyzzzzxyzzz{||||}}}}}}~~}}}}||||{{}}||||||}}~~}|}|{{{z{{||zxxwvsnjgfjpsuqnknoniis}|wx } |rlhhilsvvqmjjortsrty}}sjcbaeoplea\YTLA<78?FC@IVgwwqrsuz~~wvyywpnqtuxxxxwwwwxxwyxxyyzzyyxyzz{{{{||||}}}}~~}}}}||||{{}}||||||}~~~}|}}|{{{{||||zxxwurnjffipturnjkkkgep|{ww } sy } rihgffgkstqnkhjlonnpv|}tjcbadlpngb\ZULB635=GECIUcnz ursvw|~~xwyztmnqvwxxwwwwwwxxwyxxyyyyyyxyzzzzzz||||}}}}~~}}}}||||{{}}||||||}}~~}|||{{{{{||||zxxwurnjeehotusojjjjdcmzzvv}| tfp~ |w{ wlhhfefgiottqlhdehihjs|ujfcabjqpib\ZUNG>9;DLLNTQSfr~~ustvx}~~}wxyz|qmnsx{wwwwwwwwxxwyxxyyxxxxxyzzzzzz||}}}}}}~~}}}}||||{{}}||||||||~~}|{|z{{{{||||zxxwvsnjedhotuuplkjjddlyzvu}} l^f{ |uu|slkkiggfhnuvsmhbbcdbgq|ukhdaajssle^YUQLKGILPRX]L>\i{ }sssux~~~}vxy|yqmoty~wwvvwwwwwwwwwwyyxxyyyzzzzyzz{{||}}}}}}}}}}}{{{{{||}}{{}}}}}}~|{zzyy||||{{{{zyxvsokdcfnswwtokkjfcjwytu} xgY\u srw~zqnookiiiimtvumhcb`_`er|wlhebblyyqjc^[WTWYWVXSX_H.Ebv zppruz|}~ }wxy|~uonmt|vvvvwwwwwwwwwwxxxxyyzzzzzyzy{{||}}}}}}}}||}|||||{{||||}}}}~~zxxxxy|{{|{{{{{zyvsolebclquwvplljgdhw~ytv ~~ ~rdVSl| ursv~ xsssromkkjmswwsnhdaa`gt~{pjfcdpzysleb^[X[^]\WX^^H+4Ri| yqqruy{}~zvyz|{soorx}uuvvwwwwwwwwwwwwxxyyzzzzzxyy{{||}}}}}}|||||}}}||{{{{}}}}}}~zwvuux{{{|{{{{{{zvsolfbbhnrwvsokjgcfu~ywsv}~}~{vkaTO^z xqssy wustuutomjlswwvrmheedjv}rlfbfszxsked_]\]bdc\]_Z?+0CXl~ xqqruz|}~yvyz~yqpqt{uuvvwwwwwwwwwwwwxxyyzzzzzxyy{{||}}}}}}{{{{{}}}{{zz{{}}}}}~~~}yvuttxz|{|{{{{{zyvsokfdbhnrvwuqkjgccpz{vsqu} ~zvxwrg_TMUs xpprw| ~xttsw{ysnjkrvvutpmiiinu~}skeaeqzwskfea^^^dihca^T918EPby wpoqt{}}~ zwyy}wpqrw}ttvvwwwwvvxxwwvvwwyyzzzzzyyyz{||||{|}{||||}}}}||{{||}}}}}~~~~~}zwsqsvz|}||||{z{yxuqlgbcinsuvtrnkidajstpprv|yqlprmf`TKNf| vnnruy||xwtt|~upmnquvutspommpu~{rkd_cpwvsnhcbaabgjljd\N97DLUksooouz~}zwxy~ {upqrxuuvvvvwwwwxxwwwwxxyyzzzzzzzzz{{{||{|}{}|}}}|||}}||||}}}}}~~|zwsqruy|}}}}}{zzyyvsnfbckptvwusolje_enmlmsv} zlehkje`ULGWw xonruyz{~~zyxvyxqnprvwwwurqonrw~{rme`dqwxupieeccdgijf`XI::HZo~ponov{~}zxxyxsqrs{ vvvvvvwwxxxxwwxxyyyyzzzzzzzz{|{{||}~}|}|}}|{||}}||||}}}}}~~|zwsqqsw{|~~~~|{zyxvrmebempvxxvuplkf_bjiimrw |mfgjid^VNFNo} zspruyzyz{zwyxz zqoruxxwwwurppty}smfagsz{uqjggfededdb^UE9=Tq~qpoov|}yyyz~urstv~vvvvuuwwxxxxxxyyyyyyzzzzzz{{{}||||~~}{{{}}|{{{||||||}}}}}~~|{wsqpsv{|~~|{zyvtpkecgoqwxxxwpmjeacghhnsy }rigmje[VPHHbz |vqruyzzzzyxyx|zrpswzxwwvvtrru{}tldbiu|ztqjjjfcb``_`]RD=B_~{|~~uqoov|~~zyz{ |rqruyuuvvuuuvwwxxxxxxyyyyyyyz{{||{|||}}~~|||{|||{{{{{{{|||||||}~}zvrqquy|~}|xwuuroieeiptvxxxvspniefikjosztkhiic[SNHFSw yuruxzyz{yyzx| zspsy|zxwwwusrv||tkecmx|ytqmllgbbba^`_SIEMg yvxyy}{tpopw}~ ~{zy{wrqtvzuuvvuuuvwwxxxxxxxxyyyyyz{{{{{|||~}}}|||{||||{{{{{{{{|||||~~{wrqptx|}~{wsrppnnhegkpvwwyywurolllnqnnqz ypliec[ROHBHl} }wsuxyyz{yyzy{ zsqt{}{zwwywuux} {skeenwzwrpnmmhbbcc`a`VOLSm|wtvurtysqoqx~| }zzy{|sqrtv|uuvvuuuvwwxxxxxxwwyyyyyz{{{{{|||}|||||}|{|{{{{{{{{{{||||~~{wrqprx{||xuqmkiijifehmrvwvxxyxuqoqrtvpmqyunhdb]VPJBD\z |vtwyy{{{{{z| ysqu}}|{yyyxuvz}yqkefnwytollmmidbcfbb`YROWpzyvuromxxqppry~||zzy{ytqsvzuuvvuuuvwwxxxxxxwwyyyyyzzzzz{|||}|||||}|{{{z{{{{{{||||||~~|wrqpqw{{zrpjfecdggddinuvywxwywusqtwwxsprwwniea^XQMDAOt ~wtvyyz{||{y}xsqv~~|zzxxwuy~ypkefoxzrmkjlljfbcecca[SRYq¡z{xsqmioxpoorz~}|zzy|}vtttx| uuuuuuuvwwwxxxwwxxxxxxyyzzyyz{{z{{{{{{|z{{z{||{{{{||{}}}}~~~~|xsnnov{zxqjea`_]_acgmruwxxxxzxvspty{{sosx}slhd`ZSLF@Fg| {uuxyzz}|{|~wqpv~{ywvy~vokegs|zsnihjjlgcdedab^WV]s yuqlfevxoopry}}}{yzx{~{trsuxuuuuuuuvwwwxxxwwwwxxxxyyzzyyyz{z{{{{{{|z{{z|||{{{{||{}~~}~~|ztnnouzywpha__^Z\`ciouuxxxxy{ywtqtz}|soswytpjd]UMHA@Uv ~xvxy{{~|{}}}|vpqw}zxwy~~tojdgs{ytmhgijlgdfhebc`[[at{toie`jwopqsy}}}zyzxz|wrrsvz~ttuuuuuvwwwxxxwwwwxxxxyyzzyyyzzy{{{{{{|z{{z|||{{{{||{}~~}~~}{upnosxxskeabca^]_dkrvwxxxxy{ywtqsy|{rnrvzwpg^XPG@=Ik{ {xy{{{~|{}}zyspqx}yxy~~toicgs{yulhgilkhghhfdb_^^csyqiec^cx wqrrt{}}{zzxxyvqsux~ ssuuuuuvwwwxxxwwvvxxxxyyzzyyxyzy{{{{{{|z{{z|||{{{{||{}~~}~~|wqnoqvvqhdcegea`agntwyxxxxy{yxuqtx|{qnrv} zria[SG@<E^y }zz{||~|{}}yvqoqyzxz~}sniciu~{vlhhinkhhjgeec^__dswnea`^_nvqstv|}} {|{ywvtstvz~ssuuuuuvxxxyyywwwwxxyyyyzzzzyz{{{{{{{{{{{{{{{{{{||||||~~}~}yspppuuofdglnkecemruxyxxyyzzyxtrtx|zqnqu{ |siaZVLD>CTs} }{{|||}|~|xuqoqz}{}|smgciu}|wokhjlklmmjfec`_bdq wnd^]\[e|ursst|~~ }z}|xuussux{ vvvvuuuvwwxxxxwwxxxxyyyyzzzzzz{|{{{{{{{{{{{{{{{{}}||||}}~~zspnoqsogekqurkggntwyyxxyyyyyxuttw{ztqqtx ~vka[XQHAAKdz {||||~|~}zuqqr{~ {rmedkw}|wqlijklnoolhfebacemx zpf^]\[_stqqpt|~~ }{~}xtsrsux~ vvvvuuuvwwxxwwwwxxxxyyyyzzzz{{{|{{{{{{{{{{{{{{{{}}||||}}|tpmlornhhmtyxqjhntwyyxxyyyy{yvttwzyspqrw xne]XUMGCFVt |||||~}|~|uqps~ zrlcdlx}|xsokjknoooljjgdcdflr~ }tia__]\jspont|~~ ~|~~xsqqrtx ttuuuuuvvvwwwwwwxxxxyyyyzzzz||||{{{{{{{{{{{{{{{{||||||||}~~xrnlnpngimv{{smlpuxyyxxyyzzzyvttvzyspqqw |sk_YVQLFEPg} |||||~||~}zuqps~ yplbcmy}|xtpmmkmnnnnlljeddfloy {ska^`^]f}spont|~~}|~~wrorrtzstuutuuvuuvwwwxxyyyyxxyyyyzz{{{{zz{{{{||{{{{{{{{||||||}}|}smjkmjggqy{yvsptyzyxyyyyzzzzxwuuxxuqqrw yqdYWXQLGL^x }||||}|}{tqrs} wplfgq{~|xwtonllmommoomighkmmt~~xsi__aa`bu|roqqu}} }{~}xqossu| ~stuuuvuuuuvwxxxxyyyyxxyyyyyz{{{{zz{{{{{z{{{{{{{{||||}}}}|~wojkkgefqy}zwvxzzyxyyyyzzzyyxuuxxtppqv~ }sfXWXSPOQ[q ~}}}}~}|{trrt| uolghs|}zxxvqpnlmqonppokijmoou}}xoe_`cb`^n}rooov~} }}|wqortu|~stuuvvvvvvwxyyxxyyyyxxyyyyyyzzzzzz{{zz{{{{{{{{{{||||~~}}~~~yokkhdbeow~}{yyz{{zyyyyyzzzyzyvvxytpnqv} |o\UUVUSRZhz ~~}|{{urqt| }rmifis}|yxxvsponnrrqqppomorsswy}~zzwoe__bda_l|rooow~} }}~zuqoqux ~stuuvvwwwwwyyyxxyyyyxxyyyyyyzzzzzz{{yyz{{{{{{{{{||||}}~~~}~~~|pjjeb_dov{{{{{{||{yyy{{zzz{zzwwwztpnqx~ yfWRVXVS[cs ~~{zz{urpu~ {pmifjt}}zxxvuqpnntusqprqpruvvvvy}{wyvod__`deal{ropqx}} }}}ytqoquz~rsttuuwwwwvxyywwxxxxwwxxxyyyzzzzzzzzyzzz||{{{{{{{{{}~~~~~~~~~~~{vokfb^`jsz}~}}|{{{yyy{{zz{{{ywvvwuqnr{~ vbUVZZZY_l| ~~}~|zz|vqpv ynmjfkv|}{ywwwrpoostrpqrqpquxxtqtwvttpkc^^begejyzrpppx~|~~~vsoortz pqttuuvwwwvxxxwwxxxxwwxxxyyyzzzzzzzz{zzz{{{{{{{{||{}~~~~~~~~}zsmhb]^fqz~~~}|{{{yyy{{zz{{{yxvvwvurv} q`WW]_\^hw ~~}|zz}vqpx wlligny~}zyxxxutppsuroqsqpptvvrnqrrqpnke^]`dhhiu xrqppx~}~~}vrnort| oqtttuvwwwvxwwwwxxxxwwxxwyyyzzzzzzzz{zzz{{{{{{{{}}{}~~~~~~~~}xqje`^dpz~~~}|zzz{yy{{zz{{{yywwxxxx| {o`X[^_`fs} }{z~wqpv }rlljhpy|yxyyywvspsvtnnnmlkoqqolmmnnnolh`\]cghhpxqpppx~~~{tpnosvprtttuvwwwvxwwwwxxxxwwxxwxxyzzzzzzzzzzzzzz{{{{{{}}{}~~~~~~~~~~zrkfb_eoz~~~}|zzy{{{{{zz{{{yyxwyzz{ yj][]_afp{ }{{~vporz yojkhiow}|ywyyzzxuruwulihgefjlllkjjklnnmja]]cghglywqoppx~~ ~~zsonoswoqrssvwvwwxyxxxxxxxxxxyyxyyzzzzzyyyyyzzyzzyy{{||}}|||}~~~~~~}wniebgoz}~}}{yzz{{{{|{{{{{zzxxw{{} th\X]bjs{ ~{yz~{smlnt} ~tmjljilptxyzz||||{wxxumfa_^_afihffgjmpmkgc__ahijmt{~ ~qppoqx~}~~~~yroopt{opqrsuvvxxyyyyyyxxxxxxxxxxyyzzzzyyyyyzzyzzyy{{||}}|||}~~~~~~~ytojhks{~~}|{z{{{{{{||{{{{{zxxw|} ~tf\Z]kx|} ~{yz~wnhfgjsz yokjljgiklquwz}}~zywqja[XXX[aeffghilollhd`_agiilmqw|zonnnpy}~~~|wrppru|noprruwvxxyyyyyyxxxxxxxxxxyyzzzzyy{{yzzyyyyy{{||}}|||}~~~~~~~~~~zytonpw}~}|{z{{{{{{{{{{{{{zxxy| }tg^^jy}} ~{yz~}rjdaceju~ vljkjiehkkkmqtxz~}ywsmf]VUUTV\dgiklnopmmiea`cfhiiginsxxtmllmoy~~}vqpqsv}nopqrtvvwwyyyyyyxxxxxxwwwwxxzzzz{{{{z{{zyyyy{{|||||||}~~}}}}~~~~~~||wusuz~}~}{|{{{{{{{{{{{{{{zxwz||| |rhemx}~~{yz~znfbabbfp| ukikihfhmlijjlruz~{wtpjbZTQQTVZdknpqssromieb`cfghhfehmrrnkkklnx~~|tpqrsv} mmopqttuvwwyyyyyyywwxxxxwwxxyyzzyyyy{{{{zz{{||{{||}}}}}}}}||}}~~~~~~~|{z{|}|}|||{z||}}|{{|||||{{xz|}{z zvwz|} ~zyy~uicbbbablw}sighiieillgfdcgkrxxsnjd^WROOSX]isxxxxwwrojdcacddefebbdhjiiiiknw~~{sppsuxllopqstuvwwyyyyyyywwxxxxxxxxwwxxyyzz{{{{{{{{||{{||}}}}}}}}||}}}}~~~~~~~~~~~|~|||{z{{||||||||||{{{{| }yy~ }~ }yz~|qgedecbahr |sighhhfjiihecaadhmnljf`[XSQRU\hu|~}zxsqnihfeefhigedddeffffhkt{|~~ztrrsuzllopprstvwwyyyyyyywwxxyyyyxxwwxxyy{{{{{{{{{{||||||}}}}}}}}}}}}}}~~~~~~~~~}||{zzz{{|}}|||||{z{||} |yz~ } |{~{piihhdb`dn} {skiihhfhiiifddbcbddedb`\[WWX\ev|{vrponjhjkmnkigfcaccccdgqx{y~~||xsuusu{~oooporstvwwyyyyyyywwxxzzyyxxxxyy{{{{{{{{{{{{||}}||}}}}}}}}~~}}}}~~~~}}}}~~}~||{zzz{{||||||||{{{}}} }{{}~ ~} |~{qmlljgc`akz {qjkjgggijjjgffdfaababa`]][\]co }~ywqsqnklorsromjfbdcccbgnuyx~|||xqttsu| ~mmnoprsuvwxyyyyyyyxxxy{{yyyyzz{{{{zz{{zzzzzz||||||||||||}}}}}}}}~~}}{{}{}}~~}|}}|{zz{{{{{{||||}}|||}{| }|||} {}~~ ~yrqoomkf`adr~~~}yojljgfhjijihggecbababcbcdbaer~zxuuurnnsw|{xvqlfcdcddelpsu|z{wrsstw{mmnoqrsuvwyyyyyyyyxxxyzzyyyyzz{{{{{{{{{{zz{{||||||||{{{{||||}}}}}}}}{{}{||}~}|}}|{zz||{{||||||}}}|}}|} }}|| |}| ~{vtturqokc`ajsrqsx}wnkmlhedfghhgfedbbbbaabdgkkns|zyzzyrrv|{ulfeeeeeimpu}|zzvrssuy{mmnopqrsvwyyyyyyyyxxxyyyyyyyzz{{{{||||{{{{||||||||||{{{{||||}}}}||}}}}}{||}~~~}|}}|{zz||{{||||||||||}}}~ ~|z} ~|~{} }ytuxwtssnd`_fmjijou||vomnmjffeefgedbcbddecaafmqty}|{~xvy~ |skjhfeefjmr||{ytrssuy} oonoopqrvwyyyyyyyyxxxyyyyyyyzz{{{{||}}||||||||||||||{{{{||||}}}}||}}}}}{{{}~}}}|}}|{zz||{{||||||{{||}~ |z{ |~|| }}vrvyusuurg`_difeehpwxsnmlkkhhfefebaabbddgebafowz~~}~{{|xpmjgeefjkox ||ysrssuz~ || nonopqrswxzzyyyyyyyyyzzzyyyyzz{{||||{z{{{{||||||{{{{{{{{{|||||||}}}}{{{{||||}}}}{{}}||{{||||||||{{||||~~}z{ }} ~{{rptvsqtxule`aeedachnsplmnmjjhhdcdcccbbddfgfcdny} }}~|tqnjgefhjjtzzwssssvz {zmonopqrsvwxyyyyyyyyyzzzzyyyyzz{{}}}}{z{{{{||||||{{{{{{{{{|||||||||||{{{{{{|||}||{{|||||||||||||}||}}||~~~ |} } {~wpostqpsxwph`acdcacchllklkkjlmmheddcbddddehlmgfjq| |wtnjhgihhq~yyussssv{ {|lnnooqrsvwwxyyyyyyyyzzzzyyyyzz||~~~~}{{{{{||||||{{{{zzzz{|||||||{{{{{{{{zz|||}||{{{{||||||||||}}||}}||~~ ~ {{tnoqsoptxztkaaddddeeegiklkklorrnhffccddeeegmtm`[ew |xsmjijggnz|yxussrsw{|lmnooqrsuvxyyyyyyy{{zzzzyyyyzz||~~~~}|{{{{||||||{{{{zzzz{|||||||{{{{{{{{zz|||}{{{{{{|||||||||}}}||}}||~~ z}yqmoppootwzvlcaddfeffdeijkjikpuuokgeeeeeeedgmuo\R]v }wpmkjgglv{xxtssrtx||mnnnnpqrvxyxzzyyzz{{{{{{zz{{||}}~~}||||||||{{{{{{{{zz{{z|}}|{|||||{{{zzz{{{}}{{}}{{{{||||||}}~~~~}}}}}~ }}vnkmoooquxyuledghiidcccfggefjnvupjfefeeccdcfkmeXJWv |xtqmkgjs| {xusttsuz~ ~|nnnnnpqruwxxzzyyzz{{{{{{{{||}}}}~~}||||||||{{||||{{{{{{z||||{|||||{yyzzz{{{{{{{{{||||||{{{{}}||~~}}}}}~ z~|tmjlonorvyxslffhjkmhfddefedehmrroifeeecaaa_`dd^TJUn¡£ ~{wuqniipw|zxtsttsuz|}nnnnnpqrtvwyyyyyzz{{{{{{{{||}}}}~~}||||||||||}}||{{{{{{z|{{z{{{{{{zyyzzz{{{zz{{{{||||||{{{{||||}}}}}}}~ {{yrljlmlnqvyysmhgijlonlhhggeedfikllgggfec`_]][^^ZSPWf ¢¢~zxupliotx~ wvtsttsv{znnnnnpqrtvwzxxyyzz{{{{{{{{||}}}}~~}||||||||}}}}}}{{||{{z|{{z{{{{{{zyyzzz{{{{{{{||||||{{{{{{{{||~}}}}}}~ zwuqllmlknqvyyslhgilnqrrmihhgfdefgiigghfeeb`^\\\\XTWZb|£¢¢|ywsnjmru|wutstttw|{ oonnnpqsuvwxyyyyzzzz{{|||||||}~~~~~~}}|{{{{{||}}||||{||{{{||{{{{{{{{zzzzzzzzzzzz{{{{|||||z{{||||~}||}}}~ wspmmoonlmntyzsmkhhlpptvtnnkhfccdcdghhihgggc\YXYYWX^_cv¢¡ zyvokortz~wutssssx}} oonnnpqsvvwxyyyyzzzz{{{{{{|||~~~~~~~~~|{{{{{||}}||||{||{{{{{{{{{{{{{zzzzzzzzzzzz{{{{|||||z{{||||~~}}}}}~ ysnkoqpnkllrxzrmjiilprwvtqpmica``acfgkmmlmolb^][[Z[adhv ¡ }|zslptsw{}vssqsssy| oonnnpqsvvwxyyyyzzzz{{{{{{||}~~~~~~~~|{{{{{||||||||{||{{{{{zzzzzzzzzzzzzzzzzzzzzz{{{{{{{yzz{{||~~}}}}}~ zsnnpqpmiijpwxrmihimptvutrqnjda``acdgloopqrqhcbb`^_fhkv¢ ~}vmqvsuw}}ussqssuy |oonnnpqsvvwxyyyyzzzz{{zzzz||~~~~~~~~~|{{{{{||{{||||{||{{{zzzzzzzzzzzzzzzzzzzzzzyyzzzzzz{yzzzz||}}|}}}}~ |rnnpqplihhnwwrmhfgnrtuuusrplgcbbbcdhlnprsttnihhebdhknw¤ }xptxussy|srtrssv{ ~|ppoonpqtvvwxxyxxyyyy{{{{{{}}~~~}|||||{{{||||}}|}zzzyyyyyxxxxzzzzzzyyzzzzzzzzyyz{zz{z{{z{||}}}~~~} |spqsrpmihgnxxrkgcgossstsqpnjedbbacdhlpqrvttspnnkjjmmpy ¤¡ zy~~~ysx{utrv} zttttttx|| ppoonpqtvvwxxyxxyyyy{{{{|{}}~~~~~~~}|||}|{zz}}||}}}}z{zyyyyyxxxxxxxxyyyyzzzzzzzzzzz{{{{{{{z|||~~~~~~} {qssttppmielwwqjfcgqttsttqnliecbbacfimqrsvuusqorqprrrrz¢¤£¡ xu{~~zv}yvruzzstuttux}}{ ppppnpqtvvwxxyyyyyyy{{{{{}~~~~~~~}}||}|{zz}}||}}}}||zyyyyyxxxxwwxxyyyyzzzzzzzzzzz{{{{{{{z|||~~} zqstuurqnhejsuoieckruvutsomkgdcbcdfjloqstwvtrqrusrsuvux¤¦¤¢¡¡¡ vpt{~{yxuuv~ ystuuuv{ | ppqqnpqtvvwxxyzzyy{{{{{{{~}}}}~~}||}|{{{}}||}}}}||zyyyyyxxxxwwxxxxyyzzzzzzzz{{{|{{{{{{z|||}}~~} {rqtuutrofekstniddnsvwvurnliecbbefhlopqsuwvsrqtxwusuxuv|£¥¤£¡¡¢¡ ulpz{zzwtszxtuvvuv}~| ppoonqruwwwwwwxzzzzzz{||}~~~~~~~|}}||||||||}}}}}{zyyyywyyxxxxxxxxyyzzyyyyz{{{{{{{||{{{{||}~~~}}~~ {srxwustpgfkrrlgcentwwvuqokjfdcdfhkpqqrruvrqoqw{zxuvvtqt¤¥¥£¢¢¢¡¡uijv~|{xvwzzuvvvuw|}} ppoonrsuvvwwwwxyzzzzz{}}~~~~|}}||||||||}}}}|{zyyyywxxxxxxxxxxyyyyyyyyz{{{{{{{||{{{{||}~~~~~~~ ~wuyxvuvqhfmrqljefnuxywvrpjjgfdefiosrrrrutpljou{|wuttqmpw ¥¥¥£¢¢¢¡¡ zlfp|||zvvx|xuvvvvx{}}pppppqsuuuwwwwwxyyzzz{||}~}}|}}||||||||}}}}{zzyyyywwwxxxxxxxxyyyyyyyyyz{{{{{{||{{{{||}~~~~~ zwyxxwwrkiotrmjdgpvwzzwrpiiifeehmuxvtttsrnhglsy{vsttpmmt¢¥¥¥£¢¢¢¡ ~pfm{}{wsu{vtwuuvyy|z ppqqqqsuuuwwwwwxxxzzz{||}~}}}}}||||||||}}}}{zzyyyywwwxxwwxxxxyyxxyyyyyz{{{{{{||{{{{||}~~~~~~ ~zyxwvwrljpsrmhehqvwzyvsnjiigfeiox|xvvurplfehqwyvsrrpnlt ¥¥¥£¢¢¢¢ ~qglz}{ysuz}ttwvvwyxyzppqqprqttuxwxxvwxx{{{||}}}}}}~~~|}}~~||{{||}}}}{{{zzzywxxwwvvwwwyyyxxyyzzz{{{||||{{||||}}}}~~~~~ ~zvwxyunmpsqmhfkuwuxxtqmjiigfekq{|wxuromhccluxutrrqnkr¡¥¦¥¤££ shky~z~wwz|uuwuvvxwyzppqqprqttuvuuuvwxxyyz{|}}}}}}}}~|}}}}||||||}}}}{{{{zzywxxwwwwwwwyyyxxyyzz{{{{||||||||}}}}}}~~~~} }yxzzwooprqmhflvzxyxtqmjjjiihmr|}yvuronjdbluwutssrnkq}¢¦¦¥¤¤¢¡ rgjw~z}x|zuuvuvvxx{ysppqqrsstttutvvvwxxyyz{|}}}}}}}}}~~~~~~~~}}||||}}}}}}}}{{{{zzywxxxxxxxxwyyyxxyyzzzz{{||||||||}}}}}}~~~~} }|}|wqpqsplhfoyzxyxuojjllkkklpx{|yvurqpkedltustsssonr|£¦¦¥¤¥£¢}qgju~~z~z}xsuvuvwyz {rorrqqrsutttvuuuvwxxyy{||}}}}}||}}~~~~}}~~}}||||}}~~}}}}{{{{zzywxxyyxxyywyyyxxyyzzyy{{||||}}||}}}}}}~~~~}~ ~wsqrsokhfqzzwyyuniilkllkkoswyxxursqkfelssrttttpps|¤¦¦¥¤¥¤¢zphju~z{~xqvvuvxy| }snkrrqrssttstuuvvwxyyzzz{||||}}||}~~~~~}}}}}}||{{}}}}~~}}{{{z{{yxxxyyyyzzxxyyxxxxyyyyzz{{{{{{{{{|}}}}}~~~~~ vqqrrolhgr~}yzzuqlllonnnmlnqstuwtsrnkilqsqtutrqrsz ¢¦¦¦¤¤¥¢ zqlmv~}z}|}xtvvwwx} |xpkhjrrqrsstttuuvwwwxzzzzz{||||||{|||||||}}}}}}||||}}~~~~}}{{}{{{zzyyyyzzzzyxyyxxxxyyzzzz{{{{z{{{{|}}}}}~~~~} wrqrrolhgs|||xsooorpppokklnpqvtsspnmlnpqtutssssw¢¦§§¥¥¥ yqmnv||z}}xuvvtty~~vokffimqqqrsstttuuvwwwxyyyyxyzzzzzz{{{{{{{{}}}}}}||}}~~~~~~}}||}|{{zzyyyyzzzzzyyyxxxxyyzzzz{{{{z{{{{|}}}}}~~~~} xrqrqnkgfs}~~zurrrtrrpokjjjklpqqqqqplloqtutuussw¢¥§§¥¥¤ yqmntz~}z}}xuvvuuz~wvzyrkeccejnqqqrssttuuvvwwwxzzyywxyyyyzz{{zz{{{{}}}}}}||}}~~}}}}|z{{yyzzyyzzzz{yyyxxxxyy{{zz{{{{z{{{{|}}}}}~~~} xstrpnjges~~}yuttttrrqpnkijijloppppnlloqtutttsrw¡¥¦¦¦¦¤{smnsy}}z} }xuvuvv{|tpsslfcabdjnqqrsrrttttvvwwwwyyxxwwxxyzyyzzy{zzz{||}}~~}}}~~~~}~~}}{yzzzzxxxxzzzzzzzzyyyyyyz{{{||||{|||{|}}}}~~~~ yuutonjgdp}|zuttuutqsvrnlhgghknnmpnmlnrutusttv|¡¥§§¥¥¢ }vnlqvz}y{ {vuuuuw} wokmjgedaadhmssssssttttuuvvvvxxwwwwwwxxyyyyxzzzz{||}}}}}}}~~~~}}}|zzzzzxxyyzzzzzzzzyyyyyyz{{{||||||||{|}}}}~~~~ ~zxyzuqlheq }|yuttrrsotyxsoigfdgkkjnmllmquuvtuwz¥§§¦¥¡~{rnquz|x{ yvuuuvy}tnkljjiigfhmquuttttttttttuuuuvvwwwwwwwwxxyyxzzz{|||}}|||~}}~~~~}}}|zzzzzzzyyzzzzyyzzyyyyyyz{{{||||||||{|}}}}~~~~ |{{~}vpifs}zwuuussronv}zunjgefhhilljjknquvvwx|£¥§§¦¢yrqsy{xz~wtuuuwzyqpnmnopqpoqvxttuuttttttttuuuuvvvvwwvvvvxxxxwyzy{{||}}||{~}}~~~~}}}}{zzzzzzzzzzzzxxzzyyyyyyz{{{||||||||{|}}}}~~~~~~ ~zz |vlgs}yvuvvttrnjt~{smideeegkkiijjottuvy~¡¥¦§¦¡uqrx{y{ }vsuuuvy {uqprqstvvvy|~ttttttttssuuuuttvvwwwwwwwwvwwwwwyy{|||}}||||}}~~~~~}}}{{{zzzzzzzzyyyyyyzzzzzz{{{{{{{{{{z{|||}~~~~~~}}| {} ult{xuuuwutrmio|yqlhcaaeghikjjnqruwy £¦¦¦xrqwzww~|vuvuuwyxqqsvvvxz| ttttttttttuuuuttvvvvvvuuvvwwwwwwxxz{|||||||||}|}~~}}}{{{zzzzzzzzyyyyyyzzzzzz{{{{{{{{{{z{|||}~~~~~~~~}}} ~zzzxtstuvusngkw~xsmd__bdehkjlnorty} £¦¦¦}vruurpsxwvvvx{zuvvuuxztpsuxxy} ttssttttttuuttttuuuuuuuuvvwxxxxxxxz{{{{{|||||}|}~~}}}{{{zzzzzzzzyyyyyyzzzzzz{{{{{{{{{{z{|||}}}}}~~~~}}~ ~xtstuwvsmggp}|wodaaabcfhikmoqs{¢¥¦¦ zttqolkmjkkkptusvvuuy{{srtvyz} ttssttttuuuussttuussssuuuuwwwwwwxxyzzz{{|||||}}}~~}}}{{{zzzzzzzzyyyyyyzzzzzz{{{{{{{{{{z{|||}}}}}~~~~}}~ zustvwvrlfdky~zpgcc`badfhilnpt| ¢¥¦¦ |sqqmiggceeekprrvvuuz{ztuvwz} ttssrsttuuuuttuuuuttstuuuvwwvvwwxxyyzz{{{{||||}}}~~~~~~~~||{{{zyyy{{zzzzzyz{zz{{{{|z{{{{{|{{||||~~}}}}}}~} zxyxwtqlgehq{zsigfb``afgghjnr{ ¤§§¡ zrmjgecba``bhmqqstuvx~~vuvvw} {ttssrsttuuuuuuuuttttstuuuwwwvvwwxxyyzz{{{{||||}}}~~~~~~~~||{{{zyzz{{{{zzz{{{{{{{|||{{{{{{|{{||||~~}}}}}}}} ~|wsmhefkuz{tpikjebbcdefghlpx~ ¤¦¦ zumjgedca`]]`ejmqrtuvx}}zutwy~ {ttssrsttuuuuuuttttsssttutvvvvvwwxxyyzz{{{{||||}}}~~~~~~||{{{zy{{{{{{zz{{{|{{|||||{{{{{{|||||||~~~~}}}}} zsliginuuokjmnjhggffffghlsy~£¥¤}}|zzyxvqpjfedccb`]\_adhnpsuuxz{wvy} ttssrsttuuuuuuttssrrsttutvvvvvwwxxyyzz{{{{||||}}}~~~~~~||{{{zyyy{{{{zz{{||||}|}}}{{{{{{|}}||||~~}}}}~ ypmjikqqmjjnplmmlihffgfiouz¢¤¡ ~||{yyxusrqplkfffdcdb`^\``bfkoruuwz{y{ ssttstttuuuuttsssssstuuvuvvvvvvvvvxxyyyz{{{{{{{|}}~~~~~}}{z{{zzyy{{zz{{z{{{||{{{|||{{{|||}}{{||~~~~}}|~~ ~xqmopnlilprqorpmifeddhmsw}¢¡~~}{zzzyxvuttsqolhgeeddbcdeda``adglotvz| ssttttttuuuuttttssssttuvvvvvvvwwwwyyyyyz{{{{{{{|}}~~~~~}}|{{{zz{{{{{{{{{{{|||||||||||{}||}}{{{{~~~~}}|~ ~{yxvsqswxyz|ztpkgddfkotx|{z{yxyxyyxwuttsqolhgfdcbdceggcaaadfilqw rrssttttuuuuttttssssttuvvvvvwwwwxxyyyyyz{{{{{{{|}}~~~~~}}}|||{{{{{{{{||{|||||{{||||}}{}||||{{{{~~~~}}|~ }{}zvpifddgjosx{~ }zyxzyxyyyyzzwxvusqokihdcacefhhfeccdefimv qqrrttttuuuuttuussssttuvvvvvxxxxxxxxyyyz{{{{{{{|}}~~~~~}}}}}}||||{{||}}|||}||||||||}}{}||{{{{||~~~~}}|~ {umgdcdglotwz ~|yxyyyzzz{z{|}{{zxusqmiidcbaefiihgcdddegku rrssssssttutssttssrsrsttvvwwwwwwxxyyzzz{{{||zzz|}}~~~~}}}}~~}|||||||{{}|||||||{{||}}~~|}}}||||||~~~~}|~~ |slgcdfjkmps{~~}{xxxxyxxz|}~~|xutrnkhda`cgghiihfecccgmyssssssssuuutttttssrsrsuuvvwwwwxxxxxxzzzz{{||{{{|}}~~~~}}}}}}|{{{{{{{||}|||||||{{||}}~~~~~~~~||||~~~~}|} {qieefhjjmotvttvuxxyzzz} zwvuqmjgcbcfhjlljigdddegny ttttuuuuvvutttttssrstuvwwwwwxxxxxxyyyyyy{{|||||}}}~~~~}}}}}}|{{{||||}}}|||||||||||}}~~}}}}}}||||~~~~~}} xmhfghihikmopqvvxxy{|~ zwwuromjhgeeimnnlkhfccedfo~ttuuuuuuvvutuuttssrsuvvwwwvvyyyywwxxxxyy{{|||||}}}~~~~}}}}}}|{{{}}}}}}}|||||||}}||}}~~{{{{{{||||~~~~}~~ {rjhggiggjjlmpuvwwy|{yxxurqqnlkecimppmkjgdddediyssttuuuuuuutttttssrtuvwwwwwwxyzzyyxxyyzz{{|||||}|~~~}}}|}}||||}}}}}}||||{{}}~~}}}}}}|{{{{{||||{{{|{} ~ynhihiikkhhjmptsv{yxwxxuwwtsnihkmqqnnjiecddberssttuuuuuuutttttssstuvwxxxxxxyzz{{zzzzzz{{|||||}|~~~~~}|}}||||}}}}}}||||||}~~~~~}}}}}{{{{{||||{{{|{} }skjihikkfegknruy{zz|}}vplllnqqpomlgdddcel|uuuuuuuuuuutttttsstuvvwxxxyyxyzz{{zzzzzz{{|||||}|~~~~~}|}}||||}}}}}}||||||}~~~~~}}}}}{{{{{||||{{{|}}} yplihillgeehimrv{soonpqqpppnjfddccgtuuuuuuuuuuutttttsstuvwwxxyyyxyzz||{{zzzz{{|||||}|~~~}|}}||||}}}}}}||||}}}~~~~~}}}}}{{{{{||||{{{|~~|~ ulihillifeffjnrz~uoopqssqqpnjgcddbenvvuuvvuuvvvutssstttvwwxyzyzzzz{{||{{{{zz{{zz{{}}}}~~}~||||}}}}}}}}||||}}~~~~~}||||{{{{|||||||||}|~ yojfgjmkifceghlpvywomruwwtqpomifeedcizuuuuvvuuvvvutssstttvxwzz{zyyzzzz||{{{{zz{{zz{{}}}}~~}~||||}}}}}}}}}}||}}}}~~~}||||{{{{|||||||||} {z yqhgjlmlifedcefjmqvz xplow|}wsponkhgfecftuuuuuuuuvvvutssstttvwxzz{zyyzzzz||||{{zz{{{{||}}}}~~~}||||}}}}}}}}~~||}}}}}}}}|{||{{||}}|||||||} |zz xniijmpomecbcdehikot} {qlmu}|xspnliigfegnzvvuuttuuvvvutssstttuwyzz{zzz{{{{||}}{{zz{{||}}}}}}~~}||||}}}}}}}}~~||}}||||}}|{||{{{{|||||||||}~y{} ulhhmsrogdcccdfeeiltz~snos{{tqnmkjhfghjtttvuttttvvvussrrsuuvwyz{|{||}}}}}}}}||{{||}}}}~~~~~~~~~~}}}}{{||}}}}}}}}}}}|}}~~}|}|||||{|||||||{~ ywy} {offhprojeecbdfcadfjqywonrzxplkmmkhgghoyuuvuuuuuvvvusssssuvvwyz{||||~~||}}}}||||}}}}}}~~~~~~~~~~}}}}||||}}}}}}~~~~}||~~~}||{{{{{{|||||||{~ ~wx| xlfflprokfc`aefddbdipy{snpw}ullnomkghgls~wwvuvvvvvvuvttsstvwwwyz{||}}~~}}}}}}}}||}}}}}}~~~~~~~~~~}}}}||||}}}}}}~~}|{}~~|||{{{zz{|||||||{~ zxz ~sgehmrspje``dhjhdceks~~vopw wmklppojhhknwvvvuvvvvvvuvuutttvwwwyz{||~~}}}}}}}}~~||}}}}}}~~~~~~~~~~}}}}}}||}}}}}}~~}|{|~~||{zzzyyz{||||||{~~xx| xkffkournhcafjmnjffir~ xqpv ynklprrmiiijq~wwwwwwwwwwvvvvuutuvwwxz{}}~|}}}}||}}||}}}}}}~~~~~~~~~~}}||}}}}|}~~~~~~~~}}||}|}|{{{zyxyzz{}}|||{wz ~tgdglrspjedimrtroknu}{uqt| }slkptxsliijmvwwxxwwwwwwvvwwuuuvwwyyz|}~~|}}}}||||||}}}}}}~~~~~~~~}}||}}}}|}~~~~~~~~}}}|}|}|{{zzyxyzz{||||} ~xw} }oedfmsvqjggjqx}{xy|~uoqzvnjntyxokjijowxxxxwwwwwwvvwwvvvwyy{{{|}~~~~}}||||}}}}}}}}~~~~~~~~}}~~}}}}|}~~~~~~}}~~}||{zzzyxwxyz{||||} zxy~ wlfdgpwvqlgjow vppxyqklpvzuojhghnxyyxxwwwwwwvvxxxxvwz{|{|}}~~}}||~~}}}}}}~~~~~~~~}}~~}}|}~~~~~~}}~~}|{zzzyyxwxyz{{{||} ~xy} }tkfcjtyvnkknt}xpqwyrmkmuzxskifeiq{ xxwwwuwwwwwwxxxxvwz{|}}~~~~~}}~~}|||}}~~~~~~~~~~~~~~~~~}}~~~~}}}}}}}}|{{{{yyyxxxwyzz{{||yvz}} |ricepy{vqmjnwwppv~|vqoouyzwoiggeis xxwwxvxxwwwwxxxxwxyz|~}~~~~~}}~~}|||}}~~}}~~~~~~~~~~~~~}~~~~~}}}}}}}}|{{yyxxxxxxwyzz{{||vuz~|| }qeagu{{wpikr{xppu}~xttpswzztmhfcem{xxxxyxxxwwwwxxyywxz{|~}~~~~~}}~~}|||}}}}}}~~~~~~~~}|}}~~~}}}}}}}}|{{xxwwxwwwvxzz{{|| |uv|~z| ylaaly~{skjnu~ zrpt{|xwrruy{voieccivxxyyxwwwwwwwxxzzxyz{|~}~~~~~}}~~}|||}}||~~~~~~~~~~}|||~~~}}}}}}}}|{{xxxxwwwwvxzz{{||zvxz|~ rc_ft}}wmjkq{ {uosz|{vstwzwoiebbeq~yyzzyxxxxxwwxxyyyyy{{|~~}~~}}}}}}}}}}||}~~~~~}}}}}}}}~~~~}|||{zzyxxwuxxwwxyxz{|}ww{ |{| yh]]hx|{smnnt~~wqryzwsvxwqiedbem}zzzzyxxxxxxxyyyyzyyz{|~~}}}}}}}}}}}}}}~~~~~}}}}}}}}~~~~}|||zzzyxxxvwwwwwwxzz{~wty} }{|qd\akwyztpnqyxrsx}{tuwwtmgebel{zzzzyxxxxxxxyyyyzyy{{|~~}~~}}}}}}}}}}}~~~~~~~}}}}}}}}~~~~|}||zyyyxxwuvvvvvwxzz{~uuy ~} |l^\`jr{{uont~zuru{ yvwxwqjifejxzzzzyxxxxxyyzz{{{{|{{|~~}}}}}}}}}~~}~}}~~~~}}}}}}}}}}}}|}||zyyxxxutvvuuuvwyz{}{sv| ~~ scZZ`ly}wrmqz{urtz}xvxwslkhehv{{yyxxxxyyyyyyzzz||||}}}}~~}}}}}}~~}}~~~~~~~~~~~~}}}|||||||{zywxxwuuvvuuvvxyy{}xtx~ {g\Y[ew~ytpnu~|vqsv|wvxvsnmiefr| {{yyxxxxyyyyyyzz{{|}}}}}}~~~~~~~~~~}}~~~~~~~}}}||||||{zzzwxxwuuvvuuuvxxy{~wv{ p_WW^mx{wropx ~wspsy ~yxvtpmjfemw {{yyxxxxyyzz{{zz{|}}}}}}}}}~~~~~~~~~}}}||||||{zzywxwvuuvvuuvwwxxz}~vx} yf[VYdu}}xsos~ysmow|~{wvuqnkgfit{{yyxxxxyyzz{{zz{|||}}}}}~~}}~~~~~~~~~}}}|||||||{{zwwvuuuvvuuwwwwxz} ~vx l^WV`s|{voq{ yrmptz|}|wvuqnkgehq} \ No newline at end of file
diff --git a/media/test/data/bali_640x360_RGB24.rgb b/media/test/data/bali_640x360_RGB24.rgb deleted file mode 100644 index 99ebd1e..0000000 --- a/media/test/data/bali_640x360_RGB24.rgb +++ /dev/null Binary files differ
diff --git a/media/test/data/bear_128x96_40frames.yuv b/media/test/data/bear_128x96_40frames.yuv deleted file mode 100644 index e2b9ac7..0000000 --- a/media/test/data/bear_128x96_40frames.yuv +++ /dev/null Binary files differ
diff --git a/media/test/data/bear_320x192_40frames.nv12.yuv b/media/test/data/bear_320x192_40frames.nv12.yuv deleted file mode 100644 index 3bff3a4d..0000000 --- a/media/test/data/bear_320x192_40frames.nv12.yuv +++ /dev/null Binary files differ
diff --git a/media/test/data/bear_320x192_40frames.nv21.yuv b/media/test/data/bear_320x192_40frames.nv21.yuv deleted file mode 100644 index 9ef82dd..0000000 --- a/media/test/data/bear_320x192_40frames.nv21.yuv +++ /dev/null Binary files differ
diff --git a/media/test/data/bear_320x192_40frames.yv12.yuv b/media/test/data/bear_320x192_40frames.yv12.yuv deleted file mode 100644 index 65eca63..0000000 --- a/media/test/data/bear_320x192_40frames.yv12.yuv +++ /dev/null Binary files differ
diff --git a/media/test/data/sync_192p_20frames.yuv b/media/test/data/sync_192p_20frames.yuv deleted file mode 100644 index 35c6e92..0000000 --- a/media/test/data/sync_192p_20frames.yuv +++ /dev/null Binary files differ
diff --git a/net/BUILD.gn b/net/BUILD.gn index 1dce4bf..4d93a3f 100644 --- a/net/BUILD.gn +++ b/net/BUILD.gn
@@ -2100,6 +2100,8 @@ "urlmon.lib", "winhttp.lib", ] + + ldflags = [ "/DELAYLOAD:urlmon.dll" ] } if (!is_nacl) { @@ -5545,6 +5547,8 @@ "third_party/quiche/src/quic/quartc/test/quartc_peer.cc", "third_party/quiche/src/quic/quartc/test/quartc_peer.h", "third_party/quiche/src/quic/quartc/test/quartc_peer_test.cc", + "third_party/quiche/src/quic/quartc/test/quic_trace_interceptor.cc", + "third_party/quiche/src/quic/quartc/test/quic_trace_interceptor.h", "third_party/quiche/src/quic/quartc/test/random_delay_link.cc", "third_party/quiche/src/quic/quartc/test/random_delay_link.h", "third_party/quiche/src/quic/quartc/test/random_packet_filter.cc",
diff --git a/net/base/data_url.cc b/net/base/data_url.cc index 495f91b..90b0bb6 100644 --- a/net/base/data_url.cc +++ b/net/base/data_url.cc
@@ -96,25 +96,23 @@ // (Spaces in a data URL should be escaped, which is handled below, so any // spaces now are wrong. People expect to be able to enter them in the URL // bar for text, and it can't hurt, so we allow it.) - std::string temp_data = std::string(comma + 1, end); + // + // TODO(mmenke): Is removing all spaces reasonable? GURL removes trailing + // spaces itself, anyways. Should we just trim leading spaces instead? + // Allowing random intermediary spaces seems unnecessary. + + base::StringPiece raw_body(comma + 1, end); // For base64, we may have url-escaped whitespace which is not part // of the data, and should be stripped. Otherwise, the escaped whitespace // could be part of the payload, so don't strip it. - if (base64_encoded) - temp_data = UnescapeBinaryURLComponent(temp_data); - - // Strip whitespace. - if (base64_encoded || !(mime_type->compare(0, 5, "text/") == 0 || - mime_type->find("xml") != std::string::npos)) { - base::EraseIf(temp_data, base::IsAsciiWhitespace<wchar_t>); - } - - if (!base64_encoded) - temp_data = UnescapeBinaryURLComponent(temp_data); - if (base64_encoded) { - size_t length = temp_data.length(); + std::string unescaped_body = UnescapeBinaryURLComponent(raw_body); + + // Strip spaces, which aren't allowed in Base64 encoding. + base::EraseIf(unescaped_body, base::IsAsciiWhitespace<char>); + + size_t length = unescaped_body.length(); size_t padding_needed = 4 - (length % 4); // If the input wasn't padded, then we pad it as necessary until we have a // length that is a multiple of 4 as required by our decoder. We don't @@ -122,13 +120,22 @@ // then the input isn't well formed and decoding will fail with or without // padding. if ((padding_needed == 1 || padding_needed == 2) && - temp_data[length - 1] != '=') { - temp_data.resize(length + padding_needed, '='); + unescaped_body[length - 1] != '=') { + unescaped_body.resize(length + padding_needed, '='); } - return base::Base64Decode(temp_data, data); + return base::Base64Decode(unescaped_body, data); } - temp_data.swap(*data); + // Strip whitespace for non-text MIME types. + std::string temp; + if (!(mime_type->compare(0, 5, "text/") == 0 || + mime_type->find("xml") != std::string::npos)) { + temp = raw_body.as_string(); + base::EraseIf(temp, base::IsAsciiWhitespace<char>); + raw_body = temp; + } + + *data = UnescapeBinaryURLComponent(raw_body); return true; }
diff --git a/net/docs/proxy.md b/net/docs/proxy.md index 55ae06ac..66c157a 100644 --- a/net/docs/proxy.md +++ b/net/docs/proxy.md
@@ -2,7 +2,8 @@ ## Implicit bypass rules -Requests to certain hosts will not be sent through a proxy, and will instead be sent directly. +Requests to certain hosts will not be sent through a proxy, and will instead be +sent directly. We call these the _implicit bypass rules_. The implicit bypass rules match URLs whose host portion is either a localhost name or a link-local IP literal. @@ -38,7 +39,8 @@ * Prior to M71 there were no implicit proxy bypass rules (except if using `--winhttp-proxy-resolver`) * In M71 Chrome applied implicit proxy bypass rules to PAC scripts -* In M72 Chrome generalized the implicit proxy bypass rules to manually configured proxies +* In M72 Chrome generalized the implicit proxy bypass rules to manually + configured proxies ## Overriding the implicit bypass rules @@ -79,8 +81,8 @@ Initially, Chrome will try the proxies in order. This means first attempting the request through the HTTP WebProxy `proxy1`. If that "fails", the request is -next attempted through the HTTPS proxy `proxy2`. Lastly if that fails, the request is -attempted through the SOCKSv5 proxy `proxy3`. +next attempted through the HTTPS proxy `proxy2`. Lastly if that fails, the +request is attempted through the SOCKSv5 proxy `proxy3`. This process is referred to as _proxy fallback_. What constitutes a "failure" is described later. @@ -88,7 +90,8 @@ Proxy fallback is stateful. The actual order of proxy attempts made be Chrome is influenced by the past responsiveness of proxy servers. -Let's say we request `http://www.example.com/`. Per the PAC script this resolves to: +Let's say we request `http://www.example.com/`. Per the PAC script this +resolves to: ``` "PROXY proxy1; HTTPS proxy2; SOCKS5 proxy3" @@ -99,8 +102,8 @@ Let's say that the attempt through `proxy1` fails, but then the attempt through `proxy2` succeeds. Chrome will mark `proxy1` as _bad_ for the next 5 minutes. -Being marked as _bad_ means that `proxy1` is de-prioritized with respect to other -proxies options (including DIRECT) that are not marked as bad. +Being marked as _bad_ means that `proxy1` is de-prioritized with respect to +other proxies options (including DIRECT) that are not marked as bad. That means the next time `http://www.example.com/` is requested, the effective order for proxies to attempt will be: @@ -156,3 +159,56 @@ [chrome://net-internals/#proxy](chrome://net-internals/#proxy). Note the UI will not give feedback that the bad proxies were cleared, however capturing a new NetLog dump can confirm it was cleared. + +## Arguments are passed to `FindProxyForURL(url, host)` in PAC scripts + +PAC scripts in Chrome are expected to define a JavaScript function +`FindProxyForURL`. + +The historical signature for this function is: + +``` +function FindProxyForURL(url, host) { + ... +} +``` + +Scripts can expect to be called with string arguments `url` and `host` such +that: + +* `url` is a *sanitized* version of the request's URL +* `host` is the unbracketed host portion of the origin. + +Sanitization of the URL means that the path, query, fragment, and identity +portions of the URL are stripped. Effectively `url` will be +limited to a `scheme://host:port/` style URL + +Examples of how `FindProxyForURL()` will be called: + +``` +// Actual URL: https://www.google.com/Foo +FindProxyForURL('https://www.google.com/', 'www.google.com') + +// Actual URL: https://[dead::beef]/foo?bar +FindProxyForURL('https://[dead::beef]/', 'dead::beef') + +// Actual URL: https://www.example.com:8080#search +FindProxyForURL('https://www.example.com:8080/', 'example.com') + +// Actual URL: https://username:password@www.example.com +FindProxyForURL('https://www.example.com/', 'example.com') +``` + +Stripping the path and query from the `url` is a departure from the original +Netscape implementation of PAC. It was introduced in Chrome 52 for [security +reasons](https://bugs.chromium.org/p/chromium/issues/detail?id=593759). + +There is currently no option to turn off sanitization of URLs passed to PAC +scripts (removed in Chrome 75). + +The sanitization of http:// URLs currently has a different policy, and does not +strip query and path portions of the URL. That said, users are advised not to +depend on reading the query/path portion of any URL +type, since future versions of Chrome may [deprecate that +capability](https://bugs.chromium.org/p/chromium/issues/detail?id=882536) in +favor of a consistent policy.
diff --git a/net/ftp/ftp_network_transaction.cc b/net/ftp/ftp_network_transaction.cc index c2cdd05..2c21765 100644 --- a/net/ftp/ftp_network_transaction.cc +++ b/net/ftp/ftp_network_transaction.cc
@@ -282,6 +282,31 @@ DetectTypecode(); + if (request_->url.has_path()) { + std::string gurl_path(request_->url.path()); + + // Get rid of the typecode, see RFC 1738 section 3.2.2. FTP url-path. + std::string::size_type pos = gurl_path.rfind(';'); + if (pos != std::string::npos) + gurl_path.resize(pos); + + // If the path contains characters not considered safe to unescape, fail the + // request. + std::set<unsigned char> illegal_encoded_bytes{'/', '\\'}; + // Null shouldn't be needed, since GURLs with nulls in the path aren't + // considered valid, but can't hurt. Note that this range includes CRs and + // LFs. + for (char c = '\x00'; c < '\x20'; ++c) { + illegal_encoded_bytes.insert(c); + } + if (ContainsEncodedBytes(gurl_path, illegal_encoded_bytes)) + return ERR_INVALID_URL; + + // This may unescape to non-ASCII characters, but we allow that. See the + // comment for IsValidFTPCommandSubstring. + unescaped_path_ = UnescapeBinaryURLComponent(gurl_path); + } + next_state_ = STATE_CTRL_RESOLVE_HOST; int rv = DoLoop(OK); if (rv == ERR_IO_PENDING) @@ -498,27 +523,12 @@ std::string FtpNetworkTransaction::GetRequestPathForFtpCommand( bool is_directory) const { - std::string path(current_remote_directory_); - if (request_->url.has_path()) { - std::string gurl_path(request_->url.path()); + std::string path(current_remote_directory_ + unescaped_path_); - // Get rid of the typecode, see RFC 1738 section 3.2.2. FTP url-path. - std::string::size_type pos = gurl_path.rfind(';'); - if (pos != std::string::npos) - gurl_path.resize(pos); - - path.append(gurl_path); - } // Make sure that if the path is expected to be a file, it won't end // with a trailing slash. if (!is_directory && path.length() > 1 && path.back() == '/') path.erase(path.length() - 1); - UnescapeRule::Type unescape_rules = - UnescapeRule::SPACES | - UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS; - // This may unescape to non-ASCII characters, but we allow that. See the - // comment for IsValidFTPCommandSubstring. - path = UnescapeURLComponent(path, unescape_rules); if (system_type_ == SYSTEM_TYPE_VMS) { if (is_directory)
diff --git a/net/ftp/ftp_network_transaction.h b/net/ftp/ftp_network_transaction.h index cbcfdf7..c7cb875 100644 --- a/net/ftp/ftp_network_transaction.h +++ b/net/ftp/ftp_network_transaction.h
@@ -251,6 +251,8 @@ ClientSocketFactory* const socket_factory_; + std::string unescaped_path_; + std::unique_ptr<StreamSocket> ctrl_socket_; std::unique_ptr<StreamSocket> data_socket_;
diff --git a/net/ftp/ftp_network_transaction_unittest.cc b/net/ftp/ftp_network_transaction_unittest.cc index ce412717..b204b89 100644 --- a/net/ftp/ftp_network_transaction_unittest.cc +++ b/net/ftp/ftp_network_transaction_unittest.cc
@@ -8,6 +8,7 @@ #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "net/base/io_buffer.h" #include "net/base/ip_endpoint.h" @@ -399,51 +400,28 @@ return MockWriteResult(ASYNC, data.length()); switch (state()) { case PRE_SIZE: - return Verify("SIZE /file\r\n", data, PRE_CWD, "213 18\r\n"); + return Verify(base::StringPrintf("SIZE %s\r\n", file_path_.c_str()), + data, PRE_CWD, "213 18\r\n"); case PRE_CWD: - return Verify("CWD /file\r\n", data, - use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV, + return Verify(base::StringPrintf("CWD %s\r\n", file_path_.c_str()), + data, use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV, "550 Not a directory\r\n"); case PRE_RETR: - return Verify("RETR /file\r\n", data, PRE_QUIT, "200 OK\r\n"); + return Verify(base::StringPrintf("RETR %s\r\n", file_path_.c_str()), + data, PRE_QUIT, "200 OK\r\n"); default: return FtpSocketDataProvider::OnWrite(data); } } + void set_file_path(const std::string& file_path) { file_path_ = file_path; } + private: + std::string file_path_ = "/file"; + DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderFileDownload); }; -class FtpSocketDataProviderPathSeparatorsNotUnescaped - : public FtpSocketDataProvider { - public: - FtpSocketDataProviderPathSeparatorsNotUnescaped() = default; - ~FtpSocketDataProviderPathSeparatorsNotUnescaped() override = default; - - MockWriteResult OnWrite(const std::string& data) override { - if (InjectFault()) - return MockWriteResult(ASYNC, data.length()); - switch (state()) { - case PRE_SIZE: - return Verify("SIZE /foo%2f..%2fbar%5c\r\n", data, PRE_CWD, - "213 18\r\n"); - case PRE_CWD: - return Verify("CWD /foo%2f..%2fbar%5c\r\n", data, - use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV, - "550 Not a directory\r\n"); - case PRE_RETR: - return Verify("RETR /foo%2f..%2fbar%5c\r\n", data, PRE_QUIT, - "200 OK\r\n"); - default: - return FtpSocketDataProvider::OnWrite(data); - } - } - - private: - DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderPathSeparatorsNotUnescaped); -}; - class FtpSocketDataProviderFileNotFound : public FtpSocketDataProvider { public: FtpSocketDataProviderFileNotFound() = default; @@ -582,34 +560,6 @@ DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderVMSFileDownload); }; -class FtpSocketDataProviderEscaping : public FtpSocketDataProviderFileDownload { - public: - FtpSocketDataProviderEscaping() = default; - ~FtpSocketDataProviderEscaping() override = default; - - MockWriteResult OnWrite(const std::string& data) override { - if (InjectFault()) - return MockWriteResult(ASYNC, data.length()); - switch (state()) { - case PRE_SIZE: - return Verify("SIZE / !\"#$%y\200\201\r\n", data, PRE_CWD, - "213 18\r\n"); - case PRE_CWD: - return Verify("CWD / !\"#$%y\200\201\r\n", data, - use_epsv() ? PRE_RETR_EPSV : PRE_RETR_PASV, - "550 Not a directory\r\n"); - case PRE_RETR: - return Verify("RETR / !\"#$%y\200\201\r\n", data, PRE_QUIT, - "200 OK\r\n"); - default: - return FtpSocketDataProviderFileDownload::OnWrite(data); - } - } - - private: - DISALLOW_COPY_AND_ASSIGN(FtpSocketDataProviderEscaping); -}; - class FtpSocketDataProviderFileDownloadInvalidResponse : public FtpSocketDataProviderFileDownload { public: @@ -1322,16 +1272,31 @@ ExecuteTransaction(&ctrl_socket, "ftp://test:hello%20world@host/file", OK); } -// Make sure FtpNetworkTransaction doesn't request paths like -// "/foo/../bar". Doing so wouldn't be a security issue, client side, but just -// doesn't seem like a good idea. -TEST_P(FtpNetworkTransactionTest, - DownloadTransactionPathSeparatorsNotUnescaped) { - FtpSocketDataProviderPathSeparatorsNotUnescaped ctrl_socket; - ExecuteTransaction(&ctrl_socket, "ftp://host/foo%2f..%2fbar%5c", OK); +TEST_P(FtpNetworkTransactionTest, FailOnInvalidUrls) { + const char* const kBadUrls[]{ + // Make sure FtpNetworkTransaction doesn't request paths like + // "/foo/../bar". Doing so wouldn't be a security issue, client side, but + // just doesn't seem like a good idea. + "ftp://host/foo%2f..%2fbar%5c", - // We pass an artificial value of 18 as a response to the SIZE command. - EXPECT_EQ(18, transaction_->GetResponseInfo()->expected_content_size); + // LF + "ftp://host/foo%10.txt", + // CR + "ftp://host/foo%13.txt", + + "ftp://host/foo%00.txt", + }; + + for (const char* bad_url : kBadUrls) { + SCOPED_TRACE(bad_url); + + SetUpTransaction(); + FtpRequestInfo request_info = GetRequestInfo(bad_url); + ASSERT_EQ( + ERR_INVALID_URL, + transaction_->Start(&request_info, callback_.callback(), + NetLogWithSource(), TRAFFIC_ANNOTATION_FOR_TESTS)); + } } TEST_P(FtpNetworkTransactionTest, EvilRestartUser) { @@ -1403,9 +1368,27 @@ } TEST_P(FtpNetworkTransactionTest, Escaping) { - FtpSocketDataProviderEscaping ctrl_socket; - ExecuteTransaction(&ctrl_socket, "ftp://host/%20%21%22%23%24%25%79%80%81", - OK); + const struct TestCase { + const char* url; + const char* expected_path; + } kTestCases[] = { + {"ftp://host/%20%21%22%23%24%25%79%80%81", "/ !\"#$%y\200\201"}, + // This is no allowed to be unescaped by UnescapeURLComponent, since it's + // a lock icon. But it has no special meaning or security concern in the + // context of making FTP requests. + {"ftp://host/%F0%9F%94%92", "/\xF0\x9F\x94\x92"}, + // Invalid UTF-8 character, which again has no special meaning over FTP. + {"ftp://host/%81", "/\x81"}, + }; + + for (const auto& test_case : kTestCases) { + SCOPED_TRACE(test_case.url); + + SetUpTransaction(); + FtpSocketDataProviderFileDownload ctrl_socket; + ctrl_socket.set_file_path(test_case.expected_path); + ExecuteTransaction(&ctrl_socket, test_case.url, OK); + } } // Test for http://crbug.com/23794.
diff --git a/net/http/transport_security_state_static.json b/net/http/transport_security_state_static.json index 88b51910..f5a6ba8 100644 --- a/net/http/transport_security_state_static.json +++ b/net/http/transport_security_state_static.json
@@ -256,11 +256,15 @@ { "name": "dev", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true }, { "name": "foo", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true }, { "name": "gle", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true }, + { "name": "gmail", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true }, { "name": "google", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true, "pins": "google" }, + { "name": "hangout", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true, "pins": "google" }, { "name": "insurance", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true }, + { "name": "meet", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true }, { "name": "new", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true }, { "name": "page", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true }, { "name": "play", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true }, + { "name": "search", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true }, { "name": "youtube", "policy": "public-suffix", "mode": "force-https", "include_subdomains": true }, // Google domains using Expect-CT.
diff --git a/net/log/net_log_entry.cc b/net/log/net_log_entry.cc index da6329ac..16be908 100644 --- a/net/log/net_log_entry.cc +++ b/net/log/net_log_entry.cc
@@ -28,9 +28,9 @@ entry_dict.SetInteger("phase", static_cast<int>(data_->phase)); // Set the event-specific parameters. - if (data_->parameters_callback) { - entry_dict.SetKey("params", data_->parameters_callback->Run(capture_mode_)); - } + base::Value params = ParametersToValue(); + if (!params.is_none()) + entry_dict.SetKey("params", std::move(params)); return std::move(entry_dict); }
diff --git a/net/log/net_log_unittest.cc b/net/log/net_log_unittest.cc index 6ff65a3..0169afb 100644 --- a/net/log/net_log_unittest.cc +++ b/net/log/net_log_unittest.cc
@@ -509,6 +509,33 @@ SerializedNetLogUint64(std::numeric_limits<uint64_t>::max())); } +// Tests that serializing a NetLogEntry with empty parameters omits a value for +// "params". +TEST(NetLogTest, NetLogEntryToValueEmptyParams) { + // NetLogEntry with a null parameters callback. + NetLogEntryData entry_data1(NetLogEventType::REQUEST_ALIVE, NetLogSource(), + NetLogEventPhase::BEGIN, base::TimeTicks(), + nullptr); + NetLogEntry entry1(&entry_data1, NetLogCaptureMode::Default()); + + // NetLogEntry with a parameters callback that returns a NONE value. + NetLogParametersCallback callback2 = + base::BindRepeating([](NetLogCaptureMode) { return base::Value(); }); + NetLogEntryData entry_data2(NetLogEventType::REQUEST_ALIVE, NetLogSource(), + NetLogEventPhase::BEGIN, base::TimeTicks(), + &callback2); + NetLogEntry entry2(&entry_data2, NetLogCaptureMode::Default()); + + ASSERT_FALSE(entry_data1.parameters_callback); + ASSERT_TRUE(entry_data2.parameters_callback); + + ASSERT_TRUE(entry1.ParametersToValue().is_none()); + ASSERT_TRUE(entry2.ParametersToValue().is_none()); + + ASSERT_FALSE(entry1.ToValue().FindKey("params")); + ASSERT_FALSE(entry2.ToValue().FindKey("params")); +} + } // namespace } // namespace net
diff --git a/net/nqe/network_quality_estimator.cc b/net/nqe/network_quality_estimator.cc index ab2e213..a227a3a0 100644 --- a/net/nqe/network_quality_estimator.cc +++ b/net/nqe/network_quality_estimator.cc
@@ -496,6 +496,10 @@ void NetworkQualityEstimator::OnConnectionTypeChanged( NetworkChangeNotifier::ConnectionType type) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + // It's possible that |type| has the same value as |current_network_id_.type|. + // This can happen if the device switches from one WiFi SSID to another. + DCHECK_EQ(nqe::internal::OBSERVATION_CATEGORY_COUNT, base::size(rtt_ms_observations_)); @@ -1319,7 +1323,12 @@ // Maybe recompute the effective connection type since a new RTT observation // is available. - MaybeComputeEffectiveConnectionType(); + if (observation.source() != + NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP_CACHED_ESTIMATE && + observation.source() != + NETWORK_QUALITY_OBSERVATION_SOURCE_TRANSPORT_CACHED_ESTIMATE) { + MaybeComputeEffectiveConnectionType(); + } for (auto& observer : rtt_observer_list_) { observer.OnRTTObservation(observation.value(), observation.timestamp(), observation.source()); @@ -1355,7 +1364,12 @@ // Maybe recompute the effective connection type since a new throughput // observation is available. - MaybeComputeEffectiveConnectionType(); + if (observation.source() != + NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP_CACHED_ESTIMATE && + observation.source() != + NETWORK_QUALITY_OBSERVATION_SOURCE_TRANSPORT_CACHED_ESTIMATE) { + MaybeComputeEffectiveConnectionType(); + } for (auto& observer : throughput_observer_list_) { observer.OnThroughputObservation( observation.value(), observation.timestamp(), observation.source());
diff --git a/net/quic/quic_flags_list.h b/net/quic/quic_flags_list.h index 3ee2b916e..2c3446e 100644 --- a/net/quic/quic_flags_list.h +++ b/net/quic/quic_flags_list.h
@@ -28,7 +28,7 @@ // Enables server-side support for QUIC stateless rejects. QUIC_FLAG(bool, FLAGS_quic_reloadable_flag_enable_quic_stateless_reject_support, - true) + false) // If true, require handshake confirmation for QUIC connections, functionally // disabling 0-rtt handshakes. @@ -47,7 +47,7 @@ // connection. QUIC_FLAG(bool, FLAGS_quic_reloadable_flag_quic_use_cheap_stateless_rejects, - true) + false) // If true, allows packets to be buffered in anticipation of a future CHLO, and // allow CHLO packets to be buffered until next iteration of the event loop. @@ -348,3 +348,8 @@ QUIC_FLAG(bool, FLAGS_quic_reloadable_flag_quic_do_not_accept_stop_waiting, false) + +// If true, deprecate queued_control_frames_ from QuicPacketGenerator. +QUIC_FLAG(bool, + FLAGS_quic_reloadable_flag_quic_deprecate_queued_control_frames, + false)
diff --git a/net/socket/socket.cc b/net/socket/socket.cc index 39e3db0..09d619e 100644 --- a/net/socket/socket.cc +++ b/net/socket/socket.cc
@@ -8,9 +8,6 @@ namespace net { -const base::Feature Socket::kReadIfReadyExperiment{ - "SocketReadIfReady", base::FEATURE_ENABLED_BY_DEFAULT}; - int Socket::ReadIfReady(IOBuffer* buf, int buf_len, CompletionOnceCallback callback) {
diff --git a/net/socket/socket.h b/net/socket/socket.h index e834108..d0b91b7 100644 --- a/net/socket/socket.h +++ b/net/socket/socket.h
@@ -7,7 +7,6 @@ #include <stdint.h> -#include "base/feature_list.h" #include "net/base/completion_once_callback.h" #include "net/base/net_export.h" #include "net/traffic_annotation/network_traffic_annotation.h" @@ -19,9 +18,6 @@ // Represents a read/write socket. class NET_EXPORT Socket { public: - // Name of the field trial for using ReadyIfReady() instead of Read(). - static const base::Feature kReadIfReadyExperiment; - virtual ~Socket() {} // Reads data, up to |buf_len| bytes, from the socket. The number of bytes
diff --git a/net/socket/socket_bio_adapter.cc b/net/socket/socket_bio_adapter.cc index f7975b8a..6e24969 100644 --- a/net/socket/socket_bio_adapter.cc +++ b/net/socket/socket_bio_adapter.cc
@@ -9,7 +9,6 @@ #include <algorithm> #include "base/bind.h" -#include "base/feature_list.h" #include "base/location.h" #include "base/logging.h" #include "base/threading/thread_task_runner_handle.h" @@ -123,15 +122,12 @@ DCHECK(!read_buffer_); DCHECK_EQ(0, read_offset_); read_buffer_ = base::MakeRefCounted<IOBuffer>(read_buffer_capacity_); - int result = ERR_READ_IF_READY_NOT_IMPLEMENTED; - if (base::FeatureList::IsEnabled(Socket::kReadIfReadyExperiment)) { - result = socket_->ReadIfReady( - read_buffer_.get(), read_buffer_capacity_, - base::Bind(&SocketBIOAdapter::OnSocketReadIfReadyComplete, - weak_factory_.GetWeakPtr())); - if (result == ERR_IO_PENDING) - read_buffer_ = nullptr; - } + int result = socket_->ReadIfReady( + read_buffer_.get(), read_buffer_capacity_, + base::BindOnce(&SocketBIOAdapter::OnSocketReadIfReadyComplete, + weak_factory_.GetWeakPtr())); + if (result == ERR_IO_PENDING) + read_buffer_ = nullptr; if (result == ERR_READ_IF_READY_NOT_IMPLEMENTED) { result = socket_->Read(read_buffer_.get(), read_buffer_capacity_, read_callback_);
diff --git a/net/socket/socket_bio_adapter_unittest.cc b/net/socket/socket_bio_adapter_unittest.cc index 2a20df9d..f779c3a7 100644 --- a/net/socket/socket_bio_adapter_unittest.cc +++ b/net/socket/socket_bio_adapter_unittest.cc
@@ -13,7 +13,6 @@ #include "base/logging.h" #include "base/macros.h" #include "base/run_loop.h" -#include "base/test/scoped_feature_list.h" #include "crypto/openssl_util.h" #include "net/base/address_list.h" #include "net/base/completion_once_callback.h" @@ -31,12 +30,10 @@ namespace net { enum ReadIfReadySupport { - // ReadIfReady() field trial is enabled, and ReadyIfReady() is implemented. - READ_IF_READY_ENABLED_SUPPORTED, - // ReadIfReady() field trial is enabled, but ReadyIfReady() is unimplemented. - READ_IF_READY_ENABLED_NOT_SUPPORTED, - // ReadIfReady() field trial is disabled. - READ_IF_READY_DISABLED, + // ReadyIfReady() is implemented. + READ_IF_READY_SUPPORTED, + // ReadyIfReady() is unimplemented. + READ_IF_READY_NOT_SUPPORTED, }; class SocketBIOAdapterTest : public testing::TestWithParam<ReadIfReadySupport>, @@ -44,10 +41,7 @@ public WithScopedTaskEnvironment { protected: void SetUp() override { - if (GetParam() == READ_IF_READY_DISABLED) { - scoped_feature_list_.InitAndDisableFeature( - Socket::kReadIfReadyExperiment); - } else if (GetParam() == READ_IF_READY_ENABLED_SUPPORTED) { + if (GetParam() == READ_IF_READY_SUPPORTED) { factory_.set_enable_read_if_ready(true); } } @@ -161,14 +155,12 @@ bool expect_write_ready_ = false; MockClientSocketFactory factory_; std::unique_ptr<SocketBIOAdapter>* reset_on_write_ready_ = nullptr; - base::test::ScopedFeatureList scoped_feature_list_; }; INSTANTIATE_TEST_SUITE_P(/* no prefix */, SocketBIOAdapterTest, - testing::Values(READ_IF_READY_ENABLED_SUPPORTED, - READ_IF_READY_ENABLED_NOT_SUPPORTED, - READ_IF_READY_DISABLED)); + testing::Values(READ_IF_READY_SUPPORTED, + READ_IF_READY_NOT_SUPPORTED)); // Test that data can be read synchronously. TEST_P(SocketBIOAdapterTest, ReadSync) { @@ -234,7 +226,7 @@ // After waiting, the data is available if Read() is used. WaitForReadReady(); - if (GetParam() == READ_IF_READY_ENABLED_SUPPORTED) { + if (GetParam() == READ_IF_READY_SUPPORTED) { EXPECT_FALSE(adapter->HasPendingReadData()); } else { EXPECT_TRUE(adapter->HasPendingReadData()); @@ -256,7 +248,7 @@ // After waiting, the data is available if Read() is used. WaitForReadReady(); - if (GetParam() == READ_IF_READY_ENABLED_SUPPORTED) { + if (GetParam() == READ_IF_READY_SUPPORTED) { EXPECT_FALSE(adapter->HasPendingReadData()); } else { EXPECT_TRUE(adapter->HasPendingReadData());
diff --git a/net/socket/ssl_client_socket_unittest.cc b/net/socket/ssl_client_socket_unittest.cc index 465c3bf..d3de8be44 100644 --- a/net/socket/ssl_client_socket_unittest.cc +++ b/net/socket/ssl_client_socket_unittest.cc
@@ -8,6 +8,7 @@ #include <string.h> #include <algorithm> +#include <tuple> #include <utility> #include "base/bind.h" @@ -924,17 +925,100 @@ AddressList addr_; }; -// If GetParam(), try ReadIfReady() and fall back to Read() if needed. -class SSLClientSocketReadTest : public SSLClientSocketTest, - public ::testing::WithParamInterface<bool> { - protected: - SSLClientSocketReadTest() - : SSLClientSocketTest(), read_if_ready_enabled_(GetParam()) {} +enum ReadIfReadyTransport { + // ReadIfReady() is implemented by the underlying transport. + READ_IF_READY_SUPPORTED, + // ReadIfReady() is not implemented by the underlying transport. + READ_IF_READY_NOT_SUPPORTED, +}; - void SetUp() override { - if (!read_if_ready_enabled()) { - scoped_feature_list_.InitAndDisableFeature( - Socket::kReadIfReadyExperiment); +enum ReadIfReadySSL { + // Test reads by calling ReadIfReady() on the SSL socket. + TEST_SSL_READ_IF_READY, + // Test reads by calling Read() on the SSL socket. + TEST_SSL_READ, +}; + +class StreamSocketWithoutReadIfReady : public WrappedStreamSocket { + public: + explicit StreamSocketWithoutReadIfReady( + std::unique_ptr<StreamSocket> transport) + : WrappedStreamSocket(std::move(transport)) {} + + int ReadIfReady(IOBuffer* buf, + int buf_len, + CompletionOnceCallback callback) override { + return ERR_READ_IF_READY_NOT_IMPLEMENTED; + } + + int CancelReadIfReady() override { return ERR_READ_IF_READY_NOT_IMPLEMENTED; } +}; + +class ClientSocketFactoryWithoutReadIfReady : public ClientSocketFactory { + public: + explicit ClientSocketFactoryWithoutReadIfReady(ClientSocketFactory* factory) + : factory_(factory) {} + + std::unique_ptr<DatagramClientSocket> CreateDatagramClientSocket( + DatagramSocket::BindType bind_type, + NetLog* net_log, + const NetLogSource& source) override { + return factory_->CreateDatagramClientSocket(bind_type, net_log, source); + } + + std::unique_ptr<TransportClientSocket> CreateTransportClientSocket( + const AddressList& addresses, + std::unique_ptr<SocketPerformanceWatcher> socket_performance_watcher, + NetLog* net_log, + const NetLogSource& source) override { + return factory_->CreateTransportClientSocket( + addresses, std::move(socket_performance_watcher), net_log, source); + } + + std::unique_ptr<SSLClientSocket> CreateSSLClientSocket( + std::unique_ptr<StreamSocket> stream_socket, + const HostPortPair& host_and_port, + const SSLConfig& ssl_config, + const SSLClientSocketContext& context) override { + stream_socket = std::make_unique<StreamSocketWithoutReadIfReady>( + std::move(stream_socket)); + return factory_->CreateSSLClientSocket(std::move(stream_socket), + host_and_port, ssl_config, context); + } + + std::unique_ptr<ProxyClientSocket> CreateProxyClientSocket( + std::unique_ptr<StreamSocket> stream_socket, + const std::string& user_agent, + const HostPortPair& endpoint, + const ProxyServer& proxy_server, + HttpAuthController* http_auth_controller, + bool tunnel, + bool using_spdy, + NextProto negotiated_protocol, + ProxyDelegate* proxy_delegate, + const NetworkTrafficAnnotationTag& traffic_annotation) override { + return factory_->CreateProxyClientSocket( + std::move(stream_socket), user_agent, endpoint, proxy_server, + http_auth_controller, tunnel, using_spdy, negotiated_protocol, + proxy_delegate, traffic_annotation); + } + + private: + ClientSocketFactory* const factory_; +}; + +// If GetParam(), try ReadIfReady() and fall back to Read() if needed. +class SSLClientSocketReadTest + : public SSLClientSocketTest, + public ::testing::WithParamInterface< + std::tuple<ReadIfReadyTransport, ReadIfReadySSL>> { + protected: + SSLClientSocketReadTest() : SSLClientSocketTest() { + if (!read_if_ready_supported()) { + wrapped_socket_factory_ = + std::make_unique<ClientSocketFactoryWithoutReadIfReady>( + socket_factory_); + socket_factory_ = wrapped_socket_factory_.get(); } } @@ -944,7 +1028,7 @@ IOBuffer* buf, int buf_len, CompletionOnceCallback callback) { - if (read_if_ready_enabled()) + if (test_ssl_read_if_ready()) return socket->ReadIfReady(buf, buf_len, std::move(callback)); return socket->Read(buf, buf_len, std::move(callback)); } @@ -955,7 +1039,7 @@ int buf_len, TestCompletionCallback* callback, int rv) { - if (!read_if_ready_enabled()) + if (!test_ssl_read_if_ready()) return callback->GetResult(rv); while (rv == ERR_IO_PENDING) { rv = callback->GetResult(rv); @@ -975,16 +1059,24 @@ return WaitForReadCompletion(socket, buf, buf_len, &callback, rv); } - bool read_if_ready_enabled() const { return read_if_ready_enabled_; } + bool test_ssl_read_if_ready() const { + return std::get<1>(GetParam()) == TEST_SSL_READ_IF_READY; + } + + bool read_if_ready_supported() const { + return std::get<0>(GetParam()) == READ_IF_READY_SUPPORTED; + } private: - base::test::ScopedFeatureList scoped_feature_list_; - const bool read_if_ready_enabled_; + std::unique_ptr<ClientSocketFactory> wrapped_socket_factory_; }; -INSTANTIATE_TEST_SUITE_P(/* no prefix */, - SSLClientSocketReadTest, - ::testing::Bool()); +INSTANTIATE_TEST_SUITE_P( + /* no prefix */, + SSLClientSocketReadTest, + ::testing::Combine( + ::testing::Values(READ_IF_READY_SUPPORTED, READ_IF_READY_NOT_SUPPORTED), + ::testing::Values(TEST_SSL_READ_IF_READY, TEST_SSL_READ))); // Verifies the correctness of GetSSLCertRequestInfo. class SSLClientSocketCertRequestInfoTest : public SSLClientSocketTest { @@ -1941,7 +2033,7 @@ // |read_callback| deletes |sock| so if ReadIfReady() is used, we will get OK // asynchronously but can't continue reading because the socket is gone. rv = read_callback.WaitForResult(); - if (read_if_ready_enabled()) { + if (test_ssl_read_if_ready()) { EXPECT_THAT(rv, IsOk()); } else { EXPECT_THAT(rv, IsError(ERR_CONNECTION_RESET)); @@ -4697,7 +4789,7 @@ StreamSocket::SocketMemoryStats stats2; sock_->DumpMemoryStats(&stats2); - if (read_if_ready_enabled()) { + if (read_if_ready_supported()) { EXPECT_EQ(0u, stats2.buffer_size); EXPECT_EQ(stats.cert_size, stats2.total_size); } else {
diff --git a/net/socket/udp_socket_unittest.cc b/net/socket/udp_socket_unittest.cc index 17f7d11..efb36062 100644 --- a/net/socket/udp_socket_unittest.cc +++ b/net/socket/udp_socket_unittest.cc
@@ -316,7 +316,7 @@ // - Android: devices attached to testbots don't have default network, so // broadcasting to 255.255.255.255 returns error -109 (Address not reachable). // crbug.com/139144. -// - Fuchsia: TODO(fuchsia): broadcast support is not implemented yet. +// - Fuchsia: TODO(crbug.com/959314): broadcast support is not implemented yet. #define MAYBE_LocalBroadcast DISABLED_LocalBroadcast #else #define MAYBE_LocalBroadcast LocalBroadcast @@ -410,14 +410,7 @@ } } -#if defined(OS_FUCHSIA) -// Currently the test fails on Fuchsia because netstack allows to connect IPv4 -// socket to IPv6 address. This issue is tracked by NET-596. -#define MAYBE_ConnectFail DISABLED_ConnectFail -#else -#define MAYBE_ConnectFail ConnectFail -#endif -TEST_F(UDPSocketTest, MAYBE_ConnectFail) { +TEST_F(UDPSocketTest, ConnectFail) { UDPSocket socket(DatagramSocket::DEFAULT_BIND, nullptr, NetLogSource()); EXPECT_THAT(socket.Open(ADDRESS_FAMILY_IPV4), IsOk()); @@ -566,13 +559,7 @@ EXPECT_EQ(rv, ERR_SOCKET_NOT_CONNECTED); } -#if defined(OS_FUCHSIA) -// TODO(crbug.com/945590): Re-enable after the breaking SDK change has landed. -#define MAYBE_ClientSetDoNotFragment DISABLED_ClientSetDoNotFragment -#else -#define MAYBE_ClientSetDoNotFragment ClientSetDoNotFragment -#endif -TEST_F(UDPSocketTest, MAYBE_ClientSetDoNotFragment) { +TEST_F(UDPSocketTest, ClientSetDoNotFragment) { for (std::string ip : {"127.0.0.1", "::1"}) { UDPClientSocket client(DatagramSocket::DEFAULT_BIND, nullptr, NetLogSource()); @@ -595,13 +582,7 @@ } } -#if defined(OS_FUCHSIA) -// TODO(crbug.com/945590): Re-enable after the breaking SDK change has landed. -#define MAYBE_ServerSetDoNotFragment DISABLED_ServerSetDoNotFragment -#else -#define MAYBE_ServerSetDoNotFragment ServerSetDoNotFragment -#endif -TEST_F(UDPSocketTest, MAYBE_ServerSetDoNotFragment) { +TEST_F(UDPSocketTest, ServerSetDoNotFragment) { for (std::string ip : {"127.0.0.1", "::1"}) { IPEndPoint bind_address; ASSERT_TRUE(CreateUDPAddress(ip, 0, &bind_address)); @@ -658,17 +639,6 @@ UDPSocket socket(DatagramSocket::DEFAULT_BIND, nullptr, NetLogSource()); EXPECT_THAT(socket.Open(bind_address.GetFamily()), IsOk()); -#if defined(OS_FUCHSIA) - // Fuchsia currently doesn't support automatic interface selection for - // multicast, so interface index needs to be set explicitly. - // See https://fuchsia.atlassian.net/browse/NET-195 . - NetworkInterfaceList interfaces; - ASSERT_TRUE(GetNetworkList(&interfaces, 0)); - ASSERT_FALSE(interfaces.empty()); - EXPECT_THAT(socket.SetMulticastInterface(interfaces[0].interface_index), - IsOk()); -#endif // defined(OS_FUCHSIA) - EXPECT_THAT(socket.Bind(bind_address), IsOk()); EXPECT_THAT(socket.JoinGroup(group_ip), IsOk()); // Joining group multiple times.
diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc index de872e5..78286cc 100644 --- a/net/spdy/spdy_session.cc +++ b/net/spdy/spdy_session.cc
@@ -11,7 +11,6 @@ #include <utility> #include "base/bind.h" -#include "base/feature_list.h" #include "base/location.h" #include "base/logging.h" #include "base/metrics/histogram_functions.h" @@ -2239,25 +2238,22 @@ CHECK(socket_); read_state_ = READ_STATE_DO_READ_COMPLETE; - int rv = ERR_READ_IF_READY_NOT_IMPLEMENTED; read_buffer_ = base::MakeRefCounted<IOBuffer>(kReadBufferSize); - if (base::FeatureList::IsEnabled(Socket::kReadIfReadyExperiment)) { - rv = socket_->ReadIfReady( - read_buffer_.get(), kReadBufferSize, - base::Bind(&SpdySession::PumpReadLoop, weak_factory_.GetWeakPtr(), - READ_STATE_DO_READ)); - if (rv == ERR_IO_PENDING) { - read_buffer_ = nullptr; - read_state_ = READ_STATE_DO_READ; - return rv; - } + int rv = socket_->ReadIfReady( + read_buffer_.get(), kReadBufferSize, + base::BindOnce(&SpdySession::PumpReadLoop, weak_factory_.GetWeakPtr(), + READ_STATE_DO_READ)); + if (rv == ERR_IO_PENDING) { + read_buffer_ = nullptr; + read_state_ = READ_STATE_DO_READ; + return rv; } if (rv == ERR_READ_IF_READY_NOT_IMPLEMENTED) { // Fallback to regular Read(). return socket_->Read( read_buffer_.get(), kReadBufferSize, - base::Bind(&SpdySession::PumpReadLoop, weak_factory_.GetWeakPtr(), - READ_STATE_DO_READ_COMPLETE)); + base::BindOnce(&SpdySession::PumpReadLoop, weak_factory_.GetWeakPtr(), + READ_STATE_DO_READ_COMPLETE)); } return rv; }
diff --git a/net/spdy/spdy_session_unittest.cc b/net/spdy/spdy_session_unittest.cc index 314491a2..baabba7 100644 --- a/net/spdy/spdy_session_unittest.cc +++ b/net/spdy/spdy_session_unittest.cc
@@ -6109,12 +6109,10 @@ } enum ReadIfReadySupport { - // ReadIfReady() field trial is enabled, and ReadIfReady() is implemented. - READ_IF_READY_ENABLED_SUPPORTED, - // ReadIfReady() field trial is enabled, but ReadIfReady() is unimplemented. - READ_IF_READY_ENABLED_NOT_SUPPORTED, - // ReadIfReady() field trial is disabled. - READ_IF_READY_DISABLED, + // ReadIfReady() is implemented by the underlying transport. + READ_IF_READY_SUPPORTED, + // ReadIfReady() is unimplemented by the underlying transport. + READ_IF_READY_NOT_SUPPORTED, }; class SpdySessionReadIfReadyTest @@ -6122,24 +6120,17 @@ public testing::WithParamInterface<ReadIfReadySupport> { public: void SetUp() override { - if (GetParam() == READ_IF_READY_DISABLED) { - scoped_feature_list_.InitAndDisableFeature( - Socket::kReadIfReadyExperiment); - } else if (GetParam() == READ_IF_READY_ENABLED_SUPPORTED) { + if (GetParam() == READ_IF_READY_SUPPORTED) { session_deps_.socket_factory->set_enable_read_if_ready(true); } SpdySessionTest::SetUp(); } - - private: - base::test::ScopedFeatureList scoped_feature_list_; }; INSTANTIATE_TEST_SUITE_P(/* no prefix */, SpdySessionReadIfReadyTest, - testing::Values(READ_IF_READY_ENABLED_SUPPORTED, - READ_IF_READY_ENABLED_NOT_SUPPORTED, - READ_IF_READY_DISABLED)); + testing::Values(READ_IF_READY_SUPPORTED, + READ_IF_READY_NOT_SUPPORTED)); // Tests basic functionality of ReadIfReady() when it is enabled or disabled. TEST_P(SpdySessionReadIfReadyTest, ReadIfReady) {
diff --git a/net/third_party/quiche/OWNERS b/net/third_party/quiche/OWNERS new file mode 100644 index 0000000..e2c377d2 --- /dev/null +++ b/net/third_party/quiche/OWNERS
@@ -0,0 +1,3 @@ +file://net/quic/OWNERS + +# COMPONENT: Internals>Network>QUIC
diff --git a/services/metrics/public/cpp/ukm_recorder.h b/services/metrics/public/cpp/ukm_recorder.h index 3a52ab7f..130e53a3 100644 --- a/services/metrics/public/cpp/ukm_recorder.h +++ b/services/metrics/public/cpp/ukm_recorder.h
@@ -66,6 +66,7 @@ class DelegatingUkmRecorder; class TestRecordingHelper; +class UkmBackgroundRecorderService; namespace internal { class SourceUrlRecorderWebContentsObserver; @@ -111,6 +112,7 @@ friend PlatformNotificationServiceImpl; friend PluginInfoHostImpl; friend TestRecordingHelper; + friend UkmBackgroundRecorderService; friend autofill::TestAutofillClient; friend blink::Document; friend cc::UkmManager;
diff --git a/services/resource_coordinator/memory_instrumentation/queued_request_dispatcher.cc b/services/resource_coordinator/memory_instrumentation/queued_request_dispatcher.cc index 71bb61cf..08a8d27 100644 --- a/services/resource_coordinator/memory_instrumentation/queued_request_dispatcher.cc +++ b/services/resource_coordinator/memory_instrumentation/queued_request_dispatcher.cc
@@ -147,6 +147,7 @@ mojom::OSMemDumpPtr os_dump = mojom::OSMemDump::New(); os_dump->resident_set_kb = internal_os_dump.resident_set_kb; + os_dump->peak_resident_set_kb = internal_os_dump.peak_resident_set_kb; os_dump->private_footprint_kb = CalculatePrivateFootprintKb(internal_os_dump, shared_resident_kb); #if defined(OS_LINUX) || defined(OS_ANDROID)
diff --git a/services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics.h b/services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics.h index c4c2b00..58944b0c 100644 --- a/services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics.h +++ b/services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics.h
@@ -65,6 +65,9 @@ const size_t end_address, std::vector<uint8_t>* accessed_pages_bitmap); + // TODO(chiniforooshan): move to /base/process/process_metrics_linux.cc after + // making sure that peak RSS is useful. + static size_t GetPeakResidentSetSize(base::ProcessId pid); #endif // defined(OS_LINUX) || defined(OS_ANDROID) };
diff --git a/services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics_linux.cc b/services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics_linux.cc index a5b0773..89d9835 100644 --- a/services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics_linux.cc +++ b/services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics_linux.cc
@@ -10,12 +10,15 @@ #include "base/android/library_loader/anchor_functions.h" #include "base/android/library_loader/anchor_functions_buildflags.h" #include "base/debug/elf_reader.h" +#include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/files/scoped_file.h" #include "base/format_macros.h" #include "base/process/process_metrics.h" #include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" #include "base/strings/string_util.h" +#include "base/threading/thread_restrictions.h" #include "build/build_config.h" #include "services/resource_coordinator/public/cpp/memory_instrumentation/os_metrics.h" @@ -31,13 +34,13 @@ const uint32_t kMaxLineSize = 4096; -base::ScopedFD OpenStatm(base::ProcessId pid) { - std::string name = - "/proc/" + - (pid == base::kNullProcessId ? "self" : base::NumberToString(pid)) + - "/statm"; - base::ScopedFD fd = base::ScopedFD(open(name.c_str(), O_RDONLY)); - return fd; +// TODO(chiniforooshan): Many of the utility functions in this anonymous +// namespace should move to base/process/process_metrics_linux.cc to make the +// code a lot cleaner. However, we should do so after we made sure the metrics +// we are experimenting with here have real value. +base::FilePath GetProcPidDir(base::ProcessId pid) { + return base::FilePath("/proc").Append( + pid == base::kNullProcessId ? "self" : base::NumberToString(pid)); } bool GetResidentAndSharedPagesFromStatmFile(int fd, @@ -220,7 +223,10 @@ // static bool OSMetrics::FillOSMemoryDump(base::ProcessId pid, mojom::RawOSMemDump* dump) { - base::ScopedFD autoclose = OpenStatm(pid); + // TODO(chiniforooshan): There is no need to read both /statm and /status + // files. Refactor to get everything from /status using ProcessMetric. + auto statm_file = GetProcPidDir(pid).Append("statm"); + auto autoclose = base::ScopedFD(open(statm_file.value().c_str(), O_RDONLY)); int statm_fd = autoclose.get(); if (statm_fd == -1) @@ -243,6 +249,7 @@ dump->platform_private_footprint->rss_anon_bytes = rss_anon_bytes; dump->platform_private_footprint->vm_swap_bytes = vm_swap_bytes; dump->resident_set_kb = process_metrics->GetResidentSetSize() / 1024; + dump->peak_resident_set_kb = GetPeakResidentSetSize(pid); #if defined(OS_ANDROID) #if BUILDFLAG(SUPPORTS_CODE_ORDERING) @@ -338,4 +345,38 @@ return OSMetrics::MappedAndResidentPagesDumpState::kSuccess; } +// static +size_t OSMetrics::GetPeakResidentSetSize(base::ProcessId pid) { + std::string data; + { + // Synchronously reading files in /proc does not hit the disk. + base::ScopedAllowBlocking allow_blocking; + if (!base::ReadFileToString(GetProcPidDir(pid).Append("status"), &data)) + return 0; + } + base::StringPairs pairs; + base::SplitStringIntoKeyValuePairs(data, ':', '\n', &pairs); + for (auto& pair : pairs) { + base::TrimWhitespaceASCII(pair.first, base::TRIM_ALL, &pair.first); + // VmHWM gives the peak resident set size since the start of the process or + // since the last time it was reset. HWM stands for "High Water Mark". + if (pair.first == "VmHWM") { + base::TrimWhitespaceASCII(pair.second, base::TRIM_ALL, &pair.second); + auto split_value_str = base::SplitStringPiece( + pair.second, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + if (split_value_str.size() != 2 || split_value_str[1] != "kB") { + NOTREACHED(); + return 0; + } + size_t res; + if (!base::StringToSizeT(split_value_str[0], &res)) { + NOTREACHED(); + return 0; + } + return res; + } + } + return 0; +} + } // namespace memory_instrumentation
diff --git a/services/resource_coordinator/public/cpp/memory_instrumentation/tracing_observer.cc b/services/resource_coordinator/public/cpp/memory_instrumentation/tracing_observer.cc index 981126a..914cc17 100644 --- a/services/resource_coordinator/public/cpp/memory_instrumentation/tracing_observer.cc +++ b/services/resource_coordinator/public/cpp/memory_instrumentation/tracing_observer.cc
@@ -31,6 +31,11 @@ base::StringPrintf( "%" PRIx64, static_cast<uint64_t>(os_dump.private_footprint_kb) * 1024)); + value->SetString( + "peak_resident_set_size", + base::StringPrintf( + "%" PRIx64, + static_cast<uint64_t>(os_dump.peak_resident_set_kb) * 1024)); } std::string ApplyPathFiltering(const std::string& file,
diff --git a/services/resource_coordinator/public/mojom/memory_instrumentation/memory_instrumentation.mojom b/services/resource_coordinator/public/mojom/memory_instrumentation/memory_instrumentation.mojom index aab6920..9cb28cc 100644 --- a/services/resource_coordinator/public/mojom/memory_instrumentation/memory_instrumentation.mojom +++ b/services/resource_coordinator/public/mojom/memory_instrumentation/memory_instrumentation.mojom
@@ -139,6 +139,9 @@ struct RawOSMemDump { uint32 resident_set_kb = 0; + // peak_resident_set_kb is an experimental field. Please do not use it until + // crbug.com/957978 reaches to a conclusion. + uint32 peak_resident_set_kb = 0; PlatformPrivateFootprint platform_private_footprint; array<VmRegion> memory_maps; @@ -152,6 +155,11 @@ struct OSMemDump { uint32 resident_set_kb = 0; + // The peak resident set size as observed by the kernel. This is currently + // reported on Linux and Android only. Also, this is an experimental field. + // Please do not use it until crbug.com/957978 reaches to a conclusion. + uint32 peak_resident_set_kb = 0; + // This is roughly private, anonymous, non-discardable, resident or swapped // memory in kilobytes. For more details, see https://goo.gl/3kPb9S. uint32 private_footprint_kb = 0;
diff --git a/services/service_manager/embedder/main.cc b/services/service_manager/embedder/main.cc index 2f1c180..cd12986 100644 --- a/services/service_manager/embedder/main.cc +++ b/services/service_manager/embedder/main.cc
@@ -46,6 +46,7 @@ #include <windows.h> #include "base/win/process_startup_helper.h" +#include "base/win/win_util.h" #include "ui/base/win/atl_module.h" #endif @@ -132,9 +133,11 @@ // HACK: Let Windows know that we have started. This is needed to suppress // the IDC_APPSTARTING cursor from being displayed for a prolonged period // while a subprocess is starting. - PostThreadMessage(GetCurrentThreadId(), WM_NULL, 0, 0); - MSG msg; - PeekMessage(&msg, NULL, 0, 0, PM_REMOVE); + if (base::win::IsUser32AndGdi32Available()) { + PostThreadMessage(GetCurrentThreadId(), WM_NULL, 0, 0); + MSG msg; + PeekMessage(&msg, NULL, 0, 0, PM_REMOVE); + } #endif #if !defined(OFFICIAL_BUILD) && defined(OS_WIN)
diff --git a/services/service_manager/sandbox/mac/gpu.sb b/services/service_manager/sandbox/mac/gpu.sb index 6b672a8..c02bbd5 100644 --- a/services/service_manager/sandbox/mac/gpu.sb +++ b/services/service_manager/sandbox/mac/gpu.sb
@@ -91,5 +91,9 @@ ; Needed for VideoToolbox usage - https://crbug.com/767037 (allow mach-lookup (global-name "com.apple.coremedia.videodecoder")) +; Needed for 10.14.5+ - https://crbug.com/957217 +(if (defined? 'xpc-service-name) + (allow mach-lookup (xpc-service-name "com.apple.MTLCompilerService"))) + ; Needed for GPU process to fallback to SwiftShader - https://crbug.com/897914 (allow file-read-data file-read-metadata (subpath (param bundle-version-path)))
diff --git a/services/service_manager/sandbox/mac/gpu_v2.sb b/services/service_manager/sandbox/mac/gpu_v2.sb index 3db67c8..af09bab8 100644 --- a/services/service_manager/sandbox/mac/gpu_v2.sb +++ b/services/service_manager/sandbox/mac/gpu_v2.sb
@@ -22,6 +22,11 @@ (global-name "com.apple.windowserver.active") ) +; Needed for metal decoding - https://crbug.com/957217 +(if (>= os-version 1014) + (allow mach-lookup (xpc-service-name "com.apple.MTLCompilerService")) +) + ; Needed for WebGL - https://crbug.com/75343 (allow iokit-open (iokit-connection "IOAccelerator")
diff --git a/services/service_manager/sandbox/mac/renderer.sb b/services/service_manager/sandbox/mac/renderer.sb index 4c7f572..47c38ba0 100644 --- a/services/service_manager/sandbox/mac/renderer.sb +++ b/services/service_manager/sandbox/mac/renderer.sb
@@ -92,3 +92,9 @@ (iokit-property "Removable") (iokit-property "image-encrypted") )) + +; For V8 to use in thread calculations. +(if (>= os-version 1014) + (allow sysctl-read (sysctl-name "kern.tcsm_available")) + (allow sysctl-write (sysctl-name "kern.tcsm_enable")) +)
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json index 1557c633..c356e48 100644 --- a/testing/buildbot/chromium.android.json +++ b/testing/buildbot/chromium.android.json
@@ -1026,6 +1026,49 @@ "--bucket", "chromium-result-details", "--test-name", + "breakpad_unittests" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "swarming": { + "can_use_on_swarming_builders": true, + "cipd_packages": [ + { + "cipd_package": "infra/tools/luci/logdog/butler/${platform}", + "location": "bin", + "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c" + } + ], + "dimension_sets": [ + { + "device_os": "KTU84P", + "device_type": "hammerhead", + "os": "Android" + } + ], + "output_links": [ + { + "link": [ + "https://luci-logdog.appspot.com/v/?s", + "=android%2Fswarming%2Flogcats%2F", + "${TASK_ID}%2F%2B%2Funified_logcats" + ], + "name": "shard #${SHARD_INDEX} logcats" + } + ] + }, + "test": "breakpad_unittests" + }, + { + "args": [ + "--gs-results-bucket=chromium-result-details", + "--recover-devices" + ], + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", "cacheinvalidation_unittests" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py" @@ -3579,6 +3622,50 @@ "--bucket", "chromium-result-details", "--test-name", + "breakpad_unittests" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "swarming": { + "can_use_on_swarming_builders": true, + "cipd_packages": [ + { + "cipd_package": "infra/tools/luci/logdog/butler/${platform}", + "location": "bin", + "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c" + } + ], + "dimension_sets": [ + { + "device_os": "KTU84Z", + "device_type": "flo", + "os": "Android" + } + ], + "expiration": 10800, + "output_links": [ + { + "link": [ + "https://luci-logdog.appspot.com/v/?s", + "=android%2Fswarming%2Flogcats%2F", + "${TASK_ID}%2F%2B%2Funified_logcats" + ], + "name": "shard #${SHARD_INDEX} logcats" + } + ] + }, + "test": "breakpad_unittests" + }, + { + "args": [ + "--gs-results-bucket=chromium-result-details", + "--recover-devices" + ], + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", "cacheinvalidation_unittests" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py" @@ -6148,6 +6235,50 @@ "--bucket", "chromium-result-details", "--test-name", + "breakpad_unittests" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "swarming": { + "can_use_on_swarming_builders": true, + "cipd_packages": [ + { + "cipd_package": "infra/tools/luci/logdog/butler/${platform}", + "location": "bin", + "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c" + } + ], + "dimension_sets": [ + { + "device_os": "LMY48I", + "device_type": "hammerhead", + "os": "Android" + } + ], + "expiration": 10800, + "output_links": [ + { + "link": [ + "https://luci-logdog.appspot.com/v/?s", + "=android%2Fswarming%2Flogcats%2F", + "${TASK_ID}%2F%2B%2Funified_logcats" + ], + "name": "shard #${SHARD_INDEX} logcats" + } + ] + }, + "test": "breakpad_unittests" + }, + { + "args": [ + "--gs-results-bucket=chromium-result-details", + "--recover-devices" + ], + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", "cacheinvalidation_unittests" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py" @@ -8905,6 +9036,50 @@ "--bucket", "chromium-result-details", "--test-name", + "breakpad_unittests" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "swarming": { + "can_use_on_swarming_builders": true, + "cipd_packages": [ + { + "cipd_package": "infra/tools/luci/logdog/butler/${platform}", + "location": "bin", + "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c" + } + ], + "dimension_sets": [ + { + "device_os": "LMY49B", + "device_type": "flo", + "os": "Android" + } + ], + "expiration": 10800, + "output_links": [ + { + "link": [ + "https://luci-logdog.appspot.com/v/?s", + "=android%2Fswarming%2Flogcats%2F", + "${TASK_ID}%2F%2B%2Funified_logcats" + ], + "name": "shard #${SHARD_INDEX} logcats" + } + ] + }, + "test": "breakpad_unittests" + }, + { + "args": [ + "--gs-results-bucket=chromium-result-details", + "--recover-devices" + ], + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", "cacheinvalidation_unittests" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py" @@ -11511,6 +11686,49 @@ "--bucket", "chromium-result-details", "--test-name", + "breakpad_unittests" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "swarming": { + "can_use_on_swarming_builders": true, + "cipd_packages": [ + { + "cipd_package": "infra/tools/luci/logdog/butler/${platform}", + "location": "bin", + "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c" + } + ], + "dimension_sets": [ + { + "device_os": "MMB29Q", + "device_type": "bullhead", + "os": "Android" + } + ], + "output_links": [ + { + "link": [ + "https://luci-logdog.appspot.com/v/?s", + "=android%2Fswarming%2Flogcats%2F", + "${TASK_ID}%2F%2B%2Funified_logcats" + ], + "name": "shard #${SHARD_INDEX} logcats" + } + ] + }, + "test": "breakpad_unittests" + }, + { + "args": [ + "--gs-results-bucket=chromium-result-details", + "--recover-devices" + ], + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", "cacheinvalidation_unittests" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py" @@ -14216,6 +14434,50 @@ "--bucket", "chromium-result-details", "--test-name", + "breakpad_unittests" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "swarming": { + "can_use_on_swarming_builders": true, + "cipd_packages": [ + { + "cipd_package": "infra/tools/luci/logdog/butler/${platform}", + "location": "bin", + "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c" + } + ], + "dimension_sets": [ + { + "device_os": "MRA58Z", + "device_type": "flo", + "os": "Android" + } + ], + "expiration": 10800, + "output_links": [ + { + "link": [ + "https://luci-logdog.appspot.com/v/?s", + "=android%2Fswarming%2Flogcats%2F", + "${TASK_ID}%2F%2B%2Funified_logcats" + ], + "name": "shard #${SHARD_INDEX} logcats" + } + ] + }, + "test": "breakpad_unittests" + }, + { + "args": [ + "--gs-results-bucket=chromium-result-details", + "--recover-devices" + ], + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", "cacheinvalidation_unittests" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py" @@ -18830,6 +19092,49 @@ "--bucket", "chromium-result-details", "--test-name", + "breakpad_unittests" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "swarming": { + "can_use_on_swarming_builders": true, + "cipd_packages": [ + { + "cipd_package": "infra/tools/luci/logdog/butler/${platform}", + "location": "bin", + "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c" + } + ], + "dimension_sets": [ + { + "device_os": "KTU84P", + "device_type": "hammerhead", + "os": "Android" + } + ], + "output_links": [ + { + "link": [ + "https://luci-logdog.appspot.com/v/?s", + "=android%2Fswarming%2Flogcats%2F", + "${TASK_ID}%2F%2B%2Funified_logcats" + ], + "name": "shard #${SHARD_INDEX} logcats" + } + ] + }, + "test": "breakpad_unittests" + }, + { + "args": [ + "--gs-results-bucket=chromium-result-details", + "--recover-devices" + ], + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", "cacheinvalidation_unittests" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py" @@ -21471,6 +21776,49 @@ "--bucket", "chromium-result-details", "--test-name", + "breakpad_unittests" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "swarming": { + "can_use_on_swarming_builders": true, + "cipd_packages": [ + { + "cipd_package": "infra/tools/luci/logdog/butler/${platform}", + "location": "bin", + "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c" + } + ], + "dimension_sets": [ + { + "device_os": "MMB29Q", + "device_type": "bullhead", + "os": "Android" + } + ], + "output_links": [ + { + "link": [ + "https://luci-logdog.appspot.com/v/?s", + "=android%2Fswarming%2Flogcats%2F", + "${TASK_ID}%2F%2B%2Funified_logcats" + ], + "name": "shard #${SHARD_INDEX} logcats" + } + ] + }, + "test": "breakpad_unittests" + }, + { + "args": [ + "--gs-results-bucket=chromium-result-details", + "--recover-devices" + ], + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", "cacheinvalidation_unittests" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
diff --git a/testing/buildbot/chromium.chrome.json b/testing/buildbot/chromium.chrome.json index 4a269ef..253f7e6 100644 --- a/testing/buildbot/chromium.chrome.json +++ b/testing/buildbot/chromium.chrome.json
@@ -271,23 +271,6 @@ "os": "Ubuntu-14.04", "pool": "chrome.tests" } - ] - }, - "test": "breakpad_unittests" - }, - { - "experiment_percentage": 100, - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04", - "pool": "chrome.tests" - } ], "shards": 10 },
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json index d475427..8c0f7b2 100644 --- a/testing/buildbot/chromium.chromiumos.json +++ b/testing/buildbot/chromium.chromiumos.json
@@ -818,21 +818,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 21 }, @@ -2422,21 +2407,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 10 },
diff --git a/testing/buildbot/chromium.clang.json b/testing/buildbot/chromium.clang.json index 95671653..c33fe87 100644 --- a/testing/buildbot/chromium.clang.json +++ b/testing/buildbot/chromium.clang.json
@@ -195,21 +195,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 10 }, @@ -4832,6 +4817,49 @@ "--bucket", "chromium-result-details", "--test-name", + "breakpad_unittests" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "swarming": { + "can_use_on_swarming_builders": true, + "cipd_packages": [ + { + "cipd_package": "infra/tools/luci/logdog/butler/${platform}", + "location": "bin", + "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c" + } + ], + "dimension_sets": [ + { + "device_os": "KTU84P", + "device_type": "hammerhead", + "os": "Android" + } + ], + "output_links": [ + { + "link": [ + "https://luci-logdog.appspot.com/v/?s", + "=android%2Fswarming%2Flogcats%2F", + "${TASK_ID}%2F%2B%2Funified_logcats" + ], + "name": "shard #${SHARD_INDEX} logcats" + } + ] + }, + "test": "breakpad_unittests" + }, + { + "args": [ + "--gs-results-bucket=chromium-result-details", + "--recover-devices" + ], + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", "cacheinvalidation_unittests" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py" @@ -7452,6 +7480,49 @@ "--bucket", "chromium-result-details", "--test-name", + "breakpad_unittests" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "swarming": { + "can_use_on_swarming_builders": true, + "cipd_packages": [ + { + "cipd_package": "infra/tools/luci/logdog/butler/${platform}", + "location": "bin", + "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c" + } + ], + "dimension_sets": [ + { + "device_os": "MMB29Q", + "device_type": "bullhead", + "os": "Android" + } + ], + "output_links": [ + { + "link": [ + "https://luci-logdog.appspot.com/v/?s", + "=android%2Fswarming%2Flogcats%2F", + "${TASK_ID}%2F%2B%2Funified_logcats" + ], + "name": "shard #${SHARD_INDEX} logcats" + } + ] + }, + "test": "breakpad_unittests" + }, + { + "args": [ + "--gs-results-bucket=chromium-result-details", + "--recover-devices" + ], + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", "cacheinvalidation_unittests" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py" @@ -9805,21 +9876,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 10 }, @@ -11219,24 +11275,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "args": [ - "--test-launcher-print-test-stdio=always" - ], - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 10 }, @@ -12791,21 +12829,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 10 }, @@ -14143,21 +14166,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 10 }, @@ -15503,21 +15511,6 @@ "test": "boringssl_ssl_tests" }, { - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } - ] - }, - "test": "breakpad_unittests" - }, - { "args": [ "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0", "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter" @@ -16866,21 +16859,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 10 }, @@ -18236,21 +18214,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 10 }, @@ -35325,21 +35288,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 10 },
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json index bab4392..20375d7 100644 --- a/testing/buildbot/chromium.fyi.json +++ b/testing/buildbot/chromium.fyi.json
@@ -8941,22 +8941,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "isolate_coverage_data": true, - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 21 }, @@ -10540,22 +10524,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "isolate_coverage_data": true, - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 21 }, @@ -12103,21 +12071,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 10 }, @@ -13717,22 +13670,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "isolate_coverage_data": true, - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 10 }, @@ -17854,21 +17791,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 10 },
diff --git a/testing/buildbot/chromium.linux.json b/testing/buildbot/chromium.linux.json index 29ed2d0..229944d 100644 --- a/testing/buildbot/chromium.linux.json +++ b/testing/buildbot/chromium.linux.json
@@ -2267,21 +2267,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 10 }, @@ -3882,21 +3867,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 10 }, @@ -5456,21 +5426,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 10 }, @@ -6961,22 +6916,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "isolate_coverage_data": true, - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 10 }, @@ -8740,21 +8679,6 @@ { "os": "Ubuntu-16.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-16.04" - } ], "shards": 10 },
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json index a7f9770..f13bd1e 100644 --- a/testing/buildbot/chromium.memory.json +++ b/testing/buildbot/chromium.memory.json
@@ -402,6 +402,49 @@ "--bucket", "chromium-result-details", "--test-name", + "breakpad_unittests" + ], + "script": "//build/android/pylib/results/presentation/test_results_presentation.py" + }, + "swarming": { + "can_use_on_swarming_builders": true, + "cipd_packages": [ + { + "cipd_package": "infra/tools/luci/logdog/butler/${platform}", + "location": "bin", + "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c" + } + ], + "dimension_sets": [ + { + "device_os": "MMB29Q", + "device_type": "bullhead", + "os": "Android" + } + ], + "output_links": [ + { + "link": [ + "https://luci-logdog.appspot.com/v/?s", + "=android%2Fswarming%2Flogcats%2F", + "${TASK_ID}%2F%2B%2Funified_logcats" + ], + "name": "shard #${SHARD_INDEX} logcats" + } + ] + }, + "test": "breakpad_unittests" + }, + { + "args": [ + "--gs-results-bucket=chromium-result-details", + "--recover-devices" + ], + "merge": { + "args": [ + "--bucket", + "chromium-result-details", + "--test-name", "cacheinvalidation_unittests" ], "script": "//build/android/pylib/results/presentation/test_results_presentation.py" @@ -2837,24 +2880,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "args": [ - "--test-launcher-print-test-stdio=always" - ], - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 30 }, @@ -4488,21 +4513,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 10 }, @@ -5938,24 +5948,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "args": [ - "--test-launcher-print-test-stdio=always" - ], - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 31 }, @@ -7712,25 +7704,6 @@ "cpu": "x86-64", "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "args": [ - "--test-launcher-print-test-stdio=always" - ], - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "cpu": "x86-64", - "os": "Ubuntu-14.04" - } ], "shards": 25 }, @@ -9510,25 +9483,6 @@ "cpu": "x86-64", "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "args": [ - "--test-launcher-print-test-stdio=always" - ], - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "cpu": "x86-64", - "os": "Ubuntu-14.04" - } ], "shards": 10 }, @@ -11156,24 +11110,6 @@ }, { "args": [ - "--test-launcher-print-test-stdio=always" - ], - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } - ] - }, - "test": "breakpad_unittests" - }, - { - "args": [ "--disable-blink-features=HTMLImports,ShadowDOMV0,CustomElementsV0", "--test-launcher-filter-file=../../testing/buildbot/filters/webui_html_imports_polyfill_browser_tests.filter", "--test-launcher-print-test-stdio=always"
diff --git a/testing/buildbot/chromium.perf.json b/testing/buildbot/chromium.perf.json index 4c954cfe..0ea25eb 100644 --- a/testing/buildbot/chromium.perf.json +++ b/testing/buildbot/chromium.perf.json
@@ -653,6 +653,104 @@ "microdump_stackwalk", "angle_perftests", "chrome_apk" + ], + "isolated_scripts": [ + { + "args": [], + "isolate_name": "resource_sizes_chrome_modern_public_minimal_apks", + "merge": { + "script": "//tools/perf/process_perf_results.py" + }, + "name": "resource_sizes_chrome_modern_public_minimal_apks", + "override_compile_targets": [ + "resource_sizes_chrome_modern_public_minimal_apks" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "os": "Ubuntu-14.04", + "pool": "chrome.tests" + } + ], + "expiration": 7200, + "hard_timeout": 36000, + "ignore_task_failure": false, + "io_timeout": 1800 + } + }, + { + "args": [], + "isolate_name": "resource_sizes_chrome_public_apk", + "merge": { + "script": "//tools/perf/process_perf_results.py" + }, + "name": "resource_sizes_chrome_public_apk", + "override_compile_targets": [ + "resource_sizes_chrome_public_apk" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "os": "Ubuntu-14.04", + "pool": "chrome.tests" + } + ], + "expiration": 7200, + "hard_timeout": 36000, + "ignore_task_failure": false, + "io_timeout": 1800 + } + }, + { + "args": [], + "isolate_name": "resource_sizes_monochrome_public_minimal_apks", + "merge": { + "script": "//tools/perf/process_perf_results.py" + }, + "name": "resource_sizes_monochrome_public_minimal_apks", + "override_compile_targets": [ + "resource_sizes_monochrome_public_minimal_apks" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "os": "Ubuntu-14.04", + "pool": "chrome.tests" + } + ], + "expiration": 7200, + "hard_timeout": 36000, + "ignore_task_failure": false, + "io_timeout": 1800 + } + }, + { + "args": [], + "isolate_name": "resource_sizes_system_webview_apk", + "merge": { + "script": "//tools/perf/process_perf_results.py" + }, + "name": "resource_sizes_system_webview_apk", + "override_compile_targets": [ + "resource_sizes_system_webview_apk" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "os": "Ubuntu-14.04", + "pool": "chrome.tests" + } + ], + "expiration": 7200, + "hard_timeout": 36000, + "ignore_task_failure": false, + "io_timeout": 1800 + } + } ] }, "android-go-perf": { @@ -1034,6 +1132,104 @@ "microdump_stackwalk", "angle_perftests", "chrome_apk" + ], + "isolated_scripts": [ + { + "args": [], + "isolate_name": "resource_sizes_chrome_modern_public_minimal_apks", + "merge": { + "script": "//tools/perf/process_perf_results.py" + }, + "name": "resource_sizes_chrome_modern_public_minimal_apks", + "override_compile_targets": [ + "resource_sizes_chrome_modern_public_minimal_apks" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "os": "Ubuntu-14.04", + "pool": "chrome.tests" + } + ], + "expiration": 7200, + "hard_timeout": 36000, + "ignore_task_failure": false, + "io_timeout": 1800 + } + }, + { + "args": [], + "isolate_name": "resource_sizes_chrome_public_apk", + "merge": { + "script": "//tools/perf/process_perf_results.py" + }, + "name": "resource_sizes_chrome_public_apk", + "override_compile_targets": [ + "resource_sizes_chrome_public_apk" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "os": "Ubuntu-14.04", + "pool": "chrome.tests" + } + ], + "expiration": 7200, + "hard_timeout": 36000, + "ignore_task_failure": false, + "io_timeout": 1800 + } + }, + { + "args": [], + "isolate_name": "resource_sizes_monochrome_public_minimal_apks", + "merge": { + "script": "//tools/perf/process_perf_results.py" + }, + "name": "resource_sizes_monochrome_public_minimal_apks", + "override_compile_targets": [ + "resource_sizes_monochrome_public_minimal_apks" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "os": "Ubuntu-14.04", + "pool": "chrome.tests" + } + ], + "expiration": 7200, + "hard_timeout": 36000, + "ignore_task_failure": false, + "io_timeout": 1800 + } + }, + { + "args": [], + "isolate_name": "resource_sizes_system_webview_apk", + "merge": { + "script": "//tools/perf/process_perf_results.py" + }, + "name": "resource_sizes_system_webview_apk", + "override_compile_targets": [ + "resource_sizes_system_webview_apk" + ], + "swarming": { + "can_use_on_swarming_builders": true, + "dimension_sets": [ + { + "os": "Ubuntu-14.04", + "pool": "chrome.tests" + } + ], + "expiration": 7200, + "hard_timeout": 36000, + "ignore_task_failure": false, + "io_timeout": 1800 + } + } ] }, "linux-builder-perf": {
diff --git a/testing/buildbot/filters/fuchsia.content_unittests.filter b/testing/buildbot/filters/fuchsia.content_unittests.filter index c93a056..90153b1db 100644 --- a/testing/buildbot/filters/fuchsia.content_unittests.filter +++ b/testing/buildbot/filters/fuchsia.content_unittests.filter
@@ -8,10 +8,6 @@ -RendererAudioOutputStreamFactoryIntegrationTest.StreamIntegrationTest -WebContentsAudioInputStreamTest.MirroringNothingWithTargetChange/0 -# Flaky: https://crbug.com/760687. --CacheStorageManagerTest.GetAllOriginsUsageWithOldIndex --CacheStorageManagerTest.GetOriginSizeWithOldIndex - # Flaky: https://crbug.com/759653. -RenderWidgetHostViewAuraTest.Resize
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl index 431e0ec..88162d9 100644 --- a/testing/buildbot/gn_isolate_map.pyl +++ b/testing/buildbot/gn_isolate_map.pyl
@@ -2222,11 +2222,31 @@ "label": "//third_party/webrtc/test/fuzzers:residual_echo_detector_fuzzer", "type": "fuzzer", }, + "resource_sizes_chrome_modern_public_minimal_apks": { + "label": "//chrome/android:resource_sizes_chrome_modern_public_minimal_apks", + "type": "generated_script", + "script": "bin/resource_sizes_chrome_modern_public_minimal_apks", + }, + "resource_sizes_chrome_public_apk": { + "label": "//chrome/android:resource_sizes_chrome_public_apk", + "type": "generated_script", + "script": "bin/resource_sizes_chrome_public_apk", + }, "resource_sizes_cronet_sample_apk": { "label": "//components/cronet/android:resource_sizes_cronet_sample_apk", "type": "generated_script", "script": "bin/resource_sizes_cronet_sample_apk", }, + "resource_sizes_monochrome_public_minimal_apks": { + "label": "//chrome/android:resource_sizes_monochrome_public_minimal_apks", + "type": "generated_script", + "script": "bin/resource_sizes_monochrome_public_minimal_apks", + }, + "resource_sizes_system_webview_apk": { + "label": "//android_webview:resource_sizes_system_webview_apk", + "type": "generated_script", + "script": "bin/resource_sizes_system_webview_apk", + }, "rtcp_receiver_fuzzer": { "label": "//third_party/webrtc/test/fuzzers:rtcp_receiver_fuzzer", "type": "fuzzer",
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl index 9969d4ee..e77cf7d 100644 --- a/testing/buildbot/test_suites.pyl +++ b/testing/buildbot/test_suites.pyl
@@ -89,6 +89,7 @@ 'android_specific_chromium_gtests': { 'android_webview_unittests': {}, + 'breakpad_unittests': {}, 'content_shell_test_apk': { 'swarming': { 'shards': 3, @@ -2231,7 +2232,6 @@ }, 'chromium_gtests_for_linux_and_chromeos_only': { - 'breakpad_unittests': {}, 'dbus_unittests': {}, 'mojo_core_unittests': {}, 'nacl_helper_nonsfi_unittests': {},
diff --git a/testing/buildbot/tryserver.chromium.linux.json b/testing/buildbot/tryserver.chromium.linux.json index a700123..354605b 100644 --- a/testing/buildbot/tryserver.chromium.linux.json +++ b/testing/buildbot/tryserver.chromium.linux.json
@@ -208,22 +208,6 @@ { "os": "Ubuntu-14.04" } - ] - }, - "test": "breakpad_unittests" - }, - { - "isolate_coverage_data": true, - "merge": { - "args": [], - "script": "//testing/merge_scripts/standard_gtest_merge.py" - }, - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "os": "Ubuntu-14.04" - } ], "shards": 10 },
diff --git a/testing/libfuzzer/fuzzers/dicts/feature_policy.dict b/testing/libfuzzer/fuzzers/dicts/feature_policy.dict deleted file mode 100644 index de763851..0000000 --- a/testing/libfuzzer/fuzzers/dicts/feature_policy.dict +++ /dev/null
@@ -1,20 +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. - -"\"cookie\"" -"\"domain\"" -"\"docwrite\"" -"\"geolocation\"" -"\"midi\"" -"\"notifications\"" -"\"payment\"" -"\"push\"" -"\"sync-script\"" -"\"sync-xhr\"" -"\"usermedia\"" -"\"vibrate\"" -"\"webrtc\"" -"\"https://example.com/\"" -"*" -"\"self\""
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json index 588ecfd..83bdc4f5b 100644 --- a/testing/variations/fieldtrial_testing_config.json +++ b/testing/variations/fieldtrial_testing_config.json
@@ -4832,26 +4832,6 @@ ] } ], - "SocketReadIfReady": [ - { - "platforms": [ - "android", - "chromeos", - "linux", - "mac", - "ios", - "windows" - ], - "experiments": [ - { - "name": "Enabled", - "enable_features": [ - "SocketReadIfReady" - ] - } - ] - } - ], "SqlTempStoreMemory": [ { "platforms": [
diff --git a/third_party/android_sdk/BUILD.gn b/third_party/android_sdk/BUILD.gn index e70d925f..c302490 100644 --- a/third_party/android_sdk/BUILD.gn +++ b/third_party/android_sdk/BUILD.gn
@@ -90,9 +90,10 @@ android_java_prebuilt("android_gcm_java") { jar_path = "//third_party/android_sdk/public/extras/google/gcm/gcm-client/dist/gcm.jar" } - android_java_prebuilt("emma_device_java") { + java_prebuilt("emma_device_java") { jar_path = "//third_party/android_sdk/public/tools/lib/emma_device.jar" include_java_resources = true + supports_android = true } # The current version of //third_party/byte_buddy relies on an older
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc index f561f7d..73af4e8 100644 --- a/third_party/blink/common/features.cc +++ b/third_party/blink/common/features.cc
@@ -62,6 +62,11 @@ const base::Feature kBlinkGenPropertyTrees{"BlinkGenPropertyTrees", base::FEATURE_ENABLED_BY_DEFAULT}; +// Enable applying rounded corner masks via a GL shader rather than +// a mask layer. +const base::Feature kFastBorderRadius{"FastBorderRadius", + base::FEATURE_DISABLED_BY_DEFAULT}; + // Enable LayoutNG. const base::Feature kLayoutNG{"LayoutNG", base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/third_party/blink/public/common/features.h b/third_party/blink/public/common/features.h index 736d41c..139c5c1 100644 --- a/third_party/blink/public/common/features.h +++ b/third_party/blink/public/common/features.h
@@ -26,6 +26,7 @@ BLINK_COMMON_EXPORT extern const base::Feature kImplicitRootScroller; BLINK_COMMON_EXPORT extern const base::Feature kJankTrackingSweepLine; BLINK_COMMON_EXPORT extern const base::Feature kBlinkGenPropertyTrees; +BLINK_COMMON_EXPORT extern const base::Feature kFastBorderRadius; BLINK_COMMON_EXPORT extern const base::Feature kLayoutNG; BLINK_COMMON_EXPORT extern const base::Feature kMixedContentAutoupgrade; BLINK_COMMON_EXPORT extern const base::Feature kMojoBlobURLs;
diff --git a/third_party/blink/renderer/bindings/core/v8/script_controller.cc b/third_party/blink/renderer/bindings/core/v8/script_controller.cc index 5987273..83023119 100644 --- a/third_party/blink/renderer/bindings/core/v8/script_controller.cc +++ b/third_party/blink/renderer/bindings/core/v8/script_controller.cc
@@ -59,7 +59,6 @@ #include "third_party/blink/renderer/core/inspector/main_thread_debugger.h" #include "third_party/blink/renderer/core/loader/document_loader.h" #include "third_party/blink/renderer/core/loader/frame_loader.h" -#include "third_party/blink/renderer/core/loader/navigation_scheduler.h" #include "third_party/blink/renderer/core/loader/progress_tracker.h" #include "third_party/blink/renderer/core/probe/core_probes.h" #include "third_party/blink/renderer/platform/histogram.h"
diff --git a/third_party/blink/renderer/config.gni b/third_party/blink/renderer/config.gni index d9e4e22..cc53ed2 100644 --- a/third_party/blink/renderer/config.gni +++ b/third_party/blink/renderer/config.gni
@@ -14,6 +14,13 @@ } declare_args() { + # If true, use PFFFT for WebAudio FFT support. This can be used for + # any Android architecture and also Linux and Windows. We only use + # it on Android. + use_webaudio_pffft = is_android +} + +declare_args() { # DEPRECATED: Use blink_symbol_level=0. https://crbug.com/943869 remove_webcore_debug_symbols = false @@ -31,8 +38,8 @@ # If true, defaults image interpolation to low quality. use_low_quality_image_interpolation = is_android - # If true, ffmpeg will be used for decoding audio. - use_webaudio_ffmpeg = !is_mac && !is_android + # If true, ffmpeg will be used for computing FFTs for WebAudio + use_webaudio_ffmpeg = !is_mac && !is_android && !use_webaudio_pffft # If true, webgl2-compute context will be supported. support_webgl2_compute_context = !is_android @@ -44,12 +51,13 @@ "https://crbug.com/943869") # Whether Android build uses OpenMAX DL FFT. Currently supported only on -# ARMv7+, ARM64, x86 or x64 without webview. Also enables WebAudio support. -# Whether WebAudio is actually available depends on runtime settings and flags. -use_openmax_dl_fft = - is_android && (current_cpu == "x86" || current_cpu == "x64" || - (current_cpu == "arm" && arm_version >= 7) || - current_cpu == "arm64" || current_cpu == "mipsel") +# ARMv7+, ARM64, x86 or x64 without webview. +# TODO(crbug.com/917355): Remove support for openmax_dl FFT in favor +# of PFFFT +use_openmax_dl_fft = !use_webaudio_pffft && is_android && + (current_cpu == "x86" || current_cpu == "x64" || + (current_cpu == "arm" && arm_version >= 7) || + current_cpu == "arm64" || current_cpu == "mipsel") # feature_defines_list --------------------------------------------------------- @@ -75,6 +83,10 @@ feature_defines_list += [ "WTF_USE_WEBAUDIO_OPENMAX_DL_FFT=1" ] } +if (use_webaudio_pffft) { + feature_defines_list += [ "WTF_USE_WEBAUDIO_PFFFT=1" ] +} + if (use_default_render_theme) { # Mirrors the USE_DEFAULT_RENDER_THEME buildflag_header in WebKit/public. # If/when Blink can use buildflag headers, this should be removed in
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn index 12ae4df..175077c 100644 --- a/third_party/blink/renderer/core/BUILD.gn +++ b/third_party/blink/renderer/core/BUILD.gn
@@ -2358,7 +2358,7 @@ } } -# Fuzzer for blink::FeaturePolicy. +# Fuzzers for blink::FeaturePolicy. fuzzer_test("feature_policy_fuzzer") { sources = [ "feature_policy/feature_policy_fuzzer.cc", @@ -2367,6 +2367,32 @@ "//third_party/blink/renderer/platform:blink_fuzzer_test_support", "//third_party/icu", ] - dict = "//testing/libfuzzer/fuzzers/dicts/feature_policy.dict" - seed_corpus = "//testing/libfuzzer/fuzzers/feature_policy_corpus" + dict = "//third_party/blink/renderer/core/feature_policy/feature_policy.dict" + seed_corpus = + "//third_party/blink/renderer/core/feature_policy/feature_policy_corpus" +} + +fuzzer_test("feature_policy_attr_fuzzer") { + sources = [ + "feature_policy/feature_policy_attr_fuzzer.cc", + ] + deps = [ + "//third_party/blink/renderer/platform:blink_fuzzer_test_support", + "//third_party/icu", + ] + dict = "//third_party/blink/renderer/core/feature_policy/feature_policy.dict" + seed_corpus = + "//third_party/blink/renderer/core/feature_policy/feature_policy_corpus" +} + +fuzzer_test("feature_policy_value_fuzzer") { + sources = [ + "feature_policy/feature_policy_value_fuzzer.cc", + ] + deps = [ + "//third_party/blink/renderer/platform:blink_fuzzer_test_support", + "//third_party/icu", + ] + dict = "//third_party/blink/renderer/core/feature_policy/feature_policy_value.dict" + seed_corpus = "//third_party/blink/renderer/core/feature_policy/feature_policy_value_corpus" }
diff --git a/third_party/blink/renderer/core/animation/element_animation.cc b/third_party/blink/renderer/core/animation/element_animation.cc index 4b2724b..cc83c606 100644 --- a/third_party/blink/renderer/core/animation/element_animation.cc +++ b/third_party/blink/renderer/core/animation/element_animation.cc
@@ -14,7 +14,6 @@ #include "third_party/blink/renderer/core/animation/timing_input.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/element.h" -#include "third_party/blink/renderer/core/feature_policy/feature_policy.h" #include "third_party/blink/renderer/core/feature_policy/layout_animations_policy.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h" #include "third_party/blink/renderer/platform/bindings/script_state.h"
diff --git a/third_party/blink/renderer/core/core_idl_files.gni b/third_party/blink/renderer/core/core_idl_files.gni index 365f62a2..a8e18bf 100644 --- a/third_party/blink/renderer/core/core_idl_files.gni +++ b/third_party/blink/renderer/core/core_idl_files.gni
@@ -222,7 +222,6 @@ "html/html_directory_element.idl", "html/html_div_element.idl", "html/html_document.idl", - "html/html_element.idl", "html/html_embed_element.idl", "html/html_font_element.idl", "html/html_frame_element.idl", @@ -501,6 +500,7 @@ "frame/navigator.idl", "frame/screen.idl", "frame/window.idl", + "html/html_element.idl", "html/html_iframe_element.idl", "html/canvas/html_canvas_element.idl", "html/forms/html_input_element.idl",
diff --git a/third_party/blink/renderer/core/css/parser/css_parser_context.cc b/third_party/blink/renderer/core/css/parser/css_parser_context.cc index 2a06cec7..6b57500a 100644 --- a/third_party/blink/renderer/core/css/parser/css_parser_context.cc +++ b/third_party/blink/renderer/core/css/parser/css_parser_context.cc
@@ -10,7 +10,6 @@ #include "third_party/blink/renderer/core/css/style_rule_keyframe.h" #include "third_party/blink/renderer/core/css/style_sheet_contents.h" #include "third_party/blink/renderer/core/execution_context/execution_context.h" -#include "third_party/blink/renderer/core/feature_policy/feature_policy.h" #include "third_party/blink/renderer/core/feature_policy/layout_animations_policy.h" #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" #include "third_party/blink/renderer/core/frame/deprecation.h"
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc index 56b0bdc..3bac040 100644 --- a/third_party/blink/renderer/core/dom/document.cc +++ b/third_party/blink/renderer/core/dom/document.cc
@@ -145,6 +145,7 @@ #include "third_party/blink/renderer/core/events/visual_viewport_resize_event.h" #include "third_party/blink/renderer/core/events/visual_viewport_scroll_event.h" #include "third_party/blink/renderer/core/feature_policy/document_policy.h" +#include "third_party/blink/renderer/core/feature_policy/feature_policy_parser.h" #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" #include "third_party/blink/renderer/core/frame/dom_timer.h" #include "third_party/blink/renderer/core/frame/dom_visual_viewport.h" @@ -223,9 +224,9 @@ #include "third_party/blink/renderer/core/loader/document_loader.h" #include "third_party/blink/renderer/core/loader/frame_fetch_context.h" #include "third_party/blink/renderer/core/loader/frame_loader.h" +#include "third_party/blink/renderer/core/loader/http_refresh_scheduler.h" #include "third_party/blink/renderer/core/loader/idleness_detector.h" #include "third_party/blink/renderer/core/loader/interactive_detector.h" -#include "third_party/blink/renderer/core/loader/navigation_scheduler.h" #include "third_party/blink/renderer/core/loader/prerenderer_client.h" #include "third_party/blink/renderer/core/loader/progress_tracker.h" #include "third_party/blink/renderer/core/loader/text_resource_decoder_builder.h" @@ -616,6 +617,7 @@ imports_controller_(initializer.ImportsController()), context_document_(initializer.ContextDocument()), context_features_(ContextFeatures::DefaultSwitch()), + http_refresh_scheduler_(MakeGarbageCollected<HttpRefreshScheduler>(this)), well_formed_(false), printing_(kNotPrinting), compatibility_mode_(kNoQuirksMode), @@ -3080,14 +3082,13 @@ // want to treat ongoing navigation and queued navigation the same way. // However, we don't want to consider navigations scheduled too much into the // future through Refresh headers or a <meta> refresh pragma to be a current - // navigation. Thus, we cut it off with IsNavigationScheduledWithin(0). + // navigation. Thus, we cut it off with IsHttpRefreshScheduledWithin(0). // // This also prevents window.open(url) -- eg window.open("about:blank") -- // from blowing away results from a subsequent window.document.open / // window.document.write call. - if (frame_ && - (frame_->Loader().HasProvisionalNavigation() || - frame_->GetNavigationScheduler().IsNavigationScheduledWithin(0))) { + if (frame_ && (frame_->Loader().HasProvisionalNavigation() || + IsHttpRefreshScheduledWithin(0))) { frame_->Loader().StopAllLoaders(); // Navigations handled by the client should also be cancelled. if (frame_ && frame_->Client()) @@ -3137,6 +3138,7 @@ if (!LoadEventFinished()) load_event_progress_ = kLoadEventCompleted; CancelPendingJavaScriptUrls(); + http_refresh_scheduler_->Cancel(); } DocumentParser* Document::OpenForNavigation( @@ -3508,7 +3510,7 @@ // The readystatechanged or load event may have disconnected this frame. if (!frame_ || !frame_->IsAttached()) return false; - frame_->GetNavigationScheduler().StartTimer(); + http_refresh_scheduler_->MaybeStartTimer(); View()->HandleLoadCompleted(); // The document itself is complete, but if a child frame was restarted due to // an event, this document is still considered to be in progress. @@ -4198,8 +4200,11 @@ if (http_refresh_type == kHttpRefreshFromHeader) { UseCounter::Count(this, WebFeature::kRefreshHeader); } - frame_->GetNavigationScheduler().ScheduleRedirect(delay, refresh_url, - http_refresh_type); + http_refresh_scheduler_->Schedule(delay, refresh_url, http_refresh_type); +} + +bool Document::IsHttpRefreshScheduledWithin(double interval_in_seconds) { + return http_refresh_scheduler_->IsScheduledWithin(interval_in_seconds); } // https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer @@ -6224,7 +6229,7 @@ if (!feature_policy_header.IsEmpty()) UseCounter::Count(*this, WebFeature::kFeaturePolicyHeader); Vector<String> messages; - auto declared_policy = ParseFeaturePolicyHeader( + auto declared_policy = FeaturePolicyParser::ParseHeader( feature_policy_header, GetSecurityOrigin(), &messages, this); for (auto& message : messages) { AddConsoleMessage( @@ -6305,8 +6310,9 @@ UseCounter::Count(*this, WebFeature::kFeaturePolicyReportOnlyHeader); Vector<String> messages; - const ParsedFeaturePolicy& report_only_policy = ParseFeaturePolicyHeader( - feature_policy_report_only_header, GetSecurityOrigin(), &messages, this); + const ParsedFeaturePolicy& report_only_policy = + FeaturePolicyParser::ParseHeader(feature_policy_report_only_header, + GetSecurityOrigin(), &messages, this); for (auto& message : messages) { AddConsoleMessage(ConsoleMessage::Create( mojom::ConsoleMessageSource::kSecurity, @@ -7668,6 +7674,7 @@ visitor->Trace(fetcher_); visitor->Trace(parser_); visitor->Trace(context_features_); + visitor->Trace(http_refresh_scheduler_); visitor->Trace(style_sheet_list_); visitor->Trace(document_timing_); visitor->Trace(media_query_matcher_);
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h index 5a86225..082f9545 100644 --- a/third_party/blink/renderer/core/dom/document.h +++ b/third_party/blink/renderer/core/dom/document.h
@@ -137,6 +137,7 @@ class HTMLLinkElement; class HTMLScriptElementOrSVGScriptElement; class HitTestRequest; +class HttpRefreshScheduler; class IdleRequestOptions; class IntersectionObserverController; class LayoutPoint; @@ -1289,6 +1290,7 @@ enum HttpRefreshType { kHttpRefreshFromHeader, kHttpRefreshFromMetaTag }; void MaybeHandleHttpRefresh(const String&, HttpRefreshType); + bool IsHttpRefreshScheduledWithin(double interval_in_seconds); void UpdateSecurityOrigin(scoped_refptr<SecurityOrigin>); @@ -1515,7 +1517,6 @@ void IncrementNumberOfCanvases(); void ProcessJavaScriptUrl(const KURL&, ContentSecurityPolicyDisposition); - void CancelPendingJavaScriptUrls(); // Functions to keep count of display locks in this document. void AddActivationBlockingDisplayLock(); @@ -1641,6 +1642,7 @@ void ExecuteScriptsWaitingForResources(); void ExecuteJavaScriptUrls(); + void CancelPendingJavaScriptUrls(); void LoadEventDelayTimerFired(TimerBase*); void PluginLoadingTimerFired(TimerBase*); @@ -1722,6 +1724,7 @@ Member<ResourceFetcher> fetcher_; Member<DocumentParser> parser_; Member<ContextFeatures> context_features_; + Member<HttpRefreshScheduler> http_refresh_scheduler_; bool well_formed_;
diff --git a/third_party/blink/renderer/core/feature_policy/BUILD.gn b/third_party/blink/renderer/core/feature_policy/BUILD.gn index 93827549..9aec8ac 100644 --- a/third_party/blink/renderer/core/feature_policy/BUILD.gn +++ b/third_party/blink/renderer/core/feature_policy/BUILD.gn
@@ -9,9 +9,9 @@ "document_policy.h", "dom_feature_policy.cc", "dom_feature_policy.h", - "feature_policy.cc", - "feature_policy.h", "feature_policy_helper.h", + "feature_policy_parser.cc", + "feature_policy_parser.h", "iframe_policy.h", "layout_animations_policy.cc", "layout_animations_policy.h",
diff --git a/third_party/blink/renderer/core/feature_policy/dom_feature_policy.cc b/third_party/blink/renderer/core/feature_policy/dom_feature_policy.cc index d5af2531d..64f7b57 100644 --- a/third_party/blink/renderer/core/feature_policy/dom_feature_policy.cc +++ b/third_party/blink/renderer/core/feature_policy/dom_feature_policy.cc
@@ -5,7 +5,7 @@ #include "third_party/blink/renderer/core/feature_policy/dom_feature_policy.h" #include "third_party/blink/renderer/core/dom/document.h" -#include "third_party/blink/renderer/core/feature_policy/feature_policy.h" +#include "third_party/blink/renderer/core/feature_policy/feature_policy_parser.h" #include "third_party/blink/renderer/core/inspector/console_message.h" #include "third_party/blink/renderer/platform/bindings/script_state.h" #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy.dict b/third_party/blink/renderer/core/feature_policy/feature_policy.dict new file mode 100644 index 0000000..3e9db3b --- /dev/null +++ b/third_party/blink/renderer/core/feature_policy/feature_policy.dict
@@ -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. + +"accelerometer" +"ambient-light-sensor" +"autoplay" +"camera" +"document-domain" +"document-write" +"encrypted-media" +"font-display-late-swap" +"forms" +"fullscreen" +"geolocation" +"gyroscope" +"hid" +"idle-detection" +"layout-animations" +"lazyload" +"magnetometer" +"microphone" +"midi" +"modals" +"orientation-lock" +"oversized-images" +"payment" +"picture-in-picture" +"pointer-lock" +"popups" +"presentation" +"scripts" +"serial" +"speaker" +"sync-script" +"sync-xkr" +"top-navigation" +"unoptimized-lossless-images" +"unoptimized-lossless-images-strict" +"unoptimized-lossy-images" +"unsized-media" +"usb" +"vertical-scroll" +"wake-lock" +"vr" +"\"https://example.com/\"" +"*" +"'self'" +"'src'" +"'none'" +"(" +")" +"true" +"false" +"inf" +"0" +"1" +".0"
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy.h b/third_party/blink/renderer/core/feature_policy/feature_policy.h deleted file mode 100644 index 38d3beba..0000000 --- a/third_party/blink/renderer/core/feature_policy/feature_policy.h +++ /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. - -#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_FEATURE_POLICY_FEATURE_POLICY_H_ -#define THIRD_PARTY_BLINK_RENDERER_CORE_FEATURE_POLICY_FEATURE_POLICY_H_ - -#include "base/memory/scoped_refptr.h" -#include "third_party/blink/public/common/feature_policy/feature_policy.h" -#include "third_party/blink/renderer/core/core_export.h" -#include "third_party/blink/renderer/core/feature_policy/feature_policy_helper.h" -#include "third_party/blink/renderer/platform/weborigin/security_origin.h" -#include "third_party/blink/renderer/platform/wtf/text/string_hash.h" -#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" -#include "third_party/blink/renderer/platform/wtf/vector.h" - -#include <memory> - -namespace blink { - -class Document; -class ExecutionContext; - -// Returns the list of features which are currently available in this context, -// including any features which have been made available by an origin trial. -CORE_EXPORT const Vector<String> GetAvailableFeatures(ExecutionContext*); - -// Converts a header policy string into a vector of allowlists, one for each -// feature specified. Unrecognized features are filtered out. If |messages| is -// not null, then any message in the input will cause a warning message to be -// appended to it. The optional ExecutionContext is used to determine if any -// origin trials affect the parsing. -// Example of a feature policy string: -// "vibrate a.com b.com; fullscreen 'none'; payment 'self', payment *". -CORE_EXPORT ParsedFeaturePolicy -ParseFeaturePolicyHeader(const String& policy, - scoped_refptr<const SecurityOrigin>, - Vector<String>* messages, - ExecutionContext* execution_context = nullptr); - -// Converts a container policy string into a vector of allowlists, given self -// and src origins provided, one for each feature specified. Unrecognized -// features are filtered out. If |messages| is not null, then any message in the -// input will cause as warning message to be appended to it. -// Example of a feature policy string: -// "vibrate a.com 'src'; fullscreen 'none'; payment 'self', payment *". -CORE_EXPORT ParsedFeaturePolicy -ParseFeaturePolicyAttribute(const String& policy, - scoped_refptr<const SecurityOrigin> self_origin, - scoped_refptr<const SecurityOrigin> src_origin, - Vector<String>* messages, - Document* document = nullptr); - -// Converts a feature policy string into a vector of allowlists (see comments -// above), with an explicit FeatureNameMap. This algorithm is called by both -// header policy parsing and container policy parsing. |self_origin|, -// |src_origin|, and |execution_context| are nullable. The optional -// ExecutionContext is used to determine if any origin trials affect the -// parsing. -CORE_EXPORT ParsedFeaturePolicy -ParseFeaturePolicy(const String& policy, - scoped_refptr<const SecurityOrigin> self_origin, - scoped_refptr<const SecurityOrigin> src_origin, - Vector<String>* messages, - const FeatureNameMap& feature_names, - ExecutionContext* execution_context = nullptr); - -// Returns true iff any declaration in the policy is for the given feature. -CORE_EXPORT bool IsFeatureDeclared(mojom::FeaturePolicyFeature, - const ParsedFeaturePolicy&); - -// Removes any declaration in the policy for the given feature. Returns true if -// the policy was modified. -CORE_EXPORT bool RemoveFeatureIfPresent(mojom::FeaturePolicyFeature, - ParsedFeaturePolicy&); - -// If no declaration in the policy exists already for the feature, adds a -// declaration which disallows the feature in all origins. Returns true if the -// policy was modified. -CORE_EXPORT bool DisallowFeatureIfNotPresent(mojom::FeaturePolicyFeature, - ParsedFeaturePolicy&); - -// If no declaration in the policy exists already for the feature, adds a -// declaration which allows the feature in all origins. Returns true if the -// policy was modified. -CORE_EXPORT bool AllowFeatureEverywhereIfNotPresent(mojom::FeaturePolicyFeature, - ParsedFeaturePolicy&); - -// Replaces any existing declarations in the policy for the given feature with -// a declaration which disallows the feature in all origins. -CORE_EXPORT void DisallowFeature(mojom::FeaturePolicyFeature, - ParsedFeaturePolicy&); - -// Replaces any existing declarations in the policy for the given feature with -// a declaration which allows the feature in all origins. -CORE_EXPORT void AllowFeatureEverywhere(mojom::FeaturePolicyFeature, - ParsedFeaturePolicy&); - -CORE_EXPORT const String& GetNameForFeature(mojom::FeaturePolicyFeature); - -} // namespace blink - -#endif // THIRD_PARTY_BLINK_RENDERER_CORE_FEATURE_POLICY_FEATURE_POLICY_H_
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy_attr_fuzzer.cc b/third_party/blink/renderer/core/feature_policy/feature_policy_attr_fuzzer.cc new file mode 100644 index 0000000..f3f4c57 --- /dev/null +++ b/third_party/blink/renderer/core/feature_policy/feature_policy_attr_fuzzer.cc
@@ -0,0 +1,29 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/feature_policy/feature_policy_parser.h" + +#include <stddef.h> +#include <stdint.h> +#include <memory> +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h" +#include "third_party/blink/renderer/platform/weborigin/security_origin.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + static blink::BlinkFuzzerTestSupport test_support = + blink::BlinkFuzzerTestSupport(); + WTF::Vector<WTF::String> messages; + // TODO(csharrison): Be smarter about parsing these origins for performance. + scoped_refptr<const blink::SecurityOrigin> parent_origin = + blink::SecurityOrigin::CreateFromString("https://example.com/"); + scoped_refptr<const blink::SecurityOrigin> child_origin = + blink::SecurityOrigin::CreateFromString("https://example.net/"); + blink::FeaturePolicyParser::ParseAttribute(WTF::String(data, size), + parent_origin.get(), + child_origin.get(), &messages); + return 0; +}
diff --git a/testing/libfuzzer/fuzzers/feature_policy_corpus/1 b/third_party/blink/renderer/core/feature_policy/feature_policy_corpus/1 similarity index 100% rename from testing/libfuzzer/fuzzers/feature_policy_corpus/1 rename to third_party/blink/renderer/core/feature_policy/feature_policy_corpus/1
diff --git a/testing/libfuzzer/fuzzers/feature_policy_corpus/10 b/third_party/blink/renderer/core/feature_policy/feature_policy_corpus/10 similarity index 100% rename from testing/libfuzzer/fuzzers/feature_policy_corpus/10 rename to third_party/blink/renderer/core/feature_policy/feature_policy_corpus/10
diff --git a/testing/libfuzzer/fuzzers/feature_policy_corpus/11 b/third_party/blink/renderer/core/feature_policy/feature_policy_corpus/11 similarity index 100% rename from testing/libfuzzer/fuzzers/feature_policy_corpus/11 rename to third_party/blink/renderer/core/feature_policy/feature_policy_corpus/11
diff --git a/testing/libfuzzer/fuzzers/feature_policy_corpus/12 b/third_party/blink/renderer/core/feature_policy/feature_policy_corpus/12 similarity index 100% rename from testing/libfuzzer/fuzzers/feature_policy_corpus/12 rename to third_party/blink/renderer/core/feature_policy/feature_policy_corpus/12
diff --git a/testing/libfuzzer/fuzzers/feature_policy_corpus/13 b/third_party/blink/renderer/core/feature_policy/feature_policy_corpus/13 similarity index 100% rename from testing/libfuzzer/fuzzers/feature_policy_corpus/13 rename to third_party/blink/renderer/core/feature_policy/feature_policy_corpus/13
diff --git a/testing/libfuzzer/fuzzers/feature_policy_corpus/14 b/third_party/blink/renderer/core/feature_policy/feature_policy_corpus/14 similarity index 100% rename from testing/libfuzzer/fuzzers/feature_policy_corpus/14 rename to third_party/blink/renderer/core/feature_policy/feature_policy_corpus/14
diff --git a/testing/libfuzzer/fuzzers/feature_policy_corpus/15 b/third_party/blink/renderer/core/feature_policy/feature_policy_corpus/15 similarity index 100% rename from testing/libfuzzer/fuzzers/feature_policy_corpus/15 rename to third_party/blink/renderer/core/feature_policy/feature_policy_corpus/15
diff --git a/testing/libfuzzer/fuzzers/feature_policy_corpus/16 b/third_party/blink/renderer/core/feature_policy/feature_policy_corpus/16 similarity index 100% rename from testing/libfuzzer/fuzzers/feature_policy_corpus/16 rename to third_party/blink/renderer/core/feature_policy/feature_policy_corpus/16
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy_corpus/17 b/third_party/blink/renderer/core/feature_policy/feature_policy_corpus/17 new file mode 100644 index 0000000..43cc41c --- /dev/null +++ b/third_party/blink/renderer/core/feature_policy/feature_policy_corpus/17
@@ -0,0 +1 @@ +unoptimized-lossy-images *(9.0)
diff --git a/testing/libfuzzer/fuzzers/feature_policy_corpus/2 b/third_party/blink/renderer/core/feature_policy/feature_policy_corpus/2 similarity index 100% rename from testing/libfuzzer/fuzzers/feature_policy_corpus/2 rename to third_party/blink/renderer/core/feature_policy/feature_policy_corpus/2
diff --git a/testing/libfuzzer/fuzzers/feature_policy_corpus/3 b/third_party/blink/renderer/core/feature_policy/feature_policy_corpus/3 similarity index 100% rename from testing/libfuzzer/fuzzers/feature_policy_corpus/3 rename to third_party/blink/renderer/core/feature_policy/feature_policy_corpus/3
diff --git a/testing/libfuzzer/fuzzers/feature_policy_corpus/4 b/third_party/blink/renderer/core/feature_policy/feature_policy_corpus/4 similarity index 100% rename from testing/libfuzzer/fuzzers/feature_policy_corpus/4 rename to third_party/blink/renderer/core/feature_policy/feature_policy_corpus/4
diff --git a/testing/libfuzzer/fuzzers/feature_policy_corpus/5 b/third_party/blink/renderer/core/feature_policy/feature_policy_corpus/5 similarity index 100% rename from testing/libfuzzer/fuzzers/feature_policy_corpus/5 rename to third_party/blink/renderer/core/feature_policy/feature_policy_corpus/5
diff --git a/testing/libfuzzer/fuzzers/feature_policy_corpus/6 b/third_party/blink/renderer/core/feature_policy/feature_policy_corpus/6 similarity index 100% rename from testing/libfuzzer/fuzzers/feature_policy_corpus/6 rename to third_party/blink/renderer/core/feature_policy/feature_policy_corpus/6
diff --git a/testing/libfuzzer/fuzzers/feature_policy_corpus/7 b/third_party/blink/renderer/core/feature_policy/feature_policy_corpus/7 similarity index 100% rename from testing/libfuzzer/fuzzers/feature_policy_corpus/7 rename to third_party/blink/renderer/core/feature_policy/feature_policy_corpus/7
diff --git a/testing/libfuzzer/fuzzers/feature_policy_corpus/8 b/third_party/blink/renderer/core/feature_policy/feature_policy_corpus/8 similarity index 100% rename from testing/libfuzzer/fuzzers/feature_policy_corpus/8 rename to third_party/blink/renderer/core/feature_policy/feature_policy_corpus/8
diff --git a/testing/libfuzzer/fuzzers/feature_policy_corpus/9 b/third_party/blink/renderer/core/feature_policy/feature_policy_corpus/9 similarity index 100% rename from testing/libfuzzer/fuzzers/feature_policy_corpus/9 rename to third_party/blink/renderer/core/feature_policy/feature_policy_corpus/9
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy_fuzzer.cc b/third_party/blink/renderer/core/feature_policy/feature_policy_fuzzer.cc index 89610c8..213666cc 100644 --- a/third_party/blink/renderer/core/feature_policy/feature_policy_fuzzer.cc +++ b/third_party/blink/renderer/core/feature_policy/feature_policy_fuzzer.cc
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "third_party/blink/renderer/core/feature_policy/feature_policy.h" +#include "third_party/blink/renderer/core/feature_policy/feature_policy_parser.h" #include <stddef.h> #include <stdint.h> @@ -20,7 +20,7 @@ // TODO(csharrison): Be smarter about parsing this origin for performance. scoped_refptr<const blink::SecurityOrigin> origin = blink::SecurityOrigin::CreateFromString("https://example.com/"); - blink::ParseFeaturePolicyHeader(WTF::String(data, size), origin.get(), - &messages); + blink::FeaturePolicyParser::ParseHeader(WTF::String(data, size), origin.get(), + &messages); return 0; }
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy.cc b/third_party/blink/renderer/core/feature_policy/feature_policy_parser.cc similarity index 94% rename from third_party/blink/renderer/core/feature_policy/feature_policy.cc rename to third_party/blink/renderer/core/feature_policy/feature_policy_parser.cc index 4b5ee622..fbe06af 100644 --- a/third_party/blink/renderer/core/feature_policy/feature_policy.cc +++ b/third_party/blink/renderer/core/feature_policy/feature_policy_parser.cc
@@ -1,7 +1,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "third_party/blink/renderer/core/feature_policy/feature_policy.h" +#include "third_party/blink/renderer/core/feature_policy/feature_policy_parser.h" #include <algorithm> #include <map> @@ -22,89 +22,26 @@ namespace blink { -namespace { - -// TODO(loonybear): once the new syntax is implemented, use this method to -// parse the policy value for each parameterized feature, and for non -// parameterized feature (i.e. boolean-type policy value). -PolicyValue GetFallbackValueForFeature(mojom::FeaturePolicyFeature feature) { - if (feature == mojom::FeaturePolicyFeature::kOversizedImages) { - return PolicyValue(2.0); - } - if (feature == mojom::FeaturePolicyFeature::kUnoptimizedLossyImages) { - // Lossy images default to at most 0.5 bytes per pixel. - return PolicyValue(0.5); - } - if (feature == mojom::FeaturePolicyFeature::kUnoptimizedLosslessImages || - feature == - mojom::FeaturePolicyFeature::kUnoptimizedLosslessImagesStrict) { - // Lossless images default to at most 1 byte per pixel. - return PolicyValue(1.0); - } - - return PolicyValue(false); -} - -PolicyValue ParseValueForType(mojom::PolicyValueType feature_type, - const String& value_string, - bool* ok) { - *ok = false; - PolicyValue value; - switch (feature_type) { - case mojom::PolicyValueType::kBool: - // recognize true, false - if (value_string.LowerASCII() == "true") { - value = PolicyValue(true); - *ok = true; - } else if (value_string.LowerASCII() == "false") { - value = PolicyValue(false); - *ok = true; - } - break; - case mojom::PolicyValueType::kDecDouble: { - if (value_string.LowerASCII() == "inf") { - value = PolicyValue::CreateMaxPolicyValue(feature_type); - *ok = true; - } else { - double parsed_value = value_string.ToDouble(ok); - if (*ok && parsed_value >= 0.0f) { - value = PolicyValue(parsed_value); - } else { - *ok = false; - } - } - break; - } - default: - NOTREACHED(); - } - if (!*ok) - return PolicyValue(); - return value; -} - -} // namespace - -ParsedFeaturePolicy ParseFeaturePolicyHeader( +ParsedFeaturePolicy FeaturePolicyParser::ParseHeader( const String& policy, scoped_refptr<const SecurityOrigin> origin, Vector<String>* messages, ExecutionContext* execution_context) { - return ParseFeaturePolicy(policy, origin, nullptr, messages, - GetDefaultFeatureNameMap(), execution_context); + return Parse(policy, origin, nullptr, messages, GetDefaultFeatureNameMap(), + execution_context); } -ParsedFeaturePolicy ParseFeaturePolicyAttribute( +ParsedFeaturePolicy FeaturePolicyParser::ParseAttribute( const String& policy, scoped_refptr<const SecurityOrigin> self_origin, scoped_refptr<const SecurityOrigin> src_origin, Vector<String>* messages, Document* document) { - return ParseFeaturePolicy(policy, self_origin, src_origin, messages, - GetDefaultFeatureNameMap(), document); + return Parse(policy, self_origin, src_origin, messages, + GetDefaultFeatureNameMap(), document); } -ParsedFeaturePolicy ParseFeaturePolicy( +ParsedFeaturePolicy FeaturePolicyParser::Parse( const String& policy, scoped_refptr<const SecurityOrigin> self_origin, scoped_refptr<const SecurityOrigin> src_origin, @@ -308,6 +245,67 @@ return allowlists; } +// TODO(loonybear): once the new syntax is implemented, use this method to +// parse the policy value for each parameterized feature, and for non +// parameterized feature (i.e. boolean-type policy value). +PolicyValue FeaturePolicyParser::GetFallbackValueForFeature( + mojom::FeaturePolicyFeature feature) { + if (feature == mojom::FeaturePolicyFeature::kOversizedImages) { + return PolicyValue(2.0); + } + if (feature == mojom::FeaturePolicyFeature::kUnoptimizedLossyImages) { + // Lossy images default to at most 0.5 bytes per pixel. + return PolicyValue(0.5); + } + if (feature == mojom::FeaturePolicyFeature::kUnoptimizedLosslessImages || + feature == + mojom::FeaturePolicyFeature::kUnoptimizedLosslessImagesStrict) { + // Lossless images default to at most 1 byte per pixel. + return PolicyValue(1.0); + } + + return PolicyValue(false); +} + +PolicyValue FeaturePolicyParser::ParseValueForType( + mojom::PolicyValueType feature_type, + const String& value_string, + bool* ok) { + *ok = false; + PolicyValue value; + switch (feature_type) { + case mojom::PolicyValueType::kBool: + // recognize true, false + if (value_string.LowerASCII() == "true") { + value = PolicyValue(true); + *ok = true; + } else if (value_string.LowerASCII() == "false") { + value = PolicyValue(false); + *ok = true; + } + break; + case mojom::PolicyValueType::kDecDouble: { + if (value_string.LowerASCII() == "inf") { + value = PolicyValue::CreateMaxPolicyValue(feature_type); + *ok = true; + } else { + double parsed_value = value_string.ToDouble(ok); + if (*ok && parsed_value >= 0.0f) { + value = PolicyValue(parsed_value); + } else { + *ok = false; + } + } + break; + } + default: + NOTREACHED(); + } + if (!*ok) + return PolicyValue(); + return value; +} + bool IsFeatureDeclared(mojom::FeaturePolicyFeature feature, const ParsedFeaturePolicy& policy) { return std::any_of(policy.begin(), policy.end(),
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy_parser.h b/third_party/blink/renderer/core/feature_policy/feature_policy_parser.h new file mode 100644 index 0000000..fd25d90 --- /dev/null +++ b/third_party/blink/renderer/core/feature_policy/feature_policy_parser.h
@@ -0,0 +1,125 @@ +// 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 THIRD_PARTY_BLINK_RENDERER_CORE_FEATURE_POLICY_FEATURE_POLICY_PARSER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_FEATURE_POLICY_FEATURE_POLICY_PARSER_H_ + +#include <memory> + +#include "base/memory/scoped_refptr.h" +#include "third_party/blink/public/common/feature_policy/feature_policy.h" +#include "third_party/blink/renderer/core/core_export.h" +#include "third_party/blink/renderer/core/feature_policy/feature_policy_helper.h" +#include "third_party/blink/renderer/platform/weborigin/security_origin.h" +#include "third_party/blink/renderer/platform/wtf/text/string_hash.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +// Forward declare for friendship. +void ParseValueForFuzzer(blink::mojom::PolicyValueType, const WTF::String&); + +namespace blink { + +class Document; +class ExecutionContext; + +// Returns the list of features which are currently available in this context, +// including any features which have been made available by an origin trial. +CORE_EXPORT const Vector<String> GetAvailableFeatures(ExecutionContext*); + +// FeaturePolicyParser is a collection of methods which are used to convert +// Feature Policy declarations, in headers and iframe attributes, into +// ParsedFeaturePolicy structs. This class encapsulates all of the logic for +// parsing feature names, origin lists, and threshold values. +// Note that code outside of /renderer/ should not be parsing policy directives +// from strings, but if necessary, should be constructing ParsedFeaturePolicy +// structs directly. +class CORE_EXPORT FeaturePolicyParser { + STATIC_ONLY(FeaturePolicyParser); + + public: + // Converts a header policy string into a vector of allowlists, one for each + // feature specified. Unrecognized features are filtered out. If |messages| is + // not null, then any message in the input will cause a warning message to be + // appended to it. The optional ExecutionContext is used to determine if any + // origin trials affect the parsing. + // Example of a feature policy string: + // "vibrate a.com b.com; fullscreen 'none'; payment 'self', payment *". + static ParsedFeaturePolicy ParseHeader( + const String& policy, + scoped_refptr<const SecurityOrigin>, + Vector<String>* messages, + ExecutionContext* execution_context = nullptr); + + // Converts a container policy string into a vector of allowlists, given self + // and src origins provided, one for each feature specified. Unrecognized + // features are filtered out. If |messages| is not null, then any message in + // the input will cause as warning message to be appended to it. Example of a + // feature policy string: + // "vibrate a.com 'src'; fullscreen 'none'; payment 'self', payment *". + static ParsedFeaturePolicy ParseAttribute( + const String& policy, + scoped_refptr<const SecurityOrigin> self_origin, + scoped_refptr<const SecurityOrigin> src_origin, + Vector<String>* messages, + Document* document = nullptr); + + // Converts a feature policy string into a vector of allowlists (see comments + // above), with an explicit FeatureNameMap. This algorithm is called by both + // header policy parsing and container policy parsing. |self_origin|, + // |src_origin|, and |execution_context| are nullable. The optional + // ExecutionContext is used to determine if any origin trials affect the + // parsing. + static ParsedFeaturePolicy Parse( + const String& policy, + scoped_refptr<const SecurityOrigin> self_origin, + scoped_refptr<const SecurityOrigin> src_origin, + Vector<String>* messages, + const FeatureNameMap& feature_names, + ExecutionContext* execution_context = nullptr); + + private: + friend void ::ParseValueForFuzzer(mojom::PolicyValueType, const String&); + static PolicyValue GetFallbackValueForFeature( + mojom::FeaturePolicyFeature feature); + static PolicyValue ParseValueForType(mojom::PolicyValueType feature_type, + const String& value_string, + bool* ok); +}; +// Returns true iff any declaration in the policy is for the given feature. +CORE_EXPORT bool IsFeatureDeclared(mojom::FeaturePolicyFeature, + const ParsedFeaturePolicy&); + +// Removes any declaration in the policy for the given feature. Returns true if +// the policy was modified. +CORE_EXPORT bool RemoveFeatureIfPresent(mojom::FeaturePolicyFeature, + ParsedFeaturePolicy&); + +// If no declaration in the policy exists already for the feature, adds a +// declaration which disallows the feature in all origins. Returns true if the +// policy was modified. +CORE_EXPORT bool DisallowFeatureIfNotPresent(mojom::FeaturePolicyFeature, + ParsedFeaturePolicy&); + +// If no declaration in the policy exists already for the feature, adds a +// declaration which allows the feature in all origins. Returns true if the +// policy was modified. +CORE_EXPORT bool AllowFeatureEverywhereIfNotPresent(mojom::FeaturePolicyFeature, + ParsedFeaturePolicy&); + +// Replaces any existing declarations in the policy for the given feature with +// a declaration which disallows the feature in all origins. +CORE_EXPORT void DisallowFeature(mojom::FeaturePolicyFeature, + ParsedFeaturePolicy&); + +// Replaces any existing declarations in the policy for the given feature with +// a declaration which allows the feature in all origins. +CORE_EXPORT void AllowFeatureEverywhere(mojom::FeaturePolicyFeature, + ParsedFeaturePolicy&); + +CORE_EXPORT const String& GetNameForFeature(mojom::FeaturePolicyFeature); + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_FEATURE_POLICY_FEATURE_POLICY_PARSER_H_
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy_test.cc b/third_party/blink/renderer/core/feature_policy/feature_policy_test.cc index be888f3..15a9333f 100644 --- a/third_party/blink/renderer/core/feature_policy/feature_policy_test.cc +++ b/third_party/blink/renderer/core/feature_policy/feature_policy_test.cc
@@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "third_party/blink/renderer/core/feature_policy/feature_policy.h" +#include "third_party/blink/renderer/core/feature_policy/feature_policy_parser.h" #include <map> @@ -125,8 +125,8 @@ Vector<String> messages; for (const char* policy_string : kValidPolicies) { messages.clear(); - ParseFeaturePolicy(policy_string, origin_a_.get(), origin_b_.get(), - &messages, test_feature_name_map); + FeaturePolicyParser::Parse(policy_string, origin_a_.get(), origin_b_.get(), + &messages, test_feature_name_map); EXPECT_EQ(0UL, messages.size()) << "Should parse " << policy_string; } } @@ -135,8 +135,8 @@ Vector<String> messages; for (const char* policy_string : kInvalidPolicies) { messages.clear(); - ParseFeaturePolicy(policy_string, origin_a_.get(), origin_b_.get(), - &messages, test_feature_name_map); + FeaturePolicyParser::Parse(policy_string, origin_a_.get(), origin_b_.get(), + &messages, test_feature_name_map); EXPECT_LT(0UL, messages.size()) << "Should fail to parse " << policy_string; } } @@ -145,14 +145,14 @@ Vector<String> messages; // Empty policy. - ParsedFeaturePolicy parsed_policy = ParseFeaturePolicy( + ParsedFeaturePolicy parsed_policy = FeaturePolicyParser::Parse( "", origin_a_.get(), origin_b_.get(), &messages, test_feature_name_map); EXPECT_EQ(0UL, parsed_policy.size()); // Simple policy with 'self'. - parsed_policy = - ParseFeaturePolicy("geolocation 'self'", origin_a_.get(), origin_b_.get(), - &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse("geolocation 'self'", + origin_a_.get(), origin_b_.get(), + &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kGeolocation, @@ -164,9 +164,9 @@ expected_url_origin_a_)); // Simple policy with *. - parsed_policy = - ParseFeaturePolicy("geolocation *", origin_a_.get(), origin_b_.get(), - &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse("geolocation *", origin_a_.get(), + origin_b_.get(), &messages, + test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kGeolocation, parsed_policy[0].feature); @@ -175,7 +175,7 @@ EXPECT_EQ(0UL, parsed_policy[0].values.size()); // Complicated policy. - parsed_policy = ParseFeaturePolicy( + parsed_policy = FeaturePolicyParser::Parse( "geolocation *; " "fullscreen https://example.net https://example.org; " "payment 'self'", @@ -201,7 +201,7 @@ expected_url_origin_a_)); // Multiple policies. - parsed_policy = ParseFeaturePolicy( + parsed_policy = FeaturePolicyParser::Parse( "geolocation * https://example.net; " "fullscreen https://example.net none https://example.org," "payment 'self' badorigin", @@ -227,9 +227,9 @@ expected_url_origin_a_)); // Header policies with no optional origin lists. - parsed_policy = - ParseFeaturePolicy("geolocation;fullscreen;payment", origin_a_.get(), - nullptr, &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse("geolocation;fullscreen;payment", + origin_a_.get(), nullptr, + &messages, test_feature_name_map); EXPECT_EQ(3UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kGeolocation, parsed_policy[0].feature); @@ -259,14 +259,14 @@ // Empty policy. ParsedFeaturePolicy parsed_policy = - ParseFeaturePolicy("", origin_a_.get(), opaque_origin.get(), &messages, - test_feature_name_map); + FeaturePolicyParser::Parse("", origin_a_.get(), opaque_origin.get(), + &messages, test_feature_name_map); EXPECT_EQ(0UL, parsed_policy.size()); // Simple policy. - parsed_policy = - ParseFeaturePolicy("geolocation", origin_a_.get(), opaque_origin.get(), - &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse("geolocation", origin_a_.get(), + opaque_origin.get(), &messages, + test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kGeolocation, @@ -276,9 +276,9 @@ EXPECT_EQ(0UL, parsed_policy[0].values.size()); // Simple policy with 'src'. - parsed_policy = - ParseFeaturePolicy("geolocation 'src'", origin_a_.get(), - opaque_origin.get(), &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse( + "geolocation 'src'", origin_a_.get(), opaque_origin.get(), &messages, + test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kGeolocation, @@ -288,9 +288,9 @@ EXPECT_EQ(0UL, parsed_policy[0].values.size()); // Simple policy with *. - parsed_policy = - ParseFeaturePolicy("geolocation *", origin_a_.get(), opaque_origin.get(), - &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse("geolocation *", origin_a_.get(), + opaque_origin.get(), &messages, + test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kGeolocation, @@ -300,7 +300,7 @@ EXPECT_EQ(0UL, parsed_policy[0].values.size()); // Policy with explicit origins - parsed_policy = ParseFeaturePolicy( + parsed_policy = FeaturePolicyParser::Parse( "geolocation https://example.net https://example.org", origin_a_.get(), opaque_origin.get(), &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); @@ -315,9 +315,9 @@ EXPECT_TRUE((++it)->first.IsSameOriginWith(expected_url_origin_c_)); // Policy with multiple origins, including 'src'. - parsed_policy = ParseFeaturePolicy("geolocation https://example.net 'src'", - origin_a_.get(), opaque_origin.get(), - &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse( + "geolocation https://example.net 'src'", origin_a_.get(), + opaque_origin.get(), &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kGeolocation, @@ -335,9 +335,9 @@ // Test no origin specified, in a container policy context. // (true) - parsed_policy = - ParseFeaturePolicy("fullscreen (true)", origin_a_.get(), origin_b_.get(), - &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse("fullscreen (true)", + origin_a_.get(), origin_b_.get(), + &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kFullscreen, parsed_policy[0].feature); EXPECT_EQ(min_value, parsed_policy[0].fallback_value); @@ -349,8 +349,9 @@ // Test no origin specified, in a header context. // (true) - parsed_policy = ParseFeaturePolicy("fullscreen (true)", origin_a_.get(), - nullptr, &messages, test_feature_name_map); + parsed_policy = + FeaturePolicyParser::Parse("fullscreen (true)", origin_a_.get(), nullptr, + &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kFullscreen, parsed_policy[0].feature); EXPECT_EQ(min_value, parsed_policy[0].fallback_value); @@ -364,9 +365,9 @@ // (true) scoped_refptr<SecurityOrigin> opaque_origin = SecurityOrigin::CreateUniqueOpaque(); - parsed_policy = - ParseFeaturePolicy("fullscreen (true)", origin_a_.get(), opaque_origin, - &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse("fullscreen (true)", + origin_a_.get(), opaque_origin, + &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kFullscreen, parsed_policy[0].feature); EXPECT_EQ(min_value, parsed_policy[0].fallback_value); @@ -374,9 +375,9 @@ EXPECT_EQ(0UL, parsed_policy[0].values.size()); // 'self'(true) - parsed_policy = - ParseFeaturePolicy("fullscreen 'self'(true)", origin_a_.get(), - origin_b_.get(), &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse("fullscreen 'self'(true)", + origin_a_.get(), origin_b_.get(), + &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kFullscreen, parsed_policy[0].feature); @@ -388,9 +389,9 @@ EXPECT_EQ(max_value, parsed_policy[0].values.begin()->second); // *(false) - parsed_policy = - ParseFeaturePolicy("fullscreen *(false)", origin_a_.get(), - origin_b_.get(), &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse("fullscreen *(false)", + origin_a_.get(), origin_b_.get(), + &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kFullscreen, parsed_policy[0].feature); @@ -399,9 +400,9 @@ EXPECT_EQ(0UL, parsed_policy[0].values.size()); // *(true) - parsed_policy = - ParseFeaturePolicy("fullscreen *(true)", origin_a_.get(), origin_b_.get(), - &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse("fullscreen *(true)", + origin_a_.get(), origin_b_.get(), + &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kFullscreen, parsed_policy[0].feature); @@ -415,9 +416,9 @@ ParsedFeaturePolicy parsed_policy; // 'self'(inf) - parsed_policy = - ParseFeaturePolicy("oversized-images 'self'(inf)", origin_a_.get(), - origin_b_.get(), &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse("oversized-images 'self'(inf)", + origin_a_.get(), origin_b_.get(), + &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages, @@ -430,9 +431,9 @@ EXPECT_EQ(max_double_value, parsed_policy[0].values.begin()->second); // 'self'(1.5) - parsed_policy = - ParseFeaturePolicy("oversized-images 'self'(1.5)", origin_a_.get(), - origin_b_.get(), &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse("oversized-images 'self'(1.5)", + origin_a_.get(), origin_b_.get(), + &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages, @@ -445,9 +446,9 @@ EXPECT_EQ(sample_double_value, parsed_policy[0].values.begin()->second); // *(inf) - parsed_policy = - ParseFeaturePolicy("oversized-images *(inf)", origin_a_.get(), - origin_b_.get(), &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse("oversized-images *(inf)", + origin_a_.get(), origin_b_.get(), + &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages, @@ -457,9 +458,9 @@ EXPECT_EQ(0UL, parsed_policy[0].values.size()); // *(0) - parsed_policy = - ParseFeaturePolicy("oversized-images *(0)", origin_a_.get(), - origin_b_.get(), &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse("oversized-images *(0)", + origin_a_.get(), origin_b_.get(), + &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages, @@ -469,9 +470,9 @@ EXPECT_EQ(0UL, parsed_policy[0].values.size()); // *(1.5) - parsed_policy = - ParseFeaturePolicy("oversized-images *(1.5)", origin_a_.get(), - origin_b_.get(), &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse("oversized-images *(1.5)", + origin_a_.get(), origin_b_.get(), + &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages, @@ -482,9 +483,9 @@ // 'self'(1.5) 'src'(inf) // Fallbacks should be default values. - parsed_policy = ParseFeaturePolicy("oversized-images 'self'(1.5) 'src'(inf)", - origin_a_.get(), origin_b_.get(), - &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse( + "oversized-images 'self'(1.5) 'src'(inf)", origin_a_.get(), + origin_b_.get(), &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages, @@ -501,9 +502,9 @@ // *(1.5) 'src'(inf) // Fallbacks should be 1.5 - parsed_policy = - ParseFeaturePolicy("oversized-images *(1.5) 'src'(inf)", origin_a_.get(), - origin_b_.get(), &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse( + "oversized-images *(1.5) 'src'(inf)", origin_a_.get(), origin_b_.get(), + &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages, @@ -517,7 +518,7 @@ // Test policy: 'self'(1.5) https://example.org(inf) // Fallbacks should be default value. - parsed_policy = ParseFeaturePolicy( + parsed_policy = FeaturePolicyParser::Parse( "oversized-images 'self'(1.5) " ORIGIN_C "(inf)", origin_a_.get(), origin_b_.get(), &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); @@ -536,7 +537,7 @@ // Test policy: 'self'(1.5) https://example.org(inf) *(0) // Fallbacks should be 0. - parsed_policy = ParseFeaturePolicy( + parsed_policy = FeaturePolicyParser::Parse( "oversized-images 'self'(1.5) " ORIGIN_C "(inf) *(0)", origin_a_.get(), origin_b_.get(), &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); @@ -559,9 +560,9 @@ ParsedFeaturePolicy parsed_policy; // 'self'(true) *(true) - parsed_policy = - ParseFeaturePolicy("fullscreen 'self'(true) *(true)", origin_a_.get(), - origin_b_.get(), &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse("fullscreen 'self'(true) *(true)", + origin_a_.get(), origin_b_.get(), + &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kFullscreen, parsed_policy[0].feature); @@ -570,9 +571,9 @@ EXPECT_EQ(0UL, parsed_policy[0].values.size()); // 'self'(false) - parsed_policy = - ParseFeaturePolicy("fullscreen 'self'(false)", origin_a_.get(), - origin_b_.get(), &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse("fullscreen 'self'(false)", + origin_a_.get(), origin_b_.get(), + &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kFullscreen, parsed_policy[0].feature); @@ -581,9 +582,9 @@ EXPECT_EQ(0UL, parsed_policy[0].values.size()); // (true) - parsed_policy = - ParseFeaturePolicy("fullscreen (false)", origin_a_.get(), origin_b_.get(), - &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse("fullscreen (false)", + origin_a_.get(), origin_b_.get(), + &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kFullscreen, parsed_policy[0].feature); EXPECT_EQ(min_value, parsed_policy[0].fallback_value); @@ -596,9 +597,9 @@ ParsedFeaturePolicy parsed_policy; // 'self'(1.5) *(1.5) - parsed_policy = - ParseFeaturePolicy("oversized-images 'self'(1.5) *(1.5)", origin_a_.get(), - origin_b_.get(), &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse( + "oversized-images 'self'(1.5) *(1.5)", origin_a_.get(), origin_b_.get(), + &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages, @@ -608,9 +609,9 @@ EXPECT_EQ(0UL, parsed_policy[0].values.size()); // 'self'(inf) - parsed_policy = - ParseFeaturePolicy("oversized-images 'self'(2.0)", origin_a_.get(), - origin_b_.get(), &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse("oversized-images 'self'(2.0)", + origin_a_.get(), origin_b_.get(), + &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages, @@ -620,9 +621,9 @@ EXPECT_EQ(0UL, parsed_policy[0].values.size()); // (inf) - parsed_policy = - ParseFeaturePolicy("oversized-images (2.0)", origin_a_.get(), - origin_b_.get(), &messages, test_feature_name_map); + parsed_policy = FeaturePolicyParser::Parse("oversized-images (2.0)", + origin_a_.get(), origin_b_.get(), + &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages, parsed_policy[0].feature); @@ -637,8 +638,8 @@ HistogramTester tester; Vector<String> messages; - ParseFeaturePolicy("payment; fullscreen", origin_a_.get(), nullptr, &messages, - test_feature_name_map); + FeaturePolicyParser::Parse("payment; fullscreen", origin_a_.get(), nullptr, + &messages, test_feature_name_map); tester.ExpectTotalCount(histogram_name, 2); tester.ExpectBucketCount( histogram_name, @@ -656,11 +657,11 @@ // If the same feature is listed multiple times, it should only be counted // once. - ParseFeaturePolicy("geolocation 'self'; payment; geolocation *", - origin_a_.get(), nullptr, &messages, - test_feature_name_map); - ParseFeaturePolicy("fullscreen 'self', fullscreen *", origin_a_.get(), - nullptr, &messages, test_feature_name_map); + FeaturePolicyParser::Parse("geolocation 'self'; payment; geolocation *", + origin_a_.get(), nullptr, &messages, + test_feature_name_map); + FeaturePolicyParser::Parse("fullscreen 'self', fullscreen *", origin_a_.get(), + nullptr, &messages, test_feature_name_map); tester.ExpectTotalCount(histogram_name, 3); tester.ExpectBucketCount( histogram_name, @@ -678,11 +679,12 @@ Vector<String> messages; auto dummy = std::make_unique<DummyPageHolder>(); - ParseFeaturePolicy("payment; fullscreen", origin_a_.get(), origin_b_.get(), - &messages, test_feature_name_map, &dummy->GetDocument()); - ParseFeaturePolicy("fullscreen; geolocation", origin_a_.get(), - origin_b_.get(), &messages, test_feature_name_map, - &dummy->GetDocument()); + FeaturePolicyParser::Parse("payment; fullscreen", origin_a_.get(), + origin_b_.get(), &messages, test_feature_name_map, + &dummy->GetDocument()); + FeaturePolicyParser::Parse("fullscreen; geolocation", origin_a_.get(), + origin_b_.get(), &messages, test_feature_name_map, + &dummy->GetDocument()); tester.ExpectTotalCount(histogram_name, 3); tester.ExpectBucketCount( histogram_name, @@ -704,11 +706,12 @@ auto dummy = std::make_unique<DummyPageHolder>(); auto dummy2 = std::make_unique<DummyPageHolder>(); - ParseFeaturePolicy("payment; fullscreen", origin_a_.get(), origin_b_.get(), - &messages, test_feature_name_map, &dummy->GetDocument()); - ParseFeaturePolicy("fullscreen; geolocation", origin_a_.get(), - origin_b_.get(), &messages, test_feature_name_map, - &dummy2->GetDocument()); + FeaturePolicyParser::Parse("payment; fullscreen", origin_a_.get(), + origin_b_.get(), &messages, test_feature_name_map, + &dummy->GetDocument()); + FeaturePolicyParser::Parse("fullscreen; geolocation", origin_a_.get(), + origin_b_.get(), &messages, test_feature_name_map, + &dummy2->GetDocument()); tester.ExpectTotalCount(histogram_name, 4); tester.ExpectBucketCount( histogram_name, @@ -728,9 +731,9 @@ SecurityOrigin::CreateUniqueOpaque(); // Simple policy with *. - ParsedFeaturePolicy parsed_policy = - ParseFeaturePolicy("oversized-images *", origin_a_.get(), - opaque_origin.get(), &messages, test_feature_name_map); + ParsedFeaturePolicy parsed_policy = FeaturePolicyParser::Parse( + "oversized-images *", origin_a_.get(), opaque_origin.get(), &messages, + test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kOversizedImages, parsed_policy[0].feature); @@ -739,7 +742,7 @@ EXPECT_EQ(0UL, parsed_policy[0].values.size()); // Policy with explicit origins - parsed_policy = ParseFeaturePolicy( + parsed_policy = FeaturePolicyParser::Parse( "oversized-images https://example.net 'src'", origin_a_.get(), opaque_origin.get(), &messages, test_feature_name_map); EXPECT_EQ(1UL, parsed_policy.size());
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy_value.dict b/third_party/blink/renderer/core/feature_policy/feature_policy_value.dict new file mode 100644 index 0000000..569ac1c5 --- /dev/null +++ b/third_party/blink/renderer/core/feature_policy/feature_policy_value.dict
@@ -0,0 +1,10 @@ +# 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. + +"true" +"false" +"inf" +"0" +"1" +".0"
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy_value_corpus/1 b/third_party/blink/renderer/core/feature_policy/feature_policy_value_corpus/1 new file mode 100644 index 0000000..d3827e7 --- /dev/null +++ b/third_party/blink/renderer/core/feature_policy/feature_policy_value_corpus/1
@@ -0,0 +1 @@ +1.0
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy_value_corpus/2 b/third_party/blink/renderer/core/feature_policy/feature_policy_value_corpus/2 new file mode 100644 index 0000000..27ba77d --- /dev/null +++ b/third_party/blink/renderer/core/feature_policy/feature_policy_value_corpus/2
@@ -0,0 +1 @@ +true
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy_value_corpus/3 b/third_party/blink/renderer/core/feature_policy/feature_policy_value_corpus/3 new file mode 100644 index 0000000..c508d53 --- /dev/null +++ b/third_party/blink/renderer/core/feature_policy/feature_policy_value_corpus/3
@@ -0,0 +1 @@ +false
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy_value_corpus/4 b/third_party/blink/renderer/core/feature_policy/feature_policy_value_corpus/4 new file mode 100644 index 0000000..730c31b --- /dev/null +++ b/third_party/blink/renderer/core/feature_policy/feature_policy_value_corpus/4
@@ -0,0 +1 @@ +0.000001
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy_value_corpus/5 b/third_party/blink/renderer/core/feature_policy/feature_policy_value_corpus/5 new file mode 100644 index 0000000..8484d06 --- /dev/null +++ b/third_party/blink/renderer/core/feature_policy/feature_policy_value_corpus/5
@@ -0,0 +1 @@ +inf
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy_value_corpus/6 b/third_party/blink/renderer/core/feature_policy/feature_policy_value_corpus/6 new file mode 100644 index 0000000..b8626c4c --- /dev/null +++ b/third_party/blink/renderer/core/feature_policy/feature_policy_value_corpus/6
@@ -0,0 +1 @@ +4
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy_value_fuzzer.cc b/third_party/blink/renderer/core/feature_policy/feature_policy_value_fuzzer.cc new file mode 100644 index 0000000..7f8e6aa --- /dev/null +++ b/third_party/blink/renderer/core/feature_policy/feature_policy_value_fuzzer.cc
@@ -0,0 +1,31 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/core/feature_policy/feature_policy_parser.h" + +#include <stddef.h> +#include <stdint.h> +#include <memory> +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h" +#include "third_party/blink/renderer/platform/weborigin/security_origin.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" + +void ParseValueForFuzzer(blink::mojom::PolicyValueType feature_type, + const WTF::String& value_string) { + bool ok; + blink::FeaturePolicyParser::ParseValueForType(feature_type, value_string, + &ok); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + static blink::BlinkFuzzerTestSupport test_support = + blink::BlinkFuzzerTestSupport(); + ParseValueForFuzzer(blink::mojom::PolicyValueType::kBool, + WTF::String(data, size)); + ParseValueForFuzzer(blink::mojom::PolicyValueType::kDecDouble, + WTF::String(data, size)); + return 0; +}
diff --git a/third_party/blink/renderer/core/feature_policy/layout_animations_policy.cc b/third_party/blink/renderer/core/feature_policy/layout_animations_policy.cc index 3722ff9..3b08064 100644 --- a/third_party/blink/renderer/core/feature_policy/layout_animations_policy.cc +++ b/third_party/blink/renderer/core/feature_policy/layout_animations_policy.cc
@@ -6,7 +6,7 @@ #include "third_party/blink/renderer/core/css/properties/css_property.h" #include "third_party/blink/renderer/core/execution_context/security_context.h" -#include "third_party/blink/renderer/core/feature_policy/feature_policy.h" +#include "third_party/blink/renderer/core/feature_policy/feature_policy_parser.h" #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" namespace blink {
diff --git a/third_party/blink/renderer/core/feature_policy/policy_test.cc b/third_party/blink/renderer/core/feature_policy/policy_test.cc index b7a13b6e..2e990590 100644 --- a/third_party/blink/renderer/core/feature_policy/policy_test.cc +++ b/third_party/blink/renderer/core/feature_policy/policy_test.cc
@@ -8,7 +8,7 @@ #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/renderer/core/dom/document.h" -#include "third_party/blink/renderer/core/feature_policy/feature_policy.h" +#include "third_party/blink/renderer/core/feature_policy/feature_policy_parser.h" #include "third_party/blink/renderer/platform/weborigin/security_origin.h" namespace blink { @@ -154,7 +154,7 @@ } TEST_F(IFramePolicyTest, TestCombinedPolicy) { - ParsedFeaturePolicy container_policy = ParseFeaturePolicyAttribute( + ParsedFeaturePolicy container_policy = FeaturePolicyParser::ParseAttribute( "geolocation 'src'; payment 'none'; midi; camera 'src'", SecurityOrigin::CreateFromString(kSelfOrigin), SecurityOrigin::CreateFromString(kOriginA), nullptr);
diff --git a/third_party/blink/renderer/core/frame/frame.cc b/third_party/blink/renderer/core/frame/frame.cc index 2d66f4d..f3a50ae 100644 --- a/third_party/blink/renderer/core/frame/frame.cc +++ b/third_party/blink/renderer/core/frame/frame.cc
@@ -46,7 +46,6 @@ #include "third_party/blink/renderer/core/input/event_handler.h" #include "third_party/blink/renderer/core/layout/layout_embedded_content.h" #include "third_party/blink/renderer/core/loader/empty_clients.h" -#include "third_party/blink/renderer/core/loader/navigation_scheduler.h" #include "third_party/blink/renderer/core/page/focus_controller.h" #include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/core/probe/core_probes.h"
diff --git a/third_party/blink/renderer/core/frame/history.cc b/third_party/blink/renderer/core/frame/history.cc index 5ece476..519e1bd6 100644 --- a/third_party/blink/renderer/core/frame/history.cc +++ b/third_party/blink/renderer/core/frame/history.cc
@@ -36,7 +36,6 @@ #include "third_party/blink/renderer/core/loader/document_loader.h" #include "third_party/blink/renderer/core/loader/frame_loader.h" #include "third_party/blink/renderer/core/loader/history_item.h" -#include "third_party/blink/renderer/core/loader/navigation_scheduler.h" #include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h" #include "third_party/blink/renderer/platform/bindings/script_state.h"
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc index 6bc973f1..c1bd6b3 100644 --- a/third_party/blink/renderer/core/frame/local_frame.cc +++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -89,7 +89,6 @@ #include "third_party/blink/renderer/core/loader/document_loader.h" #include "third_party/blink/renderer/core/loader/frame_load_request.h" #include "third_party/blink/renderer/core/loader/idleness_detector.h" -#include "third_party/blink/renderer/core/loader/navigation_scheduler.h" #include "third_party/blink/renderer/core/loader/previews_resource_loading_hints_receiver_impl.h" #include "third_party/blink/renderer/core/page/drag_controller.h" #include "third_party/blink/renderer/core/page/focus_controller.h" @@ -300,7 +299,6 @@ visitor->Trace(idleness_detector_); visitor->Trace(inspector_trace_events_); visitor->Trace(loader_); - visitor->Trace(navigation_scheduler_); visitor->Trace(view_); visitor->Trace(dom_window_); visitor->Trace(page_popup_owner_); @@ -925,7 +923,6 @@ IsMainFrame() ? FrameScheduler::FrameType::kMainFrame : FrameScheduler::FrameType::kSubframe)), loader_(this), - navigation_scheduler_(MakeGarbageCollected<NavigationScheduler>(this)), script_controller_(MakeGarbageCollected<ScriptController>( *this, *static_cast<LocalWindowProxyManager*>(GetWindowProxyManager()))),
diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/blink/renderer/core/frame/local_frame.h index 6e5b813..a99145ea 100644 --- a/third_party/blink/renderer/core/frame/local_frame.h +++ b/third_party/blink/renderer/core/frame/local_frame.h
@@ -90,7 +90,6 @@ class LocalDOMWindow; class LocalWindowProxy; class LocalFrameClient; -class NavigationScheduler; class Node; class NodeTraversal; class PerformanceMonitor; @@ -176,7 +175,6 @@ EventHandler& GetEventHandler() const; EventHandlerRegistry& GetEventHandlerRegistry() const; FrameLoader& Loader() const; - NavigationScheduler& GetNavigationScheduler() const; FrameSelection& Selection() const; InputMethodController& GetInputMethodController() const; TextSuggestionController& GetTextSuggestionController() const; @@ -490,7 +488,6 @@ pause_handle_bindings_; mutable FrameLoader loader_; - Member<NavigationScheduler> navigation_scheduler_; // Cleared by LocalFrame::detach(), so as to keep the observable lifespan // of LocalFrame::view(). @@ -572,11 +569,6 @@ return loader_; } -inline NavigationScheduler& LocalFrame::GetNavigationScheduler() const { - DCHECK(navigation_scheduler_); - return *navigation_scheduler_.Get(); -} - inline LocalFrameView* LocalFrame::View() const { return view_.Get(); }
diff --git a/third_party/blink/renderer/core/frame/picture_in_picture_controller.h b/third_party/blink/renderer/core/frame/picture_in_picture_controller.h index 32eb893..0113c2e 100644 --- a/third_party/blink/renderer/core/frame/picture_in_picture_controller.h +++ b/third_party/blink/renderer/core/frame/picture_in_picture_controller.h
@@ -11,7 +11,9 @@ namespace blink { +class HTMLElement; class HTMLVideoElement; +class PictureInPictureOptions; class ScriptPromiseResolver; // PictureInPictureController allows to know if Picture-in-Picture is allowed @@ -51,13 +53,19 @@ virtual void EnterPictureInPicture(HTMLVideoElement*, ScriptPromiseResolver*) = 0; + // Enter Picture-in-Picture for an element (except a video element) with + // options if any and resolve promise if any. + virtual void EnterPictureInPicture(HTMLElement*, + PictureInPictureOptions*, + ScriptPromiseResolver*) = 0; + // Exit Picture-in-Picture for a video element and resolve promise if any. virtual void ExitPictureInPicture(HTMLVideoElement*, ScriptPromiseResolver*) = 0; - // Returns whether a given video element in a document associated with the + // Returns whether a given element in a document associated with the // controller is allowed to request Picture-in-Picture. - virtual Status IsElementAllowed(const HTMLVideoElement&) const = 0; + virtual Status IsElementAllowed(const HTMLElement&) const = 0; // Should be called when an element has exited Picture-in-Picture. virtual void OnExitedPictureInPicture(ScriptPromiseResolver*) = 0;
diff --git a/third_party/blink/renderer/core/frame/sandbox_flags.cc b/third_party/blink/renderer/core/frame/sandbox_flags.cc index 86bbc00..7425a5e 100644 --- a/third_party/blink/renderer/core/frame/sandbox_flags.cc +++ b/third_party/blink/renderer/core/frame/sandbox_flags.cc
@@ -27,7 +27,7 @@ #include "third_party/blink/renderer/core/frame/sandbox_flags.h" #include "third_party/blink/public/common/frame/sandbox_flags.h" -#include "third_party/blink/renderer/core/feature_policy/feature_policy.h" +#include "third_party/blink/renderer/core/feature_policy/feature_policy_parser.h" #include "third_party/blink/renderer/core/html/html_iframe_element.h" #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc index bbeaed0..33be66c 100644 --- a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc +++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
@@ -228,7 +228,6 @@ #include "third_party/blink/renderer/core/loader/frame_loader.h" #include "third_party/blink/renderer/core/loader/history_item.h" #include "third_party/blink/renderer/core/loader/mixed_content_checker.h" -#include "third_party/blink/renderer/core/loader/navigation_scheduler.h" #include "third_party/blink/renderer/core/messaging/message_port.h" #include "third_party/blink/renderer/core/page/context_menu_controller.h" #include "third_party/blink/renderer/core/page/focus_controller.h" @@ -2195,9 +2194,8 @@ bool WebLocalFrameImpl::IsNavigationScheduledWithin( double interval_in_seconds) const { - return GetFrame() && - GetFrame()->GetNavigationScheduler().IsNavigationScheduledWithin( - interval_in_seconds); + return GetFrame() && GetFrame()->GetDocument()->IsHttpRefreshScheduledWithin( + interval_in_seconds); } void WebLocalFrameImpl::SetCommittedFirstRealLoad() { @@ -2568,7 +2566,9 @@ DCHECK(media_element->IsHTMLVideoElement()); if (action.enable) { PictureInPictureController::From(node->GetDocument()) - .EnterPictureInPicture(ToHTMLVideoElement(media_element), nullptr); + .EnterPictureInPicture(ToHTMLVideoElement(media_element), + nullptr /* promise */, + nullptr /* options */); } else { PictureInPictureController::From(node->GetDocument()) .ExitPictureInPicture(ToHTMLVideoElement(media_element), nullptr);
diff --git a/third_party/blink/renderer/core/html/forms/html_form_element.cc b/third_party/blink/renderer/core/html/forms/html_form_element.cc index 745e79e..58490070 100644 --- a/third_party/blink/renderer/core/html/forms/html_form_element.cc +++ b/third_party/blink/renderer/core/html/forms/html_form_element.cc
@@ -64,7 +64,6 @@ #include "third_party/blink/renderer/core/layout/layout_object.h" #include "third_party/blink/renderer/core/loader/form_submission.h" #include "third_party/blink/renderer/core/loader/mixed_content_checker.h" -#include "third_party/blink/renderer/core/loader/navigation_scheduler.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h" #include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
diff --git a/third_party/blink/renderer/core/html/html_frame_owner_element.h b/third_party/blink/renderer/core/html/html_frame_owner_element.h index 36f8bf7..2a526e95 100644 --- a/third_party/blink/renderer/core/html/html_frame_owner_element.h +++ b/third_party/blink/renderer/core/html/html_frame_owner_element.h
@@ -24,7 +24,7 @@ #include "third_party/blink/public/common/frame/frame_owner_element_type.h" #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/dom/document.h" -#include "third_party/blink/renderer/core/feature_policy/feature_policy.h" +#include "third_party/blink/renderer/core/feature_policy/feature_policy_parser.h" #include "third_party/blink/renderer/core/frame/dom_window.h" #include "third_party/blink/renderer/core/frame/embedded_content_view.h" #include "third_party/blink/renderer/core/frame/frame_owner.h"
diff --git a/third_party/blink/renderer/core/html/html_iframe_element.cc b/third_party/blink/renderer/core/html/html_iframe_element.cc index 1bb9368..7d866f9e 100644 --- a/third_party/blink/renderer/core/html/html_iframe_element.cc +++ b/third_party/blink/renderer/core/html/html_iframe_element.cc
@@ -29,6 +29,7 @@ #include "third_party/blink/renderer/core/css/css_property_names.h" #include "third_party/blink/renderer/core/css/style_change_reason.h" #include "third_party/blink/renderer/core/dom/element.h" +#include "third_party/blink/renderer/core/feature_policy/feature_policy_parser.h" #include "third_party/blink/renderer/core/feature_policy/iframe_policy.h" #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" #include "third_party/blink/renderer/core/frame/sandbox_flags.h" @@ -282,7 +283,7 @@ GetDocument().GetSecurityOrigin(); // Start with the allow attribute - ParsedFeaturePolicy container_policy = ParseFeaturePolicyAttribute( + ParsedFeaturePolicy container_policy = FeaturePolicyParser::ParseAttribute( allow_, self_origin, src_origin, messages, &GetDocument()); // Next, process sandbox flags. These all only take effect if a corresponding
diff --git a/third_party/blink/renderer/core/html/html_iframe_element_test.cc b/third_party/blink/renderer/core/html/html_iframe_element_test.cc index 9ab7b17..72a13c5 100644 --- a/third_party/blink/renderer/core/html/html_iframe_element_test.cc +++ b/third_party/blink/renderer/core/html/html_iframe_element_test.cc
@@ -6,7 +6,7 @@ #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/renderer/core/dom/document.h" -#include "third_party/blink/renderer/core/feature_policy/feature_policy.h" +#include "third_party/blink/renderer/core/feature_policy/feature_policy_parser.h" #include "third_party/blink/renderer/platform/heap/heap.h" #include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
diff --git a/third_party/blink/renderer/core/html/parser/html_document_parser.cc b/third_party/blink/renderer/core/html/parser/html_document_parser.cc index 55bfe09..403501f 100644 --- a/third_party/blink/renderer/core/html/parser/html_document_parser.cc +++ b/third_party/blink/renderer/core/html/parser/html_document_parser.cc
@@ -48,7 +48,6 @@ #include "third_party/blink/renderer/core/html_names.h" #include "third_party/blink/renderer/core/inspector/inspector_trace_events.h" #include "third_party/blink/renderer/core/loader/document_loader.h" -#include "third_party/blink/renderer/core/loader/navigation_scheduler.h" #include "third_party/blink/renderer/core/loader/preload_helper.h" #include "third_party/blink/renderer/core/probe/core_probes.h" #include "third_party/blink/renderer/core/script/html_parser_script_runner.h"
diff --git a/third_party/blink/renderer/core/html/parser/xss_auditor_delegate.cc b/third_party/blink/renderer/core/html/parser/xss_auditor_delegate.cc index cf2e8cd9..e5c87dd 100644 --- a/third_party/blink/renderer/core/html/parser/xss_auditor_delegate.cc +++ b/third_party/blink/renderer/core/html/parser/xss_auditor_delegate.cc
@@ -32,7 +32,6 @@ #include "third_party/blink/renderer/core/inspector/console_message.h" #include "third_party/blink/renderer/core/loader/document_loader.h" #include "third_party/blink/renderer/core/loader/frame_loader.h" -#include "third_party/blink/renderer/core/loader/navigation_scheduler.h" #include "third_party/blink/renderer/core/loader/ping_loader.h" #include "third_party/blink/renderer/platform/json/json_values.h" #include "third_party/blink/renderer/platform/loader/fetch/resource_error.h"
diff --git a/third_party/blink/renderer/core/inspector/inspector_css_agent.cc b/third_party/blink/renderer/core/inspector/inspector_css_agent.cc index 2e8214f..c9b3820f 100644 --- a/third_party/blink/renderer/core/inspector/inspector_css_agent.cc +++ b/third_party/blink/renderer/core/inspector/inspector_css_agent.cc
@@ -2358,10 +2358,12 @@ MakeGarbageCollected<CSSComputedStyleDeclaration>(element, true); const CSSValue* font_size = computed_style_info->GetPropertyCSSValue(GetCSSPropertyFontSize()); - *computed_font_size = font_size->CssText(); + if (font_size) + *computed_font_size = font_size->CssText(); const CSSValue* font_weight = computed_style_info->GetPropertyCSSValue(GetCSSPropertyFontWeight()); - *computed_font_weight = font_weight->CssText(); + if (font_weight) + *computed_font_weight = font_weight->CssText(); } void InspectorCSSAgent::SetCoverageEnabled(bool enabled) {
diff --git a/third_party/blink/renderer/core/inspector/inspector_page_agent.cc b/third_party/blink/renderer/core/inspector/inspector_page_agent.cc index 97e0828..7df717e1 100644 --- a/third_party/blink/renderer/core/inspector/inspector_page_agent.cc +++ b/third_party/blink/renderer/core/inspector/inspector_page_agent.cc
@@ -65,7 +65,6 @@ #include "third_party/blink/renderer/core/loader/idleness_detector.h" #include "third_party/blink/renderer/core/loader/resource/css_style_sheet_resource.h" #include "third_party/blink/renderer/core/loader/resource/script_resource.h" -#include "third_party/blink/renderer/core/loader/scheduled_navigation.h" #include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" #include "third_party/blink/renderer/core/probe/core_probes.h"
diff --git a/third_party/blink/renderer/core/layout/ng/ng_text_decoration_offset.cc b/third_party/blink/renderer/core/layout/ng/ng_text_decoration_offset.cc index 9d7badb..559bef2 100644 --- a/third_party/blink/renderer/core/layout/ng/ng_text_decoration_offset.cc +++ b/third_party/blink/renderer/core/layout/ng/ng_text_decoration_offset.cc
@@ -20,7 +20,6 @@ FontBaseline baseline_type = style.GetFontBaseline(); if (decorating_box_) { - // TODO(eae): Replace with actual baseline once available. NGBaselineRequest baseline_request = { NGBaselineAlgorithmType::kAtomicInline, FontBaseline::kIdeographicBaseline};
diff --git a/third_party/blink/renderer/core/loader/BUILD.gn b/third_party/blink/renderer/core/loader/BUILD.gn index d939370..00032678 100644 --- a/third_party/blink/renderer/core/loader/BUILD.gn +++ b/third_party/blink/renderer/core/loader/BUILD.gn
@@ -41,6 +41,8 @@ "history_item.h", "http_equiv.cc", "http_equiv.h", + "http_refresh_scheduler.cc", + "http_refresh_scheduler.h", "idleness_detector.cc", "idleness_detector.h", "image_loader.cc", @@ -85,8 +87,6 @@ "modulescript/worklet_module_script_fetcher.h", "navigation_policy.cc", "navigation_policy.h", - "navigation_scheduler.cc", - "navigation_scheduler.h", "ping_loader.cc", "ping_loader.h", "preload_helper.cc", @@ -129,8 +129,6 @@ "resource_load_observer_for_frame.h", "resource_load_observer_for_worker.cc", "resource_load_observer_for_worker.h", - "scheduled_navigation.cc", - "scheduled_navigation.h", "subresource_filter.cc", "subresource_filter.h", "subresource_integrity_helper.cc",
diff --git a/third_party/blink/renderer/core/loader/frame_loader.cc b/third_party/blink/renderer/core/loader/frame_loader.cc index 8e28edac..c13fce6aa 100644 --- a/third_party/blink/renderer/core/loader/frame_loader.cc +++ b/third_party/blink/renderer/core/loader/frame_loader.cc
@@ -76,7 +76,6 @@ #include "third_party/blink/renderer/core/loader/form_submission.h" #include "third_party/blink/renderer/core/loader/frame_load_request.h" #include "third_party/blink/renderer/core/loader/mixed_content_checker.h" -#include "third_party/blink/renderer/core/loader/navigation_scheduler.h" #include "third_party/blink/renderer/core/loader/progress_tracker.h" #include "third_party/blink/renderer/core/page/chrome_client.h" #include "third_party/blink/renderer/core/page/frame_tree.h" @@ -227,8 +226,6 @@ document_loader_->SetDefersLoading(defers); if (provisional_document_loader_) provisional_document_loader_->SetDefersLoading(defers); - if (!defers) - frame_->GetNavigationScheduler().StartTimer(); } void FrameLoader::SaveScrollAnchor() { @@ -906,8 +903,6 @@ RecordLatestRequiredCSP(); if (!CancelProvisionalLoaderForNewNavigation( - false /* cancel_scheduled_navigations */, - DocumentLoader::WillLoadUrlAsEmpty(navigation_params->url), false /* is_form_submission */)) { return; } @@ -954,11 +949,8 @@ bool FrameLoader::CreatePlaceholderDocumentLoader( const WebNavigationInfo& info, std::unique_ptr<WebDocumentLoader::ExtraData> extra_data) { - if (!CancelProvisionalLoaderForNewNavigation( - true /* cancel_scheduled_navigations */, - false /* is_starting_blank_navigation */, !info.form.IsNull())) { + if (!CancelProvisionalLoaderForNewNavigation(!info.form.IsNull())) return false; - } progress_tracker_->ProgressStarted(); @@ -998,7 +990,6 @@ if (document_loader_) document_loader_->StopLoading(); DetachDocumentLoader(provisional_document_loader_); - frame_->GetNavigationScheduler().Cancel(); DidFinishNavigation(); TakeObjectSnapshot(); @@ -1101,18 +1092,6 @@ return; Client()->TransitionToCommittedForNewPage(); - - // If this is an about:blank navigation committing asynchronously, don't - // cancel scheduled navigations, so that the scheduled navigation still goes - // through. This handles the case where a navigation is scheduled between the - // about:blank navigation starting and finishing, where previously it would - // have happened after about:blank completed. - // TODO(japhet): This is an atrocious hack. Get rid of NavigationScheduler - // so it isn't needed. - if (!state_machine_.CommittedFirstRealDocumentLoad() || - !DocumentLoader::WillLoadUrlAsEmpty(document_loader_->Url())) { - frame_->GetNavigationScheduler().Cancel(); - } } void FrameLoader::RestoreScrollPositionAndViewState() { @@ -1244,7 +1223,6 @@ provisional_document_loader_->SetSentDidFinishLoad(); DetachDocumentLoader(provisional_document_loader_); } - frame_->GetNavigationScheduler().Cancel(); DidFinishNavigation(); if (progress_tracker_) { @@ -1419,8 +1397,6 @@ } bool FrameLoader::CancelProvisionalLoaderForNewNavigation( - bool cancel_scheduled_navigations, - bool is_starting_blank_navigation, bool is_form_submission) { bool had_placeholder_client_document_loader = provisional_document_loader_ && !provisional_document_loader_->DidStart(); @@ -1446,24 +1422,6 @@ // can be used to detach this frame. if (!frame_->GetPage()) return false; - - // If this is an about:blank navigation committing asynchronously, don't - // cancel scheduled navigations, so that the scheduled navigation still goes - // through. This handles the case where a navigation is scheduled between the - // about:blank navigation starting and finishing, where previously it would - // have happened after about:blank completed. - // TODO(japhet): This is an atrocious hack. Get rid of NavigationScheduler - // so it isn't needed. - bool skip_cancel_for_about_blank = - state_machine_.CommittedFirstRealDocumentLoad() && - is_starting_blank_navigation; - // We need to ensure that script initiated navigations are honored. - if (!skip_cancel_for_about_blank && - (!had_placeholder_client_document_loader || - cancel_scheduled_navigations)) { - frame_->GetNavigationScheduler().Cancel(); - } - return true; }
diff --git a/third_party/blink/renderer/core/loader/frame_loader.h b/third_party/blink/renderer/core/loader/frame_loader.h index ec012d0d..82ff7f3 100644 --- a/third_party/blink/renderer/core/loader/frame_loader.h +++ b/third_party/blink/renderer/core/loader/frame_loader.h
@@ -237,8 +237,6 @@ // Returns whether we should continue with new navigation. bool CancelProvisionalLoaderForNewNavigation( - bool cancel_scheduled_navigations, - bool is_starting_blank_navigation, bool is_form_submission); void RestoreScrollPositionAndViewState(WebFrameLoadType,
diff --git a/third_party/blink/renderer/core/loader/http_refresh_scheduler.cc b/third_party/blink/renderer/core/loader/http_refresh_scheduler.cc new file mode 100644 index 0000000..ad310ed --- /dev/null +++ b/third_party/blink/renderer/core/loader/http_refresh_scheduler.cc
@@ -0,0 +1,145 @@ +/* + * Copyright (C) 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) + * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. + * (http://www.torchmobile.com/) + * Copyright (C) 2009 Adam Barth. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/core/loader/http_refresh_scheduler.h" + +#include <memory> +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/renderer/core/events/current_input_event.h" +#include "third_party/blink/renderer/core/frame/local_frame.h" +#include "third_party/blink/renderer/core/loader/frame_load_request.h" +#include "third_party/blink/renderer/core/loader/frame_loader.h" +#include "third_party/blink/renderer/core/probe/core_probes.h" +#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +namespace blink { + +static ClientNavigationReason ToReason( + Document::HttpRefreshType http_refresh_type) { + switch (http_refresh_type) { + case Document::HttpRefreshType::kHttpRefreshFromHeader: + return ClientNavigationReason::kHttpHeaderRefresh; + case Document::HttpRefreshType::kHttpRefreshFromMetaTag: + return ClientNavigationReason::kMetaTagRefresh; + default: + break; + } + NOTREACHED(); + return ClientNavigationReason::kMetaTagRefresh; +} + +HttpRefreshScheduler::HttpRefreshScheduler(Document* document) + : document_(document) {} + +bool HttpRefreshScheduler::IsScheduledWithin(double interval) const { + return refresh_ && refresh_->delay <= interval; +} + +void HttpRefreshScheduler::Schedule( + double delay, + const KURL& url, + Document::HttpRefreshType http_refresh_type) { + DCHECK(document_->GetFrame()); + if (!document_->GetFrame()->IsNavigationAllowed()) + return; + if (delay < 0 || delay > INT_MAX / 1000) + return; + if (url.IsEmpty()) + return; + if (refresh_ && refresh_->delay < delay) + return; + + base::TimeTicks timestamp; + if (const WebInputEvent* input_event = CurrentInputEvent::Get()) + timestamp = input_event->TimeStamp(); + + Cancel(); + refresh_ = std::make_unique<ScheduledHttpRefresh>( + delay, url, ToReason(http_refresh_type), timestamp); + MaybeStartTimer(); +} + +void HttpRefreshScheduler::NavigateTask() { + DCHECK(document_->GetFrame()); + std::unique_ptr<ScheduledHttpRefresh> refresh(refresh_.release()); + + FrameLoadRequest request(document_, ResourceRequest(refresh->url), "_self"); + request.SetInputStartTime(refresh->input_timestamp); + request.SetClientRedirectReason(refresh->reason); + + // We want a new back/forward list item if the refresh timeout is > 1 second. + WebFrameLoadType load_type = WebFrameLoadType::kStandard; + if (EqualIgnoringFragmentIdentifier(document_->Url(), refresh->url)) { + request.GetResourceRequest().SetCacheMode( + mojom::FetchCacheMode::kValidateCache); + load_type = WebFrameLoadType::kReload; + } else if (refresh->delay <= 1) { + load_type = WebFrameLoadType::kReplaceCurrentItem; + } + + document_->GetFrame()->Loader().StartNavigation(request, load_type); + probe::FrameClearedScheduledNavigation(document_->GetFrame()); +} + +void HttpRefreshScheduler::MaybeStartTimer() { + if (!refresh_) + return; + if (navigate_task_handle_.IsActive()) + return; + if (!document_->LoadEventFinished()) + return; + + // wrapWeakPersistent(this) is safe because a posted task is canceled when the + // task handle is destroyed on the dtor of this HttpRefreshScheduler. + navigate_task_handle_ = PostDelayedCancellableTask( + *document_->GetTaskRunner(TaskType::kInternalLoading), FROM_HERE, + WTF::Bind(&HttpRefreshScheduler::NavigateTask, WrapWeakPersistent(this)), + TimeDelta::FromSecondsD(refresh_->delay)); + + probe::FrameScheduledNavigation(document_->GetFrame(), refresh_->url, + refresh_->delay, refresh_->reason); +} + +void HttpRefreshScheduler::Cancel() { + if (navigate_task_handle_.IsActive()) { + probe::FrameClearedScheduledNavigation(document_->GetFrame()); + } + navigate_task_handle_.Cancel(); + refresh_.reset(); +} + +void HttpRefreshScheduler::Trace(blink::Visitor* visitor) { + visitor->Trace(document_); +} + +} // namespace blink
diff --git a/third_party/blink/renderer/core/loader/navigation_scheduler.h b/third_party/blink/renderer/core/loader/http_refresh_scheduler.h similarity index 65% rename from third_party/blink/renderer/core/loader/navigation_scheduler.h rename to third_party/blink/renderer/core/loader/http_refresh_scheduler.h index 9387998..f511b98 100644 --- a/third_party/blink/renderer/core/loader/navigation_scheduler.h +++ b/third_party/blink/renderer/core/loader/http_refresh_scheduler.h
@@ -29,59 +29,62 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_NAVIGATION_SCHEDULER_H_ -#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_NAVIGATION_SCHEDULER_H_ +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_HTTP_REFRESH_SCHEDULER_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_HTTP_REFRESH_SCHEDULER_H_ #include <memory> #include "base/macros.h" -#include "base/memory/scoped_refptr.h" -#include "third_party/blink/public/platform/scheduler/web_thread_scheduler.h" #include "third_party/blink/public/web/web_frame_load_type.h" #include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/loader/frame_loader_types.h" #include "third_party/blink/renderer/platform/heap/handle.h" -#include "third_party/blink/renderer/platform/scheduler/public/post_cancellable_task.h" #include "third_party/blink/renderer/platform/weborigin/kurl.h" #include "third_party/blink/renderer/platform/wtf/forward.h" -#include "third_party/blink/renderer/platform/wtf/hash_map.h" -#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" namespace blink { -class LocalFrame; -class ScheduledNavigation; - -class CORE_EXPORT NavigationScheduler final - : public GarbageCollectedFinalized<NavigationScheduler> { +class CORE_EXPORT HttpRefreshScheduler final + : public GarbageCollectedFinalized<HttpRefreshScheduler> { public: - explicit NavigationScheduler(LocalFrame*); - ~NavigationScheduler(); + explicit HttpRefreshScheduler(Document*); + ~HttpRefreshScheduler() = default; - bool IsNavigationScheduledWithin(double interval_in_seconds) const; - - void ScheduleRedirect(double delay, const KURL&, Document::HttpRefreshType); - - void StartTimer(); + bool IsScheduledWithin(double interval_in_seconds) const; + void Schedule(double delay, const KURL&, Document::HttpRefreshType); + void MaybeStartTimer(); void Cancel(); void Trace(blink::Visitor*); private: - bool ShouldScheduleNavigation(const KURL&) const; - void NavigateTask(); - void Schedule(ScheduledNavigation*); - base::TimeTicks InputTimestamp(); - - Member<LocalFrame> frame_; + Member<Document> document_; TaskHandle navigate_task_handle_; - Member<ScheduledNavigation> redirect_; - DISALLOW_COPY_AND_ASSIGN(NavigationScheduler); + struct ScheduledHttpRefresh { + public: + ScheduledHttpRefresh(double delay, + const KURL& url, + ClientNavigationReason reason, + base::TimeTicks input_timestamp) + : delay(delay), + url(url), + reason(reason), + input_timestamp(input_timestamp) {} + + double delay; + KURL url; + ClientNavigationReason reason; + base::TimeTicks input_timestamp; + }; + std::unique_ptr<ScheduledHttpRefresh> refresh_; + + DISALLOW_COPY_AND_ASSIGN(HttpRefreshScheduler); }; } // namespace blink -#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_NAVIGATION_SCHEDULER_H_ +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_HTTP_REFRESH_SCHEDULER_H_
diff --git a/third_party/blink/renderer/core/loader/navigation_scheduler.cc b/third_party/blink/renderer/core/loader/navigation_scheduler.cc deleted file mode 100644 index 5a88413..0000000 --- a/third_party/blink/renderer/core/loader/navigation_scheduler.cc +++ /dev/null
@@ -1,233 +0,0 @@ -/* - * Copyright (C) 2006, 2007, 2008, 2009, 2010 Apple Inc. All rights reserved. - * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies) - * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. - * (http://www.torchmobile.com/) - * Copyright (C) 2009 Adam Barth. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of - * its contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "third_party/blink/renderer/core/loader/navigation_scheduler.h" - -#include <memory> -#include "third_party/blink/public/common/blob/blob_utils.h" -#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-shared.h" -#include "third_party/blink/public/platform/platform.h" -#include "third_party/blink/renderer/bindings/core/v8/script_controller.h" -#include "third_party/blink/renderer/core/dom/events/event.h" -#include "third_party/blink/renderer/core/dom/user_gesture_indicator.h" -#include "third_party/blink/renderer/core/events/current_input_event.h" -#include "third_party/blink/renderer/core/fileapi/public_url_manager.h" -#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" -#include "third_party/blink/renderer/core/frame/deprecation.h" -#include "third_party/blink/renderer/core/frame/local_frame.h" -#include "third_party/blink/renderer/core/frame/local_frame_client.h" -#include "third_party/blink/renderer/core/html/forms/html_form_element.h" -#include "third_party/blink/renderer/core/loader/document_load_timing.h" -#include "third_party/blink/renderer/core/loader/document_loader.h" -#include "third_party/blink/renderer/core/loader/form_submission.h" -#include "third_party/blink/renderer/core/loader/frame_load_request.h" -#include "third_party/blink/renderer/core/loader/frame_loader.h" -#include "third_party/blink/renderer/core/loader/frame_loader_state_machine.h" -#include "third_party/blink/renderer/core/loader/scheduled_navigation.h" -#include "third_party/blink/renderer/core/page/page.h" -#include "third_party/blink/renderer/core/probe/core_probes.h" -#include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h" -#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h" -#include "third_party/blink/renderer/platform/shared_buffer.h" -#include "third_party/blink/renderer/platform/wtf/time.h" - -namespace blink { - -class ScheduledRedirect final : public ScheduledNavigation { - public: - ScheduledRedirect(double delay, - Document* origin_document, - const KURL& url, - Document::HttpRefreshType http_refresh_type, - WebFrameLoadType frame_load_type, - base::TimeTicks input_timestamp) - : ScheduledNavigation(ToReason(http_refresh_type), - delay, - origin_document, - url, - frame_load_type, - input_timestamp) { - ClearUserGesture(); - } - - bool ShouldStartTimer(LocalFrame* frame) override { - return frame->GetDocument()->LoadEventFinished(); - } - - void Fire(LocalFrame* frame) override { - std::unique_ptr<UserGestureIndicator> gesture_indicator = - CreateUserGestureIndicator(); - FrameLoadRequest request(OriginDocument(), ResourceRequest(Url()), "_self"); - request.SetInputStartTime(InputTimestamp()); - WebFrameLoadType load_type = LoadType(); - if (EqualIgnoringFragmentIdentifier(frame->GetDocument()->Url(), - request.GetResourceRequest().Url())) { - request.GetResourceRequest().SetCacheMode( - mojom::FetchCacheMode::kValidateCache); - load_type = WebFrameLoadType::kReload; - } - request.SetClientRedirectReason(GetReason()); - frame->Loader().StartNavigation(request, load_type); - } - - private: - static ClientNavigationReason ToReason( - Document::HttpRefreshType http_refresh_type) { - switch (http_refresh_type) { - case Document::HttpRefreshType::kHttpRefreshFromHeader: - return ClientNavigationReason::kHttpHeaderRefresh; - case Document::HttpRefreshType::kHttpRefreshFromMetaTag: - return ClientNavigationReason::kMetaTagRefresh; - default: - break; - } - NOTREACHED(); - return ClientNavigationReason::kMetaTagRefresh; - } -}; - -NavigationScheduler::NavigationScheduler(LocalFrame* frame) : frame_(frame) {} - -NavigationScheduler::~NavigationScheduler() { -} - -bool NavigationScheduler::IsNavigationScheduledWithin(double interval) const { - return redirect_ && redirect_->Delay() <= interval; -} - -inline bool NavigationScheduler::ShouldScheduleNavigation( - const KURL& url) const { - return frame_->GetPage() && frame_->IsNavigationAllowed(); -} - -void NavigationScheduler::ScheduleRedirect( - double delay, - const KURL& url, - Document::HttpRefreshType http_refresh_type) { - if (!ShouldScheduleNavigation(url)) - return; - if (delay < 0 || delay > INT_MAX / 1000) - return; - if (url.IsEmpty()) - return; - - // We want a new back/forward list item if the refresh timeout is > 1 second. - if (!redirect_ || delay <= redirect_->Delay()) { - WebFrameLoadType frame_load_type = WebFrameLoadType::kStandard; - if (delay <= 1) - frame_load_type = WebFrameLoadType::kReplaceCurrentItem; - Schedule(MakeGarbageCollected<ScheduledRedirect>( - delay, frame_->GetDocument(), url, http_refresh_type, frame_load_type, - InputTimestamp())); - } -} - -base::TimeTicks NavigationScheduler::InputTimestamp() { - if (const WebInputEvent* input_event = CurrentInputEvent::Get()) { - return input_event->TimeStamp(); - } - return base::TimeTicks(); -} - -void NavigationScheduler::NavigateTask() { - if (!frame_->GetPage()) - return; - if (frame_->GetPage()->Paused()) { - probe::FrameClearedScheduledNavigation(frame_); - return; - } - - ScheduledNavigation* redirect(redirect_.Release()); - redirect->Fire(frame_); - probe::FrameClearedScheduledNavigation(frame_); -} - -void NavigationScheduler::Schedule(ScheduledNavigation* redirect) { - DCHECK(frame_->GetPage()); - - // In a back/forward navigation, we sometimes restore history state to - // iframes, even though the state was generated dynamically and JS will try to - // put something different in the iframe. In this case, we will load stale - // things and/or confuse the JS when it shortly thereafter tries to schedule a - // location change. Let the JS have its way. - // FIXME: This check seems out of place. - if (!frame_->Loader().StateMachine()->CommittedFirstRealDocumentLoad() && - frame_->Loader().GetProvisionalDocumentLoader() && - frame_->Loader().GetProvisionalDocumentLoader()->DidStart()) { - frame_->Loader().StopAllLoaders(); - if (!frame_->GetPage()) - return; - } - - Cancel(); - redirect_ = redirect; - StartTimer(); -} - -void NavigationScheduler::StartTimer() { - if (!redirect_) - return; - - DCHECK(frame_->GetPage()); - if (navigate_task_handle_.IsActive()) - return; - if (!redirect_->ShouldStartTimer(frame_)) - return; - - // wrapWeakPersistent(this) is safe because a posted task is canceled when the - // task handle is destroyed on the dtor of this NavigationScheduler. - navigate_task_handle_ = PostDelayedCancellableTask( - *frame_->GetFrameScheduler()->GetTaskRunner(TaskType::kInternalLoading), - FROM_HERE, - WTF::Bind(&NavigationScheduler::NavigateTask, WrapWeakPersistent(this)), - TimeDelta::FromSecondsD(redirect_->Delay())); - - probe::FrameScheduledNavigation(frame_, redirect_->Url(), redirect_->Delay(), - redirect_->GetReason()); -} - -void NavigationScheduler::Cancel() { - if (navigate_task_handle_.IsActive()) { - probe::FrameClearedScheduledNavigation(frame_); - } - if (frame_->GetDocument()) - frame_->GetDocument()->CancelPendingJavaScriptUrls(); - navigate_task_handle_.Cancel(); - redirect_.Clear(); -} - -void NavigationScheduler::Trace(blink::Visitor* visitor) { - visitor->Trace(frame_); - visitor->Trace(redirect_); -} - -} // namespace blink
diff --git a/third_party/blink/renderer/core/loader/scheduled_navigation.cc b/third_party/blink/renderer/core/loader/scheduled_navigation.cc deleted file mode 100644 index 53214b6a..0000000 --- a/third_party/blink/renderer/core/loader/scheduled_navigation.cc +++ /dev/null
@@ -1,39 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "third_party/blink/renderer/core/loader/scheduled_navigation.h" - -#include <memory> - -#include "third_party/blink/renderer/core/dom/document.h" -#include "third_party/blink/renderer/core/frame/frame.h" -#include "third_party/blink/renderer/core/frame/local_frame.h" - -namespace blink { - -ScheduledNavigation::ScheduledNavigation(ClientNavigationReason reason, - double delay, - Document* origin_document, - const KURL& url, - WebFrameLoadType frame_load_type, - base::TimeTicks input_timestamp) - : reason_(reason), - delay_(delay), - origin_document_(origin_document), - url_(url), - frame_load_type_(frame_load_type), - input_timestamp_(input_timestamp) { - if (LocalFrame::HasTransientUserActivation( - origin_document ? origin_document->GetFrame() : nullptr)) - user_gesture_token_ = UserGestureIndicator::CurrentToken(); -} - -ScheduledNavigation::~ScheduledNavigation() = default; - -std::unique_ptr<UserGestureIndicator> -ScheduledNavigation::CreateUserGestureIndicator() { - return std::make_unique<UserGestureIndicator>(user_gesture_token_); -} - -} // namespace blink
diff --git a/third_party/blink/renderer/core/loader/scheduled_navigation.h b/third_party/blink/renderer/core/loader/scheduled_navigation.h deleted file mode 100644 index 74975e5..0000000 --- a/third_party/blink/renderer/core/loader/scheduled_navigation.h +++ /dev/null
@@ -1,65 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_SCHEDULED_NAVIGATION_H_ -#define THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_SCHEDULED_NAVIGATION_H_ - -#include "base/macros.h" -#include "third_party/blink/public/platform/platform.h" -#include "third_party/blink/public/web/web_frame_load_type.h" -#include "third_party/blink/renderer/core/loader/frame_loader_types.h" -#include "third_party/blink/renderer/platform/weborigin/kurl.h" - -namespace blink { - -class Document; -class LocalFrame; -class UserGestureIndicator; -class UserGestureToken; - -class ScheduledNavigation - : public GarbageCollectedFinalized<ScheduledNavigation> { - public: - ScheduledNavigation(ClientNavigationReason, - double delay, - Document* origin_document, - const KURL&, - WebFrameLoadType, - base::TimeTicks input_timestamp); - virtual ~ScheduledNavigation(); - - virtual void Fire(LocalFrame*) = 0; - - virtual bool ShouldStartTimer(LocalFrame*) { return true; } - - ClientNavigationReason GetReason() const { return reason_; } - double Delay() const { return delay_; } - Document* OriginDocument() const { return origin_document_.Get(); } - const KURL& Url() const { return url_; } - std::unique_ptr<UserGestureIndicator> CreateUserGestureIndicator(); - base::TimeTicks InputTimestamp() const { return input_timestamp_; } - - virtual void Trace(blink::Visitor* visitor) { - visitor->Trace(origin_document_); - } - - protected: - void ClearUserGesture() { user_gesture_token_ = nullptr; } - WebFrameLoadType LoadType() const { return frame_load_type_; } - - private: - ClientNavigationReason reason_; - double delay_; - Member<Document> origin_document_; - KURL url_; - WebFrameLoadType frame_load_type_; - scoped_refptr<UserGestureToken> user_gesture_token_; - base::TimeTicks input_timestamp_; - - DISALLOW_COPY_AND_ASSIGN(ScheduledNavigation); -}; - -} // namespace blink - -#endif // THIRD_PARTY_BLINK_RENDERER_CORE_LOADER_SCHEDULED_NAVIGATION_H_
diff --git a/third_party/blink/renderer/core/origin_trials/origin_trial_context_test.cc b/third_party/blink/renderer/core/origin_trials/origin_trial_context_test.cc index f2a8c38f..be78fc0 100644 --- a/third_party/blink/renderer/core/origin_trials/origin_trial_context_test.cc +++ b/third_party/blink/renderer/core/origin_trials/origin_trial_context_test.cc
@@ -9,7 +9,7 @@ #include "third_party/blink/public/common/origin_trials/trial_token.h" #include "third_party/blink/public/common/origin_trials/trial_token_validator.h" #include "third_party/blink/renderer/core/dom/dom_exception.h" -#include "third_party/blink/renderer/core/feature_policy/feature_policy.h" +#include "third_party/blink/renderer/core/feature_policy/feature_policy_parser.h" #include "third_party/blink/renderer/core/frame/local_frame_view.h" #include "third_party/blink/renderer/core/html/html_head_element.h" #include "third_party/blink/renderer/core/html/html_meta_element.h" @@ -245,8 +245,8 @@ SecurityOrigin::CreateFromString(kFrobulateEnabledOrigin); Vector<String> messages; ParsedFeaturePolicy result; - result = ParseFeaturePolicy("frobulate", security_origin, nullptr, &messages, - feature_map, document); + result = FeaturePolicyParser::Parse("frobulate", security_origin, nullptr, + &messages, feature_map, document); EXPECT_TRUE(messages.IsEmpty()); ASSERT_EQ(1u, result.size()); EXPECT_EQ(mojom::FeaturePolicyFeature::kFrobulate, result[0].feature);
diff --git a/third_party/blink/renderer/core/page/spatial_navigation.cc b/third_party/blink/renderer/core/page/spatial_navigation.cc index 031d6d2..04c10fd 100644 --- a/third_party/blink/renderer/core/page/spatial_navigation.cc +++ b/third_party/blink/renderer/core/page/spatial_navigation.cc
@@ -169,6 +169,22 @@ return RectInViewport(*node).IsEmpty(); } +ScrollableArea* ScrollableAreaFor(const Node* node) { + if (node->IsDocumentNode()) { + LocalFrameView* view = node->GetDocument().View(); + if (!view) + return nullptr; + + return view->GetScrollableArea(); + } + + LayoutObject* object = node->GetLayoutObject(); + if (!object || !object->IsBox()) + return nullptr; + + return ToLayoutBox(object)->GetScrollableArea(); +} + bool HasRemoteFrame(const Node* node) { auto* frame_owner_element = DynamicTo<HTMLFrameOwnerElement>(node); if (!frame_owner_element) @@ -181,12 +197,6 @@ bool ScrollInDirection(Node* container, SpatialNavigationDirection direction) { DCHECK(container); - if (!container->GetLayoutBox()) - return false; - - if (!container->GetLayoutBox()->GetScrollableArea()) - return false; - if (!CanScrollInDirection(container, direction)) return false; @@ -226,8 +236,9 @@ // that it returns a ScrollResult so we don't need to call // CanScrollInDirection(). Regular arrow-key scrolling (without // --enable-spatial-navigation) already uses smooth scrolling by default. - container->GetLayoutBox()->GetScrollableArea()->ScrollBy(ScrollOffset(dx, dy), - kUserScroll); + ScrollableArea* scroller = ScrollableAreaFor(container); + DCHECK(scroller); + scroller->ScrollBy(ScrollOffset(dx, dy), kUserScroll); return true; } @@ -248,17 +259,17 @@ } bool IsScrollableNode(const Node* node) { - DCHECK(!node->IsDocumentNode()); - if (!node) return false; - if (LayoutObject* layout_object = node->GetLayoutObject()) - return layout_object->IsBox() && - ToLayoutBox(layout_object)->CanBeScrolledAndHasScrollableArea() && - node->hasChildren(); + if (node->IsDocumentNode()) + return true; - return false; + LayoutObject* layout_object = node->GetLayoutObject(); + if (!layout_object || !layout_object->IsBox()) + return false; + + return ToLayoutBox(layout_object)->CanBeScrolledAndHasScrollableArea(); } Node* ScrollableAreaOrDocumentOf(Node* node) { @@ -280,8 +291,7 @@ return false; auto* frame_owner_element = DynamicTo<HTMLFrameOwnerElement>(node); - return node->IsDocumentNode() || - (frame_owner_element && frame_owner_element->ContentFrame()) || + return (frame_owner_element && frame_owner_element->ContentFrame()) || IsScrollableNode(node); } @@ -294,6 +304,7 @@ if (!IsScrollableNode(container)) return false; + DCHECK(container->GetLayoutObject()); switch (direction) { case SpatialNavigationDirection::kLeft: return (container->GetLayoutObject()->Style()->OverflowX() !=
diff --git a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc index 4047f2c..cf16795d 100644 --- a/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc +++ b/third_party/blink/renderer/core/paint/ng/ng_box_fragment_painter.cc
@@ -1225,8 +1225,6 @@ result, *child, location_in_container, child_physical_offset, action); } else if (fragment.Type() == NGPhysicalFragment::kFragmentText) { - // TODO(eae): Should this hit test on the text itself or the containing - // node? stop_hit_testing = HitTestTextFragment( result, *child, location_in_container, child_physical_offset, action); }
diff --git a/third_party/blink/renderer/core/script/script_loader.cc b/third_party/blink/renderer/core/script/script_loader.cc index 449ce7d3..bb438c4 100644 --- a/third_party/blink/renderer/core/script/script_loader.cc +++ b/third_party/blink/renderer/core/script/script_loader.cc
@@ -24,13 +24,13 @@ #include "third_party/blink/renderer/core/script/script_loader.h" +#include "third_party/blink/public/common/feature_policy/feature_policy.h" #include "third_party/blink/renderer/bindings/core/v8/sanitize_script_errors.h" #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/events/event.h" #include "third_party/blink/renderer/core/dom/scriptable_document_parser.h" #include "third_party/blink/renderer/core/dom/text.h" -#include "third_party/blink/renderer/core/feature_policy/feature_policy.h" #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" #include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/frame/use_counter.h"
diff --git a/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc index 06fde4ec..1615671 100644 --- a/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc +++ b/third_party/blink/renderer/modules/canvas/offscreencanvas2d/offscreen_canvas_rendering_context_2d.cc
@@ -35,7 +35,7 @@ ExecutionContext* execution_context = canvas->GetTopExecutionContext(); if (auto* document = DynamicTo<Document>(execution_context)) { Settings* settings = document->GetSettings(); - if (settings->GetDisableReadingFromCanvas()) + if (settings && settings->GetDisableReadingFromCanvas()) canvas->SetDisableReadingFromCanvasTrue(); return; }
diff --git a/third_party/blink/renderer/modules/modules_idl_files.gni b/third_party/blink/renderer/modules/modules_idl_files.gni index 368bbee..1519caa 100644 --- a/third_party/blink/renderer/modules/modules_idl_files.gni +++ b/third_party/blink/renderer/modules/modules_idl_files.gni
@@ -729,6 +729,7 @@ "permissions/permission_descriptor.idl", "permissions/push_permission_descriptor.idl", "picture_in_picture/enter_picture_in_picture_event_init.idl", + "picture_in_picture/picture_in_picture_options.idl", "presentation/presentation_connection_available_event_init.idl", "presentation/presentation_connection_close_event_init.idl", "push_messaging/push_event_init.idl", @@ -924,6 +925,7 @@ "permissions/navigator_permissions.idl", "permissions/worker_navigator_permissions.idl", "picture_in_picture/document_picture_in_picture.idl", + "picture_in_picture/html_element_picture_in_picture.idl", "picture_in_picture/html_video_element_picture_in_picture.idl", "picture_in_picture/shadow_root_picture_in_picture.idl", "plugins/navigator_plugins.idl",
diff --git a/third_party/blink/renderer/modules/picture_in_picture/BUILD.gn b/third_party/blink/renderer/modules/picture_in_picture/BUILD.gn index 9613582..be418f1 100644 --- a/third_party/blink/renderer/modules/picture_in_picture/BUILD.gn +++ b/third_party/blink/renderer/modules/picture_in_picture/BUILD.gn
@@ -10,6 +10,8 @@ "document_picture_in_picture.h", "enter_picture_in_picture_event.cc", "enter_picture_in_picture_event.h", + "html_element_picture_in_picture.cc", + "html_element_picture_in_picture.h", "html_video_element_picture_in_picture.cc", "html_video_element_picture_in_picture.h", "picture_in_picture_controller_impl.cc",
diff --git a/third_party/blink/renderer/modules/picture_in_picture/html_element_picture_in_picture.cc b/third_party/blink/renderer/modules/picture_in_picture/html_element_picture_in_picture.cc new file mode 100644 index 0000000..1397153a --- /dev/null +++ b/third_party/blink/renderer/modules/picture_in_picture/html_element_picture_in_picture.cc
@@ -0,0 +1,98 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/modules/picture_in_picture/html_element_picture_in_picture.h" + +#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" +#include "third_party/blink/renderer/core/dom/document.h" +#include "third_party/blink/renderer/core/dom/dom_exception.h" +#include "third_party/blink/renderer/core/html/html_element.h" +#include "third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.h" +#include "third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_options.h" + +namespace blink { + +using Status = PictureInPictureControllerImpl::Status; + +namespace { + +const char kDetachedError[] = + "The element is no longer associated with a document."; +const char kMetadataNotLoadedError[] = + "Metadata for the video element are not loaded yet."; +const char kVideoTrackNotAvailableError[] = + "The video element has no video track."; +const char kFeaturePolicyBlocked[] = + "Access to the feature \"picture-in-picture\" is disallowed by feature " + "policy."; +const char kNotAvailable[] = "Picture-in-Picture is not available."; +const char kUserGestureRequired[] = + "Must be handling a user gesture if there isn't already an element in " + "Picture-in-Picture."; +const char kDisablePictureInPicturePresent[] = + "\"disablePictureInPicture\" attribute is present."; +} // namespace + +// static +ScriptPromise HTMLElementPictureInPicture::requestPictureInPicture( + ScriptState* script_state, + HTMLElement& element, + PictureInPictureOptions* options) { + DOMException* exception = CheckIfPictureInPictureIsAllowed(element); + if (exception) + return ScriptPromise::RejectWithDOMException(script_state, exception); + + auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state); + auto promise = resolver->Promise(); + + PictureInPictureControllerImpl::From(element.GetDocument()) + .EnterPictureInPicture(&element, options, resolver); + + return promise; +} + +// static +DOMException* HTMLElementPictureInPicture::CheckIfPictureInPictureIsAllowed( + HTMLElement& element) { + Document& document = element.GetDocument(); + PictureInPictureControllerImpl& controller = + PictureInPictureControllerImpl::From(document); + + switch (controller.IsElementAllowed(element)) { + case Status::kFrameDetached: + return DOMException::Create(DOMExceptionCode::kInvalidStateError, + kDetachedError); + case Status::kMetadataNotLoaded: + return DOMException::Create(DOMExceptionCode::kInvalidStateError, + kMetadataNotLoadedError); + case Status::kVideoTrackNotAvailable: + return DOMException::Create(DOMExceptionCode::kInvalidStateError, + kVideoTrackNotAvailableError); + case Status::kDisabledByFeaturePolicy: + return DOMException::Create(DOMExceptionCode::kSecurityError, + kFeaturePolicyBlocked); + case Status::kDisabledByAttribute: + return DOMException::Create(DOMExceptionCode::kInvalidStateError, + kDisablePictureInPicturePresent); + case Status::kDisabledBySystem: + return DOMException::Create(DOMExceptionCode::kNotSupportedError, + kNotAvailable); + case Status::kEnabled: + break; + } + + // Frame is not null, otherwise `IsElementAllowed()` would have return + // `kFrameDetached`. + LocalFrame* frame = document.GetFrame(); + DCHECK(frame); + if (!controller.PictureInPictureElement() && + !LocalFrame::ConsumeTransientUserActivation(frame)) { + return DOMException::Create(DOMExceptionCode::kNotAllowedError, + kUserGestureRequired); + } + + return nullptr; +} + +} // namespace blink
diff --git a/third_party/blink/renderer/modules/picture_in_picture/html_element_picture_in_picture.h b/third_party/blink/renderer/modules/picture_in_picture/html_element_picture_in_picture.h new file mode 100644 index 0000000..ec1ab5fc --- /dev/null +++ b/third_party/blink/renderer/modules/picture_in_picture/html_element_picture_in_picture.h
@@ -0,0 +1,32 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_PICTURE_IN_PICTURE_HTML_ELEMENT_PICTURE_IN_PICTURE_H_ +#define THIRD_PARTY_BLINK_RENDERER_MODULES_PICTURE_IN_PICTURE_HTML_ELEMENT_PICTURE_IN_PICTURE_H_ + +#include "third_party/blink/renderer/platform/wtf/allocator.h" + +namespace blink { + +class DOMException; +class HTMLElement; +class PictureInPictureOptions; +class ScriptPromise; +class ScriptState; + +class HTMLElementPictureInPicture { + STATIC_ONLY(HTMLElementPictureInPicture); + + public: + static ScriptPromise requestPictureInPicture( + ScriptState*, + HTMLElement&, + PictureInPictureOptions* options); + + static DOMException* CheckIfPictureInPictureIsAllowed(HTMLElement&); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_PICTURE_IN_PICTURE_HTML_ELEMENT_PICTURE_IN_PICTURE_H_
diff --git a/third_party/blink/renderer/modules/picture_in_picture/html_element_picture_in_picture.idl b/third_party/blink/renderer/modules/picture_in_picture/html_element_picture_in_picture.idl new file mode 100644 index 0000000..b9ee532 --- /dev/null +++ b/third_party/blink/renderer/modules/picture_in_picture/html_element_picture_in_picture.idl
@@ -0,0 +1,12 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// https://wicg.github.io/picture-in-picture/v2/ +[ + ImplementedAs=HTMLElementPictureInPicture, + RuntimeEnabled=PictureInPictureV2 +] +partial interface HTMLElement { + [CallWith=ScriptState, NewObject] Promise<PictureInPictureWindow> requestPictureInPicture(PictureInPictureOptions options); +};
diff --git a/third_party/blink/renderer/modules/picture_in_picture/html_video_element_picture_in_picture.cc b/third_party/blink/renderer/modules/picture_in_picture/html_video_element_picture_in_picture.cc index c7bd7c4..33ecc52 100644 --- a/third_party/blink/renderer/modules/picture_in_picture/html_video_element_picture_in_picture.cc +++ b/third_party/blink/renderer/modules/picture_in_picture/html_video_element_picture_in_picture.cc
@@ -6,93 +6,30 @@ #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" #include "third_party/blink/renderer/core/dom/document.h" -#include "third_party/blink/renderer/core/dom/dom_exception.h" #include "third_party/blink/renderer/core/dom/events/event.h" #include "third_party/blink/renderer/core/html/media/html_video_element.h" +#include "third_party/blink/renderer/modules/picture_in_picture/html_element_picture_in_picture.h" #include "third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.h" #include "third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_window.h" #include "third_party/blink/renderer/platform/wtf/functional.h" namespace blink { -using Status = PictureInPictureControllerImpl::Status; - -namespace { - -const char kDetachedError[] = - "The element is no longer associated with a document."; -const char kMetadataNotLoadedError[] = - "Metadata for the video element are not loaded yet."; -const char kVideoTrackNotAvailableError[] = - "The video element has no video track."; -const char kFeaturePolicyBlocked[] = - "Access to the feature \"picture-in-picture\" is disallowed by feature " - "policy."; -const char kNotAvailable[] = "Picture-in-Picture is not available."; -const char kUserGestureRequired[] = - "Must be handling a user gesture if there isn't already an element in " - "Picture-in-Picture."; -const char kDisablePictureInPicturePresent[] = - "\"disablePictureInPicture\" attribute is present."; -} // namespace - // static ScriptPromise HTMLVideoElementPictureInPicture::requestPictureInPicture( ScriptState* script_state, HTMLVideoElement& element) { - Document& document = element.GetDocument(); - PictureInPictureControllerImpl& controller = - PictureInPictureControllerImpl::From(document); - - switch (controller.IsElementAllowed(element)) { - case Status::kFrameDetached: - return ScriptPromise::RejectWithDOMException( - script_state, - DOMException::Create(DOMExceptionCode::kInvalidStateError, - kDetachedError)); - case Status::kMetadataNotLoaded: - return ScriptPromise::RejectWithDOMException( - script_state, - DOMException::Create(DOMExceptionCode::kInvalidStateError, - kMetadataNotLoadedError)); - case Status::kVideoTrackNotAvailable: - return ScriptPromise::RejectWithDOMException( - script_state, - DOMException::Create(DOMExceptionCode::kInvalidStateError, - kVideoTrackNotAvailableError)); - case Status::kDisabledByFeaturePolicy: - return ScriptPromise::RejectWithDOMException( - script_state, DOMException::Create(DOMExceptionCode::kSecurityError, - kFeaturePolicyBlocked)); - case Status::kDisabledByAttribute: - return ScriptPromise::RejectWithDOMException( - script_state, - DOMException::Create(DOMExceptionCode::kInvalidStateError, - kDisablePictureInPicturePresent)); - case Status::kDisabledBySystem: - return ScriptPromise::RejectWithDOMException( - script_state, - DOMException::Create(DOMExceptionCode::kNotSupportedError, - kNotAvailable)); - case Status::kEnabled: - break; - } - - // Frame is not null, otherwise `IsElementAllowed()` would have return - // `kFrameDetached`. - LocalFrame* frame = element.GetFrame(); - DCHECK(frame); - if (!controller.PictureInPictureElement() && - !LocalFrame::ConsumeTransientUserActivation(frame)) { - return ScriptPromise::RejectWithDOMException( - script_state, DOMException::Create(DOMExceptionCode::kNotAllowedError, - kUserGestureRequired)); - } + DOMException* exception = + HTMLElementPictureInPicture::CheckIfPictureInPictureIsAllowed(element); + if (exception) + return ScriptPromise::RejectWithDOMException(script_state, exception); auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state); - ScriptPromise promise = resolver->Promise(); + auto promise = resolver->Promise(); - controller.EnterPictureInPicture(&element, resolver); + PictureInPictureControllerImpl::From(element.GetDocument()) + .EnterPictureInPicture(&element, resolver); + return promise; }
diff --git a/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.cc b/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.cc index cc942b3..3812e9a 100644 --- a/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.cc +++ b/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.cc
@@ -16,6 +16,7 @@ #include "third_party/blink/renderer/core/dom/events/event.h" #include "third_party/blink/renderer/core/frame/settings.h" #include "third_party/blink/renderer/core/fullscreen/fullscreen.h" +#include "third_party/blink/renderer/core/html/media/html_media_element.h" #include "third_party/blink/renderer/core/html/media/html_video_element.h" #include "third_party/blink/renderer/modules/picture_in_picture/enter_picture_in_picture_event.h" #include "third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_window.h" @@ -31,6 +32,13 @@ element.duration() != std::numeric_limits<double>::infinity(); } +bool IsVideoElement(const Element& element) { + if (!element.IsMediaElement()) + return false; + + return static_cast<const HTMLMediaElement&>(element).IsHTMLVideoElement(); +} + } // namespace // static @@ -79,18 +87,24 @@ PictureInPictureController::Status PictureInPictureControllerImpl::IsElementAllowed( - const HTMLVideoElement& element) const { + const HTMLElement& element) const { PictureInPictureController::Status status = IsDocumentAllowed(); if (status != Status::kEnabled) return status; - if (element.getReadyState() == HTMLMediaElement::kHaveNothing) + if (!IsVideoElement(element)) + return Status::kEnabled; + + const HTMLVideoElement* video_element = + static_cast<const HTMLVideoElement*>(&element); + + if (video_element->getReadyState() == HTMLMediaElement::kHaveNothing) return Status::kMetadataNotLoaded; - if (!element.HasVideo()) + if (!video_element->HasVideo()) return Status::kVideoTrackNotAvailable; - if (element.FastHasAttribute(html_names::kDisablepictureinpictureAttr)) + if (video_element->FastHasAttribute(html_names::kDisablepictureinpictureAttr)) return Status::kDisabledByAttribute; return Status::kEnabled; @@ -126,6 +140,16 @@ WrapPersistent(resolver))); } +void PictureInPictureControllerImpl::EnterPictureInPicture( + HTMLElement* element, + PictureInPictureOptions* options, + ScriptPromiseResolver* resolver) { + DCHECK(!IsVideoElement(*element)); + + // TODO(https://crbug.com/953957): Support element level pip. + resolver->Resolve(); +} + void PictureInPictureControllerImpl::OnEnteredPictureInPicture( HTMLVideoElement* element, ScriptPromiseResolver* resolver, @@ -307,7 +331,7 @@ // If page becomes hidden and entering Auto Picture-in-Picture is allowed, // enter Picture-in-Picture. if (GetSupplementable()->hidden() && IsEnterAutoPictureInPictureAllowed()) { - EnterPictureInPicture(AutoPictureInPictureElement(), nullptr); + EnterPictureInPicture(AutoPictureInPictureElement(), nullptr /* promise */); } }
diff --git a/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.h b/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.h index 8e9f5d9..fa575f5 100644 --- a/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.h +++ b/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.h
@@ -15,6 +15,7 @@ namespace blink { class HTMLVideoElement; +class PictureInPictureOptions; class PictureInPictureWindow; class TreeScope; struct WebSize; @@ -71,10 +72,13 @@ // Implementation of PictureInPictureController. void EnterPictureInPicture(HTMLVideoElement*, ScriptPromiseResolver*) override; + void EnterPictureInPicture(HTMLElement*, + PictureInPictureOptions*, + ScriptPromiseResolver*) override; void ExitPictureInPicture(HTMLVideoElement*, ScriptPromiseResolver*) override; void AddToAutoPictureInPictureElementsList(HTMLVideoElement*) override; void RemoveFromAutoPictureInPictureElementsList(HTMLVideoElement*) override; - Status IsElementAllowed(const HTMLVideoElement&) const override; + Status IsElementAllowed(const HTMLElement&) const override; bool IsPictureInPictureElement(const Element*) const override; void OnPictureInPictureStateChange() override;
diff --git a/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_options.idl b/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_options.idl new file mode 100644 index 0000000..8f3f7bf9 --- /dev/null +++ b/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_options.idl
@@ -0,0 +1,9 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// https://wicg.github.io/picture-in-picture/v2/ +dictionary PictureInPictureOptions { + required double aspectRatio; + boolean interactive = false; +};
diff --git a/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_window.h b/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_window.h index 061060a..245df4aa 100644 --- a/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_window.h +++ b/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_window.h
@@ -28,6 +28,7 @@ int width() const { return size_.width; } int height() const { return size_.height; } + Document* document() const { return nullptr; } // Called when Picture-in-Picture window state is closed. void OnClose();
diff --git a/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_window.idl b/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_window.idl index 5ec2557..f8adf34 100644 --- a/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_window.idl +++ b/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_window.idl
@@ -11,5 +11,7 @@ [Measure] readonly attribute long width; [Measure] readonly attribute long height; + [RuntimeEnabled=PictureInPictureV2] readonly attribute Document? document; + attribute EventHandler onresize; };
diff --git a/third_party/blink/renderer/modules/sms/sms.idl b/third_party/blink/renderer/modules/sms/sms.idl index 0fa301f..a3c624b 100644 --- a/third_party/blink/renderer/modules/sms/sms.idl +++ b/third_party/blink/renderer/modules/sms/sms.idl
@@ -7,7 +7,7 @@ [ SecureContext, Exposed=(Window,DedicatedWorker), - RuntimeEnabled=SmsRetrieval] + RuntimeEnabled=SmsReceiver] interface SMS { readonly attribute DOMString content; };
diff --git a/third_party/blink/renderer/modules/sms/sms_receiver.idl b/third_party/blink/renderer/modules/sms/sms_receiver.idl index 7d1c1e3..6515294 100644 --- a/third_party/blink/renderer/modules/sms/sms_receiver.idl +++ b/third_party/blink/renderer/modules/sms/sms_receiver.idl
@@ -11,7 +11,7 @@ Constructor(optional SMSReceiverOptions options), ConstructorCallWith=ScriptState, RaisesException=Constructor, - RuntimeEnabled=SmsRetrieval + RuntimeEnabled=SmsReceiver ] interface SMSReceiver : EventTarget { readonly attribute SMS sms; attribute EventHandler onchange;
diff --git a/third_party/blink/renderer/modules/xr/BUILD.gn b/third_party/blink/renderer/modules/xr/BUILD.gn index 6c32656..8e942aaf1 100644 --- a/third_party/blink/renderer/modules/xr/BUILD.gn +++ b/third_party/blink/renderer/modules/xr/BUILD.gn
@@ -6,6 +6,8 @@ blink_modules_sources("xr") { sources = [ + "type_converters.cc", + "type_converters.h", "xr.cc", "xr.h", "xr_bounded_reference_space.cc",
diff --git a/third_party/blink/renderer/modules/xr/type_converters.cc b/third_party/blink/renderer/modules/xr/type_converters.cc new file mode 100644 index 0000000..90f6523b --- /dev/null +++ b/third_party/blink/renderer/modules/xr/type_converters.cc
@@ -0,0 +1,72 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/modules/xr/type_converters.h" + +namespace mojo { + +base::Optional<blink::XRPlane::Orientation> +TypeConverter<base::Optional<blink::XRPlane::Orientation>, + device::mojom::blink::XRPlaneOrientation>:: + Convert(const device::mojom::blink::XRPlaneOrientation& orientation) { + switch (orientation) { + case device::mojom::blink::XRPlaneOrientation::UNKNOWN: + return base::nullopt; + case device::mojom::blink::XRPlaneOrientation::HORIZONTAL: + return blink::XRPlane::Orientation::kHorizontal; + case device::mojom::blink::XRPlaneOrientation::VERTICAL: + return blink::XRPlane::Orientation::kVertical; + } +} + +blink::TransformationMatrix +TypeConverter<blink::TransformationMatrix, device::mojom::blink::VRPosePtr>:: + Convert(const device::mojom::blink::VRPosePtr& pose) { + DCHECK(pose); + + blink::TransformationMatrix result; + blink::TransformationMatrix::DecomposedType decomp = {}; + + decomp.perspective_w = 1; + decomp.scale_x = 1; + decomp.scale_y = 1; + decomp.scale_z = 1; + + if (pose->orientation) { + // TODO(https://crbug.com/929841): Remove negation once the bug is fixed. + decomp.quaternion_x = -pose->orientation.value()[0]; + decomp.quaternion_y = -pose->orientation.value()[1]; + decomp.quaternion_z = -pose->orientation.value()[2]; + decomp.quaternion_w = pose->orientation.value()[3]; + } else { + decomp.quaternion_w = 1.0; + } + + if (pose->position) { + decomp.translate_x = pose->position.value()[0]; + decomp.translate_y = pose->position.value()[1]; + decomp.translate_z = pose->position.value()[2]; + } + + result.Recompose(decomp); + + return result; +} + +blink::HeapVector<blink::Member<blink::DOMPointReadOnly>> +TypeConverter<blink::HeapVector<blink::Member<blink::DOMPointReadOnly>>, + WTF::Vector<device::mojom::blink::XRPlanePointDataPtr>>:: + Convert(const WTF::Vector<device::mojom::blink::XRPlanePointDataPtr>& + vertices) { + blink::HeapVector<blink::Member<blink::DOMPointReadOnly>> result; + + for (const auto& vertex_data : vertices) { + result.push_back(blink::DOMPointReadOnly::Create(vertex_data->x, 0.0, + vertex_data->z, 1.0)); + } + + return result; +} + +} // namespace mojo
diff --git a/third_party/blink/renderer/modules/xr/type_converters.h b/third_party/blink/renderer/modules/xr/type_converters.h new file mode 100644 index 0000000..700d7ec --- /dev/null +++ b/third_party/blink/renderer/modules/xr/type_converters.h
@@ -0,0 +1,38 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_XR_TYPE_CONVERTERS_H_ +#define THIRD_PARTY_BLINK_RENDERER_MODULES_XR_TYPE_CONVERTERS_H_ + +#include "device/vr/public/mojom/vr_service.mojom-blink.h" +#include "third_party/blink/renderer/core/geometry/dom_point_read_only.h" +#include "third_party/blink/renderer/modules/xr/xr_plane.h" +#include "third_party/blink/renderer/platform/transforms/transformation_matrix.h" + +namespace mojo { + +template <> +struct TypeConverter<base::Optional<blink::XRPlane::Orientation>, + device::mojom::blink::XRPlaneOrientation> { + static base::Optional<blink::XRPlane::Orientation> Convert( + const device::mojom::blink::XRPlaneOrientation& orientation); +}; + +template <> +struct TypeConverter<blink::TransformationMatrix, + device::mojom::blink::VRPosePtr> { + static blink::TransformationMatrix Convert( + const device::mojom::blink::VRPosePtr& pose); +}; + +template <> +struct TypeConverter<blink::HeapVector<blink::Member<blink::DOMPointReadOnly>>, + WTF::Vector<device::mojom::blink::XRPlanePointDataPtr>> { + static blink::HeapVector<blink::Member<blink::DOMPointReadOnly>> Convert( + const WTF::Vector<device::mojom::blink::XRPlanePointDataPtr>& vertices); +}; + +} // namespace mojo + +#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_XR_TYPE_CONVERTERS_H_
diff --git a/third_party/blink/renderer/modules/xr/xr_frame.cc b/third_party/blink/renderer/modules/xr/xr_frame.cc index 8a89c528..01e2c48 100644 --- a/third_party/blink/renderer/modules/xr/xr_frame.cc +++ b/third_party/blink/renderer/modules/xr/xr_frame.cc
@@ -10,6 +10,7 @@ #include "third_party/blink/renderer/modules/xr/xr_session.h" #include "third_party/blink/renderer/modules/xr/xr_view.h" #include "third_party/blink/renderer/modules/xr/xr_viewer_pose.h" +#include "third_party/blink/renderer/modules/xr/xr_world_information.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h" namespace blink { @@ -27,7 +28,8 @@ } // namespace -XRFrame::XRFrame(XRSession* session) : session_(session) {} +XRFrame::XRFrame(XRSession* session, XRWorldInformation* world_information) + : world_information_(world_information), session_(session) {} std::unique_ptr<TransformationMatrix> XRFrame::CloneBasePoseMatrix() const { if (!base_pose_matrix_) { @@ -115,6 +117,7 @@ void XRFrame::Trace(blink::Visitor* visitor) { visitor->Trace(session_); + visitor->Trace(world_information_); ScriptWrappable::Trace(visitor); }
diff --git a/third_party/blink/renderer/modules/xr/xr_frame.h b/third_party/blink/renderer/modules/xr/xr_frame.h index 589e1f96..ffa5e80 100644 --- a/third_party/blink/renderer/modules/xr/xr_frame.h +++ b/third_party/blink/renderer/modules/xr/xr_frame.h
@@ -28,13 +28,13 @@ DEFINE_WRAPPERTYPEINFO(); public: - explicit XRFrame(XRSession*); + explicit XRFrame(XRSession* session, XRWorldInformation* world_information); XRSession* session() const { return session_; } XRViewerPose* getViewerPose(XRReferenceSpace*, ExceptionState&) const; XRPose* getPose(XRSpace*, XRSpace*, ExceptionState&); - XRWorldInformation* worldInformation() const { return nullptr; } + XRWorldInformation* worldInformation() const { return world_information_; } void SetBasePoseMatrix(const TransformationMatrix&); std::unique_ptr<TransformationMatrix> CloneBasePoseMatrix() const; @@ -52,6 +52,8 @@ XRPose* GetTargetRayPose(XRInputSource*, XRSpace*) const; XRPose* GetGripPose(XRInputSource*, XRSpace*) const; + Member<XRWorldInformation> world_information_; + const Member<XRSession> session_; // Maps from mojo space to headset space.
diff --git a/third_party/blink/renderer/modules/xr/xr_frame_provider.cc b/third_party/blink/renderer/modules/xr/xr_frame_provider.cc index 67f4ce4..1e780f00 100644 --- a/third_party/blink/renderer/modules/xr/xr_frame_provider.cc +++ b/third_party/blink/renderer/modules/xr/xr_frame_provider.cc
@@ -14,10 +14,12 @@ #include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h" #include "third_party/blink/renderer/core/loader/document_loader.h" #include "third_party/blink/renderer/modules/xr/xr.h" +#include "third_party/blink/renderer/modules/xr/xr_plane_detection_state.h" #include "third_party/blink/renderer/modules/xr/xr_presentation_context.h" #include "third_party/blink/renderer/modules/xr/xr_session.h" #include "third_party/blink/renderer/modules/xr/xr_viewport.h" #include "third_party/blink/renderer/modules/xr/xr_webgl_layer.h" +#include "third_party/blink/renderer/modules/xr/xr_world_tracking_state.h" #include "third_party/blink/renderer/platform/graphics/gpu/xr_frame_transport.h" #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" #include "third_party/blink/renderer/platform/transforms/transformation_matrix.h" @@ -128,7 +130,7 @@ // to schedule immersive frames when focus is acquired. if (focus && !last_has_focus_ && requesting_sessions_.size() > 0 && !immersive_session_) { - ScheduleNonImmersiveFrame(); + ScheduleNonImmersiveFrame(nullptr); } last_has_focus_ = focus; } @@ -161,7 +163,7 @@ // outstanding frames that were requested while the immersive session was // active. if (requesting_sessions_.size() > 0) - ScheduleNonImmersiveFrame(); + ScheduleNonImmersiveFrame(nullptr); } // Schedule a session to be notified when the next XR frame is available. @@ -169,9 +171,12 @@ TRACE_EVENT0("gpu", __FUNCTION__); DCHECK(session); + auto options = device::mojom::blink::XRFrameDataRequestOptions::New( + session->worldTrackingState()->planeDetectionState()->enabled()); + // Immersive frame logic. if (session->immersive()) { - ScheduleImmersiveFrame(); + ScheduleImmersiveFrame(std::move(options)); return; } @@ -184,10 +189,11 @@ if (immersive_session_) return; - ScheduleNonImmersiveFrame(); + ScheduleNonImmersiveFrame(std::move(options)); } -void XRFrameProvider::ScheduleImmersiveFrame() { +void XRFrameProvider::ScheduleImmersiveFrame( + device::mojom::blink::XRFrameDataRequestOptionsPtr options) { TRACE_EVENT0("gpu", __FUNCTION__); if (pending_immersive_vsync_) return; @@ -195,11 +201,14 @@ pending_immersive_vsync_ = true; immersive_data_provider_->GetFrameData( - nullptr, WTF::Bind(&XRFrameProvider::OnImmersiveFrameData, - WrapWeakPersistent(this))); + std::move(options), WTF::Bind(&XRFrameProvider::OnImmersiveFrameData, + WrapWeakPersistent(this))); } -void XRFrameProvider::ScheduleNonImmersiveFrame() { +// TODO(lincolnfrog): add a ScheduleNonImmersiveARFrame, if we want camera RAF +// alignment instead of doc RAF alignment. +void XRFrameProvider::ScheduleNonImmersiveFrame( + device::mojom::blink::XRFrameDataRequestOptionsPtr options) { TRACE_EVENT0("gpu", __FUNCTION__); DCHECK(!immersive_session_) << "Scheduling should be done via the exclusive session if present."; @@ -228,8 +237,8 @@ // ProcessScheduledFrame handles it appropriately. if (xr_->xrMagicWindowProviderPtr()) { xr_->xrMagicWindowProviderPtr()->GetFrameData( - nullptr, WTF::Bind(&XRFrameProvider::OnNonImmersiveFrameData, - WrapWeakPersistent(this))); + std::move(options), WTF::Bind(&XRFrameProvider::OnNonImmersiveFrameData, + WrapWeakPersistent(this))); } else { frame_pose_ = nullptr; } @@ -389,8 +398,14 @@ if (frame_data && frame_data->stage_parameters_updated) { immersive_session_->UpdateStageParameters(frame_data->stage_parameters); } - immersive_session_->OnFrame(high_res_now_ms, std::move(pose_matrix), - buffer_mailbox_holder_); + if (frame_data) { + immersive_session_->OnFrame(high_res_now_ms, std::move(pose_matrix), + buffer_mailbox_holder_, + frame_data->detected_planes); + } else { + immersive_session_->OnFrame(high_res_now_ms, std::move(pose_matrix), + buffer_mailbox_holder_, base::nullopt); + } } else { // In the process of fulfilling the frame requests for each session they are // extremely likely to request another frame. Work off of a separate list @@ -413,7 +428,13 @@ std::unique_ptr<TransformationMatrix> pose_matrix = getPoseMatrix(frame_pose_); - session->OnFrame(high_res_now_ms, std::move(pose_matrix), base::nullopt); + if (frame_data) { + session->OnFrame(high_res_now_ms, std::move(pose_matrix), base::nullopt, + frame_data->detected_planes); + } else { + session->OnFrame(high_res_now_ms, std::move(pose_matrix), base::nullopt, + base::nullopt); + } } processing_sessions_.clear();
diff --git a/third_party/blink/renderer/modules/xr/xr_frame_provider.h b/third_party/blink/renderer/modules/xr/xr_frame_provider.h index 1710e5a..2496591d 100644 --- a/third_party/blink/renderer/modules/xr/xr_frame_provider.h +++ b/third_party/blink/renderer/modules/xr/xr_frame_provider.h
@@ -53,8 +53,12 @@ void OnImmersiveFrameData(device::mojom::blink::XRFrameDataPtr data); void OnNonImmersiveFrameData(device::mojom::blink::XRFrameDataPtr data); - void ScheduleImmersiveFrame(); - void ScheduleNonImmersiveFrame(); + // TODO(https://crbug.com/955819): options should be removed from those + // methods as they'll no longer be passed on a per-frame basis. + void ScheduleImmersiveFrame( + device::mojom::blink::XRFrameDataRequestOptionsPtr options); + void ScheduleNonImmersiveFrame( + device::mojom::blink::XRFrameDataRequestOptionsPtr options); void OnPresentationProviderConnectionError(); void ProcessScheduledFrame(device::mojom::blink::XRFrameDataPtr frame_data,
diff --git a/third_party/blink/renderer/modules/xr/xr_plane.cc b/third_party/blink/renderer/modules/xr/xr_plane.cc index 953fb43..dd34384c 100644 --- a/third_party/blink/renderer/modules/xr/xr_plane.cc +++ b/third_party/blink/renderer/modules/xr/xr_plane.cc
@@ -3,3 +3,61 @@ // found in the LICENSE file. #include "third_party/blink/renderer/modules/xr/xr_plane.h" +#include "third_party/blink/renderer/modules/xr/type_converters.h" + +namespace blink { + +XRPlane::XRPlane(const device::mojom::blink::XRPlaneDataPtr& plane_data) + : XRPlane(mojo::ConvertTo<base::Optional<blink::XRPlane::Orientation>>( + plane_data->orientation), + mojo::ConvertTo<blink::TransformationMatrix>(plane_data->pose), + mojo::ConvertTo<HeapVector<Member<DOMPointReadOnly>>>( + plane_data->polygon)) {} + +XRPlane::XRPlane(const base::Optional<Orientation>& orientation, + const TransformationMatrix& pose_matrix, + const HeapVector<Member<DOMPointReadOnly>>& polygon) + : polygon_(polygon), orientation_(orientation), pose_matrix_(pose_matrix) { + DVLOG(3) << __func__; +} + +XRPose* XRPlane::getPose(XRSpace*) const { + return nullptr; +} + +String XRPlane::orientation() const { + if (orientation_.has_value()) { + switch (orientation_.value()) { + case Orientation::kHorizontal: + return "Horizontal"; + case Orientation::kVertical: + return "Vertical"; + } + } + return ""; +} + +HeapVector<Member<DOMPointReadOnly>> XRPlane::polygon() const { + // Returns copy of a vector - by design. This way, JavaScript code could + // store the state of the plane's polygon in frame N just by storing the + // array (`let polygon = plane.polygon`) - the stored array won't be affected + // by the changes to the plane that could happen in frames >N. + return polygon_; +} + +void XRPlane::Update(const device::mojom::blink::XRPlaneDataPtr& plane_data) { + DVLOG(3) << __func__; + + orientation_ = mojo::ConvertTo<base::Optional<blink::XRPlane::Orientation>>( + plane_data->orientation); + pose_matrix_ = mojo::ConvertTo<blink::TransformationMatrix>(plane_data->pose); + polygon_ = mojo::ConvertTo<HeapVector<Member<DOMPointReadOnly>>>( + plane_data->polygon); +} + +void XRPlane::Trace(blink::Visitor* visitor) { + visitor->Trace(polygon_); + ScriptWrappable::Trace(visitor); +} + +} // namespace blink
diff --git a/third_party/blink/renderer/modules/xr/xr_plane.h b/third_party/blink/renderer/modules/xr/xr_plane.h index fc579e2..6b92eca 100644 --- a/third_party/blink/renderer/modules/xr/xr_plane.h +++ b/third_party/blink/renderer/modules/xr/xr_plane.h
@@ -5,7 +5,12 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_PLANE_H_ #define THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_PLANE_H_ +#include <memory> + +#include "base/optional.h" +#include "device/vr/public/mojom/vr_service.mojom-blink.h" #include "third_party/blink/renderer/core/geometry/dom_point_read_only.h" +#include "third_party/blink/renderer/platform/transforms/transformation_matrix.h" #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" namespace blink { @@ -17,10 +22,29 @@ DEFINE_WRAPPERTYPEINFO(); public: - XRPose* getPose(XRSpace*) const { return nullptr; } + enum Orientation { kHorizontal, kVertical }; - String orientation() const { return {}; } - HeapVector<Member<DOMPointReadOnly>> polygon() const { return {}; } + explicit XRPlane(const device::mojom::blink::XRPlaneDataPtr& plane_data); + XRPlane(const base::Optional<Orientation>& orientation, + const TransformationMatrix& pose_matrix, + const HeapVector<Member<DOMPointReadOnly>>& polygon); + + // Returns a pose expressed in passed in reference space. + XRPose* getPose(XRSpace*) const; + String orientation() const; + HeapVector<Member<DOMPointReadOnly>> polygon() const; + + // Updates plane data from passed in |plane_data|. The resulting instance + // should be equivalent to the instance that would be create by calling + // XRPlane(plane_data). + void Update(const device::mojom::blink::XRPlaneDataPtr& plane_data); + + void Trace(blink::Visitor* visitor) override; + + private: + HeapVector<Member<DOMPointReadOnly>> polygon_; + base::Optional<Orientation> orientation_; + TransformationMatrix pose_matrix_; }; } // namespace blink
diff --git a/third_party/blink/renderer/modules/xr/xr_plane_detection_state.cc b/third_party/blink/renderer/modules/xr/xr_plane_detection_state.cc index 174691b..32d24227 100644 --- a/third_party/blink/renderer/modules/xr/xr_plane_detection_state.cc +++ b/third_party/blink/renderer/modules/xr/xr_plane_detection_state.cc
@@ -3,3 +3,17 @@ // found in the LICENSE file. #include "third_party/blink/renderer/modules/xr/xr_plane_detection_state.h" + +#include "third_party/blink/renderer/modules/xr/xr_plane_detection_state_init.h" +namespace blink { + +XRPlaneDetectionState::XRPlaneDetectionState( + XRPlaneDetectionStateInit* plane_detection_state_init) { + if (plane_detection_state_init) { + if (plane_detection_state_init->hasEnabled()) { + enabled_ = plane_detection_state_init->enabled(); + } + } +} + +} // namespace blink
diff --git a/third_party/blink/renderer/modules/xr/xr_plane_detection_state.h b/third_party/blink/renderer/modules/xr/xr_plane_detection_state.h index 66fa47b..ff33f51d 100644 --- a/third_party/blink/renderer/modules/xr/xr_plane_detection_state.h +++ b/third_party/blink/renderer/modules/xr/xr_plane_detection_state.h
@@ -7,13 +7,23 @@ #include "third_party/blink/renderer/platform/bindings/script_wrappable.h" +#include "third_party/blink/renderer/platform/bindings/script_wrappable.h" + namespace blink { +class XRPlaneDetectionStateInit; + class XRPlaneDetectionState : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); public: - bool enabled() const { return false; } + XRPlaneDetectionState( + XRPlaneDetectionStateInit* plane_detection_state_init = nullptr); + + bool enabled() const { return enabled_; } + + private: + bool enabled_; }; } // namespace blink
diff --git a/third_party/blink/renderer/modules/xr/xr_session.cc b/third_party/blink/renderer/modules/xr/xr_session.cc index 539b47e..0438edf 100644 --- a/third_party/blink/renderer/modules/xr/xr_session.cc +++ b/third_party/blink/renderer/modules/xr/xr_session.cc
@@ -36,6 +36,9 @@ #include "third_party/blink/renderer/modules/xr/xr_view.h" #include "third_party/blink/renderer/modules/xr/xr_viewer_space.h" #include "third_party/blink/renderer/modules/xr/xr_webgl_layer.h" +#include "third_party/blink/renderer/modules/xr/xr_world_information.h" +#include "third_party/blink/renderer/modules/xr/xr_world_tracking_state.h" +#include "third_party/blink/renderer/modules/xr/xr_world_tracking_state_init.h" #include "third_party/blink/renderer/platform/transforms/transformation_matrix.h" namespace blink { @@ -116,6 +119,8 @@ : xr_(xr), mode_(mode), environment_integration_(mode == kModeImmersiveAR), + world_tracking_state_(MakeGarbageCollected<XRWorldTrackingState>()), + world_information_(MakeGarbageCollected<XRWorldInformation>()), client_binding_(this, std::move(client_request)), callback_collection_( MakeGarbageCollected<XRFrameRequestCallbackCollection>( @@ -197,6 +202,21 @@ pending_render_state_.push_back(init); } +void XRSession::updateWorldTrackingState( + XRWorldTrackingStateInit* world_tracking_state_init, + ExceptionState& exception_state) { + DVLOG(3) << __func__; + + if (ended_) { + exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, + kSessionEnded); + return; + } + + world_tracking_state_ = + MakeGarbageCollected<XRWorldTrackingState>(world_tracking_state_init); +} + void XRSession::UpdateEyeParameters( const device::mojom::blink::VREyeParametersPtr& left_eye, const device::mojom::blink::VREyeParametersPtr& right_eye) { @@ -632,7 +652,9 @@ void XRSession::OnFrame( double timestamp, std::unique_ptr<TransformationMatrix> base_pose_matrix, - const base::Optional<gpu::MailboxHolder>& output_mailbox_holder) { + const base::Optional<gpu::MailboxHolder>& output_mailbox_holder, + const base::Optional<WTF::Vector<device::mojom::blink::XRPlaneDataPtr>>& + detected_planes) { TRACE_EVENT0("gpu", __FUNCTION__); DVLOG(2) << __FUNCTION__; // Don't process any outstanding frames once the session is ended. @@ -644,6 +666,8 @@ // If there are pending render state changes, apply them now. ApplyPendingRenderState(); + world_information_->ProcessPlaneInformation(detected_planes); + if (pending_frame_) { pending_frame_ = false; @@ -697,7 +721,8 @@ } XRFrame* XRSession::CreatePresentationFrame() { - XRFrame* presentation_frame = MakeGarbageCollected<XRFrame>(this); + XRFrame* presentation_frame = + MakeGarbageCollected<XRFrame>(this, world_information_); if (base_pose_matrix_) { presentation_frame->SetBasePoseMatrix(*base_pose_matrix_); } @@ -999,6 +1024,8 @@ void XRSession::Trace(blink::Visitor* visitor) { visitor->Trace(xr_); visitor->Trace(render_state_); + visitor->Trace(world_tracking_state_); + visitor->Trace(world_information_); visitor->Trace(viewer_space_); visitor->Trace(pending_render_state_); visitor->Trace(views_);
diff --git a/third_party/blink/renderer/modules/xr/xr_session.h b/third_party/blink/renderer/modules/xr/xr_session.h index 9567a8e..4ae09f6 100644 --- a/third_party/blink/renderer/modules/xr/xr_session.h +++ b/third_party/blink/renderer/modules/xr/xr_session.h
@@ -39,6 +39,7 @@ class XRRenderStateInit; class XRView; class XRViewerSpace; +class XRWorldInformation; class XRWorldTrackingState; class XRWorldTrackingStateInit; @@ -67,7 +68,7 @@ XR* xr() const { return xr_; } const String& environmentBlendMode() const { return blend_mode_string_; } XRRenderState* renderState() const { return render_state_; } - XRWorldTrackingState* worldTrackingState() { return nullptr; } + XRWorldTrackingState* worldTrackingState() { return world_tracking_state_; } XRSpace* viewerSpace() const; bool immersive() const; @@ -81,7 +82,9 @@ DEFINE_ATTRIBUTE_EVENT_LISTENER(selectend, kSelectend) void updateRenderState(XRRenderStateInit*, ExceptionState&); - void updateWorldTrackingState(XRWorldTrackingStateInit*) {} + void updateWorldTrackingState( + XRWorldTrackingStateInit* worldTrackingStateInit, + ExceptionState& exception_state); ScriptPromise requestReferenceSpace(ScriptState*, const XRReferenceSpaceOptions*); @@ -126,9 +129,12 @@ const AtomicString& InterfaceName() const override; void OnFocusChanged(); - void OnFrame(double timestamp, - std::unique_ptr<TransformationMatrix>, - const base::Optional<gpu::MailboxHolder>& output_mailbox_holder); + void OnFrame( + double timestamp, + std::unique_ptr<TransformationMatrix>, + const base::Optional<gpu::MailboxHolder>& output_mailbox_holder, + const base::Optional<WTF::Vector<device::mojom::blink::XRPlaneDataPtr>>& + detected_planes); void OnInputStateChange( int16_t frame_id, const WTF::Vector<device::mojom::blink::XRInputSourceStatePtr>&); @@ -213,6 +219,8 @@ const bool environment_integration_; String blend_mode_string_; Member<XRRenderState> render_state_; + Member<XRWorldTrackingState> world_tracking_state_; + Member<XRWorldInformation> world_information_; Member<XRViewerSpace> viewer_space_; HeapVector<Member<XRRenderStateInit>> pending_render_state_; HeapVector<Member<XRView>> views_;
diff --git a/third_party/blink/renderer/modules/xr/xr_session.idl b/third_party/blink/renderer/modules/xr/xr_session.idl index 54b874a..0a07dfdb 100644 --- a/third_party/blink/renderer/modules/xr/xr_session.idl +++ b/third_party/blink/renderer/modules/xr/xr_session.idl
@@ -48,7 +48,7 @@ // Configures a session with real-world understanding capabilities. // More details can be found here: // https://github.com/immersive-web/real-world-geometry/blob/master/plane-detection-explainer.md - [RuntimeEnabled=WebXRPlaneDetection] void updateWorldTrackingState(optional XRWorldTrackingStateInit state); + [RuntimeEnabled=WebXRPlaneDetection, RaisesException] void updateWorldTrackingState(optional XRWorldTrackingStateInit state); [CallWith=ScriptState] Promise<void> end(); };
diff --git a/third_party/blink/renderer/modules/xr/xr_world_information.cc b/third_party/blink/renderer/modules/xr/xr_world_information.cc index 5d75930..8f9abf1 100644 --- a/third_party/blink/renderer/modules/xr/xr_world_information.cc +++ b/third_party/blink/renderer/modules/xr/xr_world_information.cc
@@ -3,3 +3,56 @@ // found in the LICENSE file. #include "third_party/blink/renderer/modules/xr/xr_world_information.h" + +namespace blink { + +XRWorldInformation::XRWorldInformation() {} + +void XRWorldInformation::Trace(blink::Visitor* visitor) { + visitor->Trace(plane_ids_to_planes_); + ScriptWrappable::Trace(visitor); +} + +HeapVector<Member<XRPlane>> XRWorldInformation::detectedPlanes( + bool& is_null) const { + HeapVector<Member<XRPlane>> result; + + is_null = is_detected_planes_null_; + if (is_null) + return result; + + for (auto& plane_id_and_plane : plane_ids_to_planes_) { + result.push_back(plane_id_and_plane.value); + } + + return result; +} + +void XRWorldInformation::ProcessPlaneInformation( + const base::Optional<WTF::Vector<device::mojom::blink::XRPlaneDataPtr>>& + detected_planes) { + if (!detected_planes.has_value()) { + // We have received a nullopt - plane detection is not supported or + // disabled. Mark detected_planes as null & clear stored planes. + is_detected_planes_null_ = true; + plane_ids_to_planes_.clear(); + return; + } + + is_detected_planes_null_ = false; + + HeapHashMap<int32_t, Member<XRPlane>> updated_planes; + for (const auto& plane : *detected_planes) { + auto it = plane_ids_to_planes_.find(plane->id); + if (it != plane_ids_to_planes_.end()) { + updated_planes.insert(plane->id, it->value); + it->value->Update(plane); + } else { + updated_planes.insert(plane->id, MakeGarbageCollected<XRPlane>(plane)); + } + } + + plane_ids_to_planes_.swap(updated_planes); +} + +} // namespace blink
diff --git a/third_party/blink/renderer/modules/xr/xr_world_information.h b/third_party/blink/renderer/modules/xr/xr_world_information.h index edc31d6b..f3c5ad2 100644 --- a/third_party/blink/renderer/modules/xr/xr_world_information.h +++ b/third_party/blink/renderer/modules/xr/xr_world_information.h
@@ -5,6 +5,7 @@ #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_WORLD_INFORMATION_H_ #define THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_WORLD_INFORMATION_H_ +#include "device/vr/public/mojom/vr_service.mojom-blink.h" #include "third_party/blink/renderer/modules/xr/xr_plane.h" namespace blink { @@ -13,10 +14,27 @@ DEFINE_WRAPPERTYPEINFO(); public: - HeapVector<Member<XRPlane>> detectedPlanes(bool& is_null) const { - is_null = true; - return {}; - } + XRWorldInformation(); + + // Returns vector containing detected planes, |is_null| will be set to true + // if plane detection is not enabled. + HeapVector<Member<XRPlane>> detectedPlanes(bool& is_null) const; + + void Trace(blink::Visitor* visitor) override; + + // Applies changes to the stored plane information based on the contents of + // the received frame data. This will update the contents of + // plane_ids_to_planes_. + void ProcessPlaneInformation( + const base::Optional<WTF::Vector<device::mojom::blink::XRPlaneDataPtr>>& + detected_planes); + + private: + // Signifies if we should return null from `detectedPlanes()`. + // This is the case if we have a freshly constructed instance, or if our + // last `ProcessPlaneInformation()` was called with base::nullopt. + bool is_detected_planes_null_ = true; + HeapHashMap<int32_t, Member<XRPlane>> plane_ids_to_planes_; }; } // namespace blink
diff --git a/third_party/blink/renderer/modules/xr/xr_world_tracking_state.cc b/third_party/blink/renderer/modules/xr/xr_world_tracking_state.cc index 591eb7c..5367956 100644 --- a/third_party/blink/renderer/modules/xr/xr_world_tracking_state.cc +++ b/third_party/blink/renderer/modules/xr/xr_world_tracking_state.cc
@@ -3,3 +3,25 @@ // found in the LICENSE file. #include "third_party/blink/renderer/modules/xr/xr_world_tracking_state.h" +#include "third_party/blink/renderer/modules/xr/xr_plane_detection_state.h" +#include "third_party/blink/renderer/modules/xr/xr_world_tracking_state_init.h" + +namespace blink { + +XRWorldTrackingState::XRWorldTrackingState( + XRWorldTrackingStateInit* world_tracking_state_init) { + if (world_tracking_state_init && + world_tracking_state_init->hasPlaneDetectionState()) { + plane_detection_state_ = MakeGarbageCollected<XRPlaneDetectionState>( + world_tracking_state_init->planeDetectionState()); + } else { + plane_detection_state_ = MakeGarbageCollected<XRPlaneDetectionState>(); + } +} + +void XRWorldTrackingState::Trace(blink::Visitor* visitor) { + visitor->Trace(plane_detection_state_); + ScriptWrappable::Trace(visitor); +} + +} // namespace blink
diff --git a/third_party/blink/renderer/modules/xr/xr_world_tracking_state.h b/third_party/blink/renderer/modules/xr/xr_world_tracking_state.h index 125b775..87625a8c 100644 --- a/third_party/blink/renderer/modules/xr/xr_world_tracking_state.h +++ b/third_party/blink/renderer/modules/xr/xr_world_tracking_state.h
@@ -7,15 +7,29 @@ #include "third_party/blink/renderer/platform/bindings/script_wrappable.h" +#include "third_party/blink/renderer/platform/bindings/script_wrappable.h" +#include "third_party/blink/renderer/platform/heap/handle.h" + namespace blink { class XRPlaneDetectionState; +class XRWorldTrackingStateInit; class XRWorldTrackingState : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); public: - XRPlaneDetectionState* planeDetectionState() const { return nullptr; } + XRWorldTrackingState( + XRWorldTrackingStateInit* world_tracking_state_init = nullptr); + + XRPlaneDetectionState* planeDetectionState() const { + return plane_detection_state_; + } + + void Trace(blink::Visitor* visitor) override; + + private: + Member<XRPlaneDetectionState> plane_detection_state_; }; } // namespace blink
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn index 0bc2a989..f502bb8 100644 --- a/third_party/blink/renderer/platform/BUILD.gn +++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -394,6 +394,7 @@ "audio/multi_channel_resampler.h", "audio/panner.cc", "audio/panner.h", + "audio/pffft/fft_frame_pffft.cc", "audio/push_pull_fifo.cc", "audio/push_pull_fifo.h", "audio/reverb.cc", @@ -1555,6 +1556,10 @@ include_dirs += [ "//third_party/openmax_dl" ] deps += [ "//third_party/openmax_dl/dl" ] } + if (use_webaudio_pffft) { + include_dirs += [ "//third_party/pffft/src" ] + deps += [ "//third_party/pffft" ] + } configs -= [ "//build/config/compiler:default_symbols" ] configs += blink_symbols_config
diff --git a/third_party/blink/renderer/platform/audio/fft_frame.h b/third_party/blink/renderer/platform/audio/fft_frame.h index 65c6191..37e2f74a 100644 --- a/third_party/blink/renderer/platform/audio/fft_frame.h +++ b/third_party/blink/renderer/platform/audio/fft_frame.h
@@ -44,6 +44,8 @@ #include <dl/sp/api/omxSP.h> #elif defined(WTF_USE_WEBAUDIO_FFMPEG) struct RDFTContext; +#elif defined(WTF_USE_WEBAUDIO_PFFFT) +#include "third_party/pffft/src/pffft.h" #endif namespace blink { @@ -100,6 +102,32 @@ unsigned fft_size_; unsigned log2fft_size_; + // These two arrays contain the transformed data. Instead of a single array + // of complex numbers, we split the complex data into an array of the real + // part and the imaginary part. + // + // Let the forward transform, X[k], of the real signal x[n] be defined by + // + // X[k] = sum(x[n]*W^(k*n)) for n = 0 to N-1 + // + // where W = exp(-2*pi*i/N), and N is the FFT size. + // + // Since x[n] is assumed to be real, X[k] has complex conjugate symmetry with + // X[N-k] = conj(X[k]). Thus, we only need to keep X[k] for k = 0 to N/2. + // But since X[0] is purely real and X[N/2] is also purely real, so we could + // place the real part of X[N/2] in the imaginary part of X[0]. Thus + // for k = 1 to N/2: + // + // real_data[k] = Re(X[k]) + // imag_data[k] = Im(X[k]) + // + // and + // + // real_data[0] = Re(X[0]); + // imag_data[0] = Re(X[N/2]) + // + // The routine |DoFFT| must produce transformed data in this format, and the + // routine |DoInverseFFT| must expect transformed data in this format. AudioFloatArray real_data_; AudioFloatArray imag_data_; @@ -121,6 +149,23 @@ OMXFFTSpec_R_F32* forward_context_; OMXFFTSpec_R_F32* inverse_context_; AudioFloatArray complex_data_; +#elif defined(WTF_USE_WEBAUDIO_PFFFT) + // Create and return the setup data for an FFT (forward or ivnerse) of the + // given size. + static PFFFT_Setup* ContextForSize(unsigned fft_size); + + // The context can be used for both forward and inverse transforms. + // TODO(rtoy): Consider using an array to hold the possible contexts since the + // contexts are read-only after creation and can be shared between FFTFrame + // objects. + PFFFT_Setup* context_; + + // Work array for converting PFFFT results to and from the format expected in + // |real_data_| and |imag_datra_|. + AudioFloatArray complex_data_; + + // Work array used by the PFFFT transform routines. + AudioFloatArray pffft_work_; #endif };
diff --git a/third_party/blink/renderer/platform/audio/fft_frame_stub.cc b/third_party/blink/renderer/platform/audio/fft_frame_stub.cc index a331fc4..16c10dd 100644 --- a/third_party/blink/renderer/platform/audio/fft_frame_stub.cc +++ b/third_party/blink/renderer/platform/audio/fft_frame_stub.cc
@@ -28,7 +28,8 @@ #include "build/build_config.h" #if !defined(OS_MACOSX) && !defined(WTF_USE_WEBAUDIO_FFMPEG) && \ - !defined(WTF_USE_WEBAUDIO_OPENMAX_DL_FFT) + !defined(WTF_USE_WEBAUDIO_OPENMAX_DL_FFT) && \ + !defined(WTF_USE_WEBAUDIO_PFFFT) #include "third_party/blink/renderer/platform/audio/fft_frame.h"
diff --git a/third_party/blink/renderer/platform/audio/pffft/fft_frame_pffft.cc b/third_party/blink/renderer/platform/audio/pffft/fft_frame_pffft.cc new file mode 100644 index 0000000..3cf74de --- /dev/null +++ b/third_party/blink/renderer/platform/audio/pffft/fft_frame_pffft.cc
@@ -0,0 +1,119 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#if defined(WTF_USE_WEBAUDIO_PFFFT) + +#include "third_party/blink/renderer/platform/audio/fft_frame.h" + +#include "third_party/blink/renderer/platform/audio/audio_array.h" +#include "third_party/blink/renderer/platform/audio/vector_math.h" +#include "third_party/blink/renderer/platform/wtf/math_extras.h" +#include "third_party/pffft/src/pffft.h" + +namespace blink { + +// Not really clear what the largest size of FFT PFFFT supports, but the docs +// indicate it can go up to at least 1048576 (order 20). Since we're using +// single-floats, accuracy decreases quite a bit at that size. Plus we only +// need 32K (order 15) for WebAudio. +const unsigned kMaxFFTPow2Size = 20; + +FFTFrame::FFTFrame(unsigned fft_size) + : fft_size_(fft_size), + log2fft_size_(static_cast<unsigned>(log2(fft_size))), + real_data_(fft_size / 2), + imag_data_(fft_size / 2), + context_(nullptr), + complex_data_(fft_size), + pffft_work_(fft_size) { + // We only allow power of two. + DCHECK_EQ(1UL << log2fft_size_, fft_size_); + + context_ = ContextForSize(fft_size); +} + +// Creates a blank/empty frame (interpolate() must later be called). +FFTFrame::FFTFrame() : fft_size_(0), log2fft_size_(0), context_(nullptr) {} + +// Copy constructor. +FFTFrame::FFTFrame(const FFTFrame& frame) + : fft_size_(frame.fft_size_), + log2fft_size_(frame.log2fft_size_), + real_data_(frame.fft_size_ / 2), + imag_data_(frame.fft_size_ / 2), + context_(nullptr), + complex_data_(frame.fft_size_), + pffft_work_(frame.fft_size_) { + context_ = ContextForSize(fft_size_); + + // Copy/setup frame data. + unsigned nbytes = sizeof(float) * (fft_size_ / 2); + memcpy(RealData(), frame.RealData(), nbytes); + memcpy(ImagData(), frame.ImagData(), nbytes); +} + +void FFTFrame::Initialize() {} + +void FFTFrame::Cleanup() {} + +FFTFrame::~FFTFrame() { + if (context_) + pffft_destroy_setup(context_); +} + +void FFTFrame::DoFFT(const float* data) { + DCHECK(context_); + DCHECK_EQ(complex_data_.size(), fft_size_); + + pffft_transform_ordered(context_, data, complex_data_.Data(), + pffft_work_.Data(), PFFFT_FORWARD); + + unsigned len = fft_size_ / 2; + + // Split FFT data into real and imaginary arrays. PFFFT transform already + // uses the desired format. + const float* c = complex_data_.Data(); + float* real = real_data_.Data(); + float* imag = imag_data_.Data(); + for (unsigned k = 0; k < len; ++k) { + int index = 2 * k; + real[k] = c[index]; + imag[k] = c[index + 1]; + } +} + +void FFTFrame::DoInverseFFT(float* data) { + DCHECK(context_); + DCHECK_EQ(complex_data_.size(), fft_size_); + + unsigned len = fft_size_ / 2; + + // Pack the real and imaginary data into the complex array format. PFFFT + // already uses the desired format. + float* fft_data = complex_data_.Data(); + const float* real = real_data_.Data(); + const float* imag = imag_data_.Data(); + for (unsigned k = 0; k < len; ++k) { + int index = 2 * k; + fft_data[index] = real[k]; + fft_data[index + 1] = imag[k]; + } + + pffft_transform_ordered(context_, fft_data, data, pffft_work_.Data(), + PFFFT_BACKWARD); + + // The inverse transform needs to be scaled because PFFFT doesn't. + float scale = 1.0 / fft_size_; + vector_math::Vsmul(data, 1, &scale, data, 1, fft_size_); +} + +PFFFT_Setup* FFTFrame::ContextForSize(unsigned fft_size) { + DCHECK_LE(fft_size, 1U << kMaxFFTPow2Size); + + return pffft_new_setup(fft_size, PFFFT_REAL); +} + +} // namespace blink + +#endif // #if defined(WTF_USE_WEBAUDIO_PFFFT)
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.cc b/third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.cc index 11d79922..73f8a4c 100644 --- a/third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.cc +++ b/third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.cc
@@ -25,40 +25,13 @@ } // namespace -// TODO(eae): This is a bit of a hack to allow reuse of the implementation -// for both ShapeResultBuffer and single ShapeResult use cases. Ideally the -// logic should move into ShapeResult itself and then the ShapeResultBuffer -// implementation may wrap that. CharacterRange ShapeResultBuffer::GetCharacterRange( - scoped_refptr<const ShapeResult> result, - const StringView& text, - TextDirection direction, - float total_width, - unsigned from, - unsigned to) { - Vector<scoped_refptr<const ShapeResult>, 64> results; - results.push_back(result); - return GetCharacterRangeInternal(results, text, direction, - total_width, from, to); -} - -CharacterRange ShapeResultBuffer::GetCharacterRange(const StringView& text, - TextDirection direction, - float total_width, - unsigned from, - unsigned to) const { - return GetCharacterRangeInternal(results_, text, direction, - total_width, from, to); -} - -CharacterRange ShapeResultBuffer::GetCharacterRangeInternal( - const Vector<scoped_refptr<const ShapeResult>, 64>& results, const StringView& text, TextDirection direction, float total_width, unsigned absolute_from, - unsigned absolute_to) { - DCHECK_EQ(CharactersInShapeResult(results), text.length()); + unsigned absolute_to) const { + DCHECK_EQ(CharactersInShapeResult(results_), text.length()); float current_x = 0; float from_x = 0; @@ -78,8 +51,8 @@ int to = absolute_to; unsigned total_num_characters = 0; - for (unsigned j = 0; j < results.size(); j++) { - const scoped_refptr<const ShapeResult> result = results[j]; + for (unsigned j = 0; j < results_.size(); j++) { + const scoped_refptr<const ShapeResult> result = results_[j]; result->EnsureGraphemes( StringView(text, total_num_characters, result->NumCharacters())); if (direction == TextDirection::kRtl) {
diff --git a/third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.h b/third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.h index 12821956..7bbf1ef 100644 --- a/third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.h +++ b/third_party/blink/renderer/platform/fonts/shaping/shape_result_buffer.h
@@ -47,13 +47,6 @@ TextDirection, float total_width) const; - static CharacterRange GetCharacterRange(scoped_refptr<const ShapeResult>, - const StringView& text, - TextDirection, - float total_width, - unsigned from, - unsigned to); - Vector<ShapeResult::RunFontData> GetRunFontData() const; GlyphData EmphasisMarkGlyphData(const FontDescription&) const; @@ -62,13 +55,6 @@ private: friend class ShapeResultBloberizer; - static CharacterRange GetCharacterRangeInternal( - const Vector<scoped_refptr<const ShapeResult>, 64>&, - const StringView& text, - TextDirection, - float total_width, - unsigned from, - unsigned to); static void AddRunInfoAdvances(const ShapeResult::RunInfo& run_info, double offset,
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc index c163574..1fdda6c5 100644 --- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc +++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
@@ -916,8 +916,8 @@ LayerListBuilder layer_list_builder; - property_tree_manager_.Initialize(host->property_trees(), - &layer_list_builder); + property_tree_manager_.Initialize(host->property_trees(), &layer_list_builder, + g_s_property_tree_sequence_number); CollectPendingLayers(*paint_artifact, settings); UpdateCompositorViewportProperties(viewport_properties,
diff --git a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc index 0c2265d..1006dac 100644 --- a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc +++ b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
@@ -43,17 +43,12 @@ : client_(client) {} void PropertyTreeManager::Initialize(cc::PropertyTrees* property_trees, - LayerListBuilder* layer_list_builder) { + LayerListBuilder* layer_list_builder, + int new_sequence_number) { DCHECK(root_layer_); property_trees_ = property_trees; layer_list_builder_ = layer_list_builder; - - // If we're initializing the manager again, it means that we will be - // rebuilding everything. Clear the current maps. - transform_node_map_.clear(); - clip_node_map_.clear(); - scroll_node_map_.clear(); - effect_node_map_.clear(); + new_sequence_number_ = new_sequence_number; SetupRootTransformNode(); SetupRootClipNode(); @@ -72,7 +67,6 @@ property_trees_ = nullptr; layer_list_builder_ = nullptr; #if DCHECK_IS_ON() - effect_nodes_converted_.clear(); DCHECK(initialized_); initialized_ = false; #endif @@ -81,10 +75,8 @@ bool PropertyTreeManager::DirectlyUpdateCompositedOpacityValue( cc::PropertyTrees* property_trees, const EffectPaintPropertyNode& effect) { - auto it = effect_node_map_.find(&effect); - if (it == effect_node_map_.end()) - return false; - auto* cc_effect = property_trees->effect_tree.Node(it->value); + auto* cc_effect = property_trees->effect_tree.Node( + effect.CcNodeId(property_trees->sequence_number)); if (!cc_effect) return false; @@ -107,27 +99,22 @@ if (!scroll_node) return false; - auto scroll_it = scroll_node_map_.find(scroll_node); - if (scroll_it == scroll_node_map_.end()) + auto* cc_scroll_node = property_trees->scroll_tree.Node( + scroll_node->CcNodeId(property_trees->sequence_number)); + if (!cc_scroll_node) return false; - auto* cc_scroll_node = property_trees->scroll_tree.Node(scroll_it->value); - auto transform_it = transform_node_map_.find(&transform); - if (transform_it == transform_node_map_.end()) - return false; - auto* cc_transform = property_trees->transform_tree.Node(transform_it->value); - - if (!cc_scroll_node || !cc_transform) + auto* cc_transform = property_trees->transform_tree.Node( + transform.CcNodeId(property_trees->sequence_number)); + if (!cc_transform) return false; DCHECK(!cc_transform->is_currently_animating); UpdateCcTransformLocalMatrix(*cc_transform, transform); - SetCcTransformNodeScrollToTransformTranslation(*cc_transform, transform); property_trees->scroll_tree.SetScrollOffset( scroll_node->GetCompositorElementId(), cc_transform->scroll_offset); - cc_transform->transform_changed = true; property_trees->transform_tree.set_needs_update(true); property_trees->scroll_tree.set_needs_update(true); return true; @@ -140,10 +127,8 @@ // DirectlyUpdateScrollOffsetTransform(). DCHECK(!transform.ScrollNode()); - auto transform_it = transform_node_map_.find(&transform); - if (transform_it == transform_node_map_.end()) - return false; - auto* cc_transform = property_trees->transform_tree.Node(transform_it->value); + auto* cc_transform = property_trees->transform_tree.Node( + transform.CcNodeId(property_trees->sequence_number)); if (!cc_transform) return false; @@ -154,7 +139,6 @@ // flag, we should clear it to let the compositor respect the new value. cc_transform->is_currently_animating = false; - cc_transform->transform_changed = true; property_trees->transform_tree.set_needs_update(true); return true; } @@ -164,16 +148,13 @@ const TransformPaintPropertyNode& transform) { DCHECK(!transform.ScrollNode()); - auto transform_it = transform_node_map_.find(&transform); - if (transform_it == transform_node_map_.end()) - return false; - auto* cc_transform = property_trees->transform_tree.Node(transform_it->value); + auto* cc_transform = property_trees->transform_tree.Node( + transform.CcNodeId(property_trees->sequence_number)); if (!cc_transform) return false; UpdateCcTransformLocalMatrix(*cc_transform, transform); AdjustPageScaleToUsePostLocal(*cc_transform); - cc_transform->transform_changed = true; SetTransformTreePageScaleFactor(&property_trees->transform_tree, cc_transform); @@ -223,8 +204,8 @@ transform_tree.SetFromScreen(kRealRootNodeId, from_screen); transform_tree.set_needs_update(true); - transform_node_map_.Set(&TransformPaintPropertyNode::Root(), - transform_node.id); + TransformPaintPropertyNode::Root().SetCcNodeId(new_sequence_number_, + transform_node.id); root_layer_->SetTransformTreeIndex(transform_node.id); } @@ -245,7 +226,7 @@ gfx::SizeF(root_layer_->layer_tree_host()->device_viewport_size())); clip_node.transform_id = kRealRootNodeId; - clip_node_map_.Set(&ClipPaintPropertyNode::Root(), clip_node.id); + ClipPaintPropertyNode::Root().SetCcNodeId(new_sequence_number_, clip_node.id); root_layer_->SetClipTreeIndex(clip_node.id); } @@ -267,6 +248,8 @@ effect_node.render_surface_reason = cc::RenderSurfaceReason::kRoot; root_layer_->SetEffectTreeIndex(effect_node.id); + EffectPaintPropertyNode::Root().SetCcNodeId(new_sequence_number_, + effect_node.id); SetCurrentEffectState(effect_node, CcEffectType::kEffect, EffectPaintPropertyNode::Root(), ClipPaintPropertyNode::Root()); @@ -281,7 +264,8 @@ DCHECK_EQ(scroll_node.id, kSecondaryRootNodeId); scroll_node.transform_id = kSecondaryRootNodeId; - scroll_node_map_.Set(&ScrollPaintPropertyNode::Root(), scroll_node.id); + ScrollPaintPropertyNode::Root().SetCcNodeId(new_sequence_number_, + scroll_node.id); root_layer_->SetScrollTreeIndex(scroll_node.id); } @@ -349,19 +333,20 @@ int PropertyTreeManager::EnsureCompositorTransformNode( const TransformPaintPropertyNode& transform_node_arg) { const auto& transform_node = transform_node_arg.Unalias(); - auto it = transform_node_map_.find(&transform_node); - if (it != transform_node_map_.end()) - return it->value; + int id = transform_node.CcNodeId(new_sequence_number_); + if (id != kInvalidNodeId) { + DCHECK(GetTransformTree().Node(id)); + return id; + } DCHECK(transform_node.Parent()); int parent_id = EnsureCompositorTransformNode(*transform_node.Parent()); - int id = GetTransformTree().Insert(cc::TransformNode(), parent_id); + id = GetTransformTree().Insert(cc::TransformNode(), parent_id); cc::TransformNode& compositor_node = *GetTransformTree().Node(id); compositor_node.source_node_id = parent_id; UpdateCcTransformLocalMatrix(compositor_node, transform_node); - compositor_node.transform_changed = true; compositor_node.flattens_inherited_transform = transform_node.FlattensInheritedTransform(); compositor_node.sorting_context_id = transform_node.RenderingContextId(); @@ -412,12 +397,8 @@ // compositor scroll property node and adjust the compositor transform node's // scroll offset. if (auto* scroll_node = transform_node.ScrollNode()) { - // Blink creates a 2d transform node just for scroll offset whereas cc's - // transform node has a special scroll offset field. To handle this we - // adjust cc's transform node to remove the 2d scroll translation and - // instead set the scroll_offset field. - SetCcTransformNodeScrollToTransformTranslation(compositor_node, - transform_node); + compositor_node.scrolls = true; + compositor_node.should_be_snapped = true; CreateCompositorScrollNode(*scroll_node, compositor_node); } @@ -435,8 +416,7 @@ cc::RenderSurfaceReason::k3dTransformFlattening); } - auto result = transform_node_map_.Set(&transform_node, id); - DCHECK(result.is_new_entry); + transform_node.SetCcNodeId(new_sequence_number_, id); GetTransformTree().set_needs_update(true); return id; @@ -445,33 +425,43 @@ void PropertyTreeManager::UpdateCcTransformLocalMatrix( cc::TransformNode& compositor_node, const TransformPaintPropertyNode& transform_node) { - FloatPoint3D origin = transform_node.Origin(); - compositor_node.pre_local.matrix().setTranslate(-origin.X(), -origin.Y(), - -origin.Z()); - // The sticky offset on the blink transform node is pre-computed and stored - // to the local matrix. Cc applies sticky offset dynamically on top of the - // local matrix. We should not set the local matrix on cc node if it is a - // sticky node because the sticky offset would be applied twice otherwise. - if (!transform_node.GetStickyConstraint()) { + if (transform_node.GetStickyConstraint()) { + // The sticky offset on the blink transform node is pre-computed and stored + // to the local matrix. Cc applies sticky offset dynamically on top of the + // local matrix. We should not set the local matrix on cc node if it is a + // sticky node because the sticky offset would be applied twice otherwise. + DCHECK(compositor_node.local.IsIdentity()); + DCHECK(compositor_node.pre_local.IsIdentity()); + DCHECK(compositor_node.post_local.IsIdentity()); + } else if (transform_node.IsIdentityOr2DTranslation()) { + auto translation = transform_node.Translation2D(); + if (transform_node.ScrollNode()) { + // Blink creates a 2d transform node just for scroll offset whereas cc's + // transform node has a special scroll offset field. + compositor_node.scroll_offset = + gfx::ScrollOffset(-translation.Width(), -translation.Height()); + DCHECK(compositor_node.local.IsIdentity()); + DCHECK(compositor_node.pre_local.IsIdentity()); + DCHECK(compositor_node.post_local.IsIdentity()); + } else { + compositor_node.local.matrix().setTranslate(translation.Width(), + translation.Height(), 0); + DCHECK_EQ(FloatPoint3D(), transform_node.Origin()); + compositor_node.pre_local.matrix().setIdentity(); + compositor_node.post_local.matrix().setIdentity(); + } + } else { + DCHECK(!transform_node.ScrollNode()); + FloatPoint3D origin = transform_node.Origin(); + compositor_node.pre_local.matrix().setTranslate(-origin.X(), -origin.Y(), + -origin.Z()); compositor_node.local.matrix() = - TransformationMatrix::ToSkMatrix44(transform_node.SlowMatrix()); + TransformationMatrix::ToSkMatrix44(transform_node.Matrix()); + compositor_node.post_local.matrix().setTranslate(origin.X(), origin.Y(), + origin.Z()); } - compositor_node.post_local.matrix().setTranslate(origin.X(), origin.Y(), - origin.Z()); compositor_node.needs_local_transform_update = true; -} - -void PropertyTreeManager::SetCcTransformNodeScrollToTransformTranslation( - cc::TransformNode& cc_transform, - const TransformPaintPropertyNode& transform) { - auto scroll_offset_size = transform.Translation2D(); - auto scroll_offset = gfx::ScrollOffset(-scroll_offset_size.Width(), - -scroll_offset_size.Height()); - DCHECK(cc_transform.local.IsIdentityOr2DTranslation()); - cc_transform.scroll_offset = scroll_offset; - cc_transform.local.MakeIdentity(); - cc_transform.scrolls = true; - cc_transform.should_be_snapped = true; + compositor_node.transform_changed = true; } int PropertyTreeManager::EnsureCompositorPageScaleTransformNode( @@ -481,7 +471,6 @@ DCHECK(GetTransformTree().Node(id)); cc::TransformNode& compositor_node = *GetTransformTree().Node(id); AdjustPageScaleToUsePostLocal(compositor_node); - compositor_node.transform_changed = true; SetTransformTreePageScaleFactor(&GetTransformTree(), &compositor_node); GetTransformTree().set_needs_update(true); @@ -498,6 +487,7 @@ page_scale.post_local.matrix() = page_scale.local.matrix(); page_scale.pre_local.matrix().setIdentity(); page_scale.local.matrix().setIdentity(); + page_scale.transform_changed = true; } void PropertyTreeManager::SetTransformTreePageScaleFactor( @@ -517,13 +507,15 @@ int PropertyTreeManager::EnsureCompositorClipNode( const ClipPaintPropertyNode& clip_node_arg) { const auto& clip_node = clip_node_arg.Unalias(); - auto it = clip_node_map_.find(&clip_node); - if (it != clip_node_map_.end()) - return it->value; + int id = clip_node.CcNodeId(new_sequence_number_); + if (id != kInvalidNodeId) { + DCHECK(GetClipTree().Node(id)); + return id; + } DCHECK(clip_node.Parent()); int parent_id = EnsureCompositorClipNode(*clip_node.Parent()); - int id = GetClipTree().Insert(cc::ClipNode(), parent_id); + id = GetClipTree().Insert(cc::ClipNode(), parent_id); cc::ClipNode& compositor_node = *GetClipTree().Node(id); @@ -532,8 +524,7 @@ EnsureCompositorTransformNode(clip_node.LocalTransformSpace()); compositor_node.clip_type = cc::ClipNode::ClipType::APPLIES_LOCAL_CLIP; - auto result = clip_node_map_.Set(&clip_node, id); - DCHECK(result.is_new_entry); + clip_node.SetCcNodeId(new_sequence_number_, id); GetClipTree().set_needs_update(true); return id; } @@ -541,14 +532,13 @@ void PropertyTreeManager::CreateCompositorScrollNode( const ScrollPaintPropertyNode& scroll_node, const cc::TransformNode& scroll_offset_translation) { - DCHECK(!scroll_node_map_.Contains(&scroll_node)); + DCHECK(!GetScrollTree().Node(scroll_node.CcNodeId(new_sequence_number_))); - auto parent_it = scroll_node_map_.find(scroll_node.Parent()); + int parent_id = scroll_node.Parent()->CcNodeId(new_sequence_number_); // Compositor transform nodes up to scroll_offset_translation must exist. // Scrolling uses the transform tree for scroll offsets so this means all // ancestor scroll nodes must also exist. - DCHECK(parent_it != scroll_node_map_.end()); - int parent_id = parent_it->value; + DCHECK(GetScrollTree().Node(parent_id)); int id = GetScrollTree().Insert(cc::ScrollNode(), parent_id); cc::ScrollNode& compositor_node = *GetScrollTree().Node(id); @@ -585,8 +575,7 @@ // TODO(pdr): Set the scroll node's non_fast_scrolling_region value. - auto result = scroll_node_map_.Set(&scroll_node, id); - DCHECK(result.is_new_entry); + scroll_node.SetCcNodeId(new_sequence_number_, id); GetScrollTree().SetScrollOffset(compositor_element_id, scroll_offset_translation.scroll_offset); @@ -598,9 +587,9 @@ const auto* scroll_node = scroll_offset_translation.ScrollNode(); DCHECK(scroll_node); EnsureCompositorTransformNode(scroll_offset_translation); - auto it = scroll_node_map_.find(scroll_node); - DCHECK(it != scroll_node_map_.end()); - return it->value; + int id = scroll_node->CcNodeId(new_sequence_number_); + DCHECK(GetScrollTree().Node(id)); + return id; } void PropertyTreeManager::EmitClipMaskLayer() { @@ -907,10 +896,9 @@ DCHECK_EQ(&next_effect.Parent()->Unalias(), current_.effect); #if DCHECK_IS_ON() - DCHECK(!effect_nodes_converted_.Contains(&next_effect)) + DCHECK(!GetEffectTree().Node(next_effect.CcNodeId(new_sequence_number_))) << "Malformed paint artifact. Paint chunks under the same effect should " "be contiguous."; - effect_nodes_converted_.insert(&next_effect); #endif // If we don't have an output clip, then we'll use the clip of the last @@ -930,7 +918,7 @@ int effect_node_id = GetEffectTree().Insert(cc::EffectNode(), current_.effect_id); auto& effect_node = *GetEffectTree().Node(effect_node_id); - effect_node_map_.Set(&next_effect, effect_node_id); + next_effect.SetCcNodeId(new_sequence_number_, effect_node_id); PopulateCcEffectNode(effect_node, next_effect, output_clip_id, blend_mode);
diff --git a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.h b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.h index 0bf293e..ad38a2a 100644 --- a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.h +++ b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.h
@@ -56,7 +56,8 @@ } void Initialize(cc::PropertyTrees* property_trees, - LayerListBuilder* layer_list_builder); + LayerListBuilder* layer_list_builder, + int new_sequence_number); void SetRootLayer(cc::Layer* root_layer) { DCHECK(!root_layer_) << "We can only set root layer once."; @@ -165,13 +166,6 @@ // scale node. void SetTransformTreePageScaleFactor(cc::TransformTree*, cc::TransformNode* page_scale_node); - // Move the local translation into the scroll_offset field of the compositor - // transform node. The compositor uses a speical scroll_offset field instead - // of the local matrix for scroll nodes, whereas - // blink::TransformPaintPropertyNode represents this as a 2d translation. - void SetCcTransformNodeScrollToTransformTranslation( - cc::TransformNode&, - const TransformPaintPropertyNode&); bool IsCurrentCcEffectSynthetic() const { return current_.effect_type; } bool IsCurrentCcEffectSyntheticForNonTrivialClip() const { @@ -225,12 +219,7 @@ LayerListBuilder* layer_list_builder_ = nullptr; - // Maps from Blink-side property tree nodes to cc property node indices. - HashMap<scoped_refptr<const TransformPaintPropertyNode>, int> - transform_node_map_; - HashMap<scoped_refptr<const ClipPaintPropertyNode>, int> clip_node_map_; - HashMap<scoped_refptr<const ScrollPaintPropertyNode>, int> scroll_node_map_; - HashMap<scoped_refptr<const EffectPaintPropertyNode>, int> effect_node_map_; + int new_sequence_number_ = -1; struct EffectState { // The cc effect node that has the corresponding drawing state to the @@ -284,7 +273,6 @@ HashSet<int> pending_synthetic_mask_layers_; #if DCHECK_IS_ON() - HashSet<const EffectPaintPropertyNode*> effect_nodes_converted_; bool initialized_ = false; #endif
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_property_node.h b/third_party/blink/renderer/platform/graphics/paint/paint_property_node.h index 2980189..81d7675 100644 --- a/third_party/blink/renderer/platform/graphics/paint/paint_property_node.h +++ b/third_party/blink/renderer/platform/graphics/paint/paint_property_node.h
@@ -138,6 +138,14 @@ #endif } + int CcNodeId(int sequence_number) const { + return cc_sequence_number_ == sequence_number ? cc_node_id_ : -1; + } + void SetCcNodeId(int sequence_number, int id) const { + cc_sequence_number_ = sequence_number; + cc_node_id_ = id; + } + #if DCHECK_IS_ON() String ToTreeString() const; @@ -177,6 +185,11 @@ scoped_refptr<const NodeType> parent_; + // Caches the id of the associated cc property node. It's valid only when + // cc_sequence_number_ matches the sequence number of the cc property tree. + mutable int cc_node_id_ = -1; + mutable int cc_sequence_number_ = 0; + // Indicates whether this node is an alias for its parent. Parent aliases are // nodes that do not affect rendering and are ignored for the purposes of // display item list generation.
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 index ab4f1d9..8b2388a 100644 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1119,6 +1119,11 @@ status: {"Android": "", "default": "stable"}, }, { + name: "PictureInPictureV2", + depends_on: ["PictureInPictureAPI"], + settable_from_internals: true, + }, + { name: "PointerRawUpdate", status: "experimental", }, @@ -1354,7 +1359,7 @@ status: "stable", }, { - name: "SmsRetrieval", + name: "SmsReceiver", status: "experimental", }, {
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations index ae8fa4c..652e330 100644 --- a/third_party/blink/web_tests/TestExpectations +++ b/third_party/blink/web_tests/TestExpectations
@@ -5776,7 +5776,6 @@ crbug.com/919587 [ Linux ] virtual/threaded/fast/idle-callback/idle_periods.html [ Pass Failure ] # WebRTC Unified Plan -crbug.com/920979 external/wpt/webrtc/RTCRtpTransceiver.https.html [ Timeout ] crbug.com/923244 external/wpt/webrtc/RTCDTMFSender-ontonechange.https.html [ Failure Pass ] # WebRTC Plan B crbug.com/920979 virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-remote-track-mute.https.html [ Timeout ]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json index 7b26c937..77f8217 100644 --- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json +++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json
@@ -6403,15 +6403,9 @@ {} ] ], - "pointerevents/extension/pointerevent_pointerrawmove-manual.html": [ + "pointerevents/extension/pointerevent_pointerrawupdate_in_pointerlock-manual.html": [ [ - "pointerevents/extension/pointerevent_pointerrawmove-manual.html", - {} - ] - ], - "pointerevents/extension/pointerevent_pointerrawmove_in_pointerlock-manual.html": [ - [ - "pointerevents/extension/pointerevent_pointerrawmove_in_pointerlock-manual.html", + "pointerevents/extension/pointerevent_pointerrawupdate_in_pointerlock-manual.html", {} ] ], @@ -41341,6 +41335,18 @@ {} ] ], + "css/css-flexbox/auto-margins-002.html": [ + [ + "css/css-flexbox/auto-margins-002.html", + [ + [ + "/css/reference/ref-filled-green-100px-square.xht", + "==" + ] + ], + {} + ] + ], "css/css-flexbox/css-box-justify-content.html": [ [ "css/css-flexbox/css-box-justify-content.html", @@ -119042,16 +119048,6 @@ {} ] ], - "IndexedDB/idbcursor-request.any-expected.txt": [ - [ - {} - ] - ], - "IndexedDB/idbcursor-request.any.worker-expected.txt": [ - [ - {} - ] - ], "IndexedDB/idbobjectstore_createIndex15-autoincrement-expected.txt": [ [ {} @@ -119062,26 +119058,6 @@ {} ] ], - "IndexedDB/idlharness.any-expected.txt": [ - [ - {} - ] - ], - "IndexedDB/idlharness.any.serviceworker-expected.txt": [ - [ - {} - ] - ], - "IndexedDB/idlharness.any.sharedworker-expected.txt": [ - [ - {} - ] - ], - "IndexedDB/idlharness.any.worker-expected.txt": [ - [ - {} - ] - ], "IndexedDB/interleaved-cursors-common.js": [ [ {} @@ -134022,6 +133998,11 @@ {} ] ], + "css/css-flexbox/support/300x150-green.png": [ + [ + {} + ] + ], "css/css-flexbox/support/60x60-gg-rr.png": [ [ {} @@ -144322,11 +144303,6 @@ {} ] ], - "css/css-masking/parsing/clip-path-invalid-expected.txt": [ - [ - {} - ] - ], "css/css-masking/parsing/clip-path-valid-expected.txt": [ [ {} @@ -146182,11 +146158,6 @@ {} ] ], - "css/css-shapes/parsing/shape-outside-invalid-expected.txt": [ - [ - {} - ] - ], "css/css-shapes/parsing/shape-outside-valid-expected.txt": [ [ {} @@ -166432,17 +166403,17 @@ {} ] ], - "fetch/stale-while-revalidate/stale-css.py": [ + "fetch/stale-while-revalidate/resources/stale-css.py": [ [ {} ] ], - "fetch/stale-while-revalidate/stale-image.py": [ + "fetch/stale-while-revalidate/resources/stale-image.py": [ [ {} ] ], - "fetch/stale-while-revalidate/stale-script.py": [ + "fetch/stale-while-revalidate/resources/stale-script.py": [ [ {} ] @@ -181927,6 +181898,11 @@ {} ] ], + "mediasession/idlharness.window-expected.txt": [ + [ + {} + ] + ], "mimesniff/META.yml": [ [ {} @@ -183962,7 +183938,7 @@ {} ] ], - "pointerevents/resources/pointerevent_pointerrawmove_in_pointerlock-iframe.html": [ + "pointerevents/resources/pointerevent_pointerrawupdate_in_pointerlock-iframe.html": [ [ {} ] @@ -184022,11 +183998,6 @@ {} ] ], - "portals/resources/portal-cross-origin.sub.html": [ - [ - {} - ] - ], "portals/resources/portal-embed-and-activate.html": [ [ {} @@ -184042,11 +184013,6 @@ {} ] ], - "portals/resources/portal-host-cross-origin.sub.html": [ - [ - {} - ] - ], "portals/resources/portal-host-hidden-after-activation-portal.html": [ [ {} @@ -202047,6 +202013,11 @@ {} ] ], + "webrtc/RTCRtpTransceiver.https-expected.txt": [ + [ + {} + ] + ], "webrtc/RTCSctpTransport-maxMessageSize-expected.txt": [ [ {} @@ -232018,6 +231989,12 @@ {} ] ], + "css/css-masking/parsing/clip-path-computed.html": [ + [ + "css/css-masking/parsing/clip-path-computed.html", + {} + ] + ], "css/css-masking/parsing/clip-path-invalid.html": [ [ "css/css-masking/parsing/clip-path-invalid.html", @@ -259335,33 +259312,33 @@ {} ] ], - "fetch/stale-while-revalidate/fetch-sw.https.tentative.html": [ + "fetch/stale-while-revalidate/fetch-sw.https.html": [ [ - "fetch/stale-while-revalidate/fetch-sw.https.tentative.html", + "fetch/stale-while-revalidate/fetch-sw.https.html", {} ] ], - "fetch/stale-while-revalidate/fetch.tentative.html": [ + "fetch/stale-while-revalidate/fetch.html": [ [ - "fetch/stale-while-revalidate/fetch.tentative.html", + "fetch/stale-while-revalidate/fetch.html", {} ] ], - "fetch/stale-while-revalidate/stale-css.tentative.html": [ + "fetch/stale-while-revalidate/stale-css.html": [ [ - "fetch/stale-while-revalidate/stale-css.tentative.html", + "fetch/stale-while-revalidate/stale-css.html", {} ] ], - "fetch/stale-while-revalidate/stale-image.tentative.html": [ + "fetch/stale-while-revalidate/stale-image.html": [ [ - "fetch/stale-while-revalidate/stale-image.tentative.html", + "fetch/stale-while-revalidate/stale-image.html", {} ] ], - "fetch/stale-while-revalidate/stale-script.tentative.html": [ + "fetch/stale-while-revalidate/stale-script.html": [ [ - "fetch/stale-while-revalidate/stale-script.tentative.html", + "fetch/stale-while-revalidate/stale-script.html", {} ] ], @@ -290269,6 +290246,14 @@ {} ] ], + "pointerevents/extension/pointerevent_pointerrawupdate.html": [ + [ + "pointerevents/extension/pointerevent_pointerrawupdate.html", + { + "testdriver": true + } + ] + ], "pointerevents/extension/pointerevent_touch-action-verification.html": [ [ "pointerevents/extension/pointerevent_touch-action-verification.html", @@ -340506,18 +340491,10 @@ "9216a0c4416b47ab5ab51e0b407429512a0125d2", "testharness" ], - "IndexedDB/idbcursor-request.any-expected.txt": [ - "c28fc3283d251184f7634ab7a6e793d1e0da90c3", - "support" - ], "IndexedDB/idbcursor-request.any.js": [ - "a62efc0b8eb7a39dffd0e3414b46d9c2969fc0d7", + "60e68548d20ce0fb55c1051b4410e7e49e06f644", "testharness" ], - "IndexedDB/idbcursor-request.any.worker-expected.txt": [ - "c28fc3283d251184f7634ab7a6e793d1e0da90c3", - "support" - ], "IndexedDB/idbcursor-reused.htm": [ "603041e7cdf98bcbbf9a513970902db72b038e97", "testharness" @@ -341582,26 +341559,10 @@ "359f6fb69119b5c6605047f0e0c38ef6fe847009", "support" ], - "IndexedDB/idlharness.any-expected.txt": [ - "b2a16a0835f199d09bac21cfffa04049197c1aaa", - "support" - ], "IndexedDB/idlharness.any.js": [ "efb84661e8044d146ab0704f18a6a3805ff3c803", "testharness" ], - "IndexedDB/idlharness.any.serviceworker-expected.txt": [ - "2dc632ce8fc9d0fde57e9ce1f7e876b59f656bf6", - "support" - ], - "IndexedDB/idlharness.any.sharedworker-expected.txt": [ - "2dc632ce8fc9d0fde57e9ce1f7e876b59f656bf6", - "support" - ], - "IndexedDB/idlharness.any.worker-expected.txt": [ - "2dc632ce8fc9d0fde57e9ce1f7e876b59f656bf6", - "support" - ], "IndexedDB/index_sort_order.htm": [ "6249c4204897ffd9f292908ca50dae6f8655c02e", "testharness" @@ -343495,7 +343456,7 @@ "support" ], "animation-worklet/inactive-timeline.https.html": [ - "c2311e68e5ace89d533417a80fa885c8631f687a", + "874e709546b979a7fe8da6ac2cbac6c2e66aac03", "testharness" ], "animation-worklet/multiple-effects-on-same-target-driven-by-individual-local-time.https.html": [ @@ -372838,6 +372799,10 @@ "3a90ef2be84635834c5c3f7114451c8e8c437ac7", "reftest" ], + "css/css-flexbox/auto-margins-002.html": [ + "454bc922f6a7c082de3e08d336b2c615ea15ed3b", + "reftest" + ], "css/css-flexbox/css-box-justify-content.html": [ "d5c7244f08dcad0b0955290804ec5959754a963d", "reftest" @@ -375658,6 +375623,10 @@ "1136e7230b51e0b6208fac90a8bb45e34439cb4b", "support" ], + "css/css-flexbox/support/300x150-green.png": [ + "57ece824a40be3d5d42d1ae53fa505cd1b8f237d", + "support" + ], "css/css-flexbox/support/60x60-gg-rr.png": [ "84f5b2a4f1d1865d763cac875bfa6a8c5c576c91", "support" @@ -388678,20 +388647,20 @@ "852612027840e24673432bcdcf6abaa33f422631", "testharness" ], - "css/css-masking/parsing/clip-path-invalid-expected.txt": [ - "bafc82945a838419604a9500bf8e56a915cac127", - "support" + "css/css-masking/parsing/clip-path-computed.html": [ + "712cad57b4c9d1d82af65a88b91b4e4f6ea3f9d4", + "testharness" ], "css/css-masking/parsing/clip-path-invalid.html": [ - "2672d486d3e8341cf2bd4e62af047e05ca4bf711", + "129d403ed6b0d8b3a70b025c632ed6a831ab8ba6", "testharness" ], "css/css-masking/parsing/clip-path-valid-expected.txt": [ - "3b1e5847926cf8f177c9edf1c9231711943dfbd1", + "b2b37820d99e3ca84dde94fddb7b12e0f3f182b2", "support" ], "css/css-masking/parsing/clip-path-valid.html": [ - "83c76fb9ff5289250c1920f7c2e59b240991cf31", + "e734b9027f6d8e4175848c62dc00009b5828bef9", "testharness" ], "css/css-masking/parsing/clip-rule-invalid.html": [ @@ -393099,23 +393068,19 @@ "testharness" ], "css/css-shapes/parsing/shape-outside-computed.html": [ - "2be31ce473b3f19e4a39dd18176dc00ff0652e98", + "fb86b66fbb6c7767471db008092ae9b6b8ddd928", "testharness" ], - "css/css-shapes/parsing/shape-outside-invalid-expected.txt": [ - "0d52e476bb82e0a98494144cf009b30e5f7aded0", - "support" - ], "css/css-shapes/parsing/shape-outside-invalid-position.html": [ "e61c7071f9180bcbf127e09b3b8edef5401e09d9", "testharness" ], "css/css-shapes/parsing/shape-outside-invalid.html": [ - "484bafe4b5f8527076636a7495f10148ab0fe299", + "366fa920819f57bb7d30968f16a22c6a29cf2d00", "testharness" ], "css/css-shapes/parsing/shape-outside-valid-expected.txt": [ - "15065eae038131b8ecf331ac99819d310c8485e7", + "ff80dc81dbd73d8a1896bccbdb7a6e85ade7ee95", "support" ], "css/css-shapes/parsing/shape-outside-valid-position.html": [ @@ -393123,7 +393088,7 @@ "testharness" ], "css/css-shapes/parsing/shape-outside-valid.html": [ - "d52eb9ada0a5e91f4e78f3b669b683b4c1e6f054", + "43e715dd3e396ab59be3e9f963be5505628a53d8", "testharness" ], "css/css-shapes/shape-outside-invalid-001.html": [ @@ -393995,19 +393960,19 @@ "testharness" ], "css/css-shapes/shape-outside/values/shape-outside-ellipse-010-expected.txt": [ - "d2bcdb95138fecd7a04cb98956a8027c55e04f5c", + "b7f5a6c7be2e69c534a7c29347d142527510ad2d", "support" ], "css/css-shapes/shape-outside/values/shape-outside-ellipse-010.html": [ - "19f80c25d59d620aff41bd78e8d7798182824a14", + "e5673b07cc4ac8e367e327b1f1187d04f61393e4", "testharness" ], "css/css-shapes/shape-outside/values/shape-outside-ellipse-011-expected.txt": [ - "ebe434e047586370070a202bdafb56599b300af9", + "9bb0068dc08e35d94a98fbeccda87d7119f64c33", "support" ], "css/css-shapes/shape-outside/values/shape-outside-ellipse-011.html": [ - "983ff7b58798377a5ec32eaa0f0102c866b82524", + "ad01bb60ea43a06b8513e1bd9ea1abcd595c4453", "testharness" ], "css/css-shapes/shape-outside/values/shape-outside-inset-000.html": [ @@ -394127,7 +394092,7 @@ "testharness" ], "css/css-shapes/shape-outside/values/support/parsing-utils.js": [ - "81bcf7da56dbb228badb80f1dbb2865487fc2a18", + "118a11453337731f380063b736380c2c8610ae81", "support" ], "css/css-shapes/spec-examples/reference/shape-outside-001-ref.html": [ @@ -438058,36 +438023,36 @@ "20d307e9188405dcec011042487aa2c7354930bf", "support" ], - "fetch/stale-while-revalidate/fetch-sw.https.tentative.html": [ - "f6ece2cfe3e1f8f85b89d9f6402641104f6085d4", + "fetch/stale-while-revalidate/fetch-sw.https.html": [ + "efcebc24a63f40de54a4c3991c9050eb74486a29", "testharness" ], - "fetch/stale-while-revalidate/fetch.tentative.html": [ - "33d844fd08f3352bff1677cabf0c1dd06177e40a", + "fetch/stale-while-revalidate/fetch.html": [ + "73390c7ad5948c682b2fbdcb3c95a4c374dbba2d", "testharness" ], - "fetch/stale-while-revalidate/stale-css.py": [ + "fetch/stale-while-revalidate/resources/stale-css.py": [ "a6ae546d0651f97f3020829452db6225486dc451", "support" ], - "fetch/stale-while-revalidate/stale-css.tentative.html": [ - "3459493f28c22011e96def2549f01df1d5e0cbf2", - "testharness" - ], - "fetch/stale-while-revalidate/stale-image.py": [ - "4b67184185eb6cf9206bc57e2ca25c3da96643c2", + "fetch/stale-while-revalidate/resources/stale-image.py": [ + "839eb84bb34bbd15e2ca7061ee4fa5724d2c64f8", "support" ], - "fetch/stale-while-revalidate/stale-image.tentative.html": [ - "8b6a896eb15219e2222d65863c81724959c1ee1a", - "testharness" - ], - "fetch/stale-while-revalidate/stale-script.py": [ + "fetch/stale-while-revalidate/resources/stale-script.py": [ "8ad54671f4211735f56df50a55deea6ed281d5a2", "support" ], - "fetch/stale-while-revalidate/stale-script.tentative.html": [ - "8cbb54b7dab3bc9b9e8763c5358a9232d24c1e7f", + "fetch/stale-while-revalidate/stale-css.html": [ + "f56260fdb45c0c2c60b4d76f63a76389df818b11", + "testharness" + ], + "fetch/stale-while-revalidate/stale-image.html": [ + "0a08f81729de49985c6575e1a7a5fcb77d3a0ee2", + "testharness" + ], + "fetch/stale-while-revalidate/stale-script.html": [ + "68793e50056bc6053ea410d658aed373d19f9a41", "testharness" ], "fetch/stale-while-revalidate/sw-intercept.js": [ @@ -460211,7 +460176,7 @@ "support" ], "interfaces/mediasession.idl": [ - "6fd5725dbb2975e724594629f0c2882477455caa", + "95210c0bca3e06a8c1b418ea48d0c0c2e6b1caba", "support" ], "interfaces/mediastream-recording.idl": [ @@ -460259,7 +460224,7 @@ "support" ], "interfaces/payment-request.idl": [ - "53922d315cfd3de149f88d18baeccc852bb3dca1", + "f8bd336dbe3b22223030a60cdd36be93067b3a9f", "support" ], "interfaces/performance-timeline.idl": [ @@ -462830,6 +462795,10 @@ "7c7c9f8d57a46ae310b1a63df7c6117f89b56d63", "support" ], + "mediasession/idlharness.window-expected.txt": [ + "90ec4e06dfd6a2d52b4375034327329c91a0bc57", + "support" + ], "mediasession/idlharness.window.js": [ "5fa0d22c10a543f187bcc047fd2a244ec699eba9", "testharness" @@ -465567,7 +465536,7 @@ "testharness" ], "native-file-system/FileSystemWriter.tentative.window.js": [ - "cc8444237b6576f5425f74c0c3c5b12d0f644cd0", + "bc5cfd104b6a2645a5321e0f9d38e990b6533969", "testharness" ], "native-file-system/README.md": [ @@ -473239,7 +473208,7 @@ "testharness" ], "payment-request/idlharness.https.window-expected.txt": [ - "705e093b4e488f1bf1d7801ec421f3695adacb9d", + "62e7edfb041df6a6cd15e356cf642cd51bd7f8e2", "support" ], "payment-request/idlharness.https.window.js": [ @@ -473694,12 +473663,12 @@ "eaf08d675a403aaf8bbf5a39e965af39a15d9d4e", "manual" ], - "pointerevents/extension/pointerevent_pointerrawmove-manual.html": [ - "0c4ccf9ad48d49c62b3d138845dfa3ac631a8e4e", - "manual" + "pointerevents/extension/pointerevent_pointerrawupdate.html": [ + "0d317010621d005b32631ad26a4f3b3ca08b181a", + "testharness" ], - "pointerevents/extension/pointerevent_pointerrawmove_in_pointerlock-manual.html": [ - "970355863bb7d6affb0b50289af75b98cfccbb57", + "pointerevents/extension/pointerevent_pointerrawupdate_in_pointerlock-manual.html": [ + "704e44195f6db0653e7187119a6c485dbb3c32c5", "manual" ], "pointerevents/extension/pointerevent_predicted_events_attributes-manual.html": [ @@ -473919,7 +473888,7 @@ "support" ], "pointerevents/pointerevent_support.js": [ - "e8c847b12a880a557d8d3cd69bc0bc77cf3378af", + "dc35ea24eebc65dd3b0fe52dd54042c78959bd1d", "support" ], "pointerevents/pointerevent_suppress_compat_events_on_click.html": [ @@ -474070,7 +474039,7 @@ "ab33560b35216ea0976d1c037650122d9336ae39", "support" ], - "pointerevents/resources/pointerevent_pointerrawmove_in_pointerlock-iframe.html": [ + "pointerevents/resources/pointerevent_pointerrawupdate_in_pointerlock-iframe.html": [ "505fc2cae40b80612fdd67ba98918aafad2f1b0a", "support" ], @@ -474175,11 +474144,11 @@ "testharness" ], "portals/portals-activate-resolution.html": [ - "9fb99e42b05feb9b952a2c794d586d47ffffc98c", + "03dc09f3cf51c17a4b15e01719ff6ef36d15621b", "testharness" ], "portals/portals-activate-twice.html": [ - "074d3f4c7ae424d1cba5c07a6728ab5671522529", + "731a342c7f58101cabd82f9a8f71e739ac31d217", "testharness" ], "portals/portals-adopt-predecessor.html": [ @@ -474187,11 +474156,11 @@ "testharness" ], "portals/portals-cross-origin-load.sub.html": [ - "f860ac54dc9dc6578fa1a66c25da70bc3262d995", + "e94c45be6d75b964e59d985c2826d9cb97947e31", "testharness" ], "portals/portals-host-exposure.sub.html": [ - "83e31bd4735131d35b2a03ae82d07be364497689", + "36fc2b48c8d87e10a1cb533e19372de1a9251825", "testharness" ], "portals/portals-host-hidden-after-activation.html": [ @@ -474219,7 +474188,7 @@ "testharness" ], "portals/portals-rendering.html": [ - "8683a38326d96f5e180d3a2042aba652b8c0fa03", + "0eae0ddfd6ef1e9de2251f50914a855f4142b9d5", "reftest" ], "portals/references/portals-rendering.html": [ @@ -474235,19 +474204,15 @@ "support" ], "portals/resources/portal-activate-inside-portal.html": [ - "241a75e4a28d95aecac219549f8ed2d7c9e58d97", + "9c9c4df6695909cbe3dc64e9f8f72b918fa58dd3", "support" ], "portals/resources/portal-activate-twice-window-1.html": [ - "197153f02d803fcef5b1af124626381c19c164bb", + "2fe6755e96102eaa4a66589347ba5cadb6f0a1ed", "support" ], "portals/resources/portal-activate-twice-window-2.html": [ - "dc161c0e0b82da493aba1cb8fbefb4262b203a48", - "support" - ], - "portals/resources/portal-cross-origin.sub.html": [ - "145ab5a2d21295f615d3ecd5d36f9e3034a4202a", + "89fdf15d7891718522f32ea39e87da9245a86f69", "support" ], "portals/resources/portal-embed-and-activate.html": [ @@ -474259,11 +474224,7 @@ "support" ], "portals/resources/portal-host-cross-origin-navigate.sub.html": [ - "44c6c16c5771f1027c3cc82e966342bbaa80ad8d", - "support" - ], - "portals/resources/portal-host-cross-origin.sub.html": [ - "dc4e9e7b5de4d17d2110f943fc04b99ed076102c", + "ce7aaf846486f987966470f7150effc93a178468", "support" ], "portals/resources/portal-host-hidden-after-activation-portal.html": [ @@ -474287,7 +474248,7 @@ "support" ], "portals/resources/portal-host.html": [ - "5043a158ea74ef173f166c0580f9c1a27242bd14", + "3a407d8c325823c5c48dde7d2f4524fe436655a2", "support" ], "portals/resources/portal-inside-iframe.html": [ @@ -474335,7 +474296,7 @@ "support" ], "portals/resources/portals-rendering-portal.html": [ - "1b6f23f512da5bb7d1c7b5b85e48277470d2e146", + "c3fe18ef5345c8d963da76bc9d6445635f310f17", "support" ], "portals/resources/postmessage-referrer.sub.html": [ @@ -474343,7 +474304,7 @@ "support" ], "portals/resources/simple-portal.html": [ - "fe7f6536edc902e98b48d12a5fb8ca0186e65a0e", + "d4710c018c0653406cc6e219c7ae677bc0566d62", "support" ], "preload/META.yml": [ @@ -497351,11 +497312,11 @@ "support" ], "tools/wptrunner/wptrunner/formatters/chromium.py": [ - "071c2f378902965971a77d46911fd89a85a18bdc", + "15e8e0b0ce9d09ef4783cd429610d221c91255f3", "support" ], "tools/wptrunner/wptrunner/formatters/tests/test_chromium.py": [ - "12962f4d8bcab0578912fa88ea4156e5adf0dd7f", + "b2c261f92f7e9b4ef8b225e337044d50dfa0c46c", "support" ], "tools/wptrunner/wptrunner/formatters/wptreport.py": [ @@ -502975,7 +502936,7 @@ "support" ], "webrtc-quic/RTCQuicTransport.https.html": [ - "12c2371f8e214becabb7a8a221a875d61d002ff2", + "b84d90f2388fffa49bf1f57ee178d31c953497db", "testharness" ], "webrtc-stats/META.yml": [ @@ -503530,8 +503491,12 @@ "4f57dc7b6d348486e2715aff1e7d7e773c29d0e3", "testharness" ], + "webrtc/RTCRtpTransceiver.https-expected.txt": [ + "dedfbd18e8ada4110435b0b3c990e33e3d23e47f", + "support" + ], "webrtc/RTCRtpTransceiver.https.html": [ - "0b560d2f26e34db31d19ad51a87d3690a61e5947", + "eaab18d6bbb63031edc9f67bcdeab080d8d884a8", "testharness" ], "webrtc/RTCSctpTransport-constructor.html": [
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/invisible-images.html b/third_party/blink/web_tests/external/wpt/element-timing/invisible-images.html new file mode 100644 index 0000000..50aa6d13 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/element-timing/invisible-images.html
@@ -0,0 +1,53 @@ +<!DOCTYPE HTML> +<meta charset=utf-8> +<title>Element Timing: invisible images are not observable</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<style> + #opacity0 { + opacity: 0; + } + #visibilityHidden { + visibility: hidden; + } + #displayNone { + display: none; + } +</style> +<script> + async_test(function (t) { + const observer = new PerformanceObserver( + t.step_func_done(() => { + // The image should not have caused an entry, so fail test. + assert_unreached('Should not have received an entry!'); + }) + ); + observer.observe({entryTypes: ['element']}); + // We add the images during onload to be sure that the observer is registered + // in time for it to observe the element timing. + window.onload = () => { + const img = document.createElement('img'); + img.src = 'resources/square100.png'; + img.setAttribute('elementtiming', 'my_image'); + img.setAttribute('id', 'opacity0'); + document.body.appendChild(img); + + const img2 = document.createElement('img'); + img2.src = 'resources/square20.png'; + img2.setAttribute('elementtiming', 'my_image2'); + img2.setAttribute('id', 'visibilityHidden'); + document.body.appendChild(img2); + + const img3 = document.createElement('img'); + img3.src = 'resources/circle.svg'; + img3.setAttribute('elementtiming', 'my_image3'); + img3.setAttribute('id', 'displayNone'); + document.body.appendChild(img3); + // Images have been added but should not cause entries to be dispatched. + // Wait for 500ms and end test, ensuring no entry was created. + t.step_timeout(() => { + t.done(); + }, 500); + }; + }, 'Images with opacity: 0, visibility: hidden, or display: none are not observable.'); +</script>
diff --git a/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/fetch-sw.https.tentative.html b/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/fetch-sw.https.html similarity index 86% rename from third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/fetch-sw.https.tentative.html rename to third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/fetch-sw.https.html index f6ece2c..efcebc2 100644 --- a/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/fetch-sw.https.tentative.html +++ b/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/fetch-sw.https.html
@@ -1,8 +1,4 @@ <!DOCTYPE html> -<!--- -Tentative test against: -https://github.com/whatwg/fetch/pull/853 ---> <html> <head> <meta charset="utf-8"> @@ -39,9 +35,9 @@ promise_test(async (test) => { var request_token = token(); - const uri = 'stale-script.py?token=' + request_token; + const uri = 'resources/stale-script.py?token=' + request_token; - await setupRegistrationAndWaitToBeControlled(test, 'stale-script.py'); + await setupRegistrationAndWaitToBeControlled(test, 'resources/stale-script.py'); var service_worker_count = 0; navigator.serviceWorker.addEventListener('message', function once(event) { @@ -54,7 +50,7 @@ const response2 = await fetch(uri); assert_equals(response.headers.get('Unique-Id'), response2.headers.get('Unique-Id')); while(true) { - const revalidation_check = await fetch(`stale-script.py?query&token=` + request_token); + const revalidation_check = await fetch(`resources/stale-script.py?query&token=` + request_token); if (revalidation_check.headers.get('Count') == '2') { // The service worker should not see the revalidation request. assert_equals(service_worker_count, 2);
diff --git a/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/fetch.tentative.html b/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/fetch.html similarity index 69% rename from third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/fetch.tentative.html rename to third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/fetch.html index 33d844fd..73390c7 100644 --- a/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/fetch.tentative.html +++ b/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/fetch.html
@@ -1,8 +1,4 @@ <!DOCTYPE html> -<!--- -Tentative test against: -https://github.com/whatwg/fetch/pull/853 ---> <meta charset="utf-8"> <title>Tests Stale While Revalidate is not executed for fetch API</title> <script src="/resources/testharness.js"></script> @@ -20,13 +16,13 @@ promise_test(async (test) => { var request_token = token(); - const response = await fetch(`stale-script.py?token=` + request_token); - const response2 = await fetch(`stale-script.py?token=` + request_token); + const response = await fetch(`resources/stale-script.py?token=` + request_token); + const response2 = await fetch(`resources/stale-script.py?token=` + request_token); assert_equals(response.headers.get('Unique-Id'), response2.headers.get('Unique-Id')); while(true) { - const revalidation_check = await fetch(`stale-script.py?query&token=` + request_token); + const revalidation_check = await fetch(`resources/stale-script.py?query&token=` + request_token); if (revalidation_check.headers.get('Count') == '2') { break; }
diff --git a/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-css.py b/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/resources/stale-css.py similarity index 100% rename from third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-css.py rename to third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/resources/stale-css.py
diff --git a/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-image.py b/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/resources/stale-image.py similarity index 92% rename from third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-image.py rename to third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/resources/stale-image.py index 4b671841..839eb84 100644 --- a/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-image.py +++ b/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/resources/stale-image.py
@@ -25,7 +25,7 @@ if count > 1: filename = "green-256x256.png" - path = os.path.join(os.path.dirname(__file__), "../../images", filename) + path = os.path.join(os.path.dirname(__file__), "../../../images", filename) body = open(path, "rb").read() response.add_required_headers = False
diff --git a/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-script.py b/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/resources/stale-script.py similarity index 100% rename from third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-script.py rename to third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/resources/stale-script.py
diff --git a/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-css.tentative.html b/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-css.html similarity index 82% rename from third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-css.tentative.html rename to third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-css.html index 3459493f..f56260fd 100644 --- a/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-css.tentative.html +++ b/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-css.html
@@ -1,8 +1,4 @@ <!DOCTYPE html> -<!--- -Tentative test against: -https://github.com/whatwg/fetch/pull/853 ---> <meta charset="utf-8"> <title>Tests Stale While Revalidate works for css</title> <script src="/resources/testharness.js"></script> @@ -21,7 +17,7 @@ assert_equals(window.getComputedStyle(document.body).getPropertyValue('background-color'), "rgb(0, 128, 0)"); var checkResult = () => { // We poll because we don't know when the revalidation will occur. - fetch("stale-css.py?query&token=" + request_token).then(t.step_func((response) => { + fetch("resources/stale-css.py?query&token=" + request_token).then(t.step_func((response) => { var count = response.headers.get("Count"); if (count == '2') { t.done(); @@ -34,7 +30,7 @@ }); link2.rel = "stylesheet"; link2.type = "text/css"; - link2.href = "stale-css.py?token=" + request_token; + link2.href = "resources/stale-css.py?token=" + request_token; document.body.appendChild(link2); }, 0); }); @@ -43,7 +39,7 @@ var link = document.createElement("link"); link.rel = "stylesheet"; link.type = "text/css"; -link.href = "stale-css.py?token=" + request_token; +link.href = "resources/stale-css.py?token=" + request_token; document.body.appendChild(link); </script> </body>
diff --git a/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-image.tentative.html b/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-image.html similarity index 82% rename from third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-image.tentative.html rename to third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-image.html index 8b6a896e..0a08f81 100644 --- a/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-image.tentative.html +++ b/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-image.html
@@ -1,8 +1,4 @@ <!DOCTYPE html> -<!--- -Tentative test against: -https://github.com/whatwg/fetch/pull/853 ---> <meta charset="utf-8"> <title>Tests Stale While Revalidate works for images</title> <script src="/resources/testharness.js"></script> @@ -28,10 +24,10 @@ assert_equals(img2.width, 16, "image dimension"); var checkResult = () => { // We poll because we don't know when the revalidation will occur. - fetch("stale-image.py?query&token=" + request_token).then(t.step_func((response) => { + fetch("resources/stale-image.py?query&token=" + request_token).then(t.step_func((response) => { var count = response.headers.get("Count"); if (count == '2') { - t.done(); + t.done(); } else { t.step_timeout(checkResult, 25); } @@ -39,14 +35,14 @@ }; t.step_timeout(checkResult, 25); }); - img2.src = "stale-image.py?token=" + request_token; + img2.src = "resources/stale-image.py?token=" + request_token; childDocument.body.appendChild(img2); }, 0); }); }, 'Cache returns stale resource'); var img = document.createElement("img"); -img.src = "stale-image.py?token=" + request_token; +img.src = "resources/stale-image.py?token=" + request_token; img.id = "firstimage"; document.body.appendChild(img); </script>
diff --git a/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-script.tentative.html b/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-script.html similarity index 83% rename from third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-script.tentative.html rename to third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-script.html index 8cbb54b..68793e50 100644 --- a/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-script.tentative.html +++ b/third_party/blink/web_tests/external/wpt/fetch/stale-while-revalidate/stale-script.html
@@ -1,8 +1,4 @@ <!DOCTYPE html> -<!--- -Tentative test against: -https://github.com/whatwg/fetch/pull/853 ---> <meta charset="utf-8"> <title>Tests Stale While Revalidate works for scripts</title> <script src="/resources/testharness.js"></script> @@ -29,13 +25,13 @@ window.onload = t.step_func(() => { step_timeout(() => { var script = document.createElement("script"); - script.src = "stale-script.py?token=" + request_token; + script.src = "resources/stale-script.py?token=" + request_token; document.body.appendChild(script); script.onload = t.step_func(() => { assert_equals(last_modified_count, 2, "last modified"); var checkResult = () => { // We poll because we don't know when the revalidation will occur. - fetch("stale-script.py?query&token=" + request_token).then(t.step_func((response) => { + fetch("resources/stale-script.py?query&token=" + request_token).then(t.step_func((response) => { var count = response.headers.get("Count"); if (count == '2') { t.done(); @@ -51,7 +47,7 @@ }, 'Cache returns stale resource'); var script = document.createElement("script"); -script.src = "stale-script.py?token=" + request_token; +script.src = "resources/stale-script.py?token=" + request_token; document.body.appendChild(script); </script> </body>
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/mediasession.idl b/third_party/blink/web_tests/external/wpt/interfaces/mediasession.idl index 6fd5725..95210c0 100644 --- a/third_party/blink/web_tests/external/wpt/interfaces/mediasession.idl +++ b/third_party/blink/web_tests/external/wpt/interfaces/mediasession.idl
@@ -34,6 +34,8 @@ attribute MediaSessionPlaybackState playbackState; void setActionHandler(MediaSessionAction action, MediaSessionActionHandler? handler); + + void setPositionState(MediaPositionState? state); }; [Constructor(optional MediaMetadataInit init), Exposed=Window] @@ -56,3 +58,9 @@ DOMString sizes = ""; DOMString type = ""; }; + +dictionary MediaPositionState { + required double duration; + double playbackRate = 1.0; + double position = 0.0; +};
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/payment-request.idl b/third_party/blink/web_tests/external/wpt/interfaces/payment-request.idl index 53922d31..f8bd336 100644 --- a/third_party/blink/web_tests/external/wpt/interfaces/payment-request.idl +++ b/third_party/blink/web_tests/external/wpt/interfaces/payment-request.idl
@@ -12,6 +12,8 @@ Promise<void> abort(); [NewObject] Promise<boolean> canMakePayment(); + [NewObject] + Promise<boolean> hasEnrolledInstrument(); readonly attribute DOMString id; readonly attribute PaymentAddress? shippingAddress;
diff --git a/third_party/blink/web_tests/external/wpt/mediasession/idlharness.window-expected.txt b/third_party/blink/web_tests/external/wpt/mediasession/idlharness.window-expected.txt new file mode 100644 index 0000000..90ec4e0 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/mediasession/idlharness.window-expected.txt
@@ -0,0 +1,42 @@ +This is a testharness.js-based test. +PASS idl_test setup +PASS Partial interface Navigator: original interface defined +PASS Partial interface Navigator: valid exposure set +PASS MediaSession interface: existence and properties of interface object +PASS MediaSession interface object length +PASS MediaSession interface object name +PASS MediaSession interface: existence and properties of interface prototype object +PASS MediaSession interface: existence and properties of interface prototype object's "constructor" property +PASS MediaSession interface: existence and properties of interface prototype object's @@unscopables property +PASS MediaSession interface: attribute metadata +PASS MediaSession interface: attribute playbackState +PASS MediaSession interface: operation setActionHandler(MediaSessionAction, MediaSessionActionHandler) +FAIL MediaSession interface: operation setPositionState(MediaPositionState) assert_own_property: interface prototype object missing non-static operation expected property "setPositionState" missing +PASS MediaSession must be primary interface of navigator.mediaSession +PASS Stringification of navigator.mediaSession +PASS MediaSession interface: navigator.mediaSession must inherit property "metadata" with the proper type +PASS MediaSession interface: navigator.mediaSession must inherit property "playbackState" with the proper type +PASS MediaSession interface: navigator.mediaSession must inherit property "setActionHandler(MediaSessionAction, MediaSessionActionHandler)" with the proper type +PASS MediaSession interface: calling setActionHandler(MediaSessionAction, MediaSessionActionHandler) on navigator.mediaSession with too few arguments must throw TypeError +FAIL MediaSession interface: navigator.mediaSession must inherit property "setPositionState(MediaPositionState)" with the proper type assert_inherits: property "setPositionState" not found in prototype chain +FAIL MediaSession interface: calling setPositionState(MediaPositionState) on navigator.mediaSession with too few arguments must throw TypeError assert_inherits: property "setPositionState" not found in prototype chain +PASS MediaMetadata interface: existence and properties of interface object +PASS MediaMetadata interface object length +PASS MediaMetadata interface object name +PASS MediaMetadata interface: existence and properties of interface prototype object +PASS MediaMetadata interface: existence and properties of interface prototype object's "constructor" property +PASS MediaMetadata interface: existence and properties of interface prototype object's @@unscopables property +PASS MediaMetadata interface: attribute title +PASS MediaMetadata interface: attribute artist +PASS MediaMetadata interface: attribute album +PASS MediaMetadata interface: attribute artwork +PASS MediaMetadata must be primary interface of new MediaMetadata() +PASS Stringification of new MediaMetadata() +PASS MediaMetadata interface: new MediaMetadata() must inherit property "title" with the proper type +PASS MediaMetadata interface: new MediaMetadata() must inherit property "artist" with the proper type +PASS MediaMetadata interface: new MediaMetadata() must inherit property "album" with the proper type +PASS MediaMetadata interface: new MediaMetadata() must inherit property "artwork" with the proper type +PASS Navigator interface: attribute mediaSession +PASS Navigator interface: navigator must inherit property "mediaSession" with the proper type +Harness: the test ran to completion. +
diff --git a/third_party/blink/web_tests/external/wpt/payment-request/idlharness.https.window-expected.txt b/third_party/blink/web_tests/external/wpt/payment-request/idlharness.https.window-expected.txt index 705e093..62e7edf 100644 --- a/third_party/blink/web_tests/external/wpt/payment-request/idlharness.https.window-expected.txt +++ b/third_party/blink/web_tests/external/wpt/payment-request/idlharness.https.window-expected.txt
@@ -1,5 +1,5 @@ This is a testharness.js-based test. -Found 107 tests; 105 PASS, 2 FAIL, 0 TIMEOUT, 0 NOTRUN. +Found 109 tests; 107 PASS, 2 FAIL, 0 TIMEOUT, 0 NOTRUN. PASS idl_test setup PASS PaymentRequest interface: existence and properties of interface object PASS PaymentRequest interface object length @@ -10,6 +10,7 @@ PASS PaymentRequest interface: operation show([object Object]) PASS PaymentRequest interface: operation abort() PASS PaymentRequest interface: operation canMakePayment() +PASS PaymentRequest interface: operation hasEnrolledInstrument() PASS PaymentRequest interface: attribute id PASS PaymentRequest interface: attribute shippingAddress PASS PaymentRequest interface: attribute shippingOption @@ -24,6 +25,7 @@ PASS PaymentRequest interface: calling show([object Object]) on paymentRequest with too few arguments must throw TypeError PASS PaymentRequest interface: paymentRequest must inherit property "abort()" with the proper type PASS PaymentRequest interface: paymentRequest must inherit property "canMakePayment()" with the proper type +PASS PaymentRequest interface: paymentRequest must inherit property "hasEnrolledInstrument()" with the proper type PASS PaymentRequest interface: paymentRequest must inherit property "id" with the proper type PASS PaymentRequest interface: paymentRequest must inherit property "shippingAddress" with the proper type PASS PaymentRequest interface: paymentRequest must inherit property "shippingOption" with the proper type
diff --git a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_support.js b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_support.js index dc35ea2..44dba45 100644 --- a/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_support.js +++ b/third_party/blink/web_tests/external/wpt/pointerevents/pointerevent_support.js
@@ -294,3 +294,33 @@ .pointerUp() .send(); } + +function pointerDragInTarget(pointerType, target, direction) { + var x_delta = 0; + var y_delta = 0; + if (direction == "down") { + x_delta = 0; + y_delta = 10; + } else if (direction == "up") { + x_delta = 0; + y_delta = -10; + } else if (direction == "right") { + x_delta = 10; + y_delta = 0; + } else if (direction == "left") { + x_delta = -10; + y_delta = 0; + } else { + throw("drag direction '" + direction + "' is not expected, direction should be 'down', 'up', 'left' or 'right'"); + } + var pointerId = pointerType + "Pointer1"; + return new test_driver.Actions() + .addPointer(pointerId, pointerType) + .pointerMove(0, 0, {origin: target}) + .pointerDown() + .pointerMove(x_delta, y_delta, {origin: target}) + .pointerMove(2 * x_delta, 2 * y_delta, {origin: target}) + .pointerMove(3 * x_delta, 3 * y_delta, {origin: target}) + .pointerUp() + .send(); +}
diff --git a/third_party/blink/web_tests/external/wpt/portals/portals-activate-resolution.html b/third_party/blink/web_tests/external/wpt/portals/portals-activate-resolution.html index 9fb99e42..03dc09f 100644 --- a/third_party/blink/web_tests/external/wpt/portals/portals-activate-resolution.html +++ b/third_party/blink/web_tests/external/wpt/portals/portals-activate-resolution.html
@@ -8,11 +8,7 @@ portal.src = new URL("resources/simple-portal.html", location.href) await new Promise((resolve, reject) => { - var bc = new BroadcastChannel("simple-portal"); - bc.onmessage = () => { - bc.close(); - resolve(); - } + portal.onmessage = resolve; win.document.body.appendChild(portal); });
diff --git a/third_party/blink/web_tests/external/wpt/portals/portals-activate-twice.html b/third_party/blink/web_tests/external/wpt/portals/portals-activate-twice.html index 074d3f4..731a342 100644 --- a/third_party/blink/web_tests/external/wpt/portals/portals-activate-twice.html +++ b/third_party/blink/web_tests/external/wpt/portals/portals-activate-twice.html
@@ -4,10 +4,7 @@ <script> promise_test(async t => { let waitForMessage = new Promise((resolve, reject) => { - var bc = new BroadcastChannel("portals-activate-twice"); - bc.onmessage = e => { - resolve(e.data); - } + window.onmessage = e => resolve(e.data); }); window.open("resources/portal-activate-twice-window-1.html"); let error = await waitForMessage; @@ -16,10 +13,7 @@ promise_test(async t => { let waitForMessage = new Promise((resolve, reject) => { - var bc = new BroadcastChannel("portals-activate-twice"); - bc.onmessage = e => { - resolve(e.data); - } + window.onmessage = e => resolve(e.data); }); window.open("resources/portal-activate-twice-window-2.html"); let error = await waitForMessage;
diff --git a/third_party/blink/web_tests/external/wpt/portals/portals-cross-origin-load.sub.html b/third_party/blink/web_tests/external/wpt/portals/portals-cross-origin-load.sub.html index 0d837b99..e94c45b 100644 --- a/third_party/blink/web_tests/external/wpt/portals/portals-cross-origin-load.sub.html +++ b/third_party/blink/web_tests/external/wpt/portals/portals-cross-origin-load.sub.html
@@ -5,7 +5,7 @@ <script> promise_test(async () => { var portal = document.createElement("portal"); - portal.src = "http://{{hosts[alt][www]}}:{{ports[http][0]}}/portals/resources/portal-cross-origin.sub.html"; + portal.src = "http://{{hosts[alt][www]}}:{{ports[http][0]}}/portals/resources/simple-portal.html"; var receiveMessage = new Promise((resolve, reject) => { portal.onmessage = e => { resolve(e.data) }; });
diff --git a/third_party/blink/web_tests/external/wpt/portals/resources/portal-activate-inside-portal.html b/third_party/blink/web_tests/external/wpt/portals/resources/portal-activate-inside-portal.html index 241a75e..9c9c4df6 100644 --- a/third_party/blink/web_tests/external/wpt/portals/resources/portal-activate-inside-portal.html +++ b/third_party/blink/web_tests/external/wpt/portals/resources/portal-activate-inside-portal.html
@@ -3,9 +3,7 @@ <script> var portal = document.createElement("portal"); portal.src = "simple-portal.html"; - var bc = new BroadcastChannel("simple-portal"); - bc.onmessage = () => { - bc.close(); + portal.onmessage = () => { portal.activate().catch(e => { var bc2 = new BroadcastChannel("portals-activate-inside-portal"); bc2.postMessage(e.name);
diff --git a/third_party/blink/web_tests/external/wpt/portals/resources/portal-activate-twice-window-1.html b/third_party/blink/web_tests/external/wpt/portals/resources/portal-activate-twice-window-1.html index 197153f..2fe6755 100644 --- a/third_party/blink/web_tests/external/wpt/portals/resources/portal-activate-twice-window-1.html +++ b/third_party/blink/web_tests/external/wpt/portals/resources/portal-activate-twice-window-1.html
@@ -3,18 +3,10 @@ <script> var portal = document.createElement("portal"); portal.src = "simple-portal.html" - - var bc = new BroadcastChannel("simple-portal"); - bc.onmessage = () => { - bc.close(); + portal.onmessage = () => { portal.activate(); - portal.activate().catch(e => { - var bc2 = new BroadcastChannel("portals-activate-twice"); - bc2.postMessage(e.name, "*"); - bc2.close(); - }); + portal.activate().catch(e => window.opener.postMessage(e.name, "*")); } - document.body.append(portal); </script> </body>
diff --git a/third_party/blink/web_tests/external/wpt/portals/resources/portal-activate-twice-window-2.html b/third_party/blink/web_tests/external/wpt/portals/resources/portal-activate-twice-window-2.html index dc161c0..89fdf15d7 100644 --- a/third_party/blink/web_tests/external/wpt/portals/resources/portal-activate-twice-window-2.html +++ b/third_party/blink/web_tests/external/wpt/portals/resources/portal-activate-twice-window-2.html
@@ -6,25 +6,14 @@ var portal2 = document.createElement("portal"); portal2.src = "simple-portal.html" - var bc = new BroadcastChannel("simple-portal"); - var waitForPortalsToLoad = new Promise((resolve, reject) => { - var count = 0; - bc.onmessage = () => { - count++; - if (count == 2) { - bc.close(); - resolve(); - } - } + var waitForPortalToLoad = portal => new Promise((resolve, reject) => { + portal.onmessage = resolve; }); - waitForPortalsToLoad.then(() => { + Promise.all([waitForPortalToLoad(portal1), + waitForPortalToLoad(portal2)]).then(() => { portal1.activate(); - portal2.activate().catch(e => { - var bc2 = new BroadcastChannel("portals-activate-twice"); - bc2.postMessage(e.name, "*"); - bc2.close(); - }); + portal2.activate().catch(e => window.opener.postMessage(e.name, "*")); }); document.body.append(portal1);
diff --git a/third_party/blink/web_tests/external/wpt/portals/resources/portal-cross-origin.sub.html b/third_party/blink/web_tests/external/wpt/portals/resources/portal-cross-origin.sub.html deleted file mode 100644 index d4710c0..0000000 --- a/third_party/blink/web_tests/external/wpt/portals/resources/portal-cross-origin.sub.html +++ /dev/null
@@ -1,6 +0,0 @@ -<!DOCTYPE html> -<body> - <script> - window.portalHost.postMessage("loaded", "*"); - </script> -</body>
diff --git a/third_party/blink/web_tests/external/wpt/portals/resources/simple-portal.html b/third_party/blink/web_tests/external/wpt/portals/resources/simple-portal.html index fe7f653..d4710c0 100644 --- a/third_party/blink/web_tests/external/wpt/portals/resources/simple-portal.html +++ b/third_party/blink/web_tests/external/wpt/portals/resources/simple-portal.html
@@ -1,8 +1,6 @@ <!DOCTYPE html> <body> <script> - var bc = new BroadcastChannel("simple-portal"); - bc.postMessage("loaded"); - bc.close(); + window.portalHost.postMessage("loaded", "*"); </script> </body>
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/chromium.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/chromium.py index 071c2f3..15e8e0b0 100644 --- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/chromium.py +++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/chromium.py
@@ -24,12 +24,32 @@ # the trie and the leaf contains the dict of per-test data. self.tests = {} - def _store_test_result(self, name, actual, expected): + # Message dictionary, keyed by test name. Value is a concatenation of + # the subtest messages for this test. + self.messages = defaultdict(str) + + def _append_test_message(self, test, subtest, status, message): + """ + Appends the message data for a test. + :param str test: the name of the test + :param str subtest: the name of the subtest with the message + :param str message: the string to append to the message for this test + """ + if not message: + return + # Add the prefix, with the test status and subtest name (if available) + prefix = "[%s] " % status + if subtest: + prefix += "%s: " % subtest + self.messages[test] += prefix + message + "\n" + + def _store_test_result(self, name, actual, expected, message): """ Stores the result of a single test in |self.tests| :param str name: name of the test. :param str actual: actual status of the test. :param str expected: expected status of the test. + :param str message: test output, such as status, subtest, errors etc. """ # The test name can contain a leading / which will produce an empty # string in the first position of the list returned by split. We use @@ -40,6 +60,16 @@ cur_dict = cur_dict.setdefault(name_part, {}) cur_dict["actual"] = actual cur_dict["expected"] = expected + if message != "": + cur_dict["artifacts"] = {"log": message} + + # Figure out if there was a regression or unexpected status. This only + # happens for tests that were run + if actual != "SKIP": + if actual != expected: + cur_dict["is_unexpected"] = True + if actual != "PASS": + cur_dict["is_regression"] = True def _map_status_name(self, status): """ @@ -73,12 +103,27 @@ return status def suite_start(self, data): - self.start_timestamp_seconds = data["time"] if "time" in data else time.time() + self.start_timestamp_seconds = (data["time"] if "time" in data + else time.time()) + + def test_status(self, data): + if "message" in data: + self._append_test_message(data["test"], data["subtest"], + data["status"], data["message"]) def test_end(self, data): actual_status = self._map_status_name(data["status"]) - expected_status = self._map_status_name(data["expected"]) if "expected" in data else "PASS" - self._store_test_result(data["test"], actual_status, expected_status) + expected_status = (self._map_status_name(data["expected"]) + if "expected" in data else "PASS") + test_name = data["test"] + if "message" in data: + self._append_test_message(test_name, None, actual_status, + data["message"]) + self._store_test_result(test_name, actual_status, expected_status, + self.messages[test_name]) + + # Remove the test from messages dict to avoid accumulating too many. + self.messages.pop(test_name) # Update the count of how many tests ran with each status. self.num_failures_by_status[actual_status] += 1
diff --git a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/tests/test_chromium.py b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/tests/test_chromium.py index 12962f4..b2c261f 100644 --- a/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/tests/test_chromium.py +++ b/third_party/blink/web_tests/external/wpt/tools/wptrunner/wptrunner/formatters/tests/test_chromium.py
@@ -6,7 +6,7 @@ from mozlog import handlers, structuredlog sys.path.insert(0, join(dirname(__file__), "..", "..")) -from formatters import chromium +from formatters.chromium import ChromiumFormatter def test_chromium_required_fields(capfd): @@ -15,7 +15,7 @@ # Set up the handler. output = StringIO() logger = structuredlog.StructuredLogger("test_a") - logger.add_handler(handlers.StreamHandler(output, chromium.ChromiumFormatter())) + logger.add_handler(handlers.StreamHandler(output, ChromiumFormatter())) # output a bunch of stuff logger.suite_start(["test-id-1"], run_info={}, time=123) @@ -44,6 +44,7 @@ assert "actual" in test_obj assert "expected" in test_obj + def test_chromium_test_name_trie(capfd): # Ensure test names are broken into directories and stored in a trie with # test results at the leaves. @@ -51,10 +52,11 @@ # Set up the handler. output = StringIO() logger = structuredlog.StructuredLogger("test_a") - logger.add_handler(handlers.StreamHandler(output, chromium.ChromiumFormatter())) + logger.add_handler(handlers.StreamHandler(output, ChromiumFormatter())) # output a bunch of stuff - logger.suite_start(["/foo/bar/test-id-1", "/foo/test-id-2"], run_info={}, time=123) + logger.suite_start(["/foo/bar/test-id-1", "/foo/test-id-2"], run_info={}, + time=123) logger.test_start("/foo/bar/test-id-1") logger.test_end("/foo/bar/test-id-1", status="TIMEOUT", expected="FAIL") logger.test_start("/foo/test-id-2") @@ -82,13 +84,14 @@ assert test_obj["actual"] == "FAIL" assert test_obj["expected"] == "TIMEOUT" + def test_num_failures_by_type(capfd): # Test that the number of failures by status type is correctly calculated. # Set up the handler. output = StringIO() logger = structuredlog.StructuredLogger("test_a") - logger.add_handler(handlers.StreamHandler(output, chromium.ChromiumFormatter())) + logger.add_handler(handlers.StreamHandler(output, ChromiumFormatter())) # Run some tests with different statuses: 3 passes, 1 timeout logger.suite_start(["t1", "t2", "t3", "t4"], run_info={}, time=123) @@ -116,3 +119,46 @@ assert sorted(num_failures_by_type.keys()) == ["PASS", "TIMEOUT"] assert num_failures_by_type["PASS"] == 3 assert num_failures_by_type["TIMEOUT"] == 1 + + +def test_subtest_messages(capfd): + # Tests accumulation of test output + + # Set up the handler. + output = StringIO() + logger = structuredlog.StructuredLogger("test_a") + logger.add_handler(handlers.StreamHandler(output, ChromiumFormatter())) + + # Run two tests with subtest messages. The subtest name should be included + # in the output. We should also tolerate missing messages. + logger.suite_start(["t1", "t2"], run_info={}, time=123) + logger.test_start("t1") + logger.test_status("t1", status="FAIL", subtest="t1_a", + message="t1_a_message") + logger.test_status("t1", status="PASS", subtest="t1_b", + message="t1_b_message") + logger.test_end("t1", status="PASS", expected="PASS") + logger.test_start("t2") + # Currently, subtests with empty messages will be ignored + logger.test_status("t2", status="PASS", subtest="t2_a") + # A test-level message will also be appended + logger.test_end("t2", status="TIMEOUT", expected="PASS", + message="t2_message") + logger.suite_end() + + # check nothing got output to stdout/stderr + # (note that mozlog outputs exceptions during handling to stderr!) + captured = capfd.readouterr() + assert captured.out == "" + assert captured.err == "" + + # check the actual output of the formatter + output.seek(0) + output_json = json.load(output) + + t1_log = output_json["tests"]["t1"]["artifacts"]["log"] + assert t1_log == "[FAIL] t1_a: t1_a_message\n" \ + "[PASS] t1_b: t1_b_message\n" + + t2_log = output_json["tests"]["t2"]["artifacts"]["log"] + assert t2_log == "[TIMEOUT] t2_message\n"
diff --git a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-biquadfilternode-interface/no-dezippering.html b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-biquadfilternode-interface/no-dezippering.html index d54bc0bd..63d4be3 100644 --- a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-biquadfilternode-interface/no-dezippering.html +++ b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-biquadfilternode-interface/no-dezippering.html
@@ -155,7 +155,7 @@ let match = should(actual, 'Output from ' + f.type + ' filter') .beCloseToArray( - expected, {absoluteThreshold: 4.7684e-7}); + expected, {absoluteThreshold: 5.9607e-7}); should(match, 'Output matches JS filter results').beTrue(); }) .then(() => task.done());
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpTransceiver.https-expected.txt b/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpTransceiver.https-expected.txt new file mode 100644 index 0000000..dedfbd1 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpTransceiver.https-expected.txt
@@ -0,0 +1,4 @@ +This is a testharness.js-based test. +FAIL RTCRtpTransceiver Uncaught ReferenceError: checkAddIceCandidateToStoppedTransceiver is not defined +Harness: the test ran to completion. +
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpTransceiver.https.html b/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpTransceiver.https.html index 0b560d2..eaab18d 100644 --- a/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpTransceiver.https.html +++ b/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpTransceiver.https.html
@@ -1972,6 +1972,30 @@ ]); }; + const checkBundleTagRejected = async t => { + const pc1 = new RTCPeerConnection(); + t.add_cleanup(() => pc1.close()); + const pc2 = new RTCPeerConnection(); + t.add_cleanup(() => pc2.close()); + + const stream1 = await getNoiseStream({audio: true}); + t.add_cleanup(() => stopTracks(stream1)); + const track1 = stream1.getAudioTracks()[0]; + const stream2 = await getNoiseStream({audio: true}); + t.add_cleanup(() => stopTracks(stream2)); + const track2 = stream2.getAudioTracks()[0]; + + pc1.addTrack(track1, stream1); + pc1.addTrack(track2, stream2); + + await offerAnswer(pc1, pc2); + + pc2.getTransceivers()[0].stop(); + + await offerAnswer(pc1, pc2); + await offerAnswer(pc2, pc1); + }; + const checkMsectionReuse = async t => { // Use max-compat to make it easier to check for disabled m-sections const pc1 = new RTCPeerConnection({ bundlePolicy: "max-compat" }); @@ -2254,7 +2278,9 @@ checkRollbackAndSetRemoteOfferWithDifferentType, checkRemoteRollback, checkMsectionReuse, - checkStopAfterCreateOfferWithReusedMsection + checkStopAfterCreateOfferWithReusedMsection, + checkAddIceCandidateToStoppedTransceiver, + checkBundleTagRejected ].forEach(test => promise_test(test, test.name)); </script>
diff --git a/third_party/blink/web_tests/fast/spatial-navigation/snav-scrolls-visual-viewport.html b/third_party/blink/web_tests/fast/spatial-navigation/snav-scrolls-visual-viewport.html new file mode 100644 index 0000000..487d2ee --- /dev/null +++ b/third_party/blink/web_tests/fast/spatial-navigation/snav-scrolls-visual-viewport.html
@@ -0,0 +1,53 @@ +<!DOCTYPE html> +<script src="../../resources/testharness.js"></script> +<script src="../../resources/testharnessreport.js"></script> +<script src="resources/snav-testharness.js"></script> + +<style> + body,html { + width: 100%; + height: 100%; + margin: 0; + } + + #spacer { + height: 1000px; + } +</style> + +<div id="spacer"></div> + +<script> + // This test ensures that, when zoomed in, spatial navigation scrolling will + // also cause the visual viewport to be scrolled. + snav.assertSnavEnabledAndTestable(); + t = async_test("Spatial Navigation scrolls visual viewport."); + + addEventListener('load', () => { + t.step(() => { + const scale = 2; + const spacer = document.getElementById('spacer'); + + window.internals.setPageScaleFactor(scale); + + assert_equals(window.visualViewport.offsetTop, 0, + "Visual Viewport should start unscrolled."); + + snav.triggerMove('Down'); + + assert_greater_than(window.visualViewport.offsetTop, 0, + "Visual Viewport should have been scrolled."); + + // Scroll a bunch and check if we reached the bottom. + for(let i = 0; i < 20; ++i) + snav.triggerMove('Down'); + + assert_equals(window.visualViewport.offsetTop, window.innerHeight / scale, + "Visual viewport should have been fully scrolled."); + assert_equals(window.scrollY, spacer.clientHeight - window.innerHeight, + "Layout viewport should have been fully scrolled."); + + t.done(); + }); + }); +</script>
diff --git a/third_party/blink/web_tests/http/tests/devtools/background-services/background-service-grid-expected.txt b/third_party/blink/web_tests/http/tests/devtools/background-services/background-service-grid-expected.txt new file mode 100644 index 0000000..49d10a6 --- /dev/null +++ b/third_party/blink/web_tests/http/tests/devtools/background-services/background-service-grid-expected.txt
@@ -0,0 +1,18 @@ +Tests that the grid shows information as expected. + +Grid Entries: + [empty] +Grid Entries: + 0, 1556889085000, Event1, http://127.0.0.1:8000/, [blank], Instance1, [blank] +Grid Entries: + 0, 1556889085000, Event1, http://127.0.0.1:8000/, [blank], Instance1, [blank] +Grid Entries: + 0, 1556889085000, Event1, http://127.0.0.1:8000/, [blank], Instance1, [blank] +Grid Entries: + 0, 1556889085000, Event1, http://127.0.0.1:8000/, [blank], Instance1, [blank] + 1, 1556889085000, Event2, http://127.0.0.1:8080/, [blank], Instance1, [blank] +Grid Entries: + 0, 1556889085000, Event1, http://127.0.0.1:8000/, [blank], Instance1, [blank] +Grid Entries: + [empty] +
diff --git a/third_party/blink/web_tests/http/tests/devtools/background-services/background-service-grid.js b/third_party/blink/web_tests/http/tests/devtools/background-services/background-service-grid.js new file mode 100644 index 0000000..bd99c4f --- /dev/null +++ b/third_party/blink/web_tests/http/tests/devtools/background-services/background-service-grid.js
@@ -0,0 +1,101 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +function dumpBackgroundServiceGrid() { + TestRunner.addResult('Grid Entries:'); + + const treeElement = UI.panels.resources._sidebar.backgroundFetchTreeElement; + treeElement.onselect(false); + + const dataGrid = treeElement._view._dataGrid; + if (!dataGrid.rootNode().children.length) { + TestRunner.addResult(' [empty]'); + return; + } + + for (const node of dataGrid.rootNode().children) { + const children = Array.from(node.element().children).map(element => { + if (!element.classList.contains('timestamp-column')) + return element; + // Format the timestamp column for consistent behavior. + return {textContent: new Date(element.textContent).getTime()}; + }); + + // Extract the text from the columns. + const entries = Array.from(children, td => td.textContent).map(content => content ? content : '[blank]'); + TestRunner.addResult(' '.repeat(4) + entries.join(', ')); + } +}; + +function setOriginCheckbox(value) { + const treeElement = UI.panels.resources._sidebar.backgroundFetchTreeElement; + treeElement.onselect(false); + treeElement._view._originCheckbox.setChecked(value); + // Simulate click. + treeElement._view._refreshView(); +} + +(async function() { + Runtime.experiments.setEnabled('backgroundServices', true); + + TestRunner.addResult(`Tests that the grid shows information as expected.\n`); + await TestRunner.showPanel('resources'); + + const backgroundServiceModel = TestRunner.mainTarget.model(Resources.BackgroundServiceModel); + backgroundServiceModel.enable(Protocol.BackgroundService.ServiceName.BackgroundFetch); + backgroundServiceModel.enable(Protocol.BackgroundService.ServiceName.BackgroundSync); + + // Grid is initially empty. + dumpBackgroundServiceGrid(); + + // Grid should have an entry now. + backgroundServiceModel.backgroundServiceEventReceived({ + timestamp: 1556889085, // 2019-05-03 14:11:25.000. + origin: 'http://127.0.0.1:8000/', + serviceWorkerRegistrationId: 42, // invalid. + service: Protocol.BackgroundService.ServiceName.BackgroundFetch, + eventName: 'Event1', + instanceId: 'Instance1', + eventMetadata: [], + }); + dumpBackgroundServiceGrid(); + + // Event from a different service is ignored. + backgroundServiceModel.backgroundServiceEventReceived({ + timestamp: 1556889085, // 2019-05-03 14:11:25.000. + origin: 'http://127.0.0.1:8000/', + serviceWorkerRegistrationId: 42, // invalid. + service: Protocol.BackgroundService.ServiceName.BackgroundSync, + eventName: 'Event1', + instanceId: 'Instance2', + eventMetadata: [], + }); + dumpBackgroundServiceGrid(); + + // Event from a different origin is ignored. + backgroundServiceModel.backgroundServiceEventReceived({ + timestamp: 1556889085, // 2019-05-03 14:11:25.000. + origin: 'http://127.0.0.1:8080/', + serviceWorkerRegistrationId: 42, // invalid. + service: Protocol.BackgroundService.ServiceName.BackgroundFetch, + eventName: 'Event2', + instanceId: 'Instance1', + eventMetadata: [], + }); + dumpBackgroundServiceGrid(); + + // The event from a different origin should show up now. + setOriginCheckbox(true); + dumpBackgroundServiceGrid(); + + // Unchecking the origin checkbox should remove it again. + setOriginCheckbox(false); + dumpBackgroundServiceGrid(); + + // Simulate clicking the clear button. + UI.panels.resources._sidebar.backgroundFetchTreeElement._view._clearEvents(); + dumpBackgroundServiceGrid(); + + TestRunner.completeTest(); +})();
diff --git a/third_party/blink/web_tests/http/tests/devtools/background-services/background-services-panel-expected.txt b/third_party/blink/web_tests/http/tests/devtools/background-services/background-services-panel-expected.txt new file mode 100644 index 0000000..7f43554b --- /dev/null +++ b/third_party/blink/web_tests/http/tests/devtools/background-services/background-services-panel-expected.txt
@@ -0,0 +1,17 @@ +Tests the bottom panel shows information as expected. + +Panel view: + Click the record button +Panel view: + Recording Background Fetch activity... +Panel view: + Click the record button +Panel view: + Select an entry to view metadata +Panel view: + No metadata for this event +Panel view: + key: value +Panel view: + Click the record button +
diff --git a/third_party/blink/web_tests/http/tests/devtools/background-services/background-services-panel.js b/third_party/blink/web_tests/http/tests/devtools/background-services/background-services-panel.js new file mode 100644 index 0000000..fec5fe1 --- /dev/null +++ b/third_party/blink/web_tests/http/tests/devtools/background-services/background-services-panel.js
@@ -0,0 +1,88 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +function dumpPreviewPanel() { + TestRunner.addResult('Panel view:'); + + const treeElement = UI.panels.resources._sidebar.backgroundFetchTreeElement; + treeElement.onselect(false); + + const preview = treeElement._view._preview; + + let text = ''; + if (preview.contentElement.getElementsByClassName('empty-view').length) + text = preview.contentElement.getElementsByClassName('empty-view')[0].textContent; + else + text = preview.element.getElementsByClassName('background-service-metadata-entry')[0].textContent; + + // There's some platform specific shortcuts + the button in the text, just keep the important bits. + if (text.startsWith('Click the record button')) + text = 'Click the record button'; + + TestRunner.addResult(' '.repeat(4) + text); +}; + +async function toggleRecord(model) { + const treeElement = UI.panels.resources._sidebar.backgroundFetchTreeElement; + treeElement.onselect(false); + + // Simulate click. + treeElement._view._toggleRecording(); + + // Wait for the view to be aware of the change. + await new Promise(r => { + model.addEventListener(Resources.BackgroundServiceModel.Events.RecordingStateChanged, r); + }); + + // Yield thread in case this listener was called before the UI's listener. + await new Promise(r => setTimeout(r, 0)); +} + +(async function() { + Runtime.experiments.setEnabled('backgroundServices', true); + + TestRunner.addResult(`Tests the bottom panel shows information as expected.\n`); + await TestRunner.showPanel('resources'); + + const backgroundServiceModel = TestRunner.mainTarget.model(Resources.BackgroundServiceModel); + backgroundServiceModel.enable(Protocol.BackgroundService.ServiceName.BackgroundFetch); + + dumpPreviewPanel(); + await toggleRecord(backgroundServiceModel); + dumpPreviewPanel(); + await toggleRecord(backgroundServiceModel); + dumpPreviewPanel(); + + backgroundServiceModel.backgroundServiceEventReceived({ + timestamp: 1556889085, // 2019-05-03 14:11:25.000. + origin: 'http://127.0.0.1:8000/', + serviceWorkerRegistrationId: 42, // invalid. + service: Protocol.BackgroundService.ServiceName.BackgroundFetch, + eventName: 'Event1', + instanceId: 'Instance1', + eventMetadata: [], + }); + backgroundServiceModel.backgroundServiceEventReceived({ + timestamp: 1556889085, // 2019-05-03 14:11:25.000. + origin: 'http://127.0.0.1:8000/', + serviceWorkerRegistrationId: 42, // invalid. + service: Protocol.BackgroundService.ServiceName.BackgroundFetch, + eventName: 'Event2', + instanceId: 'Instance1', + eventMetadata: [{key: 'key', value: 'value'}], + }); + dumpPreviewPanel(); + + const dataGrid = UI.panels.resources._sidebar.backgroundFetchTreeElement._view._dataGrid; + dataGrid.rootNode().children[0].select(); + dumpPreviewPanel(); + dataGrid.rootNode().children[1].select(); + dumpPreviewPanel(); + + // Simulate clicking the clear button. + UI.panels.resources._sidebar.backgroundFetchTreeElement._view._clearEvents(); + dumpPreviewPanel(); + + TestRunner.completeTest(); +})();
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-detached-expected.txt b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-detached-expected.txt new file mode 100644 index 0000000..3d994969 --- /dev/null +++ b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-detached-expected.txt
@@ -0,0 +1,3 @@ +Tests that highlighting a detached node does not crash. crbug.com/958958 + +
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-detached.js b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-detached.js new file mode 100644 index 0000000..c1aa876 --- /dev/null +++ b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-detached.js
@@ -0,0 +1,23 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +(async function() { + TestRunner.addResult(`Tests that highlighting a detached node does not crash. crbug.com/958958\n`); + await TestRunner.loadModule('elements_test_runner'); + await TestRunner.showPanel('elements'); + + const remoteObject = await TestRunner.evaluateInPageRemoteObject(` + var styleElement = document.createElement('style'); + styleElement.type = 'text/css'; + styleElement.textContent = 'content'; + styleElement.id = 'inspected'; + styleElement; + `); + const domModel = remoteObject.runtimeModel().target().model(SDK.DOMModel); + const node = await domModel.pushObjectAsNodeToFrontend(remoteObject); + node.highlight(); + + await TestRunner.OverlayAgent.getHighlightObjectForTest(node.id); + TestRunner.completeTest(); +})(); \ No newline at end of file
diff --git a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/xr/webxr-origin-trial-interfaces.html b/third_party/blink/web_tests/http/tests/origin_trials/webexposed/xr/webxr-origin-trial-interfaces.html index 103842d..f099b8be 100644 --- a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/xr/webxr-origin-trial-interfaces.html +++ b/third_party/blink/web_tests/http/tests/origin_trials/webexposed/xr/webxr-origin-trial-interfaces.html
@@ -5,12 +5,14 @@ <script src="../../../resources/testharnessreport.js"></script> <script src="../../../resources/origin-trials-helper.js"></script> <script> - -let properties_to_check = {'Navigator': ['xr']}; +let properties_to_check = { + 'Navigator': ['xr'] +}; // The WebXR APIs should not be present without the token. test(t => { - OriginTrialsHelper.check_properties_missing_unless_runtime_flag(this, properties_to_check, 'webXREnabled'); + OriginTrialsHelper.check_properties_missing_unless_runtime_flag( + this, properties_to_check, 'webXREnabled'); }, "WebXR's entrypoint properties are not available without a token."); // Add the token, which was generated with the following command: @@ -24,18 +26,59 @@ }, "WebXR's entrypoint properties are available with origin trial token."); -// Ensure that Gamepad Extensions are NOT enabled by the origin trial token. +// Ensure Gamepad Extensions are NOT enabled by the WebXR origin trial token. test(t => { - webvr_gamepad_properties = { + webvr_gamepad_properties = { 'Gamepad': ['pose', 'hand', 'displayId'], }; - OriginTrialsHelper.check_properties_missing_unless_runtime_flag(this, webvr_gamepad_properties, 'webVREnabled'); -}, "WebVR-specific Gamepad properties are not available with a token."); - + OriginTrialsHelper.check_properties_missing_unless_runtime_flag( + this, webvr_gamepad_properties, 'webVREnabled'); +}, "WebVR-specific Gamepad properties are not available with a WebXR token."); test(t => { webvr_gamepad_interfaces = [ 'GamepadPose' ]; - OriginTrialsHelper.check_interfaces_missing_unless_runtime_flag(this, webvr_gamepad_interfaces, 'webVREnabled'); -}, "WebVR-specific Gamepad interfaces are not available with a token."); + OriginTrialsHelper.check_interfaces_missing_unless_runtime_flag( + this, webvr_gamepad_interfaces, 'webVREnabled'); +}, "WebVR-specific Gamepad interfaces are not available with a WebXR token."); + + +// Ensure that APIs that are not part of the core WebXR set are NOT enabled by +// the WebXR origin trial token. + +test(t => { + let hittest_properties = { + 'XRSession': ['requestHitTest'] + }; + OriginTrialsHelper.check_properties_missing_unless_runtime_flag( + this, hittest_properties, 'webXRHitTestEnabled'); +}, "Hit-test properties are not available with a WebXR token."); +test(t => { + let hittest_interfaces = [ + 'XRHitResult'/*, + TODO(https://crbug.com/958561): Uncomment after resolving the issue. + 'XRRay'*/ + ]; + OriginTrialsHelper.check_interfaces_missing_unless_runtime_flag( + this, hittest_interfaces, 'webXRHitTestEnabled'); +}, "Hit-test interfaces are not available with a WebXR token."); + +test(t => { + let planes_properties = { + 'XRFrame': ['worldInformation'], + 'XRSession': ['worldTrackingState', 'updateWorldTrackingState'] + }; + OriginTrialsHelper.check_properties_missing_unless_runtime_flag( + this, planes_properties, 'webXRPlaneDetectionEnabled'); +}, "Plane properties are not available with a WebXR token."); +test(t => { + let planes_interfaces = [ + 'XRPlane', + 'XRPlaneDetectionState', + 'XRWorldInformation', + 'XRWorldTrackingState' + ]; + OriginTrialsHelper.check_interfaces_missing_unless_runtime_flag( + this, planes_interfaces, 'webXRPlaneDetectionEnabled'); +}, "Plane interfaces are not available with a WebXR token."); </script>
diff --git a/third_party/blink/web_tests/media/picture-in-picture/v2/detached-iframe.html b/third_party/blink/web_tests/media/picture-in-picture/v2/detached-iframe.html new file mode 100644 index 0000000..a53bb97 --- /dev/null +++ b/third_party/blink/web_tests/media/picture-in-picture/v2/detached-iframe.html
@@ -0,0 +1,25 @@ +<!DOCTYPE html> +<title>Test detached iframe in Picture-in-Picture</title> +<script src="../../../resources/testharness.js"></script> +<script src="../../../resources/testharnessreport.js"></script> +<script src="../../../resources/testdriver.js"></script> +<script src="../../../resources/testdriver-vendor.js"></script> +<script src="utils.js"></script> +<body></body> +<script> +promise_test(async t => { + enablePictureInPictureV2ForTest(t); + + const frame = document.createElement('iframe'); + const frameLoadedPromise = new Promise(resolve => { + frame.addEventListener('load', resolve, { once: true }); + document.body.appendChild(frame); + }); + await frameLoadedPromise; + + const element = frame.contentDocument.createElement('div'); + document.body.removeChild(frame); + return promise_rejects(t, 'InvalidStateError', + requestPictureInPictureWithTrustedClick(element, { aspectRatio: 1 })); +}, 'request Picture-in-Picture rejects when frame is detached'); +</script>
diff --git a/third_party/blink/web_tests/media/picture-in-picture/v2/request-picture-in-picture-twice.html b/third_party/blink/web_tests/media/picture-in-picture/v2/request-picture-in-picture-twice.html new file mode 100644 index 0000000..a1084cb --- /dev/null +++ b/third_party/blink/web_tests/media/picture-in-picture/v2/request-picture-in-picture-twice.html
@@ -0,0 +1,35 @@ +<!DOCTYPE html> +<title>Test request Picture-in-Picture on two elements</title> +<script src="../../../resources/testharness.js"></script> +<script src="../../../resources/testharnessreport.js"></script> +<script src="../../../resources/testdriver.js"></script> +<script src="../../../resources/testdriver-vendor.js"></script> +<script src="../../../external/wpt/picture-in-picture/resources/picture-in-picture-helpers.js"></script> +<script src="utils.js"></script> +<body></body> +<script> +const options = { aspectRatio: 1 }; + +promise_test(async t => { + enablePictureInPictureV2ForTest(t); + + const element1 = document.createElement('div'); + const element2 = document.createElement('div'); + await test_driver.bless('request Picture-in-Picture'); + const promise = element1.requestPictureInPicture(options); + await promise_rejects(t, 'NotAllowedError', element2.requestPictureInPicture(options)); + return promise; +}, 'request Picture-in-Picture consumes user gesture'); + +promise_test(async t => { + enablePictureInPictureV2ForTest(t); + + const video = await loadVideo(window.document, '../../content/test.ogv'); + await test_driver.bless('request Picture-in-Picture'); + await video.requestPictureInPicture(); + assert_equals(document.pictureInPictureElement, video); + + const element = document.createElement('div'); + return element.requestPictureInPicture({ aspectRatio: 1 }); +}, 'request Picture-in-Picture does not require user gesture if document.pictureInPictureElement is set'); +</script>
diff --git a/third_party/blink/web_tests/media/picture-in-picture/v2/request-picture-in-picture.html b/third_party/blink/web_tests/media/picture-in-picture/v2/request-picture-in-picture.html new file mode 100644 index 0000000..de99c65 --- /dev/null +++ b/third_party/blink/web_tests/media/picture-in-picture/v2/request-picture-in-picture.html
@@ -0,0 +1,53 @@ +<!DOCTYPE html> +<title>picture in picture v2 requires gesture</title> +<script src="../../../resources/testharness.js"></script> +<script src="../../../resources/testharnessreport.js"></script> +<script src="../../../resources/testdriver.js"></script> +<script src="../../../resources/testdriver-vendor.js"></script> +<script src="utils.js"></script> +<body> +<div id=example></div> +<script> +promise_test(async t => { + enablePictureInPictureV2ForTest(t); + + const element = document.getElementById('example'); + return promise_rejects(t, 'NotAllowedError', element.requestPictureInPicture({ aspectRatio: 1 })); +}, 'request Picture-in-Picture requires a user gesture'); + +promise_test(async t => { + enablePictureInPictureV2ForTest(t); + + const element = document.getElementById('example'); + return requestPictureInPictureWithTrustedClick(element, { aspectRatio: 1 }); +}, 'request Picture-in-Picture resolves on user click'); + +promise_test(async t => { + enablePictureInPictureV2ForTest(t); + + const element = document.getElementById('example'); + return requestPictureInPictureWithTrustedClick(element, { aspectRatio: 1, interactive: true }); +}, 'request Picture-in-Picture takes an interactive option that can be true'); + +promise_test(async t => { + enablePictureInPictureV2ForTest(t); + + const element = document.getElementById('example'); + return requestPictureInPictureWithTrustedClick(element, { aspectRatio: 1, interactive: false }); +}, 'request Picture-in-Picture takes an interactive option that can be false'); + +promise_test(async t => { + enablePictureInPictureV2ForTest(t); + + const element = document.getElementById('example'); + return requestPictureInPictureWithTrustedClick(element, { aspectRatio: 1, interactive: 'test' }); +}, 'request Picture-in-Picture validates interactive option'); + +promise_test(async t => { + enablePictureInPictureV2ForTest(t); + + const element = document.getElementById('example'); + return promise_rejects(t, new TypeError(), element.requestPictureInPicture({})); +}, 'request Picture-in-Picture requires aspect ratio option'); +</script> +</body>
diff --git a/third_party/blink/web_tests/media/picture-in-picture/v2/utils.js b/third_party/blink/web_tests/media/picture-in-picture/v2/utils.js new file mode 100644 index 0000000..a46500c --- /dev/null +++ b/third_party/blink/web_tests/media/picture-in-picture/v2/utils.js
@@ -0,0 +1,22 @@ +function enablePictureInPictureV2ForTest(t) { + const pictureInPictureEnabledValue = + internals.runtimeFlags.pictureInPictureEnabled; + const pictureInPictureEnabledV2Value = + internals.runtimeFlags.pictureInPictureV2Enabled; + + internals.runtimeFlags.pictureInPictureEnabled = true; + internals.runtimeFlags.pictureInPictureV2Enabled = true; + + t.add_cleanup(() => { + internals.runtimeFlags.pictureInPictureEnabled = + pictureInPictureEnabledValue; + internals.runtimeFlags.pictureInPictureV2Enabled = + pictureInPictureEnabledV2Value; + }); +} + +// Calls requestPictureInPicture() in a context that's 'allowed to request PiP'. +async function requestPictureInPictureWithTrustedClick(element, options) { + await test_driver.bless('request Picture-in-Picture'); + return element.requestPictureInPicture(options); +}
diff --git a/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCRtpTransceiver.https-expected.txt b/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCRtpTransceiver.https-expected.txt deleted file mode 100644 index 2cbd0e7..0000000 --- a/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCRtpTransceiver.https-expected.txt +++ /dev/null
@@ -1,40 +0,0 @@ -This is a testharness.js-based test. -Harness Error. harness_status.status = 1 , harness_status.message = Cannot read property 'receiver' of undefined -FAIL checkAddTransceiverNoTrack promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument." -FAIL checkAddTransceiverWithTrack promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument." -FAIL checkAddTransceiverWithAddTrack assert_equals: expected "[{currentDirection:null,direction:\"sendrecv\",mid:null,receiver:{track:{kind:\"audio\"}},sender:{track:{}},stopped:false},{currentDirection:null,direction:\"sendrecv\",mid:null,receiver:{track:{kind:\"video\"}},sender:{track:{}},stopped:false}]" but got "[]" -FAIL checkAddTransceiverWithDirection promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument." -FAIL checkMsidNoTrackId assert_true: expected true got false -FAIL checkAddTransceiverWithSetRemoteOfferSending promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument." -FAIL checkAddTransceiverWithSetRemoteOfferNoSend promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument." -FAIL checkAddTransceiverBadKind assert_true: addTransceiver("foo") throws a TypeError expected true got false -FAIL checkNoMidOffer assert_equals: expected "[{currentDirection:null,direction:\"recvonly\",receiver:{track:{kind:\"audio\"}},sender:{track:null},stopped:false}]" but got "[]" -FAIL checkNoMidAnswer assert_equals: expected "[{currentDirection:\"sendonly\",direction:\"sendrecv\",receiver:{track:{kind:\"audio\"}},sender:{track:{kind:\"audio\"}},stopped:false}]" but got "[]" -FAIL checkSetDirection promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument." -FAIL checkCurrentDirection assert_equals: expected "[{currentDirection:null}]" but got "[]" -FAIL checkSendrecvWithNoSendTrack promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument." -FAIL checkSendrecvWithTracklessStream promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument." -FAIL checkAddTransceiverNoTrackDoesntPair promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument." -FAIL checkAddTransceiverWithTrackDoesntPair promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument." -FAIL checkAddTransceiverThenReplaceTrackDoesntPair promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument." -FAIL checkAddTransceiverThenAddTrackPairs promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument." -FAIL checkAddTrackPairs promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument." -FAIL checkReplaceTrackNullDoesntPreventPairing promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument." -FAIL checkRemoveAndReadd assert_equals: expected "[{direction:\"recvonly\",sender:{track:null}},{direction:\"sendrecv\",sender:{track:{}}}]" but got "[]" -FAIL checkAddTrackExistingTransceiverThenRemove promise_test: Unhandled rejection with value: object "InvalidStateError: Failed to execute 'addTransceiver' on 'RTCPeerConnection': This operation is only supported in 'unified-plan'. 'unified-plan' will become the default behavior in the future, but it is currently experimental. To try it out, construct the RTCPeerConnection with sdpSemantics:'unified-plan' present in the RTCConfiguration argument." -FAIL checkRemoveTrackNegotiation promise_test: Unhandled rejection with value: object "TypeError: Cannot set property 'direction' of undefined" -FAIL checkMute promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'receiver' of undefined" -FAIL checkStop promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'stop' of undefined" -FAIL checkStopAfterCreateOffer promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'stop' of undefined" -FAIL checkStopAfterSetLocalOffer promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'stop' of undefined" -FAIL checkStopAfterSetRemoteOffer promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'stop' of undefined" -FAIL checkStopAfterCreateAnswer promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'stop' of undefined" -FAIL checkStopAfterSetLocalAnswer promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'receiver' of undefined" -FAIL checkStopAfterClose assert_equals: Stopping a transceiver on a closed PC should throw. throws InvalidStateError expected "InvalidStateError" but got "TypeError" -FAIL checkLocalRollback assert_equals: expected "[{currentDirection:null,direction:\"sendrecv\",receiver:{track:{kind:\"audio\"}},sender:{track:{}},stopped:false}]" but got "[]" -FAIL checkRollbackAndSetRemoteOfferWithDifferentType promise_test: Unhandled rejection with value: object "TypeError: Failed to execute 'setLocalDescription' on 'RTCPeerConnection': The provided value 'rollback' is not a valid enum value of type RTCSdpType." -FAIL checkRemoteRollback promise_test: Unhandled rejection with value: object "TypeError: Failed to execute 'setRemoteDescription' on 'RTCPeerConnection': The provided value 'rollback' is not a valid enum value of type RTCSdpType." -FAIL checkMsectionReuse promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'mid' of undefined" -FAIL checkStopAfterCreateOfferWithReusedMsection promise_test: Unhandled rejection with value: object "TypeError: Cannot read property 'stop' of undefined" -Harness: the test ran to completion. -
diff --git a/third_party/blink/web_tests/webaudio/Analyser/realtimeanalyser-fftsize-reset.html b/third_party/blink/web_tests/webaudio/Analyser/realtimeanalyser-fftsize-reset.html index 56894a3..a3a3d143 100644 --- a/third_party/blink/web_tests/webaudio/Analyser/realtimeanalyser-fftsize-reset.html +++ b/third_party/blink/web_tests/webaudio/Analyser/realtimeanalyser-fftsize-reset.html
@@ -23,7 +23,7 @@ testFFTSize(should, { initialFFTSize: 128, finalFFTSize: 1024, - errorThreshold: {relativeThreshold: 1.9095e-6} + errorThreshold: {relativeThreshold: 1.9238e-6} }).then(() => task.done()); });
diff --git a/third_party/blink/web_tests/webaudio/Analyser/realtimeanalyser-freq-data.html b/third_party/blink/web_tests/webaudio/Analyser/realtimeanalyser-freq-data.html index dc247673..5960bcd 100644 --- a/third_party/blink/web_tests/webaudio/Analyser/realtimeanalyser-freq-data.html +++ b/third_party/blink/web_tests/webaudio/Analyser/realtimeanalyser-freq-data.html
@@ -27,7 +27,12 @@ let audit = Audit.createTaskRunner(); // Options for basic tests of the AnalyserNode frequency domain data. The - // thresholds are experimentally determined. + // thresholds are experimentally determined. The threshold for the byte + // frequency results could in general be off by 1 depending on very minor + // differences in computing the FFT value and converting it to a byte + // value (because Math.floor must be used). For the tests that fail, set + // |byteThreshold| to 1. Using any threshold larger than this is a + // serious error in the implementation of the AnalyserNode FFT. let testConfig = [ { order: 5, @@ -42,7 +47,7 @@ {order: 7, floatRelError: 1.1473e-6}, {order: 8, floatRelError: 1.0442e-6}, {order: 9, floatRelError: 2.6427e-5}, - {order: 10, floatRelError: 2.9771e-5}, + {order: 10, floatRelError: 2.9771e-5, byteThreshold: 1}, {order: 11, floatRelError: 1.3456e-5}, {order: 12, floatRelError: 4.6116e-7}, {order: 13, floatRelError: 3.2106e-7}, @@ -170,8 +175,9 @@ expected, analyser.minDecibels, analyser.maxDecibels); should(byteFreqData, analyser.fftSize + '-point byte FFT') - .beCloseToArray(expectedByteData, 0); - + .beCloseToArray( + expectedByteData, + {absoluteThreshold: options.byteThreshold || 0}); }) .then(context.resume.bind(context));
diff --git a/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-detune-modulation.html b/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-detune-modulation.html index d34ef54..cc99fe3 100644 --- a/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-detune-modulation.html +++ b/third_party/blink/web_tests/webaudio/AudioBufferSource/audiobuffersource-detune-modulation.html
@@ -57,8 +57,8 @@ // experiments. compareBuffersWithConstraints(should, actual, expected, { prefix: '', - thresholdSNR: 93.336, - thresholdDiffULP: 1.0141, + thresholdSNR: 93.320, + thresholdDiffULP: 1.0352, thresholdDiffCount: 0, bitDepth: 16 });
diff --git a/third_party/blink/web_tests/webaudio/Oscillator/osc-negative-freq.html b/third_party/blink/web_tests/webaudio/Oscillator/osc-negative-freq.html index 503241c..19b23dd 100644 --- a/third_party/blink/web_tests/webaudio/Oscillator/osc-negative-freq.html +++ b/third_party/blink/web_tests/webaudio/Oscillator/osc-negative-freq.html
@@ -27,7 +27,7 @@ runTest(should, { message: 'Sum of positive and negative frequency sine oscillators', type: 'sine', - threshold: 3.5763e-7 + threshold: 4.1724e-7 }).then(() => task.done()); }); @@ -145,7 +145,7 @@ should( actual, 'Sum of positive and negative frequency custom oscillators') - .beCloseToArray(expected, {absoluteThreshold: 3.5763e-7}); + .beCloseToArray(expected, {absoluteThreshold: 4.1724e-7}); }) .then(() => task.done()); });
diff --git a/third_party/blink/web_tests/webaudio/Oscillator/start-sampling.html b/third_party/blink/web_tests/webaudio/Oscillator/start-sampling.html index 59b6994..4667416 100644 --- a/third_party/blink/web_tests/webaudio/Oscillator/start-sampling.html +++ b/third_party/blink/web_tests/webaudio/Oscillator/start-sampling.html
@@ -27,7 +27,7 @@ }, function(task, should) { testStartSampling(should, 1.25, { - error: 1.0842e-4, + error: 1.0843e-4, snrThreshold: 84.054 }).then(task.done.bind(task)); });
diff --git a/third_party/blink/web_tests/webaudio/PeriodicWave/ctor-periodicwave.html b/third_party/blink/web_tests/webaudio/PeriodicWave/ctor-periodicwave.html index eda343b..d0dd451 100644 --- a/third_party/blink/web_tests/webaudio/PeriodicWave/ctor-periodicwave.html +++ b/third_party/blink/web_tests/webaudio/PeriodicWave/ctor-periodicwave.html
@@ -101,21 +101,21 @@ audit.define('1: imag periodicwave test', (task, should) => { verifyPeriodicWaveOutput( - should, {imag: [0, 2]}, generateReference(Math.sin), 2.7232e-5) + should, {imag: [0, 2]}, generateReference(Math.sin), 2.7262e-5) .then(() => task.done()); }); audit.define('2: imag periodicwave test', (task, should) => { verifyPeriodicWaveOutput( should, {imag: [0, 2], disableNormalization: false}, - generateReference(Math.sin), 2.7232e-5) + generateReference(Math.sin), 2.7262e-5) .then(() => task.done()); }); audit.define('3: imag periodicwave test', (task, should) => { verifyPeriodicWaveOutput( should, {imag: [0, 2], disableNormalization: true}, - generateReference(x => 2 * Math.sin(x)), 5.4464e-5) + generateReference(x => 2 * Math.sin(x)), 5.4524-5) .then(() => task.done()); });
diff --git a/third_party/breakpad/BUILD.gn b/third_party/breakpad/BUILD.gn index 27c3d0c..d9d959d 100644 --- a/third_party/breakpad/BUILD.gn +++ b/third_party/breakpad/BUILD.gn
@@ -712,9 +712,9 @@ deps = [ ":client", ":processor_support", - "//base/test:run_all_unittests", "//testing/gmock", "//testing/gtest", + "//testing/gtest:gtest_main", ] data_deps = [
diff --git a/third_party/crashpad/README.chromium b/third_party/crashpad/README.chromium index 1e9de7d..a670e02c 100644 --- a/third_party/crashpad/README.chromium +++ b/third_party/crashpad/README.chromium
@@ -2,7 +2,7 @@ Short Name: crashpad URL: https://crashpad.chromium.org/ Version: unknown -Revision: c96226c6baa853f43981b0240974e33a0b661f8d +Revision: 607c80e0b8f0c0f22e1c5a80402df7ebd9114b19 License: Apache 2.0 License File: crashpad/LICENSE Security Critical: yes
diff --git a/third_party/crashpad/crashpad/client/crashpad_client.h b/third_party/crashpad/crashpad/client/crashpad_client.h index 8bf43ac..3db1c9c 100644 --- a/third_party/crashpad/crashpad/client/crashpad_client.h +++ b/third_party/crashpad/crashpad/client/crashpad_client.h
@@ -78,6 +78,10 @@ //! On Fuchsia, this method binds to the exception port of the current default //! job, and starts a Crashpad handler to monitor that port. //! + //! On Linux, this method starts a Crashpad handler, connected to this process + //! via an `AF_UNIX` socket pair and installs signal handlers to request crash + //! dumps on the client's socket end. + //! //! \param[in] handler The path to a Crashpad handler executable. //! \param[in] database The path to a Crashpad database. The handler will be //! started with this path as its `--database` argument.
diff --git a/third_party/crashpad/crashpad/client/crashpad_client_linux.cc b/third_party/crashpad/crashpad/client/crashpad_client_linux.cc index b72980c1..008f8587 100644 --- a/third_party/crashpad/crashpad/client/crashpad_client_linux.cc +++ b/third_party/crashpad/crashpad/client/crashpad_client_linux.cc
@@ -31,6 +31,7 @@ #include "util/linux/exception_information.h" #include "util/linux/scoped_pr_set_dumpable.h" #include "util/linux/scoped_pr_set_ptracer.h" +#include "util/linux/socket.h" #include "util/misc/from_pointer_cast.h" #include "util/posix/double_fork_and_exec.h" #include "util/posix/signals.h" @@ -43,7 +44,7 @@ return base::StringPrintf("--%s=%d", name.c_str(), value); } -std::string FormatArgumentAddress(const std::string& name, void* addr) { +std::string FormatArgumentAddress(const std::string& name, const void* addr) { return base::StringPrintf("--%s=%p", name.c_str(), addr); } @@ -110,8 +111,90 @@ #endif // OS_ANDROID +// A base class for Crashpad signal handler implementations. +class SignalHandler { + public: + // Returns the currently installed signal hander. May be `nullptr` if no + // handler has been installed. + static SignalHandler* Get() { return handler_; } + + // Disables any installed Crashpad signal handler for the calling thread. If a + // crash signal is received, any previously installed (non-Crashpad) signal + // handler will be restored and the signal reraised. + static void DisableForThread() { disabled_for_thread_ = true; } + + void SetFirstChanceHandler(CrashpadClient::FirstChanceHandler handler) { + first_chance_handler_ = handler; + } + + // The base implementation for all signal handlers, suitable for calling + // directly to simulate signal delivery. + bool HandleCrash(int signo, siginfo_t* siginfo, void* context) { + if (disabled_for_thread_) { + return false; + } + + if (first_chance_handler_ && + first_chance_handler_( + signo, siginfo, static_cast<ucontext_t*>(context))) { + return true; + } + + exception_information_.siginfo_address = + FromPointerCast<decltype(exception_information_.siginfo_address)>( + siginfo); + exception_information_.context_address = + FromPointerCast<decltype(exception_information_.context_address)>( + context); + exception_information_.thread_id = sys_gettid(); + + HandleCrashImpl(); + return false; + } + + protected: + SignalHandler() = default; + + bool Install() { + DCHECK(!handler_); + handler_ = this; + return Signals::InstallCrashHandlers( + HandleOrReraiseSignal, 0, &old_actions_); + } + + const ExceptionInformation& GetExceptionInfo() { + return exception_information_; + } + + virtual void HandleCrashImpl() = 0; + + private: + // The signal handler installed at OS-level. + static void HandleOrReraiseSignal(int signo, + siginfo_t* siginfo, + void* context) { + if (handler_->HandleCrash(signo, siginfo, context)) { + return; + } + Signals::RestoreHandlerAndReraiseSignalOnReturn( + siginfo, handler_->old_actions_.ActionForSignal(signo)); + } + + Signals::OldActions old_actions_ = {}; + ExceptionInformation exception_information_ = {}; + CrashpadClient::FirstChanceHandler first_chance_handler_ = nullptr; + + static SignalHandler* handler_; + + static thread_local bool disabled_for_thread_; + + DISALLOW_COPY_AND_ASSIGN(SignalHandler); +}; +SignalHandler* SignalHandler::handler_ = nullptr; +thread_local bool SignalHandler::disabled_for_thread_ = false; + // Launches a single use handler to snapshot this process. -class LaunchAtCrashHandler { +class LaunchAtCrashHandler : public SignalHandler { public: static LaunchAtCrashHandler* Get() { static LaunchAtCrashHandler* instance = new LaunchAtCrashHandler(); @@ -129,33 +212,19 @@ } argv_strings_.push_back(FormatArgumentAddress("trace-parent-with-exception", - &exception_information_)); + &GetExceptionInfo())); StringVectorToCStringVector(argv_strings_, &argv_); - return Signals::InstallCrashHandlers(HandleCrash, 0, &old_actions_); + return Install(); } - bool HandleCrashNonFatal(int signo, siginfo_t* siginfo, void* context) { - if (first_chance_handler_ && - first_chance_handler_( - signo, siginfo, static_cast<ucontext_t*>(context))) { - return true; - } - - exception_information_.siginfo_address = - FromPointerCast<decltype(exception_information_.siginfo_address)>( - siginfo); - exception_information_.context_address = - FromPointerCast<decltype(exception_information_.context_address)>( - context); - exception_information_.thread_id = syscall(SYS_gettid); - + void HandleCrashImpl() override { ScopedPrSetPtracer set_ptracer(sys_getpid(), /* may_log= */ false); ScopedPrSetDumpable set_dumpable(/* may_log= */ false); pid_t pid = fork(); if (pid < 0) { - return false; + return; } if (pid == 0) { if (set_envp_) { @@ -170,52 +239,72 @@ int status; waitpid(pid, &status, 0); - return false; } - void HandleCrashFatal(int signo, siginfo_t* siginfo, void* context) { - if (enabled_ && HandleCrashNonFatal(signo, siginfo, context)) { - return; - } - Signals::RestoreHandlerAndReraiseSignalOnReturn( - siginfo, old_actions_.ActionForSignal(signo)); - } - - void SetFirstChanceHandler(CrashpadClient::FirstChanceHandler handler) { - first_chance_handler_ = handler; - } - - static void Disable() { enabled_ = false; } - private: LaunchAtCrashHandler() = default; ~LaunchAtCrashHandler() = delete; - static void HandleCrash(int signo, siginfo_t* siginfo, void* context) { - auto state = Get(); - state->HandleCrashFatal(signo, siginfo, context); - } - - Signals::OldActions old_actions_ = {}; std::vector<std::string> argv_strings_; std::vector<const char*> argv_; std::vector<std::string> envp_strings_; std::vector<const char*> envp_; - ExceptionInformation exception_information_; - CrashpadClient::FirstChanceHandler first_chance_handler_ = nullptr; bool set_envp_ = false; - static thread_local bool enabled_; - DISALLOW_COPY_AND_ASSIGN(LaunchAtCrashHandler); }; -thread_local bool LaunchAtCrashHandler::enabled_ = true; -// A pointer to the currently installed crash signal handler. This allows -// the static method CrashpadClient::DumpWithoutCrashing to simulate a crash -// using the currently configured crash handling strategy. -static LaunchAtCrashHandler* g_crash_handler; +class RequestCrashDumpHandler : public SignalHandler { + public: + static RequestCrashDumpHandler* Get() { + static RequestCrashDumpHandler* instance = new RequestCrashDumpHandler(); + return instance; + } + + // pid < 0 indicates the handler pid should be determined by communicating + // over the socket. + // pid == 0 indicates it is not necessary to set the handler as this process' + // ptracer. e.g. if the handler has CAP_SYS_PTRACE or if this process is in a + // user namespace and the handler's uid matches the uid of the process that + // created the namespace. + // pid > 0 directly indicates what the handler's pid is expected to be, so + // retrieving this information from the handler is not necessary. + bool Initialize(ScopedFileHandle sock, pid_t pid) { + ExceptionHandlerClient client(sock.get(), true); + if (pid < 0) { + ucred creds; + if (!client.GetHandlerCredentials(&creds)) { + return false; + } + pid = creds.pid; + } + if (pid > 0 && client.SetPtracer(pid) != 0) { + LOG(ERROR) << "failed to set ptracer"; + return false; + } + sock_to_handler_.reset(sock.release()); + return Install(); + } + + void HandleCrashImpl() override { + ExceptionHandlerProtocol::ClientInformation info = {}; + info.exception_information_address = + FromPointerCast<VMAddress>(&GetExceptionInfo()); + + ExceptionHandlerClient client(sock_to_handler_.get(), true); + client.RequestCrashDump(info); + } + + private: + RequestCrashDumpHandler() = default; + + ~RequestCrashDumpHandler() = delete; + + ScopedFileHandle sock_to_handler_; + + DISALLOW_COPY_AND_ASSIGN(RequestCrashDumpHandler); +}; } // namespace @@ -232,11 +321,26 @@ const std::vector<std::string>& arguments, bool restartable, bool asynchronous_start) { - // TODO(jperaza): Implement this after the Android/Linux ExceptionHandlerSever - // supports accepting new connections. - // https://crashpad.chromium.org/bug/30 - NOTREACHED(); - return false; + DCHECK(!restartable); + DCHECK(!asynchronous_start); + + ScopedFileHandle client_sock, handler_sock; + if (!UnixCredentialSocket::CreateCredentialSocketpair(&client_sock, + &handler_sock)) { + return false; + } + + std::vector<std::string> argv = BuildHandlerArgvStrings( + handler, database, metrics_dir, url, annotations, arguments); + + argv.push_back(FormatArgumentInt("initial-client-fd", handler_sock.get())); + argv.push_back("--shared-client-connection"); + if (!DoubleForkAndExec(argv, nullptr, handler_sock.get(), false, nullptr)) { + return false; + } + + auto signal_handler = RequestCrashDumpHandler::Get(); + return signal_handler->Initialize(std::move(client_sock), -1); } #if defined(OS_ANDROID) @@ -259,12 +363,7 @@ kInvalidFileHandle); auto signal_handler = LaunchAtCrashHandler::Get(); - if (signal_handler->Initialize(&argv, env)) { - DCHECK(!g_crash_handler); - g_crash_handler = signal_handler; - return true; - } - return false; + return signal_handler->Initialize(&argv, env); } // static @@ -304,12 +403,7 @@ arguments, kInvalidFileHandle); auto signal_handler = LaunchAtCrashHandler::Get(); - if (signal_handler->Initialize(&argv, env)) { - DCHECK(!g_crash_handler); - g_crash_handler = signal_handler; - return true; - } - return false; + return signal_handler->Initialize(&argv, env); } // static @@ -351,12 +445,7 @@ handler, database, metrics_dir, url, annotations, arguments); auto signal_handler = LaunchAtCrashHandler::Get(); - if (signal_handler->Initialize(&argv, nullptr)) { - DCHECK(!g_crash_handler); - g_crash_handler = signal_handler; - return true; - } - return false; + return signal_handler->Initialize(&argv, nullptr); } // static @@ -378,7 +467,7 @@ // static void CrashpadClient::DumpWithoutCrash(NativeCPUContext* context) { - if (!g_crash_handler) { + if (!SignalHandler::Get()) { DLOG(ERROR) << "Crashpad isn't enabled"; return; } @@ -395,21 +484,21 @@ siginfo.si_signo = Signals::kSimulatedSigno; siginfo.si_errno = 0; siginfo.si_code = 0; - g_crash_handler->HandleCrashNonFatal( + SignalHandler::Get()->HandleCrash( siginfo.si_signo, &siginfo, reinterpret_cast<void*>(context)); } // static void CrashpadClient::CrashWithoutDump(const std::string& message) { - LaunchAtCrashHandler::Disable(); + SignalHandler::DisableForThread(); LOG(FATAL) << message; } // static void CrashpadClient::SetFirstChanceExceptionHandler( FirstChanceHandler handler) { - DCHECK(g_crash_handler); - g_crash_handler->SetFirstChanceHandler(handler); + DCHECK(SignalHandler::Get()); + SignalHandler::Get()->SetFirstChanceHandler(handler); } } // namespace crashpad
diff --git a/third_party/crashpad/crashpad/client/crashpad_client_linux_test.cc b/third_party/crashpad/crashpad/client/crashpad_client_linux_test.cc index 68a49e7..2ed8825 100644 --- a/third_party/crashpad/crashpad/client/crashpad_client_linux_test.cc +++ b/third_party/crashpad/crashpad/client/crashpad_client_linux_test.cc
@@ -16,7 +16,6 @@ #include <dlfcn.h> #include <stdlib.h> -#include <sys/socket.h> #include <sys/syscall.h> #include <sys/types.h> #include <unistd.h> @@ -38,6 +37,7 @@ #include "util/file/filesystem.h" #include "util/linux/exception_handler_client.h" #include "util/linux/exception_information.h" +#include "util/linux/socket.h" #include "util/misc/address_types.h" #include "util/misc/from_pointer_cast.h" #include "util/posix/signals.h" @@ -55,59 +55,55 @@ namespace test { namespace { +struct StartHandlerForSelfTestOptions { + bool start_handler_at_crash; + bool simulate_crash; + bool set_first_chance_handler; +}; + +class StartHandlerForSelfTest + : public testing::TestWithParam<std::tuple<bool, bool, bool>> { + public: + StartHandlerForSelfTest() = default; + ~StartHandlerForSelfTest() = default; + + void SetUp() override { + std::tie(options_.start_handler_at_crash, + options_.simulate_crash, + options_.set_first_chance_handler) = GetParam(); + } + + const StartHandlerForSelfTestOptions& Options() const { return options_; } + + private: + StartHandlerForSelfTestOptions options_; + + DISALLOW_COPY_AND_ASSIGN(StartHandlerForSelfTest); +}; + bool HandleCrashSuccessfully(int, siginfo_t*, ucontext_t*) { return true; } -TEST(CrashpadClient, SimulateCrash) { - ScopedTempDir temp_dir; - - base::FilePath handler_path = TestPaths::Executable().DirName().Append( - FILE_PATH_LITERAL("crashpad_handler")); - - crashpad::CrashpadClient client; - ASSERT_TRUE(client.StartHandlerAtCrash(handler_path, - base::FilePath(temp_dir.path()), - base::FilePath(), - "", - std::map<std::string, std::string>(), - std::vector<std::string>())); - - auto database = - CrashReportDatabase::InitializeWithoutCreating(temp_dir.path()); - ASSERT_TRUE(database); - - { - CrashpadClient::SetFirstChanceExceptionHandler(HandleCrashSuccessfully); - - CRASHPAD_SIMULATE_CRASH(); - - std::vector<CrashReportDatabase::Report> reports; - ASSERT_EQ(database->GetPendingReports(&reports), - CrashReportDatabase::kNoError); - EXPECT_EQ(reports.size(), 0u); - - reports.clear(); - ASSERT_EQ(database->GetCompletedReports(&reports), - CrashReportDatabase::kNoError); - EXPECT_EQ(reports.size(), 0u); - } - - { - CrashpadClient::SetFirstChanceExceptionHandler(nullptr); - - CRASHPAD_SIMULATE_CRASH(); - - std::vector<CrashReportDatabase::Report> reports; - ASSERT_EQ(database->GetPendingReports(&reports), - CrashReportDatabase::kNoError); - EXPECT_EQ(reports.size(), 1u); - - reports.clear(); - ASSERT_EQ(database->GetCompletedReports(&reports), - CrashReportDatabase::kNoError); - EXPECT_EQ(reports.size(), 0u); - } +bool InstallHandler(CrashpadClient* client, + bool start_at_crash, + const base::FilePath& handler_path, + const base::FilePath& database_path) { + return start_at_crash + ? client->StartHandlerAtCrash(handler_path, + database_path, + base::FilePath(), + "", + std::map<std::string, std::string>(), + std::vector<std::string>()) + : client->StartHandler(handler_path, + database_path, + base::FilePath(), + "", + std::map<std::string, std::string>(), + std::vector<std::string>(), + false, + false); } constexpr char kTestAnnotationName[] = "name_of_annotation"; @@ -152,7 +148,7 @@ ADD_FAILURE(); } -CRASHPAD_CHILD_TEST_MAIN(StartHandlerAtCrashChild) { +CRASHPAD_CHILD_TEST_MAIN(StartHandlerForSelfTestChild) { FileHandle in = StdioFileHandle(StdioStream::kStandardInput); VMSize temp_dir_length; @@ -161,6 +157,9 @@ std::string temp_dir(temp_dir_length, '\0'); CheckedReadFileExactly(in, &temp_dir[0], temp_dir_length); + StartHandlerForSelfTestOptions options; + CheckedReadFileExactly(in, &options, sizeof(options)); + base::FilePath handler_path = TestPaths::Executable().DirName().Append( FILE_PATH_LITERAL("crashpad_handler")); @@ -170,12 +169,10 @@ test_annotation.Set(kTestAnnotationValue); crashpad::CrashpadClient client; - if (!client.StartHandlerAtCrash(handler_path, - base::FilePath(temp_dir), - base::FilePath(), - "", - std::map<std::string, std::string>(), - std::vector<std::string>())) { + if (!InstallHandler(&client, + options.start_handler_at_crash, + handler_path, + base::FilePath(temp_dir))) { return EXIT_FAILURE; } @@ -185,17 +182,28 @@ } #endif + if (options.simulate_crash) { + if (options.set_first_chance_handler) { + client.SetFirstChanceExceptionHandler(HandleCrashSuccessfully); + } + CRASHPAD_SIMULATE_CRASH(); + return EXIT_SUCCESS; + } + __builtin_trap(); NOTREACHED(); return EXIT_SUCCESS; } -class StartHandlerAtCrashTest : public MultiprocessExec { +class StartHandlerForSelfInChildTest : public MultiprocessExec { public: - StartHandlerAtCrashTest() : MultiprocessExec() { - SetChildTestMainFunction("StartHandlerAtCrashChild"); - SetExpectedChildTerminationBuiltinTrap(); + StartHandlerForSelfInChildTest(const StartHandlerForSelfTestOptions& options) + : MultiprocessExec(), options_(options) { + SetChildTestMainFunction("StartHandlerForSelfTestChild"); + if (!options.simulate_crash) { + SetExpectedChildTerminationBuiltinTrap(); + } } private: @@ -206,6 +214,8 @@ WritePipeHandle(), &temp_dir_length, sizeof(temp_dir_length))); ASSERT_TRUE(LoggingWriteFile( WritePipeHandle(), temp_dir.path().value().data(), temp_dir_length)); + ASSERT_TRUE( + LoggingWriteFile(WritePipeHandle(), &options_, sizeof(options_))); // Wait for child to finish. CheckedReadFileAtEOF(ReadPipeHandle()); @@ -221,7 +231,11 @@ reports.clear(); ASSERT_EQ(database->GetPendingReports(&reports), CrashReportDatabase::kNoError); - ASSERT_EQ(reports.size(), 1u); + ASSERT_EQ(reports.size(), options_.set_first_chance_handler ? 0u : 1u); + + if (options_.set_first_chance_handler) { + return; + } std::unique_ptr<const CrashReportDatabase::UploadReport> report; ASSERT_EQ(database->GetReportForUploading(reports[0].uuid, &report), @@ -229,14 +243,26 @@ ValidateDump(report.get()); } - DISALLOW_COPY_AND_ASSIGN(StartHandlerAtCrashTest); + StartHandlerForSelfTestOptions options_; + + DISALLOW_COPY_AND_ASSIGN(StartHandlerForSelfInChildTest); }; -TEST(CrashpadClient, StartHandlerAtCrash) { - StartHandlerAtCrashTest test; +TEST_P(StartHandlerForSelfTest, StartHandlerInChild) { + if (Options().set_first_chance_handler && !Options().simulate_crash) { + // TODO(jperaza): test first chance handlers with real crashes. + return; + } + StartHandlerForSelfInChildTest test(Options()); test.Run(); } +INSTANTIATE_TEST_SUITE_P(StartHandlerForSelfTestSuite, + StartHandlerForSelfTest, + testing::Combine(testing::Bool(), + testing::Bool(), + testing::Bool())); + // Test state for starting the handler for another process. class StartHandlerForClientTest { public: @@ -245,16 +271,8 @@ bool Initialize(bool sanitize) { sanitize_ = sanitize; - - int socks[2]; - if (socketpair(AF_UNIX, SOCK_STREAM, 0, socks) != 0) { - PLOG(ERROR) << "socketpair"; - return false; - } - client_sock_.reset(socks[0]); - server_sock_.reset(socks[1]); - - return true; + return UnixCredentialSocket::CreateCredentialSocketpair(&client_sock_, + &server_sock_); } bool StartHandlerOnDemand() { @@ -356,7 +374,7 @@ FromPointerCast<VMAddress>(&sanitization_info); } - ExceptionHandlerClient handler_client(state->client_sock_); + ExceptionHandlerClient handler_client(state->client_sock_, false); CHECK_EQ(handler_client.RequestCrashDump(info), 0); Signals::RestoreHandlerAndReraiseSignalOnReturn(siginfo, nullptr);
diff --git a/third_party/crashpad/crashpad/compat/win/sys/types.h b/third_party/crashpad/crashpad/compat/win/sys/types.h index fcded87..e8fae88 100644 --- a/third_party/crashpad/crashpad/compat/win/sys/types.h +++ b/third_party/crashpad/crashpad/compat/win/sys/types.h
@@ -20,6 +20,4 @@ #include <stdint.h> -typedef unsigned int pid_t; - #endif // CRASHPAD_COMPAT_WIN_SYS_TYPES_H_
diff --git a/third_party/crashpad/crashpad/handler/crashpad_handler.md b/third_party/crashpad/crashpad/handler/crashpad_handler.md index 003ee2e..c4126b6 100644 --- a/third_party/crashpad/crashpad/handler/crashpad_handler.md +++ b/third_party/crashpad/crashpad/handler/crashpad_handler.md
@@ -121,13 +121,6 @@ Either this option or **--mach-service**, but not both, is required. This option is only valid on macOS. - * **--no-identify-client-via-url** - - Do not add client-identifying fields to the URL. By default, `"prod"`, - `"ver"`, and `"guid"` annotations are added to the upload URL as name-value - pairs `"product"`, `"version"`, and `"guid"`, respectively. Using this - option disables that behavior. - * **--initial-client-data**=*HANDLE_request_crash_dump*,*HANDLE_request_non_crash_dump*,*HANDLE_non_crash_dump_completed*,*HANDLE_first_pipe_instance*,*HANDLE_client_process*,*Address_crash_exception_information*,*Address_non_crash_exception_information*,*Address_debug_critical_section* Register the initial client using the inherited handles and data provided. @@ -141,6 +134,13 @@ client to register, and exits when all clients have exited, after waiting for any uploads in progress to complete. + * **--initial-client-fd**=_FD_ + + Wait for client requests on _FD_. Either this option or + **--trace-parent-with-exception**, but not both, is required. The handler + exits when all client connections have been closed. This option is only valid + on Linux platforms. + * **--mach-service**=_SERVICE_ Check in with the bootstrap server under the name _SERVICE_. Either this @@ -198,6 +198,13 @@ To prevent excessive accumulation of handler processes, _ARGUMENT_ must not be `--monitor-self`. +* **--no-identify-client-via-url** + + Do not add client-identifying fields to the URL. By default, `"prod"`, + `"ver"`, and `"guid"` annotations are added to the upload URL as name-value + pairs `"product"`, `"version"`, and `"guid"`, respectively. Using this + option disables that behavior. + * **--no-periodic-tasks** Do not scan for new pending crash reports or prune the crash report database. @@ -245,17 +252,24 @@ parent process. This option is only valid on macOS. Use of this option is discouraged. It should not be used absent extraordinary circumstances. + * **--sanitization-information**=_SANITIZATION-INFORMATION-ADDRESS_ + + Provides sanitization settings in a SanitizationInformation struct at + _SANITIZATION-INFORMATION-ADDRESS_. This option requires + **--trace-parent-with-exception** and is only valid on Linux platforms. + + * **--shared-client-connection** + + Indicates that the file descriptor provided by **--initial-client-fd** is + shared among mulitple clients. Using a broker process is not supported for + clients using this option. This option is only valid on Linux platforms. + * **--trace-parent-with-exception**=_EXCEPTION-INFORMATION-ADDRESS_ Causes the handler process to trace its parent process and exit. The parent process should have an ExceptionInformation struct at - _EXCEPTION-INFORMATION-ADDRESS_. - - * **--initial-client-fd**=_FD_ - - Starts the excetion handler server with an initial ExceptionHandlerClient - connected on the socket _FD_. The server will exit when all connected client - sockets have been closed. + _EXCEPTION-INFORMATION-ADDRESS_. This option is only valid on Linux + platforms. * **--url**=_URL_
diff --git a/third_party/crashpad/crashpad/handler/handler_main.cc b/third_party/crashpad/crashpad/handler/handler_main.cc index e87d17b7..724168ee 100644 --- a/third_party/crashpad/crashpad/handler/handler_main.cc +++ b/third_party/crashpad/crashpad/handler/handler_main.cc
@@ -118,6 +118,9 @@ " Address_debug_critical_section\n" " use precreated data to register initial client\n" #endif // OS_WIN +#if defined(OS_ANDROID) || defined(OS_LINUX) +" --initial-client-fd=FD a socket connected to a client.\n" +#endif // OS_ANDROID || OS_LINUX #if defined(OS_MACOSX) " --mach-service=SERVICE register SERVICE with the bootstrap server\n" #endif // OS_MACOSX @@ -141,11 +144,13 @@ " reset the server's exception handler to default\n" #endif // OS_MACOSX #if defined(OS_LINUX) || defined(OS_ANDROID) +" --sanitization-information=SANITIZATION_INFORMATION_ADDRESS\n" +" the address of a SanitizationInformation struct.\n" +" --shared-client-connection the file descriptor provided by\n" +" --initial-client-fd is shared among multiple\n" +" clients\n" " --trace-parent-with-exception=EXCEPTION_INFORMATION_ADDRESS\n" " request a dump for the handler's parent process\n" -" --initial-client-fd=FD a socket connected to a client.\n" -" --sanitization_information=SANITIZATION_INFORMATION_ADDRESS\n" -" the address of a SanitizationInformation struct.\n" #endif // OS_LINUX || OS_ANDROID " --url=URL send crash reports to this Breakpad server URL,\n" " only if uploads are enabled for the database\n" @@ -168,8 +173,9 @@ bool reset_own_crash_exception_port_to_system_default; #elif defined(OS_LINUX) || defined(OS_ANDROID) VMAddress exception_information_address; - int initial_client_fd; VMAddress sanitization_information_address; + int initial_client_fd; + bool shared_client_connection; #elif defined(OS_WIN) std::string pipe_name; InitialClientData initial_client_data; @@ -530,6 +536,9 @@ #if defined(OS_WIN) kOptionInitialClientData, #endif // OS_WIN +#if defined(OS_ANDROID) || defined(OS_LINUX) + kOptionInitialClientFD, +#endif // OS_ANDROID || OS_LINUX #if defined(OS_MACOSX) kOptionMachService, #endif // OS_MACOSX @@ -548,9 +557,9 @@ kOptionResetOwnCrashExceptionPortToSystemDefault, #endif // OS_MACOSX #if defined(OS_LINUX) || defined(OS_ANDROID) - kOptionTraceParentWithException, - kOptionInitialClientFD, kOptionSanitizationInformation, + kOptionSharedClientConnection, + kOptionTraceParentWithException, #endif kOptionURL, @@ -571,6 +580,9 @@ nullptr, kOptionInitialClientData}, #endif // OS_MACOSX +#if defined(OS_ANDROID) || defined(OS_LINUX) + {"initial-client-fd", required_argument, nullptr, kOptionInitialClientFD}, +#endif // OS_ANDROID || OS_LINUX #if defined(OS_MACOSX) {"mach-service", required_argument, nullptr, kOptionMachService}, #endif // OS_MACOSX @@ -601,15 +613,18 @@ kOptionResetOwnCrashExceptionPortToSystemDefault}, #endif // OS_MACOSX #if defined(OS_LINUX) || defined(OS_ANDROID) - {"trace-parent-with-exception", - required_argument, - nullptr, - kOptionTraceParentWithException}, - {"initial-client-fd", required_argument, nullptr, kOptionInitialClientFD}, {"sanitization-information", required_argument, nullptr, kOptionSanitizationInformation}, + {"shared-client-connection", + no_argument, + nullptr, + kOptionSharedClientConnection}, + {"trace-parent-with-exception", + required_argument, + nullptr, + kOptionTraceParentWithException}, #endif // OS_LINUX || OS_ANDROID {"url", required_argument, nullptr, kOptionURL}, {"help", no_argument, nullptr, kOptionHelp}, @@ -622,14 +637,12 @@ options.handshake_fd = -1; #endif options.identify_client_via_url = true; +#if defined(OS_LINUX) || defined(OS_ANDROID) + options.initial_client_fd = kInvalidFileHandle; +#endif options.periodic_tasks = true; options.rate_limit = true; options.upload_gzip = true; -#if defined(OS_LINUX) || defined(OS_ANDROID) - options.exception_information_address = 0; - options.initial_client_fd = kInvalidFileHandle; - options.sanitization_information_address = 0; -#endif int opt; while ((opt = getopt_long(argc, argv, "", long_options, nullptr)) != -1) { @@ -670,6 +683,15 @@ break; } #endif // OS_WIN +#if defined(OS_ANDROID) || defined(OS_LINUX) + case kOptionInitialClientFD: { + if (!base::StringToInt(optarg, &options.initial_client_fd)) { + ToolSupport::UsageHint(me, "failed to parse --initial-client-fd"); + return ExitFailure(); + } + break; + } +#endif // OS_ANDROID || OS_LINUX case kOptionMetrics: { options.metrics_dir = base::FilePath( ToolSupport::CommandLineArgumentToFilePathStringType(optarg)); @@ -720,21 +742,6 @@ } #endif // OS_MACOSX #if defined(OS_LINUX) || defined(OS_ANDROID) - case kOptionTraceParentWithException: { - if (!StringToNumber(optarg, &options.exception_information_address)) { - ToolSupport::UsageHint( - me, "failed to parse --trace-parent-with-exception"); - return ExitFailure(); - } - break; - } - case kOptionInitialClientFD: { - if (!base::StringToInt(optarg, &options.initial_client_fd)) { - ToolSupport::UsageHint(me, "failed to parse --initial-client-fd"); - return ExitFailure(); - } - break; - } case kOptionSanitizationInformation: { if (!StringToNumber(optarg, &options.sanitization_information_address)) { @@ -744,6 +751,18 @@ } break; } + case kOptionSharedClientConnection: { + options.shared_client_connection = true; + break; + } + case kOptionTraceParentWithException: { + if (!StringToNumber(optarg, &options.exception_information_address)) { + ToolSupport::UsageHint( + me, "failed to parse --trace-parent-with-exception"); + return ExitFailure(); + } + break; + } #endif // OS_LINUX || OS_ANDROID case kOptionURL: { options.url = optarg; @@ -793,7 +812,7 @@ if (!options.exception_information_address && options.initial_client_fd == kInvalidFileHandle) { ToolSupport::UsageHint( - me, "--trace-parent-with-exception or --initial_client_fd is required"); + me, "--trace-parent-with-exception or --initial-client-fd is required"); return ExitFailure(); } if (options.sanitization_information_address && @@ -803,6 +822,12 @@ "--sanitization_information requires --trace-parent-with-exception"); return ExitFailure(); } + if (options.shared_client_connection && + options.initial_client_fd == kInvalidFileHandle) { + ToolSupport::UsageHint( + me, "--shared-client-connection requires --initial-client-fd"); + return ExitFailure(); + } #endif // OS_MACOSX if (options.database.empty()) { @@ -993,8 +1018,9 @@ } #elif defined(OS_LINUX) || defined(OS_ANDROID) if (options.initial_client_fd == kInvalidFileHandle || - !exception_handler_server.InitializeWithClient( - ScopedFileHandle(options.initial_client_fd))) { + !exception_handler_server.InitializeWithClient( + ScopedFileHandle(options.initial_client_fd), + options.shared_client_connection)) { return ExitFailure(); } #endif // OS_WIN
diff --git a/third_party/crashpad/crashpad/handler/linux/exception_handler_server.cc b/third_party/crashpad/crashpad/handler/linux/exception_handler_server.cc index 4cefd8b..ef03696a 100644 --- a/third_party/crashpad/crashpad/handler/linux/exception_handler_server.cc +++ b/third_party/crashpad/crashpad/handler/linux/exception_handler_server.cc
@@ -32,6 +32,8 @@ #include "build/build_config.h" #include "util/file/file_io.h" #include "util/file/filesystem.h" +#include "util/linux/proc_task_reader.h" +#include "util/linux/socket.h" #include "util/misc/as_underlying_type.h" namespace crashpad { @@ -122,20 +124,61 @@ return LoggingWriteFile(client_sock, &message, sizeof(message)); } +int tgkill(pid_t pid, pid_t tid, int signo) { + return syscall(SYS_tgkill, pid, tid, signo); +} + +void SendSIGCONT(pid_t pid, pid_t tid) { + if (tid > 0) { + if (tgkill(pid, tid, ExceptionHandlerProtocol::kDumpDoneSignal) != 0) { + PLOG(ERROR) << "tgkill"; + } + return; + } + + std::vector<pid_t> threads; + if (!ReadThreadIDs(pid, &threads)) { + return; + } + for (const auto& thread : threads) { + if (tgkill(pid, thread, ExceptionHandlerProtocol::kDumpDoneSignal) != 0) { + PLOG(ERROR) << "tgkill"; + } + } +} + +bool SendCredentials(int client_sock) { + ExceptionHandlerProtocol::ServerToClientMessage message = {}; + message.type = + ExceptionHandlerProtocol::ServerToClientMessage::kTypeCredentials; + return UnixCredentialSocket::SendMsg( + client_sock, &message, sizeof(message)) == 0; +} + class PtraceStrategyDeciderImpl : public PtraceStrategyDecider { public: PtraceStrategyDeciderImpl() : PtraceStrategyDecider() {} ~PtraceStrategyDeciderImpl() = default; - Strategy ChooseStrategy(int sock, const ucred& client_credentials) override { + Strategy ChooseStrategy(int sock, + bool multiple_clients, + const ucred& client_credentials) override { + if (client_credentials.pid <= 0) { + LOG(ERROR) << "invalid credentials"; + return Strategy::kNoPtrace; + } + switch (GetPtraceScope()) { case PtraceScope::kClassic: - if (getuid() == client_credentials.uid) { + if (getuid() == client_credentials.uid || HaveCapSysPtrace()) { return Strategy::kDirectPtrace; } - return TryForkingBroker(sock); + return multiple_clients ? Strategy::kNoPtrace : TryForkingBroker(sock); case PtraceScope::kRestricted: + if (multiple_clients) { + return Strategy::kDirectPtrace; + } if (!SendMessageToClient(sock, ExceptionHandlerProtocol:: ServerToClientMessage::kTypeSetPtracer)) { @@ -196,12 +239,6 @@ } // namespace -struct ExceptionHandlerServer::Event { - enum class Type { kShutdown, kClientMessage } type; - - ScopedFileHandle fd; -}; - ExceptionHandlerServer::ExceptionHandlerServer() : clients_(), shutdown_event_(), @@ -217,7 +254,8 @@ strategy_decider_ = std::move(decider); } -bool ExceptionHandlerServer::InitializeWithClient(ScopedFileHandle sock) { +bool ExceptionHandlerServer::InitializeWithClient(ScopedFileHandle sock, + bool multiple_clients) { INITIALIZATION_STATE_SET_INITIALIZING(initialized_); pollfd_.reset(epoll_create1(EPOLL_CLOEXEC)); @@ -245,7 +283,9 @@ return false; } - if (!InstallClientSocket(std::move(sock))) { + if (!InstallClientSocket(std::move(sock), + multiple_clients ? Event::Type::kSharedSocketMessage + : Event::Type::kClientMessage)) { return false; } @@ -287,8 +327,8 @@ } void ExceptionHandlerServer::HandleEvent(Event* event, uint32_t event_type) { - DCHECK_EQ(AsUnderlyingType(event->type), - AsUnderlyingType(Event::Type::kClientMessage)); + DCHECK_NE(AsUnderlyingType(event->type), + AsUnderlyingType(Event::Type::kShutdown)); if (event_type & EPOLLERR) { LogSocketError(event->fd.get()); @@ -312,7 +352,8 @@ return; } -bool ExceptionHandlerServer::InstallClientSocket(ScopedFileHandle socket) { +bool ExceptionHandlerServer::InstallClientSocket(ScopedFileHandle socket, + Event::Type type) { // The handler may not have permission to set SO_PASSCRED on the socket, but // it doesn't need to if the client has already set it. // https://bugs.chromium.org/p/crashpad/issues/detail?id=252 @@ -334,7 +375,7 @@ } auto event = std::make_unique<Event>(); - event->type = Event::Type::kClientMessage; + event->type = type; event->fd.reset(socket.release()); Event* eventp = event.get(); @@ -375,56 +416,23 @@ bool ExceptionHandlerServer::ReceiveClientMessage(Event* event) { ExceptionHandlerProtocol::ClientToServerMessage message; - iovec iov; - iov.iov_base = &message; - iov.iov_len = sizeof(message); - - msghdr msg; - msg.msg_name = nullptr; - msg.msg_namelen = 0; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - char cmsg_buf[CMSG_SPACE(sizeof(ucred))]; - msg.msg_control = cmsg_buf; - msg.msg_controllen = sizeof(cmsg_buf); - msg.msg_flags = 0; - - int res = HANDLE_EINTR(recvmsg(event->fd.get(), &msg, 0)); - if (res < 0) { - PLOG(ERROR) << "recvmsg"; - return false; - } - if (res == 0) { - // The client had an orderly shutdown. + ucred creds; + if (!UnixCredentialSocket::RecvMsg( + event->fd.get(), &message, sizeof(message), &creds)) { return false; } - if (msg.msg_name != nullptr || msg.msg_namelen != 0) { - LOG(ERROR) << "unexpected msg name"; - return false; - } + switch (message.type) { + case ExceptionHandlerProtocol::ClientToServerMessage::kTypeCheckCredentials: + return SendCredentials(event->fd.get()); - if (msg.msg_iovlen != 1) { - LOG(ERROR) << "unexpected iovlen"; - return false; - } - - if (msg.msg_iov[0].iov_len != - sizeof(ExceptionHandlerProtocol::ClientToServerMessage)) { - LOG(ERROR) << "unexpected message size " << msg.msg_iov[0].iov_len; - return false; - } - auto client_msg = - reinterpret_cast<ExceptionHandlerProtocol::ClientToServerMessage*>( - msg.msg_iov[0].iov_base); - - switch (client_msg->type) { - case ExceptionHandlerProtocol::ClientToServerMessage::kCrashDumpRequest: - return HandleCrashDumpRequest(msg, - client_msg->client_info, - client_msg->requesting_thread_stack_address, - event->fd.get()); + case ExceptionHandlerProtocol::ClientToServerMessage::kTypeCrashDumpRequest: + return HandleCrashDumpRequest( + creds, + message.client_info, + message.requesting_thread_stack_address, + event->fd.get(), + event->type == Event::Type::kSharedSocketMessage); } DCHECK(false); @@ -433,50 +441,46 @@ } bool ExceptionHandlerServer::HandleCrashDumpRequest( - const msghdr& msg, + const ucred& creds, const ExceptionHandlerProtocol::ClientInformation& client_info, VMAddress requesting_thread_stack_address, - int client_sock) { - cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - if (cmsg == nullptr) { - LOG(ERROR) << "missing credentials"; - return false; - } + int client_sock, + bool multiple_clients) { + pid_t client_process_id = creds.pid; + pid_t requesting_thread_id = -1; - if (cmsg->cmsg_level != SOL_SOCKET) { - LOG(ERROR) << "unexpected cmsg_level " << cmsg->cmsg_level; - return false; - } - - if (cmsg->cmsg_type != SCM_CREDENTIALS) { - LOG(ERROR) << "unexpected cmsg_type " << cmsg->cmsg_type; - return false; - } - - if (cmsg->cmsg_len != CMSG_LEN(sizeof(ucred))) { - LOG(ERROR) << "unexpected cmsg_len " << cmsg->cmsg_len; - return false; - } - - ucred* client_credentials = reinterpret_cast<ucred*>(CMSG_DATA(cmsg)); - pid_t client_process_id = client_credentials->pid; - - switch (strategy_decider_->ChooseStrategy(client_sock, *client_credentials)) { + switch ( + strategy_decider_->ChooseStrategy(client_sock, multiple_clients, creds)) { case PtraceStrategyDecider::Strategy::kError: + if (multiple_clients) { + SendSIGCONT(client_process_id, requesting_thread_id); + } return false; case PtraceStrategyDecider::Strategy::kNoPtrace: + if (multiple_clients) { + SendSIGCONT(client_process_id, requesting_thread_id); + return true; + } return SendMessageToClient( client_sock, ExceptionHandlerProtocol::ServerToClientMessage:: kTypeCrashDumpFailed); - case PtraceStrategyDecider::Strategy::kDirectPtrace: - delegate_->HandleException( - client_process_id, client_info, requesting_thread_stack_address); + case PtraceStrategyDecider::Strategy::kDirectPtrace: { + delegate_->HandleException(client_process_id, + client_info, + requesting_thread_stack_address, + &requesting_thread_id); + if (multiple_clients) { + SendSIGCONT(client_process_id, requesting_thread_id); + return true; + } break; + } case PtraceStrategyDecider::Strategy::kUseBroker: + DCHECK(!multiple_clients); delegate_->HandleExceptionWithBroker( client_process_id, client_info, client_sock); break;
diff --git a/third_party/crashpad/crashpad/handler/linux/exception_handler_server.h b/third_party/crashpad/crashpad/handler/linux/exception_handler_server.h index 1b73601c..9752195 100644 --- a/third_party/crashpad/crashpad/handler/linux/exception_handler_server.h +++ b/third_party/crashpad/crashpad/handler/linux/exception_handler_server.h
@@ -54,9 +54,12 @@ //! \brief Chooses an appropriate `ptrace` strategy. //! //! \param[in] sock A socket conncted to a ExceptionHandlerClient. + //! \param[in] multiple_clients `true` if the socket is connected to multiple + //! clients. The broker is not supported in this configuration. //! \param[in] client_credentials The credentials for the connected client. //! \return the chosen #Strategy. virtual Strategy ChooseStrategy(int sock, + bool multiple_clients, const ucred& client_credentials) = 0; protected: @@ -122,8 +125,11 @@ //! This method must be successfully called before Run(). //! //! \param[in] sock A socket on which to receive client requests. + //! \param[in] multiple_clients `true` if this socket is used by multiple + //! clients. Using a broker process is not supported in this + //! configuration. //! \return `true` on success. `false` on failure with a message logged. - bool InitializeWithClient(ScopedFileHandle sock); + bool InitializeWithClient(ScopedFileHandle sock, bool multiple_clients); //! \brief Runs the exception-handling server. //! @@ -143,17 +149,32 @@ void Stop(); private: - struct Event; + struct Event { + enum class Type { + // Used by Stop() to shutdown the server. + kShutdown, + + // A message from a client on a private socket connection. + kClientMessage, + + // A message from a client on a shared socket connection. + kSharedSocketMessage + }; + + Type type; + ScopedFileHandle fd; + }; void HandleEvent(Event* event, uint32_t event_type); - bool InstallClientSocket(ScopedFileHandle socket); + bool InstallClientSocket(ScopedFileHandle socket, Event::Type type); bool UninstallClientSocket(Event* event); bool ReceiveClientMessage(Event* event); bool HandleCrashDumpRequest( - const msghdr& msg, + const ucred& creds, const ExceptionHandlerProtocol::ClientInformation& client_info, VMAddress requesting_thread_stack_address, - int client_sock); + int client_sock, + bool multiple_clients); std::unordered_map<int, std::unique_ptr<Event>> clients_; std::unique_ptr<Event> shutdown_event_;
diff --git a/third_party/crashpad/crashpad/handler/linux/exception_handler_server_test.cc b/third_party/crashpad/crashpad/handler/linux/exception_handler_server_test.cc index 1c57fce..7ef007e0 100644 --- a/third_party/crashpad/crashpad/handler/linux/exception_handler_server_test.cc +++ b/third_party/crashpad/crashpad/handler/linux/exception_handler_server_test.cc
@@ -18,6 +18,7 @@ #include <unistd.h> #include "base/logging.h" +#include "build/build_config.h" #include "gtest/gtest.h" #include "snapshot/linux/process_snapshot_linux.h" #include "test/errors.h" @@ -30,6 +31,10 @@ #include "util/synchronization/semaphore.h" #include "util/thread/thread.h" +#if defined(OS_ANDROID) +#include <android/api-level.h> +#endif + namespace crashpad { namespace test { namespace { @@ -164,7 +169,9 @@ ~MockPtraceStrategyDecider() {} - Strategy ChooseStrategy(int sock, const ucred& client_credentials) override { + Strategy ChooseStrategy(int sock, + bool multiple_clients, + const ucred& client_credentials) override { if (strategy_ == Strategy::kUseBroker) { ExceptionHandlerProtocol::ServerToClientMessage message = {}; message.type = @@ -194,13 +201,14 @@ DISALLOW_COPY_AND_ASSIGN(MockPtraceStrategyDecider); }; -class ExceptionHandlerServerTest : public testing::Test { +class ExceptionHandlerServerTest : public testing::TestWithParam<bool> { public: ExceptionHandlerServerTest() : server_(), delegate_(), server_thread_(&server_, &delegate_), - sock_to_handler_() {} + sock_to_handler_(), + use_multi_client_socket_(GetParam()) {} ~ExceptionHandlerServerTest() = default; @@ -243,7 +251,6 @@ ExceptionHandlerProtocol::ClientInformation info; info.exception_information_address = 42; - ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &info, sizeof(info))); // If the current ptrace_scope is restricted, the broker needs to be set @@ -251,7 +258,8 @@ // ptracer allows the broker to inherit this condition. ScopedPrSetPtracer set_ptracer(getpid(), /* may_log= */ true); - ExceptionHandlerClient client(server_test_->SockToHandler()); + ExceptionHandlerClient client(server_test_->SockToHandler(), + server_test_->use_multi_client_socket_); ASSERT_EQ(client.RequestCrashDump(info), 0); } @@ -274,6 +282,8 @@ test.Run(); } + bool UsingMultiClientSocket() const { return use_multi_client_socket_; } + protected: void SetUp() override { int socks[2]; @@ -281,7 +291,8 @@ sock_to_handler_.reset(socks[0]); sock_to_client_ = socks[1]; - ASSERT_TRUE(server_.InitializeWithClient(ScopedFileHandle(socks[1]))); + ASSERT_TRUE(server_.InitializeWithClient(ScopedFileHandle(socks[1]), + use_multi_client_socket_)); } private: @@ -290,36 +301,37 @@ RunServerThread server_thread_; ScopedFileHandle sock_to_handler_; int sock_to_client_; + bool use_multi_client_socket_; DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServerTest); }; -TEST_F(ExceptionHandlerServerTest, ShutdownWithNoClients) { +TEST_P(ExceptionHandlerServerTest, ShutdownWithNoClients) { ServerThread()->Start(); Hangup(); ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0)); } -TEST_F(ExceptionHandlerServerTest, StopWithClients) { +TEST_P(ExceptionHandlerServerTest, StopWithClients) { ServerThread()->Start(); Server()->Stop(); ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0)); } -TEST_F(ExceptionHandlerServerTest, StopBeforeRun) { +TEST_P(ExceptionHandlerServerTest, StopBeforeRun) { Server()->Stop(); ServerThread()->Start(); ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0)); } -TEST_F(ExceptionHandlerServerTest, MultipleStops) { +TEST_P(ExceptionHandlerServerTest, MultipleStops) { ServerThread()->Start(); Server()->Stop(); Server()->Stop(); ASSERT_TRUE(ServerThread()->JoinWithTimeout(5.0)); } -TEST_F(ExceptionHandlerServerTest, RequestCrashDumpDefault) { +TEST_P(ExceptionHandlerServerTest, RequestCrashDumpDefault) { ScopedStopServerAndJoinThread stop_server(Server(), ServerThread()); ServerThread()->Start(); @@ -327,25 +339,44 @@ test.Run(); } -TEST_F(ExceptionHandlerServerTest, RequestCrashDumpNoPtrace) { +TEST_P(ExceptionHandlerServerTest, RequestCrashDumpNoPtrace) { ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kNoPtrace, false); } -TEST_F(ExceptionHandlerServerTest, RequestCrashDumpForkBroker) { +TEST_P(ExceptionHandlerServerTest, RequestCrashDumpForkBroker) { + if (UsingMultiClientSocket()) { + // The broker is not supported with multiple clients connected on a single + // socket. + return; + } ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kUseBroker, true); } -TEST_F(ExceptionHandlerServerTest, RequestCrashDumpDirectPtrace) { +TEST_P(ExceptionHandlerServerTest, RequestCrashDumpDirectPtrace) { ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kDirectPtrace, true); } -TEST_F(ExceptionHandlerServerTest, RequestCrashDumpError) { +TEST_P(ExceptionHandlerServerTest, RequestCrashDumpError) { ExpectCrashDumpUsingStrategy(PtraceStrategyDecider::Strategy::kError, false); } +INSTANTIATE_TEST_SUITE_P(ExceptionHandlerServerTestSuite, + ExceptionHandlerServerTest, +#if defined(OS_ANDROID) && __ANDROID_API__ < 23 + // TODO(jperaza): Using a multi-client socket is not + // supported on Android until an lss sigtimedwait() + // wrapper is available to use in + // ExceptionHandlerClient::SignalCrashDump(). + // https://crbug.com/crashpad/265 + testing::Values(false) +#else + testing::Bool() +#endif +); + } // namespace } // namespace test } // namespace crashpad
diff --git a/third_party/crashpad/crashpad/snapshot/fuchsia/process_snapshot_fuchsia.cc b/third_party/crashpad/crashpad/snapshot/fuchsia/process_snapshot_fuchsia.cc index 59e5d48..9ae414e 100644 --- a/third_party/crashpad/crashpad/snapshot/fuchsia/process_snapshot_fuchsia.cc +++ b/third_party/crashpad/crashpad/snapshot/fuchsia/process_snapshot_fuchsia.cc
@@ -98,12 +98,12 @@ *options = local_options; } -pid_t ProcessSnapshotFuchsia::ProcessID() const { +crashpad::ProcessID ProcessSnapshotFuchsia::ProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return GetKoidForHandle(*zx::process::self()); } -pid_t ProcessSnapshotFuchsia::ParentProcessID() const { +crashpad::ProcessID ProcessSnapshotFuchsia::ParentProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); NOTREACHED(); // TODO(scottmg): https://crashpad.chromium.org/bug/196 return 0;
diff --git a/third_party/crashpad/crashpad/snapshot/fuchsia/process_snapshot_fuchsia.h b/third_party/crashpad/crashpad/snapshot/fuchsia/process_snapshot_fuchsia.h index 35538fa..6af2ebe 100644 --- a/third_party/crashpad/crashpad/snapshot/fuchsia/process_snapshot_fuchsia.h +++ b/third_party/crashpad/crashpad/snapshot/fuchsia/process_snapshot_fuchsia.h
@@ -35,6 +35,7 @@ #include "snapshot/process_snapshot.h" #include "snapshot/unloaded_module_snapshot.h" #include "util/misc/initialization_state_dcheck.h" +#include "util/process/process_id.h" #include "util/process/process_memory_range.h" namespace crashpad { @@ -105,8 +106,8 @@ } // ProcessSnapshot: - pid_t ProcessID() const override; - pid_t ParentProcessID() const override; + crashpad::ProcessID ProcessID() const override; + crashpad::ProcessID ParentProcessID() const override; void SnapshotTime(timeval* snapshot_time) const override; void ProcessStartTime(timeval* start_time) const override; void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override;
diff --git a/third_party/crashpad/crashpad/snapshot/linux/process_snapshot_linux.cc b/third_party/crashpad/crashpad/snapshot/linux/process_snapshot_linux.cc index 3442e4a..bd95ac7 100644 --- a/third_party/crashpad/crashpad/snapshot/linux/process_snapshot_linux.cc +++ b/third_party/crashpad/crashpad/snapshot/linux/process_snapshot_linux.cc
@@ -155,12 +155,12 @@ *options = local_options; } -pid_t ProcessSnapshotLinux::ProcessID() const { +crashpad::ProcessID ProcessSnapshotLinux::ProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return process_reader_.ProcessID(); } -pid_t ProcessSnapshotLinux::ParentProcessID() const { +crashpad::ProcessID ProcessSnapshotLinux::ParentProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return process_reader_.ParentProcessID(); }
diff --git a/third_party/crashpad/crashpad/snapshot/linux/process_snapshot_linux.h b/third_party/crashpad/crashpad/snapshot/linux/process_snapshot_linux.h index c96384d..705cb5f 100644 --- a/third_party/crashpad/crashpad/snapshot/linux/process_snapshot_linux.h +++ b/third_party/crashpad/crashpad/snapshot/linux/process_snapshot_linux.h
@@ -39,6 +39,7 @@ #include "util/linux/ptrace_connection.h" #include "util/misc/initialization_state_dcheck.h" #include "util/misc/uuid.h" +#include "util/process/process_id.h" #include "util/process/process_memory_range.h" namespace crashpad { @@ -106,8 +107,8 @@ // ProcessSnapshot: - pid_t ProcessID() const override; - pid_t ParentProcessID() const override; + crashpad::ProcessID ProcessID() const override; + crashpad::ProcessID ParentProcessID() const override; void SnapshotTime(timeval* snapshot_time) const override; void ProcessStartTime(timeval* start_time) const override; void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override;
diff --git a/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.cc b/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.cc index 1242b49c..63f653d 100644 --- a/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.cc +++ b/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.cc
@@ -58,7 +58,7 @@ arch_(CPUArchitecture::kCPUArchitectureUnknown), annotations_simple_map_(), file_reader_(nullptr), - process_id_(static_cast<pid_t>(-1)), + process_id_(kInvalidProcessID), initialized_() {} ProcessSnapshotMinidump::~ProcessSnapshotMinidump() {} @@ -120,12 +120,12 @@ return true; } -pid_t ProcessSnapshotMinidump::ProcessID() const { +crashpad::ProcessID ProcessSnapshotMinidump::ProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return process_id_; } -pid_t ProcessSnapshotMinidump::ParentProcessID() const { +crashpad::ProcessID ProcessSnapshotMinidump::ParentProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); NOTREACHED(); // https://crashpad.chromium.org/bug/10 return 0;
diff --git a/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.h b/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.h index 5713aad8..1deac87 100644 --- a/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.h +++ b/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump.h
@@ -42,6 +42,7 @@ #include "util/file/file_reader.h" #include "util/misc/initialization_state_dcheck.h" #include "util/misc/uuid.h" +#include "util/process/process_id.h" namespace crashpad { @@ -66,8 +67,8 @@ // ProcessSnapshot: - pid_t ProcessID() const override; - pid_t ParentProcessID() const override; + crashpad::ProcessID ProcessID() const override; + crashpad::ProcessID ParentProcessID() const override; void SnapshotTime(timeval* snapshot_time) const override; void ProcessStartTime(timeval* start_time) const override; void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override; @@ -151,7 +152,7 @@ std::map<std::string, std::string> annotations_simple_map_; std::string full_version_; FileReaderInterface* file_reader_; // weak - pid_t process_id_; + crashpad::ProcessID process_id_; InitializationStateDcheck initialized_; DISALLOW_COPY_AND_ASSIGN(ProcessSnapshotMinidump);
diff --git a/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump_test.cc b/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump_test.cc index a471fbc..54ab6eb3 100644 --- a/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump_test.cc +++ b/third_party/crashpad/crashpad/snapshot/minidump/process_snapshot_minidump_test.cc
@@ -544,7 +544,7 @@ MINIDUMP_HEADER header = {}; ASSERT_TRUE(string_file.Write(&header, sizeof(header))); - static const pid_t kTestProcessId = 42; + static const crashpad::ProcessID kTestProcessId = 42; MINIDUMP_MISC_INFO misc_info = {}; misc_info.SizeOfInfo = sizeof(misc_info); misc_info.Flags1 = MINIDUMP_MISC1_PROCESS_ID;
diff --git a/third_party/crashpad/crashpad/snapshot/process_snapshot.h b/third_party/crashpad/crashpad/snapshot/process_snapshot.h index f5859d22..08d9f2bc 100644 --- a/third_party/crashpad/crashpad/snapshot/process_snapshot.h +++ b/third_party/crashpad/crashpad/snapshot/process_snapshot.h
@@ -24,6 +24,7 @@ #include "snapshot/handle_snapshot.h" #include "util/misc/uuid.h" +#include "util/process/process_id.h" namespace crashpad { @@ -50,10 +51,10 @@ virtual ~ProcessSnapshot() {} //! \brief Returns the snapshot process’ process ID. - virtual pid_t ProcessID() const = 0; + virtual crashpad::ProcessID ProcessID() const = 0; //! \brief Returns the snapshot process’ parent process’ process ID. - virtual pid_t ParentProcessID() const = 0; + virtual crashpad::ProcessID ParentProcessID() const = 0; //! \brief Returns the time that the snapshot was taken in \a snapshot_time. //!
diff --git a/third_party/crashpad/crashpad/snapshot/sanitized/process_snapshot_sanitized.cc b/third_party/crashpad/crashpad/snapshot/sanitized/process_snapshot_sanitized.cc index 20807b9..c8d3f67 100644 --- a/third_party/crashpad/crashpad/snapshot/sanitized/process_snapshot_sanitized.cc +++ b/third_party/crashpad/crashpad/snapshot/sanitized/process_snapshot_sanitized.cc
@@ -161,12 +161,12 @@ return true; } -pid_t ProcessSnapshotSanitized::ProcessID() const { +crashpad::ProcessID ProcessSnapshotSanitized::ProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return snapshot_->ProcessID(); } -pid_t ProcessSnapshotSanitized::ParentProcessID() const { +crashpad::ProcessID ProcessSnapshotSanitized::ParentProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return snapshot_->ParentProcessID(); }
diff --git a/third_party/crashpad/crashpad/snapshot/sanitized/process_snapshot_sanitized.h b/third_party/crashpad/crashpad/snapshot/sanitized/process_snapshot_sanitized.h index b5f0c406..d11c863 100644 --- a/third_party/crashpad/crashpad/snapshot/sanitized/process_snapshot_sanitized.h +++ b/third_party/crashpad/crashpad/snapshot/sanitized/process_snapshot_sanitized.h
@@ -29,6 +29,7 @@ #include "util/misc/address_types.h" #include "util/misc/initialization_state_dcheck.h" #include "util/misc/range_set.h" +#include "util/process/process_id.h" namespace crashpad { @@ -66,8 +67,8 @@ // ProcessSnapshot: - pid_t ProcessID() const override; - pid_t ParentProcessID() const override; + crashpad::ProcessID ProcessID() const override; + crashpad::ProcessID ParentProcessID() const override; void SnapshotTime(timeval* snapshot_time) const override; void ProcessStartTime(timeval* start_time) const override; void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override;
diff --git a/third_party/crashpad/crashpad/snapshot/test/test_process_snapshot.cc b/third_party/crashpad/crashpad/snapshot/test/test_process_snapshot.cc index 1a31370..42a049a 100644 --- a/third_party/crashpad/crashpad/snapshot/test/test_process_snapshot.cc +++ b/third_party/crashpad/crashpad/snapshot/test/test_process_snapshot.cc
@@ -42,11 +42,11 @@ TestProcessSnapshot::~TestProcessSnapshot() { } -pid_t TestProcessSnapshot::ProcessID() const { +crashpad::ProcessID TestProcessSnapshot::ProcessID() const { return process_id_; } -pid_t TestProcessSnapshot::ParentProcessID() const { +crashpad::ProcessID TestProcessSnapshot::ParentProcessID() const { return parent_process_id_; }
diff --git a/third_party/crashpad/crashpad/snapshot/test/test_process_snapshot.h b/third_party/crashpad/crashpad/snapshot/test/test_process_snapshot.h index bfa26ab..5495cba1 100644 --- a/third_party/crashpad/crashpad/snapshot/test/test_process_snapshot.h +++ b/third_party/crashpad/crashpad/snapshot/test/test_process_snapshot.h
@@ -35,6 +35,7 @@ #include "snapshot/thread_snapshot.h" #include "snapshot/unloaded_module_snapshot.h" #include "util/misc/uuid.h" +#include "util/process/process_id.h" #include "util/process/process_memory.h" namespace crashpad { @@ -47,8 +48,10 @@ TestProcessSnapshot(); ~TestProcessSnapshot() override; - void SetProcessID(pid_t process_id) { process_id_ = process_id; } - void SetParentProcessID(pid_t parent_process_id) { + void SetProcessID(crashpad::ProcessID process_id) { + process_id_ = process_id; + } + void SetParentProcessID(crashpad::ProcessID parent_process_id) { parent_process_id_ = parent_process_id; } void SetSnapshotTime(const timeval& snapshot_time) { @@ -146,8 +149,8 @@ // ProcessSnapshot: - pid_t ProcessID() const override; - pid_t ParentProcessID() const override; + crashpad::ProcessID ProcessID() const override; + crashpad::ProcessID ParentProcessID() const override; void SnapshotTime(timeval* snapshot_time) const override; void ProcessStartTime(timeval* start_time) const override; void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override; @@ -166,8 +169,8 @@ const ProcessMemory* Memory() const override; private: - pid_t process_id_; - pid_t parent_process_id_; + crashpad::ProcessID process_id_; + crashpad::ProcessID parent_process_id_; timeval snapshot_time_; timeval process_start_time_; timeval process_cpu_user_time_;
diff --git a/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win.cc b/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win.cc index f91ef29..d2eef49a 100644 --- a/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win.cc +++ b/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win.cc
@@ -123,12 +123,12 @@ *options = options_; } -pid_t ProcessSnapshotWin::ProcessID() const { +crashpad::ProcessID ProcessSnapshotWin::ProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return process_reader_.GetProcessInfo().ProcessID(); } -pid_t ProcessSnapshotWin::ParentProcessID() const { +crashpad::ProcessID ProcessSnapshotWin::ParentProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return process_reader_.GetProcessInfo().ParentProcessID(); }
diff --git a/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win.h b/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win.h index 092aaf16..4acfe3984 100644 --- a/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win.h +++ b/third_party/crashpad/crashpad/snapshot/win/process_snapshot_win.h
@@ -44,6 +44,7 @@ #include "snapshot/win/thread_snapshot_win.h" #include "util/misc/initialization_state_dcheck.h" #include "util/misc/uuid.h" +#include "util/process/process_id.h" #include "util/win/address_types.h" #include "util/win/process_structs.h" @@ -112,8 +113,8 @@ // ProcessSnapshot: - pid_t ProcessID() const override; - pid_t ParentProcessID() const override; + crashpad::ProcessID ProcessID() const override; + crashpad::ProcessID ParentProcessID() const override; void SnapshotTime(timeval* snapshot_time) const override; void ProcessStartTime(timeval* start_time) const override; void ProcessCPUTimes(timeval* user_time, timeval* system_time) const override;
diff --git a/third_party/crashpad/crashpad/test/win/win_multiprocess_with_temp_dir.cc b/third_party/crashpad/crashpad/test/win/win_multiprocess_with_temp_dir.cc index b61ebf62..ca1e01a 100644 --- a/third_party/crashpad/crashpad/test/win/win_multiprocess_with_temp_dir.cc +++ b/third_party/crashpad/crashpad/test/win/win_multiprocess_with_temp_dir.cc
@@ -17,6 +17,7 @@ #include <tlhelp32.h> #include "test/errors.h" +#include "util/process/process_id.h" #include "util/win/process_info.h" namespace crashpad { @@ -28,20 +29,20 @@ // Returns the process IDs of all processes that have |parent_pid| as // parent process ID. -std::vector<pid_t> GetPotentialChildProcessesOf(pid_t parent_pid) { +std::vector<ProcessID> GetPotentialChildProcessesOf(ProcessID parent_pid) { ScopedFileHANDLE snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)); if (!snapshot.is_valid()) { ADD_FAILURE() << ErrorMessage("CreateToolhelp32Snapshot"); - return std::vector<pid_t>(); + return std::vector<ProcessID>(); } PROCESSENTRY32 entry = {sizeof(entry)}; if (!Process32First(snapshot.get(), &entry)) { ADD_FAILURE() << ErrorMessage("Process32First"); - return std::vector<pid_t>(); + return std::vector<ProcessID>(); } - std::vector<pid_t> child_pids; + std::vector<ProcessID> child_pids; do { if (entry.th32ParentProcessID == parent_pid) child_pids.push_back(entry.th32ProcessID); @@ -68,11 +69,11 @@ // not their offspring. For this to work without race, |parent| has to be // suspended or have exited. void WaitForAllChildProcessesOf(HANDLE parent) { - pid_t parent_pid = GetProcessId(parent); - std::vector<pid_t> child_pids = GetPotentialChildProcessesOf(parent_pid); + ProcessID parent_pid = GetProcessId(parent); + std::vector<ProcessID> child_pids = GetPotentialChildProcessesOf(parent_pid); ULARGE_INTEGER parent_creationtime = GetProcessCreationTime(parent); - for (pid_t child_pid : child_pids) { + for (ProcessID child_pid : child_pids) { // Try and open the process. This may fail for reasons such as: // 1. The process isn't |parent|'s child process, but rather a // higher-privilege sub-process of an earlier process that had
diff --git a/third_party/crashpad/crashpad/tools/generate_dump.cc b/third_party/crashpad/crashpad/tools/generate_dump.cc index 35a4e05..76bb86e 100644 --- a/third_party/crashpad/crashpad/tools/generate_dump.cc +++ b/third_party/crashpad/crashpad/tools/generate_dump.cc
@@ -26,6 +26,7 @@ #include "minidump/minidump_file_writer.h" #include "tools/tool_support.h" #include "util/file/file_writer.h" +#include "util/process/process_id.h" #include "util/stdlib/string_number_conversion.h" #if defined(OS_POSIX) @@ -92,7 +93,7 @@ struct { std::string dump_path; - pid_t pid; + ProcessID pid; bool suspend; } options = {}; options.suspend = true; @@ -175,7 +176,8 @@ #endif // OS_MACOSX if (options.dump_path.empty()) { - options.dump_path = base::StringPrintf("minidump.%d", options.pid); + options.dump_path = base::StringPrintf("minidump.%" PRI_PROCESS_ID, + options.pid); } {
diff --git a/third_party/crashpad/crashpad/util/BUILD.gn b/third_party/crashpad/crashpad/util/BUILD.gn index f571b86..620ae25 100644 --- a/third_party/crashpad/crashpad/util/BUILD.gn +++ b/third_party/crashpad/crashpad/util/BUILD.gn
@@ -137,6 +137,7 @@ "numeric/in_range_cast.h", "numeric/int128.h", "numeric/safe_assignment.h", + "process/process_id.h", "process/process_memory.cc", "process/process_memory.h", "process/process_memory_native.h", @@ -294,6 +295,8 @@ "linux/memory_map.h", "linux/proc_stat_reader.cc", "linux/proc_stat_reader.h", + "linux/proc_task_reader.cc", + "linux/proc_task_reader.h", "linux/ptrace_broker.cc", "linux/ptrace_broker.h", "linux/ptrace_client.cc", @@ -307,6 +310,8 @@ "linux/scoped_pr_set_ptracer.h", "linux/scoped_ptrace_attach.cc", "linux/scoped_ptrace_attach.h", + "linux/socket.cc", + "linux/socket.h", "linux/thread_info.cc", "linux/thread_info.h", "linux/traits.h", @@ -469,6 +474,10 @@ if (crashpad_is_fuchsia) { public_deps += [ "../third_party/fuchsia" ] } + + if (crashpad_is_android || crashpad_is_linux) { + deps += [ "../third_party/lss" ] + } } if (crashpad_use_boringssl_for_http_transport_socket) { @@ -619,9 +628,11 @@ "linux/auxiliary_vector_test.cc", "linux/memory_map_test.cc", "linux/proc_stat_reader_test.cc", + "linux/proc_task_reader_test.cc", "linux/ptrace_broker_test.cc", "linux/ptracer_test.cc", "linux/scoped_ptrace_attach_test.cc", + "linux/socket_test.cc", "misc/capture_context_test_util_linux.cc", ] } @@ -667,6 +678,10 @@ "../third_party/zlib", ] + if (crashpad_is_android || crashpad_is_linux) { + deps += [ "../third_party/lss" ] + } + if (!crashpad_is_android) { data_deps = [ ":http_transport_test_server",
diff --git a/third_party/crashpad/crashpad/util/linux/direct_ptrace_connection.cc b/third_party/crashpad/crashpad/util/linux/direct_ptrace_connection.cc index e757d8f..8b55205 100644 --- a/third_party/crashpad/crashpad/util/linux/direct_ptrace_connection.cc +++ b/third_party/crashpad/crashpad/util/linux/direct_ptrace_connection.cc
@@ -14,16 +14,10 @@ #include "util/linux/direct_ptrace_connection.h" -#include <stdio.h> - #include <utility> -#include "base/logging.h" -#include "base/stl_util.h" -#include "base/strings/string_number_conversions.h" -#include "util/file/directory_reader.h" #include "util/file/file_io.h" -#include "util/misc/as_underlying_type.h" +#include "util/linux/proc_task_reader.h" namespace crashpad { @@ -90,33 +84,7 @@ bool DirectPtraceConnection::Threads(std::vector<pid_t>* threads) { INITIALIZATION_STATE_DCHECK_VALID(initialized_); - DCHECK(threads->empty()); - - char path[32]; - snprintf(path, base::size(path), "/proc/%d/task", pid_); - DirectoryReader reader; - if (!reader.Open(base::FilePath(path))) { - return false; - } - - std::vector<pid_t> local_threads; - base::FilePath tid_str; - DirectoryReader::Result result; - while ((result = reader.NextFile(&tid_str)) == - DirectoryReader::Result::kSuccess) { - pid_t tid; - if (!base::StringToInt(tid_str.value(), &tid)) { - LOG(ERROR) << "format error"; - continue; - } - - local_threads.push_back(tid); - } - DCHECK_EQ(AsUnderlyingType(result), - AsUnderlyingType(DirectoryReader::Result::kNoMoreFiles)); - - threads->swap(local_threads); - return true; + return ReadThreadIDs(pid_, threads); } } // namespace crashpad
diff --git a/third_party/crashpad/crashpad/util/linux/exception_handler_client.cc b/third_party/crashpad/crashpad/util/linux/exception_handler_client.cc index 60970695..64ae006 100644 --- a/third_party/crashpad/crashpad/util/linux/exception_handler_client.cc +++ b/third_party/crashpad/crashpad/util/linux/exception_handler_client.cc
@@ -15,8 +15,8 @@ #include "util/linux/exception_handler_client.h" #include <errno.h> +#include <signal.h> #include <sys/prctl.h> -#include <sys/socket.h> #include <sys/wait.h> #include <unistd.h> @@ -25,20 +25,71 @@ #include "build/build_config.h" #include "util/file/file_io.h" #include "util/linux/ptrace_broker.h" +#include "util/linux/socket.h" #include "util/misc/from_pointer_cast.h" #include "util/posix/signals.h" +#if defined(OS_ANDROID) +#include <android/api-level.h> +#endif + namespace crashpad { -ExceptionHandlerClient::ExceptionHandlerClient(int sock) - : server_sock_(sock), ptracer_(-1), can_set_ptracer_(true) {} +namespace { + +class ScopedSigprocmaskRestore { + public: + explicit ScopedSigprocmaskRestore(const sigset_t& set_to_block) + : orig_mask_(), mask_is_set_(false) { + mask_is_set_ = sigprocmask(SIG_BLOCK, &set_to_block, &orig_mask_) == 0; + DPLOG_IF(ERROR, !mask_is_set_) << "sigprocmask"; + } + + ~ScopedSigprocmaskRestore() { + if (mask_is_set_ && sigprocmask(SIG_SETMASK, &orig_mask_, nullptr) != 0) { + DPLOG(ERROR) << "sigprocmask"; + } + } + + private: + sigset_t orig_mask_; + bool mask_is_set_; + + DISALLOW_COPY_AND_ASSIGN(ScopedSigprocmaskRestore); +}; + +} // namespace + +ExceptionHandlerClient::ExceptionHandlerClient(int sock, bool multiple_clients) + : server_sock_(sock), + ptracer_(-1), + can_set_ptracer_(true), + multiple_clients_(multiple_clients) {} ExceptionHandlerClient::~ExceptionHandlerClient() = default; +bool ExceptionHandlerClient::GetHandlerCredentials(ucred* creds) { + ExceptionHandlerProtocol::ClientToServerMessage message = {}; + message.type = + ExceptionHandlerProtocol::ClientToServerMessage::kTypeCheckCredentials; + if (UnixCredentialSocket::SendMsg(server_sock_, &message, sizeof(message)) != + 0) { + return false; + } + + ExceptionHandlerProtocol::ServerToClientMessage response; + return UnixCredentialSocket::RecvMsg( + server_sock_, &response, sizeof(response), creds); +} + int ExceptionHandlerClient::RequestCrashDump( const ExceptionHandlerProtocol::ClientInformation& info) { VMAddress sp = FromPointerCast<VMAddress>(&sp); + if (multiple_clients_) { + return SignalCrashDump(info, sp); + } + int status = SendCrashDumpRequest(info, sp); if (status != 0) { return status; @@ -65,46 +116,47 @@ can_set_ptracer_ = can_set_ptracer; } +int ExceptionHandlerClient::SignalCrashDump( + const ExceptionHandlerProtocol::ClientInformation& info, + VMAddress stack_pointer) { + // TODO(jperaza): Use lss for system calls when sys_sigtimedwait() exists. + // https://crbug.com/crashpad/265 + sigset_t dump_done_sigset; + sigemptyset(&dump_done_sigset); + sigaddset(&dump_done_sigset, ExceptionHandlerProtocol::kDumpDoneSignal); + ScopedSigprocmaskRestore scoped_block(dump_done_sigset); + + int status = SendCrashDumpRequest(info, stack_pointer); + if (status != 0) { + return status; + } + +#if defined(OS_ANDROID) && __ANDROID_API__ < 23 + // sigtimedwait() wrappers aren't available on Android until API 23 but this + // can use the lss wrapper when it's available. + NOTREACHED(); +#else + siginfo_t siginfo = {}; + timespec timeout; + timeout.tv_sec = 5; + timeout.tv_nsec = 0; + if (HANDLE_EINTR(sigtimedwait(&dump_done_sigset, &siginfo, &timeout)) < 0) { + return errno; + } +#endif + + return 0; +} + int ExceptionHandlerClient::SendCrashDumpRequest( const ExceptionHandlerProtocol::ClientInformation& info, VMAddress stack_pointer) { ExceptionHandlerProtocol::ClientToServerMessage message; message.type = - ExceptionHandlerProtocol::ClientToServerMessage::kCrashDumpRequest; + ExceptionHandlerProtocol::ClientToServerMessage::kTypeCrashDumpRequest; message.requesting_thread_stack_address = stack_pointer; message.client_info = info; - - iovec iov; - iov.iov_base = &message; - iov.iov_len = sizeof(message); - - msghdr msg; - msg.msg_name = nullptr; - msg.msg_namelen = 0; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ucred creds; - creds.pid = getpid(); - creds.uid = geteuid(); - creds.gid = getegid(); - - char cmsg_buf[CMSG_SPACE(sizeof(creds))]; - msg.msg_control = cmsg_buf; - msg.msg_controllen = sizeof(cmsg_buf); - - cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_CREDENTIALS; - cmsg->cmsg_len = CMSG_LEN(sizeof(creds)); - *reinterpret_cast<ucred*>(CMSG_DATA(cmsg)) = creds; - - if (HANDLE_EINTR(sendmsg(server_sock_, &msg, MSG_NOSIGNAL)) < 0) { - PLOG(ERROR) << "sendmsg"; - return errno; - } - - return 0; + return UnixCredentialSocket::SendMsg(server_sock_, &message, sizeof(message)); } int ExceptionHandlerClient::WaitForCrashDumpComplete() { @@ -159,6 +211,10 @@ continue; } + case ExceptionHandlerProtocol::ServerToClientMessage::kTypeCredentials: + DCHECK(false); + continue; + case ExceptionHandlerProtocol::ServerToClientMessage:: kTypeCrashDumpComplete: case ExceptionHandlerProtocol::ServerToClientMessage::
diff --git a/third_party/crashpad/crashpad/util/linux/exception_handler_client.h b/third_party/crashpad/crashpad/util/linux/exception_handler_client.h index 6492a12..4e10fa6 100644 --- a/third_party/crashpad/crashpad/util/linux/exception_handler_client.h +++ b/third_party/crashpad/crashpad/util/linux/exception_handler_client.h
@@ -15,6 +15,7 @@ #ifndef CRASHPAD_UTIL_LINUX_EXCEPTION_HANDLER_CLIENT_H_ #define CRASHPAD_UTIL_LINUX_EXCEPTION_HANDLER_CLIENT_H_ +#include <sys/socket.h> #include <sys/types.h> #include "base/macros.h" @@ -28,10 +29,23 @@ //! \brief Constructs this object. //! //! \param[in] sock A socket connected to an ExceptionHandlerServer. - explicit ExceptionHandlerClient(int sock); + //! \param[in] multiple_clients `true` if this socket may be used by multiple + //! clients. + ExceptionHandlerClient(int sock, bool multiple_clients); ~ExceptionHandlerClient(); + //! \brief Communicates with the handler to determine its credentials. + //! + //! If using a multi-client socket, this method should be called before + //! sharing the client socket end, or the handler's response may not be + //! received. + //! + //! \param[out] creds The handler process' credentials, valid if this method + //! returns `true`. + //! \return `true` on success. Otherwise, `false` with a message logged. + bool GetHandlerCredentials(ucred* creds); + //! \brief Request a crash dump from the ExceptionHandlerServer. //! //! This method blocks until the crash dump is complete. @@ -56,11 +70,14 @@ int SendCrashDumpRequest( const ExceptionHandlerProtocol::ClientInformation& info, VMAddress stack_pointer); + int SignalCrashDump(const ExceptionHandlerProtocol::ClientInformation& info, + VMAddress stack_pointer); int WaitForCrashDumpComplete(); int server_sock_; pid_t ptracer_; bool can_set_ptracer_; + bool multiple_clients_; DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerClient); };
diff --git a/third_party/crashpad/crashpad/util/linux/exception_handler_protocol.cc b/third_party/crashpad/crashpad/util/linux/exception_handler_protocol.cc index 7ab77d3..31b932226 100644 --- a/third_party/crashpad/crashpad/util/linux/exception_handler_protocol.cc +++ b/third_party/crashpad/crashpad/util/linux/exception_handler_protocol.cc
@@ -20,6 +20,6 @@ : exception_information_address(0), sanitization_information_address(0) {} ExceptionHandlerProtocol::ClientToServerMessage::ClientToServerMessage() - : version(kVersion), type(kCrashDumpRequest), client_info() {} + : version(kVersion), type(kTypeCrashDumpRequest), client_info() {} } // namespace crashpad
diff --git a/third_party/crashpad/crashpad/util/linux/exception_handler_protocol.h b/third_party/crashpad/crashpad/util/linux/exception_handler_protocol.h index d5eb5e8..7f4da26 100644 --- a/third_party/crashpad/crashpad/util/linux/exception_handler_protocol.h +++ b/third_party/crashpad/crashpad/util/linux/exception_handler_protocol.h
@@ -16,6 +16,7 @@ #define CRASHPAD_UTIL_LINUX_EXCEPTION_HANDLER_PROTOCOL_H_ #include <errno.h> +#include <signal.h> #include <stdint.h> #include <sys/types.h> @@ -51,6 +52,13 @@ VMAddress sanitization_information_address; }; + //! \brief The signal used to indicate a crash dump is complete. + //! + //! When multiple clients share a single socket connection with the handler, + //! the handler sends this signal to the dump requestor to indicate when the + //! the dump is either done or has failed and the client may continue. + static constexpr int kDumpDoneSignal = SIGCONT; + //! \brief The message passed from client to server. struct ClientToServerMessage { static constexpr int32_t kVersion = 1; @@ -61,14 +69,19 @@ //! \brief Indicates what message version is being used. int32_t version; + enum Type : uint32_t { + //! \brief Request that the server respond with its credentials. + kTypeCheckCredentials, + + //! \brief Used to request a crash dump for the sending client. + kTypeCrashDumpRequest + }; + + Type type; + //! \brief A stack address of the thread sending the message. VMAddress requesting_thread_stack_address; - enum Type : uint32_t { - //! \brief Used to request a crash dump for the sending client. - kCrashDumpRequest - } type; - union { //! \brief Valid for type == kCrashDumpRequest ClientInformation client_info; @@ -78,6 +91,9 @@ //! \brief The message passed from server to client. struct ServerToClientMessage { enum Type : uint32_t { + //! \brief Used to pass credentials with `SCM_CREDENTIALS`. + kTypeCredentials, + //! \brief Indicates that the client should fork a PtraceBroker process. kTypeForkBroker, @@ -92,7 +108,9 @@ //! \brief Indicicates that the handler was unable to produce a crash //! dump. kTypeCrashDumpFailed - } type; + }; + + Type type; //! \brief The handler's process ID. Valid for kTypeSetPtracer. pid_t pid;
diff --git a/third_party/crashpad/crashpad/util/linux/proc_task_reader.cc b/third_party/crashpad/crashpad/util/linux/proc_task_reader.cc new file mode 100644 index 0000000..360f83a --- /dev/null +++ b/third_party/crashpad/crashpad/util/linux/proc_task_reader.cc
@@ -0,0 +1,59 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// 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. + +#include "util/linux/proc_task_reader.h" + +#include <stdio.h> + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "util/file/directory_reader.h" +#include "util/misc/as_underlying_type.h" + +namespace crashpad { + +bool ReadThreadIDs(pid_t pid, std::vector<pid_t>* tids) { + DCHECK(tids->empty()); + + char path[32]; + snprintf(path, base::size(path), "/proc/%d/task", pid); + DirectoryReader reader; + if (!reader.Open(base::FilePath(path))) { + return false; + } + + std::vector<pid_t> local_tids; + base::FilePath tid_str; + DirectoryReader::Result result; + while ((result = reader.NextFile(&tid_str)) == + DirectoryReader::Result::kSuccess) { + pid_t tid; + if (!base::StringToInt(tid_str.value(), &tid)) { + LOG(ERROR) << "format error"; + continue; + } + + local_tids.push_back(tid); + } + DCHECK_EQ(AsUnderlyingType(result), + AsUnderlyingType(DirectoryReader::Result::kNoMoreFiles)); + DCHECK(!local_tids.empty()); + + tids->swap(local_tids); + return true; +} + +} // namespace crashpad
diff --git a/third_party/crashpad/crashpad/util/linux/proc_task_reader.h b/third_party/crashpad/crashpad/util/linux/proc_task_reader.h new file mode 100644 index 0000000..71287d9 --- /dev/null +++ b/third_party/crashpad/crashpad/util/linux/proc_task_reader.h
@@ -0,0 +1,34 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// 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. + +#ifndef CRASHPAD_UTIL_LINUX_PROC_TASK_READER_H_ +#define CRASHPAD_UTIL_LINUX_PROC_TASK_READER_H_ + +#include <sys/types.h> + +#include <vector> + +namespace crashpad { + +//! \brief Enumerates the thread IDs of a process by reading /proc/<pid>/task. +//! +//! \param[in] pid The process ID for which to read thread IDs. +//! \param[out] tids The read thread IDs. +//! \return `true` if the task directory was successfully read. Format errors +//! are logged, but won't cause this function to return `false`. +bool ReadThreadIDs(pid_t pid, std::vector<pid_t>* tids); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_LINUX_PROC_TASK_READER_H_
diff --git a/third_party/crashpad/crashpad/util/linux/proc_task_reader_test.cc b/third_party/crashpad/crashpad/util/linux/proc_task_reader_test.cc new file mode 100644 index 0000000..911f6d3d --- /dev/null +++ b/third_party/crashpad/crashpad/util/linux/proc_task_reader_test.cc
@@ -0,0 +1,162 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// 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. + +#include "util/linux/proc_task_reader.h" + +#include "base/logging.h" +#include "base/macros.h" +#include "base/strings/stringprintf.h" +#include "gtest/gtest.h" +#include "test/multiprocess_exec.h" +#include "third_party/lss/lss.h" +#include "util/synchronization/semaphore.h" +#include "util/thread/thread.h" + +namespace crashpad { +namespace test { +namespace { + +bool FindThreadID(pid_t tid, const std::vector<pid_t>& threads) { + for (const auto& thread : threads) { + if (thread == tid) { + return true; + } + } + return false; +} + +class ScopedBlockingThread : public Thread { + public: + ScopedBlockingThread() : tid_sem_(0), join_sem_(0), tid_(-1) {} + + ~ScopedBlockingThread() { + join_sem_.Signal(); + Join(); + } + + pid_t ThreadID() { + tid_sem_.Wait(); + return tid_; + } + + private: + void ThreadMain() override { + tid_ = sys_gettid(); + tid_sem_.Signal(); + join_sem_.Wait(); + } + + Semaphore tid_sem_; + Semaphore join_sem_; + pid_t tid_; +}; + +TEST(ProcTaskReader, Self) { + std::vector<pid_t> tids; + ASSERT_TRUE(ReadThreadIDs(getpid(), &tids)); + EXPECT_TRUE(FindThreadID(getpid(), tids)); + EXPECT_TRUE(FindThreadID(sys_gettid(), tids)); + + ScopedBlockingThread thread1; + thread1.Start(); + + ScopedBlockingThread thread2; + thread2.Start(); + + pid_t thread1_tid = thread1.ThreadID(); + pid_t thread2_tid = thread2.ThreadID(); + + tids.clear(); + ASSERT_TRUE(ReadThreadIDs(getpid(), &tids)); + EXPECT_TRUE(FindThreadID(getpid(), tids)); + EXPECT_TRUE(FindThreadID(thread1_tid, tids)); + EXPECT_TRUE(FindThreadID(thread2_tid, tids)); +} + +TEST(ProcTaskReader, BadPID) { + std::vector<pid_t> tids; + EXPECT_FALSE(ReadThreadIDs(-1, &tids)); + + tids.clear(); + EXPECT_FALSE(ReadThreadIDs(0, &tids)); +} + +CRASHPAD_CHILD_TEST_MAIN(ProcTaskTestChild) { + FileHandle in = StdioFileHandle(StdioStream::kStandardInput); + FileHandle out = StdioFileHandle(StdioStream::kStandardOutput); + + pid_t tid = getpid(); + CheckedWriteFile(out, &tid, sizeof(tid)); + + tid = sys_gettid(); + CheckedWriteFile(out, &tid, sizeof(tid)); + + ScopedBlockingThread thread1; + thread1.Start(); + + ScopedBlockingThread thread2; + thread2.Start(); + + tid = thread1.ThreadID(); + CheckedWriteFile(out, &tid, sizeof(tid)); + + tid = thread2.ThreadID(); + CheckedWriteFile(out, &tid, sizeof(tid)); + + CheckedReadFileAtEOF(in); + return 0; +} + +class ProcTaskTest : public MultiprocessExec { + public: + ProcTaskTest() : MultiprocessExec() { + SetChildTestMainFunction("ProcTaskTestChild"); + } + + private: + bool ReadIDFromChild(std::vector<pid_t>* threads) { + pid_t tid; + if (!LoggingReadFileExactly(ReadPipeHandle(), &tid, sizeof(tid))) { + return false; + } + threads->push_back(tid); + return true; + } + + void MultiprocessParent() override { + std::vector<pid_t> ids_to_find; + for (size_t id_count = 0; id_count < 4; ++id_count) { + ASSERT_TRUE(ReadIDFromChild(&ids_to_find)); + } + + std::vector<pid_t> threads; + ASSERT_TRUE(ReadThreadIDs(ChildPID(), &threads)); + for (size_t index = 0; index < ids_to_find.size(); ++index) { + SCOPED_TRACE( + base::StringPrintf("index %zd, tid %d", index, ids_to_find[index])); + EXPECT_TRUE(FindThreadID(ids_to_find[index], threads)); + } + } + + DISALLOW_COPY_AND_ASSIGN(ProcTaskTest); +}; + +TEST(ProcTaskReader, ReadChild) { + ProcTaskTest test; + test.Run(); +} + +} // namespace +} // namespace test +} // namespace crashpad
diff --git a/third_party/crashpad/crashpad/util/linux/socket.cc b/third_party/crashpad/crashpad/util/linux/socket.cc new file mode 100644 index 0000000..68efd570 --- /dev/null +++ b/third_party/crashpad/crashpad/util/linux/socket.cc
@@ -0,0 +1,192 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// 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. + +#include "util/linux/socket.h" + +#include <unistd.h> + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "third_party/lss/lss.h" + +namespace crashpad { + +// static +bool UnixCredentialSocket::CreateCredentialSocketpair(ScopedFileHandle* sock1, + ScopedFileHandle* sock2) { + int socks[2]; + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, socks) != 0) { + PLOG(ERROR) << "socketpair"; + return false; + } + ScopedFileHandle local_sock1(socks[0]); + ScopedFileHandle local_sock2(socks[1]); + + int optval = 1; + socklen_t optlen = sizeof(optval); + if (setsockopt(local_sock1.get(), SOL_SOCKET, SO_PASSCRED, &optval, optlen) != + 0 || + setsockopt(local_sock2.get(), SOL_SOCKET, SO_PASSCRED, &optval, optlen) != + 0) { + PLOG(ERROR) << "setsockopt"; + return false; + } + + sock1->swap(local_sock1); + sock2->swap(local_sock2); + return true; +} + +constexpr size_t UnixCredentialSocket::kMaxSendRecvMsgFDs = 4; + +// static +int UnixCredentialSocket::SendMsg(int fd, + const void* buf, + size_t buf_size, + const int* fds, + size_t fd_count) { + // This function is intended to be used after a crash. fds is an integer + // array instead of a vector to avoid forcing callers to provide a vector, + // which they would have to create prior to the crash. + if (fds && fd_count > kMaxSendRecvMsgFDs) { + DLOG(ERROR) << "too many fds " << fd_count; + return EINVAL; + } + + iovec iov; + iov.iov_base = const_cast<void*>(buf); + iov.iov_len = buf_size; + + msghdr msg = {}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + char cmsg_buf[CMSG_SPACE(sizeof(int) * kMaxSendRecvMsgFDs)]; + if (fds) { + msg.msg_control = cmsg_buf; + msg.msg_controllen = CMSG_SPACE(sizeof(int) * fd_count); + + cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + DCHECK(cmsg); + + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int) * fd_count); + memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * fd_count); + } + + // TODO(jperaza): Use sys_sendmsg when lss has macros for maniuplating control + // messages. https://crbug.com/crashpad/265 + if (HANDLE_EINTR(sendmsg(fd, &msg, MSG_NOSIGNAL)) < 0) { + DPLOG(ERROR) << "sendmsg"; + return errno; + } + return 0; +} + +// static +bool UnixCredentialSocket::RecvMsg(int fd, + void* buf, + size_t buf_size, + ucred* creds, + std::vector<ScopedFileHandle>* fds) { + iovec iov; + iov.iov_base = buf; + iov.iov_len = buf_size; + + msghdr msg = {}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + char cmsg_buf[CMSG_SPACE(sizeof(ucred)) + + CMSG_SPACE(sizeof(int) * kMaxSendRecvMsgFDs)]; + msg.msg_control = cmsg_buf; + msg.msg_controllen = sizeof(cmsg_buf); + + int res = HANDLE_EINTR(recvmsg(fd, &msg, 0)); + if (res < 0) { + PLOG(ERROR) << "recvmsg"; + return false; + } + + ucred* local_creds = nullptr; + std::vector<ScopedFileHandle> local_fds; + bool unhandled_cmsgs = false; + + for (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + cmsg; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + int* fdp = reinterpret_cast<int*>(CMSG_DATA(cmsg)); + size_t fd_count = (reinterpret_cast<char*>(cmsg) + cmsg->cmsg_len - + reinterpret_cast<char*>(fdp)) / + sizeof(int); + DCHECK_LE(fd_count, kMaxSendRecvMsgFDs); + for (size_t index = 0; index < fd_count; ++index) { + if (fds) { + local_fds.emplace_back(fdp[index]); + } else if (IGNORE_EINTR(close(fdp[index])) != 0) { + PLOG(ERROR) << "close"; + } + } + continue; + } + + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS) { + DCHECK(!local_creds); + local_creds = reinterpret_cast<ucred*>(CMSG_DATA(cmsg)); + continue; + } + + LOG(ERROR) << "unhandled cmsg " << cmsg->cmsg_level << ", " + << cmsg->cmsg_type; + unhandled_cmsgs = true; + } + + if (unhandled_cmsgs) { + return false; + } + + if (msg.msg_name != nullptr || msg.msg_namelen != 0) { + LOG(ERROR) << "unexpected msg name"; + return false; + } + + if (msg.msg_flags & MSG_TRUNC || msg.msg_flags & MSG_CTRUNC) { + LOG(ERROR) << "truncated msg"; + return false; + } + + if (!local_creds) { + LOG(ERROR) << "missing credentials"; + return false; + } + + // res == 0 may also indicate that the sending socket disconnected, but in + // that case, the message will also have missing or invalid credentials. + if (static_cast<size_t>(res) != buf_size) { + if (res != 0 || (local_creds && local_creds->pid != 0)) { + LOG(ERROR) << "incorrect payload size " << res; + } + return false; + } + + *creds = *local_creds; + if (fds) { + fds->swap(local_fds); + } + return true; +} + +} // namespace crashpad
diff --git a/third_party/crashpad/crashpad/util/linux/socket.h b/third_party/crashpad/crashpad/util/linux/socket.h new file mode 100644 index 0000000..c02a6c3 --- /dev/null +++ b/third_party/crashpad/crashpad/util/linux/socket.h
@@ -0,0 +1,92 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// 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. + +#ifndef CRASHPAD_UTIL_LINUX_SOCKET_H_ +#define CRASHPAD_UTIL_LINUX_SOCKET_H_ + +#include <sys/socket.h> +#include <sys/types.h> + +#include <vector> + +#include "base/macros.h" +#include "util/file/file_io.h" + +namespace crashpad { + +//! \brief Utilities for communicating over `SO_PASSCRED` enabled `AF_UNIX` +//! sockets. +class UnixCredentialSocket { + public: + //! \brief Creates an `AF_UNIX` family socket pair with `SO_PASSCRED` set on + //! each socket. + //! + //! \param[out] s1 One end of the connected pair. + //! \param[out] s2 The other end of the connected pair. + //! \return `true` on success. Otherwise, `false` with a message logged. + static bool CreateCredentialSocketpair(ScopedFileHandle* s1, + ScopedFileHandle* s2); + + //! \brief The maximum number of file descriptors that may be sent/received + //! with `SendMsg()` or `RecvMsg()`. + static const size_t kMaxSendRecvMsgFDs; + + //! \brief Wraps `sendmsg()` to send a message with file descriptors. + //! + //! This function is intended for use with `AF_UNIX` family sockets and + //! passes file descriptors with `SCM_RIGHTS`. + //! + //! This function may be used in a compromised context. + //! + //! \param[in] fd The file descriptor to write the message to. + //! \param[in] buf The buffer containing the message. + //! \param[in] buf_size The size of the message. + //! \param[in] fds An array of at most `kMaxSendRecvMsgFDs` file descriptors. + //! Optional. + //! \param[in] fd_count The number of file descriptors in \a fds. Required + //! only if \a fds was set. + //! \return 0 on success or an error code on failure. + static int SendMsg(int fd, + const void* buf, + size_t buf_size, + const int* fds = nullptr, + size_t fd_count = 0); + + //! \brief Wraps `recvmsg()` to receive a message with file descriptors and + //! credentials. + //! + //! This function is intended to be used with `AF_UNIX` family sockets. Up to + //! `kMaxSendRecvMsgFDs` file descriptors may be received (via `SCM_RIGHTS`). + //! The socket must have `SO_PASSCRED` set. + //! + //! \param[in] fd The file descriptor to receive the message on. + //! \param[out] buf The buffer to fill with the message. + //! \param[in] buf_size The size of the message. + //! \param[out] creds The credentials of the sender. + //! \param[out] fds The recieved file descriptors. Optional. If `nullptr`, all + //! received file descriptors will be closed. + //! \return `true` on success. Otherwise, `false`, with a message logged. + static bool RecvMsg(int fd, + void* buf, + size_t buf_size, + ucred* creds, + std::vector<ScopedFileHandle>* fds = nullptr); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(UnixCredentialSocket); +}; + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_LINUX_SOCKET_H_
diff --git a/third_party/crashpad/crashpad/util/linux/socket_test.cc b/third_party/crashpad/crashpad/util/linux/socket_test.cc new file mode 100644 index 0000000..4e583aedf --- /dev/null +++ b/third_party/crashpad/crashpad/util/linux/socket_test.cc
@@ -0,0 +1,139 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// 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. + +#include "util/linux/socket.h" + +#include "base/logging.h" +#include "base/macros.h" +#include "base/posix/eintr_wrapper.h" +#include "gtest/gtest.h" +#include "util/linux/socket.h" + +namespace crashpad { +namespace test { +namespace { + +TEST(Socket, Credentials) { + ScopedFileHandle send_sock, recv_sock; + ASSERT_TRUE( + UnixCredentialSocket::CreateCredentialSocketpair(&send_sock, &recv_sock)); + + char msg = 42; + ASSERT_EQ(UnixCredentialSocket::SendMsg(send_sock.get(), &msg, sizeof(msg)), + 0); + + char recv_msg = 0; + ucred creds; + ASSERT_TRUE(UnixCredentialSocket::RecvMsg( + recv_sock.get(), &recv_msg, sizeof(recv_msg), &creds)); + EXPECT_EQ(recv_msg, msg); + EXPECT_EQ(creds.pid, getpid()); + EXPECT_EQ(creds.uid, geteuid()); + EXPECT_EQ(creds.gid, getegid()); +} + +TEST(Socket, EmptyMessages) { + ScopedFileHandle send_sock, recv_sock; + ASSERT_TRUE( + UnixCredentialSocket::CreateCredentialSocketpair(&send_sock, &recv_sock)); + + ASSERT_EQ(UnixCredentialSocket::SendMsg(send_sock.get(), nullptr, 0), 0); + + ucred creds; + ASSERT_TRUE( + UnixCredentialSocket::RecvMsg(recv_sock.get(), nullptr, 0, &creds)); + EXPECT_EQ(creds.pid, getpid()); + EXPECT_EQ(creds.uid, geteuid()); + EXPECT_EQ(creds.gid, getegid()); +} + +TEST(Socket, Hangup) { + ScopedFileHandle send_sock, recv_sock; + ASSERT_TRUE( + UnixCredentialSocket::CreateCredentialSocketpair(&send_sock, &recv_sock)); + + send_sock.reset(); + + char recv_msg = 0; + ucred creds; + EXPECT_FALSE(UnixCredentialSocket::RecvMsg( + recv_sock.get(), &recv_msg, sizeof(recv_msg), &creds)); +} + +TEST(Socket, FileDescriptors) { + ScopedFileHandle send_sock, recv_sock; + ASSERT_TRUE( + UnixCredentialSocket::CreateCredentialSocketpair(&send_sock, &recv_sock)); + + ScopedFileHandle test_fd1, test_fd2; + ASSERT_TRUE( + UnixCredentialSocket::CreateCredentialSocketpair(&test_fd1, &test_fd2)); + + char msg = 42; + ASSERT_EQ(UnixCredentialSocket::SendMsg( + send_sock.get(), &msg, sizeof(msg), &test_fd1.get(), 1), + 0); + + char recv_msg = 0; + ucred creds; + std::vector<ScopedFileHandle> fds; + ASSERT_TRUE(UnixCredentialSocket::RecvMsg( + recv_sock.get(), &recv_msg, sizeof(recv_msg), &creds, &fds)); + ASSERT_EQ(fds.size(), 1u); +} + +TEST(Socket, RecvClosesFileDescriptors) { + ScopedFileHandle send_sock, recv_sock; + ASSERT_TRUE( + UnixCredentialSocket::CreateCredentialSocketpair(&send_sock, &recv_sock)); + + ScopedFileHandle send_fds[UnixCredentialSocket::kMaxSendRecvMsgFDs]; + ScopedFileHandle recv_fds[UnixCredentialSocket::kMaxSendRecvMsgFDs]; + int raw_recv_fds[UnixCredentialSocket::kMaxSendRecvMsgFDs]; + for (size_t index = 0; index < UnixCredentialSocket::kMaxSendRecvMsgFDs; + ++index) { + ASSERT_TRUE(UnixCredentialSocket::CreateCredentialSocketpair( + &send_fds[index], &recv_fds[index])); + raw_recv_fds[index] = recv_fds[index].get(); + } + + char msg = 42; + ASSERT_EQ( + UnixCredentialSocket::SendMsg(send_sock.get(), + &msg, + sizeof(msg), + raw_recv_fds, + UnixCredentialSocket::kMaxSendRecvMsgFDs), + 0); + + char recv_msg = 0; + ucred creds; + ASSERT_TRUE(UnixCredentialSocket::RecvMsg( + recv_sock.get(), &recv_msg, sizeof(recv_msg), &creds)); + EXPECT_EQ(creds.pid, getpid()); + + for (size_t index = 0; index < UnixCredentialSocket::kMaxSendRecvMsgFDs; + ++index) { + recv_fds[index].reset(); + char c; + EXPECT_EQ( + HANDLE_EINTR(send(send_fds[index].get(), &c, sizeof(c), MSG_NOSIGNAL)), + -1); + EXPECT_EQ(errno, EPIPE); + } +} + +} // namespace +} // namespace test +} // namespace crashpad
diff --git a/third_party/crashpad/crashpad/util/process/process_id.h b/third_party/crashpad/crashpad/util/process/process_id.h new file mode 100644 index 0000000..113f6fc --- /dev/null +++ b/third_party/crashpad/crashpad/util/process/process_id.h
@@ -0,0 +1,54 @@ +// Copyright 2019 The Crashpad Authors. All rights reserved. +// +// 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. + +#ifndef CRASHPAD_UTIL_PROCESS_PROCESS_ID_H_ +#define CRASHPAD_UTIL_PROCESS_PROCESS_ID_H_ + +#include <type_traits> + +#include "base/format_macros.h" +#include "build/build_config.h" + +#if defined(OS_POSIX) +#include <sys/types.h> +#elif defined(OS_WIN) +#include <windows.h> +#elif defined(OS_FUCHSIA) +#include <zircon/types.h> +#endif + +namespace crashpad { + +#if defined(OS_POSIX) || DOXYGEN +//! \brief Alias for platform-specific type to represent a process. +using ProcessID = pid_t; +constexpr ProcessID kInvalidProcessID = -1; +static_assert(std::is_same<ProcessID, int>::value, "Port."); +#define PRI_PROCESS_ID "d" +#elif defined(OS_WIN) +using ProcessID = DWORD; +constexpr ProcessID kInvalidProcessID = 0; +#define PRI_PROCESS_ID "lu" +#elif defined(OS_FUCHSIA) +using ProcessID = zx_koid_t; +constexpr ProcessID kInvalidProcessID = ZX_KOID_INVALID; +static_assert(std::is_same<ProcessID, int64_t>::value, "Port."); +#define PRI_PROCESS_ID PRId64 +#else +#error Port. +#endif + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_PROCESS_PROCESS_ID_H_
diff --git a/third_party/crashpad/crashpad/util/util.gyp b/third_party/crashpad/crashpad/util/util.gyp index 32be8ba..7efec7d 100644 --- a/third_party/crashpad/crashpad/util/util.gyp +++ b/third_party/crashpad/crashpad/util/util.gyp
@@ -24,6 +24,7 @@ '../compat/compat.gyp:crashpad_compat', '../third_party/mini_chromium/mini_chromium.gyp:base', '../third_party/zlib/zlib.gyp:zlib', + '../third_party/lss/lss.gyp:lss', ], 'include_dirs': [ '..', @@ -67,6 +68,8 @@ 'linux/memory_map.h', 'linux/proc_stat_reader.cc', 'linux/proc_stat_reader.h', + 'linux/proc_task_reader.cc', + 'linux/proc_task_reader.h', 'linux/ptrace_broker.cc', 'linux/ptrace_broker.h', 'linux/ptrace_client.cc', @@ -80,6 +83,8 @@ 'linux/scoped_pr_set_ptracer.h', 'linux/scoped_ptrace_attach.cc', 'linux/scoped_ptrace_attach.h', + 'linux/socket.cc', + 'linux/socket.h', 'linux/thread_info.cc', 'linux/thread_info.h', 'linux/traits.h', @@ -209,6 +214,7 @@ 'posix/signals.h', 'posix/symbolic_constants_posix.cc', 'posix/symbolic_constants_posix.h', + 'process/process_id.h', 'process/process_memory.cc', 'process/process_memory.h', 'process/process_memory_linux.cc',
diff --git a/third_party/crashpad/crashpad/util/util_test.gyp b/third_party/crashpad/crashpad/util/util_test.gyp index 9edd3dcb..573162e6 100644 --- a/third_party/crashpad/crashpad/util/util_test.gyp +++ b/third_party/crashpad/crashpad/util/util_test.gyp
@@ -28,6 +28,7 @@ '../test/test.gyp:crashpad_test', '../third_party/gtest/gmock.gyp:gmock', '../third_party/gtest/gtest.gyp:gtest', + '../third_party/lss/lss.gyp:lss', '../third_party/mini_chromium/mini_chromium.gyp:base', '../third_party/zlib/zlib.gyp:zlib', ], @@ -44,9 +45,11 @@ 'linux/auxiliary_vector_test.cc', 'linux/memory_map_test.cc', 'linux/proc_stat_reader_test.cc', + 'linux/proc_task_reader_test.cc', 'linux/ptrace_broker_test.cc', 'linux/ptracer_test.cc', 'linux/scoped_ptrace_attach_test.cc', + 'linux/socket_test.cc', 'mac/launchd_test.mm', 'mac/mac_util_test.mm', 'mac/service_management_test.mm',
diff --git a/third_party/crashpad/crashpad/util/win/process_info.cc b/third_party/crashpad/crashpad/util/win/process_info.cc index ff8f34d..fc0598e 100644 --- a/third_party/crashpad/crashpad/util/win/process_info.cc +++ b/third_party/crashpad/crashpad/util/win/process_info.cc
@@ -565,12 +565,12 @@ return is_wow64_; } -pid_t ProcessInfo::ProcessID() const { +crashpad::ProcessID ProcessInfo::ProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return process_id_; } -pid_t ProcessInfo::ParentProcessID() const { +crashpad::ProcessID ProcessInfo::ParentProcessID() const { INITIALIZATION_STATE_DCHECK_VALID(initialized_); return inherited_from_process_id_; }
diff --git a/third_party/crashpad/crashpad/util/win/process_info.h b/third_party/crashpad/crashpad/util/win/process_info.h index 58921c5..afbe1462 100644 --- a/third_party/crashpad/crashpad/util/win/process_info.h +++ b/third_party/crashpad/crashpad/util/win/process_info.h
@@ -24,6 +24,7 @@ #include "base/macros.h" #include "util/misc/initialization_state_dcheck.h" #include "util/numeric/checked_range.h" +#include "util/process/process_id.h" #include "util/stdlib/aligned_allocator.h" #include "util/win/address_types.h" @@ -105,10 +106,10 @@ bool IsWow64() const; //! \return The target process's process ID. - pid_t ProcessID() const; + crashpad::ProcessID ProcessID() const; //! \return The target process's parent process ID. - pid_t ParentProcessID() const; + crashpad::ProcessID ParentProcessID() const; //! \return The command line from the target process's Process Environment //! Block. @@ -173,8 +174,8 @@ // This function is best-effort under low memory conditions. std::vector<Handle> BuildHandleVector(HANDLE process) const; - pid_t process_id_; - pid_t inherited_from_process_id_; + crashpad::ProcessID process_id_; + crashpad::ProcessID inherited_from_process_id_; HANDLE process_; std::wstring command_line_; WinVMAddress peb_address_;
diff --git a/third_party/libvpx/README.chromium b/third_party/libvpx/README.chromium index 6c5d6ad..a035b174 100644 --- a/third_party/libvpx/README.chromium +++ b/third_party/libvpx/README.chromium
@@ -5,9 +5,9 @@ License File: source/libvpx/LICENSE Security Critical: yes -Date: Thursday April 25 2019 +Date: Friday May 03 2019 Branch: master -Commit: e50f4e4112a0e031dfc9df7a115fb0f8931bd4e1 +Commit: 3fd96f7d7d848ce8fc3ff27cb689207d191996ed Description: Contains the sources used to compile libvpx binaries used by Google Chrome and
diff --git a/third_party/libvpx/source/config/ios/arm-neon/vpx_config.asm b/third_party/libvpx/source/config/ios/arm-neon/vpx_config.asm index 042def7..93794b57 100644 --- a/third_party/libvpx/source/config/ios/arm-neon/vpx_config.asm +++ b/third_party/libvpx/source/config/ios/arm-neon/vpx_config.asm
@@ -83,6 +83,7 @@ .set CONFIG_EXPERIMENTAL , 0 .set CONFIG_SIZE_LIMIT , 1 .set CONFIG_ALWAYS_ADJUST_BPM , 0 +.set CONFIG_BITSTREAM_DEBUG , 0 .set CONFIG_FP_MB_STATS , 0 .set CONFIG_EMULATE_HARDWARE , 0 .set CONFIG_NON_GREEDY_MV , 0
diff --git a/third_party/libvpx/source/config/ios/arm-neon/vpx_config.h b/third_party/libvpx/source/config/ios/arm-neon/vpx_config.h index c19d1d9..c8916bc 100644 --- a/third_party/libvpx/source/config/ios/arm-neon/vpx_config.h +++ b/third_party/libvpx/source/config/ios/arm-neon/vpx_config.h
@@ -91,6 +91,7 @@ #define CONFIG_EXPERIMENTAL 0 #define CONFIG_SIZE_LIMIT 1 #define CONFIG_ALWAYS_ADJUST_BPM 0 +#define CONFIG_BITSTREAM_DEBUG 0 #define CONFIG_FP_MB_STATS 0 #define CONFIG_EMULATE_HARDWARE 0 #define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/ios/arm64/vpx_config.asm b/third_party/libvpx/source/config/ios/arm64/vpx_config.asm index 175bf73..898eda7ce 100644 --- a/third_party/libvpx/source/config/ios/arm64/vpx_config.asm +++ b/third_party/libvpx/source/config/ios/arm64/vpx_config.asm
@@ -83,6 +83,7 @@ .set CONFIG_EXPERIMENTAL , 0 .set CONFIG_SIZE_LIMIT , 1 .set CONFIG_ALWAYS_ADJUST_BPM , 0 +.set CONFIG_BITSTREAM_DEBUG , 0 .set CONFIG_FP_MB_STATS , 0 .set CONFIG_EMULATE_HARDWARE , 0 .set CONFIG_NON_GREEDY_MV , 0
diff --git a/third_party/libvpx/source/config/ios/arm64/vpx_config.h b/third_party/libvpx/source/config/ios/arm64/vpx_config.h index e4de0be..44e3138 100644 --- a/third_party/libvpx/source/config/ios/arm64/vpx_config.h +++ b/third_party/libvpx/source/config/ios/arm64/vpx_config.h
@@ -91,6 +91,7 @@ #define CONFIG_EXPERIMENTAL 0 #define CONFIG_SIZE_LIMIT 1 #define CONFIG_ALWAYS_ADJUST_BPM 0 +#define CONFIG_BITSTREAM_DEBUG 0 #define CONFIG_FP_MB_STATS 0 #define CONFIG_EMULATE_HARDWARE 0 #define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/linux/arm-neon-cpu-detect/vpx_config.asm b/third_party/libvpx/source/config/linux/arm-neon-cpu-detect/vpx_config.asm index 0d29fb5..9a569649 100644 --- a/third_party/libvpx/source/config/linux/arm-neon-cpu-detect/vpx_config.asm +++ b/third_party/libvpx/source/config/linux/arm-neon-cpu-detect/vpx_config.asm
@@ -82,6 +82,7 @@ .equ CONFIG_EXPERIMENTAL , 0 .equ CONFIG_SIZE_LIMIT , 1 .equ CONFIG_ALWAYS_ADJUST_BPM , 0 +.equ CONFIG_BITSTREAM_DEBUG , 0 .equ CONFIG_FP_MB_STATS , 0 .equ CONFIG_EMULATE_HARDWARE , 0 .equ CONFIG_NON_GREEDY_MV , 0
diff --git a/third_party/libvpx/source/config/linux/arm-neon-cpu-detect/vpx_config.h b/third_party/libvpx/source/config/linux/arm-neon-cpu-detect/vpx_config.h index 58b42323..909f4ffd 100644 --- a/third_party/libvpx/source/config/linux/arm-neon-cpu-detect/vpx_config.h +++ b/third_party/libvpx/source/config/linux/arm-neon-cpu-detect/vpx_config.h
@@ -91,6 +91,7 @@ #define CONFIG_EXPERIMENTAL 0 #define CONFIG_SIZE_LIMIT 1 #define CONFIG_ALWAYS_ADJUST_BPM 0 +#define CONFIG_BITSTREAM_DEBUG 0 #define CONFIG_FP_MB_STATS 0 #define CONFIG_EMULATE_HARDWARE 0 #define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/linux/arm-neon-highbd/vpx_config.asm b/third_party/libvpx/source/config/linux/arm-neon-highbd/vpx_config.asm index f7a28f8..cd4ba8c 100644 --- a/third_party/libvpx/source/config/linux/arm-neon-highbd/vpx_config.asm +++ b/third_party/libvpx/source/config/linux/arm-neon-highbd/vpx_config.asm
@@ -82,6 +82,7 @@ .equ CONFIG_EXPERIMENTAL , 0 .equ CONFIG_SIZE_LIMIT , 1 .equ CONFIG_ALWAYS_ADJUST_BPM , 0 +.equ CONFIG_BITSTREAM_DEBUG , 0 .equ CONFIG_FP_MB_STATS , 0 .equ CONFIG_EMULATE_HARDWARE , 0 .equ CONFIG_NON_GREEDY_MV , 0
diff --git a/third_party/libvpx/source/config/linux/arm-neon-highbd/vpx_config.h b/third_party/libvpx/source/config/linux/arm-neon-highbd/vpx_config.h index 5df6afd..b1efd1ad3 100644 --- a/third_party/libvpx/source/config/linux/arm-neon-highbd/vpx_config.h +++ b/third_party/libvpx/source/config/linux/arm-neon-highbd/vpx_config.h
@@ -91,6 +91,7 @@ #define CONFIG_EXPERIMENTAL 0 #define CONFIG_SIZE_LIMIT 1 #define CONFIG_ALWAYS_ADJUST_BPM 0 +#define CONFIG_BITSTREAM_DEBUG 0 #define CONFIG_FP_MB_STATS 0 #define CONFIG_EMULATE_HARDWARE 0 #define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/linux/arm-neon/vpx_config.asm b/third_party/libvpx/source/config/linux/arm-neon/vpx_config.asm index 64cec85..206e2ab 100644 --- a/third_party/libvpx/source/config/linux/arm-neon/vpx_config.asm +++ b/third_party/libvpx/source/config/linux/arm-neon/vpx_config.asm
@@ -82,6 +82,7 @@ .equ CONFIG_EXPERIMENTAL , 0 .equ CONFIG_SIZE_LIMIT , 1 .equ CONFIG_ALWAYS_ADJUST_BPM , 0 +.equ CONFIG_BITSTREAM_DEBUG , 0 .equ CONFIG_FP_MB_STATS , 0 .equ CONFIG_EMULATE_HARDWARE , 0 .equ CONFIG_NON_GREEDY_MV , 0
diff --git a/third_party/libvpx/source/config/linux/arm-neon/vpx_config.h b/third_party/libvpx/source/config/linux/arm-neon/vpx_config.h index c19d1d9..c8916bc 100644 --- a/third_party/libvpx/source/config/linux/arm-neon/vpx_config.h +++ b/third_party/libvpx/source/config/linux/arm-neon/vpx_config.h
@@ -91,6 +91,7 @@ #define CONFIG_EXPERIMENTAL 0 #define CONFIG_SIZE_LIMIT 1 #define CONFIG_ALWAYS_ADJUST_BPM 0 +#define CONFIG_BITSTREAM_DEBUG 0 #define CONFIG_FP_MB_STATS 0 #define CONFIG_EMULATE_HARDWARE 0 #define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/linux/arm/vpx_config.asm b/third_party/libvpx/source/config/linux/arm/vpx_config.asm index a13825c7..1d57b94 100644 --- a/third_party/libvpx/source/config/linux/arm/vpx_config.asm +++ b/third_party/libvpx/source/config/linux/arm/vpx_config.asm
@@ -82,6 +82,7 @@ .equ CONFIG_EXPERIMENTAL , 0 .equ CONFIG_SIZE_LIMIT , 1 .equ CONFIG_ALWAYS_ADJUST_BPM , 0 +.equ CONFIG_BITSTREAM_DEBUG , 0 .equ CONFIG_FP_MB_STATS , 0 .equ CONFIG_EMULATE_HARDWARE , 0 .equ CONFIG_NON_GREEDY_MV , 0
diff --git a/third_party/libvpx/source/config/linux/arm/vpx_config.h b/third_party/libvpx/source/config/linux/arm/vpx_config.h index acc4935..2d461a03 100644 --- a/third_party/libvpx/source/config/linux/arm/vpx_config.h +++ b/third_party/libvpx/source/config/linux/arm/vpx_config.h
@@ -91,6 +91,7 @@ #define CONFIG_EXPERIMENTAL 0 #define CONFIG_SIZE_LIMIT 1 #define CONFIG_ALWAYS_ADJUST_BPM 0 +#define CONFIG_BITSTREAM_DEBUG 0 #define CONFIG_FP_MB_STATS 0 #define CONFIG_EMULATE_HARDWARE 0 #define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/linux/arm64-highbd/vpx_config.asm b/third_party/libvpx/source/config/linux/arm64-highbd/vpx_config.asm index f94c4910..3b74a31 100644 --- a/third_party/libvpx/source/config/linux/arm64-highbd/vpx_config.asm +++ b/third_party/libvpx/source/config/linux/arm64-highbd/vpx_config.asm
@@ -82,6 +82,7 @@ .equ CONFIG_EXPERIMENTAL , 0 .equ CONFIG_SIZE_LIMIT , 1 .equ CONFIG_ALWAYS_ADJUST_BPM , 0 +.equ CONFIG_BITSTREAM_DEBUG , 0 .equ CONFIG_FP_MB_STATS , 0 .equ CONFIG_EMULATE_HARDWARE , 0 .equ CONFIG_NON_GREEDY_MV , 0
diff --git a/third_party/libvpx/source/config/linux/arm64-highbd/vpx_config.h b/third_party/libvpx/source/config/linux/arm64-highbd/vpx_config.h index 1a03c79..0a2dc7a 100644 --- a/third_party/libvpx/source/config/linux/arm64-highbd/vpx_config.h +++ b/third_party/libvpx/source/config/linux/arm64-highbd/vpx_config.h
@@ -91,6 +91,7 @@ #define CONFIG_EXPERIMENTAL 0 #define CONFIG_SIZE_LIMIT 1 #define CONFIG_ALWAYS_ADJUST_BPM 0 +#define CONFIG_BITSTREAM_DEBUG 0 #define CONFIG_FP_MB_STATS 0 #define CONFIG_EMULATE_HARDWARE 0 #define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/linux/arm64/vpx_config.asm b/third_party/libvpx/source/config/linux/arm64/vpx_config.asm index 559e41d..7288dbf 100644 --- a/third_party/libvpx/source/config/linux/arm64/vpx_config.asm +++ b/third_party/libvpx/source/config/linux/arm64/vpx_config.asm
@@ -82,6 +82,7 @@ .equ CONFIG_EXPERIMENTAL , 0 .equ CONFIG_SIZE_LIMIT , 1 .equ CONFIG_ALWAYS_ADJUST_BPM , 0 +.equ CONFIG_BITSTREAM_DEBUG , 0 .equ CONFIG_FP_MB_STATS , 0 .equ CONFIG_EMULATE_HARDWARE , 0 .equ CONFIG_NON_GREEDY_MV , 0
diff --git a/third_party/libvpx/source/config/linux/arm64/vpx_config.h b/third_party/libvpx/source/config/linux/arm64/vpx_config.h index e4de0be..44e3138 100644 --- a/third_party/libvpx/source/config/linux/arm64/vpx_config.h +++ b/third_party/libvpx/source/config/linux/arm64/vpx_config.h
@@ -91,6 +91,7 @@ #define CONFIG_EXPERIMENTAL 0 #define CONFIG_SIZE_LIMIT 1 #define CONFIG_ALWAYS_ADJUST_BPM 0 +#define CONFIG_BITSTREAM_DEBUG 0 #define CONFIG_FP_MB_STATS 0 #define CONFIG_EMULATE_HARDWARE 0 #define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/linux/generic/vpx_config.asm b/third_party/libvpx/source/config/linux/generic/vpx_config.asm index fdb927f2..6cbd620 100644 --- a/third_party/libvpx/source/config/linux/generic/vpx_config.asm +++ b/third_party/libvpx/source/config/linux/generic/vpx_config.asm
@@ -82,6 +82,7 @@ .equ CONFIG_EXPERIMENTAL , 0 .equ CONFIG_SIZE_LIMIT , 1 .equ CONFIG_ALWAYS_ADJUST_BPM , 0 +.equ CONFIG_BITSTREAM_DEBUG , 0 .equ CONFIG_FP_MB_STATS , 0 .equ CONFIG_EMULATE_HARDWARE , 0 .equ CONFIG_NON_GREEDY_MV , 0
diff --git a/third_party/libvpx/source/config/linux/generic/vpx_config.h b/third_party/libvpx/source/config/linux/generic/vpx_config.h index abc94bfc..45c8a91 100644 --- a/third_party/libvpx/source/config/linux/generic/vpx_config.h +++ b/third_party/libvpx/source/config/linux/generic/vpx_config.h
@@ -91,6 +91,7 @@ #define CONFIG_EXPERIMENTAL 0 #define CONFIG_SIZE_LIMIT 1 #define CONFIG_ALWAYS_ADJUST_BPM 0 +#define CONFIG_BITSTREAM_DEBUG 0 #define CONFIG_FP_MB_STATS 0 #define CONFIG_EMULATE_HARDWARE 0 #define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/linux/ia32/vpx_config.asm b/third_party/libvpx/source/config/linux/ia32/vpx_config.asm index 5569f05..b95eed2 100644 --- a/third_party/libvpx/source/config/linux/ia32/vpx_config.asm +++ b/third_party/libvpx/source/config/linux/ia32/vpx_config.asm
@@ -79,6 +79,7 @@ %define CONFIG_EXPERIMENTAL 0 %define CONFIG_SIZE_LIMIT 1 %define CONFIG_ALWAYS_ADJUST_BPM 0 +%define CONFIG_BITSTREAM_DEBUG 0 %define CONFIG_FP_MB_STATS 0 %define CONFIG_EMULATE_HARDWARE 0 %define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/linux/ia32/vpx_config.h b/third_party/libvpx/source/config/linux/ia32/vpx_config.h index fff9290..ca9a87a8 100644 --- a/third_party/libvpx/source/config/linux/ia32/vpx_config.h +++ b/third_party/libvpx/source/config/linux/ia32/vpx_config.h
@@ -91,6 +91,7 @@ #define CONFIG_EXPERIMENTAL 0 #define CONFIG_SIZE_LIMIT 1 #define CONFIG_ALWAYS_ADJUST_BPM 0 +#define CONFIG_BITSTREAM_DEBUG 0 #define CONFIG_FP_MB_STATS 0 #define CONFIG_EMULATE_HARDWARE 0 #define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/linux/mips64el/vpx_config.h b/third_party/libvpx/source/config/linux/mips64el/vpx_config.h index 7941761..478075c6 100644 --- a/third_party/libvpx/source/config/linux/mips64el/vpx_config.h +++ b/third_party/libvpx/source/config/linux/mips64el/vpx_config.h
@@ -91,6 +91,7 @@ #define CONFIG_EXPERIMENTAL 0 #define CONFIG_SIZE_LIMIT 1 #define CONFIG_ALWAYS_ADJUST_BPM 0 +#define CONFIG_BITSTREAM_DEBUG 0 #define CONFIG_FP_MB_STATS 0 #define CONFIG_EMULATE_HARDWARE 0 #define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/linux/mipsel/vpx_config.h b/third_party/libvpx/source/config/linux/mipsel/vpx_config.h index 38c667e..85c09b7 100644 --- a/third_party/libvpx/source/config/linux/mipsel/vpx_config.h +++ b/third_party/libvpx/source/config/linux/mipsel/vpx_config.h
@@ -91,6 +91,7 @@ #define CONFIG_EXPERIMENTAL 0 #define CONFIG_SIZE_LIMIT 1 #define CONFIG_ALWAYS_ADJUST_BPM 0 +#define CONFIG_BITSTREAM_DEBUG 0 #define CONFIG_FP_MB_STATS 0 #define CONFIG_EMULATE_HARDWARE 0 #define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/linux/x64/vpx_config.asm b/third_party/libvpx/source/config/linux/x64/vpx_config.asm index 613735d8..0d0fef35 100644 --- a/third_party/libvpx/source/config/linux/x64/vpx_config.asm +++ b/third_party/libvpx/source/config/linux/x64/vpx_config.asm
@@ -79,6 +79,7 @@ %define CONFIG_EXPERIMENTAL 0 %define CONFIG_SIZE_LIMIT 1 %define CONFIG_ALWAYS_ADJUST_BPM 0 +%define CONFIG_BITSTREAM_DEBUG 0 %define CONFIG_FP_MB_STATS 0 %define CONFIG_EMULATE_HARDWARE 0 %define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/linux/x64/vpx_config.h b/third_party/libvpx/source/config/linux/x64/vpx_config.h index 677a9fc..c729ddd1 100644 --- a/third_party/libvpx/source/config/linux/x64/vpx_config.h +++ b/third_party/libvpx/source/config/linux/x64/vpx_config.h
@@ -91,6 +91,7 @@ #define CONFIG_EXPERIMENTAL 0 #define CONFIG_SIZE_LIMIT 1 #define CONFIG_ALWAYS_ADJUST_BPM 0 +#define CONFIG_BITSTREAM_DEBUG 0 #define CONFIG_FP_MB_STATS 0 #define CONFIG_EMULATE_HARDWARE 0 #define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/mac/ia32/vpx_config.asm b/third_party/libvpx/source/config/mac/ia32/vpx_config.asm index 5569f05..b95eed2 100644 --- a/third_party/libvpx/source/config/mac/ia32/vpx_config.asm +++ b/third_party/libvpx/source/config/mac/ia32/vpx_config.asm
@@ -79,6 +79,7 @@ %define CONFIG_EXPERIMENTAL 0 %define CONFIG_SIZE_LIMIT 1 %define CONFIG_ALWAYS_ADJUST_BPM 0 +%define CONFIG_BITSTREAM_DEBUG 0 %define CONFIG_FP_MB_STATS 0 %define CONFIG_EMULATE_HARDWARE 0 %define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/mac/ia32/vpx_config.h b/third_party/libvpx/source/config/mac/ia32/vpx_config.h index fff9290..ca9a87a8 100644 --- a/third_party/libvpx/source/config/mac/ia32/vpx_config.h +++ b/third_party/libvpx/source/config/mac/ia32/vpx_config.h
@@ -91,6 +91,7 @@ #define CONFIG_EXPERIMENTAL 0 #define CONFIG_SIZE_LIMIT 1 #define CONFIG_ALWAYS_ADJUST_BPM 0 +#define CONFIG_BITSTREAM_DEBUG 0 #define CONFIG_FP_MB_STATS 0 #define CONFIG_EMULATE_HARDWARE 0 #define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/mac/x64/vpx_config.asm b/third_party/libvpx/source/config/mac/x64/vpx_config.asm index 613735d8..0d0fef35 100644 --- a/third_party/libvpx/source/config/mac/x64/vpx_config.asm +++ b/third_party/libvpx/source/config/mac/x64/vpx_config.asm
@@ -79,6 +79,7 @@ %define CONFIG_EXPERIMENTAL 0 %define CONFIG_SIZE_LIMIT 1 %define CONFIG_ALWAYS_ADJUST_BPM 0 +%define CONFIG_BITSTREAM_DEBUG 0 %define CONFIG_FP_MB_STATS 0 %define CONFIG_EMULATE_HARDWARE 0 %define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/mac/x64/vpx_config.h b/third_party/libvpx/source/config/mac/x64/vpx_config.h index 677a9fc..c729ddd1 100644 --- a/third_party/libvpx/source/config/mac/x64/vpx_config.h +++ b/third_party/libvpx/source/config/mac/x64/vpx_config.h
@@ -91,6 +91,7 @@ #define CONFIG_EXPERIMENTAL 0 #define CONFIG_SIZE_LIMIT 1 #define CONFIG_ALWAYS_ADJUST_BPM 0 +#define CONFIG_BITSTREAM_DEBUG 0 #define CONFIG_FP_MB_STATS 0 #define CONFIG_EMULATE_HARDWARE 0 #define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/nacl/vpx_config.h b/third_party/libvpx/source/config/nacl/vpx_config.h index abc94bfc..45c8a91 100644 --- a/third_party/libvpx/source/config/nacl/vpx_config.h +++ b/third_party/libvpx/source/config/nacl/vpx_config.h
@@ -91,6 +91,7 @@ #define CONFIG_EXPERIMENTAL 0 #define CONFIG_SIZE_LIMIT 1 #define CONFIG_ALWAYS_ADJUST_BPM 0 +#define CONFIG_BITSTREAM_DEBUG 0 #define CONFIG_FP_MB_STATS 0 #define CONFIG_EMULATE_HARDWARE 0 #define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/vpx_version.h b/third_party/libvpx/source/config/vpx_version.h index 6f19050..0f2c371 100644 --- a/third_party/libvpx/source/config/vpx_version.h +++ b/third_party/libvpx/source/config/vpx_version.h
@@ -2,7 +2,7 @@ #define VERSION_MAJOR 1 #define VERSION_MINOR 8 #define VERSION_PATCH 0 -#define VERSION_EXTRA "424-ge50f4e411" +#define VERSION_EXTRA "456-g3fd96f7d7d" #define VERSION_PACKED ((VERSION_MAJOR<<16)|(VERSION_MINOR<<8)|(VERSION_PATCH)) -#define VERSION_STRING_NOSP "v1.8.0-424-ge50f4e411" -#define VERSION_STRING " v1.8.0-424-ge50f4e411" +#define VERSION_STRING_NOSP "v1.8.0-456-g3fd96f7d7d" +#define VERSION_STRING " v1.8.0-456-g3fd96f7d7d"
diff --git a/third_party/libvpx/source/config/win/arm64/vpx_config.asm b/third_party/libvpx/source/config/win/arm64/vpx_config.asm index 0e3d1481..14ce5d5 100644 --- a/third_party/libvpx/source/config/win/arm64/vpx_config.asm +++ b/third_party/libvpx/source/config/win/arm64/vpx_config.asm
@@ -83,6 +83,7 @@ .set CONFIG_EXPERIMENTAL , 0 .set CONFIG_SIZE_LIMIT , 1 .set CONFIG_ALWAYS_ADJUST_BPM , 0 +.set CONFIG_BITSTREAM_DEBUG , 0 .set CONFIG_FP_MB_STATS , 0 .set CONFIG_EMULATE_HARDWARE , 0 .set CONFIG_NON_GREEDY_MV , 0
diff --git a/third_party/libvpx/source/config/win/arm64/vpx_config.h b/third_party/libvpx/source/config/win/arm64/vpx_config.h index e05718c..23d80085 100644 --- a/third_party/libvpx/source/config/win/arm64/vpx_config.h +++ b/third_party/libvpx/source/config/win/arm64/vpx_config.h
@@ -91,6 +91,7 @@ #define CONFIG_EXPERIMENTAL 0 #define CONFIG_SIZE_LIMIT 1 #define CONFIG_ALWAYS_ADJUST_BPM 0 +#define CONFIG_BITSTREAM_DEBUG 0 #define CONFIG_FP_MB_STATS 0 #define CONFIG_EMULATE_HARDWARE 0 #define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/win/ia32/vpx_config.asm b/third_party/libvpx/source/config/win/ia32/vpx_config.asm index 6c3ef6c..8a65579 100644 --- a/third_party/libvpx/source/config/win/ia32/vpx_config.asm +++ b/third_party/libvpx/source/config/win/ia32/vpx_config.asm
@@ -79,6 +79,7 @@ %define CONFIG_EXPERIMENTAL 0 %define CONFIG_SIZE_LIMIT 1 %define CONFIG_ALWAYS_ADJUST_BPM 0 +%define CONFIG_BITSTREAM_DEBUG 0 %define CONFIG_FP_MB_STATS 0 %define CONFIG_EMULATE_HARDWARE 0 %define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/win/ia32/vpx_config.h b/third_party/libvpx/source/config/win/ia32/vpx_config.h index 2d967a0..e0159bd 100644 --- a/third_party/libvpx/source/config/win/ia32/vpx_config.h +++ b/third_party/libvpx/source/config/win/ia32/vpx_config.h
@@ -91,6 +91,7 @@ #define CONFIG_EXPERIMENTAL 0 #define CONFIG_SIZE_LIMIT 1 #define CONFIG_ALWAYS_ADJUST_BPM 0 +#define CONFIG_BITSTREAM_DEBUG 0 #define CONFIG_FP_MB_STATS 0 #define CONFIG_EMULATE_HARDWARE 0 #define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/win/x64/vpx_config.asm b/third_party/libvpx/source/config/win/x64/vpx_config.asm index d104fd3f..07c9b64 100644 --- a/third_party/libvpx/source/config/win/x64/vpx_config.asm +++ b/third_party/libvpx/source/config/win/x64/vpx_config.asm
@@ -79,6 +79,7 @@ %define CONFIG_EXPERIMENTAL 0 %define CONFIG_SIZE_LIMIT 1 %define CONFIG_ALWAYS_ADJUST_BPM 0 +%define CONFIG_BITSTREAM_DEBUG 0 %define CONFIG_FP_MB_STATS 0 %define CONFIG_EMULATE_HARDWARE 0 %define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/libvpx/source/config/win/x64/vpx_config.h b/third_party/libvpx/source/config/win/x64/vpx_config.h index 8a07c942..6fcac54d 100644 --- a/third_party/libvpx/source/config/win/x64/vpx_config.h +++ b/third_party/libvpx/source/config/win/x64/vpx_config.h
@@ -91,6 +91,7 @@ #define CONFIG_EXPERIMENTAL 0 #define CONFIG_SIZE_LIMIT 1 #define CONFIG_ALWAYS_ADJUST_BPM 0 +#define CONFIG_BITSTREAM_DEBUG 0 #define CONFIG_FP_MB_STATS 0 #define CONFIG_EMULATE_HARDWARE 0 #define CONFIG_NON_GREEDY_MV 0
diff --git a/third_party/openvr/BUILD.gn b/third_party/openvr/BUILD.gn index b4f53a8..83ce4120 100644 --- a/third_party/openvr/BUILD.gn +++ b/third_party/openvr/BUILD.gn
@@ -71,4 +71,8 @@ "src/src/vrcommon", "//third_party/jsoncpp/source/include", ] + + if (is_win) { + ldflags = [ "/DELAYLOAD:shell32.dll" ] + } }
diff --git a/third_party/pffft/BUILD.gn b/third_party/pffft/BUILD.gn index 17b85b5..069eec5 100644 --- a/third_party/pffft/BUILD.gn +++ b/third_party/pffft/BUILD.gn
@@ -13,7 +13,9 @@ "_USE_MATH_DEFINES", ] } - if (current_cpu == "arm" && !arm_use_neon) { + + # PFFFT doesn't support SIMD on mipsel, so build a scalar version. + if ((current_cpu == "arm" && !arm_use_neon) || current_cpu == "mipsel") { defines = [ "PFFFT_SIMD_DISABLE" ] } }
diff --git a/tools/gdb/gdbinit b/tools/gdb/gdbinit index 78d3933..4f8c7fc 100644 --- a/tools/gdb/gdbinit +++ b/tools/gdb/gdbinit
@@ -81,6 +81,21 @@ load_gdb_chrome(src_dir) +lsb_release = subprocess.Popen(['lsb_release', '-sc'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) +codename = lsb_release.communicate()[0] +if codename.rstrip() == b'rodete' and 'gg' not in gdb.VERSION: + sys.stderr.write( + '\033[93mDetected gLinux without using google-gdb. google-gdb is ' + 'recommended because it is more up-to-date than the system gdb, which ' + 'has known issues (https://crbug.com/957374). To switch to google-gdb, ' + 'run "sudo apt install google-gdb" which will *uninstall* gdb and ' + '*install* google-gdb. This is a drop-in replacement with the same ' + 'command line usage, so you can still run eg. ' + '"gdb out/Debug/chrome".\033[0m\n' + ) + # Event hook for newly loaded objfiles. # https://sourceware.org/gdb/onlinedocs/gdb/Events-In-Python.html gdb.events.new_objfile.connect(newobj_handler)
diff --git a/tools/gritsettings/translation_expectations.pyl b/tools/gritsettings/translation_expectations.pyl index 8d45a90..733e055 100644 --- a/tools/gritsettings/translation_expectations.pyl +++ b/tools/gritsettings/translation_expectations.pyl
@@ -78,6 +78,7 @@ "chrome/app/resources/locale_settings_linux.grd": "Not UI strings; localized separately", "chrome/app/resources/locale_settings_mac.grd": "Not UI strings; localized separately", "chrome/app/resources/locale_settings_win.grd": "Not UI strings; localized separately", + "chrome/browser/resources/kiosk_next_internal_resources.grd": "Internal resources; localized separately.", "chromecast/app/resources/chromecast_settings.grd": "Not UI strings; localized separately", "cloud_print/virtual_driver/win/install/virtual_driver_setup_resources.grd": "Separate release process", "components/components_locale_settings.grd": "Not UI strings; localized separately",
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index a796f52..bd2c455 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml
@@ -42806,6 +42806,12 @@ <int value="6" label="/tmp, different volume"/> </enum> +<enum name="OSXStartupUpdateState"> + <int value="0" label="Update key not set"/> + <int value="1" label="Update key set, staged copy present"/> + <int value="2" label="Update key set, staged copy not present"/> +</enum> + <enum name="OtherPossibleUsernamesUsage"> <int value="0" label="Nothing to Autofill"/> <int value="1" label="No other possible usernames"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index 85b9aec..5d20341a 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml
@@ -15580,7 +15580,7 @@ <histogram name="Cast.Sender.CastMediaType" enum="MediaContainers"> <owner>takumif@chromium.org</owner> <owner>mfoltz@chromium.org</owner> - <owner>openscreen-team@google.com</owner> + <owner>openscreen-eng@google.com</owner> <summary>Records the media type of every video being cast.</summary> </histogram> @@ -31223,7 +31223,7 @@ </histogram> <histogram name="EphemeralTab.CloseReason" - enum="OverlayPanel.StateChangeReason" expires_after="M76"> + enum="OverlayPanel.StateChangeReason" expires_after="M80"> <owner>donnd@chromium.org</owner> <owner>jinsukkim@chromium.org</owner> <summary> @@ -31232,7 +31232,7 @@ </summary> </histogram> -<histogram name="EphemeralTab.Ctr" enum="BooleanOpened" expires_after="M76"> +<histogram name="EphemeralTab.Ctr" enum="BooleanOpened" expires_after="M80"> <owner>donnd@chromium.org</owner> <owner>jinsukkim@chromium.org</owner> <summary> @@ -31241,6 +31241,24 @@ </summary> </histogram> +<histogram name="EphemeralTab.DurationOpened" units="ms" expires_after="M80"> + <owner>donnd@chromium.org</owner> + <owner>jinsukkim@chromium.org</owner> + <summary> + Records the duration in milliseconds that the Ephemeral Tab was left open. + Recorded when the UX is hidden. Implemented for Android. + </summary> +</histogram> + +<histogram name="EphemeralTab.DurationPeeked" units="ms" expires_after="M80"> + <owner>donnd@chromium.org</owner> + <owner>jinsukkim@chromium.org</owner> + <summary> + Records the duration in milliseconds that the Ephemeral Tab was peeking. + Recorded when the UX is hidden. Implemented for Android. + </summary> +</histogram> + <histogram name="Event.ActionAfterDoubleTapNoDelay" enum="ActionAfterDoubleTap" expires_after="2017-06-26"> <obsolete> @@ -55907,7 +55925,7 @@ <owner>takumif@chromium.org</owner> <owner>mfoltz@chromium.org</owner> - <owner>openscreen-team@google.com</owner> + <owner>openscreen-eng@google.com</owner> <summary> Round trip time for a Cast app availability request. Can be suffixed with Success or Failure. @@ -55992,7 +56010,7 @@ enum="MediaRouterDialCreateRouteResult"> <owner>takumif@chromium.org</owner> <owner>mfoltz@chromium.org</owner> - <owner>openscreen-team@google.com</owner> + <owner>openscreen-eng@google.com</owner> <summary> The result of a DIAL CreateRoute request. Recorded when the user requests to create a media route to a DIAL device. @@ -56003,7 +56021,7 @@ enum="MediaRouterDialFetchAppInfoResult"> <owner>takumif@chromium.org</owner> <owner>mfoltz@chromium.org</owner> - <owner>openscreen-team@google.com</owner> + <owner>openscreen-eng@google.com</owner> <summary> The result of a DIAL app info request. Recorded when an app info request is issued to a DIAL device. @@ -56022,7 +56040,7 @@ enum="MediaRouterDialParseMessageResult"> <owner>takumif@chromium.org</owner> <owner>mfoltz@chromium.org</owner> - <owner>openscreen-team@google.com</owner> + <owner>openscreen-eng@google.com</owner> <summary> The result of parsing a Cast SDK message in the DIAL media route provider. Recorded when the DIAL media route prover finishes parsing a Cast SDK @@ -56044,7 +56062,7 @@ enum="MediaRouterDialTerminateRouteResult"> <owner>takumif@chromium.org</owner> <owner>mfoltz@chromium.org</owner> - <owner>openscreen-team@google.com</owner> + <owner>openscreen-eng@google.com</owner> <summary> The result of a DIAL TerminateRoute request. Recorded the user requests to terminate a DIAL media route. @@ -56079,7 +56097,7 @@ enum="PresentationUrlType" expires_after="M77"> <owner>takumif@chromium.org</owner> <owner>mfoltz@chromium.org</owner> - <owner>openscreen-team@google.com</owner> + <owner>openscreen-eng@google.com</owner> <summary> The type of Presentation URL used in a PresentationRequest by a web page. </summary> @@ -56089,7 +56107,7 @@ enum="MediaRouteProviderResult"> <owner>takumif@chromium.org</owner> <owner>mfoltz@chromium.org</owner> - <owner>openscreen-team@google.com</owner> + <owner>openscreen-eng@google.com</owner> <summary> Result of a request to the extension (or an unknown) MediaRouteProvider to create a route. @@ -56109,7 +56127,7 @@ enum="MediaRouteProviderResult"> <owner>takumif@chromium.org</owner> <owner>mfoltz@chromium.org</owner> - <owner>openscreen-team@google.com</owner> + <owner>openscreen-eng@google.com</owner> <summary> Result of a request to the extension (or an unknown) MediaRouteProvider to join a route. @@ -83231,6 +83249,16 @@ </summary> </histogram> +<histogram name="OSX.StartupUpdateState" enum="OSXStartupUpdateState" + expires_after="2019-12-31"> + <owner>avi@chromium.org</owner> + <owner>rsesek@chromium.org</owner> + <owner>mark@chromium.org</owner> + <summary> + Records, during Chrome startup, the state of any staged updates. + </summary> +</histogram> + <histogram name="OSX.SystemHotkeyMap.LoadSuccess" enum="BooleanSuccess" expires_after="2018-08-30"> <owner>erikchen@chromium.org</owner>
diff --git a/tools/perf/benchmark.csv b/tools/perf/benchmark.csv index 7896bc7..6dbf0aaf 100644 --- a/tools/perf/benchmark.csv +++ b/tools/perf/benchmark.csv
@@ -41,7 +41,10 @@ rasterize_and_record_micro.top_25,"vmpstr@chromium.org, wkorman@chromium.org",Internals>Compositing>Rasterization,, rendering.desktop,"sadrul@chromium.org, vmiura@chromium.org",Internals>GPU>Metrics,https://bit.ly/rendering-benchmarks,"backdrop_filter,gpu_rasterization,image_decoding,key_desktop_move,maps,repaint_desktop,representative_mac_desktop,representative_win_desktop,required_webgl,top_real_world_desktop,tough_animation,tough_canvas,tough_compositor,tough_filters,tough_image_decode,tough_path_rendering,tough_pinch_zoom,tough_scheduling,tough_scrolling,tough_texture_upload,tough_webgl,use_fake_camera_device" rendering.mobile,"sadrul@chromium.org, vmiura@chromium.org",Internals>GPU>Metrics,https://bit.ly/rendering-benchmarks,"backdrop_filter,fastpath,gpu_rasterization,image_decoding,key_hit_test,key_idle_power,key_noop,key_silk,maps,motionmark,pathological_mobile_sites,polymer,representative_mac_desktop,representative_win_desktop,required_webgl,simple_mobile_sites,top_real_world_desktop,top_real_world_mobile,tough_animation,tough_canvas,tough_compositor,tough_filters,tough_image_decode,tough_path_rendering,tough_pinch_zoom_mobile,tough_scheduling,tough_scrolling,tough_texture_upload,tough_webgl,use_fake_camera_device" -resource_sizes,"agrieve@chromium.org, rnephew@chromium.org, perezju@chromium.org",,, +resource_sizes_chrome_modern_public_minimal_apks,"agrieve@chromium.org, jbudorick@chromium.org, perezju@chromium.org",BUILD,https://chromium.googlesource.com/chromium/src/+/HEAD/tools/binary_size/README.md#resource_sizes_py, +resource_sizes_chrome_public_apk,"agrieve@chromium.org, jbudorick@chromium.org, perezju@chromium.org",BUILD,https://chromium.googlesource.com/chromium/src/+/HEAD/tools/binary_size/README.md#resource_sizes_py, +resource_sizes_monochrome_public_minimal_apks,"agrieve@chromium.org, jbudorick@chromium.org, perezju@chromium.org",BUILD,https://chromium.googlesource.com/chromium/src/+/HEAD/tools/binary_size/README.md#resource_sizes_py, +resource_sizes_system_webview_apk,"agrieve@chromium.org, jbudorick@chromium.org, perezju@chromium.org",BUILD,https://chromium.googlesource.com/chromium/src/+/HEAD/tools/binary_size/README.md#resource_sizes_py, sizes (linux),thestig@chromium.org,thomasanderson@chromium.org,Internals>PlatformIntegration, sizes (mac),tapted@chromium.org,,, sizes (win),grt@chromium.org,Internals>PlatformIntegration,,
diff --git a/tools/perf/core/bot_platforms.py b/tools/perf/core/bot_platforms.py index 30f8121..8e0e6fa5 100644 --- a/tools/perf/core/bot_platforms.py +++ b/tools/perf/core/bot_platforms.py
@@ -129,6 +129,16 @@ # Android +ANDROID_ARM_BUILDER = PerfPlatform( + 'android-builder-perf', + 'Static analysis of 32-bit ARM Android build products', + num_shards=1) + +ANDROID_ARM64_BUILDER = PerfPlatform( + 'android_arm64-builder-perf', + 'Static analysis of 64-bit ARM Android build products.', + num_shards=1) + _ANDROID_GO_BENCHMARK_NAMES = { 'memory.top_10_mobile', 'system_health.memory_mobile', @@ -141,7 +151,6 @@ 'speedometer2' } - ANDROID_GO = PerfPlatform( 'android-go-perf', 'Android O (gobo)', num_shards=19,
diff --git a/tools/perf/core/perf_data_generator.py b/tools/perf/core/perf_data_generator.py index 4cab5a4..57eb8da5 100755 --- a/tools/perf/core/perf_data_generator.py +++ b/tools/perf/core/perf_data_generator.py
@@ -56,9 +56,9 @@ # # The number of shards for this test as an int. # 'num_shards': 2, # -# # Whether this is a telemetry test, as a boolean. -# # Defaults to True. -# 'telemetry': True, +# # What kind of test this is; for options, see TEST_TYPES +# # below. Defaults to TELEMETRY. +# 'type': TEST_TYPES.TELEMETRY, # }, # ... # ], @@ -76,6 +76,14 @@ # ... # } +class TEST_TYPES(object): + GENERIC = 0 + GTEST = 1 + TELEMETRY = 2 + + ALL = (GENERIC, GTEST, TELEMETRY) + + # TODO(crbug.com/902089): automatically generate --test-shard-map-filename # arguments once we track all the perf FYI builders to core/bot_platforms.py FYI_BUILDERS = { @@ -177,11 +185,65 @@ 'additional_compile_targets': [ 'microdump_stackwalk', 'angle_perftests', 'chrome_apk' ], + 'tests': [ + { + 'name': 'resource_sizes_chrome_public_apk', + 'isolate': 'resource_sizes_chrome_public_apk', + 'type': TEST_TYPES.GENERIC, + }, + { + 'name': 'resource_sizes_monochrome_public_minimal_apks', + 'isolate': 'resource_sizes_monochrome_public_minimal_apks', + 'type': TEST_TYPES.GENERIC, + }, + { + 'name': 'resource_sizes_chrome_modern_public_minimal_apks', + 'isolate': 'resource_sizes_chrome_modern_public_minimal_apks', + 'type': TEST_TYPES.GENERIC, + }, + { + 'name': 'resource_sizes_system_webview_apk', + 'isolate': 'resource_sizes_system_webview_apk', + 'type': TEST_TYPES.GENERIC, + }, + ], + 'dimension': { + 'os': 'Ubuntu-14.04', + 'pool': 'chrome.tests', + }, + 'perf_trigger': False, }, 'android_arm64-builder-perf': { 'additional_compile_targets': [ 'microdump_stackwalk', 'angle_perftests', 'chrome_apk' ], + 'tests': [ + { + 'name': 'resource_sizes_chrome_public_apk', + 'isolate': 'resource_sizes_chrome_public_apk', + 'type': TEST_TYPES.GENERIC, + }, + { + 'name': 'resource_sizes_monochrome_public_minimal_apks', + 'isolate': 'resource_sizes_monochrome_public_minimal_apks', + 'type': TEST_TYPES.GENERIC, + }, + { + 'name': 'resource_sizes_chrome_modern_public_minimal_apks', + 'isolate': 'resource_sizes_chrome_modern_public_minimal_apks', + 'type': TEST_TYPES.GENERIC, + }, + { + 'name': 'resource_sizes_system_webview_apk', + 'isolate': 'resource_sizes_system_webview_apk', + 'type': TEST_TYPES.GENERIC, + }, + ], + 'dimension': { + 'os': 'Ubuntu-14.04', + 'pool': 'chrome.tests', + }, + 'perf_trigger': False, }, 'linux-builder-perf': { 'additional_compile_targets': ['chromedriver'], @@ -250,27 +312,27 @@ { 'isolate': 'media_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'components_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'tracing_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'gpu_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'angle_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, 'extra_args': [ '--shard-timeout=300' ], @@ -278,7 +340,7 @@ { 'isolate': 'base_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, } ], 'platform': 'android', @@ -304,17 +366,17 @@ { 'isolate': 'tracing_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'components_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'gpu_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, ], 'platform': 'android', @@ -380,7 +442,7 @@ { 'isolate': 'angle_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, 'extra_args': [ '--shard-timeout=300' ], @@ -388,22 +450,22 @@ { 'isolate': 'media_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'components_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'views_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'base_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, } ], 'platform': 'win', @@ -427,17 +489,17 @@ { 'isolate': 'load_library_perf_tests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'components_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'media_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, } ], 'platform': 'win', @@ -462,23 +524,23 @@ { 'isolate': 'load_library_perf_tests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'angle_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'media_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'name': 'passthrough_command_buffer_perftests', 'isolate': 'command_buffer_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, 'extra_args': [ '--use-cmd-decoder=passthrough', '--use-angle=gl-null', @@ -488,7 +550,7 @@ 'name': 'validating_command_buffer_perftests', 'isolate': 'command_buffer_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, 'extra_args': [ '--use-cmd-decoder=validating', '--use-stub', @@ -518,12 +580,12 @@ { 'isolate': 'performance_browser_tests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'load_library_perf_tests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, } ], 'platform': 'mac', @@ -548,32 +610,32 @@ { 'isolate': 'performance_browser_tests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'load_library_perf_tests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'net_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'tracing_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'media_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'base_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, } ], 'platform': 'linux', @@ -597,27 +659,27 @@ { 'isolate': 'performance_browser_tests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'net_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'views_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'media_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, }, { 'isolate': 'base_perftests', 'num_shards': 1, - 'telemetry': False, + 'type': TEST_TYPES.GTEST, } ], 'platform': 'mac', @@ -658,7 +720,7 @@ self.tags = tags -NON_TELEMETRY_BENCHMARKS = { +GTEST_BENCHMARKS = { 'angle_perftests': BenchmarkMetadata( 'jmadill@chromium.org, chrome-gpu-perf-owners@chromium.org', 'Internals>GPU>ANGLE'), @@ -695,6 +757,21 @@ } +RESOURCE_SIZES_METADATA = BenchmarkMetadata( + 'agrieve@chromium.org, jbudorick@chromium.org, perezju@chromium.org', + 'BUILD', + ('https://chromium.googlesource.com/chromium/src/+/HEAD/' + 'tools/binary_size/README.md#resource_sizes_py')) + + +OTHER_BENCHMARKS = { + 'resource_sizes_chrome_public_apk': RESOURCE_SIZES_METADATA, + 'resource_sizes_chrome_modern_public_minimal_apks': RESOURCE_SIZES_METADATA, + 'resource_sizes_monochrome_public_minimal_apks': RESOURCE_SIZES_METADATA, + 'resource_sizes_system_webview_apk': RESOURCE_SIZES_METADATA, +} + + # If you change this dictionary, run tools/perf/generate_perf_data NON_WATERFALL_BENCHMARKS = { 'sizes (mac)': @@ -704,8 +781,6 @@ 'sizes (linux)': BenchmarkMetadata( 'thestig@chromium.org', 'thomasanderson@chromium.org', 'Internals>PlatformIntegration'), - 'resource_sizes': BenchmarkMetadata( - 'agrieve@chromium.org, rnephew@chromium.org, perezju@chromium.org'), 'supersize_archive': BenchmarkMetadata('agrieve@chromium.org'), } @@ -729,7 +804,7 @@ TELEMETRY_PERF_BENCHMARKS = _get_telemetry_perf_benchmarks_metadata() ALL_PERF_WATERFALL_BENCHMARKS_METADATA = merge_dicts( - TELEMETRY_PERF_BENCHMARKS, NON_TELEMETRY_BENCHMARKS) + TELEMETRY_PERF_BENCHMARKS, GTEST_BENCHMARKS, OTHER_BENCHMARKS) # With migration to new recipe tests are now listed in the shard maps @@ -791,7 +866,8 @@ perf_waterfall_file) all_perf_telemetry_tests = set(TELEMETRY_PERF_BENCHMARKS) - all_perf_non_telemetry_tests = set(NON_TELEMETRY_BENCHMARKS) + all_perf_gtests = set(GTEST_BENCHMARKS) + all_perf_other_tests = set(OTHER_BENCHMARKS) error_messages = [] @@ -812,17 +888,24 @@ 'dependency code, e.g: stories, WPR archives, metrics, etc.' % test_name) - for test_name in all_perf_non_telemetry_tests - scheduled_non_telemetry_tests: + for test_name in all_perf_gtests - scheduled_non_telemetry_tests: error_messages.append( 'Benchmark %s is tracked but not scheduled on any perf waterfall ' - 'builders. Either schedule or remove it from NON_TELEMETRY_BENCHMARKS.' + 'builders. Either schedule or remove it from GTEST_BENCHMARKS.' % test_name) - for test_name in scheduled_non_telemetry_tests - all_perf_non_telemetry_tests: + for test_name in all_perf_other_tests - scheduled_non_telemetry_tests: + error_messages.append( + 'Benchmark %s is tracked but not scheduled on any perf waterfall ' + 'builders. Either schedule or remove it from OTHER_BENCHMARKS.' + % test_name) + + for test_name in scheduled_non_telemetry_tests.difference( + all_perf_gtests, all_perf_other_tests): error_messages.append( 'Benchmark %s is scheduled on perf waterfall but not tracked. Please ' - 'add an entry for it in ' - 'perf.core.perf_data_generator.NON_TELEMETRY_BENCHMARKS.' % test_name) + 'add an entry for it in GTEST_BENCHMARKS or OTHER_BENCHMARKS in' + '//tools/perf/core/perf_data_generator.py.' % test_name) for message in error_messages: print >> outstream, '*', textwrap.fill(message, 70), '\n' @@ -856,7 +939,7 @@ csv_data = [] benchmark_metadatas = merge_dicts( - NON_TELEMETRY_BENCHMARKS, TELEMETRY_PERF_BENCHMARKS, + GTEST_BENCHMARKS, OTHER_BENCHMARKS, TELEMETRY_PERF_BENCHMARKS, NON_WATERFALL_BENCHMARKS) _verify_benchmark_owners(benchmark_metadatas) @@ -948,20 +1031,6 @@ os.remove(labs_docs_tempfile) -def add_common_test_properties(test_entry): - test_entry['trigger_script'] = { - 'requires_simultaneous_shard_dispatch': True, - 'script': '//testing/trigger_scripts/perf_device_trigger.py', - 'args': [ - '--multiple-dimension-script-verbose', - 'True' - ], - } - - test_entry['merge'] = { - 'script': '//tools/perf/process_perf_results.py', - } - def generate_telemetry_args(tester_config): # First determine the browser that you need based on the tester browser_name = '' @@ -991,7 +1060,7 @@ return test_args -def generate_non_telemetry_args(test_name): +def generate_gtest_args(test_name): # --gtest-benchmark-name so the benchmark name is consistent with the test # step's name. This is not always the same as the test binary's name (see # crbug.com/870692). @@ -1004,11 +1073,14 @@ isolate_name = test['isolate'] test_name = test.get('name', isolate_name) + test_type = test.get('type', TEST_TYPES.TELEMETRY) + assert test_type in TEST_TYPES.ALL - if test.get('telemetry', True): - test_args = generate_telemetry_args(tester_config) - else: - test_args = generate_non_telemetry_args(test_name=test_name) + test_args = [] + if test_type == TEST_TYPES.TELEMETRY: + test_args += generate_telemetry_args(tester_config) + elif test_type == TEST_TYPES.GTEST: + test_args += generate_gtest_args(test_name=test_name) # Append any additional args specific to an isolate test_args += test.get('extra_args', []) @@ -1020,10 +1092,25 @@ isolate_name ] } + # For now we either get shards from the number of devices specified # or a test entry needs to specify the num shards if it supports # soft device affinity. - add_common_test_properties(result) + + if tester_config.get('perf_trigger', True): + result['trigger_script'] = { + 'requires_simultaneous_shard_dispatch': True, + 'script': '//testing/trigger_scripts/perf_device_trigger.py', + 'args': [ + '--multiple-dimension-script-verbose', + 'True' + ], + } + + result['merge'] = { + 'script': '//tools/perf/process_perf_results.py', + } + shards = test.get('num_shards') result['swarming'] = { # Always say this is true regardless of whether the tester @@ -1054,20 +1141,29 @@ condensed_tests = condensed_config.get('tests') if condensed_tests: - telemetry_tests = [] gtest_tests = [] + telemetry_tests = [] + other_tests = [] for test in condensed_tests: generated_script = generate_performance_test(condensed_config, test) - if test.get('telemetry', True): - telemetry_tests.append(generated_script) - else: + test_type = test.get('type', TEST_TYPES.TELEMETRY) + if test_type == TEST_TYPES.GTEST: gtest_tests.append(generated_script) - telemetry_tests.sort(key=lambda x: x['name']) + elif test_type == TEST_TYPES.TELEMETRY: + telemetry_tests.append(generated_script) + elif test_type == TEST_TYPES.GENERIC: + other_tests.append(generated_script) + else: + raise ValueError( + 'perf_data_generator.py does not understand test type %s.' + % test_type) gtest_tests.sort(key=lambda x: x['name']) + telemetry_tests.sort(key=lambda x: x['name']) + other_tests.sort(key=lambda x: x['name']) # Put Telemetry tests as the end since they tend to run longer to avoid # starving gtests (see crbug.com/873389). - config['isolated_scripts'] = gtest_tests + telemetry_tests + config['isolated_scripts'] = gtest_tests + telemetry_tests + other_tests return config
diff --git a/tools/perf/core/perf_data_generator_unittest.py b/tools/perf/core/perf_data_generator_unittest.py index 3f326e7..06231c24 100644 --- a/tools/perf/core/perf_data_generator_unittest.py +++ b/tools/perf/core/perf_data_generator_unittest.py
@@ -63,8 +63,8 @@ } test = { 'isolate': 'angle_perftest', - 'telemetry': False, - 'num_shards': 1 + 'num_shards': 1, + 'type': perf_data_generator.TEST_TYPES.GTEST, } returned_test = perf_data_generator.generate_performance_test( test_config, test) @@ -201,10 +201,12 @@ class TestIsPerfBenchmarksSchedulingValid(unittest.TestCase): def setUp(self): self.maxDiff = None - self.original_NON_TELEMETRY_BENCHMARKS = copy.deepcopy( - perf_data_generator.NON_TELEMETRY_BENCHMARKS) + self.original_GTEST_BENCHMARKS = copy.deepcopy( + perf_data_generator.GTEST_BENCHMARKS) self.original_TELEMETRY_PERF_BENCHMARKS = copy.deepcopy( - perf_data_generator.NON_TELEMETRY_BENCHMARKS) + perf_data_generator.TELEMETRY_PERF_BENCHMARKS) + self.original_OTHER_BENCHMARKS = copy.deepcopy( + perf_data_generator.OTHER_BENCHMARKS) self.test_stream = cStringIO.StringIO() self.mock_get_telemetry_benchmarks = mock.patch( 'core.perf_data_generator.get_telemetry_tests_in_performance_test_suite' @@ -219,8 +221,10 @@ def tearDown(self): perf_data_generator.TELEMETRY_PERF_BENCHMARKS = ( self.original_TELEMETRY_PERF_BENCHMARKS) - perf_data_generator.NON_TELEMETRY_BENCHMARKS = ( - self.original_NON_TELEMETRY_BENCHMARKS) + perf_data_generator.GTEST_BENCHMARKS = ( + self.original_GTEST_BENCHMARKS) + perf_data_generator.OTHER_BENCHMARKS = ( + self.original_OTHER_BENCHMARKS) self.mock_get_telemetry_benchmarks.stop() self.mock_get_non_telemetry_benchmarks.stop() @@ -232,9 +236,10 @@ 't_foo': BenchmarkMetadata('t@foo.com'), 't_bar': BenchmarkMetadata('t@bar.com'), } - perf_data_generator.NON_TELEMETRY_BENCHMARKS = { + perf_data_generator.GTEST_BENCHMARKS = { 'honda': BenchmarkMetadata('baz@foo.com'), } + perf_data_generator.OTHER_BENCHMARKS = {} valid = perf_data_generator.is_perf_benchmarks_scheduling_valid( 'dummy', self.test_stream) @@ -250,9 +255,10 @@ 'darth.vader': BenchmarkMetadata('death@star.com'), 't_bar': BenchmarkMetadata('t@bar.com'), } - perf_data_generator.NON_TELEMETRY_BENCHMARKS = { + perf_data_generator.GTEST_BENCHMARKS = { 'honda': BenchmarkMetadata('baz@foo.com'), } + perf_data_generator.OTHER_BENCHMARKS = {} valid = perf_data_generator.is_perf_benchmarks_scheduling_valid( 'dummy', self.test_stream) @@ -267,10 +273,11 @@ perf_data_generator.TELEMETRY_PERF_BENCHMARKS = { 't_bar': BenchmarkMetadata('t@bar.com'), } - perf_data_generator.NON_TELEMETRY_BENCHMARKS = { + perf_data_generator.GTEST_BENCHMARKS = { 'honda': BenchmarkMetadata('baz@foo.com'), 'toyota': BenchmarkMetadata('baz@foo.com'), } + perf_data_generator.OTHER_BENCHMARKS = {} valid = perf_data_generator.is_perf_benchmarks_scheduling_valid( 'dummy', self.test_stream) @@ -286,9 +293,10 @@ perf_data_generator.TELEMETRY_PERF_BENCHMARKS = { 't_bar': BenchmarkMetadata('t@bar.com'), } - perf_data_generator.NON_TELEMETRY_BENCHMARKS = { + perf_data_generator.GTEST_BENCHMARKS = { 'honda': BenchmarkMetadata('baz@foo.com'), } + perf_data_generator.OTHER_BENCHMARKS = {} valid = perf_data_generator.is_perf_benchmarks_scheduling_valid( 'dummy', self.test_stream) @@ -303,9 +311,10 @@ perf_data_generator.TELEMETRY_PERF_BENCHMARKS = { 't_bar': BenchmarkMetadata('t@bar.com'), } - perf_data_generator.NON_TELEMETRY_BENCHMARKS = { + perf_data_generator.GTEST_BENCHMARKS = { 'honda': BenchmarkMetadata('baz@foo.com'), } + perf_data_generator.OTHER_BENCHMARKS = {} valid = perf_data_generator.is_perf_benchmarks_scheduling_valid( 'dummy', self.test_stream)
diff --git a/tools/perf/core/perf_json_config_validator.py b/tools/perf/core/perf_json_config_validator.py index df7cd111..28c26c2d 100644 --- a/tools/perf/core/perf_json_config_validator.py +++ b/tools/perf/core/perf_json_config_validator.py
@@ -12,9 +12,13 @@ _VALID_SWARMING_DIMENSIONS = { 'gpu', 'device_ids', 'os', 'pool', 'perf_tests', 'perf_tests_with_args', 'device_os', 'device_type', 'device_os_flavor', 'id'} -_VALID_PERF_POOLS = { +_DEFAULT_VALID_PERF_POOLS = { 'chrome.tests.perf', 'chrome.tests.perf-webview', 'chrome.tests.perf-fyi', 'chrome.tests.perf-webview-fyi'} +_VALID_PERF_POOLS = { + 'android-builder-perf': {'chrome.tests'}, + 'android_arm64-builder-perf': {'chrome.tests'}, +} def _ValidateSwarmingDimension(builder_name, swarming_dimensions): @@ -23,7 +27,8 @@ if k not in _VALID_SWARMING_DIMENSIONS: raise ValueError('Invalid swarming dimension in %s: %s' % ( builder_name, k)) - if k == 'pool' and v not in _VALID_PERF_POOLS: + if k == 'pool' and v not in _VALID_PERF_POOLS.get( + builder_name, _DEFAULT_VALID_PERF_POOLS): raise ValueError('Invalid perf pool %s in %s' % (v, builder_name)) if k == 'os' and v == 'Android': if (not 'device_type' in dimension.keys() or @@ -161,8 +166,8 @@ 'in core.bot_platforms. Please update the platforms in ' 'bot_platforms.py.\nPlatforms should be aded to core.bot_platforms:%s' '\nPlatforms should be removed from core.bot_platforms:%s' % ( - perf_testing_builder_names - bot_platforms.ALL_PLATFORM_NAMES, - bot_platforms.ALL_PLATFORM_NAMES - perf_testing_builder_names)) + perf_testing_builder_names - bot_platforms.ALL_PERF_PLATFORM_NAMES, + bot_platforms.ALL_PERF_PLATFORM_NAMES - perf_testing_builder_names)) def main(args):
diff --git a/tools/perf/core/undocumented_benchmarks.py b/tools/perf/core/undocumented_benchmarks.py index 873f02c..31dca43bb 100644 --- a/tools/perf/core/undocumented_benchmarks.py +++ b/tools/perf/core/undocumented_benchmarks.py
@@ -26,7 +26,6 @@ 'performance_browser_tests', 'rasterize_and_record_micro.partial_invalidation', 'rasterize_and_record_micro.top_25', - 'resource_sizes', 'sizes (mac)', 'sizes (win)', 'speedometer',
diff --git a/tools/perf/generate_perf_sharding b/tools/perf/generate_perf_sharding index 33766624..6bbb7d45 100755 --- a/tools/perf/generate_perf_sharding +++ b/tools/perf/generate_perf_sharding
@@ -174,9 +174,6 @@ def _UpdateShardsForBuilders(args): - if options.builders and options.waterfall: - parser.error('Cannot specify both --builders and --waterfall ' - 'at the same time') if args.builders: builders = {b for b in bot_platforms.ALL_PLATFORMS if b.name in args.builders}
diff --git a/ui/accessibility/platform/ax_platform_node_mac.mm b/ui/accessibility/platform/ax_platform_node_mac.mm index 73c44a2..bdb3623 100644 --- a/ui/accessibility/platform/ax_platform_node_mac.mm +++ b/ui/accessibility/platform/ax_platform_node_mac.mm
@@ -162,7 +162,7 @@ {ax::mojom::Role::kMenuItemCheckBox, NSAccessibilityMenuItemRole}, {ax::mojom::Role::kMenuItemRadio, NSAccessibilityMenuItemRole}, {ax::mojom::Role::kMenuListOption, NSAccessibilityMenuItemRole}, - {ax::mojom::Role::kMenuListPopup, NSAccessibilityUnknownRole}, + {ax::mojom::Role::kMenuListPopup, NSAccessibilityMenuRole}, {ax::mojom::Role::kMeter, NSAccessibilityLevelIndicatorRole}, {ax::mojom::Role::kNavigation, NSAccessibilityGroupRole}, {ax::mojom::Role::kNone, NSAccessibilityGroupRole},
diff --git a/ui/android/display_android_manager.cc b/ui/android/display_android_manager.cc index 53cd4aa..8509bd7b 100644 --- a/ui/android/display_android_manager.cc +++ b/ui/android/display_android_manager.cc
@@ -79,7 +79,7 @@ int bitsPerComponent, bool isWideColorGamut) { if (!Display::HasForceDeviceScaleFactor()) - display->SetDeviceScaleFactor(dipScale); + display->set_device_scale_factor(dipScale); if (!Display::HasForceDisplayColorProfile()) { if (isWideColorGamut) { display->set_color_space(gfx::ColorSpace::CreateDisplayP3D65());
diff --git a/ui/aura/window_tree_host.cc b/ui/aura/window_tree_host.cc index 9f5b0e6..9fdcf7f3 100644 --- a/ui/aura/window_tree_host.cc +++ b/ui/aura/window_tree_host.cc
@@ -28,6 +28,7 @@ #include "ui/compositor/compositor_switches.h" #include "ui/compositor/dip_util.h" #include "ui/compositor/layer.h" +#include "ui/display/display.h" #include "ui/display/screen.h" #include "ui/events/keycodes/dom/dom_code.h" #include "ui/gfx/geometry/insets.h" @@ -107,7 +108,9 @@ } void WindowTreeHost::InitHost() { - device_scale_factor_ = GetDisplay().device_scale_factor(); + display::Display display = + display::Screen::GetScreen()->GetDisplayNearestWindow(window()); + device_scale_factor_ = display.device_scale_factor(); UpdateRootWindowSizeInPixels(); InitCompositor(); @@ -275,7 +278,7 @@ } int64_t WindowTreeHost::GetDisplayId() { - return GetDisplay().id(); + return display::Screen::GetScreen()->GetDisplayNearestWindow(window()).id(); } void WindowTreeHost::Show() { @@ -348,11 +351,8 @@ if (!window_) window_ = new Window(nullptr); display::Screen::GetScreen()->AddObserver(this); - device_scale_factor_ = GetDisplay().device_scale_factor(); -} - -display::Display WindowTreeHost::GetDisplay() const { - return display::Screen::GetScreen()->GetDisplayNearestWindow(window_); + auto display = display::Screen::GetScreen()->GetDisplayNearestWindow(window_); + device_scale_factor_ = display.device_scale_factor(); } void WindowTreeHost::IntializeDeviceScaleFactor(float device_scale_factor) { @@ -379,7 +379,7 @@ // ~Window, but by that time any calls to virtual methods overriden here (such // as GetRootWindow()) result in Window's implementation. By destroying here // we ensure GetRootWindow() still returns this. - // window()->RemoveOrDestroyChildren(); + //window()->RemoveOrDestroyChildren(); } void WindowTreeHost::CreateCompositor( @@ -420,7 +420,8 @@ window()->GetLocalSurfaceIdAllocation()); compositor_->SetRootLayer(window()->layer()); - display::Display display = GetDisplay(); + display::Display display = + display::Screen::GetScreen()->GetDisplayNearestWindow(window()); compositor_->SetDisplayColorSpace(display.color_space(), display.sdr_white_level()); } @@ -445,7 +446,9 @@ const viz::LocalSurfaceIdAllocation& new_local_surface_id_allocation) { // TODO(jonross) Unify all OnHostResizedInPixels to have both // viz::LocalSurfaceId and allocation time as optional parameters. - device_scale_factor_ = GetDisplay().device_scale_factor(); + display::Display display = + display::Screen::GetScreen()->GetDisplayNearestWindow(window()); + device_scale_factor_ = display.device_scale_factor(); UpdateRootWindowSizeInPixels(); // Allocate a new LocalSurfaceId for the new state. @@ -476,7 +479,8 @@ void WindowTreeHost::OnHostDisplayChanged() { if (!compositor_) return; - display::Display display = GetDisplay(); + display::Display display = + display::Screen::GetScreen()->GetDisplayNearestWindow(window()); compositor_->SetDisplayColorSpace(display.color_space(), display.sdr_white_level()); } @@ -531,8 +535,11 @@ last_cursor_request_position_in_host_ = host_location; MoveCursorToScreenLocationInPixels(host_location); client::CursorClient* cursor_client = client::GetCursorClient(window()); - if (cursor_client) - cursor_client->SetDisplay(GetDisplay()); + if (cursor_client) { + const display::Display& display = + display::Screen::GetScreen()->GetDisplayNearestWindow(window()); + cursor_client->SetDisplay(display); + } dispatcher()->OnCursorMovedToRootLocation(root_location); }
diff --git a/ui/aura/window_tree_host.h b/ui/aura/window_tree_host.h index 9da1be8..70e3f69 100644 --- a/ui/aura/window_tree_host.h +++ b/ui/aura/window_tree_host.h
@@ -24,7 +24,6 @@ #include "ui/base/cursor/cursor.h" #include "ui/base/ime/input_method_delegate.h" #include "ui/compositor/compositor_observer.h" -#include "ui/display/display.h" #include "ui/display/display_observer.h" #include "ui/events/event_source.h" #include "ui/events/platform_event.h" @@ -35,7 +34,7 @@ class Rect; class Size; class Transform; -} // namespace gfx +} namespace ui { class Compositor; @@ -44,7 +43,7 @@ class InputMethod; class ViewProp; struct PlatformWindowInitProperties; -} // namespace ui +} namespace aura { @@ -257,9 +256,6 @@ explicit WindowTreeHost(std::unique_ptr<Window> window = nullptr); - // Gets the display that this window tree host resides at. - display::Display GetDisplay() const; - // Set the cached display device scale factor. This should only be called // during subclass initialization, when the value is needed before InitHost(). void IntializeDeviceScaleFactor(float device_scale_factor);
diff --git a/ui/aura/window_tree_host_platform.cc b/ui/aura/window_tree_host_platform.cc index 926c888..dbc486a 100644 --- a/ui/aura/window_tree_host_platform.cc +++ b/ui/aura/window_tree_host_platform.cc
@@ -290,6 +290,7 @@ widget_ = gfx::kNullAcceleratedWidget; } -void WindowTreeHostPlatform::OnActivationChanged(bool active) {} +void WindowTreeHostPlatform::OnActivationChanged(bool active) { +} } // namespace aura
diff --git a/ui/base/clipboard/clipboard_constants.h b/ui/base/clipboard/clipboard_constants.h index 8c499112..9f114db2 100644 --- a/ui/base/clipboard/clipboard_constants.h +++ b/ui/base/clipboard/clipboard_constants.h
@@ -20,7 +20,7 @@ namespace ui { -#if defined(OS_MACOSX) && !defined(USE_AURA) +#if defined(OS_MACOSX) COMPONENT_EXPORT(BASE_CLIPBOARD_TYPES) extern NSString* const kWebCustomDataPboardType; #endif
diff --git a/ui/base/ime/win/BUILD.gn b/ui/base/ime/win/BUILD.gn index e241603..34333cf6 100644 --- a/ui/base/ime/win/BUILD.gn +++ b/ui/base/ime/win/BUILD.gn
@@ -44,6 +44,8 @@ libs = [ "imm32.lib" ] + ldflags = [ "/DELAYLOAD:imm32.dll" ] + jumbo_excluded_sources = [ # tsf_text_store.cc needs INITGUID to be defined before # including any header to properly generate GUID objects. That
diff --git a/ui/base/test/ui_controls_internal_win.cc b/ui/base/test/ui_controls_internal_win.cc index fec214048..a577845 100644 --- a/ui/base/test/ui_controls_internal_win.cc +++ b/ui/base/test/ui_controls_internal_win.cc
@@ -19,6 +19,7 @@ #include "base/test/test_timeouts.h" #include "base/threading/thread_checker.h" #include "base/threading/thread_task_runner_handle.h" +#include "base/win/win_util.h" #include "ui/display/win/screen_win.h" #include "ui/events/keycodes/keyboard_code_conversion_win.h" #include "ui/events/keycodes/keyboard_codes.h" @@ -632,18 +633,17 @@ DCHECK_LE(num, kTouchesLengthCap); using InitializeTouchInjectionFn = BOOL(WINAPI*)(UINT32, DWORD); - static InitializeTouchInjectionFn initialize_touch_injection = - reinterpret_cast<InitializeTouchInjectionFn>(GetProcAddress( - GetModuleHandleA("user32.dll"), "InitializeTouchInjection")); + static const auto initialize_touch_injection = + reinterpret_cast<InitializeTouchInjectionFn>( + base::win::GetUser32FunctionPointer("InitializeTouchInjection")); if (!initialize_touch_injection || !initialize_touch_injection(num, TOUCH_FEEDBACK_INDIRECT)) { return false; } using InjectTouchInputFn = BOOL(WINAPI*)(UINT32, POINTER_TOUCH_INFO*); - static InjectTouchInputFn inject_touch_input = - reinterpret_cast<InjectTouchInputFn>( - GetProcAddress(GetModuleHandleA("user32.dll"), "InjectTouchInput")); + static const auto inject_touch_input = reinterpret_cast<InjectTouchInputFn>( + base::win::GetUser32FunctionPointer("InjectTouchInput")); if (!inject_touch_input) return false;
diff --git a/ui/base/webui/web_ui_util.cc b/ui/base/webui/web_ui_util.cc index 489fb7e..29ad19d 100644 --- a/ui/base/webui/web_ui_util.cc +++ b/ui/base/webui/web_ui_util.cc
@@ -11,6 +11,7 @@ #include "base/logging.h" #include "base/memory/ref_counted_memory.h" #include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" #include "base/strings/string_util.h" #include "base/trace_event/trace_event.h" #include "build/build_config.h" @@ -115,10 +116,7 @@ std::string* path, float* scale_factor, int* frame_index) { - *path = net::UnescapeURLComponent( - url.path().substr(1), - net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS | - net::UnescapeRule::SPACES); + *path = net::UnescapeBinaryURLComponent(url.path_piece().substr(1)); if (scale_factor) *scale_factor = 1.0f; if (frame_index)
diff --git a/ui/base/win/hwnd_subclass.cc b/ui/base/win/hwnd_subclass.cc index 184698b..c6325d5 100644 --- a/ui/base/win/hwnd_subclass.cc +++ b/ui/base/win/hwnd_subclass.cc
@@ -11,6 +11,7 @@ #include "base/memory/ptr_util.h" #include "base/memory/singleton.h" #include "base/stl_util.h" +#include "ui/base/win/touch_input.h" #include "ui/gfx/win/hwnd_util.h" namespace { @@ -34,19 +35,6 @@ return reinterpret_cast<WNDPROC>(GetWindowLongPtr(target, GWLP_WNDPROC)); } -// Not defined before Win7 -BOOL GetTouchInputInfoWrapper(HTOUCHINPUT handle, UINT count, - PTOUCHINPUT pointer, int size) { - typedef BOOL(WINAPI *GetTouchInputInfoPtr)(HTOUCHINPUT, UINT, - PTOUCHINPUT, int); - GetTouchInputInfoPtr get_touch_input_info_func = - reinterpret_cast<GetTouchInputInfoPtr>( - GetProcAddress(GetModuleHandleA("user32.dll"), "GetTouchInputInfo")); - if (get_touch_input_info_func) - return get_touch_input_info_func(handle, count, pointer, size); - return FALSE; -} - } // namespace namespace ui { @@ -140,8 +128,8 @@ if (message == WM_TOUCH) { TOUCHINPUT point; - if (GetTouchInputInfoWrapper(reinterpret_cast<HTOUCHINPUT>(l_param), 1, - &point, sizeof(TOUCHINPUT))) { + if (ui::GetTouchInputInfoWrapper(reinterpret_cast<HTOUCHINPUT>(l_param), 1, + &point, sizeof(TOUCHINPUT))) { POINT touch_location = {TOUCH_COORD_TO_PIXEL(point.x), TOUCH_COORD_TO_PIXEL(point.y)}; HWND actual_target = WindowFromPoint(touch_location);
diff --git a/ui/base/win/touch_input.cc b/ui/base/win/touch_input.cc index 0114bdd..708d566 100644 --- a/ui/base/win/touch_input.cc +++ b/ui/base/win/touch_input.cc
@@ -3,6 +3,7 @@ // found in the LICENSE file. #include "ui/base/win/touch_input.h" +#include "base/win/win_util.h" namespace ui { @@ -10,11 +11,9 @@ UINT count, PTOUCHINPUT pointer, int size) { - typedef BOOL(WINAPI *GetTouchInputInfoPtr)(HTOUCHINPUT, UINT, - PTOUCHINPUT, int); - static GetTouchInputInfoPtr get_touch_input_info_func = - reinterpret_cast<GetTouchInputInfoPtr>( - GetProcAddress(GetModuleHandleA("user32.dll"), "GetTouchInputInfo")); + static const auto get_touch_input_info_func = + reinterpret_cast<decltype(&::GetTouchInputInfo)>( + base::win::GetUser32FunctionPointer("GetTouchInputInfo")); if (get_touch_input_info_func) return get_touch_input_info_func(handle, count, pointer, size); return FALSE;
diff --git a/ui/display/display.cc b/ui/display/display.cc index 975e83c3..18cef1b9a 100644 --- a/ui/display/display.cc +++ b/ui/display/display.cc
@@ -228,12 +228,6 @@ return Display(kDefaultDisplayId, gfx::Rect(0, 0, 1920, 1080)); } -void Display::SetDeviceScaleFactor(float scale) { - if (HasForceDeviceScaleFactor()) - return; - device_scale_factor_ = scale; -} - int Display::RotationAsDegree() const { switch (rotation_) { case ROTATE_0:
diff --git a/ui/display/display.h b/ui/display/display.h index a3e1966..d38e37f 100644 --- a/ui/display/display.h +++ b/ui/display/display.h
@@ -148,12 +148,12 @@ const gfx::Rect& work_area() const { return work_area_; } void set_work_area(const gfx::Rect& work_area) { work_area_ = work_area; } - // Pixel scale factor. This specifies how much the UI should be scaled when - // rendering on the actual output. This is needed when the latter has more - // pixels than standard displays (which is around 100~120dpi). The potential - // return values depend on each platforms. + // Output device's pixel scale factor. This specifies how much the + // UI should be scaled when the actual output has more pixels than + // standard displays (which is around 100~120dpi.) The potential return + // values depend on each platforms. float device_scale_factor() const { return device_scale_factor_; } - void SetDeviceScaleFactor(float scale); + void set_device_scale_factor(float scale) { device_scale_factor_ = scale; } Rotation rotation() const { return rotation_; } void set_rotation(Rotation rotation) { rotation_ = rotation; } @@ -178,7 +178,7 @@ gfx::Insets GetWorkAreaInsets() const; // Sets the device scale factor and display bounds in pixel. This - // updates the work area using the same insets between old bounds and + // updates the work are using the same insets between old bounds and // work area. void SetScaleAndBounds(float device_scale_factor, const gfx::Rect& bounds_in_pixel); @@ -187,7 +187,7 @@ // between old bounds and work area. void SetSize(const gfx::Size& size_in_pixel); - // Computes and updates the display's work area using + // Computes and updates the display's work are using // |work_area_insets| and the bounds. void UpdateWorkAreaFromInsets(const gfx::Insets& work_area_insets); @@ -239,7 +239,9 @@ // The number of bits per pixel. Used by media query APIs. int color_depth() const { return color_depth_; } - void set_color_depth(int color_depth) { color_depth_ = color_depth; } + void set_color_depth(int color_depth) { + color_depth_ = color_depth; + } // The number of bits per color component (all color components are assumed to // have the same number of bits). Used by media query APIs.
diff --git a/ui/display/display_change_notifier_unittest.cc b/ui/display/display_change_notifier_unittest.cc index 3468929..af69515 100644 --- a/ui/display/display_change_notifier_unittest.cc +++ b/ui/display/display_change_notifier_unittest.cc
@@ -412,9 +412,9 @@ std::vector<Display> old_displays, new_displays; old_displays.push_back(Display(1)); - old_displays[0].SetDeviceScaleFactor(1.f); + old_displays[0].set_device_scale_factor(1.f); new_displays.push_back(Display(1)); - new_displays[0].SetDeviceScaleFactor(2.f); + new_displays[0].set_device_scale_factor(2.f); change_notifier.NotifyDisplaysChanged(old_displays, new_displays); EXPECT_EQ(1, observer.display_changed()); @@ -436,8 +436,8 @@ new_displays.push_back(Display(2)); new_displays.push_back(Display(3)); - old_displays[0].SetDeviceScaleFactor(1.f); - new_displays[0].SetDeviceScaleFactor(2.f); + old_displays[0].set_device_scale_factor(1.f); + new_displays[0].set_device_scale_factor(2.f); old_displays[1].set_bounds(gfx::Rect(0, 0, 200, 200)); new_displays[1].set_bounds(gfx::Rect(0, 0, 400, 400)); @@ -456,11 +456,11 @@ std::vector<Display> old_displays, new_displays; old_displays.push_back(Display(1, gfx::Rect(0, 0, 200, 200))); - old_displays[0].SetDeviceScaleFactor(1.f); + old_displays[0].set_device_scale_factor(1.f); old_displays[0].SetRotationAsDegree(0); new_displays.push_back(Display(1, gfx::Rect(100, 100, 200, 200))); - new_displays[0].SetDeviceScaleFactor(2.f); + new_displays[0].set_device_scale_factor(2.f); new_displays[0].SetRotationAsDegree(90); change_notifier.NotifyDisplaysChanged(old_displays, new_displays);
diff --git a/ui/display/display_list.cc b/ui/display/display_list.cc index 4982432..177aa33 100644 --- a/ui/display/display_list.cc +++ b/ui/display/display_list.cc
@@ -90,7 +90,7 @@ changed_values |= DisplayObserver::DISPLAY_METRIC_ROTATION; } if (local_display->device_scale_factor() != display.device_scale_factor()) { - local_display->SetDeviceScaleFactor(display.device_scale_factor()); + local_display->set_device_scale_factor(display.device_scale_factor()); changed_values |= DisplayObserver::DISPLAY_METRIC_DEVICE_SCALE_FACTOR; } if (local_display->color_space() != display.color_space() ||
diff --git a/ui/display/ios/screen_ios.mm b/ui/display/ios/screen_ios.mm index 7f26be4d..cb3c47b 100644 --- a/ui/display/ios/screen_ios.mm +++ b/ui/display/ios/screen_ios.mm
@@ -17,7 +17,7 @@ UIScreen* mainScreen = [UIScreen mainScreen]; CHECK(mainScreen); Display display(0, gfx::Rect(mainScreen.bounds)); - display.SetDeviceScaleFactor([mainScreen scale]); + display.set_device_scale_factor([mainScreen scale]); ProcessDisplayChanged(display, true /* is_primary */); }
diff --git a/ui/display/mac/screen_mac.mm b/ui/display/mac/screen_mac.mm index 130abcc7..4d5b83a 100644 --- a/ui/display/mac/screen_mac.mm +++ b/ui/display/mac/screen_mac.mm
@@ -79,7 +79,7 @@ CGFloat scale = [screen backingScaleFactor]; if (Display::HasForceDeviceScaleFactor()) scale = Display::GetForcedDeviceScaleFactor(); - display.SetDeviceScaleFactor(scale); + display.set_device_scale_factor(scale); // Compute the color profile. gfx::ICCProfile icc_profile;
diff --git a/ui/display/mojo/display_struct_traits.cc b/ui/display/mojo/display_struct_traits.cc index 6bfba6a4..2603ede 100644 --- a/ui/display/mojo/display_struct_traits.cc +++ b/ui/display/mojo/display_struct_traits.cc
@@ -126,7 +126,7 @@ if (!data.ReadWorkArea(&out->work_area_)) return false; - out->SetDeviceScaleFactor(data.device_scale_factor()); + out->set_device_scale_factor(data.device_scale_factor()); if (!data.ReadRotation(&out->rotation_)) return false;
diff --git a/ui/display/mojo/display_struct_traits_unittest.cc b/ui/display/mojo/display_struct_traits_unittest.cc index b7d71e4..d9b2cee 100644 --- a/ui/display/mojo/display_struct_traits_unittest.cc +++ b/ui/display/mojo/display_struct_traits_unittest.cc
@@ -132,7 +132,7 @@ Display input(246345234, bounds); input.set_work_area(work_area); - input.SetDeviceScaleFactor(2.0f); + input.set_device_scale_factor(2.0f); input.set_rotation(Display::ROTATE_270); input.set_touch_support(Display::TouchSupport::AVAILABLE); input.set_accelerometer_support(Display::AccelerometerSupport::UNAVAILABLE);
diff --git a/ui/display/win/screen_win.cc b/ui/display/win/screen_win.cc index 8c55f290..5b4fe65 100644 --- a/ui/display/win/screen_win.cc +++ b/ui/display/win/screen_win.cc
@@ -172,7 +172,7 @@ bool hdr_enabled) { Display display(display_info.id()); float scale_factor = display_info.device_scale_factor(); - display.SetDeviceScaleFactor(scale_factor); + display.set_device_scale_factor(scale_factor); display.set_work_area( gfx::ScaleToEnclosingRect(display_info.screen_work_rect(), 1.0f / scale_factor)); @@ -330,16 +330,10 @@ // static int ScreenWin::GetSystemMetricsForScaleFactor(float scale_factor, int metric) { if (base::win::IsProcessPerMonitorDpiAware()) { - static auto get_metric_for_dpi_func = []() { - using GetSystemMetricsForDpiPtr = decltype(::GetSystemMetricsForDpi)*; - HMODULE user32_dll = ::LoadLibrary(L"user32.dll"); - if (user32_dll) { - return reinterpret_cast<GetSystemMetricsForDpiPtr>( - ::GetProcAddress(user32_dll, "GetSystemMetricsForDpi")); - } - return static_cast<GetSystemMetricsForDpiPtr>(nullptr); - }(); - + using GetSystemMetricsForDpiPtr = decltype(::GetSystemMetricsForDpi)*; + static const auto get_metric_for_dpi_func = + reinterpret_cast<GetSystemMetricsForDpiPtr>( + base::win::GetUser32FunctionPointer("GetSystemMetricsForDpi")); if (get_metric_for_dpi_func) { return get_metric_for_dpi_func(metric, GetDPIFromScalingFactor(scale_factor));
diff --git a/ui/display/win/screen_win_display.cc b/ui/display/win/screen_win_display.cc index cdb36cc..5a000b5 100644 --- a/ui/display/win/screen_win_display.cc +++ b/ui/display/win/screen_win_display.cc
@@ -14,7 +14,7 @@ Display CreateDisplayFromDisplayInfo(const DisplayInfo& display_info) { Display display(display_info.id()); float scale_factor = display_info.device_scale_factor(); - display.SetDeviceScaleFactor(scale_factor); + display.set_device_scale_factor(scale_factor); display.set_work_area( gfx::ScaleToEnclosingRect(display_info.screen_work_rect(), 1.0f / scale_factor));
diff --git a/ui/ozone/platform/drm/gpu/drm_framebuffer.h b/ui/ozone/platform/drm/gpu/drm_framebuffer.h index bf8831a..c0d160a44 100644 --- a/ui/ozone/platform/drm/gpu/drm_framebuffer.h +++ b/ui/ozone/platform/drm/gpu/drm_framebuffer.h
@@ -49,30 +49,32 @@ const gfx::Size& size); // ID allocated by the KMS API when the buffer is registered (via the handle). - uint32_t framebuffer_id() { return framebuffer_id_; } + uint32_t framebuffer_id() const { return framebuffer_id_; } // ID allocated if the buffer is also registered with a different pixel format // so that it can be scheduled as an opaque buffer. - uint32_t opaque_framebuffer_id() { + uint32_t opaque_framebuffer_id() const { return opaque_framebuffer_id_ ? opaque_framebuffer_id_ : framebuffer_id_; } // Returns FourCC format representing the way pixel data has been encoded in // memory for the registered framebuffer. This can be used to check if frame // buffer is compatible with a given hardware plane. - uint32_t framebuffer_pixel_format() { return framebuffer_pixel_format_; } + uint32_t framebuffer_pixel_format() const { + return framebuffer_pixel_format_; + } // Returns FourCC format that should be used to schedule this buffer for // scanout when used as an opaque buffer. - uint32_t opaque_framebuffer_pixel_format() { + uint32_t opaque_framebuffer_pixel_format() const { return opaque_framebuffer_pixel_format_; } // Returns format modifier for buffer. - uint64_t format_modifier() { return format_modifier_; } + uint64_t format_modifier() const { return format_modifier_; } // Size of the buffer. - gfx::Size size() { return size_; } + gfx::Size size() const { return size_; } // Device on which the buffer was created. const scoped_refptr<DrmDevice>& drm_device() const { return drm_device_; }
diff --git a/ui/ozone/platform/drm/gpu/drm_window.cc b/ui/ozone/platform/drm/gpu/drm_window.cc index 99688b5..4980258 100644 --- a/ui/ozone/platform/drm/gpu/drm_window.cc +++ b/ui/ozone/platform/drm/gpu/drm_window.cc
@@ -50,11 +50,11 @@ device_manager_->RemoveDrmDevice(widget_); } -gfx::AcceleratedWidget DrmWindow::GetAcceleratedWidget() { +gfx::AcceleratedWidget DrmWindow::GetAcceleratedWidget() const { return widget_; } -HardwareDisplayController* DrmWindow::GetController() { +HardwareDisplayController* DrmWindow::GetController() const { return controller_; } @@ -134,7 +134,7 @@ last_submitted_planes_); } -const DrmOverlayPlane* DrmWindow::GetLastModesetBuffer() { +const DrmOverlayPlane* DrmWindow::GetLastModesetBuffer() const { return DrmOverlayPlane::GetPrimaryPlane(last_submitted_planes_); }
diff --git a/ui/ozone/platform/drm/gpu/drm_window.h b/ui/ozone/platform/drm/gpu/drm_window.h index f0fbdb1..a9aba6d 100644 --- a/ui/ozone/platform/drm/gpu/drm_window.h +++ b/ui/ozone/platform/drm/gpu/drm_window.h
@@ -59,11 +59,11 @@ void Shutdown(); // Returns the accelerated widget associated with the window. - gfx::AcceleratedWidget GetAcceleratedWidget(); + gfx::AcceleratedWidget GetAcceleratedWidget() const; // Returns the current controller the window is displaying on. Callers should // not cache the result as the controller may change as the window is moved. - HardwareDisplayController* GetController(); + HardwareDisplayController* GetController() const; void SetController(HardwareDisplayController* controller); @@ -86,7 +86,7 @@ const std::vector<OverlayCheck_Params>& overlay_params); // Returns the last buffer associated with this window. - const DrmOverlayPlane* GetLastModesetBuffer(); + const DrmOverlayPlane* GetLastModesetBuffer() const; private: // Draw next frame in an animated cursor.
diff --git a/ui/ozone/platform/drm/gpu/gbm_pixmap.h b/ui/ozone/platform/drm/gpu/gbm_pixmap.h index 60c856c..c10420b8 100644 --- a/ui/ozone/platform/drm/gpu/gbm_pixmap.h +++ b/ui/ozone/platform/drm/gpu/gbm_pixmap.h
@@ -42,7 +42,7 @@ std::unique_ptr<gfx::GpuFence> gpu_fence) override; gfx::NativePixmapHandle ExportHandle() override; - GbmBuffer* buffer() { return buffer_.get(); } + GbmBuffer* buffer() const { return buffer_.get(); } const scoped_refptr<DrmFramebuffer>& framebuffer() const { return framebuffer_; }
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_controller.cc b/ui/ozone/platform/drm/gpu/hardware_display_controller.cc index 828f2e44..bfc3ccb 100644 --- a/ui/ozone/platform/drm/gpu/hardware_display_controller.cc +++ b/ui/ozone/platform/drm/gpu/hardware_display_controller.cc
@@ -179,7 +179,7 @@ } std::vector<uint64_t> HardwareDisplayController::GetFormatModifiers( - uint32_t format) { + uint32_t format) const { std::vector<uint64_t> modifiers; if (crtc_controllers_.empty()) @@ -202,7 +202,7 @@ std::vector<uint64_t> HardwareDisplayController::GetFormatModifiersForModesetting( - uint32_t fourcc_format) { + uint32_t fourcc_format) const { const auto& modifiers = GetFormatModifiers(fourcc_format); std::vector<uint64_t> filtered_modifiers; for (auto modifier : modifiers) {
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_controller.h b/ui/ozone/platform/drm/gpu/hardware_display_controller.h index 7e6cb787..c1c4be4 100644 --- a/ui/ozone/platform/drm/gpu/hardware_display_controller.h +++ b/ui/ozone/platform/drm/gpu/hardware_display_controller.h
@@ -127,7 +127,7 @@ // Return the supported modifiers for |fourcc_format| for this // controller. - std::vector<uint64_t> GetFormatModifiers(uint32_t fourcc_format); + std::vector<uint64_t> GetFormatModifiers(uint32_t fourcc_format) const; // Return the supported modifiers for |fourcc_format| for this // controller to be used for modeset buffers. Currently, this only exists @@ -135,7 +135,7 @@ // See https://crbug.com/852675 // TODO: Remove this. std::vector<uint64_t> GetFormatModifiersForModesetting( - uint32_t fourcc_format); + uint32_t fourcc_format) const; // Moves the hardware cursor to |location|. void MoveCursor(const gfx::Point& location);
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane.cc b/ui/ozone/platform/drm/gpu/hardware_display_plane.cc index 54e2cb0..2d19a1a 100644 --- a/ui/ozone/platform/drm/gpu/hardware_display_plane.cc +++ b/ui/ozone/platform/drm/gpu/hardware_display_plane.cc
@@ -65,7 +65,7 @@ HardwareDisplayPlane::~HardwareDisplayPlane() {} -bool HardwareDisplayPlane::CanUseForCrtc(uint32_t crtc_index) { +bool HardwareDisplayPlane::CanUseForCrtc(uint32_t crtc_index) const { return crtc_mask_ & (1 << crtc_index); } @@ -123,7 +123,7 @@ } std::vector<uint64_t> HardwareDisplayPlane::ModifiersForFormat( - uint32_t format) { + uint32_t format) const { std::vector<uint64_t> modifiers; uint32_t format_index =
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane.h b/ui/ozone/platform/drm/gpu/hardware_display_plane.h index 58c4dafc..0f85f1b 100644 --- a/ui/ozone/platform/drm/gpu/hardware_display_plane.h +++ b/ui/ozone/platform/drm/gpu/hardware_display_plane.h
@@ -30,9 +30,9 @@ bool IsSupportedFormat(uint32_t format); - std::vector<uint64_t> ModifiersForFormat(uint32_t format); + std::vector<uint64_t> ModifiersForFormat(uint32_t format) const; - bool CanUseForCrtc(uint32_t crtc_index); + bool CanUseForCrtc(uint32_t crtc_index) const; bool in_use() const { return in_use_; } void set_in_use(bool in_use) { in_use_ = in_use; }
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.cc b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.cc index 35eb919b..66f79d0 100644 --- a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.cc +++ b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.cc
@@ -213,7 +213,7 @@ std::vector<uint64_t> HardwareDisplayPlaneManager::GetFormatModifiers( uint32_t crtc_id, - uint32_t format) { + uint32_t format) const { int crtc_index = LookupCrtcIndex(crtc_id); for (const auto& plane : planes_) {
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.h b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.h index f68e178..6298d57 100644 --- a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.h +++ b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.h
@@ -117,7 +117,7 @@ virtual bool ValidatePrimarySize(const DrmOverlayPlane& primary, const drmModeModeInfo& mode) = 0; - const std::vector<std::unique_ptr<HardwareDisplayPlane>>& planes() { + const std::vector<std::unique_ptr<HardwareDisplayPlane>>& planes() const { return planes_; } @@ -131,7 +131,8 @@ // Returns all formats which can be scanned out by this PlaneManager. const std::vector<uint32_t>& GetSupportedFormats() const; - std::vector<uint64_t> GetFormatModifiers(uint32_t crtc_id, uint32_t format); + std::vector<uint64_t> GetFormatModifiers(uint32_t crtc_id, + uint32_t format) const; protected: struct CrtcProperties {
diff --git a/ui/ozone/platform/drm/host/drm_device_connector.h b/ui/ozone/platform/drm/host/drm_device_connector.h index 2c9efb3..b915d3b 100644 --- a/ui/ozone/platform/drm/host/drm_device_connector.h +++ b/ui/ozone/platform/drm/host/drm_device_connector.h
@@ -58,7 +58,7 @@ bool BindableNow() const { return !!connector_; } private: - bool am_running_in_ws_mode() { return !!ws_runner_; } + bool am_running_in_ws_mode() const { return !!ws_runner_; } // This will be present if the Viz host has a service manager. service_manager::Connector* const connector_;
diff --git a/ui/ozone/platform/drm/host/drm_window_host.cc b/ui/ozone/platform/drm/host/drm_window_host.cc index 61157dce..9b381904 100644 --- a/ui/ozone/platform/drm/host/drm_window_host.cc +++ b/ui/ozone/platform/drm/host/drm_window_host.cc
@@ -57,7 +57,7 @@ delegate_->OnAcceleratedWidgetAvailable(widget_); } -gfx::AcceleratedWidget DrmWindowHost::GetAcceleratedWidget() { +gfx::AcceleratedWidget DrmWindowHost::GetAcceleratedWidget() const { return widget_; }
diff --git a/ui/ozone/platform/drm/host/drm_window_host.h b/ui/ozone/platform/drm/host/drm_window_host.h index 84cfc805..eae4e74 100644 --- a/ui/ozone/platform/drm/host/drm_window_host.h +++ b/ui/ozone/platform/drm/host/drm_window_host.h
@@ -53,7 +53,7 @@ void Initialize(); - gfx::AcceleratedWidget GetAcceleratedWidget(); + gfx::AcceleratedWidget GetAcceleratedWidget() const; gfx::Rect GetCursorConfinedBounds() const;
diff --git a/ui/ozone/platform/scenic/scenic_screen.cc b/ui/ozone/platform/scenic/scenic_screen.cc index ece50d1..170db83 100644 --- a/ui/ozone/platform/scenic/scenic_screen.cc +++ b/ui/ozone/platform/scenic/scenic_screen.cc
@@ -55,7 +55,7 @@ }); DCHECK(display_it != displays_.end()); - display_it->SetDeviceScaleFactor(device_pixel_ratio); + display_it->set_device_scale_factor(device_pixel_ratio); for (auto& observer : observers_) { observer.OnDisplayMetricsChanged( *display_it,
diff --git a/ui/ozone/platform/wayland/host/wayland_connection.cc b/ui/ozone/platform/wayland/host/wayland_connection.cc index fd7321ab..0c72f2d 100644 --- a/ui/ozone/platform/wayland/host/wayland_connection.cc +++ b/ui/ozone/platform/wayland/host/wayland_connection.cc
@@ -158,16 +158,6 @@ return nullptr; } -std::vector<WaylandWindow*> WaylandConnection::GetWindowsOnDisplay( - uint32_t display_id) { - std::vector<WaylandWindow*> result; - for (auto entry : window_map_) { - if (entry.second->GetEnteredOutputsIds().count(display_id) > 0) - result.push_back(entry.second); - } - return result; -} - void WaylandConnection::AddWindow(gfx::AcceleratedWidget widget, WaylandWindow* window) { window_map_[widget] = window;
diff --git a/ui/ozone/platform/wayland/host/wayland_connection.h b/ui/ozone/platform/wayland/host/wayland_connection.h index 5ccdfef..e38f5e1 100644 --- a/ui/ozone/platform/wayland/host/wayland_connection.h +++ b/ui/ozone/platform/wayland/host/wayland_connection.h
@@ -118,9 +118,6 @@ WaylandWindow* GetWindowWithLargestBounds() const; WaylandWindow* GetCurrentFocusedWindow() const; WaylandWindow* GetCurrentKeyboardFocusedWindow() const; - // TODO(adunaev) remove this in favor of targeted subscription of windows to - // their displays. - std::vector<WaylandWindow*> GetWindowsOnDisplay(uint32_t display_id); void AddWindow(gfx::AcceleratedWidget widget, WaylandWindow* window); void RemoveWindow(gfx::AcceleratedWidget widget); @@ -232,7 +229,7 @@ // xdg_shell_listener static void Ping(void* data, xdg_shell* shell, uint32_t serial); - base::flat_map<gfx::AcceleratedWidget, WaylandWindow*> window_map_; + std::map<gfx::AcceleratedWidget, WaylandWindow*> window_map_; wl::Object<wl_display> display_; wl::Object<wl_registry> registry_;
diff --git a/ui/ozone/platform/wayland/host/wayland_output.cc b/ui/ozone/platform/wayland/host/wayland_output.cc index 701f167..0b36d95 100644 --- a/ui/ozone/platform/wayland/host/wayland_output.cc +++ b/ui/ozone/platform/wayland/host/wayland_output.cc
@@ -11,10 +11,14 @@ namespace ui { +namespace { +constexpr float kDefaultScaleFactor = 1.0f; +} + WaylandOutput::WaylandOutput(const uint32_t output_id, wl_output* output) : output_id_(output_id), output_(output), - scale_factor_(kDefaultScaleFactor), + device_scale_factor_(kDefaultScaleFactor), rect_in_physical_pixels_(gfx::Rect()) {} WaylandOutput::~WaylandOutput() = default; @@ -34,7 +38,7 @@ void WaylandOutput::TriggerDelegateNotification() const { DCHECK(!rect_in_physical_pixels_.IsEmpty()); delegate_->OnOutputHandleMetrics(output_id_, rect_in_physical_pixels_, - scale_factor_); + device_scale_factor_); } // static @@ -78,7 +82,7 @@ int32_t factor) { WaylandOutput* wayland_output = static_cast<WaylandOutput*>(data); if (wayland_output) - wayland_output->scale_factor_ = factor; + wayland_output->device_scale_factor_ = factor; } } // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_output.h b/ui/ozone/platform/wayland/host/wayland_output.h index 7b8e53e..464689e 100644 --- a/ui/ozone/platform/wayland/host/wayland_output.h +++ b/ui/ozone/platform/wayland/host/wayland_output.h
@@ -36,15 +36,12 @@ uint32_t output_id() const { return output_id_; } bool has_output(wl_output* output) const { return output_.get() == output; } - int32_t scale_factor() const { return scale_factor_; } // Tells if the output has already received physical screen dimensions in the // global compositor space. bool is_ready() const { return !rect_in_physical_pixels_.IsEmpty(); } private: - static constexpr int32_t kDefaultScaleFactor = 1; - // Callback functions used for setting geometric properties of the output // and available modes. static void OutputHandleGeometry(void* data, @@ -57,6 +54,7 @@ const char* make, const char* model, int32_t output_transform); + static void OutputHandleMode(void* data, wl_output* wl_output, uint32_t flags, @@ -70,7 +68,7 @@ const uint32_t output_id_ = 0; wl::Object<wl_output> output_; - int32_t scale_factor_ = kDefaultScaleFactor; + float device_scale_factor_; gfx::Rect rect_in_physical_pixels_; Delegate* delegate_ = nullptr;
diff --git a/ui/ozone/platform/wayland/host/wayland_output_manager.cc b/ui/ozone/platform/wayland/host/wayland_output_manager.cc index 1f403f4..38dad07 100644 --- a/ui/ozone/platform/wayland/host/wayland_output_manager.cc +++ b/ui/ozone/platform/wayland/host/wayland_output_manager.cc
@@ -26,7 +26,10 @@ // Make sure an output with |output_id| has not been added yet. It's very // unlikely to happen, unless a compositor has a bug in the numeric names // representation of global objects. - auto output_it = GetOutputItById(output_id); + auto output_it = std::find_if(output_list_.begin(), output_list_.end(), + [output_id](const auto& output) { + return output->output_id() == output_id; + }); DCHECK(output_it == output_list_.end()); auto wayland_output = std::make_unique<WaylandOutput>(output_id, output); WaylandOutput* wayland_output_ptr = wayland_output.get(); @@ -41,7 +44,10 @@ } void WaylandOutputManager::RemoveWaylandOutput(const uint32_t output_id) { - auto output_it = GetOutputItById(output_id); + auto output_it = std::find_if(output_list_.begin(), output_list_.end(), + [output_id](const auto& output) { + return output->output_id() == output_id; + }); // Check the comment in the WaylandConnetion::GlobalRemove. if (output_it == output_list_.end()) @@ -83,13 +89,6 @@ return output_it->get()->output_id(); } -WaylandOutput* WaylandOutputManager::GetOutput(uint32_t id) const { - auto output_it = GetOutputItById(id); - // This is unlikely to happen, but better to be explicit here. - DCHECK(output_it != output_list_.end()); - return output_it->get(); -} - void WaylandOutputManager::OnWaylandOutputAdded(uint32_t output_id) { if (wayland_screen_) wayland_screen_->OnOutputAdded(output_id); @@ -108,11 +107,4 @@ scale_factor); } -WaylandOutputManager::OutputList::const_iterator -WaylandOutputManager::GetOutputItById(uint32_t id) const { - return std::find_if( - output_list_.begin(), output_list_.end(), - [id](const auto& item) { return item->output_id() == id; }); -} - } // namespace ui
diff --git a/ui/ozone/platform/wayland/host/wayland_output_manager.h b/ui/ozone/platform/wayland/host/wayland_output_manager.h index f05828a6..8123232 100644 --- a/ui/ozone/platform/wayland/host/wayland_output_manager.h +++ b/ui/ozone/platform/wayland/host/wayland_output_manager.h
@@ -39,9 +39,6 @@ WaylandConnection* connection); uint32_t GetIdForOutput(wl_output* output) const; - WaylandOutput* GetOutput(uint32_t id) const; - - WaylandScreen* wayland_screen() const { return wayland_screen_.get(); } private: void OnWaylandOutputAdded(uint32_t output_id); @@ -52,11 +49,7 @@ const gfx::Rect& new_bounds, int32_t scale_factor) override; - using OutputList = std::vector<std::unique_ptr<WaylandOutput>>; - - OutputList::const_iterator GetOutputItById(uint32_t id) const; - - OutputList output_list_; + std::vector<std::unique_ptr<WaylandOutput>> output_list_; // Non-owned wayland screen instance. base::WeakPtr<WaylandScreen> wayland_screen_;
diff --git a/ui/ozone/platform/wayland/host/wayland_screen.cc b/ui/ozone/platform/wayland/host/wayland_screen.cc index 9ca9632..e53a959 100644 --- a/ui/ozone/platform/wayland/host/wayland_screen.cc +++ b/ui/ozone/platform/wayland/host/wayland_screen.cc
@@ -23,12 +23,14 @@ WaylandScreen::~WaylandScreen() = default; void WaylandScreen::OnOutputAdded(uint32_t output_id) { - display_list_.AddDisplay(display::Display(output_id), + display::Display new_display(output_id); + display_list_.AddDisplay(std::move(new_display), display::DisplayList::Type::NOT_PRIMARY); } void WaylandScreen::OnOutputRemoved(uint32_t output_id) { - if (output_id == GetPrimaryDisplay().id()) { + display::Display primary_display = GetPrimaryDisplay(); + if (primary_display.id() == output_id) { // First, set a new primary display as required by the |display_list_|. It's // safe to set any of the displays to be a primary one. Once the output is // completely removed, Wayland updates geometry of other displays. And a @@ -47,9 +49,9 @@ void WaylandScreen::OnOutputMetricsChanged(uint32_t output_id, const gfx::Rect& new_bounds, - int32_t output_scale) { + float device_pixel_ratio) { display::Display changed_display(output_id); - changed_display.SetDeviceScaleFactor(output_scale); + changed_display.set_device_scale_factor(device_pixel_ratio); changed_display.set_bounds(new_bounds); changed_display.set_work_area(new_bounds); @@ -79,9 +81,6 @@ display_list_.UpdateDisplay( changed_display, is_primary ? display::DisplayList::Type::PRIMARY : display::DisplayList::Type::NOT_PRIMARY); - - for (auto* window : connection_->GetWindowsOnDisplay(output_id)) - window->UpdateBufferScale(true); } base::WeakPtr<WaylandScreen> WaylandScreen::GetWeakPtr() { @@ -100,14 +99,13 @@ display::Display WaylandScreen::GetDisplayForAcceleratedWidget( gfx::AcceleratedWidget widget) const { - auto* window = connection_->GetWindow(widget); + auto* wayland_window = connection_->GetWindow(widget); // A window might be destroyed by this time on shutting down the browser. - if (!window) + if (!wayland_window) return GetPrimaryDisplay(); - const auto* parent_window = window->parent_window(); - - const std::set<uint32_t> entered_outputs_ids = window->GetEnteredOutputsIds(); + const std::set<uint32_t> entered_outputs_ids = + wayland_window->GetEnteredOutputsIds(); // Although spec says a surface receives enter/leave surface events on // create/move/resize actions, this might be called right after a window is // created, but it has not been configured by a Wayland compositor and it has @@ -116,19 +114,14 @@ // events immediately, which can result in empty container of entered ids // (check comments in WaylandWindow::RemoveEnteredOutputId). In this case, // it's also safe to return the primary display. - // A child window will most probably enter the same display than its parent - // so we return the parent's display if there is a parent. - if (entered_outputs_ids.empty()) { - if (parent_window) - return GetDisplayForAcceleratedWidget(parent_window->GetWidget()); + if (entered_outputs_ids.empty()) return GetPrimaryDisplay(); - } DCHECK(!display_list_.displays().empty()); // A widget can be located on two or more displays. It would be better if the - // most in DIP occupied display was returned, but it's impossible to do so in - // Wayland. Thus, return the one that was used the earliest. + // most in pixels occupied display was returned, but it's impossible to do in + // Wayland. Thus, return the one, which was the very first used. for (const auto& display : display_list_.displays()) { if (display.id() == *entered_outputs_ids.begin()) return display;
diff --git a/ui/ozone/platform/wayland/host/wayland_screen.h b/ui/ozone/platform/wayland/host/wayland_screen.h index c81b47a3..f395b9b 100644 --- a/ui/ozone/platform/wayland/host/wayland_screen.h +++ b/ui/ozone/platform/wayland/host/wayland_screen.h
@@ -29,7 +29,7 @@ void OnOutputRemoved(uint32_t output_id); void OnOutputMetricsChanged(uint32_t output_id, const gfx::Rect& bounds, - int32_t output_scale); + float device_pixel_ratio); base::WeakPtr<WaylandScreen> GetWeakPtr();
diff --git a/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc b/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc index f93ac20d..54e72fb5 100644 --- a/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc +++ b/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc
@@ -223,8 +223,9 @@ EXPECT_EQ(observer.GetAndClearChangedMetrics(), changed_values); EXPECT_EQ(observer.GetDisplay().bounds(), new_rect); - const int32_t new_scale_value = 2; - output_->SetScale(new_scale_value); + const float new_scale_value = 2.0f; + wl_output_send_scale(output_->resource(), new_scale_value); + wl_output_send_done(output_->resource()); Sync(); @@ -579,23 +580,6 @@ EXPECT_EQ(gfx::Point(1912, 1071), platform_screen_->GetCursorScreenPoint()); } -// Checks that the surface that backs the window receives new scale of the -// output that it is in. -TEST_P(WaylandScreenTest, SetBufferScale) { - // Place the window onto the output. - wl_surface_send_enter(surface_->resource(), output_->resource()); - - // Change the scale of the output. Windows looking into that output must get - // the new scale and update scale of their buffers. - const int32_t kNewScale = 3; - EXPECT_CALL(*surface_, SetBufferScale(kNewScale)); - output_->SetScale(kNewScale); - - Sync(); - - EXPECT_EQ(window_->buffer_scale(), kNewScale); -} - INSTANTIATE_TEST_SUITE_P(XdgVersionV5Test, WaylandScreenTest, ::testing::Values(kXdgShellV5));
diff --git a/ui/ozone/platform/wayland/host/wayland_touch.cc b/ui/ozone/platform/wayland/host/wayland_touch.cc index eaec157..ce18b08 100644 --- a/ui/ozone/platform/wayland/host/wayland_touch.cc +++ b/ui/ozone/platform/wayland/host/wayland_touch.cc
@@ -72,9 +72,7 @@ WaylandTouch* touch = static_cast<WaylandTouch*>(data); DCHECK(touch); touch->connection_->set_serial(serial); - auto* window = WaylandWindow::FromSurface(surface); - DCHECK(window); - window->set_touch_focus(true); + WaylandWindow::FromSurface(surface)->set_touch_focus(true); // Make sure this touch point wasn't present before. if (touch->current_points_.find(id) != touch->current_points_.end()) {
diff --git a/ui/ozone/platform/wayland/host/wayland_window.cc b/ui/ozone/platform/wayland/host/wayland_window.cc index 7854c21..4b0a990 100644 --- a/ui/ozone/platform/wayland/host/wayland_window.cc +++ b/ui/ozone/platform/wayland/host/wayland_window.cc
@@ -122,11 +122,7 @@ bool WaylandWindow::Initialize(PlatformWindowInitProperties properties) { DCHECK(xdg_shell_objects_factory_); - // Properties contain DIP bounds but the buffer scale is initially 1 so it's - // OK to assign. The bounds will be recalculated when the buffer scale - // changes. - DCHECK_EQ(buffer_scale_, 1); - bounds_px_ = properties.bounds; + bounds_ = properties.bounds; opacity_ = properties.opacity; surface_.reset(wl_compositor_create_surface(connection_->compositor())); @@ -134,20 +130,15 @@ LOG(ERROR) << "Failed to create wl_surface"; return false; } - - wl_surface_set_user_data(surface(), this); - + wl_surface_set_user_data(surface_.get(), this); AddSurfaceListener(); - switch (properties.type) { + ui::PlatformWindowType ui_window_type = properties.type; + switch (ui_window_type) { case ui::PlatformWindowType::kMenu: case ui::PlatformWindowType::kPopup: parent_window_ = GetParentWindow(properties.parent_widget); - // Popups need to know their scale earlier to position themselves. - DCHECK(parent_window_); - SetBufferScale(parent_window_->buffer_scale_, false); - // TODO(msisov, jkim): Handle notification windows, which are marked // as popup windows as well. Those are the windows that do not have // parents and pop up when the browser receives a notification. @@ -168,35 +159,10 @@ PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this); delegate_->OnAcceleratedWidgetAvailable(GetWidget()); - // Will do nothing for popups because they have got their scale above. - UpdateBufferScale(false); - MaybeUpdateOpaqueRegion(); return true; } -void WaylandWindow::UpdateBufferScale(bool update_bounds) { - DCHECK(connection_->wayland_output_manager()); - const auto* screen = connection_->wayland_output_manager()->wayland_screen(); - DCHECK(screen); - const auto widget = GetWidget(); - - int32_t new_scale = 0; - if (parent_window_) { - new_scale = parent_window_->buffer_scale_; - } else if (widget == gfx::kNullAcceleratedWidget) { - new_scale = screen->GetPrimaryDisplay().device_scale_factor(); - } else { - // This is the main window that is fully set up so we can ask which display - // we are at currently. - current_display_ = screen->GetDisplayForAcceleratedWidget(widget); - new_scale = connection_->wayland_output_manager() - ->GetOutput(current_display_.id()) - ->scale_factor(); - } - SetBufferScale(new_scale, update_bounds); -} - gfx::AcceleratedWidget WaylandWindow::GetWidget() const { if (!surface_) return gfx::kNullAcceleratedWidget; @@ -208,7 +174,7 @@ } void WaylandWindow::CreateXdgPopup() { - if (bounds_px_.IsEmpty()) + if (bounds_.IsEmpty()) return; // TODO(jkim): Consider how to support DropArrow window on tabstrip. @@ -226,11 +192,11 @@ DCHECK(parent_window_ && !xdg_popup_); - auto bounds_px = AdjustPopupWindowPosition(); + auto bounds = AdjustPopupWindowPosition(); xdg_popup_ = xdg_shell_objects_factory_->CreateXDGPopup(connection_, this); - if (!xdg_popup_ || !xdg_popup_->Initialize(connection_, surface(), - parent_window_, bounds_px)) { + if (!xdg_popup_ || + !xdg_popup_->Initialize(connection_, surface(), parent_window_, bounds)) { CHECK(false) << "Failed to create xdg_popup"; } @@ -267,24 +233,22 @@ } DCHECK(tooltip_subsurface_); - // Convert position to DIP. - wl_subsurface_set_position(tooltip_subsurface_.get(), - bounds_px_.x() / buffer_scale_, - bounds_px_.y() / buffer_scale_); + wl_subsurface_set_position(tooltip_subsurface_.get(), bounds_.x(), + bounds_.y()); wl_subsurface_set_desync(tooltip_subsurface_.get()); wl_surface_commit(parent_window_->surface()); connection_->ScheduleFlush(); } void WaylandWindow::ApplyPendingBounds() { - if (pending_bounds_dip_.IsEmpty()) + if (pending_bounds_.IsEmpty()) return; DCHECK(xdg_surface_); - SetBoundsDip(pending_bounds_dip_); - xdg_surface_->SetWindowGeometry(pending_bounds_dip_); + SetBounds(pending_bounds_); + xdg_surface_->SetWindowGeometry(bounds_); xdg_surface_->AckConfigure(); - pending_bounds_dip_ = gfx::Rect(); + pending_bounds_ = gfx::Rect(); connection_->ScheduleFlush(); // Opaque region is based on the size of the window. Thus, update the region @@ -328,16 +292,9 @@ } if (!xdg_popup_) { - // When showing a sub-menu after it has been previously shown and hidden, - // Wayland sends SetBounds prior to Show, and |bounds_px| takes the pixel - // bounds. This makes a difference against the normal flow when the - // window is created (see |Initialize|). To equalize things, rescale - // |bounds_px_| to DIP. It will be adjusted while creating the popup. - bounds_px_ = gfx::ScaleToRoundedRect(bounds_px_, 1.0 / buffer_scale_); CreateXdgPopup(); connection_->ScheduleFlush(); } - UpdateBufferScale(false); } void WaylandWindow::Hide() { @@ -367,23 +324,15 @@ void WaylandWindow::PrepareForShutdown() {} -void WaylandWindow::SetBounds(const gfx::Rect& bounds_px) { - // TODO(adunaev): figure out if this return is legitimate, see - // https://crbug.com/958314 - // The X11 implementation says that even if the pixel bounds - // didn't change, we still need to forward this call to the delegate, and that - // the device scale factor may have changed which effectively changes the - // bounds. Perhaps we need to do the same here. - if (bounds_px_ == bounds_px) +void WaylandWindow::SetBounds(const gfx::Rect& bounds) { + if (bounds == bounds_) return; - - bounds_px_ = bounds_px; - - delegate_->OnBoundsChanged(bounds_px_); + bounds_ = bounds; + delegate_->OnBoundsChanged(bounds); } gfx::Rect WaylandWindow::GetBounds() { - return bounds_px_; + return bounds_; } void WaylandWindow::SetTitle(const base::string16& title) { @@ -510,12 +459,12 @@ return nullptr; } -void WaylandWindow::SetRestoredBoundsInPixels(const gfx::Rect& bounds_px) { - restored_bounds_px_ = bounds_px; +void WaylandWindow::SetRestoredBoundsInPixels(const gfx::Rect& bounds) { + restored_bounds_ = bounds; } gfx::Rect WaylandWindow::GetRestoredBoundsInPixels() const { - return restored_bounds_px_; + return restored_bounds_; } bool WaylandWindow::CanDispatchEvent(const PlatformEvent& event) { @@ -546,10 +495,6 @@ Event* event = static_cast<Event*>(native_event); if (event->IsLocatedEvent()) { - // Located events come in output scale and need to be translated to - // physical pixels. - event->AsLocatedEvent()->set_location_f(gfx::ScalePoint( - event->AsLocatedEvent()->location_f(), buffer_scale_, buffer_scale_)); auto copied_event = Event::Clone(*event); UpdateCursorPositionFromEvent(std::move(copied_event)); } @@ -631,12 +576,11 @@ // explicitly set the bounds to the current desired ones or the previous // bounds. if (width > 1 && height > 1) { - pending_bounds_dip_ = gfx::Rect(0, 0, width, height); + pending_bounds_ = gfx::Rect(0, 0, width, height); } else if (is_normal) { - pending_bounds_dip_.set_size(gfx::ScaleToRoundedSize( - restored_bounds_px_.IsEmpty() ? GetBounds().size() - : restored_bounds_px_.size(), - 1.0 / buffer_scale_)); + pending_bounds_.set_size(restored_bounds_.IsEmpty() + ? GetBounds().size() + : restored_bounds_.size()); } if (state_changed) { @@ -648,8 +592,8 @@ if (is_normal) { SetRestoredBoundsInPixels({}); } else if (old_state == PlatformWindowState::PLATFORM_WINDOW_STATE_NORMAL || - restored_bounds_px_.IsEmpty()) { - SetRestoredBoundsInPixels(bounds_px_); + restored_bounds_.IsEmpty()) { + SetRestoredBoundsInPixels(bounds_); } delegate_->OnWindowStateChanged(state_); @@ -658,18 +602,12 @@ if (did_active_change) delegate_->OnActivationChanged(is_active_); - UpdateBufferScale(true); - MaybeTriggerPendingStateChange(); } -void WaylandWindow::HandlePopupConfigure(const gfx::Rect& bounds_dip) { +void WaylandWindow::HandlePopupConfigure(const gfx::Rect& bounds) { DCHECK(xdg_popup()); - DCHECK(parent_window_); - - SetBufferScale(parent_window_->buffer_scale_, true); - - gfx::Rect new_bounds_dip = bounds_dip; + gfx::Rect new_bounds = bounds; // It's not enough to just set new bounds. If it is a menu window, whose // parent is a top level window aka browser window, it can be flipped @@ -687,10 +625,10 @@ gfx::Rect parent_bounds = parent_window_->GetBounds(); // The menu window is flipped along y-axis and have x,-y origin. Shift the // parent top level window instead. - if (new_bounds_dip.y() < 0) { + if (new_bounds.y() < 0) { // Move parent bounds along y-axis. - parent_bounds.set_y(-(new_bounds_dip.y() * buffer_scale_)); - new_bounds_dip.set_y(0); + parent_bounds.set_y(-(new_bounds.y())); + new_bounds.set_y(0); } else { // If the menu window is located at correct origin from the browser point // of view, return the top level window back to 0,0. @@ -702,16 +640,11 @@ // Thus, the location must be translated to be relative to the top level // window, which automatically becomes the same as relative to an origin of // a display. - new_bounds_dip = gfx::ScaleToRoundedRect( - TranslateBoundsToTopLevelCoordinates( - gfx::ScaleToRoundedRect(new_bounds_dip, buffer_scale_), - parent_window_->GetBounds()), - 1.0 / buffer_scale_); - DCHECK(new_bounds_dip.y() >= 0); + new_bounds = TranslateBoundsToTopLevelCoordinates( + new_bounds, parent_window_->GetBounds()); + DCHECK(new_bounds.y() >= 0); } - - // Finally, the menu bounds must be converted to physical pixels. - SetBoundsDip(new_bounds_dip); + SetBounds(new_bounds); } void WaylandWindow::OnCloseRequest() { @@ -759,26 +692,6 @@ connection_->ResetPointerFlags(); } -void WaylandWindow::SetBoundsDip(const gfx::Rect& bounds_dip) { - SetBounds(gfx::ScaleToRoundedRect(bounds_dip, buffer_scale_)); -} - -void WaylandWindow::SetBufferScale(int32_t new_scale, bool update_bounds) { - DCHECK_GT(new_scale, 0); - - if (new_scale == buffer_scale_) - return; - - auto old_scale = buffer_scale_; - buffer_scale_ = new_scale; - if (update_bounds) - SetBoundsDip(gfx::ScaleToRoundedRect(bounds_px_, 1.0 / old_scale)); - - DCHECK(surface()); - wl_surface_set_buffer_scale(surface(), buffer_scale_); - connection_->ScheduleFlush(); -} - bool WaylandWindow::IsMinimized() const { return state_ == PlatformWindowState::PLATFORM_WINDOW_STATE_MINIMIZED; } @@ -834,28 +747,14 @@ } void WaylandWindow::AddEnteredOutputId(struct wl_output* output) { - // Wayland does weird things for popups so instead of tracking outputs that - // we entered or left, we take that from the parent window and ignore this - // event. - if (xdg_popup()) - return; - const uint32_t entered_output_id = connection_->wayland_output_manager()->GetIdForOutput(output); DCHECK_NE(entered_output_id, 0u); auto result = entered_outputs_ids_.insert(entered_output_id); DCHECK(result.first != entered_outputs_ids_.end()); - - UpdateBufferScale(true); } void WaylandWindow::RemoveEnteredOutputId(struct wl_output* output) { - // Wayland does weird things for popups so instead of tracking outputs that - // we entered or left, we take that from the parent window and ignore this - // event. - if (xdg_popup()) - return; - const uint32_t left_output_id = connection_->wayland_output_manager()->GetIdForOutput(output); auto entered_output_id_it = entered_outputs_ids_.find(left_output_id); @@ -865,11 +764,8 @@ // displays in a single output mode results in leave events, but the surface // might not have received enter event before. Thus, remove the id of left // output only if it was stored before. - if (entered_output_id_it != entered_outputs_ids_.end()) { + if (entered_output_id_it != entered_outputs_ids_.end()) entered_outputs_ids_.erase(entered_output_id_it); - } - - UpdateBufferScale(true); } void WaylandWindow::UpdateCursorPositionFromEvent( @@ -918,15 +814,11 @@ ? parent_window_->parent_window_ : parent_window_; DCHECK(parent_window); - DCHECK(buffer_scale_ == parent_window->buffer_scale_); - // Chromium positions windows in screen coordinates, but Wayland requires them // to be in local surface coordinates aka relative to parent window. - const gfx::Rect parent_bounds_px = - gfx::ScaleToRoundedRect(parent_window_->GetBounds(), 1.0 / buffer_scale_); - + const gfx::Rect parent_bounds = parent_window_->GetBounds(); gfx::Rect new_bounds = - TranslateBoundsToParentCoordinates(bounds_px_, parent_bounds_px); + TranslateBoundsToParentCoordinates(bounds_, parent_bounds); // Chromium may decide to position nested menu windows on the left side // instead of the right side of parent menu windows when the size of the @@ -950,8 +842,7 @@ // Position the child menu window on the right side of the parent window // and let the Wayland compositor decide how to do constraint // adjustements. - int new_x = - parent_bounds_px.width() - (new_bounds.width() + new_bounds.x()); + int new_x = parent_bounds.width() - (new_bounds.width() + new_bounds.x()); new_bounds.set_x(new_x); } } @@ -968,7 +859,7 @@ wl::Object<wl_region> region( wl_compositor_create_region(connection_->compositor())); - wl_region_add(region.get(), 0, 0, bounds_px_.width(), bounds_px_.height()); + wl_region_add(region.get(), 0, 0, bounds_.width(), bounds_.height()); wl_surface_set_opaque_region(surface(), region.get()); connection_->ScheduleFlush();
diff --git a/ui/ozone/platform/wayland/host/wayland_window.h b/ui/ozone/platform/wayland/host/wayland_window.h index 7ea2fa94..3d46ba1f 100644 --- a/ui/ozone/platform/wayland/host/wayland_window.h +++ b/ui/ozone/platform/wayland/host/wayland_window.h
@@ -11,7 +11,6 @@ #include "base/callback.h" #include "base/memory/ref_counted.h" -#include "ui/display/display.h" #include "ui/events/platform/platform_event_dispatcher.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/native_widget_types.h" @@ -52,19 +51,10 @@ bool Initialize(PlatformWindowInitProperties properties); - // Updates the surface buffer scale of the window. Top level windows take - // scale according to scale of their current display or the primary one if - // their widget is not yet created, children inherit scale from their parent. - // The method recalculates window bounds appropriately if asked to do so - // (this is not needed upon window initialization). - void UpdateBufferScale(bool update_bounds); - wl_surface* surface() const { return surface_.get(); } XDGSurfaceWrapper* xdg_surface() const { return xdg_surface_.get(); } XDGPopupWrapper* xdg_popup() const { return xdg_popup_.get(); } - WaylandWindow* parent_window() const { return parent_window_; } - gfx::AcceleratedWidget GetWidget() const; // Returns the list of wl_outputs aka displays, which this window occupies. @@ -99,8 +89,6 @@ void set_has_implicit_grab(bool value) { has_implicit_grab_ = value; } bool has_implicit_grab() const { return has_implicit_grab_; } - int32_t buffer_scale() const { return buffer_scale_; } - bool is_active() const { return is_active_; } // WmMoveResizeHandler @@ -141,10 +129,6 @@ bool CanDispatchEvent(const PlatformEvent& event) override; uint32_t DispatchEvent(const PlatformEvent& event) override; - // Handles the configuration events coming from the surface (see - // |XDGSurfaceWrapperV5::Configure| and - // |XDGSurfaceWrapperV6::ConfigureTopLevel|. The width and height come in - // DIP of the output that the surface is currently bound to. void HandleSurfaceConfigure(int32_t widht, int32_t height, bool is_maximized, @@ -163,9 +147,6 @@ void OnDragSessionClose(uint32_t dnd_action); private: - void SetBoundsDip(const gfx::Rect& bounds_dip); - void SetBufferScale(int32_t scale, bool update_bounds); - bool IsMinimized() const; bool IsMaximized() const; bool IsFullscreen() const; @@ -233,28 +214,14 @@ base::OnceCallback<void(int)> drag_closed_callback_; - // These bounds attributes below have suffices that indicate uints used. - // Values measured in physical pixels are used in calls to PlatformWindow - // and PlatformWindowDelegate that work with physical pixels. - // - // Current bounds of the window. - gfx::Rect bounds_px_; - // Bounds that will be applied when the window state is finalized. The window - // may get several configuration events that update the pending bounds, and - // only upon finalizing the state is the latest value stored as the current - // bounds via |ApplyPendingBounds|. Measured in DIP because updated in the - // handler that receives DIP. - gfx::Rect pending_bounds_dip_; + gfx::Rect bounds_; + gfx::Rect pending_bounds_; // The bounds of the window before it went maximized or fullscreen. - gfx::Rect restored_bounds_px_; - - display::Display current_display_; - + gfx::Rect restored_bounds_; bool has_pointer_focus_ = false; bool has_keyboard_focus_ = false; bool has_touch_focus_ = false; bool has_implicit_grab_ = false; - int32_t buffer_scale_ = 1; // Stores current states of the window. ui::PlatformWindowState state_; @@ -270,14 +237,7 @@ bool is_tooltip_ = false; - // For top level window, stores the list of entered outputs that the window - // is currently in. - // - // For a popup, not used. Wayland 'repositions' sub-menus to wrong outputs - // (by sending them leave and enter events) when sub-menus are hidden and - // shown again so their list of entered outputs becomes meaningless after - // they have been hidden at least once. To determine which output the popup - // belongs to, refer to its parent. + // Stores the list of entered outputs that the window is currently in. std::set<uint32_t> entered_outputs_ids_; DISALLOW_COPY_AND_ASSIGN(WaylandWindow);
diff --git a/ui/ozone/platform/wayland/host/xdg_popup_wrapper_v6.cc b/ui/ozone/platform/wayland/host/xdg_popup_wrapper_v6.cc index 0f04fcd..a17719e 100644 --- a/ui/ozone/platform/wayland/host/xdg_popup_wrapper_v6.cc +++ b/ui/ozone/platform/wayland/host/xdg_popup_wrapper_v6.cc
@@ -166,8 +166,6 @@ break; } - if (anchor_rect.width() == 0) - anchor_rect.set_width(1); return anchor_rect; } @@ -259,10 +257,8 @@ menu_type = MenuType::TYPE_3DOT_PARENT_MENU; // Place anchor to the end of the possible position. - gfx::Rect anchor_rect = GetAnchorRect( - menu_type, bounds, - gfx::ScaleToRoundedRect(parent_window->GetBounds(), - 1.0 / parent_window->buffer_scale())); + gfx::Rect anchor_rect = + GetAnchorRect(menu_type, bounds, parent_window->GetBounds()); zxdg_positioner_v6_set_anchor_rect(positioner, anchor_rect.x(), anchor_rect.y(), anchor_rect.width(), @@ -288,10 +284,11 @@ // Wayland requires doing so in respect to parent window's origin. To properly // place windows, the bounds are translated and adjusted according to the // Wayland compositor needs during WaylandWindow::CreateXdgPopup call. + gfx::Rect new_bounds(x, y, width, height); WaylandWindow* window = static_cast<XDGPopupWrapperV6*>(data)->wayland_window_; DCHECK(window); - window->HandlePopupConfigure({x, y, width, height}); + window->HandlePopupConfigure(new_bounds); } // static
diff --git a/ui/ozone/platform/wayland/test/mock_surface.cc b/ui/ozone/platform/wayland/test/mock_surface.cc index 7669ea4..bab5b77 100644 --- a/ui/ozone/platform/wayland/test/mock_surface.cc +++ b/ui/ozone/platform/wayland/test/mock_surface.cc
@@ -41,10 +41,6 @@ GetUserDataAs<MockSurface>(resource)->Commit(); } -void SetBufferScale(wl_client* client, wl_resource* resource, int32_t scale) { - GetUserDataAs<MockSurface>(resource)->SetBufferScale(scale); -} - } // namespace const struct wl_surface_interface kMockSurfaceImpl = { @@ -56,7 +52,7 @@ SetInputRegion, // set_input_region Commit, // commit nullptr, // set_buffer_transform - SetBufferScale, // set_buffer_scale + nullptr, // set_buffer_scale nullptr, // damage_buffer };
diff --git a/ui/ozone/platform/wayland/test/mock_surface.h b/ui/ozone/platform/wayland/test/mock_surface.h index 1531c48..0f83878b 100644 --- a/ui/ozone/platform/wayland/test/mock_surface.h +++ b/ui/ozone/platform/wayland/test/mock_surface.h
@@ -36,7 +36,6 @@ MOCK_METHOD4(Damage, void(int32_t x, int32_t y, int32_t width, int32_t height)); MOCK_METHOD0(Commit, void()); - MOCK_METHOD1(SetBufferScale, void(int32_t scale)); void set_xdg_surface(std::unique_ptr<MockXdgSurface> xdg_surface) { xdg_surface_ = std::move(xdg_surface);
diff --git a/ui/ozone/platform/wayland/test/test_output.cc b/ui/ozone/platform/wayland/test/test_output.cc index bbc6475..4abdc319 100644 --- a/ui/ozone/platform/wayland/test/test_output.cc +++ b/ui/ozone/platform/wayland/test/test_output.cc
@@ -31,9 +31,4 @@ wl_output_send_done(resource()); } -void TestOutput::SetScale(int32_t factor) { - wl_output_send_scale(resource(), factor); - wl_output_send_done(resource()); -} - } // namespace wl
diff --git a/ui/ozone/platform/wayland/test/test_output.h b/ui/ozone/platform/wayland/test/test_output.h index 8dffd41..bbd6848c 100644 --- a/ui/ozone/platform/wayland/test/test_output.h +++ b/ui/ozone/platform/wayland/test/test_output.h
@@ -20,8 +20,6 @@ const gfx::Rect GetRect() { return rect_; } void OnBind() override; - void SetScale(int32_t factor); - private: gfx::Rect rect_;
diff --git a/ui/ozone/platform/wayland/test/wayland_test.cc b/ui/ozone/platform/wayland/test/wayland_test.cc index ed36b2a..e96443b 100644 --- a/ui/ozone/platform/wayland/test/wayland_test.cc +++ b/ui/ozone/platform/wayland/test/wayland_test.cc
@@ -6,8 +6,6 @@ #include "base/run_loop.h" #include "ui/events/ozone/layout/keyboard_layout_engine_manager.h" -#include "ui/ozone/platform/wayland/host/wayland_output_manager.h" -#include "ui/ozone/platform/wayland/host/wayland_screen.h" #include "ui/ozone/platform/wayland/test/mock_surface.h" #include "ui/platform_window/platform_window_init_properties.h" @@ -43,8 +41,6 @@ void WaylandTest::SetUp() { ASSERT_TRUE(server_.Start(GetParam())); ASSERT_TRUE(connection_->Initialize()); - screen_ = connection_->wayland_output_manager()->CreateWaylandScreen( - connection_.get()); EXPECT_CALL(delegate_, OnAcceleratedWidgetAvailable(_)) .WillOnce(SaveArg<0>(&widget_)); PlatformWindowInitProperties properties;
diff --git a/ui/ozone/platform/wayland/test/wayland_test.h b/ui/ozone/platform/wayland/test/wayland_test.h index 0c4103e1..461d279 100644 --- a/ui/ozone/platform/wayland/test/wayland_test.h +++ b/ui/ozone/platform/wayland/test/wayland_test.h
@@ -27,8 +27,6 @@ namespace ui { -class WaylandScreen; - const uint32_t kXdgShellV5 = 5; const uint32_t kXdgShellV6 = 6; @@ -53,7 +51,6 @@ MockPlatformWindowDelegate delegate_; std::unique_ptr<WaylandConnectionProxy> connection_proxy_; std::unique_ptr<WaylandConnection> connection_; - std::unique_ptr<WaylandScreen> screen_; std::unique_ptr<WaylandWindow> window_; gfx::AcceleratedWidget widget_ = gfx::kNullAcceleratedWidget;
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn index 889ff886e..9e71a06 100644 --- a/ui/views/BUILD.gn +++ b/ui/views/BUILD.gn
@@ -582,7 +582,12 @@ "oleacc.lib", "uiautomationcore.lib", ] - ldflags = [ "/DELAYLOAD:user32.dll" ] + ldflags = [ + "/DELAYLOAD:dwmapi.dll", + "/DELAYLOAD:imm32.dll", + "/DELAYLOAD:oleacc.dll", + "/DELAYLOAD:user32.dll", + ] deps += [ "//third_party/iaccessible2", "//third_party/wtl",
diff --git a/ui/views/controls/menu/submenu_view.cc b/ui/views/controls/menu/submenu_view.cc index b054b1fe..ac11f57e 100644 --- a/ui/views/controls/menu/submenu_view.cc +++ b/ui/views/controls/menu/submenu_view.cc
@@ -159,16 +159,16 @@ const MenuItemView* menu = static_cast<const MenuItemView*>(child); const MenuItemView::MenuItemDimensions& dimensions = menu->GetDimensions(); - max_simple_width = std::max(max_simple_width, dimensions.standard_width); + max_simple_width = std::max( + max_simple_width, dimensions.standard_width); max_minor_text_width_ = std::max(max_minor_text_width_, dimensions.minor_text_width); - max_complex_width = - std::max(max_complex_width, - dimensions.standard_width + dimensions.children_width); + max_complex_width = std::max(max_complex_width, + dimensions.standard_width + dimensions.children_width); touchable_minimum_width = dimensions.standard_width; } else { - max_complex_width = - std::max(max_complex_width, child->GetPreferredSize().width()); + max_complex_width = std::max(max_complex_width, + child->GetPreferredSize().width()); } } if (max_minor_text_width_ > 0) @@ -176,10 +176,10 @@ // Finish calculating our optimum width. gfx::Insets insets = GetInsets(); - int width = std::max( - max_complex_width, - std::max(max_simple_width + max_minor_text_width_ + insets.width(), - minimum_preferred_width_ - 2 * insets.width())); + int width = std::max(max_complex_width, + std::max(max_simple_width + max_minor_text_width_ + + insets.width(), + minimum_preferred_width_ - 2 * insets.width())); if (parent_menu_item_->GetMenuController() && parent_menu_item_->GetMenuController()->use_touchable_layout()) { @@ -316,8 +316,8 @@ if (scrolled_to_top(*i)) i = next_iter; } - ScrollRectToVisible( - gfx::Rect(gfx::Point(0, scroll_target), vis_bounds.size())); + ScrollRectToVisible(gfx::Rect(gfx::Point(0, scroll_target), + vis_bounds.size())); vis_bounds = GetVisibleBounds(); }
diff --git a/ui/views/controls/textfield/textfield_unittest.cc b/ui/views/controls/textfield/textfield_unittest.cc index 220049e..28fcc5a3af 100644 --- a/ui/views/controls/textfield/textfield_unittest.cc +++ b/ui/views/controls/textfield/textfield_unittest.cc
@@ -3197,7 +3197,7 @@ } // No touch on desktop Mac. Tracked in http://crbug.com/445520. -#if defined(OS_MACOSX) && !defined(USE_AURA) +#if defined(OS_MACOSX) #define MAYBE_TapOnSelection DISABLED_TapOnSelection #else #define MAYBE_TapOnSelection TapOnSelection
diff --git a/ui/views/widget/desktop_aura/desktop_screen_ozone.cc b/ui/views/widget/desktop_aura/desktop_screen_ozone.cc index 0fa4243..727a8da 100644 --- a/ui/views/widget/desktop_aura/desktop_screen_ozone.cc +++ b/ui/views/widget/desktop_aura/desktop_screen_ozone.cc
@@ -42,7 +42,7 @@ display::Display display(display_snapshot->display_id()); display.set_bounds(gfx::Rect(scaled_size)); display.set_work_area(display.bounds()); - display.SetDeviceScaleFactor(device_scale_factor); + display.set_device_scale_factor(device_scale_factor); ProcessDisplayChanged(display, true /* is_primary */); }
diff --git a/ui/views/widget/desktop_aura/desktop_screen_x11_unittest.cc b/ui/views/widget/desktop_aura/desktop_screen_x11_unittest.cc index 175db65..e4ce4e64 100644 --- a/ui/views/widget/desktop_aura/desktop_screen_x11_unittest.cc +++ b/ui/views/widget/desktop_aura/desktop_screen_x11_unittest.cc
@@ -440,25 +440,25 @@ NotifyDisplaysChanged(displays); ResetDisplayChanges(); - displays[0].SetDeviceScaleFactor(2.5f); + displays[0].set_device_scale_factor(2.5f); NotifyDisplaysChanged(displays); EXPECT_EQ(1u, changed_display_.size()); EXPECT_EQ(2.5f, gfx::GetFontRenderParamsDeviceScaleFactor()); - displays[1].SetDeviceScaleFactor(2.5f); + displays[1].set_device_scale_factor(2.5f); NotifyDisplaysChanged(displays); EXPECT_EQ(2u, changed_display_.size()); - displays[0].SetDeviceScaleFactor(2.5f); + displays[0].set_device_scale_factor(2.5f); NotifyDisplaysChanged(displays); EXPECT_EQ(2u, changed_display_.size()); - displays[1].SetDeviceScaleFactor(2.5f); + displays[1].set_device_scale_factor(2.5f); NotifyDisplaysChanged(displays); EXPECT_EQ(2u, changed_display_.size()); - displays[0].SetDeviceScaleFactor(1.f); - displays[1].SetDeviceScaleFactor(1.f); + displays[0].set_device_scale_factor(1.f); + displays[1].set_device_scale_factor(1.f); NotifyDisplaysChanged(displays); EXPECT_EQ(4u, changed_display_.size()); EXPECT_EQ(1.f, gfx::GetFontRenderParamsDeviceScaleFactor());
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc index 4dda760..8bdb7b0 100644 --- a/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc +++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc
@@ -1426,24 +1426,15 @@ if (override_redirect_) attribute_mask |= CWOverrideRedirect; - bool enable_transparent_visuals; - switch (params.opacity) { - case Widget::InitParams::OPAQUE_WINDOW: - enable_transparent_visuals = false; - break; - case Widget::InitParams::TRANSLUCENT_WINDOW: - enable_transparent_visuals = true; - break; - case Widget::InitParams::INFER_OPACITY: - default: - enable_transparent_visuals = params.type == Widget::InitParams::TYPE_DRAG; - } - Visual* visual = CopyFromParent; int depth = CopyFromParent; Colormap colormap = CopyFromParent; + + // GLSurfaceGLX always create child window with alpha channel. If the parent + // window doesn't have alpha channel, it causes flash, so always request argb + // visual. ui::XVisualManager::GetInstance()->ChooseVisualForWindow( - enable_transparent_visuals, &visual, &depth, &colormap, + true /* want_argb_visual */, &visual, &depth, &colormap, &use_argb_visual_); if (colormap != CopyFromParent) {
diff --git a/ui/views/widget/widget_interactive_uitest.cc b/ui/views/widget/widget_interactive_uitest.cc index e2fc55c..683c60ae 100644 --- a/ui/views/widget/widget_interactive_uitest.cc +++ b/ui/views/widget/widget_interactive_uitest.cc
@@ -1094,7 +1094,7 @@ // Disabled on Mac. Desktop Mac doesn't have system modal windows since Carbon // was deprecated. It does have application modal windows, but only Ash requests // those. -#if defined(OS_MACOSX) && !defined(USE_AURA) +#if defined(OS_MACOSX) #define MAYBE_SystemModalWindowReleasesCapture \ DISABLED_SystemModalWindowReleasesCapture #elif defined(OS_CHROMEOS) @@ -1345,7 +1345,7 @@ // currently only Desktop widgets and fullscreen changes have to coordinate with // the OS. See BridgedNativeWidgetUITest for native Mac fullscreen tests. // Maximize on mac is also (intentionally) a no-op. -#if defined(OS_MACOSX) && !defined(USE_AURA) +#if defined(OS_MACOSX) #define MAYBE_ExitFullscreenRestoreState DISABLED_ExitFullscreenRestoreState #else #define MAYBE_ExitFullscreenRestoreState ExitFullscreenRestoreState @@ -1821,7 +1821,7 @@ child->AddObserver(&observer); child->Show(); -#if defined(OS_MACOSX) && !defined(USE_AURA) +#if defined(OS_MACOSX) // On Mac, activation is asynchronous. A single trip to the runloop should be // sufficient. On Aura platforms, note that since the child widget isn't top- // level, the aura window manager gets asked whether the widget is active, not
diff --git a/ui/views/widget/widget_unittest.cc b/ui/views/widget/widget_unittest.cc index ee89998..5b5698f 100644 --- a/ui/views/widget/widget_unittest.cc +++ b/ui/views/widget/widget_unittest.cc
@@ -2350,7 +2350,7 @@ generator.MoveMouseTo(10, 10); // No touch on desktop Mac. Tracked in http://crbug.com/445520. -#if defined(OS_MACOSX) && !defined(USE_AURA) +#if defined(OS_MACOSX) generator.ClickLeftButton(); #else generator.PressTouch(); @@ -3941,7 +3941,7 @@ }; // Disabled on Mac: All drop shadows are managed out of process for now. -#if defined(OS_MACOSX) && !defined(USE_AURA) +#if defined(OS_MACOSX) #define MAYBE_ShadowsInRootWindow DISABLED_ShadowsInRootWindow #else #define MAYBE_ShadowsInRootWindow ShadowsInRootWindow
diff --git a/ui/views/win/hwnd_message_handler.cc b/ui/views/win/hwnd_message_handler.cc index 9e686fc..5c80caa5 100644 --- a/ui/views/win/hwnd_message_handler.cc +++ b/ui/views/win/hwnd_message_handler.cc
@@ -23,6 +23,7 @@ #include "base/time/time.h" #include "base/trace_event/trace_event.h" #include "base/win/scoped_gdi_object.h" +#include "base/win/win_util.h" #include "base/win/windows_version.h" #include "third_party/skia/include/core/SkPath.h" #include "ui/accessibility/accessibility_switches.h" @@ -422,18 +423,15 @@ // Create the window. WindowImpl::Init(parent, bounds); - if (!called_enable_non_client_dpi_scaling_ && - delegate_->HasFrame() && + if (!called_enable_non_client_dpi_scaling_ && delegate_->HasFrame() && base::win::IsProcessPerMonitorDpiAware()) { - static auto enable_child_window_dpi_message_func = []() { - // Derived signature; not available in headers. - // This call gets Windows to scale the non-client area when WM_DPICHANGED - // is fired. - using EnableChildWindowDpiMessagePtr = LRESULT (WINAPI*)(HWND, BOOL); - return reinterpret_cast<EnableChildWindowDpiMessagePtr>( - GetProcAddress(GetModuleHandle(L"user32.dll"), - "EnableChildWindowDpiMessage")); - }(); + // Derived signature; not available in headers. + // This call gets Windows to scale the non-client area when + // WM_DPICHANGED is fired. + using EnableChildWindowDpiMessagePtr = LRESULT(WINAPI*)(HWND, BOOL); + static const auto enable_child_window_dpi_message_func = + reinterpret_cast<EnableChildWindowDpiMessagePtr>( + base::win::GetUser32FunctionPointer("EnableChildWindowDpiMessage")); if (enable_child_window_dpi_message_func) enable_child_window_dpi_message_func(hwnd(), TRUE); } @@ -1920,11 +1918,12 @@ using GetPointerTypeFn = BOOL(WINAPI*)(UINT32, POINTER_INPUT_TYPE*); UINT32 pointer_id = GET_POINTERID_WPARAM(w_param); POINTER_INPUT_TYPE pointer_type; - static GetPointerTypeFn get_pointer_type = reinterpret_cast<GetPointerTypeFn>( - GetProcAddress(GetModuleHandleA("user32.dll"), "GetPointerType")); + static const auto get_pointer_type = reinterpret_cast<GetPointerTypeFn>( + base::win::GetUser32FunctionPointer("GetPointerType")); if (get_pointer_type && get_pointer_type(pointer_id, &pointer_type) && - pointer_type == PT_TOUCHPAD) + pointer_type == PT_TOUCHPAD) { return PA_NOACTIVATE; + } SetMsgHandled(FALSE); return -1; } @@ -1941,8 +1940,8 @@ UINT32 pointer_id = GET_POINTERID_WPARAM(w_param); using GetPointerTypeFn = BOOL(WINAPI*)(UINT32, POINTER_INPUT_TYPE*); POINTER_INPUT_TYPE pointer_type; - static GetPointerTypeFn get_pointer_type = reinterpret_cast<GetPointerTypeFn>( - GetProcAddress(GetModuleHandleA("user32.dll"), "GetPointerType")); + static const auto get_pointer_type = reinterpret_cast<GetPointerTypeFn>( + base::win::GetUser32FunctionPointer("GetPointerType")); // If the WM_POINTER messages are not sent from a stylus device, then we do // not handle them to make sure we do not change the current behavior of // touch and mouse inputs. @@ -1959,9 +1958,10 @@ return HandlePointerEventTypeTouch(message, w_param, l_param); FALLTHROUGH; default: - SetMsgHandled(FALSE); - return -1; + break; } + SetMsgHandled(FALSE); + return -1; } void HWNDMessageHandler::OnMove(const gfx::Point& point) { @@ -2130,11 +2130,10 @@ LRESULT HWNDMessageHandler::OnNCCreate(LPCREATESTRUCT lpCreateStruct) { SetMsgHandled(FALSE); if (delegate_->HasFrame() && base::win::IsProcessPerMonitorDpiAware()) { - static auto enable_non_client_dpi_scaling_func = []() { - return reinterpret_cast<decltype(::EnableNonClientDpiScaling)*>( - GetProcAddress(GetModuleHandle(L"user32.dll"), - "EnableNonClientDpiScaling")); - }(); + using EnableNonClientDpiScalingPtr = decltype(::EnableNonClientDpiScaling)*; + static const auto enable_non_client_dpi_scaling_func = + reinterpret_cast<EnableNonClientDpiScalingPtr>( + base::win::GetUser32FunctionPointer("EnableNonClientDpiScaling")); called_enable_non_client_dpi_scaling_ = !!(enable_non_client_dpi_scaling_func && enable_non_client_dpi_scaling_func(hwnd())); @@ -2960,9 +2959,9 @@ UINT32 pointer_id = GET_POINTERID_WPARAM(w_param); using GetPointerTouchInfoFn = BOOL(WINAPI*)(UINT32, POINTER_TOUCH_INFO*); POINTER_TOUCH_INFO pointer_touch_info; - static GetPointerTouchInfoFn get_pointer_touch_info = - reinterpret_cast<GetPointerTouchInfoFn>(GetProcAddress( - GetModuleHandleA("user32.dll"), "GetPointerTouchInfo")); + static const auto get_pointer_touch_info = + reinterpret_cast<GetPointerTouchInfoFn>( + base::win::GetUser32FunctionPointer("GetPointerTouchInfo")); if (!get_pointer_touch_info || !get_pointer_touch_info(pointer_id, &pointer_touch_info)) { SetMsgHandled(FALSE); @@ -3060,9 +3059,9 @@ UINT32 pointer_id = GET_POINTERID_WPARAM(w_param); using GetPointerPenInfoFn = BOOL(WINAPI*)(UINT32, POINTER_PEN_INFO*); POINTER_PEN_INFO pointer_pen_info; - static GetPointerPenInfoFn get_pointer_pen_info = + static const auto get_pointer_pen_info = reinterpret_cast<GetPointerPenInfoFn>( - GetProcAddress(GetModuleHandleA("user32.dll"), "GetPointerPenInfo")); + base::win::GetUser32FunctionPointer("GetPointerPenInfo")); if (!get_pointer_pen_info || !get_pointer_pen_info(pointer_id, &pointer_pen_info)) { SetMsgHandled(FALSE); @@ -3080,13 +3079,12 @@ // window, so use the weak ptr to check if destruction occured or not. base::WeakPtr<HWNDMessageHandler> ref(msg_handler_weak_factory_.GetWeakPtr()); if (event) { - if (event->IsTouchEvent()) { + if (event->IsTouchEvent()) delegate_->HandleTouchEvent(event->AsTouchEvent()); - } else if (event->IsMouseEvent()) { + else if (event->IsMouseEvent()) delegate_->HandleMouseEvent(event->AsMouseEvent()); - } else { + else NOTREACHED(); - } last_touch_or_pen_message_time_ = ::GetMessageTime(); }
diff --git a/ui/webui/resources/cr_elements/cr_checkbox/cr_checkbox.js b/ui/webui/resources/cr_elements/cr_checkbox/cr_checkbox.js index 10b29c1..39eb861 100644 --- a/ui/webui/resources/cr_elements/cr_checkbox/cr_checkbox.js +++ b/ui/webui/resources/cr_elements/cr_checkbox/cr_checkbox.js
@@ -42,6 +42,7 @@ tabIndex: { type: Number, value: 0, + observer: 'onTabIndexChanged_', }, }, @@ -139,6 +140,12 @@ this.click(); }, + /** @private */ + onTabIndexChanged_: function() { + // :host shouldn't have a tabindex because it's set on #checkbox. + this.removeAttribute('tabindex'); + }, + // customize the element's ripple _createRipple: function() { this._rippleContainer = this.$.checkbox;