diff --git a/DEPS b/DEPS index f2dd11b..0a69d95 100644 --- a/DEPS +++ b/DEPS
@@ -40,11 +40,11 @@ # 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': 'd170c0fb48aa1e1a00d183d72f9e53ea9bb4d951', + 'skia_revision': '5664e65eb1680a14eeaa6ca79ddf9e734518c822', # 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': '58c56c180c813e10953a1f3f3952c909181c722b', + 'v8_revision': '447fde347b249b62e25696d2b6a3c28930879063', # 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. @@ -52,7 +52,7 @@ # 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': 'f58417747f869a1e6933f68618433d209fba1cc4', + 'angle_revision': 'd262799c3784050fd21b02717273829eff1231cf', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling build tools # and whatever else without interference from each other. @@ -64,7 +64,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling PDFium # and whatever else without interference from each other. - 'pdfium_revision': 'b8e00f24780335cdd068565f39d2874c81e799b9', + 'pdfium_revision': '40baddef7fda756c29b813dc1fd67b28d745aa8c', # 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. @@ -96,7 +96,7 @@ # Three lines of non-changing comments so that # the commit queue can handle CLs rolling catapult # and whatever else without interference from each other. - 'catapult_revision': '2b7090e93566d7874d3079c2b8f2341e8fb1d201', + 'catapult_revision': '9300a23423c036c30edd7342bcf152298c98eb6f', # Three lines of non-changing comments so that # the commit queue can handle CLs rolling libFuzzer # and whatever else without interference from each other.
diff --git a/WATCHLISTS b/WATCHLISTS index 7c6b40c..119bff8f 100644 --- a/WATCHLISTS +++ b/WATCHLISTS
@@ -995,7 +995,7 @@ '|chrome/browser/.*supervised_user', }, 'swreporter': { - 'filepath': 'chrome/browser/safe_browsing/srt_'\ + 'filepath': 'chrome/browser/safe_browsing/chrome_cleaner/'\ '|chrome/browser/component_updater/sw_reporter_', }, 'sync': {
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd index 50522115..34a9e15 100644 --- a/ash/ash_strings.grd +++ b/ash/ash_strings.grd
@@ -807,10 +807,13 @@ Session ends in <ph name="session_time_remaining">$1<ex>4 minutes 23 seconds</ex></ph>. You will be signed out. </message> <message name="IDS_ASH_STATUS_TRAY_ROTATION_LOCK_AUTO" desc="The text shown in the tray menu when rotation is set to auto and the user can enable the rotation lock by tapping."> - Enable rotation lock + Auto rotate </message> - <message name="IDS_ASH_STATUS_TRAY_ROTATION_LOCK_LOCKED" desc="The text shown in the tray menu when rotation is set to locked and tapping will disable the lock."> - Disable rotation lock + <message name="IDS_ASH_STATUS_TRAY_ROTATION_LOCK_LANDSCAPE" desc="The text shown in the tray menu when rotation is set locked to landscape."> + Landscape + </message> + <message name="IDS_ASH_STATUS_TRAY_ROTATION_LOCK_PORTRAIT" desc="The text shown in the tray menu when rotation is set locked to portrait."> + Portrait </message> <message name="IDS_ASH_STATUS_TRAY_KEYBOARD_DISABLED" desc="The text shown in the tray menu when the virtual keyboard is disabled."> On-screen keyboard disabled
diff --git a/ash/resources/vector_icons/BUILD.gn b/ash/resources/vector_icons/BUILD.gn index e4fa37a..622ffc4a 100644 --- a/ash/resources/vector_icons/BUILD.gn +++ b/ash/resources/vector_icons/BUILD.gn
@@ -155,8 +155,10 @@ "system_menu_power.icon", "system_menu_rotation_lock_auto.1x.icon", "system_menu_rotation_lock_auto.icon", - "system_menu_rotation_lock_locked.1x.icon", - "system_menu_rotation_lock_locked.icon", + "system_menu_rotation_lock_landscape.1x.icon", + "system_menu_rotation_lock_landscape.icon", + "system_menu_rotation_lock_portrait.1x.icon", + "system_menu_rotation_lock_portrait.icon", "system_menu_screen_share.1x.icon", "system_menu_screen_share.icon", "system_menu_settings.1x.icon", @@ -204,6 +206,8 @@ "system_tray_cast.icon", "system_tray_recording.1x.icon", "system_tray_recording.icon", + "system_tray_rotation_lock_auto.1x.icon", + "system_tray_rotation_lock_auto.icon", "system_tray_rotation_lock_locked.1x.icon", "system_tray_rotation_lock_locked.icon", "system_tray_screen_share.1x.icon",
diff --git a/ash/resources/vector_icons/system_menu_rotation_lock_auto.1x.icon b/ash/resources/vector_icons/system_menu_rotation_lock_auto.1x.icon index 8f0c0654..6d8f8d2 100644 --- a/ash/resources/vector_icons/system_menu_rotation_lock_auto.1x.icon +++ b/ash/resources/vector_icons/system_menu_rotation_lock_auto.1x.icon
@@ -3,36 +3,40 @@ // found in the LICENSE file. CANVAS_DIMENSIONS, 20, -MOVE_TO, 13, 3.68f, -R_CUBIC_TO, 2.19f, 1.03f, 3.76f, 3.15f, 4, 5.65f, -H_LINE_TO, 18, -CUBIC_TO, 17.66f, 5.23f, 14.21f, 2, 10, 2, -R_LINE_TO, -0.44f, 0.02f, -R_LINE_TO, 2.55f, 2.54f, -R_LINE_TO, 0.89f, -0.88f, -CLOSE, -R_MOVE_TO, -4.18f, -0.51f, -R_ARC_TO, 1, 1, 0, 0, 0, -1.42f, 0, -R_LINE_TO, -4.26f, 4.24f, -R_ARC_TO, 0.99f, 0.99f, 0, 0, 0, 0, 1.41f, -R_LINE_TO, 8.05f, 8.01f, -R_ARC_TO, 1, 1, 0, 0, 0, 1.42f, 0, -R_LINE_TO, 4.26f, -4.24f, -R_ARC_TO, 0.99f, 0.99f, 0, 0, 0, 0, -1.41f, -LINE_TO, 8.82f, 3.17f, -CLOSE, -R_MOVE_TO, 2.9f, 12.39f, -LINE_TO, 4.42f, 8.29f, -R_LINE_TO, 3.86f, -3.84f, -R_LINE_TO, 7.29f, 7.26f, -R_LINE_TO, -3.86f, 3.84f, -CLOSE, -R_MOVE_TO, -4.72f, 0.77f, -R_ARC_TO, 6.99f, 6.99f, 0, 0, 1, -4, -5.65f, +MOVE_TO, 6.03f, 16.72f, +R_CUBIC_TO, -1.76f, -0.87f, -3.03f, -2.66f, -3.22f, -4.78f, H_LINE_TO, 2, -CUBIC_TO, 2.34f, 14.77f, 5.79f, 18, 10, 18, -R_LINE_TO, 0.44f, -0.02f, -R_LINE_TO, -2.55f, -2.54f, -R_LINE_TO, -0.89f, 0.88f, +R_CUBIC_TO, 0.28f, 3.47f, 3.06f, 6.2f, 6.45f, 6.2f, +R_LINE_TO, 0.36f, -0.02f, +R_LINE_TO, -2.06f, -2.15f, +R_LINE_TO, -0.72f, 0.75f, +CLOSE, +MOVE_TO, 14.64f, 3.07f, +R_CUBIC_TO, 1.77f, 0.87f, 3.03f, 2.66f, 3.22f, 4.78f, +R_H_LINE_TO, 0.81f, +R_CUBIC_TO, -0.28f, -3.47f, -3.05f, -6.2f, -6.45f, -6.2f, +R_LINE_TO, -0.36f, 0.02f, +R_LINE_TO, 2.06f, 2.15f, +R_LINE_TO, 0.72f, -0.75f, +CLOSE, +MOVE_TO, 11.9f, 16.98f, +LINE_TO, 3.54f, 8.25f, +R_LINE_TO, 5.23f, -5.45f, +R_LINE_TO, 6.6f, 6.89f, +R_LINE_TO, 0.39f, 0.41f, +R_LINE_TO, 1.37f, 1.43f, +R_LINE_TO, -5.23f, 5.46f, +CLOSE, +R_MOVE_TO, 6.27f, -6.54f, +LINE_TO, 16.8f, 9, +R_LINE_TO, -0.39f, -0.41f, +R_LINE_TO, -6.6f, -6.89f, +R_CUBIC_TO, -0.57f, -0.6f, -1.51f, -0.6f, -2.09f, 0, +LINE_TO, 2.49f, 7.16f, +R_CUBIC_TO, -0.57f, 0.6f, -0.57f, 1.58f, 0, 2.18f, +R_LINE_TO, 8.36f, 8.73f, +R_CUBIC_TO, 0.57f, 0.6f, 1.51f, 0.59f, 2.09f, -0.01f, +R_LINE_TO, 5.23f, -5.45f, +R_CUBIC_TO, 0.58f, -0.6f, 0.58f, -1.58f, 0, -2.18f, CLOSE, END
diff --git a/ash/resources/vector_icons/system_menu_rotation_lock_auto.icon b/ash/resources/vector_icons/system_menu_rotation_lock_auto.icon index 7dfba79..cf0f07e 100644 --- a/ash/resources/vector_icons/system_menu_rotation_lock_auto.icon +++ b/ash/resources/vector_icons/system_menu_rotation_lock_auto.icon
@@ -3,36 +3,40 @@ // found in the LICENSE file. CANVAS_DIMENSIONS, 40, -MOVE_TO, 26, 7.36f, -R_CUBIC_TO, 4.38f, 2.07f, 7.51f, 6.29f, 7.99f, 11.31f, -H_LINE_TO, 36, -CUBIC_TO, 35.32f, 10.45f, 28.42f, 4, 20, 4, -R_LINE_TO, -0.88f, 0.04f, -R_LINE_TO, 5.1f, 5.08f, -R_LINE_TO, 1.78f, -1.76f, -CLOSE, -MOVE_TO, 17.63f, 6.33f, -R_ARC_TO, 2, 2, 0, 0, 0, -2.84f, 0, -R_LINE_TO, -8.52f, 8.48f, -R_ARC_TO, 1.98f, 1.98f, 0, 0, 0, 0, 2.83f, -LINE_TO, 22.37f, 33.67f, -R_CUBIC_TO, 0.79f, 0.79f, 2.06f, 0.79f, 2.84f, 0, -R_LINE_TO, 8.52f, -8.48f, -R_CUBIC_TO, 0.79f, -0.79f, 0.79f, -2.05f, 0, -2.83f, -LINE_TO, 17.63f, 6.33f, -CLOSE, -R_MOVE_TO, 5.94f, 25.2f, -LINE_TO, 8.42f, 16.45f, -R_LINE_TO, 8.01f, -7.98f, -LINE_TO, 31.58f, 23.55f, -R_LINE_TO, -8.01f, 7.98f, -CLOSE, -R_MOVE_TO, -9.56f, 1.11f, -R_CUBIC_TO, -4.38f, -2.05f, -7.51f, -6.29f, -7.99f, -11.31f, +MOVE_TO, 12.07f, 34.57f, +R_CUBIC_TO, -3.53f, -1.69f, -6.06f, -5.17f, -6.45f, -9.29f, H_LINE_TO, 4, -CUBIC_TO, 4.68f, 29.55f, 11.58f, 36, 20, 36, -R_LINE_TO, 0.88f, -0.04f, -R_LINE_TO, -5.1f, -5.08f, -R_LINE_TO, -1.78f, 1.76f, +R_CUBIC_TO, 0.55f, 6.75f, 6.11f, 12.05f, 12.9f, 12.05f, +R_LINE_TO, 0.71f, -0.03f, +R_LINE_TO, -4.11f, -4.18f, +R_LINE_TO, -1.44f, 1.46f, +CLOSE, +MOVE_TO, 29.28f, 8.02f, +R_CUBIC_TO, 3.53f, 1.69f, 6.06f, 5.17f, 6.45f, 9.29f, +R_H_LINE_TO, 1.62f, +R_CUBIC_TO, -0.55f, -6.75f, -6.11f, -12.05f, -12.9f, -12.05f, +R_LINE_TO, -0.71f, 0.03f, +LINE_TO, 27.84f, 9.48f, +R_LINE_TO, 1.44f, -1.46f, +CLOSE, +MOVE_TO, 23.8f, 35.07f, +LINE_TO, 7.07f, 18.1f, +LINE_TO, 17.52f, 7.5f, +R_LINE_TO, 13.2f, 13.39f, +R_LINE_TO, 0.78f, 0.8f, +R_LINE_TO, 2.74f, 2.78f, +R_LINE_TO, -10.45f, 10.61f, +CLOSE, +R_MOVE_TO, 12.54f, -12.73f, +R_LINE_TO, -2.74f, -2.78f, +R_LINE_TO, -0.78f, -0.79f, +R_LINE_TO, -13.2f, -13.39f, +R_CUBIC_TO, -1.15f, -1.17f, -3.03f, -1.17f, -4.18f, 0, +LINE_TO, 4.98f, 15.98f, +R_CUBIC_TO, -1.15f, 1.17f, -1.15f, 3.08f, 0, 4.24f, +R_LINE_TO, 16.73f, 16.97f, +R_CUBIC_TO, 1.15f, 1.17f, 3.02f, 1.16f, 4.17f, -0.01f, +R_LINE_TO, 10.46f, -10.6f, +R_CUBIC_TO, 1.15f, -1.17f, 1.15f, -3.08f, 0, -4.24f, CLOSE, END
diff --git a/ash/resources/vector_icons/system_menu_rotation_lock_landscape.1x.icon b/ash/resources/vector_icons/system_menu_rotation_lock_landscape.1x.icon new file mode 100644 index 0000000..a229a01 --- /dev/null +++ b/ash/resources/vector_icons/system_menu_rotation_lock_landscape.1x.icon
@@ -0,0 +1,22 @@ +// 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. + +CANVAS_DIMENSIONS, 20, +MOVE_TO, 2, 6.01f, +CUBIC_TO, 2, 4.9f, 2.9f, 4, 3.99f, 4, +R_H_LINE_TO, 12.01f, +CUBIC_TO, 17.11f, 4, 18, 4.9f, 18, 6.01f, +R_V_LINE_TO, 7.98f, +R_CUBIC_TO, 0, 1.11f, -0.89f, 2.01f, -1.99f, 2.01f, +H_LINE_TO, 3.99f, +CUBIC_TO, 2.89f, 16, 2, 15.1f, 2, 13.99f, +V_LINE_TO, 6.01f, +CLOSE, +MOVE_TO, 5, 6, +R_H_LINE_TO, 10, +R_V_LINE_TO, 8, +H_LINE_TO, 5, +V_LINE_TO, 6, +CLOSE, +END
diff --git a/ash/resources/vector_icons/system_menu_rotation_lock_landscape.icon b/ash/resources/vector_icons/system_menu_rotation_lock_landscape.icon new file mode 100644 index 0000000..d22e2fd --- /dev/null +++ b/ash/resources/vector_icons/system_menu_rotation_lock_landscape.icon
@@ -0,0 +1,22 @@ +// 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. + +CANVAS_DIMENSIONS, 40, +MOVE_TO, 33, 8, +H_LINE_TO, 7, +R_CUBIC_TO, -1.69f, 0, -3, 1.35f, -3, 3, +R_V_LINE_TO, 18, +R_CUBIC_TO, 0, 1.65f, 1.31f, 3, 3, 3, +R_H_LINE_TO, 26, +R_CUBIC_TO, 1.69f, 0, 2.99f, -1.35f, 3, -3, +V_LINE_TO, 11, +R_CUBIC_TO, 0, -1.65f, -1.31f, -3, -3, -3, +CLOSE, +R_MOVE_TO, -3, 21, +H_LINE_TO, 10, +V_LINE_TO, 11, +R_H_LINE_TO, 20, +R_V_LINE_TO, 18, +CLOSE, +END
diff --git a/ash/resources/vector_icons/system_menu_rotation_lock_locked.1x.icon b/ash/resources/vector_icons/system_menu_rotation_lock_locked.1x.icon deleted file mode 100644 index 990f4fbf..0000000 --- a/ash/resources/vector_icons/system_menu_rotation_lock_locked.1x.icon +++ /dev/null
@@ -1,52 +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. - -CANVAS_DIMENSIONS, 20, -MOVE_TO, 15.44f, 9.26f, -R_LINE_TO, -0.96f, 0.96f, -R_LINE_TO, 1.52f, 1.52f, -R_LINE_TO, -3.87f, 3.87f, -LINE_TO, 4.4f, 7.87f, -R_LINE_TO, 3.87f, -3.87f, -LINE_TO, 9.7f, 5.44f, -R_LINE_TO, 0.96f, -0.96f, -R_LINE_TO, -1.67f, -1.67f, -R_ARC_TO, 1.02f, 1.02f, 0, 0, 0, -1.45f, 0, -LINE_TO, 3.2f, 7.15f, -R_ARC_TO, 1.02f, 1.02f, 0, 0, 0, 0, 1.45f, -R_LINE_TO, 8.21f, 8.21f, -R_ARC_TO, 1.02f, 1.02f, 0, 0, 0, 1.45f, 0, -R_LINE_TO, 4.34f, -4.34f, -R_ARC_TO, 1.02f, 1.02f, 0, 0, 0, 0, -1.45f, -R_LINE_TO, -1.76f, -1.75f, -CLOSE, -R_MOVE_TO, -8.34f, 7.02f, -R_ARC_TO, 7.16f, 7.16f, 0, 0, 1, -4.08f, -5.79f, -H_LINE_TO, 2, -CUBIC_TO, 2.35f, 14.69f, 5.87f, 18, 10.16f, 18, -R_LINE_TO, 0.45f, -0.02f, -R_LINE_TO, -2.6f, -2.61f, -R_LINE_TO, -0.91f, 0.91f, -CLOSE, -MOVE_TO, 15.33f, 8, -CUBIC_TO, 15.8f, 8, 16, 7.79f, 16, 7.38f, -V_LINE_TO, 5.29f, -R_CUBIC_TO, 0, -0.42f, -0.37f, -0.62f, -0.67f, -0.62f, -R_V_LINE_TO, -0.28f, -CUBIC_TO, 15.33f, 3.62f, 14.74f, 3, 14, 3, -R_CUBIC_TO, -0.74f, 0, -1.33f, 0.62f, -1.33f, 1.39f, -R_V_LINE_TO, 0.28f, -R_CUBIC_TO, -0.29f, 0, -0.67f, 0.21f, -0.67f, 0.63f, -R_V_LINE_TO, 2.08f, -R_CUBIC_TO, 0, 0.42f, 0.2f, 0.63f, 0.67f, 0.63f, -R_H_LINE_TO, 2.67f, -CLOSE, -MOVE_TO, 13.2f, 4.8f, -R_CUBIC_TO, 0, -0.33f, 0.41f, -0.8f, 0.8f, -0.8f, -R_CUBIC_TO, 0.39f, 0, 0.8f, 0.47f, 0.8f, 0.8f, -V_LINE_TO, 5, -R_H_LINE_TO, -1.6f, -R_V_LINE_TO, -0.2f, -CLOSE, -END
diff --git a/ash/resources/vector_icons/system_menu_rotation_lock_locked.icon b/ash/resources/vector_icons/system_menu_rotation_lock_locked.icon deleted file mode 100644 index 69d6ddc..0000000 --- a/ash/resources/vector_icons/system_menu_rotation_lock_locked.icon +++ /dev/null
@@ -1,51 +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. - -CANVAS_DIMENSIONS, 40, -MOVE_TO, 30.89f, 18.51f, -R_LINE_TO, -1.93f, 1.93f, -R_LINE_TO, 3.03f, 3.03f, -R_LINE_TO, -7.73f, 7.73f, -LINE_TO, 8.8f, 15.74f, -R_LINE_TO, 7.73f, -7.73f, -R_LINE_TO, 2.87f, 2.87f, -R_LINE_TO, 1.93f, -1.93f, -R_LINE_TO, -3.35f, -3.35f, -R_ARC_TO, 2.04f, 2.04f, 0, 0, 0, -2.9f, 0, -R_LINE_TO, -8.69f, 8.69f, -R_ARC_TO, 2.04f, 2.04f, 0, 0, 0, 0, 2.9f, -R_LINE_TO, 16.42f, 16.42f, -R_ARC_TO, 2.04f, 2.04f, 0, 0, 0, 2.9f, 0, -R_LINE_TO, 8.69f, -8.69f, -R_ARC_TO, 2.04f, 2.04f, 0, 0, 0, 0, -2.9f, -R_LINE_TO, -3.51f, -3.51f, -CLOSE, -R_MOVE_TO, -16.68f, 14.04f, -R_ARC_TO, 14.33f, 14.33f, 0, 0, 1, -8.16f, -11.58f, -H_LINE_TO, 4, -CUBIC_TO, 4.7f, 29.39f, 11.73f, 36, 20.33f, 36, -R_LINE_TO, 0.9f, -0.04f, -R_LINE_TO, -5.2f, -5.22f, -R_LINE_TO, -1.82f, 1.82f, -CLOSE, -MOVE_TO, 31.33f, 17, -CUBIC_TO, 32.5f, 17, 33, 16.5f, 33, 15.5f, -R_V_LINE_TO, -5, -R_CUBIC_TO, 0, -1, -0.93f, -1.5f, -1.67f, -1.5f, -R_V_LINE_TO, -0.67f, -R_ARC_TO, 3.34f, 3.34f, 0, 0, 0, -6.67f, 0, -V_LINE_TO, 9, -CUBIC_TO, 23.93f, 9, 23, 9.5f, 23, 10.5f, -R_V_LINE_TO, 5, -R_CUBIC_TO, 0, 1, 0.5f, 1.5f, 1.67f, 1.5f, -R_H_LINE_TO, 6.67f, -CLOSE, -MOVE_TO, 26, 8.49f, -R_CUBIC_TO, 0, -0.83f, 1.02f, -1.99f, 2, -1.99f, -R_CUBIC_TO, 0.98f, 0, 2, 1.17f, 2, 1.99f, -V_LINE_TO, 9, -R_H_LINE_TO, -4, -R_V_LINE_TO, -0.51f, -CLOSE, -END
diff --git a/ash/resources/vector_icons/system_menu_rotation_lock_portrait.1x.icon b/ash/resources/vector_icons/system_menu_rotation_lock_portrait.1x.icon new file mode 100644 index 0000000..29d46ab --- /dev/null +++ b/ash/resources/vector_icons/system_menu_rotation_lock_portrait.1x.icon
@@ -0,0 +1,22 @@ +// 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. + +CANVAS_DIMENSIONS, 20, +MOVE_TO, 16, 3.6f, +R_V_LINE_TO, 12.8f, +R_CUBIC_TO, 0, 0.88f, -0.77f, 1.6f, -1.71f, 1.6f, +H_LINE_TO, 5.71f, +CUBIC_TO, 4.77f, 18, 4, 17.28f, 4, 16.4f, +V_LINE_TO, 3.6f, +R_CUBIC_TO, 0, -0.88f, 0.77f, -1.59f, 1.71f, -1.59f, +LINE_TO, 14.29f, 2, +CUBIC_TO, 15.23f, 2, 16, 2.72f, 16, 3.6f, +CLOSE, +MOVE_TO, 6, 4, +R_V_LINE_TO, 12, +R_H_LINE_TO, 8, +V_LINE_TO, 4, +H_LINE_TO, 6, +CLOSE, +END
diff --git a/ash/resources/vector_icons/system_menu_rotation_lock_portrait.icon b/ash/resources/vector_icons/system_menu_rotation_lock_portrait.icon new file mode 100644 index 0000000..b301e12 --- /dev/null +++ b/ash/resources/vector_icons/system_menu_rotation_lock_portrait.icon
@@ -0,0 +1,22 @@ +// 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. + +CANVAS_DIMENSIONS, 40, +MOVE_TO, 31, 8, +R_V_LINE_TO, 24, +R_CUBIC_TO, 0, 1.65f, -1.41f, 3, -3.14f, 3, +H_LINE_TO, 12.14f, +CUBIC_TO, 10.41f, 35, 9, 33.65f, 9, 32, +V_LINE_TO, 8, +R_CUBIC_TO, 0, -1.65f, 1.41f, -2.98f, 3.14f, -2.98f, +LINE_TO, 27.86f, 5, +CUBIC_TO, 29.59f, 5, 31, 6.35f, 31, 8, +CLOSE, +MOVE_TO, 12, 8, +R_V_LINE_TO, 24, +R_H_LINE_TO, 16, +V_LINE_TO, 8, +H_LINE_TO, 12, +CLOSE, +END
diff --git a/ash/resources/vector_icons/system_tray_rotation_lock_auto.1x.icon b/ash/resources/vector_icons/system_tray_rotation_lock_auto.1x.icon new file mode 100644 index 0000000..827ae49 --- /dev/null +++ b/ash/resources/vector_icons/system_tray_rotation_lock_auto.1x.icon
@@ -0,0 +1,41 @@ +// 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. + +CANVAS_DIMENSIONS, 16, +MOVE_TO, 1.5f, 9.97f, +R_CUBIC_TO, 0.23f, 2.72f, 2.52f, 4.85f, 5.31f, 4.85f, +R_LINE_TO, 0.29f, -0.01f, +R_LINE_TO, -1.69f, -1.68f, +R_LINE_TO, -0.59f, 0.59f, +R_CUBIC_TO, -1.45f, -0.68f, -2.49f, -2.08f, -2.65f, -3.74f, +H_LINE_TO, 1.5f, +MOVE_TO, 11.91f, 3.02f, +R_CUBIC_TO, 1.45f, 0.68f, 2.49f, 2.08f, 2.65f, 3.74f, +R_H_LINE_TO, 0.67f, +CUBIC_TO, 15, 4.05f, 12.71f, 1.91f, 9.92f, 1.91f, +R_LINE_TO, -0.29f, 0.01f, +R_LINE_TO, 1.69f, 1.69f, +R_LINE_TO, 0.59f, -0.59f, +CLOSE, +MOVE_TO, 9.65f, 13.92f, +LINE_TO, 2.77f, 7.08f, +LINE_TO, 7.07f, 2.81f, +R_LINE_TO, 5.43f, 5.39f, +R_LINE_TO, 0.32f, 0.32f, +R_LINE_TO, 1.13f, 1.12f, +LINE_TO, 9.65f, 13.92f, +CLOSE, +R_MOVE_TO, 5.16f, -5.13f, +R_LINE_TO, -1.13f, -1.12f, +R_LINE_TO, -0.32f, -0.32f, +LINE_TO, 7.93f, 1.96f, +R_CUBIC_TO, -0.47f, -0.47f, -1.25f, -0.47f, -1.72f, 0, +LINE_TO, 1.9f, 6.23f, +R_CUBIC_TO, -0.47f, 0.47f, -0.47f, 1.24f, 0, 1.71f, +R_LINE_TO, 6.89f, 6.84f, +R_CUBIC_TO, 0.47f, 0.47f, 1.25f, 0.47f, 1.72f, 0, +R_LINE_TO, 4.31f, -4.27f, +R_CUBIC_TO, 0.47f, -0.47f, 0.47f, -1.24f, 0, -1.71f, +CLOSE, +END
diff --git a/ash/resources/vector_icons/system_tray_rotation_lock_auto.icon b/ash/resources/vector_icons/system_tray_rotation_lock_auto.icon new file mode 100644 index 0000000..c1b196fb --- /dev/null +++ b/ash/resources/vector_icons/system_tray_rotation_lock_auto.icon
@@ -0,0 +1,41 @@ +// 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. + +CANVAS_DIMENSIONS, 32, +MOVE_TO, 3, 19.95f, +R_CUBIC_TO, 0.45f, 5.44f, 5.03f, 9.71f, 10.63f, 9.71f, +R_LINE_TO, 0.59f, -0.02f, +R_LINE_TO, -3.38f, -3.37f, +LINE_TO, 9.64f, 27.43f, +R_CUBIC_TO, -2.91f, -1.36f, -4.99f, -4.17f, -5.31f, -7.48f, +H_LINE_TO, 3, +MOVE_TO, 23.82f, 6.05f, +R_CUBIC_TO, 2.91f, 1.36f, 4.99f, 4.17f, 5.31f, 7.48f, +R_H_LINE_TO, 1.33f, +R_CUBIC_TO, -0.45f, -5.44f, -5.03f, -9.71f, -10.62f, -9.71f, +R_LINE_TO, -0.59f, 0.03f, +R_LINE_TO, 3.39f, 3.37f, +R_LINE_TO, 1.18f, -1.17f, +CLOSE, +MOVE_TO, 19.3f, 27.84f, +LINE_TO, 5.53f, 14.17f, +R_LINE_TO, 8.61f, -8.54f, +R_LINE_TO, 10.87f, 10.79f, +R_LINE_TO, 0.64f, 0.64f, +R_LINE_TO, 2.26f, 2.24f, +R_LINE_TO, -8.61f, 8.54f, +CLOSE, +R_MOVE_TO, 10.33f, -10.25f, +R_LINE_TO, -2.26f, -2.24f, +R_LINE_TO, -0.64f, -0.64f, +R_LINE_TO, -10.87f, -10.79f, +R_CUBIC_TO, -0.94f, -0.94f, -2.5f, -0.94f, -3.44f, 0, +R_LINE_TO, -8.61f, 8.55f, +R_CUBIC_TO, -0.95f, 0.94f, -0.95f, 2.48f, 0, 3.42f, +R_LINE_TO, 13.77f, 13.67f, +R_CUBIC_TO, 0.95f, 0.94f, 2.49f, 0.93f, 3.44f, -0.01f, +R_LINE_TO, 8.62f, -8.53f, +R_CUBIC_TO, 0.95f, -0.94f, 0.95f, -2.48f, 0, -3.42f, +CLOSE, +END
diff --git a/ash/resources/vector_icons/system_tray_rotation_lock_locked.1x.icon b/ash/resources/vector_icons/system_tray_rotation_lock_locked.1x.icon index ab4deb6a..f015dfb 100644 --- a/ash/resources/vector_icons/system_tray_rotation_lock_locked.1x.icon +++ b/ash/resources/vector_icons/system_tray_rotation_lock_locked.1x.icon
@@ -3,45 +3,50 @@ // found in the LICENSE file. CANVAS_DIMENSIONS, 16, -MOVE_TO, 11, 4, -R_CUBIC_TO, -0.01f, -0.07f, -0.14f, -1, 0.52f, -1, -R_CUBIC_TO, 0.69f, 0, 0.5f, 0.99f, 0.5f, 0.99f, -R_LINE_TO, 1, -0.01f, -R_LINE_TO, -0.01f, -0.44f, -R_CUBIC_TO, 0.01f, -1.06f, -0.38f, -1.55f, -1.54f, -1.55f, -R_CUBIC_TO, -1.16f, 0, -1.46f, 0.58f, -1.46f, 1.55f, -R_V_LINE_TO, 0.45f, -LINE_TO, 10.7f, 4, -H_LINE_TO, 9.5f, -R_V_LINE_TO, 3, -R_H_LINE_TO, 4, +MOVE_TO, 4.89f, 13.41f, +R_CUBIC_TO, -1.41f, -0.66f, -2.42f, -2.01f, -2.58f, -3.61f, +R_H_LINE_TO, -0.65f, +R_CUBIC_TO, 0.22f, 2.62f, 2.44f, 4.69f, 5.16f, 4.69f, +R_LINE_TO, 0.29f, -0.01f, +R_LINE_TO, -1.65f, -1.63f, +R_LINE_TO, -0.57f, 0.57f, +CLOSE, +MOVE_TO, 14.49f, 8.76f, +R_LINE_TO, -1.41f, -1.39f, +R_LINE_TO, -0.84f, 0.83f, +R_LINE_TO, 1.41f, 1.39f, +R_LINE_TO, -4.18f, 4.12f, +R_LINE_TO, -6.69f, -6.6f, +LINE_TO, 6.96f, 2.99f, +R_LINE_TO, 1.41f, 1.39f, +R_LINE_TO, 0.84f, -0.82f, +LINE_TO, 7.8f, 2.16f, +R_CUBIC_TO, -0.46f, -0.45f, -1.21f, -0.45f, -1.67f, 0, +LINE_TO, 1.95f, 6.29f, +R_CUBIC_TO, -0.46f, 0.45f, -0.46f, 1.2f, 0, 1.65f, +R_LINE_TO, 6.69f, 6.6f, +R_CUBIC_TO, 0.46f, 0.45f, 1.21f, 0.45f, 1.67f, -0.01f, +R_LINE_TO, 4.18f, -4.12f, +R_CUBIC_TO, 0.46f, -0.45f, 0.46f, -1.2f, 0, -1.65f, +CLOSE, +MOVE_TO, 11, 3.77f, +R_CUBIC_TO, 0, -0.43f, 0.43f, -0.77f, 0.97f, -0.77f, +R_CUBIC_TO, 0.54f, 0, 0.97f, 0.35f, 0.97f, 0.77f, V_LINE_TO, 4, -R_H_LINE_TO, -2.5f, +H_LINE_TO, 11, +R_V_LINE_TO, -0.23f, CLOSE, -R_MOVE_TO, 2.77f, 4.59f, -R_LINE_TO, -1.36f, -1.36f, -R_LINE_TO, -0.75f, 0.75f, -R_LINE_TO, 1.17f, 1.17f, -R_LINE_TO, -2.99f, 2.99f, -R_LINE_TO, -5.99f, -5.99f, -LINE_TO, 6.85f, 3.17f, -R_LINE_TO, 1.11f, 1.11f, -R_LINE_TO, 0.75f, -0.74f, -LINE_TO, 7.41f, 2.23f, -R_ARC_TO, 0.79f, 0.79f, 0, 0, 0, -1.12f, 0, -LINE_TO, 2.93f, 5.6f, -R_ARC_TO, 0.79f, 0.79f, 0, 0, 0, 0, 1.12f, -R_LINE_TO, 6.36f, 6.36f, -R_ARC_TO, 0.79f, 0.79f, 0, 0, 0, 1.12f, 0, -R_LINE_TO, 3.36f, -3.36f, -R_ARC_TO, 0.79f, 0.79f, 0, 0, 0, 0, -1.12f, -CLOSE, -MOVE_TO, 5.95f, 12.67f, -R_ARC_TO, 5.55f, 5.55f, 0, 0, 1, -3.16f, -4.48f, -H_LINE_TO, 2, -ARC_TO, 6.34f, 6.34f, 0, 0, 0, 8.32f, 14, -R_LINE_TO, 0.35f, -0.02f, -R_LINE_TO, -2.01f, -2.02f, -R_LINE_TO, -0.7f, 0.7f, +MOVE_TO, 10.57f, 7, +R_H_LINE_TO, 2.86f, +R_CUBIC_TO, 0.31f, 0, 0.57f, -0.25f, 0.57f, -0.56f, +R_V_LINE_TO, -2.22f, +R_CUBIC_TO, 0, -0.31f, -0.26f, -0.56f, -0.57f, -0.56f, +V_LINE_TO, 3.39f, +CUBIC_TO, 13.43f, 2.62f, 12.79f, 2, 12, 2, +R_CUBIC_TO, -0.79f, 0, -1.43f, 0.62f, -1.43f, 1.39f, +R_V_LINE_TO, 0.28f, +R_CUBIC_TO, -0.31f, 0, -0.57f, 0.25f, -0.57f, 0.56f, +R_V_LINE_TO, 2.22f, +R_CUBIC_TO, 0, 0.31f, 0.26f, 0.56f, 0.57f, 0.56f, CLOSE, END
diff --git a/ash/resources/vector_icons/system_tray_rotation_lock_locked.icon b/ash/resources/vector_icons/system_tray_rotation_lock_locked.icon index c3623a3c..a2d2402b 100644 --- a/ash/resources/vector_icons/system_tray_rotation_lock_locked.icon +++ b/ash/resources/vector_icons/system_tray_rotation_lock_locked.icon
@@ -3,50 +3,50 @@ // found in the LICENSE file. CANVAS_DIMENSIONS, 32, -MOVE_TO, 27.53f, 17.18f, -R_LINE_TO, -2.72f, -2.72f, -R_LINE_TO, -1.49f, 1.49f, -R_LINE_TO, 2.35f, 2.35f, -R_LINE_TO, -5.99f, 5.99f, -LINE_TO, 7.71f, 12.32f, -R_LINE_TO, 5.99f, -5.99f, -LINE_TO, 15.92f, 8.55f, -R_LINE_TO, 1.49f, -1.49f, -R_LINE_TO, -2.59f, -2.59f, -R_ARC_TO, 1.58f, 1.58f, 0, 0, 0, -2.24f, 0, -R_LINE_TO, -6.73f, 6.73f, -R_ARC_TO, 1.58f, 1.58f, 0, 0, 0, 0, 2.24f, -R_LINE_TO, 12.71f, 12.71f, -R_ARC_TO, 1.58f, 1.58f, 0, 0, 0, 2.24f, 0, -R_LINE_TO, 6.73f, -6.73f, -R_ARC_TO, 1.58f, 1.58f, 0, 0, 0, 0, -2.24f, +MOVE_TO, 9.77f, 26.82f, +R_CUBIC_TO, -2.82f, -1.31f, -4.85f, -4.02f, -5.16f, -7.22f, +H_LINE_TO, 3.32f, +R_CUBIC_TO, 0.44f, 5.25f, 4.89f, 9.37f, 10.32f, 9.37f, +R_LINE_TO, 0.57f, -0.02f, +R_LINE_TO, -3.29f, -3.25f, +R_LINE_TO, -1.15f, 1.13f, CLOSE, -MOVE_TO, 11.9f, 25.34f, -R_ARC_TO, 11.09f, 11.09f, 0, 0, 1, -6.31f, -8.97f, -H_LINE_TO, 4, -CUBIC_TO, 4.54f, 22.88f, 9.99f, 28, 16.64f, 28, -R_LINE_TO, 0.7f, -0.03f, -R_LINE_TO, -4.03f, -4.04f, -R_LINE_TO, -1.41f, 1.41f, +MOVE_TO, 28.98f, 17.53f, +R_LINE_TO, -2.82f, -2.78f, +R_LINE_TO, -1.67f, 1.65f, +R_LINE_TO, 2.82f, 2.79f, +R_LINE_TO, -8.36f, 8.25f, +R_LINE_TO, -13.38f, -13.2f, +R_LINE_TO, 8.36f, -8.25f, +R_LINE_TO, 2.82f, 2.78f, +R_LINE_TO, 1.67f, -1.65f, +R_LINE_TO, -2.82f, -2.78f, +R_CUBIC_TO, -0.92f, -0.91f, -2.42f, -0.91f, -3.35f, 0, +R_LINE_TO, -8.36f, 8.25f, +R_CUBIC_TO, -0.92f, 0.91f, -0.92f, 2.39f, 0, 3.3f, +R_LINE_TO, 13.38f, 13.2f, +R_CUBIC_TO, 0.92f, 0.91f, 2.42f, 0.9f, 3.34f, -0.01f, +R_LINE_TO, 8.37f, -8.24f, +R_CUBIC_TO, 0.92f, -0.91f, 0.92f, -2.39f, 0, -3.3f, CLOSE, -MOVE_TO, 25.02f, 13, -R_CUBIC_TO, 0.54f, 0, 0.98f, -0.46f, 0.98f, -1, -V_LINE_TO, 8, -R_CUBIC_TO, 0, -0.54f, -0.44f, -1, -0.98f, -1, +MOVE_TO, 21.26f, 6.67f, +R_CUBIC_TO, 0, -1, 0.87f, -1.81f, 1.94f, -1.81f, +R_CUBIC_TO, 1.08f, 0, 1.94f, 0.81f, 1.94f, 1.81f, +V_LINE_TO, 7.2f, +R_H_LINE_TO, -3.89f, +R_V_LINE_TO, -0.53f, +CLOSE, +MOVE_TO, 20.14f, 14, +R_H_LINE_TO, 5.72f, +R_CUBIC_TO, 0.63f, 0, 1.14f, -0.5f, 1.14f, -1.11f, +V_LINE_TO, 8.45f, +R_CUBIC_TO, 0, -0.61f, -0.51f, -1.11f, -1.14f, -1.11f, R_V_LINE_TO, -0.56f, -ARC_TO, 2.45f, 2.45f, 0, 0, 0, 22.56f, 4, -CUBIC_TO, 21.2f, 4, 20, 5.1f, 20, 6.44f, -V_LINE_TO, 7, -R_CUBIC_TO, -0.54f, 0, -1, 0.46f, -1, 1, -R_V_LINE_TO, 4, -R_CUBIC_TO, 0, 0.54f, 0.46f, 1, 1, 1, -R_H_LINE_TO, 5.02f, -CLOSE, -R_MOVE_TO, -2.51f, -8.5f, -R_CUBIC_TO, 0.99f, 0, 1.8f, 0.88f, 1.8f, 1.94f, -V_LINE_TO, 7, -H_LINE_TO, 20.71f, -R_V_LINE_TO, -0.56f, -R_CUBIC_TO, 0, -1.06f, 0.8f, -1.94f, 1.8f, -1.94f, +CUBIC_TO, 25.86f, 5.25f, 24.58f, 4, 23, 4, +R_CUBIC_TO, -1.58f, 0, -2.86f, 1.25f, -2.86f, 2.78f, +R_V_LINE_TO, 0.56f, +R_CUBIC_TO, -0.63f, 0, -1.14f, 0.5f, -1.14f, 1.11f, +R_V_LINE_TO, 4.44f, +CUBIC_TO, 19, 13.5f, 19.52f, 14, 20.14f, 14, CLOSE, END
diff --git a/ash/shelf/shelf_view_unittest.cc b/ash/shelf/shelf_view_unittest.cc index a2e7b1d..357a9ed 100644 --- a/ash/shelf/shelf_view_unittest.cc +++ b/ash/shelf/shelf_view_unittest.cc
@@ -2926,8 +2926,7 @@ EXPECT_EQ(views::InkDropState::HIDDEN, overflow_button_ink_drop_->GetTargetInkDropState()); EXPECT_THAT(overflow_button_ink_drop_->GetAndResetRequestedStates(), - ElementsAre(views::InkDropState::DEACTIVATED, - views::InkDropState::HIDDEN)); + ElementsAre(views::InkDropState::DEACTIVATED)); EXPECT_FALSE(test_api_->IsShowingOverflowBubble()); }
diff --git a/ash/system/network/network_list.cc b/ash/system/network/network_list.cc index 179914c..f882160 100644 --- a/ash/system/network/network_list.cc +++ b/ash/system/network/network_list.cc
@@ -630,7 +630,7 @@ container->SetTooltipText(info.tooltip); views::View* controlled_icon = CreateControlledByExtensionView(info); if (controlled_icon) - container->AddChildView(controlled_icon); + container->AddRightView(controlled_icon); return container; } @@ -661,17 +661,12 @@ if (!extension_info) return nullptr; - // Get the tooltip text. - base::string16 tooltip_text = l10n_util::GetStringFUTF16( - IDS_ASH_STATUS_TRAY_EXTENSION_CONTROLLED_WIFI, - base::UTF8ToUTF16(extension_info->extension_name)); - - views::ImageView* controlled_icon = - new FixedSizedImageView(kTrayPopupDetailsIconWidth, 0); - + views::ImageView* controlled_icon = TrayPopupUtils::CreateMainImageView(); controlled_icon->SetImage( gfx::CreateVectorIcon(kCaptivePortalIcon, kMenuIconColor)); - controlled_icon->SetTooltipText(tooltip_text); + controlled_icon->SetTooltipText(l10n_util::GetStringFUTF16( + IDS_ASH_STATUS_TRAY_EXTENSION_CONTROLLED_WIFI, + base::UTF8ToUTF16(extension_info->extension_name))); return controlled_icon; }
diff --git a/ash/system/rotation/tray_rotation_lock.cc b/ash/system/rotation/tray_rotation_lock.cc index 9692576..1c6a3e6 100644 --- a/ash/system/rotation/tray_rotation_lock.cc +++ b/ash/system/rotation/tray_rotation_lock.cc
@@ -7,6 +7,7 @@ #include "ash/display/screen_orientation_controller_chromeos.h" #include "ash/resources/vector_icons/vector_icons.h" #include "ash/shell.h" +#include "ash/shell_port.h" #include "ash/strings/grit/ash_strings.h" #include "ash/system/tray/actionable_view.h" #include "ash/system/tray/system_tray.h" @@ -18,6 +19,7 @@ #include "ui/accessibility/ax_node_data.h" #include "ui/base/l10n/l10n_util.h" #include "ui/display/display.h" +#include "ui/display/manager/managed_display_info.h" #include "ui/gfx/paint_vector_icon.h" #include "ui/views/controls/image_view.h" #include "ui/views/controls/label.h" @@ -37,6 +39,15 @@ return Shell::Get()->screen_orientation_controller()->user_rotation_locked(); } +bool IsCurrentRotationPortrait() { + display::Display::Rotation current_rotation = + ShellPort::Get() + ->GetDisplayInfo(display::Display::InternalDisplayId()) + .GetActiveRotation(); + return current_rotation == display::Display::ROTATE_90 || + current_rotation == display::Display::ROTATE_270; +} + } // namespace namespace tray { @@ -102,14 +113,21 @@ void RotationLockDefaultView::Update() { TrayPopupItemStyle style(TrayPopupItemStyle::FontStyle::DEFAULT_VIEW_LABEL); - icon_->SetImage(gfx::CreateVectorIcon(IsUserRotationLocked() - ? kSystemMenuRotationLockLockedIcon - : kSystemMenuRotationLockAutoIcon, - kMenuIconSize, style.GetIconColor())); - - base::string16 label = l10n_util::GetStringUTF16( - IsUserRotationLocked() ? IDS_ASH_STATUS_TRAY_ROTATION_LOCK_LOCKED - : IDS_ASH_STATUS_TRAY_ROTATION_LOCK_AUTO); + base::string16 label; + if (IsUserRotationLocked()) { + icon_->SetImage(gfx::CreateVectorIcon( + IsCurrentRotationPortrait() ? kSystemMenuRotationLockPortraitIcon + : kSystemMenuRotationLockLandscapeIcon, + kMenuIconSize, style.GetIconColor())); + label = l10n_util::GetStringUTF16( + IsCurrentRotationPortrait() + ? IDS_ASH_STATUS_TRAY_ROTATION_LOCK_PORTRAIT + : IDS_ASH_STATUS_TRAY_ROTATION_LOCK_LANDSCAPE); + } else { + icon_->SetImage(gfx::CreateVectorIcon(kSystemMenuRotationLockAutoIcon, + kMenuIconSize, style.GetIconColor())); + label = l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_ROTATION_LOCK_AUTO); + } label_->SetText(label); style.SetupLabel(label_); @@ -164,7 +182,7 @@ } void TrayRotationLock::OnUserRotationLockChanged() { - tray_view()->SetVisible(ShouldBeVisible()); + UpdateTrayImage(); } views::View* TrayRotationLock::CreateDefaultView(LoginStatus status) { @@ -174,7 +192,8 @@ } void TrayRotationLock::OnMaximizeModeStarted() { - tray_view()->SetVisible(IsUserRotationLocked()); + tray_view()->SetVisible(ShouldBeVisible()); + UpdateTrayImage(); Shell::Get()->screen_orientation_controller()->AddObserver(this); } @@ -193,9 +212,14 @@ return ShouldBeVisible(); } +void TrayRotationLock::UpdateTrayImage() { + TrayImageItem::SetImageIcon(IsUserRotationLocked() + ? kSystemTrayRotationLockLockedIcon + : kSystemTrayRotationLockAutoIcon); +} + bool TrayRotationLock::ShouldBeVisible() { - return OnPrimaryDisplay() && IsMaximizeModeWindowManagerEnabled() && - IsUserRotationLocked(); + return OnPrimaryDisplay() && IsMaximizeModeWindowManagerEnabled(); } bool TrayRotationLock::OnPrimaryDisplay() const {
diff --git a/ash/system/rotation/tray_rotation_lock.h b/ash/system/rotation/tray_rotation_lock.h index c1b3617f..1dbe7a7 100644 --- a/ash/system/rotation/tray_rotation_lock.h +++ b/ash/system/rotation/tray_rotation_lock.h
@@ -45,8 +45,10 @@ private: friend class TrayRotationLockTest; - // True if |on_primary_display_|, maximize mode is enabled, and rotation is - // locked. + // Update tray image based on whether user rotation lock is enabled. + void UpdateTrayImage(); + + // True if |on_primary_display_|, maximize mode is enabled. bool ShouldBeVisible(); // True if this is owned by a SystemTray on the primary display.
diff --git a/ash/system/rotation/tray_rotation_lock_unittest.cc b/ash/system/rotation/tray_rotation_lock_unittest.cc index 8a25398..4101e250 100644 --- a/ash/system/rotation/tray_rotation_lock_unittest.cc +++ b/ash/system/rotation/tray_rotation_lock_unittest.cc
@@ -115,25 +115,12 @@ } // Tests that when the tray view is created, while MaximizeMode is active, that -// it is not visible. +// it must be visible, and becomes invisible exiting MaximizeMode. TEST_F(TrayRotationLockTest, CreateTrayViewDuringMaximizeMode) { TearDownViews(); Shell::Get()->maximize_mode_controller()->EnableMaximizeModeWindowManager( true); SetUpForStatusAreaWidget(StatusAreaWidgetTestHelper::GetStatusAreaWidget()); - EXPECT_FALSE(tray_view()->visible()); - Shell::Get()->maximize_mode_controller()->EnableMaximizeModeWindowManager( - false); -} - -// Tests that when the tray view is created, while MaximizeMode is active, and -// rotation is locked, that it is visible. -TEST_F(TrayRotationLockTest, CreateTrayViewDuringMaximizeModeAndRotationLock) { - TearDownViews(); - Shell::Get()->maximize_mode_controller()->EnableMaximizeModeWindowManager( - true); - Shell::Get()->screen_orientation_controller()->ToggleUserRotationLock(); - SetUpForStatusAreaWidget(StatusAreaWidgetTestHelper::GetStatusAreaWidget()); EXPECT_TRUE(tray_view()->visible()); Shell::Get()->maximize_mode_controller()->EnableMaximizeModeWindowManager( false); @@ -153,8 +140,8 @@ EXPECT_FALSE(tray_view()->visible()); } -// Tests that the when the tray view is created for a secondary display, that -// it is not visible, and that MaximizeMode does not affect visibility. +// Tests that when the tray view is created for a secondary display, that it is +// not visible, and that MaximizeMode does not affect visibility. TEST_F(TrayRotationLockTest, CreateSecondaryTrayView) { UpdateDisplay("400x400,200x200"); @@ -185,6 +172,7 @@ EXPECT_TRUE(default_view()->visible()); Shell::Get()->maximize_mode_controller()->EnableMaximizeModeWindowManager( false); + EXPECT_FALSE(default_view()->visible()); } // Tests that the enabling of MaximizeMode affects a previously created default @@ -210,7 +198,7 @@ } // Tests that activating the default view causes the display to have its -// rotation locked, and that the tray view becomes visible. +// rotation locked. TEST_F(TrayRotationLockTest, PerformActionOnDefaultView) { MaximizeModeController* maximize_mode_controller = Shell::Get()->maximize_mode_controller(); @@ -218,7 +206,7 @@ Shell::Get()->screen_orientation_controller(); ASSERT_FALSE(screen_orientation_controller->rotation_locked()); maximize_mode_controller->EnableMaximizeModeWindowManager(true); - ASSERT_FALSE(tray_view()->visible()); + ASSERT_TRUE(tray_view()->visible()); ui::GestureEvent tap(0, 0, 0, base::TimeTicks(), ui::GestureEventDetails(ui::ET_GESTURE_TAP));
diff --git a/ash/system/tray/fixed_sized_image_view.h b/ash/system/tray/fixed_sized_image_view.h index 99a0ab2..6960be3 100644 --- a/ash/system/tray/fixed_sized_image_view.h +++ b/ash/system/tray/fixed_sized_image_view.h
@@ -10,9 +10,8 @@ namespace ash { -// An image view with a specified width and height (kTrayPopupDetailsIconWidth). -// If the specified width or height is zero, then the image size is used for -// that dimension. +// An image view with a specified width and height. If the specified width or +// height is zero, then the image size is used for that dimension. class FixedSizedImageView : public views::ImageView { public: FixedSizedImageView(int width, int height);
diff --git a/ash/system/tray/tray_constants.cc b/ash/system/tray/tray_constants.cc index 0ce7b54..8acb371 100644 --- a/ash/system/tray/tray_constants.cc +++ b/ash/system/tray/tray_constants.cc
@@ -58,7 +58,6 @@ const int kTrayPopupLabelRightPadding = 8; -const int kTrayPopupDetailsIconWidth = 25; const SkColor kTrayPopupHoverBackgroundColor = SkColorSetRGB(0xe4, 0xe4, 0xe4); const int kTrayRoundedBorderRadius = 2;
diff --git a/ash/system/tray/tray_constants.h b/ash/system/tray/tray_constants.h index 952dd005..c3c20cb 100644 --- a/ash/system/tray/tray_constants.h +++ b/ash/system/tray/tray_constants.h
@@ -70,7 +70,6 @@ // item. This applies to all labels in the system menu. extern const int kTrayPopupLabelRightPadding; -extern const int kTrayPopupDetailsIconWidth; extern const SkColor kTrayPopupHoverBackgroundColor; extern const int kTrayRoundedBorderRadius;
diff --git a/ash/system/tray/tray_image_item.cc b/ash/system/tray/tray_image_item.cc index ca45de2f..8d4b4e9 100644 --- a/ash/system/tray/tray_image_item.cc +++ b/ash/system/tray/tray_image_item.cc
@@ -34,7 +34,7 @@ CHECK(!tray_view_); tray_view_ = new TrayItemView(this); tray_view_->CreateImageView(); - UpdateImageOnImageView(); + SetImageIcon(icon_); tray_view_->SetVisible(GetInitialVisibility()); return tray_view_; } @@ -45,15 +45,15 @@ void TrayImageItem::SetIconColor(SkColor color) { icon_color_ = color; - UpdateImageOnImageView(); + SetImageIcon(icon_); } -void TrayImageItem::UpdateImageOnImageView() { +void TrayImageItem::SetImageIcon(const gfx::VectorIcon& icon) { if (!tray_view_) return; tray_view_->image_view()->SetImage( - gfx::CreateVectorIcon(icon_, kTrayIconSize, icon_color_)); + gfx::CreateVectorIcon(icon, kTrayIconSize, icon_color_)); } } // namespace ash
diff --git a/ash/system/tray/tray_image_item.h b/ash/system/tray/tray_image_item.h index 64fdb96..ff4402f 100644 --- a/ash/system/tray/tray_image_item.h +++ b/ash/system/tray/tray_image_item.h
@@ -41,10 +41,10 @@ // Sets the color of the icon to |color|. void SetIconColor(SkColor color); - private: - // Sets the current icon on |tray_view_|'s ImageView. - void UpdateImageOnImageView(); + // Sets showing |icon| on |tray_view_|'s ImageView. + void SetImageIcon(const gfx::VectorIcon& icon); + private: // The icon and its current color. const gfx::VectorIcon& icon_; SkColor icon_color_;
diff --git a/base/BUILD.gn b/base/BUILD.gn index 0574ec7..8014a40 100644 --- a/base/BUILD.gn +++ b/base/BUILD.gn
@@ -2596,7 +2596,6 @@ "//third_party/android_support_test_runner:exposed_instrumentation_api_publish_java", "//third_party/android_support_test_runner:runner_java", "//third_party/android_tools:android_support_chromium_java", - "//third_party/android_tools:android_support_v4_java", "//third_party/hamcrest:hamcrest_core_java", "//third_party/junit", ]
diff --git a/base/base_switches.cc b/base/base_switches.cc index e8aa5cb..daa05c2 100644 --- a/base/base_switches.cc +++ b/base/base_switches.cc
@@ -20,6 +20,10 @@ // the memory-infra category is enabled. const char kEnableHeapProfiling[] = "enable-heap-profiling"; +// Report pseudo allocation traces. Pseudo traces are derived from currently +// active trace events. +const char kEnableHeapProfilingModePseudo[] = ""; + // Report native (walk the stack) allocation traces. By default pseudo stacks // derived from trace events are reported. const char kEnableHeapProfilingModeNative[] = "native";
diff --git a/base/base_switches.h b/base/base_switches.h index 04b0773..8ba05a6 100644 --- a/base/base_switches.h +++ b/base/base_switches.h
@@ -15,6 +15,7 @@ extern const char kDisableLowEndDeviceMode[]; extern const char kEnableCrashReporter[]; extern const char kEnableHeapProfiling[]; +extern const char kEnableHeapProfilingModePseudo[]; extern const char kEnableHeapProfilingModeNative[]; extern const char kEnableHeapProfilingTaskProfiler[]; extern const char kEnableLowEndDeviceMode[];
diff --git a/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumRunnerCommon.java b/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumRunnerCommon.java index e5eb2731..fcda9103 100644 --- a/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumRunnerCommon.java +++ b/base/test/android/javatests/src/org/chromium/base/test/BaseChromiumRunnerCommon.java
@@ -9,7 +9,6 @@ import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.support.v4.content.ContextCompat; import org.chromium.android.support.PackageManagerWrapper; import org.chromium.base.Log; @@ -60,8 +59,9 @@ try { ApplicationInfo ai = super.getApplicationInfo(packageName, flags); if (packageName.equals(getPackageName())) { - File dataDir = new File( - ContextCompat.getCodeCacheDir(mAppContext), "test-multidex"); + ApplicationInfo appAi = + super.getApplicationInfo(mAppContext.getPackageName(), flags); + File dataDir = new File(appAi.dataDir, "test-multidex"); if (!dataDir.exists() && !dataDir.mkdirs()) { throw new IOException(String.format( "Unable to create test multidex directory \"%s\"",
diff --git a/base/threading/thread_restrictions.h b/base/threading/thread_restrictions.h index e9f8017..1b51c46 100644 --- a/base/threading/thread_restrictions.h +++ b/base/threading/thread_restrictions.h
@@ -34,11 +34,9 @@ namespace content { class BrowserGpuChannelHostFactory; class BrowserGpuMemoryBufferManager; -class BrowserMainLoop; class BrowserShutdownProfileDumper; class BrowserSurfaceViewManager; class BrowserTestBase; -class CategorizedWorkerPool; class NestedMessagePumpAndroid; class ScopedAllowWaitForAndroidLayoutTests; class ScopedAllowWaitForDebugURL; @@ -47,6 +45,7 @@ class SynchronousCompositorBrowserFilter; class SynchronousCompositorHost; class TextInputClientMac; +class CategorizedWorkerPool; } // namespace content namespace dbus { class Bus; @@ -186,7 +185,6 @@ friend class android_webview::AwFormDatabaseService; friend class android_webview::CookieManager; friend class base::StackSamplingProfiler; - friend class content::BrowserMainLoop; friend class content::BrowserShutdownProfileDumper; friend class content::BrowserSurfaceViewManager; friend class content::BrowserTestBase;
diff --git a/base/trace_event/memory_dump_manager.cc b/base/trace_event/memory_dump_manager.cc index a41a140..2e3f36d 100644 --- a/base/trace_event/memory_dump_manager.cc +++ b/base/trace_event/memory_dump_manager.cc
@@ -187,7 +187,7 @@ std::string profiling_mode = CommandLine::ForCurrentProcess() ->GetSwitchValueASCII(switches::kEnableHeapProfiling); - if (profiling_mode == "") { + if (profiling_mode == switches::kEnableHeapProfilingModePseudo) { AllocationContextTracker::SetCaptureMode( AllocationContextTracker::CaptureMode::PSEUDO_STACK); #if !defined(OS_NACL)
diff --git a/cc/surfaces/BUILD.gn b/cc/surfaces/BUILD.gn index 118e68e..0ea753ab 100644 --- a/cc/surfaces/BUILD.gn +++ b/cc/surfaces/BUILD.gn
@@ -45,9 +45,9 @@ "display_client.h", "display_scheduler.cc", "display_scheduler.h", - "framesink_manager.cc", - "framesink_manager.h", - "framesink_manager_client.h", + "frame_sink_manager.cc", + "frame_sink_manager.h", + "frame_sink_manager_client.h", "local_surface_id_allocator.cc", "local_surface_id_allocator.h", "pending_frame_observer.h",
diff --git a/cc/surfaces/compositor_frame_sink_support.h b/cc/surfaces/compositor_frame_sink_support.h index 33e44f9..247bc1e7 100644 --- a/cc/surfaces/compositor_frame_sink_support.h +++ b/cc/surfaces/compositor_frame_sink_support.h
@@ -13,7 +13,7 @@ #include "base/memory/weak_ptr.h" #include "cc/output/compositor_frame.h" #include "cc/scheduler/begin_frame_source.h" -#include "cc/surfaces/framesink_manager_client.h" +#include "cc/surfaces/frame_sink_manager_client.h" #include "cc/surfaces/referenced_surface_tracker.h" #include "cc/surfaces/surface_factory.h" #include "cc/surfaces/surface_factory_client.h"
diff --git a/cc/surfaces/framesink_manager.cc b/cc/surfaces/frame_sink_manager.cc similarity index 98% rename from cc/surfaces/framesink_manager.cc rename to cc/surfaces/frame_sink_manager.cc index 8095f62..7e9095c 100644 --- a/cc/surfaces/framesink_manager.cc +++ b/cc/surfaces/frame_sink_manager.cc
@@ -2,13 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "cc/surfaces/framesink_manager.h" +#include "cc/surfaces/frame_sink_manager.h" #include <stddef.h> #include <stdint.h> #include "base/logging.h" -#include "cc/surfaces/framesink_manager_client.h" +#include "cc/surfaces/frame_sink_manager_client.h" #include "cc/surfaces/surface_factory_client.h" #if DCHECK_IS_ON() @@ -23,8 +23,7 @@ FrameSinkManager::FrameSinkSourceMapping::FrameSinkSourceMapping( const FrameSinkSourceMapping& other) = default; -FrameSinkManager::FrameSinkSourceMapping::~FrameSinkSourceMapping() { -} +FrameSinkManager::FrameSinkSourceMapping::~FrameSinkSourceMapping() {} FrameSinkManager::FrameSinkManager() {}
diff --git a/cc/surfaces/framesink_manager.h b/cc/surfaces/frame_sink_manager.h similarity index 96% rename from cc/surfaces/framesink_manager.h rename to cc/surfaces/frame_sink_manager.h index bcecb8a..b48642f2 100644 --- a/cc/surfaces/framesink_manager.h +++ b/cc/surfaces/frame_sink_manager.h
@@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CC_SURFACES_FRAMESINK_MANAGER_H_ -#define CC_SURFACES_FRAMESINK_MANAGER_H_ +#ifndef CC_SURFACES_FRAME_SINK_MANAGER_H_ +#define CC_SURFACES_FRAME_SINK_MANAGER_H_ #include <stdint.h> @@ -68,7 +68,7 @@ // Export list of valid frame_sink_ids for SatisfyDestructionDeps in surface // may be removed later when References replace Sequences - std::unordered_set<FrameSinkId, FrameSinkIdHash>* GetValidFrameSinkIds(){ + std::unordered_set<FrameSinkId, FrameSinkIdHash>* GetValidFrameSinkIds() { return &valid_frame_sink_ids_; } @@ -119,4 +119,4 @@ } // namespace cc -#endif // CC_SURFACES_FRAMESINK_MANAGER_H_ +#endif // CC_SURFACES_FRAME_SINK_MANAGER_H_
diff --git a/cc/surfaces/framesink_manager_client.h b/cc/surfaces/frame_sink_manager_client.h similarity index 78% rename from cc/surfaces/framesink_manager_client.h rename to cc/surfaces/frame_sink_manager_client.h index 18edf9bb..83784b9 100644 --- a/cc/surfaces/framesink_manager_client.h +++ b/cc/surfaces/frame_sink_manager_client.h
@@ -1,8 +1,8 @@ // 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 CC_SURFACES_FRAMESINK_MANAGER_CLIENT_H_ -#define CC_SURFACES_FRAMESINK_MANAGER_CLIENT_H_ +#ifndef CC_SURFACES_FRAME_SINK_MANAGER_CLIENT_H_ +#define CC_SURFACES_FRAME_SINK_MANAGER_CLIENT_H_ #include "cc/scheduler/begin_frame_source.h" #include "cc/surfaces/surfaces_export.h" @@ -19,4 +19,4 @@ } // namespace cc -#endif // CC_SURFACES_FRAMESINK_MANAGER_CLIENT_H_ +#endif // CC_SURFACES_FRAME_SINK_MANAGER_CLIENT_H_
diff --git a/cc/surfaces/surface_factory_unittest.cc b/cc/surfaces/surface_factory_unittest.cc index d942f7c..70f1051c 100644 --- a/cc/surfaces/surface_factory_unittest.cc +++ b/cc/surfaces/surface_factory_unittest.cc
@@ -16,7 +16,7 @@ #include "cc/output/copy_output_request.h" #include "cc/output/copy_output_result.h" #include "cc/resources/resource_provider.h" -#include "cc/surfaces/framesink_manager_client.h" +#include "cc/surfaces/frame_sink_manager_client.h" #include "cc/surfaces/surface.h" #include "cc/surfaces/surface_factory_client.h" #include "cc/surfaces/surface_info.h"
diff --git a/cc/surfaces/surface_manager.h b/cc/surfaces/surface_manager.h index 53708504..4eaf993e 100644 --- a/cc/surfaces/surface_manager.h +++ b/cc/surfaces/surface_manager.h
@@ -19,7 +19,7 @@ #include "base/observer_list.h" #include "base/threading/thread_checker.h" #include "cc/surfaces/frame_sink_id.h" -#include "cc/surfaces/framesink_manager.h" +#include "cc/surfaces/frame_sink_manager.h" #include "cc/surfaces/surface_dependency_tracker.h" #include "cc/surfaces/surface_id.h" #include "cc/surfaces/surface_observer.h"
diff --git a/cc/surfaces/surface_manager_unittest.cc b/cc/surfaces/surface_manager_unittest.cc index 6b59a05c..53fe6ed9 100644 --- a/cc/surfaces/surface_manager_unittest.cc +++ b/cc/surfaces/surface_manager_unittest.cc
@@ -5,7 +5,7 @@ #include <stddef.h> #include "cc/scheduler/begin_frame_source.h" -#include "cc/surfaces/framesink_manager_client.h" +#include "cc/surfaces/frame_sink_manager_client.h" #include "cc/surfaces/surface_factory_client.h" #include "cc/surfaces/surface_manager.h" #include "cc/surfaces/surface_resource_holder_client.h"
diff --git a/chrome/android/java/res/layout/promo_dialog_layout.xml b/chrome/android/java/res/layout/promo_dialog_layout.xml index 6081a823..00793b6 100644 --- a/chrome/android/java/res/layout/promo_dialog_layout.xml +++ b/chrome/android/java/res/layout/promo_dialog_layout.xml
@@ -23,7 +23,7 @@ <org.chromium.chrome.browser.widget.FadingEdgeScrollView android:id="@+id/promo_container" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" android:fadeScrollbars="false" > @@ -81,12 +81,12 @@ android:layout="@layout/infobar_control_description" android:inflatedId="@+id/footer" android:layout_margin="@dimen/promo_dialog_padding" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" /> <org.chromium.chrome.browser.widget.DualControlLayout android:id="@+id/button_bar" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="end" android:padding="@dimen/promo_dialog_padding"
diff --git a/chrome/android/java/res/layout/infobar_control_radio.xml b/chrome/android/java/res/layout/radio_button_layout_element.xml similarity index 100% rename from chrome/android/java/res/layout/infobar_control_radio.xml rename to chrome/android/java/res/layout/radio_button_layout_element.xml
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java index 91cc66ed..332cda5 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -878,7 +878,9 @@ // it will trigger the notification that tab restore is complete which is needed by // other parts of Chrome such as sync. boolean activeTabBeingRestored = !mIntentWithEffect; + mMainIntentMetrics.setIgnoreEvents(true); mTabModelSelectorImpl.restoreTabs(activeTabBeingRestored); + mMainIntentMetrics.setIgnoreEvents(false); // Only create an initial tab if no tabs were restored and no intent was handled. // Also, check whether the active tab was supposed to be restored and that the total @@ -893,8 +895,9 @@ new Runnable() { @Override public void run() { - mMainIntentMetrics.ignorePendingAddTab(); + mMainIntentMetrics.setIgnoreEvents(true); createInitialTab(); + mMainIntentMetrics.setIgnoreEvents(false); } }, INITIAL_TAB_CREATION_TIMEOUT_MS); }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuListAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuListAdapter.java index ef2f404..9b6a994 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuListAdapter.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuListAdapter.java
@@ -26,7 +26,7 @@ class TabularContextMenuListAdapter extends BaseAdapter { private final List<ContextMenuItem> mMenuItems; private final Activity mActivity; - private final Runnable mOnShareItemClicked; + private final Runnable mOnDirectShare; /** * Adapter for the tabular context menu UI @@ -34,10 +34,10 @@ * @param activity Used to inflate the layout. */ TabularContextMenuListAdapter( - List<ContextMenuItem> menuItems, Activity activity, Runnable onShareItemClicked) { + List<ContextMenuItem> menuItems, Activity activity, Runnable onDirectShare) { mMenuItems = menuItems; mActivity = activity; - mOnShareItemClicked = onShareItemClicked; + mOnDirectShare = onDirectShare; } @Override @@ -91,7 +91,7 @@ viewHolder.mShareIcon.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - mOnShareItemClicked.run(); + mOnDirectShare.run(); } }); }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuUi.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuUi.java index 64096dc..66695f0a 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuUi.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuUi.java
@@ -160,8 +160,15 @@ } // Set the list adapter and get the height to display it appropriately in a dialog. + Runnable onDirectShare = new Runnable() { + @Override + public void run() { + mOnShareItemClicked.run(); + mDialog.dismiss(); + } + }; TabularContextMenuListAdapter listAdapter = - new TabularContextMenuListAdapter(items, activity, mOnShareItemClicked); + new TabularContextMenuListAdapter(items, activity, onDirectShare); ViewGroup.LayoutParams layoutParams = listView.getLayoutParams(); layoutParams.height = measureApproximateListViewHeight(listView, listAdapter, maxCount); listView.setLayoutParams(layoutParams);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarControlLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarControlLayout.java index 50e4eca..6a5b3f6 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarControlLayout.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/InfoBarControlLayout.java
@@ -10,14 +10,13 @@ import android.support.annotation.Nullable; import android.support.v7.widget.SwitchCompat; import android.text.method.LinkMovementMethod; +import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.RadioButton; -import android.widget.RadioGroup; import android.widget.RatingBar; import android.widget.Spinner; import android.widget.TextView; @@ -26,6 +25,7 @@ import org.chromium.base.VisibleForTesting; import org.chromium.chrome.R; import org.chromium.chrome.browser.widget.DualControlLayout; +import org.chromium.chrome.browser.widget.RadioButtonLayout; import java.util.List; @@ -45,12 +45,8 @@ * TODO(dfalcantara): The line spacing multiplier is applied to all lines in JB & KK, even if the * TextView has only one line. This throws off vertical alignment. Find a * solution that hopefully doesn't involve subclassing the TextView. - * - * TODO(dfalcantara): Move this to a more general location. */ public final class InfoBarControlLayout extends ViewGroup { - public static final int INVALID_INDEX = -1; - /** * ArrayAdapter that automatically determines what size make its Views to accommodate all of * its potential values. @@ -163,7 +159,11 @@ * Do not call this method directly; use {@link InfoBarLayout#addControlLayout()}. */ public InfoBarControlLayout(Context context) { - super(context); + this(context, null); + } + + public InfoBarControlLayout(Context context, AttributeSet attrs) { + super(context, attrs); Resources resources = context.getResources(); mMarginBetweenRows = @@ -361,40 +361,17 @@ /** * Creates a set of standard radio buttons and adds it to the layout. * - * ------------------------------------------------- - * | O | MESSAGE #1 | - * | O | MESSAGE #N | - * ------------------------------------------------- - * * @param messages Messages to display for the options. * @param tags Optional list of tags to attach to the buttons. - * @param selectedIndex Which index to mark as being selected. */ - public RadioGroup addRadioButtons( - List<CharSequence> messages, @Nullable List<?> tags, int selectedIndex) { - if (tags != null) assert tags.size() == messages.size(); - + public RadioButtonLayout addRadioButtons(List<CharSequence> messages, @Nullable List<?> tags) { ControlLayoutParams params = new ControlLayoutParams(); params.mMustBeFullWidth = true; - RadioGroup radioLayout = new RadioGroup(getContext()); + RadioButtonLayout radioLayout = new RadioButtonLayout(getContext()); + radioLayout.addOptions(messages, tags); + addView(radioLayout, params); - - for (int i = 0; i < messages.size(); i++) { - RadioButton button = - (RadioButton) LayoutInflater.from(getContext()) - .inflate(R.layout.infobar_control_radio, radioLayout, false); - button.setText(messages.get(i)); - if (tags != null) button.setTag(tags.get(i)); - button.setChecked(i == selectedIndex); - radioLayout.addView(button); - - // Add margins between each of the radio buttons. - if (i < messages.size() - 1) { - ((MarginLayoutParams) button.getLayoutParams()).bottomMargin = mMarginBetweenRows; - } - } - return radioLayout; }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/locale/DefaultSearchEnginePromoDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/locale/DefaultSearchEnginePromoDialog.java index 4720f22b..57ca132 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/locale/DefaultSearchEnginePromoDialog.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/locale/DefaultSearchEnginePromoDialog.java
@@ -9,17 +9,18 @@ import android.os.Bundle; import android.support.annotation.Nullable; import android.view.View; +import android.widget.Button; import android.widget.RadioGroup; import android.widget.RadioGroup.OnCheckedChangeListener; import org.chromium.base.Callback; import org.chromium.base.library_loader.LibraryLoader; import org.chromium.chrome.R; -import org.chromium.chrome.browser.infobar.InfoBarControlLayout; import org.chromium.chrome.browser.locale.LocaleManager.SearchEnginePromoType; import org.chromium.chrome.browser.search_engines.TemplateUrlService; import org.chromium.chrome.browser.search_engines.TemplateUrlService.TemplateUrl; import org.chromium.chrome.browser.widget.PromoDialog; +import org.chromium.chrome.browser.widget.RadioButtonLayout; import java.util.ArrayList; import java.util.Collections; @@ -108,10 +109,13 @@ } // Add the search engines to the dialog. - InfoBarControlLayout controls = addControlLayout(); - mRadioGroup = controls.addRadioButtons( - engineNames, engineKeywords, InfoBarControlLayout.INVALID_INDEX); - mRadioGroup.setOnCheckedChangeListener(this); + RadioButtonLayout radioButtons = new RadioButtonLayout(getContext()); + radioButtons.addOptions(engineNames, engineKeywords); + radioButtons.setOnCheckedChangeListener(this); + addControl(radioButtons); + + Button button = (Button) findViewById(R.id.button_primary); + button.setOnClickListener(this); updateButtonState(); }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/locale/LocaleManager.java b/chrome/android/java/src/org/chromium/chrome/browser/locale/LocaleManager.java index f16cb9e..a3f3eaa 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/locale/LocaleManager.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/locale/LocaleManager.java
@@ -35,6 +35,15 @@ public static final String PREF_WAS_IN_SPECIAL_LOCALE = "LocaleManager_WAS_IN_SPECIAL_LOCALE"; public static final String SPECIAL_LOCALE_ID = "US"; + /** The current state regarding search engine promo dialogs. */ + @IntDef({SEARCH_ENGINE_PROMO_SHOULD_CHECK, SEARCH_ENGINE_PROMO_CHECKED_NOT_SHOWN, + SEARCH_ENGINE_PROMO_CHECKED_AND_SHOWN}) + @Retention(RetentionPolicy.SOURCE) + public @interface SearchEnginePromoState {} + public static final int SEARCH_ENGINE_PROMO_SHOULD_CHECK = -1; + public static final int SEARCH_ENGINE_PROMO_CHECKED_NOT_SHOWN = 0; + public static final int SEARCH_ENGINE_PROMO_CHECKED_AND_SHOWN = 1; + /** The different types of search engine promo dialogs. */ @IntDef({SEARCH_ENGINE_PROMO_DONT_SHOW, SEARCH_ENGINE_PROMO_SHOW_SOGOU, SEARCH_ENGINE_PROMO_SHOW_EXISTING, SEARCH_ENGINE_PROMO_SHOW_NEW}) @@ -46,6 +55,9 @@ public static final int SEARCH_ENGINE_PROMO_SHOW_EXISTING = 1; public static final int SEARCH_ENGINE_PROMO_SHOW_NEW = 2; + protected static final String KEY_SEARCH_ENGINE_PROMO_SHOW_STATE = + "com.android.chrome.SEARCH_ENGINE_PROMO_SHOWN"; + private static final int SNACKBAR_DURATION_MS = 6000; private static LocaleManager sInstance;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/metrics/MainIntentBehaviorMetrics.java b/chrome/android/java/src/org/chromium/chrome/browser/metrics/MainIntentBehaviorMetrics.java index 8feb225..2fa41d1 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/metrics/MainIntentBehaviorMetrics.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/metrics/MainIntentBehaviorMetrics.java
@@ -10,6 +10,7 @@ import org.chromium.base.ActivityState; import org.chromium.base.ApplicationStatus; +import org.chromium.base.annotations.SuppressFBWarnings; import org.chromium.base.metrics.RecordHistogram; import org.chromium.base.metrics.RecordUserAction; import org.chromium.chrome.browser.ChromeActivity; @@ -34,28 +35,26 @@ private static final long BACKGROUND_TIME_6_HOUR_MS = 21600000; private static final long BACKGROUND_TIME_1_HOUR_MS = 3600000; - private static final long TIMEOUT_DURATION_MS = 10000; + static final long TIMEOUT_DURATION_MS = 10000; @Retention(RetentionPolicy.SOURCE) - @IntDef({ - CONTINUATION, - FOCUS_OMNIBOX, - SWITCH_TABS, - NTP_CREATED, - BACKGROUNDED - }) - private @interface MainIntentActionType {} - private static final int CONTINUATION = 0; - private static final int FOCUS_OMNIBOX = 1; - private static final int SWITCH_TABS = 2; - private static final int NTP_CREATED = 3; - private static final int BACKGROUNDED = 4; + @IntDef({CONTINUATION, FOCUS_OMNIBOX, SWITCH_TABS, NTP_CREATED, BACKGROUNDED}) + @interface MainIntentActionType {} + static final int CONTINUATION = 0; + static final int FOCUS_OMNIBOX = 1; + static final int SWITCH_TABS = 2; + static final int NTP_CREATED = 3; + static final int BACKGROUNDED = 4; // Min and max values (in minutes) for the buckets in the duration histograms. private static final int DURATION_HISTOGRAM_MIN = 5; private static final int DURATION_HISTOGRAM_MAX = 48 * 60; private static final int DURATION_HISTOGRAM_BUCKET_COUNT = 50; + @MainIntentActionType + private static Integer sLastMainIntentBehavior; + private static long sTimeoutDurationMs = TIMEOUT_DURATION_MS; + private final ChromeActivity mActivity; private final Handler mHandler; private final Runnable mTimeoutRunnable; @@ -63,7 +62,7 @@ private boolean mPendingActionRecordForMainIntent; private long mBackgroundDurationMs; private TabModelSelectorTabModelObserver mTabModelObserver; - private boolean mIgnorePendingAddTab; + private boolean mIgnoreEvents; /** * Constructs a metrics handler for ACTION_MAIN intents received for the specified activity. @@ -84,7 +83,10 @@ * * This must only be called after the native libraries have been initialized. */ + @SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD") public void onMainIntentWithNative(long backgroundDurationMs) { + sLastMainIntentBehavior = null; + RecordUserAction.record("MobileStartup.MainIntentReceived"); if (backgroundDurationMs >= BACKGROUND_TIME_24_HOUR_MS) { @@ -103,16 +105,12 @@ ApplicationStatus.registerStateListenerForActivity(this, mActivity); mPendingActionRecordForMainIntent = true; - mHandler.postDelayed(mTimeoutRunnable , TIMEOUT_DURATION_MS); + mHandler.postDelayed(mTimeoutRunnable, sTimeoutDurationMs); mTabModelObserver = new TabModelSelectorTabModelObserver( mActivity.getTabModelSelector()) { @Override public void didAddTab(Tab tab, TabLaunchType type) { - if (mIgnorePendingAddTab) { - mIgnorePendingAddTab = false; - return; - } if (TabLaunchType.FROM_RESTORE.equals(type)) return; if (NewTabPage.isNTPUrl(tab.getUrl())) recordUserBehavior(NTP_CREATED); } @@ -125,11 +123,12 @@ } /** - * Signal that the next tab addition should be ignored as a signal of MAIN intent behavior. + * Signal that any events should be ignored as a signal of MAIN intent behavior. + * + * @param shouldIgnore Whether events should be ignored. */ - public void ignorePendingAddTab() { - if (!mPendingActionRecordForMainIntent) return; - mIgnorePendingAddTab = true; + public void setIgnoreEvents(boolean shouldIgnore) { + mIgnoreEvents = shouldIgnore; } /** @@ -146,6 +145,22 @@ } } + /** + * @return The last main intent behavior recorded, which can be null if no MAIN intent has been + * received or if the event has not yet occurred. + */ + @MainIntentActionType + public static Integer getLastMainIntentBehaviorForTesting() { + return sLastMainIntentBehavior; + } + + /** + * Allows test to override the timeout duration. + */ + public static void setTimeoutDurationMsForTesting(long duration) { + sTimeoutDurationMs = duration; + } + private String getHistogramNameForBehavior(@MainIntentActionType int behavior) { switch (behavior) { case CONTINUATION: @@ -164,9 +179,10 @@ } private void recordUserBehavior(@MainIntentActionType int behavior) { - if (!mPendingActionRecordForMainIntent) return; + if (!mPendingActionRecordForMainIntent || mIgnoreEvents) return; mPendingActionRecordForMainIntent = false; + sLastMainIntentBehavior = behavior; String histogramName = getHistogramNameForBehavior(behavior); if (histogramName != null) { RecordHistogram.recordCustomCountHistogram(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/SuggestionView.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/SuggestionView.java index 531a761..bbf2c77 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/SuggestionView.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/SuggestionView.java
@@ -35,6 +35,7 @@ import org.chromium.chrome.browser.omnibox.OmniboxResultsAdapter.OmniboxResultItem; import org.chromium.chrome.browser.omnibox.OmniboxResultsAdapter.OmniboxSuggestionDelegate; import org.chromium.chrome.browser.omnibox.OmniboxSuggestion.MatchClassification; +import org.chromium.chrome.browser.util.ViewUtils; import org.chromium.chrome.browser.widget.TintedDrawable; import org.chromium.ui.base.DeviceFormFactor; @@ -862,7 +863,8 @@ private int getUrlBarLeftOffset() { if (mLocationBar.mustQueryUrlBarLocationForSuggestions()) { - mUrlBar.getLocationInWindow(mViewPositionHolder); + View contentView = getRootView().findViewById(android.R.id.content); + ViewUtils.getRelativeLayoutPosition(contentView, mUrlBar, mViewPositionHolder); return mViewPositionHolder[0]; } else { return ApiCompatibilityUtils.isLayoutRtl(this) ? mPhoneUrlBarLeftOffsetRtlPx @@ -877,7 +879,8 @@ if (mLocationBar == null) return 0; int leftOffset = getUrlBarLeftOffset(); - getLocationInWindow(mViewPositionHolder); + View contentView = getRootView().findViewById(android.R.id.content); + ViewUtils.getRelativeLayoutPosition(contentView, this, mViewPositionHolder); return leftOffset + mUrlBar.getPaddingLeft() - mViewPositionHolder[0]; } @@ -888,7 +891,8 @@ if (mLocationBar == null) return 0; int leftOffset = getUrlBarLeftOffset(); - getLocationInWindow(mViewPositionHolder); + View contentView = getRootView().findViewById(android.R.id.content); + ViewUtils.getRelativeLayoutPosition(contentView, this, mViewPositionHolder); return leftOffset + mUrlBar.getWidth() - mUrlBar.getPaddingRight() - mViewPositionHolder[0]; }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/PromoDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/PromoDialog.java index 1458c6a..2ece772 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/widget/PromoDialog.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/PromoDialog.java
@@ -15,7 +15,6 @@ import org.chromium.base.ApiCompatibilityUtils; import org.chromium.chrome.R; -import org.chromium.chrome.browser.infobar.InfoBarControlLayout; /** * Generic builder for promo dialogs. @@ -60,9 +59,12 @@ mDialogLayout.initialize(getDialogParams()); } - /** Add a standardized set of dialog controls. */ - protected InfoBarControlLayout addControlLayout() { - return mDialogLayout.addControlLayout(); + /** + * Adds a View to the layout within the scrollable area. + * See {@link PromoDialogLayout#addControl}. + */ + protected void addControl(View control) { + mDialogLayout.addControl(control); } @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/PromoDialogLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/PromoDialogLayout.java index 970507f..511566f0 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/widget/PromoDialogLayout.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/PromoDialogLayout.java
@@ -6,6 +6,7 @@ import android.content.Context; import android.util.AttributeSet; +import android.view.View; import android.view.ViewGroup; import android.view.ViewStub; import android.widget.ImageView; @@ -14,7 +15,6 @@ import org.chromium.base.ApiCompatibilityUtils; import org.chromium.chrome.R; -import org.chromium.chrome.browser.infobar.InfoBarControlLayout; import org.chromium.chrome.browser.widget.PromoDialog.DialogParams; /** @@ -145,11 +145,9 @@ super.onMeasure(widthMeasureSpec, heightMeasureSpec); } - /** Adds a standardized set of controls to the layout. */ - InfoBarControlLayout addControlLayout() { - InfoBarControlLayout layout = new InfoBarControlLayout(getContext()); + /** Adds a View to the layout within the scrollable area. */ + void addControl(View control) { mScrollableContent.addView( - layout, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); - return layout; + control, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); } }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/RadioButtonLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/RadioButtonLayout.java new file mode 100644 index 0000000..ea1d693 --- /dev/null +++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/RadioButtonLayout.java
@@ -0,0 +1,90 @@ +// 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. + +package org.chromium.chrome.browser.widget; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.RadioButton; +import android.widget.RadioGroup; + +import org.chromium.chrome.R; + +import java.util.List; + +/** + * Manages a group of exclusive RadioButtons, automatically inserting a margin in between the rows + * to prevent them from squishing together. + * + * ------------------------------------------------- + * | O | MESSAGE #1 | + * | O | MESSAGE #N | + * ------------------------------------------------- + */ +public final class RadioButtonLayout extends RadioGroup { + public static final int INVALID_INDEX = -1; + + private final int mMarginBetweenRows; + + public RadioButtonLayout(Context context) { + this(context, null); + } + + public RadioButtonLayout(Context context, AttributeSet attrs) { + super(context, attrs); + mMarginBetweenRows = context.getResources().getDimensionPixelSize( + R.dimen.infobar_control_margin_between_rows); + } + + /** + * Adds a set of standard radio buttons for the given messages and adds them to the layout. + * + * @param messages Messages to display for the options. + * @param tags Optional list of tags to attach to the buttons. + */ + public void addOptions(List<CharSequence> messages, @Nullable List<?> tags) { + if (tags != null) assert tags.size() == messages.size(); + + for (int i = 0; i < messages.size(); i++) { + RadioButton button = (RadioButton) LayoutInflater.from(getContext()) + .inflate(R.layout.radio_button_layout_element, null); + button.setText(messages.get(i)); + if (tags != null) button.setTag(tags.get(i)); + + addView(button, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); + } + + updateMargins(); + } + + /** + * Marks a RadioButton child as being checked. + * + * Android doesn't provide a way of generating View IDs on the fly before API level 17, so this + * function requires passing in the child's index. Passing in {@link #INVALID_INDEX} marks them + * all as de-selected. + * + * @param childIndex Index of the child to select. + */ + public void selectChildAtIndex(int childIndex) { + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + RadioButton child = (RadioButton) getChildAt(i); + child.setChecked(i == childIndex); + } + } + + /** Sets margins between each of the radio buttons. */ + private void updateMargins() { + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + int margin = (i < childCount - 1) ? mMarginBetweenRows : 0; + ((MarginLayoutParams) child.getLayoutParams()).bottomMargin = margin; + } + } +}
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni index e5124e3..7f1fc04 100644 --- a/chrome/android/java_sources.gni +++ b/chrome/android/java_sources.gni
@@ -1206,6 +1206,7 @@ "java/src/org/chromium/chrome/browser/widget/PromoDialogLayout.java", "java/src/org/chromium/chrome/browser/widget/PulseDrawable.java", "java/src/org/chromium/chrome/browser/widget/PulseInterpolator.java", + "java/src/org/chromium/chrome/browser/widget/RadioButtonLayout.java", "java/src/org/chromium/chrome/browser/widget/RadioButtonWithDescription.java", "java/src/org/chromium/chrome/browser/widget/RoundedIconGenerator.java", "java/src/org/chromium/chrome/browser/widget/TintedDrawable.java", @@ -1428,6 +1429,7 @@ "javatests/src/org/chromium/chrome/browser/media/router/MockMediaRouteProvider.java", "javatests/src/org/chromium/chrome/browser/media/ui/AutoplayMutedNotificationTest.java", "javatests/src/org/chromium/chrome/browser/media/ui/PauseOnHeadsetUnplugTest.java", + "javatests/src/org/chromium/chrome/browser/metrics/MainIntentBehaviorMetricsIntegrationTest.java", "javatests/src/org/chromium/chrome/browser/metrics/PageLoadMetricsTest.java", "javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowIntegrationTest.java", "javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtilsTest.java", @@ -1624,6 +1626,7 @@ "javatests/src/org/chromium/chrome/browser/widget/DualControlLayoutTest.java", "javatests/src/org/chromium/chrome/browser/widget/OverviewListLayoutTest.java", "javatests/src/org/chromium/chrome/browser/widget/PromoDialogTest.java", + "javatests/src/org/chromium/chrome/browser/widget/RadioButtonLayoutTest.java", "javatests/src/org/chromium/chrome/browser/widget/RoundedIconGeneratorTest.java", "javatests/src/org/chromium/chrome/browser/widget/ToolbarProgressBarTest.java", "javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetObserverTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/MainIntentBehaviorMetricsIntegrationTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/MainIntentBehaviorMetricsIntegrationTest.java new file mode 100644 index 0000000..0468f43 --- /dev/null +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/metrics/MainIntentBehaviorMetricsIntegrationTest.java
@@ -0,0 +1,133 @@ +// 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. + +package org.chromium.chrome.browser.metrics; + +import android.support.test.filters.MediumTest; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.ThreadUtils; +import org.chromium.base.test.util.CommandLineFlags; +import org.chromium.chrome.R; +import org.chromium.chrome.browser.ChromeSwitches; +import org.chromium.chrome.browser.ChromeTabbedActivity; +import org.chromium.chrome.browser.omnibox.UrlBar; +import org.chromium.chrome.browser.tabmodel.TabModel.TabLaunchType; +import org.chromium.chrome.browser.tabmodel.TabModelUtils; +import org.chromium.chrome.test.ChromeActivityTestRule; +import org.chromium.chrome.test.ChromeJUnit4ClassRunner; +import org.chromium.chrome.test.util.OmniboxTestUtils; +import org.chromium.content.browser.test.util.Criteria; +import org.chromium.content.browser.test.util.CriteriaHelper; +import org.chromium.content_public.browser.LoadUrlParams; +import org.chromium.content_public.common.ContentUrlConstants; + +import java.util.concurrent.Callable; + +@RunWith(ChromeJUnit4ClassRunner.class) +@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, + ChromeActivityTestRule.DISABLE_NETWORK_PREDICTION_FLAG}) +public class MainIntentBehaviorMetricsIntegrationTest { + @Rule + public ChromeActivityTestRule<ChromeTabbedActivity> mActivityTestRule = + new ChromeActivityTestRule<>(ChromeTabbedActivity.class); + + @MediumTest + @Test + public void testFocusOmnibox() throws InterruptedException { + mActivityTestRule.startMainActivityFromLauncher(); + assertMainIntentBehavior(null); + ThreadUtils.runOnUiThreadBlocking(new Runnable() { + @Override + public void run() { + UrlBar urlBar = (UrlBar) mActivityTestRule.getActivity().findViewById(R.id.url_bar); + OmniboxTestUtils.toggleUrlBarFocus(urlBar, true); + } + }); + assertMainIntentBehavior(MainIntentBehaviorMetrics.FOCUS_OMNIBOX); + } + + @MediumTest + @Test + public void testSwitchTabs() throws InterruptedException { + mActivityTestRule.startMainActivityFromLauncher(); + assertMainIntentBehavior(null); + ThreadUtils.runOnUiThreadBlocking(new Runnable() { + @Override + public void run() { + mActivityTestRule.getActivity().getTabCreator(false).createNewTab( + new LoadUrlParams(ContentUrlConstants.ABOUT_BLANK_URL), + TabLaunchType.FROM_RESTORE, null); + } + }); + CriteriaHelper.pollUiThread(Criteria.equals(2, new Callable<Integer>() { + @Override + public Integer call() throws Exception { + return mActivityTestRule.getActivity().getTabModelSelector().getTotalTabCount(); + } + })); + assertMainIntentBehavior(null); + + ThreadUtils.runOnUiThreadBlocking(new Runnable() { + @Override + public void run() { + TabModelUtils.setIndex(mActivityTestRule.getActivity().getCurrentTabModel(), 1); + } + }); + assertMainIntentBehavior(MainIntentBehaviorMetrics.SWITCH_TABS); + } + + @MediumTest + @Test + public void testBackgrounded() throws InterruptedException { + mActivityTestRule.startMainActivityFromLauncher(); + assertMainIntentBehavior(null); + ThreadUtils.runOnUiThreadBlocking(new Runnable() { + @Override + public void run() { + mActivityTestRule.getActivity().finish(); + } + }); + assertMainIntentBehavior(MainIntentBehaviorMetrics.BACKGROUNDED); + } + + @MediumTest + @Test + public void testCreateNtp() throws InterruptedException { + mActivityTestRule.startMainActivityFromLauncher(); + assertMainIntentBehavior(null); + ThreadUtils.runOnUiThreadBlocking(new Runnable() { + @Override + public void run() { + mActivityTestRule.getActivity().getTabCreator(false).launchNTP(); + } + }); + assertMainIntentBehavior(MainIntentBehaviorMetrics.NTP_CREATED); + } + + @MediumTest + @Test + public void testContinuation() throws InterruptedException { + try { + MainIntentBehaviorMetrics.setTimeoutDurationMsForTesting(500); + mActivityTestRule.startMainActivityFromLauncher(); + assertMainIntentBehavior(MainIntentBehaviorMetrics.CONTINUATION); + } finally { + MainIntentBehaviorMetrics.setTimeoutDurationMsForTesting( + MainIntentBehaviorMetrics.TIMEOUT_DURATION_MS); + } + } + + private void assertMainIntentBehavior(Integer expected) { + CriteriaHelper.pollUiThread(Criteria.equals(expected, new Callable<Integer>() { + @Override + public Integer call() throws Exception { + return MainIntentBehaviorMetrics.getLastMainIntentBehaviorForTesting(); + } + })); + } +}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/RadioButtonLayoutTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/RadioButtonLayoutTest.java new file mode 100644 index 0000000..927c06e --- /dev/null +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/RadioButtonLayoutTest.java
@@ -0,0 +1,166 @@ +// 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. + +package org.chromium.chrome.browser.widget; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.rule.UiThreadTestRule; +import android.test.UiThreadTest; +import android.view.View; +import android.view.ViewGroup.MarginLayoutParams; +import android.widget.RadioButton; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.chrome.test.ChromeJUnit4ClassRunner; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Tests for {@link RadioButtonLayout}. + */ +@RunWith(ChromeJUnit4ClassRunner.class) +public class RadioButtonLayoutTest { + @Rule + public UiThreadTestRule mRule = new UiThreadTestRule(); + + private Context mContext; + + @Before + public void setUp() { + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + } + + @Test + @SmallTest + @UiThreadTest + public void testMargins() { + RadioButtonLayout layout = new RadioButtonLayout(mContext); + + // Add one set of options. + List<CharSequence> messages = new ArrayList<CharSequence>(); + messages.add("a"); + messages.add("b"); + messages.add("c"); + layout.addOptions(messages, null); + + // Test the margins. + for (int i = 0; i < layout.getChildCount(); i++) { + View child = layout.getChildAt(i); + MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams(); + + if (i < layout.getChildCount() - 1) { + Assert.assertNotEquals(0, params.bottomMargin); + } else { + Assert.assertEquals(0, params.bottomMargin); + } + } + + // Add more options. + List<CharSequence> moreMessages = new ArrayList<CharSequence>(); + moreMessages.add("d"); + moreMessages.add("e"); + moreMessages.add("f"); + layout.addOptions(moreMessages, null); + + // Test the margins. + for (int i = 0; i < layout.getChildCount(); i++) { + View child = layout.getChildAt(i); + MarginLayoutParams params = (MarginLayoutParams) child.getLayoutParams(); + + if (i < layout.getChildCount() - 1) { + Assert.assertNotEquals(0, params.bottomMargin); + } else { + Assert.assertEquals(0, params.bottomMargin); + } + } + } + + @Test + @SmallTest + @UiThreadTest + public void testAddOptions() { + RadioButtonLayout layout = new RadioButtonLayout(mContext); + + // Add one set of options. + List<CharSequence> messages = new ArrayList<CharSequence>(); + messages.add("a"); + messages.add("b"); + messages.add("c"); + List<String> tags = Arrays.asList("tag 1", "tag 2", "tag 3"); + layout.addOptions(messages, tags); + Assert.assertEquals(3, layout.getChildCount()); + for (int i = 0; i < layout.getChildCount(); i++) { + Assert.assertEquals(messages.get(i), ((RadioButton) layout.getChildAt(i)).getText()); + Assert.assertEquals(tags.get(i), ((RadioButton) layout.getChildAt(i)).getTag()); + } + + // Add even more options, but without tags. + List<CharSequence> moreMessages = new ArrayList<CharSequence>(); + moreMessages.add("d"); + moreMessages.add("e"); + moreMessages.add("f"); + layout.addOptions(moreMessages, null); + Assert.assertEquals(6, layout.getChildCount()); + for (int i = 0; i < 3; i++) { + Assert.assertEquals(messages.get(i), ((RadioButton) layout.getChildAt(i)).getText()); + Assert.assertEquals(tags.get(i), ((RadioButton) layout.getChildAt(i)).getTag()); + } + for (int i = 3; i < 6; i++) { + Assert.assertEquals( + moreMessages.get(i - 3), ((RadioButton) layout.getChildAt(i)).getText()); + Assert.assertNull(((RadioButton) layout.getChildAt(i)).getTag()); + } + } + + @Test + @SmallTest + @UiThreadTest + public void testSelection() { + RadioButtonLayout layout = new RadioButtonLayout(mContext); + + // Add one set of options. + List<CharSequence> messages = new ArrayList<CharSequence>(); + messages.add("a"); + messages.add("b"); + messages.add("c"); + layout.addOptions(messages, null); + Assert.assertEquals(3, layout.getChildCount()); + + // Nothing should be selected by default. + for (int i = 0; i < layout.getChildCount(); i++) { + RadioButton child = (RadioButton) layout.getChildAt(i); + Assert.assertFalse(child.isChecked()); + } + + // Select the second one. + layout.selectChildAtIndex(1); + for (int i = 0; i < layout.getChildCount(); i++) { + RadioButton child = (RadioButton) layout.getChildAt(i); + Assert.assertEquals(i == 1, child.isChecked()); + } + + // Add even more options. + List<CharSequence> moreMessages = new ArrayList<CharSequence>(); + moreMessages.add("d"); + moreMessages.add("e"); + moreMessages.add("f"); + layout.addOptions(moreMessages, null); + Assert.assertEquals(6, layout.getChildCount()); + + // Second child should still be checked. + for (int i = 0; i < layout.getChildCount(); i++) { + RadioButton child = (RadioButton) layout.getChildAt(i); + Assert.assertEquals(i == 1, child.isChecked()); + } + } +}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn index 9cfc638..7c5874a 100644 --- a/chrome/browser/BUILD.gn +++ b/chrome/browser/BUILD.gn
@@ -1134,18 +1134,18 @@ "resource_delegate_mac.mm", "resources_util.cc", "resources_util.h", + "safe_browsing/chrome_cleaner/srt_chrome_prompt_impl.cc", + "safe_browsing/chrome_cleaner/srt_chrome_prompt_impl.h", + "safe_browsing/chrome_cleaner/srt_client_info_win.cc", + "safe_browsing/chrome_cleaner/srt_client_info_win.h", + "safe_browsing/chrome_cleaner/srt_fetcher_win.cc", + "safe_browsing/chrome_cleaner/srt_fetcher_win.h", + "safe_browsing/chrome_cleaner/srt_field_trial_win.cc", + "safe_browsing/chrome_cleaner/srt_field_trial_win.h", + "safe_browsing/chrome_cleaner/srt_global_error_win.cc", + "safe_browsing/chrome_cleaner/srt_global_error_win.h", "safe_browsing/safe_browsing_tab_observer.cc", "safe_browsing/safe_browsing_tab_observer.h", - "safe_browsing/srt_chrome_prompt_impl.cc", - "safe_browsing/srt_chrome_prompt_impl.h", - "safe_browsing/srt_client_info_win.cc", - "safe_browsing/srt_client_info_win.h", - "safe_browsing/srt_fetcher_win.cc", - "safe_browsing/srt_fetcher_win.h", - "safe_browsing/srt_field_trial_win.cc", - "safe_browsing/srt_field_trial_win.h", - "safe_browsing/srt_global_error_win.cc", - "safe_browsing/srt_global_error_win.h", "safe_search_api/safe_search_url_checker.cc", "safe_search_api/safe_search_url_checker.h", "search/iframe_source.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index 5fcb423..a979408c5 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc
@@ -913,6 +913,16 @@ arraysize(kPersistentMenuItemEnabled), nullptr}}; #endif // OS_ANDROID +const FeatureEntry::Choice kEnableHeapProfilingChoices[] = { + {flags_ui::kGenericExperimentChoiceDisabled, "", ""}, + {flag_descriptions::kEnableHeapProfilingModePseudo, + switches::kEnableHeapProfiling, switches::kEnableHeapProfilingModePseudo}, + {flag_descriptions::kEnableHeapProfilingModeNative, + switches::kEnableHeapProfiling, switches::kEnableHeapProfilingModeNative}, + {flag_descriptions::kEnableHeapProfilingTaskProfiler, + switches::kEnableHeapProfiling, + switches::kEnableHeapProfilingTaskProfiler}}; + // RECORDING USER METRICS FOR FLAGS: // ----------------------------------------------------------------------------- // The first line of the entry is the internal name. @@ -2759,6 +2769,10 @@ flag_descriptions::kAutoplayPolicyDescription, kOsAll, MULTI_VALUE_TYPE(kAutoplayPolicyChoices)}, + {"enable-heap-profiling", flag_descriptions::kEnableHeapProfilingName, + flag_descriptions::kEnableHeapProfilingDescription, kOsAll, + MULTI_VALUE_TYPE(kEnableHeapProfilingChoices)}, + // NOTE: Adding new command-line switches requires adding corresponding // entries to enum "LoginCustomFlags" in histograms.xml. See note in // histograms.xml and don't forget to run AboutFlagsHistogramTest unit test.
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc index becb9cd..7207066 100644 --- a/chrome/browser/android/chrome_feature_list.cc +++ b/chrome/browser/android/chrome_feature_list.cc
@@ -71,6 +71,8 @@ &NTPShowGoogleGInOmniboxFeature, &kPhysicalWebFeature, &kPhysicalWebSharing, + &kSearchEnginePromoExistingDevice, + &kSearchEnginePromoNewDevice, &kSpecialLocaleFeature, &kSpecialLocaleWrapper, &kTabsInCBD,
diff --git a/chrome/browser/android/vr_shell/mailbox_to_surface_bridge.cc b/chrome/browser/android/vr_shell/mailbox_to_surface_bridge.cc index d4ca0340..5f51a8e 100644 --- a/chrome/browser/android/vr_shell/mailbox_to_surface_bridge.cc +++ b/chrome/browser/android/vr_shell/mailbox_to_surface_bridge.cc
@@ -142,7 +142,7 @@ if (surface_handle_) { // Unregister from the surface tracker to avoid a resource leak. gpu::GpuSurfaceTracker* tracker = gpu::GpuSurfaceTracker::Get(); - tracker->UnregisterViewSurface(surface_handle_); + tracker->RemoveSurface(surface_handle_); } DestroyContext(); } @@ -173,10 +173,10 @@ gpu::GpuSurfaceTracker* tracker = gpu::GpuSurfaceTracker::Get(); ANativeWindow_acquire(window); // Skip ANativeWindow_setBuffersGeometry, the default size appears to work. - surface_handle_ = tracker->AddSurfaceForNativeWidget(window); - auto surface = base::MakeUnique<gl::ScopedJavaSurface>(surface_texture); - tracker->RegisterViewSurface(surface_handle_, surface->j_surface().obj()); + surface_handle_ = + tracker->AddSurfaceForNativeWidget(gpu::GpuSurfaceTracker::SurfaceRecord( + window, surface->j_surface().obj())); // Unregistering happens in the destructor. ANativeWindow_release(window);
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc index 168569c..c31ae0a 100644 --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc
@@ -205,7 +205,6 @@ #include "services/preferences/public/interfaces/preferences.mojom.h" #include "services/service_manager/public/cpp/binder_registry.h" #include "services/service_manager/public/cpp/interface_provider.h" -#include "services/service_manager/public/cpp/interface_registry.h" #include "services/service_manager/public/cpp/service.h" #include "storage/browser/fileapi/external_mount_points.h" #include "third_party/WebKit/public/platform/modules/installedapp/installed_app_provider.mojom.h" @@ -3149,7 +3148,7 @@ } void ChromeContentBrowserClient::ExposeInterfacesToMediaService( - service_manager::InterfaceRegistry* registry, + service_manager::BinderRegistry* registry, content::RenderFrameHost* render_frame_host) { // TODO(xhwang): Only register this when ENABLE_MOJO_MEDIA. #if defined(OS_CHROMEOS)
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h index 5937190b2..1a3af3a 100644 --- a/chrome/browser/chrome_content_browser_client.h +++ b/chrome/browser/chrome_content_browser_client.h
@@ -286,7 +286,7 @@ content::AssociatedInterfaceRegistry* associated_registry, content::RenderProcessHost* render_process_host) override; void ExposeInterfacesToMediaService( - service_manager::InterfaceRegistry* registry, + service_manager::BinderRegistry* registry, content::RenderFrameHost* render_frame_host) override; void ExposeInterfacesToFrame( service_manager::BinderRegistry* registry,
diff --git a/chrome/browser/chromeos/drive/download_handler.cc b/chrome/browser/chromeos/drive/download_handler.cc index 42e2303..9111e8b 100644 --- a/chrome/browser/chromeos/drive/download_handler.cc +++ b/chrome/browser/chromeos/drive/download_handler.cc
@@ -9,6 +9,7 @@ #include "base/bind.h" #include "base/files/file_util.h" #include "base/macros.h" +#include "base/memory/ptr_util.h" #include "base/strings/string_util.h" #include "base/supports_user_data.h" #include "base/task_scheduler/post_task.h" @@ -211,7 +212,8 @@ return; if (util::IsUnderDriveMountPoint(drive_path)) { - download->SetUserData(&kDrivePathKey, new DriveUserData(drive_path)); + download->SetUserData(&kDrivePathKey, + base::MakeUnique<DriveUserData>(drive_path)); download->SetDisplayName(drive_path.BaseName()); } else if (IsDriveDownload(download)) { // This may have been previously set if the default download folder is
diff --git a/chrome/browser/chromeos/login/saml/saml_browsertest.cc b/chrome/browser/chromeos/login/saml/saml_browsertest.cc index 743d699..dda3f1f 100644 --- a/chrome/browser/chromeos/login/saml/saml_browsertest.cc +++ b/chrome/browser/chromeos/login/saml/saml_browsertest.cc
@@ -1347,8 +1347,9 @@ } // PRE_TransferCookiesUnaffiliated and TransferCookiesUnaffiliated are flaky on -// MSAN and ASAN, most probably timing out. See crbug.com/683161. -#if defined(MEMORY_SANITIZER) || defined(ADDRESS_SANITIZER) +// MSAN, ASAN, Debug due to time out - most likely, because of the general +// slowness of the test. See crbug.com/683161, crbug.com/714167. +#if defined(MEMORY_SANITIZER) || defined(ADDRESS_SANITIZER) || !defined(NDEBUG) #define MAYBE_PRE_TransferCookiesUnaffiliated \ DISABLED_PRE_TransferCookiesUnaffiliated #define MAYBE_TransferCookiesUnaffiliated DISABLED_TransferCookiesUnaffiliated
diff --git a/chrome/browser/chromeos/login/ui/web_contents_forced_title.cc b/chrome/browser/chromeos/login/ui/web_contents_forced_title.cc index 51dbef2..a29383e 100644 --- a/chrome/browser/chromeos/login/ui/web_contents_forced_title.cc +++ b/chrome/browser/chromeos/login/ui/web_contents_forced_title.cc
@@ -4,6 +4,7 @@ #include "chrome/browser/chromeos/login/ui/web_contents_forced_title.h" +#include "base/memory/ptr_util.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/web_contents.h" @@ -19,8 +20,9 @@ return; web_contents->UpdateTitleForEntry(nullptr, title); - web_contents->SetUserData(UserDataKey(), - new WebContentsForcedTitle(web_contents, title)); + web_contents->SetUserData( + UserDataKey(), + base::WrapUnique(new WebContentsForcedTitle(web_contents, title))); } WebContentsForcedTitle::WebContentsForcedTitle(
diff --git a/chrome/browser/chromeos/login/ui/web_contents_set_background_color.cc b/chrome/browser/chromeos/login/ui/web_contents_set_background_color.cc index ba44ca00..6b57011 100644 --- a/chrome/browser/chromeos/login/ui/web_contents_set_background_color.cc +++ b/chrome/browser/chromeos/login/ui/web_contents_set_background_color.cc
@@ -4,6 +4,7 @@ #include "chrome/browser/chromeos/login/ui/web_contents_set_background_color.h" +#include "base/memory/ptr_util.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host.h" #include "content/public/browser/render_widget_host_view.h" @@ -23,7 +24,8 @@ // WebContentsSetBackgroundColor instance and will destroy it when the // WebContents instance is destroyed. web_contents->SetUserData( - UserDataKey(), new WebContentsSetBackgroundColor(web_contents, color)); + UserDataKey(), + base::WrapUnique(new WebContentsSetBackgroundColor(web_contents, color))); } WebContentsSetBackgroundColor::WebContentsSetBackgroundColor(
diff --git a/chrome/browser/chromeos/system/timezone_resolver_manager.cc b/chrome/browser/chromeos/system/timezone_resolver_manager.cc index d1e9ef35..43508c38 100644 --- a/chrome/browser/chromeos/system/timezone_resolver_manager.cc +++ b/chrome/browser/chromeos/system/timezone_resolver_manager.cc
@@ -98,8 +98,12 @@ // Returns service configuration for the signin screen. ServiceConfiguration GetServiceConfigurationForSigninScreen() { - if (!g_browser_process->local_state()->GetBoolean( - prefs::kResolveDeviceTimezoneByGeolocation)) { + const PrefService::Preference* device_pref = + g_browser_process->local_state()->FindPreference( + prefs::kResolveDeviceTimezoneByGeolocation); + bool device_pref_value; + if (!device_pref || + !device_pref->GetValue()->GetAsBoolean(&device_pref_value)) { // CfM devices default to static timezone. bool keyboard_driven_oobe = system::InputDeviceSettings::Get()->ForceKeyboardDrivenUINavigation(); @@ -112,7 +116,7 @@ if (base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kLoginUser)) return SHOULD_STOP; - return SHOULD_START; + return device_pref_value ? SHOULD_START : SHOULD_STOP; } } // anonymous namespace.
diff --git a/chrome/browser/component_updater/sw_reporter_installer_win.cc b/chrome/browser/component_updater/sw_reporter_installer_win.cc index ea42ade..b5134c7 100644 --- a/chrome/browser/component_updater/sw_reporter_installer_win.cc +++ b/chrome/browser/component_updater/sw_reporter_installer_win.cc
@@ -34,8 +34,8 @@ #include "base/win/registry.h" #include "base/win/windows_version.h" #include "chrome/browser/browser_process.h" -#include "chrome/browser/safe_browsing/srt_fetcher_win.h" -#include "chrome/browser/safe_browsing/srt_field_trial_win.h" +#include "chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.h" +#include "chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.h" #include "components/chrome_cleaner/public/constants/constants.h" #include "components/component_updater/component_updater_paths.h" #include "components/component_updater/component_updater_service.h"
diff --git a/chrome/browser/component_updater/sw_reporter_installer_win.h b/chrome/browser/component_updater/sw_reporter_installer_win.h index dc3f19c..66fdf0e 100644 --- a/chrome/browser/component_updater/sw_reporter_installer_win.h +++ b/chrome/browser/component_updater/sw_reporter_installer_win.h
@@ -12,7 +12,7 @@ #include "base/callback.h" #include "base/macros.h" -#include "chrome/browser/safe_browsing/srt_fetcher_win.h" +#include "chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.h" #include "components/component_updater/default_component_installer.h" class PrefRegistrySimple;
diff --git a/chrome/browser/component_updater/sw_reporter_installer_win_unittest.cc b/chrome/browser/component_updater/sw_reporter_installer_win_unittest.cc index fc8e31e..c9759fe 100644 --- a/chrome/browser/component_updater/sw_reporter_installer_win_unittest.cc +++ b/chrome/browser/component_updater/sw_reporter_installer_win_unittest.cc
@@ -22,7 +22,7 @@ #include "base/test/scoped_feature_list.h" #include "base/values.h" #include "base/version.h" -#include "chrome/browser/safe_browsing/srt_fetcher_win.h" +#include "chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.h" #include "components/chrome_cleaner/public/constants/constants.h" #include "components/variations/variations_params_manager.h" #include "content/public/test/test_browser_thread_bundle.h"
diff --git a/chrome/browser/download/save_page_browsertest.cc b/chrome/browser/download/save_page_browsertest.cc index 9ed8b3a..c0b7b717 100644 --- a/chrome/browser/download/save_page_browsertest.cc +++ b/chrome/browser/download/save_page_browsertest.cc
@@ -880,6 +880,9 @@ void SetUpOnMainThread() override { SavePageBrowserTest::SetUpOnMainThread(); + // Used by the BrokenImage test which depends on *.no.such.host not + // resolving to 127.0.0.1 + host_resolver()->AddRule("no.such.host", "128.0.0.1"); host_resolver()->AddRule("*", "127.0.0.1"); ASSERT_TRUE(embedded_test_server()->InitializeAndListen()); content::SetupCrossSiteRedirector(embedded_test_server()); @@ -1331,10 +1334,6 @@ // - Broken, undecodable image (see also https://crbug.com/586680) // - Broken link, to unresolvable host (see also https://crbug.com/594219) IN_PROC_BROWSER_TEST_P(SavePageOriginalVsSavedComparisonTest, BrokenImage) { - // Clear resolver rules to make sure that *.no.such.host used in the test html - // doesn't resolve to 127.0.0.1 - host_resolver()->ClearRules(); - content::SavePageType save_page_type = GetParam(); std::string arr[] = {
diff --git a/chrome/browser/extensions/api/activity_log_private/activity_log_private_apitest.cc b/chrome/browser/extensions/api/activity_log_private/activity_log_private_apitest.cc index 4893fc3..6ef2b74 100644 --- a/chrome/browser/extensions/api/activity_log_private/activity_log_private_apitest.cc +++ b/chrome/browser/extensions/api/activity_log_private/activity_log_private_apitest.cc
@@ -39,6 +39,11 @@ command_line->AppendSwitch(switches::kEnableExtensionActivityLogging); } + void SetUpOnMainThread() override { + ExtensionApiTest::SetUpOnMainThread(); + host_resolver()->AddRule("*", "127.0.0.1"); + } + std::unique_ptr<HttpResponse> HandleRequest(const HttpRequest& request) { std::unique_ptr<BasicHttpResponse> response(new BasicHttpResponse); response->set_code(net::HTTP_OK); @@ -63,7 +68,6 @@ IN_PROC_BROWSER_TEST_F(ActivityLogApiTest, MAYBE_TriggerEvent) { ActivityLog::GetInstance(profile())->SetWatchdogAppActiveForTesting(true); - host_resolver()->AddRule("*", "127.0.0.1"); embedded_test_server()->RegisterRequestHandler( base::Bind(&ActivityLogApiTest::HandleRequest, base::Unretained(this))); ASSERT_TRUE(StartEmbeddedTestServer());
diff --git a/chrome/browser/extensions/api/feedback_private/feedback_private_api.cc b/chrome/browser/extensions/api/feedback_private/feedback_private_api.cc index d96b830..bfe6f6a 100644 --- a/chrome/browser/extensions/api/feedback_private/feedback_private_api.cc +++ b/chrome/browser/extensions/api/feedback_private/feedback_private_api.cc
@@ -39,7 +39,7 @@ #if defined(OS_WIN) #include "base/feature_list.h" -#include "chrome/browser/safe_browsing/srt_fetcher_win.h" +#include "chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.h" #endif using extensions::api::feedback_private::SystemInformation;
diff --git a/chrome/browser/extensions/webstore_inline_installer.cc b/chrome/browser/extensions/webstore_inline_installer.cc index 6816f21..82bf478 100644 --- a/chrome/browser/extensions/webstore_inline_installer.cc +++ b/chrome/browser/extensions/webstore_inline_installer.cc
@@ -9,11 +9,8 @@ #include "base/json/json_writer.h" #include "base/strings/stringprintf.h" #include "base/values.h" -#include "chrome/browser/browser_process.h" #include "chrome/browser/extensions/webstore_data_fetcher.h" #include "chrome/browser/profiles/profile.h" -#include "chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h" -#include "chrome/browser/safe_browsing/safe_browsing_service.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/exclusive_access/fullscreen_controller.h" #include "chrome/common/pref_names.h" @@ -23,14 +20,6 @@ #include "content/public/browser/web_contents.h" using content::WebContents; -using safe_browsing::SafeBrowsingNavigationObserverManager; -using safe_browsing::ReferrerChain; - -namespace { - -// The number of user gestures to trace back for CWS pings. -const int kExtensionReferrerUserGestureLimit = 2; -} namespace extensions { @@ -114,10 +103,6 @@ return true; } -bool WebstoreInlineInstaller::SafeBrowsingNavigationEventsEnabled() const { - return SafeBrowsingNavigationObserverManager::IsEnabledAndReady(profile()); -} - std::string WebstoreInlineInstaller::GetJsonPostData() { // web_contents() might return null during tab destruction. This object would // also be destroyed shortly thereafter but check to be on the safe side. @@ -129,71 +114,32 @@ if (!profile()->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled)) return std::string(); - auto redirect_chain = base::MakeUnique<base::ListValue>(); + content::NavigationController& navigation_controller = + web_contents()->GetController(); + content::NavigationEntry* navigation_entry = + navigation_controller.GetLastCommittedEntry(); - if (SafeBrowsingNavigationEventsEnabled()) { - // If we have it, use the new referrer checker. - safe_browsing::SafeBrowsingService* safe_browsing_service = - g_browser_process->safe_browsing_service(); - // May be null in some tests. - if (!safe_browsing_service) - return std::string(); + if (navigation_entry) { + const std::vector<GURL>& redirect_urls = + navigation_entry->GetRedirectChain(); - scoped_refptr<SafeBrowsingNavigationObserverManager> - navigation_observer_manager = - safe_browsing_service->navigation_observer_manager(); - // This may be null if the navigation observer manager feature is - // disabled by experiment. - if (!navigation_observer_manager) - return std::string(); - - ReferrerChain referrer_chain; - SafeBrowsingNavigationObserverManager::AttributionResult result = - navigation_observer_manager->IdentifyReferrerChainByWebContents( - web_contents(), kExtensionReferrerUserGestureLimit, - &referrer_chain); - if (result != - SafeBrowsingNavigationObserverManager::NAVIGATION_EVENT_NOT_FOUND) { - // For now the CWS post data is JSON encoded. Consider moving it to a - // proto. - for (const auto& referrer_chain_entry : referrer_chain) { - // Referrer chain entries are a list of URLs in reverse chronological - // order, so the final URL is the last thing in the list and the initial - // landing page is the first thing in the list. - // Furthermore each entry may contain a series of server redirects - // stored in the same order. - redirect_chain->AppendString(referrer_chain_entry.url()); - for (const auto& server_side_redirect : - referrer_chain_entry.server_redirect_chain()) { - redirect_chain->AppendString(server_side_redirect.url()); - } - } - } - } else { - content::NavigationController& navigation_controller = - web_contents()->GetController(); - content::NavigationEntry* navigation_entry = - navigation_controller.GetLastCommittedEntry(); - if (navigation_entry) { - const std::vector<GURL>& redirect_urls = - navigation_entry->GetRedirectChain(); + if (!redirect_urls.empty()) { + base::DictionaryValue dictionary; + dictionary.SetString("id", id()); + dictionary.SetString("referrer", requestor_url_.spec()); + std::unique_ptr<base::ListValue> redirect_chain = + base::MakeUnique<base::ListValue>(); for (const GURL& url : redirect_urls) { redirect_chain->AppendString(url.spec()); } + dictionary.Set("redirect_chain", std::move(redirect_chain)); + + std::string json; + base::JSONWriter::Write(dictionary, &json); + return json; } } - if (!redirect_chain->empty()) { - base::DictionaryValue dictionary; - dictionary.SetString("id", id()); - dictionary.SetString("referrer", requestor_url_.spec()); - dictionary.Set("redirect_chain", std::move(redirect_chain)); - - std::string json; - base::JSONWriter::Write(dictionary, &json); - return json; - } - return std::string(); }
diff --git a/chrome/browser/extensions/webstore_inline_installer.h b/chrome/browser/extensions/webstore_inline_installer.h index 3e3760e..0eb4ce4 100644 --- a/chrome/browser/extensions/webstore_inline_installer.h +++ b/chrome/browser/extensions/webstore_inline_installer.h
@@ -49,9 +49,6 @@ ~WebstoreInlineInstaller() override; - // Returns whether to use the new navigation event tracker. - virtual bool SafeBrowsingNavigationEventsEnabled() const; - // Implementations WebstoreStandaloneInstaller Template Method's hooks. std::string GetJsonPostData() override; bool CheckRequestorAlive() const override;
diff --git a/chrome/browser/extensions/webstore_inline_installer_browsertest.cc b/chrome/browser/extensions/webstore_inline_installer_browsertest.cc index 6eb3a8885..c7f6a627 100644 --- a/chrome/browser/extensions/webstore_inline_installer_browsertest.cc +++ b/chrome/browser/extensions/webstore_inline_installer_browsertest.cc
@@ -118,8 +118,7 @@ content::RenderFrameHost* host, const std::string& extension_id, const GURL& requestor_url, - const Callback& callback, - bool enable_safebrowsing_redirects) + const Callback& callback) : WebstoreInlineInstaller( contents, host, @@ -140,10 +139,6 @@ return WebstoreInlineInstaller::CheckRequestorAlive(); } - bool SafeBrowsingNavigationEventsEnabled() const override { - return enable_safebrowsing_redirects_; - } - // Tests that care about the actual arguments to the install callback can use // this to receive a copy in |install_result_target|. void set_install_result_target( @@ -171,22 +166,13 @@ // arguments. std::unique_ptr<InstallResult>* install_result_target_; - // This can be set by tests that want to use the new SafeBrowsing redirect - // tracker. - bool enable_safebrowsing_redirects_; - ProgrammableInstallPrompt* programmable_prompt_; }; class WebstoreInlineInstallerForTestFactory : public WebstoreInlineInstallerFactory { public: - WebstoreInlineInstallerForTestFactory() - : last_installer_(nullptr), enable_safebrowsing_redirects_(false) {} - explicit WebstoreInlineInstallerForTestFactory( - bool enable_safebrowsing_redirects) - : last_installer_(nullptr), - enable_safebrowsing_redirects_(enable_safebrowsing_redirects) {} + WebstoreInlineInstallerForTestFactory() : last_installer_(nullptr) {} ~WebstoreInlineInstallerForTestFactory() override {} WebstoreInlineInstallerForTest* last_installer() { return last_installer_; } @@ -198,16 +184,13 @@ const GURL& requestor_url, const WebstoreStandaloneInstaller::Callback& callback) override { last_installer_ = new WebstoreInlineInstallerForTest( - contents, host, webstore_item_id, requestor_url, callback, - enable_safebrowsing_redirects_); + contents, host, webstore_item_id, requestor_url, callback); return last_installer_; } private: // The last installer that was created. WebstoreInlineInstallerForTest* last_installer_; - - bool enable_safebrowsing_redirects_; }; IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerTest, @@ -390,9 +373,7 @@ RunTest("runTest"); } -class WebstoreInlineInstallerRedirectTest - : public WebstoreInlineInstallerTest, - public ::testing::WithParamInterface<bool> { +class WebstoreInlineInstallerRedirectTest : public WebstoreInlineInstallerTest { public: WebstoreInlineInstallerRedirectTest() : cws_request_received_(false) {} ~WebstoreInlineInstallerRedirectTest() override {} @@ -420,16 +401,8 @@ // Test that an install from a page arrived at via redirects includes the // redirect information in the webstore request. -IN_PROC_BROWSER_TEST_P(WebstoreInlineInstallerRedirectTest, +IN_PROC_BROWSER_TEST_F(WebstoreInlineInstallerRedirectTest, IncludesRedirectData) { - const bool using_safe_browsing_tracker = GetParam(); - WebContents* web_contents = - browser()->tab_strip_model()->GetActiveWebContents(); - TabHelper* tab_helper = TabHelper::FromWebContents(web_contents); - WebstoreInlineInstallerForTestFactory* factory = - new WebstoreInlineInstallerForTestFactory(using_safe_browsing_tracker); - tab_helper->SetWebstoreInlineInstallerFactoryForTests(factory); - // Hand craft a url that will cause the test server to issue redirects. const std::vector<std::string> redirects = {kRedirect1Domain, kRedirect2Domain}; @@ -446,11 +419,7 @@ AutoAcceptInstall(); ui_test_utils::NavigateToURL(browser(), install_url); - - RunTestAsync("runTest"); - while (!ProgrammableInstallPrompt::Ready()) - base::RunLoop().RunUntilIdle(); - web_contents->Close(); + RunTest("runTest"); EXPECT_TRUE(cws_request_received_); ASSERT_NE(nullptr, cws_request_json_data_); @@ -460,21 +429,15 @@ ASSERT_NE(nullptr, redirect_list); // Check that the expected domains are in the redirect list. - const std::set<std::string> expected_redirect_domains = { + const std::vector<std::string> expected_redirect_domains = { kRedirect1Domain, kRedirect2Domain, kAppDomain}; - - // The SafeBrowsing tracker has a much more liberal definition of "redirect" - // and it may (based on timing) pick up additional navigations that occur - // shortly before the navigation we mainly care about here. Be somewhat - // permissive in what we accept as redirect results. - ASSERT_LE(expected_redirect_domains.size(), redirect_list->GetSize()); - + ASSERT_EQ(expected_redirect_domains.size(), redirect_list->GetSize()); + int i = 0; for (const auto& value : *redirect_list) { std::string value_string; ASSERT_TRUE(value.GetAsString(&value_string)); GURL redirect_url(value_string); - EXPECT_TRUE(expected_redirect_domains.find(redirect_url.host()) != - expected_redirect_domains.end()); + EXPECT_EQ(expected_redirect_domains[i++], redirect_url.host()); } } @@ -510,13 +473,6 @@ ASSERT_EQ(nullptr, cws_request_json_data_); } -INSTANTIATE_TEST_CASE_P(NetRedirectTracking, - WebstoreInlineInstallerRedirectTest, - testing::Values(false)); -INSTANTIATE_TEST_CASE_P(SafeBrowsingRedirectTracking, - WebstoreInlineInstallerRedirectTest, - testing::Values(true)); - class WebstoreInlineInstallerListenerTest : public WebstoreInlineInstallerTest { public: WebstoreInlineInstallerListenerTest() {}
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc index 8e3ab41b..fb54c3b 100644 --- a/chrome/browser/flag_descriptions.cc +++ b/chrome/browser/flag_descriptions.cc
@@ -3058,4 +3058,14 @@ "the current page is provided as the first suggestion without a title. " "Enabling this flag causes the title to be displayed."; +const char kEnableHeapProfilingName[] = "Heap profiling"; + +const char kEnableHeapProfilingDescription[] = "Enables heap profiling."; + +const char kEnableHeapProfilingModePseudo[] = "Enabled (pseudo mode)"; + +const char kEnableHeapProfilingModeNative[] = "Enabled (native mode)"; + +const char kEnableHeapProfilingTaskProfiler[] = "Enabled (task mode)"; + } // namespace flag_descriptions
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h index f7b4227..4103afb 100644 --- a/chrome/browser/flag_descriptions.h +++ b/chrome/browser/flag_descriptions.h
@@ -3323,6 +3323,15 @@ // for current URL. extern const char kOmniboxDisplayTitleForCurrentUrlDescription[]; +// Name & description for the heap profiling flag. +extern const char kEnableHeapProfilingName[]; +extern const char kEnableHeapProfilingDescription[]; + +// Descriptions of the different heap profiling modes. +extern const char kEnableHeapProfilingModePseudo[]; +extern const char kEnableHeapProfilingModeNative[]; +extern const char kEnableHeapProfilingTaskProfiler[]; + } // namespace flag_descriptions #endif // CHROME_BROWSER_FLAG_DESCRIPTIONS_H_
diff --git a/chrome/browser/resources/chromeos/switch_access/BUILD.gn b/chrome/browser/resources/chromeos/switch_access/BUILD.gn index 9c11288..ee22609a 100644 --- a/chrome/browser/resources/chromeos/switch_access/BUILD.gn +++ b/chrome/browser/resources/chromeos/switch_access/BUILD.gn
@@ -26,6 +26,7 @@ dest_dir = switch_access_dir sources = [ "auto_scan_manager.js", + "automation_manager.js", "background.js", "keyboard_handler.js", "options.css",
diff --git a/chrome/browser/resources/chromeos/switch_access/automation_manager.js b/chrome/browser/resources/chromeos/switch_access/automation_manager.js new file mode 100644 index 0000000..855539da --- /dev/null +++ b/chrome/browser/resources/chromeos/switch_access/automation_manager.js
@@ -0,0 +1,157 @@ +// 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. + +/** + * Class to manage interactions with the accessibility tree, including moving + * to and selecting nodes. + * + * @constructor + */ +function AutomationManager() { + /** + * Currently selected node. + * + * @private {chrome.automation.AutomationNode} + */ + this.node_ = null; + + /** + * Root node (i.e., the desktop). + * + * @private {chrome.automation.AutomationNode} + */ + this.root_ = null; + + /** + * Moves to the appropriate node in the accessibility tree. + * + * @private {AutomationTreeWalker} + */ + this.treeWalker_ = null; + + this.init_(); +}; + +AutomationManager.prototype = { + /** + * Set this.node_ and this.root_ to the desktop node, and initialize the + * tree walker. + * + * @private + */ + init_: function() { + this.treeWalker_ = new AutomationTreeWalker(); + + chrome.automation.getDesktop(function(desktop) { + this.node_ = desktop; + this.root_ = desktop; + console.log('AutomationNode for desktop is loaded'); + this.printNode_(this.node_); + }.bind(this)); + }, + + /** + * Set this.node_ to the next/previous interesting node, and then highlight + * it on the screen. If no interesting node is found, set this.node_ to the + * first/last interesting node. If |doNext| is true, will search for next + * node. Otherwise, will search for previous node. + * + * @param {boolean} doNext + */ + moveToNode: function(doNext) { + let node = this.treeWalker_.moveToNode(this.node_, this.root_, doNext); + if (node) { + this.node_ = node; + this.printNode_(this.node_); + chrome.accessibilityPrivate.setFocusRing([this.node_.location]); + } + }, + + /** + * Perform the default action on the currently selected node. + */ + doDefault: function() { + if (!this.node_) + return; + + this.node_.doDefault(); + }, + + // TODO(elichtenberg): Move print functions to a custom logger class. Only + // log when debuggingEnabled is true. + /** + * Print out details about a node. + * + * @param {chrome.automation.AutomationNode} node + * @private + */ + printNode_: function(node) { + if (node) { + console.log('Name = ' + node.name); + console.log('Role = ' + node.role); + console.log('Root role = ' + node.root.role); + if (!node.parent) + console.log('At index ' + node.indexInParent + ', has no parent'); + else { + let numSiblings = node.parent.children.length; + console.log( + 'At index ' + node.indexInParent + ', there are ' + + numSiblings + ' siblings'); + } + console.log('Has ' + node.children.length + ' children'); + } else { + console.log('Node is null'); + } + console.log(node); + console.log('\n'); + }, + + /** + * Move to the next sibling of this.node_ if it has one. + */ + debugMoveToNext: function() { + let next = this.treeWalker_.debugMoveToNext(this.node_); + if (next) { + this.node_ = next; + this.printNode_(this.node_); + chrome.accessibilityPrivate.setFocusRing([this.node_.location]); + } + }, + + /** + * Move to the previous sibling of this.node_ if it has one. + */ + debugMoveToPrevious: function() { + let prev = this.treeWalker_.debugMoveToPrevious(this.node_); + if (prev) { + this.node_ = prev; + this.printNode_(this.node_); + chrome.accessibilityPrivate.setFocusRing([this.node_.location]); + } + }, + + /** + * Move to the first child of this.node_ if it has one. + */ + debugMoveToFirstChild: function() { + let child = this.treeWalker_.debugMoveToFirstChild(this.node_); + if (child) { + this.node_ = child; + this.printNode_(this.node_); + chrome.accessibilityPrivate.setFocusRing([this.node_.location]); + } + }, + + /** + * Move to the parent of this.node_ if it has one. + */ + debugMoveToParent: function() { + let parent = this.treeWalker_.debugMoveToParent(this.node_); + if (parent) { + this.node_ = parent; + this.printNode_(this.node_); + chrome.accessibilityPrivate.setFocusRing([this.node_.location]); + } + } +};
diff --git a/chrome/browser/resources/chromeos/switch_access/compiled_resources2.gyp b/chrome/browser/resources/chromeos/switch_access/compiled_resources2.gyp index 56172d4..d5e30fc3 100644 --- a/chrome/browser/resources/chromeos/switch_access/compiled_resources2.gyp +++ b/chrome/browser/resources/chromeos/switch_access/compiled_resources2.gyp
@@ -13,6 +13,15 @@ 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], }, { + 'target_name': 'automation_manager', + 'dependencies': [ + '<(EXTERNS_GYP):accessibility_private', + '<(EXTERNS_GYP):automation', + 'tree_walker', + ], + 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], + }, + { 'target_name': 'background', 'dependencies': [ '<(EXTERNS_GYP):chrome_extensions', @@ -52,8 +61,8 @@ '<(EXTERNS_GYP):chrome_extensions', 'prefs', 'auto_scan_manager', + 'automation_manager', 'keyboard_handler', - 'tree_walker', ], 'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'], },
diff --git a/chrome/browser/resources/chromeos/switch_access/manifest.json.jinja2 b/chrome/browser/resources/chromeos/switch_access/manifest.json.jinja2 index 25fdb385..40ad0e1 100644 --- a/chrome/browser/resources/chromeos/switch_access/manifest.json.jinja2 +++ b/chrome/browser/resources/chromeos/switch_access/manifest.json.jinja2
@@ -13,6 +13,7 @@ "scripts": [ "prefs.js", "auto_scan_manager.js", + "automation_manager.js", "keyboard_handler.js", "tree_walker.js", "switch_access.js",
diff --git a/chrome/browser/resources/chromeos/switch_access/switch_access.js b/chrome/browser/resources/chromeos/switch_access/switch_access.js index 58bcb3a..f24f524 100644 --- a/chrome/browser/resources/chromeos/switch_access/switch_access.js +++ b/chrome/browser/resources/chromeos/switch_access/switch_access.js
@@ -33,33 +33,19 @@ this.keyboardHandler_ = null; /** - * Moves to the appropriate node in the accessibility tree. + * Handles interactions with the accessibility tree, including moving to and + * selecting nodes. * - * @private {AutomationTreeWalker} + * @private {AutomationManager} */ - this.treeWalker_ = null; - - /** - * Currently selected node. - * - * @private {chrome.automation.AutomationNode} - */ - this.node_ = null; - - /** - * Root node (i.e., the desktop). - * - * @private {chrome.automation.AutomationNode} - */ - this.root_ = null; + this.automationManager_ = null; this.init_(); }; SwitchAccess.prototype = { /** - * Set this.node_ and this.root_ to the desktop node, and set up preferences, - * controllers, and event listeners. + * Set up preferences, controllers, and event listeners. * * @private */ @@ -67,47 +53,30 @@ this.switchAccessPrefs = new SwitchAccessPrefs(); this.autoScanManager_ = new AutoScanManager(this); this.keyboardHandler_ = new KeyboardHandler(this); - this.treeWalker_ = new AutomationTreeWalker(); - - chrome.automation.getDesktop(function(desktop) { - this.node_ = desktop; - this.root_ = desktop; - console.log('AutomationNode for desktop is loaded'); - this.printNode_(this.node_); - }.bind(this)); + this.automationManager_ = new AutomationManager(); document.addEventListener( 'prefsUpdate', this.handlePrefsUpdate_.bind(this)); }, /** - * Set this.node_ to the next/previous interesting node. If no interesting - * node is found, set this.node_ to the first/last interesting node. If - * |doNext| is true, will search for next node. Otherwise, will search for - * previous node. + * Move to the next/previous interesting node. If |doNext| is true, move to + * the next node. Otherwise, move to the previous node. * * @param {boolean} doNext * @override */ moveToNode: function(doNext) { - let node = this.treeWalker_.moveToNode(this.node_, this.root_, doNext); - if (node) { - this.node_ = node; - this.printNode_(this.node_); - chrome.accessibilityPrivate.setFocusRing([this.node_.location]); - } + this.automationManager_.moveToNode(doNext); }, /** - * Perform the default action on the currently selected node. + * Perform the default action on the current node. * * @override */ doDefault: function() { - if (!this.node_) - return; - - this.node_.doDefault(); + this.automationManager_.doDefault(); }, /** @@ -150,88 +119,39 @@ } }, - // TODO(elichtenberg): Move print functions to a custom logger class. Only - // log when debuggingEnabled is true. /** - * Print out details about a node. - * - * @param {chrome.automation.AutomationNode} node - * @private - */ - printNode_: function(node) { - if (node) { - console.log('Name = ' + node.name); - console.log('Role = ' + node.role); - console.log('Root role = ' + node.root.role); - if (!node.parent) - console.log('At index ' + node.indexInParent + ', has no parent'); - else { - let numSiblings = node.parent.children.length; - console.log( - 'At index ' + node.indexInParent + ', there are ' - + numSiblings + ' siblings'); - } - console.log('Has ' + node.children.length + ' children'); - } else { - console.log('Node is null'); - } - console.log(node); - console.log('\n'); - }, - - /** - * Move to the next sibling of this.node_ if it has one. + * Move to the next sibling of the current node if it has one. * * @override */ debugMoveToNext: function() { - let next = this.treeWalker_.debugMoveToNext(this.node_); - if (next) { - this.node_ = next; - this.printNode_(this.node_); - chrome.accessibilityPrivate.setFocusRing([this.node_.location]); - } + this.automationManager_.debugMoveToNext(); }, /** - * Move to the previous sibling of this.node_ if it has one. + * Move to the previous sibling of the current node if it has one. * * @override */ debugMoveToPrevious: function() { - let prev = this.treeWalker_.debugMoveToPrevious(this.node_); - if (prev) { - this.node_ = prev; - this.printNode_(this.node_); - chrome.accessibilityPrivate.setFocusRing([this.node_.location]); - } + this.automationManager_.debugMoveToPrevious(); }, /** - * Move to the first child of this.node_ if it has one. + * Move to the first child of the current node if it has one. * * @override */ debugMoveToFirstChild: function() { - let child = this.treeWalker_.debugMoveToFirstChild(this.node_); - if (child) { - this.node_ = child; - this.printNode_(this.node_); - chrome.accessibilityPrivate.setFocusRing([this.node_.location]); - } + this.automationManager_.debugMoveToFirstChild(); }, /** - * Move to the parent of this.node_ if it has one. + * Move to the parent of the current node if it has one. * * @override */ debugMoveToParent: function() { - let parent = this.treeWalker_.debugMoveToParent(this.node_); - if (parent) { - this.node_ = parent; - this.printNode_(this.node_); - chrome.accessibilityPrivate.setFocusRing([this.node_.location]); - } + this.automationManager_.debugMoveToParent(); } };
diff --git a/chrome/browser/resources/settings/appearance_page/appearance_page.html b/chrome/browser/resources/settings/appearance_page/appearance_page.html index 9bccfc19..fcc3055 100644 --- a/chrome/browser/resources/settings/appearance_page/appearance_page.html +++ b/chrome/browser/resources/settings/appearance_page/appearance_page.html
@@ -75,17 +75,18 @@ </a> <if expr="not is_linux or chromeos"> <template is="dom-if" if="[[prefs.extensions.theme.id.value]]"> - <div class="secondary-action"> - <paper-button id="useDefault" on-tap="onUseDefaultTap_" - class="secondary-button"> - $i18n{resetToDefaultTheme} - </paper-button> - </div> + <div class="separator"></div> + <paper-button id="useDefault" on-tap="onUseDefaultTap_" + class="secondary-button"> + $i18n{resetToDefaultTheme} + </paper-button> </template> </if> <if expr="is_linux and not chromeos"> - <div class="secondary-action" hidden="[[!showThemesSecondary_( - prefs.extensions.theme.id.value, useSystemTheme_)]]"> + <div class="layout horizontal center" hidden="[[!showThemesSecondary_( + prefs.extensions.theme.id.value, useSystemTheme_)]]" + id="themesSecondaryActions"> + <div class="separator"></div> <template is="dom-if" if="[[showUseClassic_( prefs.extensions.theme.id.value, useSystemTheme_)]]" restamp> <paper-button id="useDefault" on-tap="onUseDefaultTap_" @@ -177,7 +178,7 @@ <div class="settings-box" hidden="[[!pageVisibility.pageZoom]]"> <div id="pageZoom" class="start">$i18n{pageZoom}</div> <div class="md-select-wrapper"> - <select id="zoomLevel" class="md-select" aria-labelledy="pageZoom" + <select id="zoomLevel" class="md-select" aria-labelledby="pageZoom" on-change="onZoomLevelChange_"> <template is="dom-repeat" items="[[pageZoomLevels_]]"> <option value="[[item]]"
diff --git a/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_dialog.html b/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_dialog.html index c662c3c6..ff5ddea9 100644 --- a/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_dialog.html +++ b/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_dialog.html
@@ -2,7 +2,7 @@ <link rel="import" href="chrome://resources/cr_elements/cr_scrollable_behavior.html"> <link rel="import" href="chrome://resources/html/cr.html"> <link rel="import" href="chrome://resources/html/polymer.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-input/paper-input.html"> @@ -11,7 +11,7 @@ <dom-module id="bluetooth-device-dialog"> <template> - <style include="settings-shared"> + <style include="settings-shared iron-flex"> #pairing { margin-bottom: 10px; }
diff --git a/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_list_item.html b/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_list_item.html index 079cdc3..da91fa3 100644 --- a/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_list_item.html +++ b/chrome/browser/resources/settings/bluetooth_page/bluetooth_device_list_item.html
@@ -2,7 +2,6 @@ <link rel="import" href="chrome://resources/cr_elements/icons.html"> <link rel="import" href="chrome://resources/html/i18n_behavior.html"> <link rel="import" href="chrome://resources/html/polymer.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button.html"> <link rel="import" href="../settings_shared_css.html">
diff --git a/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.html b/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.html index b54d6c5..2ad577446 100644 --- a/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.html +++ b/chrome/browser/resources/settings/bluetooth_page/bluetooth_page.html
@@ -1,6 +1,5 @@ <link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_pref_indicator.html"> <link rel="import" href="chrome://resources/html/polymer.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/neon-animation/neon-animatable.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-toggle-button/paper-toggle-button.html"> @@ -53,7 +52,7 @@ <template is="dom-if" route-path="/bluetoothDevices"> <settings-subpage associated-control="[[$$('#bluetoothDevices')]]" page-title="$i18n{bluetoothPageTitle}"> - <settings-bluetooth-subpage + <settings-bluetooth-subpage adapter-state="[[adapterState_]]" bluetooth-toggle-state="{{bluetoothToggleState_}}" bluetooth-toggle-disabled="[[bluetoothToggleDisabled_]]"
diff --git a/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.html b/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.html index e5bd151..769121f 100644 --- a/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.html +++ b/chrome/browser/resources/settings/bluetooth_page/bluetooth_subpage.html
@@ -1,7 +1,6 @@ <link rel="import" href="chrome://resources/cr_elements/cr_scrollable_behavior.html"> <link rel="import" href="chrome://resources/html/i18n_behavior.html"> <link rel="import" href="chrome://resources/html/polymer.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-spinner/paper-spinner.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-toggle-button/paper-toggle-button.html"> @@ -13,9 +12,11 @@ <dom-module id="settings-bluetooth-subpage"> <template> - <style include="settings-shared"> + <style include="settings-shared iron-flex"> .container { @apply(--settings-list-frame-padding); + display: flex; + flex-direction: column; min-height: 10px; overflow-y: auto; } @@ -57,7 +58,7 @@ hidden="[[!showNoDevices_(bluetoothToggleState, pairedDeviceList_)]]"> $i18n{bluetoothNoDevices} </div> - <div id="pairedContainer" class="container layout vertical" + <div id="pairedContainer" class="container" scrollable on-device-event="onDeviceEvent_" hidden="[[!showDevices_(bluetoothToggleState, pairedDeviceList_)]]"> <iron-list id="pairedDevices" class="vertical-list" @@ -82,7 +83,7 @@ hidden="[[!showNoDevices_(bluetoothToggleState, unpairedDeviceList_)]]"> $i18n{bluetoothNoDevicesFound} </div> - <div id="unpairedContainer" class="container layout vertical" + <div id="unpairedContainer" class="container" scrollable on-device-event="onDeviceEvent_" hidden="[[!showDevices_(bluetoothToggleState, unpairedDeviceList_)]]"> <iron-list id="unpairedDevices" class="vertical-list"
diff --git a/chrome/browser/resources/settings/default_browser_page/default_browser_page.html b/chrome/browser/resources/settings/default_browser_page/default_browser_page.html index e72f7615..02603a22 100644 --- a/chrome/browser/resources/settings/default_browser_page/default_browser_page.html +++ b/chrome/browser/resources/settings/default_browser_page/default_browser_page.html
@@ -14,12 +14,11 @@ <div id="canBeDefaultBrowser">$i18n{defaultBrowser}</div> <div class="secondary">$i18n{defaultBrowserMakeDefault}</div> </div> - <div class="secondary-action"> - <paper-button class="secondary-button" - on-tap="onSetDefaultBrowserTap_"> - $i18n{defaultBrowserMakeDefaultButton} - </paper-button> - </div> + <div class="separator"></div> + <paper-button class="secondary-button" + on-tap="onSetDefaultBrowserTap_"> + $i18n{defaultBrowserMakeDefaultButton} + </paper-button> </div> </template> <template is="dom-if" if="[[!maySetDefaultBrowser_]]">
diff --git a/chrome/browser/resources/settings/device_page/display.html b/chrome/browser/resources/settings/device_page/display.html index b0dea45..72bd111 100644 --- a/chrome/browser/resources/settings/device_page/display.html +++ b/chrome/browser/resources/settings/device_page/display.html
@@ -1,7 +1,7 @@ <link rel="import" href="chrome://resources/html/polymer.html"> <link rel="import" href="chrome://resources/html/i18n_behavior.html"> <link rel="import" href="chrome://resources/html/md_select_css.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-tabs/paper-tabs.html"> @@ -14,7 +14,7 @@ <dom-module id="settings-display"> <template> - <style include="settings-shared md-select"> + <style include="settings-shared md-select iron-flex iron-flex-alignment"> .settings-box.embedded { -webkit-margin-start: 20px; align-self: stretch; @@ -50,7 +50,7 @@ #controlsDiv > .settings-box:first-of-type { border-top: none; - } + } </style> <div class="settings-box first layout vertical self-stretch"> <h2 class="layout self-start">
diff --git a/chrome/browser/resources/settings/device_page/display_overscan_dialog.html b/chrome/browser/resources/settings/device_page/display_overscan_dialog.html index 76206a6..e579a0b 100644 --- a/chrome/browser/resources/settings/device_page/display_overscan_dialog.html +++ b/chrome/browser/resources/settings/device_page/display_overscan_dialog.html
@@ -1,7 +1,7 @@ <link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> <link rel="import" href="chrome://resources/cr_elements/icons.html"> <link rel="import" href="chrome://resources/html/polymer.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> <link rel="import" href="../icons.html"> @@ -9,7 +9,7 @@ <dom-module id="settings-display-overscan-dialog"> <template> - <style include="settings-shared"> + <style include="settings-shared iron-flex iron-flex-alignment"> .subtitle { margin-top: 10px; }
diff --git a/chrome/browser/resources/settings/internet_page/internet_detail_page.html b/chrome/browser/resources/settings/internet_page/internet_detail_page.html index 281d81e..6cf9e6fb 100644 --- a/chrome/browser/resources/settings/internet_page/internet_detail_page.html +++ b/chrome/browser/resources/settings/internet_page/internet_detail_page.html
@@ -8,7 +8,7 @@ <link rel="import" href="chrome://resources/html/i18n_behavior.html"> <link rel="import" href="chrome://resources/html/polymer.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-collapse/iron-collapse.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-toggle-button/paper-toggle-button.html"> @@ -25,7 +25,7 @@ <dom-module id="settings-internet-detail-page"> <template> - <style include="internet-shared"> + <style include="internet-shared iron-flex"> :host { padding-bottom: 40px; }
diff --git a/chrome/browser/resources/settings/internet_page/internet_known_networks_page.html b/chrome/browser/resources/settings/internet_page/internet_known_networks_page.html index 9668c5d..9d2a3db 100644 --- a/chrome/browser/resources/settings/internet_page/internet_known_networks_page.html +++ b/chrome/browser/resources/settings/internet_page/internet_known_networks_page.html
@@ -1,7 +1,6 @@ <link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html"> <link rel="import" href="chrome://resources/cr_elements/icons.html"> <link rel="import" href="chrome://resources/html/polymer.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html"> <link rel="import" href="../settings_shared_css.html">
diff --git a/chrome/browser/resources/settings/internet_page/internet_page.html b/chrome/browser/resources/settings/internet_page/internet_page.html index 6dc72c3..8a39db9 100644 --- a/chrome/browser/resources/settings/internet_page/internet_page.html +++ b/chrome/browser/resources/settings/internet_page/internet_page.html
@@ -30,7 +30,7 @@ focus-config="[[focusConfig_]]"> <neon-animatable route-path="default"> <network-summary default-network="{{defaultNetwork}}" - device-states="{{deviceStates}}" + device-states="{{deviceStates}}" networking-private="[[networkingPrivate]]"> </network-summary> <template is="dom-if" if="[[allowAddConnection_(globalPolicy_)]]">
diff --git a/chrome/browser/resources/settings/internet_page/internet_subpage.html b/chrome/browser/resources/settings/internet_page/internet_subpage.html index 5b53e7b..db4543e9 100644 --- a/chrome/browser/resources/settings/internet_page/internet_subpage.html +++ b/chrome/browser/resources/settings/internet_page/internet_subpage.html
@@ -1,7 +1,7 @@ <link rel="import" href="chrome://resources/cr_elements/network/cr_network_list.html"> <link rel="import" href="chrome://resources/html/i18n_behavior.html"> <link rel="import" href="chrome://resources/html/polymer.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-toggle-button/paper-toggle-button.html"> @@ -9,7 +9,7 @@ <dom-module id="settings-internet-subpage"> <template> - <style include="settings-shared"> + <style include="settings-shared iron-flex"> #networkListDiv { /* cr-network-list is padded to the right to allow space for a ripple */ -webkit-padding-end: calc(var(--settings-box-row-padding) - @@ -41,6 +41,10 @@ .no-networks { margin: 4px; } + + cr-network-list { + flex: 1; + } </style> <template is="dom-if" if="[[enableToggleIsVisible_(deviceState)]]"> @@ -86,7 +90,7 @@ </div> <!-- List of networks or 'None'. --> - <cr-network-list id="networkList" class="flex" show-buttons + <cr-network-list id="networkList" show-buttons hidden$="[[!networkStateList_.length]]" networks="[[networkStateList_]]" on-selected="onNetworkSelected_"> @@ -106,7 +110,7 @@ on-tap="onAddThirdPartyVpnTap_" tabindex$="[[tabindex]]"> </paper-icon-button> </div> - <cr-network-list class="flex" show-buttons + <cr-network-list show-buttons hidden$="[[!haveThirdPartyVpnNetwork_(thirdPartyVpns_, item)]]" networks="[[getThirdPartyVpnNetworks_(thirdPartyVpns_, item)]]" on-selected="onNetworkSelected_">
diff --git a/chrome/browser/resources/settings/internet_page/network_proxy.html b/chrome/browser/resources/settings/internet_page/network_proxy.html index 1941afc..2098466 100644 --- a/chrome/browser/resources/settings/internet_page/network_proxy.html +++ b/chrome/browser/resources/settings/internet_page/network_proxy.html
@@ -1,4 +1,5 @@ <link rel="import" href="chrome://resources/cr_elements/cr_dialog/cr_dialog.html"> +<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> <link rel="import" href="chrome://resources/cr_elements/network/cr_onc_types.html"> <link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_network_behavior.html"> <link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_network_indicator.html"> @@ -21,7 +22,7 @@ <dom-module id="network-proxy"> <template> - <style include="internet-shared md-select"> + <style include="internet-shared md-select cr-hidden-style"> cr-policy-network-indicator { -webkit-margin-end: 10px; }
diff --git a/chrome/browser/resources/settings/internet_page/network_proxy_exclusions.html b/chrome/browser/resources/settings/internet_page/network_proxy_exclusions.html index 0421eb4f..955a869 100644 --- a/chrome/browser/resources/settings/internet_page/network_proxy_exclusions.html +++ b/chrome/browser/resources/settings/internet_page/network_proxy_exclusions.html
@@ -1,10 +1,11 @@ +<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> <link rel="import" href="chrome://resources/cr_elements/icons.html"> <link rel="import" href="chrome://resources/html/polymer.html"> <link rel="import" href="internet_shared_css.html"> <dom-module id="network-proxy-exclusions"> <template> - <style include="internet-shared"> + <style include="internet-shared cr-hidden-style"> iron-icon { @apply(--settings-actionable); margin: 5px;
diff --git a/chrome/browser/resources/settings/people_page/people_page.html b/chrome/browser/resources/settings/people_page/people_page.html index f149d72..f2d4f48c 100644 --- a/chrome/browser/resources/settings/people_page/people_page.html +++ b/chrome/browser/resources/settings/people_page/people_page.html
@@ -124,21 +124,19 @@ </div> <if expr="not chromeos"> <template is="dom-if" if="[[showSignin_(syncStatus)]]"> - <span class="secondary-action"> - <paper-button class="primary-button" on-tap="onSigninTap_" - disabled="[[syncStatus.setupInProgress]]"> - $i18n{syncSignin} - </paper-button> - </span> + <div class="separator"></div> + <paper-button class="primary-button" on-tap="onSigninTap_" + disabled="[[syncStatus.setupInProgress]]"> + $i18n{syncSignin} + </paper-button> </template> <template is="dom-if" if="[[syncStatus.signedIn]]"> - <span class="secondary-action"> - <paper-button id="disconnectButton" class="secondary-button" - on-tap="onDisconnectTap_" - disabled="[[syncStatus.setupInProgress]]"> - $i18n{syncDisconnect} - </paper-button> - </span> + <div class="separator"></div> + <paper-button id="disconnectButton" class="secondary-button" + on-tap="onDisconnectTap_" + disabled="[[syncStatus.setupInProgress]]"> + $i18n{syncDisconnect} + </paper-button> </template> </if> </template>
diff --git a/chrome/browser/resources/settings/people_page/user_list.html b/chrome/browser/resources/settings/people_page/user_list.html index c978833..ba5a87a4 100644 --- a/chrome/browser/resources/settings/people_page/user_list.html +++ b/chrome/browser/resources/settings/people_page/user_list.html
@@ -2,14 +2,14 @@ <link rel="import" href="chrome://resources/cr_elements/icons.html"> <link rel="import" href="chrome://resources/html/i18n_behavior.html"> <link rel="import" href="chrome://resources/html/polymer.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button.html"> <link rel="import" href="../route.html"> <link rel="import" href="../settings_shared_css.html"> <dom-module id="settings-user-list"> <template> - <style include="settings-shared"> + <style include="settings-shared iron-flex"> .user-list { /* 4 users (the extra 1px is to account for the border-bottom) */ max-height: calc(4 * (var(--settings-row-two-line-min-height) + 1px));
diff --git a/chrome/browser/resources/settings/people_page/users_page.html b/chrome/browser/resources/settings/people_page/users_page.html index 318d936..f9edb06 100644 --- a/chrome/browser/resources/settings/people_page/users_page.html +++ b/chrome/browser/resources/settings/people_page/users_page.html
@@ -1,7 +1,6 @@ <link rel="import" href="chrome://resources/html/action_link.html"> <link rel="import" href="chrome://resources/html/action_link_css.html"> <link rel="import" href="chrome://resources/html/polymer.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html"> <link rel="import" href="../controls/settings_toggle_button.html"> <link rel="import" href="../settings_shared_css.html"> @@ -19,6 +18,10 @@ /* Add user button must be lined up with the start of users' names. */ -webkit-margin-start: var(--settings-box-row-indent); } + + .block { + display: block; + } </style> <template is="dom-if" if="[[isWhitelistManaged_]]"> <div class="settings-box">$i18n{settingsManagedLabel}</div>
diff --git a/chrome/browser/resources/settings/settings_shared_css.html b/chrome/browser/resources/settings/settings_shared_css.html index 6d4392c..646d17bc 100644 --- a/chrome/browser/resources/settings/settings_shared_css.html +++ b/chrome/browser/resources/settings/settings_shared_css.html
@@ -320,8 +320,8 @@ outline: none; } - /* The secondary-action wraps a clickable sub-area of a .settings-box. - * An example is the |sign out| button on the People settings. + /* TODO(dschuyler): replace with .separator. + * The secondary-action wraps a clickable sub-area of a .settings-box. * Here is an example with and without a secondary action box: * * +-------------------------------------------------------+ @@ -348,6 +348,24 @@ border-top: var(--settings-separator-line); } + /* The separator a vertical line like a horizontal rule <hr> tag, but goes + * the other way. An example is near the |sign out| button on the People + * settings. */ + :-webkit-any(.settings-box, .list-item) .separator { + -webkit-border-start: var(--settings-separator-line); + -webkit-margin-end: var(--settings-box-row-padding); + -webkit-margin-start: var(--settings-box-row-padding); + flex-shrink: 0; + --settings-separator-gaps: 9px; + height: calc(var(--settings-row-min-height) - + var(--settings-separator-gaps)); + } + + :-webkit-any(.settings-box, .list-item).two-line .separator { + height: calc(var(--settings-row-two-line-min-height) - + 2 * var(--settings-separator-gaps)); + } + .settings-checkbox-spacer { -webkit-margin-start: calc( var(--checkbox-margin-start) +
diff --git a/chrome/browser/resources/ssl/ssl_error_assistant/ssl_error_assistant.asciipb b/chrome/browser/resources/ssl/ssl_error_assistant/ssl_error_assistant.asciipb index 0edfb465..27d6f0e 100644 --- a/chrome/browser/resources/ssl/ssl_error_assistant/ssl_error_assistant.asciipb +++ b/chrome/browser/resources/ssl/ssl_error_assistant/ssl_error_assistant.asciipb
@@ -6,7 +6,7 @@ # certificates. See chrome/browser/ssl/ssl_error_assistant.proto for the full # format. -version_id: 2 +version_id: 3 # https://captive-portal.badssl.com leaf. # This is a test certificate, always keep it at the top. @@ -54,6 +54,12 @@ sha256_hash: "sha256/IJPCDSE5tM9H3nuD5m6RU2i9KDdPXVn4qmC/ULlcZzc=" } +# hotelwifi.com +# https://crt.sh/?q=f9dca04c4ac67f346c505c6a9bdc931c5272547dbb512a138c4459a903b023c7 +captive_portal_cert { + sha256_hash: "sha256/0Gy8RMdbxHNWR2GQJ62QKDXORYf5JmMmnr1FJFPYpzM=" +} + # Innflux # Issuer: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., # OU=http://certs.godaddy.com/repository/, @@ -86,6 +92,12 @@ sha256_hash: "sha256/UwOkRGMlP0K/mKNJdpQ0sTg2ean9Tje8UTOvFYzt1GE=" } +# mobicare.com.br +# https://crt.sh/?q=bf9a13fc64b18221a6f0360e95ba54714d8ebf70a0291b7ea5f357be30436a7a +captive_portal_cert { + sha256_hash: "sha256/w7KUXE4/BAo1YVZdO3mBsrMpu4IQuN0mhUXUI//agVU=" +} + # ombord.info # https://crt.sh/?q=f849586eedb4c754fc53e4352948d36097ae7fec50abc5f93c08239719c8184a captive_portal_cert { @@ -101,6 +113,13 @@ sha256_hash: "sha256/AUSXlKDCf1X30WhWeAWbjToABfBkJrKWPL6KwEi5VH0=" } +# virginwifi.io +# Issuer: O=GeoTrust Inc., CN=RapidSSL SHA256 CA - G2, C=US +# Subject: CN=*.virginwifi.io +captive_portal_cert { + sha256_hash: "sha256/zSyVjjFJMIeXK0ktVTIjewwr6U5OePRqyY/nEXTI4P8=" +} + # wifipass.org # https://crt.sh/?q=1cce212718a7cf65ce33acde91b5bc66863d14ae259fbaf841f83bf89748f5fd captive_portal_cert {
diff --git a/chrome/browser/safe_browsing/chrome_cleaner/OWNERS b/chrome/browser/safe_browsing/chrome_cleaner/OWNERS new file mode 100644 index 0000000..93d2314 --- /dev/null +++ b/chrome/browser/safe_browsing/chrome_cleaner/OWNERS
@@ -0,0 +1,2 @@ +csharp@chromium.org +robertshield@chromium.org
diff --git a/chrome/browser/safe_browsing/srt_chrome_prompt_impl.cc b/chrome/browser/safe_browsing/chrome_cleaner/srt_chrome_prompt_impl.cc similarity index 93% rename from chrome/browser/safe_browsing/srt_chrome_prompt_impl.cc rename to chrome/browser/safe_browsing/chrome_cleaner/srt_chrome_prompt_impl.cc index dae99a6..002d24f 100644 --- a/chrome/browser/safe_browsing/srt_chrome_prompt_impl.cc +++ b/chrome/browser/safe_browsing/chrome_cleaner/srt_chrome_prompt_impl.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/safe_browsing/srt_chrome_prompt_impl.h" +#include "chrome/browser/safe_browsing/chrome_cleaner/srt_chrome_prompt_impl.h" #include "base/logging.h"
diff --git a/chrome/browser/safe_browsing/srt_chrome_prompt_impl.h b/chrome/browser/safe_browsing/chrome_cleaner/srt_chrome_prompt_impl.h similarity index 81% rename from chrome/browser/safe_browsing/srt_chrome_prompt_impl.h rename to chrome/browser/safe_browsing/chrome_cleaner/srt_chrome_prompt_impl.h index 6027044b..2546a4c 100644 --- a/chrome/browser/safe_browsing/srt_chrome_prompt_impl.h +++ b/chrome/browser/safe_browsing/chrome_cleaner/srt_chrome_prompt_impl.h
@@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_SAFE_BROWSING_SRT_CHROME_PROMPT_IMPL_H_ -#define CHROME_BROWSER_SAFE_BROWSING_SRT_CHROME_PROMPT_IMPL_H_ +#ifndef CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_SRT_CHROME_PROMPT_IMPL_H_ +#define CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_SRT_CHROME_PROMPT_IMPL_H_ #include "base/callback.h" #include "base/macros.h" @@ -33,4 +33,4 @@ } // namespace safe_browsing -#endif // CHROME_BROWSER_SAFE_BROWSING_SRT_CHROME_PROMPT_IMPL_H_ +#endif // CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_SRT_CHROME_PROMPT_IMPL_H_
diff --git a/chrome/browser/safe_browsing/srt_client_info_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.cc similarity index 94% rename from chrome/browser/safe_browsing/srt_client_info_win.cc rename to chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.cc index 7506dac..2e4b4ae 100644 --- a/chrome/browser/safe_browsing/srt_client_info_win.cc +++ b/chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.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/safe_browsing/srt_client_info_win.h" +#include "chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.h" #include "base/logging.h" #include "chrome/browser/profiles/profile.h"
diff --git a/chrome/browser/safe_browsing/srt_client_info_win.h b/chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.h similarity index 73% rename from chrome/browser/safe_browsing/srt_client_info_win.h rename to chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.h index 608ee96..ff02bdd 100644 --- a/chrome/browser/safe_browsing/srt_client_info_win.h +++ b/chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.h
@@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_SAFE_BROWSING_SRT_CLIENT_INFO_WIN_H_ -#define CHROME_BROWSER_SAFE_BROWSING_SRT_CLIENT_INFO_WIN_H_ +#ifndef CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_SRT_CLIENT_INFO_WIN_H_ +#define CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_SRT_CLIENT_INFO_WIN_H_ namespace safe_browsing { @@ -18,4 +18,4 @@ } // namespace safe_browsing -#endif // CHROME_BROWSER_SAFE_BROWSING_SRT_CLIENT_INFO_WIN_H_ +#endif // CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_SRT_CLIENT_INFO_WIN_H_
diff --git a/chrome/browser/safe_browsing/srt_fetcher_browsertest_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_browsertest_win.cc similarity index 99% rename from chrome/browser/safe_browsing/srt_fetcher_browsertest_win.cc rename to chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_browsertest_win.cc index adcde7569..80a8439 100644 --- a/chrome/browser/safe_browsing/srt_fetcher_browsertest_win.cc +++ b/chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_browsertest_win.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/safe_browsing/srt_fetcher_win.h" +#include "chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.h" #include <initializer_list> #include <set> @@ -31,8 +31,8 @@ #include "chrome/browser/lifetime/keep_alive_types.h" #include "chrome/browser/lifetime/scoped_keep_alive.h" #include "chrome/browser/profiles/profile.h" -#include "chrome/browser/safe_browsing/srt_chrome_prompt_impl.h" -#include "chrome/browser/safe_browsing/srt_client_info_win.h" +#include "chrome/browser/safe_browsing/chrome_cleaner/srt_chrome_prompt_impl.h" +#include "chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/test/test_browser_dialog.h"
diff --git a/chrome/browser/safe_browsing/srt_fetcher_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.cc similarity index 99% rename from chrome/browser/safe_browsing/srt_fetcher_win.cc rename to chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.cc index b786599..02dbb66d 100644 --- a/chrome/browser/safe_browsing/srt_fetcher_win.cc +++ b/chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.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/safe_browsing/srt_fetcher_win.h" +#include "chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.h" #include <stdint.h> @@ -35,9 +35,9 @@ #include "chrome/browser/metrics/chrome_metrics_service_accessor.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_io_data.h" -#include "chrome/browser/safe_browsing/srt_chrome_prompt_impl.h" -#include "chrome/browser/safe_browsing/srt_client_info_win.h" -#include "chrome/browser/safe_browsing/srt_global_error_win.h" +#include "chrome/browser/safe_browsing/chrome_cleaner/srt_chrome_prompt_impl.h" +#include "chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.h" +#include "chrome/browser/safe_browsing/chrome_cleaner/srt_global_error_win.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_list_observer.h"
diff --git a/chrome/browser/safe_browsing/srt_fetcher_win.h b/chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.h similarity index 96% rename from chrome/browser/safe_browsing/srt_fetcher_win.h rename to chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.h index 26400ce..1a0e53da 100644 --- a/chrome/browser/safe_browsing/srt_fetcher_win.h +++ b/chrome/browser/safe_browsing/chrome_cleaner/srt_fetcher_win.h
@@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_SAFE_BROWSING_SRT_FETCHER_WIN_H_ -#define CHROME_BROWSER_SAFE_BROWSING_SRT_FETCHER_WIN_H_ +#ifndef CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_SRT_FETCHER_WIN_H_ +#define CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_SRT_FETCHER_WIN_H_ #include <limits.h> #include <stdint.h> @@ -157,4 +157,4 @@ } // namespace safe_browsing -#endif // CHROME_BROWSER_SAFE_BROWSING_SRT_FETCHER_WIN_H_ +#endif // CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_SRT_FETCHER_WIN_H_
diff --git a/chrome/browser/safe_browsing/srt_field_trial_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.cc similarity index 96% rename from chrome/browser/safe_browsing/srt_field_trial_win.cc rename to chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.cc index e53c7aa..184697d 100644 --- a/chrome/browser/safe_browsing/srt_field_trial_win.cc +++ b/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.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/safe_browsing/srt_field_trial_win.h" +#include "chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.h" #include "base/metrics/field_trial.h" #include "base/metrics/histogram_macros.h"
diff --git a/chrome/browser/safe_browsing/srt_field_trial_win.h b/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.h similarity index 85% rename from chrome/browser/safe_browsing/srt_field_trial_win.h rename to chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.h index a1e19515..f979098 100644 --- a/chrome/browser/safe_browsing/srt_field_trial_win.h +++ b/chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.h
@@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_SAFE_BROWSING_SRT_FIELD_TRIAL_WIN_H_ -#define CHROME_BROWSER_SAFE_BROWSING_SRT_FIELD_TRIAL_WIN_H_ +#ifndef CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_SRT_FIELD_TRIAL_WIN_H_ +#define CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_SRT_FIELD_TRIAL_WIN_H_ #include <string> @@ -46,4 +46,4 @@ } // namespace safe_browsing -#endif // CHROME_BROWSER_SAFE_BROWSING_SRT_FIELD_TRIAL_WIN_H_ +#endif // CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_SRT_FIELD_TRIAL_WIN_H_
diff --git a/chrome/browser/safe_browsing/srt_global_error_win.cc b/chrome/browser/safe_browsing/chrome_cleaner/srt_global_error_win.cc similarity index 97% rename from chrome/browser/safe_browsing/srt_global_error_win.cc rename to chrome/browser/safe_browsing/chrome_cleaner/srt_global_error_win.cc index ae8dedd..cbaa65c 100644 --- a/chrome/browser/safe_browsing/srt_global_error_win.cc +++ b/chrome/browser/safe_browsing/chrome_cleaner/srt_global_error_win.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/safe_browsing/srt_global_error_win.h" +#include "chrome/browser/safe_browsing/chrome_cleaner/srt_global_error_win.h" #include "base/base_paths.h" #include "base/bind.h" @@ -18,8 +18,8 @@ #include "chrome/app/chrome_command_ids.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/metrics/chrome_metrics_service_accessor.h" -#include "chrome/browser/safe_browsing/srt_client_info_win.h" -#include "chrome/browser/safe_browsing/srt_field_trial_win.h" +#include "chrome/browser/safe_browsing/chrome_cleaner/srt_client_info_win.h" +#include "chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/global_error/global_error_service.h"
diff --git a/chrome/browser/safe_browsing/srt_global_error_win.h b/chrome/browser/safe_browsing/chrome_cleaner/srt_global_error_win.h similarity index 90% rename from chrome/browser/safe_browsing/srt_global_error_win.h rename to chrome/browser/safe_browsing/chrome_cleaner/srt_global_error_win.h index 5505998..aa6c13c 100644 --- a/chrome/browser/safe_browsing/srt_global_error_win.h +++ b/chrome/browser/safe_browsing/chrome_cleaner/srt_global_error_win.h
@@ -2,14 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_SAFE_BROWSING_SRT_GLOBAL_ERROR_WIN_H_ -#define CHROME_BROWSER_SAFE_BROWSING_SRT_GLOBAL_ERROR_WIN_H_ +#ifndef CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_SRT_GLOBAL_ERROR_WIN_H_ +#define CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_SRT_GLOBAL_ERROR_WIN_H_ #include <vector> #include "base/files/file_path.h" #include "base/macros.h" -#include "chrome/browser/safe_browsing/srt_field_trial_win.h" +#include "chrome/browser/safe_browsing/chrome_cleaner/srt_field_trial_win.h" #include "chrome/browser/ui/global_error/global_error.h" class GlobalErrorService; @@ -83,4 +83,4 @@ } // namespace safe_browsing -#endif // CHROME_BROWSER_SAFE_BROWSING_SRT_GLOBAL_ERROR_WIN_H_ +#endif // CHROME_BROWSER_SAFE_BROWSING_CHROME_CLEANER_SRT_GLOBAL_ERROR_WIN_H_
diff --git a/chrome/browser/safe_browsing/download_protection_service.cc b/chrome/browser/safe_browsing/download_protection_service.cc index f3ae51da..021bd1c 100644 --- a/chrome/browser/safe_browsing/download_protection_service.cc +++ b/chrome/browser/safe_browsing/download_protection_service.cc
@@ -201,9 +201,6 @@ extended_reporting_level_ = profile ? GetExtendedReportingLevel(*profile->GetPrefs()) : SBER_LEVEL_OFF; - download_attribution_enabled_ = service_->navigation_observer_manager() && - base::FeatureList::IsEnabled( - SafeBrowsingNavigationObserverManager::kDownloadAttribution); } // Implements DownloadItem::Observer. @@ -256,14 +253,13 @@ UpdateDownloadCheckStats(dangerous_type_); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, - base::BindOnce(&DownloadUrlSBClient::ReportMalware, this, - threat_type)); - } else if (download_attribution_enabled_) { - // Identify download referrer chain, which will be used in - // ClientDownloadRequest. - BrowserThread::PostTask( - BrowserThread::UI, FROM_HERE, - base::BindOnce(&DownloadUrlSBClient::IdentifyReferrerChain, this)); + base::Bind(&DownloadUrlSBClient::ReportMalware, this, threat_type)); + } else { + // Identify download referrer chain, which will be used in + // ClientDownloadRequest. + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&DownloadUrlSBClient::IdentifyReferrerChain, this)); } BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::BindOnce(callback_, result)); @@ -323,7 +319,6 @@ DownloadProtectionService::CheckDownloadCallback callback_; scoped_refptr<SafeBrowsingUIManager> ui_manager_; base::TimeTicks start_time_; - bool download_attribution_enabled_; const SBStatsType total_type_; const SBStatsType dangerous_type_; ExtendedReportingLevel extended_reporting_level_; @@ -1043,12 +1038,10 @@ ReferrerChainData* referrer_chain_data = static_cast<ReferrerChainData*>( item_->GetUserData(kDownloadReferrerChainDataKey)); - if (referrer_chain_data) { - request.set_download_attribution_finch_enabled(true); - if (!referrer_chain_data->GetReferrerChain()->empty()) { - request.mutable_referrer_chain()->Swap( - referrer_chain_data->GetReferrerChain()); - } + if (referrer_chain_data && + !referrer_chain_data->GetReferrerChain()->empty()) { + request.mutable_referrer_chain()->Swap( + referrer_chain_data->GetReferrerChain()); } if (archive_is_valid_ != ArchiveValid::UNSET) @@ -1666,7 +1659,8 @@ DownloadProtectionService::DownloadProtectionService( SafeBrowsingService* sb_service) - : request_context_getter_(sb_service ? sb_service->url_request_context() + : navigation_observer_manager_(nullptr), + request_context_getter_(sb_service ? sb_service->url_request_context() : nullptr), enabled_(false), binary_feature_extractor_(new BinaryFeatureExtractor()), @@ -1949,6 +1943,11 @@ std::unique_ptr<ReferrerChain> DownloadProtectionService::IdentifyReferrerChain( const GURL& download_url, content::WebContents* web_contents) { + // If navigation_observer_manager_ is null, return immediately. This could + // happen in tests. + if (!navigation_observer_manager_) + return nullptr; + std::unique_ptr<ReferrerChain> referrer_chain = base::MakeUnique<ReferrerChain>(); int download_tab_id = SessionTabHelper::IdForTab(web_contents); @@ -1987,11 +1986,8 @@ int tab_id, bool has_user_gesture, ClientDownloadRequest* out_request) { - if (!base::FeatureList::IsEnabled( - SafeBrowsingNavigationObserverManager::kDownloadAttribution) || - !navigation_observer_manager_) { + if (!navigation_observer_manager_) return; - } UMA_HISTOGRAM_BOOLEAN( "SafeBrowsing.ReferrerHasInvalidTabID.DownloadAttribution", @@ -2007,7 +2003,6 @@ UMA_HISTOGRAM_ENUMERATION( "SafeBrowsing.ReferrerAttributionResult.PPAPIDownloadAttribution", result, SafeBrowsingNavigationObserverManager::ATTRIBUTION_FAILURE_TYPE_MAX); - out_request->set_download_attribution_finch_enabled(true); } } // namespace safe_browsing
diff --git a/chrome/browser/safe_browsing/safe_browsing_navigation_observer.cc b/chrome/browser/safe_browsing/safe_browsing_navigation_observer.cc index d625c34..776d510 100644 --- a/chrome/browser/safe_browsing/safe_browsing_navigation_observer.cc +++ b/chrome/browser/safe_browsing/safe_browsing_navigation_observer.cc
@@ -76,7 +76,7 @@ return; if (safe_browsing::SafeBrowsingNavigationObserverManager::IsEnabledAndReady( - Profile::FromBrowserContext(web_contents->GetBrowserContext()))) { + Profile::FromBrowserContext(web_contents->GetBrowserContext()))) { web_contents->SetUserData( kWebContentsUserDataKey, base::MakeUnique<SafeBrowsingNavigationObserver>(
diff --git a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc index 1c65a29..8d3cc5484 100644 --- a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc +++ b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_browsertest.cc
@@ -163,7 +163,9 @@ SBNavigationObserverBrowserTest() {} void SetUpOnMainThread() override { - // Disable Safe Browsing service since it is irrelevant to this test. + // Disable Safe Browsing service so we can directly control when + // SafeBrowsingNavigationObserverManager and SafeBrowsingNavigationObserver + // are instantiated. browser()->profile()->GetPrefs()->SetBoolean(prefs::kSafeBrowsingEnabled, false); ASSERT_TRUE(embedded_test_server()->Start());
diff --git a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.cc b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.cc index 64de674f..3db1e17 100644 --- a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.cc +++ b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.cc
@@ -195,9 +195,6 @@ // -----------------SafeBrowsingNavigationObserverManager----------- // static -const base::Feature SafeBrowsingNavigationObserverManager::kDownloadAttribution{ - "DownloadAttribution", base::FEATURE_ENABLED_BY_DEFAULT}; -// static bool SafeBrowsingNavigationObserverManager::IsUserGestureExpired( const base::Time& timestamp) { return IsEventExpired(timestamp, kUserGestureTTLInSecond); @@ -216,11 +213,10 @@ // static bool SafeBrowsingNavigationObserverManager::IsEnabledAndReady( Profile* profile) { - return base::FeatureList::IsEnabled( - SafeBrowsingNavigationObserverManager::kDownloadAttribution) && - profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled) && - g_browser_process->safe_browsing_service() && - g_browser_process->safe_browsing_service()->navigation_observer_manager(); + return profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled) && + g_browser_process->safe_browsing_service() && + g_browser_process->safe_browsing_service() + ->navigation_observer_manager(); } SafeBrowsingNavigationObserverManager::SafeBrowsingNavigationObserverManager()
diff --git a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h index d07af76..7d04167 100644 --- a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h +++ b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h
@@ -95,8 +95,6 @@ class SafeBrowsingNavigationObserverManager : public base::RefCountedThreadSafe<SafeBrowsingNavigationObserverManager> { public: - static const base::Feature kDownloadAttribution; - // For UMA histogram counting. Do NOT change order. enum AttributionResult { SUCCESS = 1, // Identified referrer chain is not empty. @@ -120,8 +118,8 @@ static GURL ClearEmptyRef(const GURL& url); // Checks if we should enable observing navigations for safe browsing purpose. - // Return true if the safe browsing service and the |kDownloadAttribution| - // feature are both enabled, and safe browsing service is initialized. + // Return true if the safe browsing safe browsing service is enabled and + // initialized. static bool IsEnabledAndReady(Profile* profile); SafeBrowsingNavigationObserverManager();
diff --git a/chrome/browser/safe_browsing/safe_browsing_service.cc b/chrome/browser/safe_browsing/safe_browsing_service.cc index f72d6ee..678692f5 100644 --- a/chrome/browser/safe_browsing/safe_browsing_service.cc +++ b/chrome/browser/safe_browsing/safe_browsing_service.cc
@@ -324,10 +324,7 @@ database_manager_ = CreateDatabaseManager(); } - if (base::FeatureList::IsEnabled( - SafeBrowsingNavigationObserverManager::kDownloadAttribution)) { - navigation_observer_manager_ = new SafeBrowsingNavigationObserverManager(); - } + navigation_observer_manager_ = new SafeBrowsingNavigationObserverManager(); services_delegate_->Initialize(v4_enabled_); services_delegate_->InitializeCsdService(url_request_context_getter_.get());
diff --git a/chrome/browser/task_manager/web_contents_tags.cc b/chrome/browser/task_manager/web_contents_tags.cc index a9c16fd..a2b61684 100644 --- a/chrome/browser/task_manager/web_contents_tags.cc +++ b/chrome/browser/task_manager/web_contents_tags.cc
@@ -7,6 +7,13 @@ #include <memory> #include "build/build_config.h" +#include "components/guest_view/browser/guest_view_base.h" +#include "content/public/browser/web_contents.h" +#include "extensions/browser/view_type_utils.h" +#include "extensions/features/features.h" +#include "printing/features/features.h" + +#if !defined(OS_ANDROID) #include "chrome/browser/task_manager/providers/web_contents/background_contents_tag.h" #include "chrome/browser/task_manager/providers/web_contents/devtools_tag.h" #include "chrome/browser/task_manager/providers/web_contents/extension_tag.h" @@ -15,11 +22,7 @@ #include "chrome/browser/task_manager/providers/web_contents/printing_tag.h" #include "chrome/browser/task_manager/providers/web_contents/tab_contents_tag.h" #include "chrome/browser/task_manager/providers/web_contents/web_contents_tags_manager.h" -#include "components/guest_view/browser/guest_view_base.h" -#include "content/public/browser/web_contents.h" -#include "extensions/browser/view_type_utils.h" -#include "extensions/features/features.h" -#include "printing/features/features.h" +#endif // !defined(OS_ANDROID) #if BUILDFLAG(ENABLE_EXTENSIONS) #include "extensions/browser/process_manager.h"
diff --git a/chrome/browser/ui/app_list/app_list_controller_browsertest.cc b/chrome/browser/ui/app_list/app_list_controller_browsertest.cc index bc5ddf4..8d9f864 100644 --- a/chrome/browser/ui/app_list/app_list_controller_browsertest.cc +++ b/chrome/browser/ui/app_list/app_list_controller_browsertest.cc
@@ -145,7 +145,7 @@ // Ensure a search finds the extension. EXPECT_FALSE(observed_result_); - model->search_box()->SetText(base::ASCIIToUTF16("minimal")); + model->search_box()->Update(base::ASCIIToUTF16("minimal"), false); EXPECT_TRUE(observed_result_); // Ensure the UI is updated. This is via PostTask in views.
diff --git a/chrome/browser/ui/app_list/app_list_view_delegate.cc b/chrome/browser/ui/app_list/app_list_view_delegate.cc index 00f6630..27db6fd 100644 --- a/chrome/browser/ui/app_list/app_list_view_delegate.cc +++ b/chrome/browser/ui/app_list/app_list_view_delegate.cc
@@ -108,7 +108,6 @@ : controller_(controller), profile_(NULL), model_(NULL), - is_voice_query_(false), template_url_service_observer_(this) { CHECK(controller_); speech_ui_.reset(new app_list::SpeechUIModel); @@ -199,7 +198,7 @@ } // Clear search query. - model_->search_box()->SetText(base::string16()); + model_->search_box()->Update(base::string16(), false); } void AppListViewDelegate::SetUpSearchUI() { @@ -277,7 +276,7 @@ void AppListViewDelegate::StartSearch() { if (search_controller_) { - search_controller_->Start(is_voice_query_); + search_controller_->Start(); controller_->OnSearchStarted(); } if (search_answer_delegate_) @@ -296,7 +295,6 @@ if (auto_launch) base::RecordAction(base::UserMetricsAction("AppList_AutoLaunched")); search_controller_->OpenResult(result, event_flags); - is_voice_query_ = false; } void AppListViewDelegate::InvokeSearchResultAction( @@ -311,10 +309,8 @@ } void AppListViewDelegate::AutoLaunchCanceled() { - if (is_voice_query_) { + if (model_ && model_->search_box()->is_voice_query()) { base::RecordAction(base::UserMetricsAction("AppList_AutoLaunchCanceled")); - // Cancelling the auto launch means we are no longer in a voice query. - is_voice_query_ = false; } auto_launch_timeout_ = base::TimeDelta(); } @@ -421,8 +417,7 @@ if (is_final) { auto_launch_timeout_ = base::TimeDelta::FromMilliseconds( kAutoLaunchDefaultTimeoutMilliSec); - is_voice_query_ = true; - model_->search_box()->SetText(result); + model_->search_box()->Update(result, true); } } @@ -525,13 +520,11 @@ TemplateURLServiceFactory::GetForProfile(profile_); const TemplateURL* default_provider = template_url_service->GetDefaultSearchProvider(); - bool is_google = + const bool is_google = default_provider->GetEngineType( - template_url_service->search_terms_data()) == - SEARCH_ENGINE_GOOGLE; + template_url_service->search_terms_data()) == SEARCH_ENGINE_GOOGLE; model_->SetSearchEngineIsGoogle(is_google); - search_answer_delegate_->Update(); app_list::StartPageService* start_page_service = app_list::StartPageService::Get(profile_);
diff --git a/chrome/browser/ui/app_list/app_list_view_delegate.h b/chrome/browser/ui/app_list/app_list_view_delegate.h index 5dcdad3..923bf6b 100644 --- a/chrome/browser/ui/app_list/app_list_view_delegate.h +++ b/chrome/browser/ui/app_list/app_list_view_delegate.h
@@ -131,8 +131,6 @@ launcher_page_event_dispatcher_; base::TimeDelta auto_launch_timeout_; - // Determines whether the current search was initiated by speech. - bool is_voice_query_; std::unique_ptr<AppSyncUIStateWatcher> app_sync_ui_state_watcher_;
diff --git a/chrome/browser/ui/app_list/search_answer_web_contents_delegate.cc b/chrome/browser/ui/app_list/search_answer_web_contents_delegate.cc index 08af888..f4177fd 100644 --- a/chrome/browser/ui/app_list/search_answer_web_contents_delegate.cc +++ b/chrome/browser/ui/app_list/search_answer_web_contents_delegate.cc
@@ -45,9 +45,13 @@ web_contents_->SetDelegate(this); web_view_->set_owned_by_client(); web_view_->SetWebContents(web_contents_.get()); + + model->AddObserver(this); } -SearchAnswerWebContentsDelegate::~SearchAnswerWebContentsDelegate() {} +SearchAnswerWebContentsDelegate::~SearchAnswerWebContentsDelegate() { + model_->RemoveObserver(this); +} views::View* SearchAnswerWebContentsDelegate::web_view() { return web_view_.get(); @@ -62,6 +66,12 @@ model_->SetSearchAnswerAvailable(false); current_request_url_ = GURL(); + if (model_->search_box()->is_voice_query()) { + // No need to send a server request and show a card because launcher + // automatically closes upon voice queries. + return; + } + if (!model_->search_engine_is_google()) return; @@ -153,4 +163,9 @@ model_->SetSearchAnswerAvailable(true); } +void SearchAnswerWebContentsDelegate::OnSearchEngineIsGoogleChanged( + bool is_google) { + Update(); +} + } // namespace app_list
diff --git a/chrome/browser/ui/app_list/search_answer_web_contents_delegate.h b/chrome/browser/ui/app_list/search_answer_web_contents_delegate.h index ff1152e2..75ff668 100644 --- a/chrome/browser/ui/app_list/search_answer_web_contents_delegate.h +++ b/chrome/browser/ui/app_list/search_answer_web_contents_delegate.h
@@ -10,6 +10,7 @@ #include "content/public/browser/web_contents_delegate.h" #include "content/public/browser/web_contents_observer.h" +#include "ui/app_list/app_list_model_observer.h" #include "url/gurl.h" class Profile; @@ -27,7 +28,8 @@ // Manages the web contents for the search answer web view. class SearchAnswerWebContentsDelegate : public content::WebContentsDelegate, - public content::WebContentsObserver { + public content::WebContentsObserver, + public AppListModelObserver { public: SearchAnswerWebContentsDelegate(Profile* profile, app_list::AppListModel* model); @@ -58,6 +60,9 @@ content::NavigationHandle* navigation_handle) override; void DidStopLoading() override; + // AppListModelObserver overrides: + void OnSearchEngineIsGoogleChanged(bool is_google) override; + private: // Unowned pointer to the associated profile. Profile* const profile_;
diff --git a/chrome/browser/ui/browser_dialogs.h b/chrome/browser/ui/browser_dialogs.h index dd98d35..915860b 100644 --- a/chrome/browser/ui/browser_dialogs.h +++ b/chrome/browser/ui/browser_dialogs.h
@@ -174,6 +174,11 @@ payments::PaymentRequestDialog* CreatePaymentRequestDialog( payments::PaymentRequest* request); +// Used to return the target the user picked or nullopt if the user cancelled +// the share. +using WebShareTargetPickerCallback = + base::OnceCallback<void(const base::Optional<std::string>&)>; + // Shows the dialog to choose a share target app. |targets| is a list of app // title and manifest URL pairs that will be shown in a list. If the user picks // a target, this calls |callback| with the manifest URL of the chosen target, @@ -181,7 +186,7 @@ void ShowWebShareTargetPickerDialog( gfx::NativeWindow parent_window, const std::vector<std::pair<base::string16, GURL>>& targets, - const base::Callback<void(base::Optional<std::string>)>& callback); + WebShareTargetPickerCallback callback); #if defined(OS_MACOSX)
diff --git a/chrome/browser/ui/views/webshare/webshare_target_picker_view.cc b/chrome/browser/ui/views/webshare/webshare_target_picker_view.cc index 97d6dea..79952a3 100644 --- a/chrome/browser/ui/views/webshare/webshare_target_picker_view.cc +++ b/chrome/browser/ui/views/webshare/webshare_target_picker_view.cc
@@ -66,9 +66,9 @@ void ShowWebShareTargetPickerDialog( gfx::NativeWindow parent_window, const std::vector<std::pair<base::string16, GURL>>& targets, - const base::Callback<void(base::Optional<std::string>)>& callback) { + chrome::WebShareTargetPickerCallback callback) { constrained_window::CreateBrowserModalDialogViews( - new WebShareTargetPickerView(targets, callback), parent_window) + new WebShareTargetPickerView(targets, std::move(callback)), parent_window) ->Show(); } @@ -76,10 +76,10 @@ WebShareTargetPickerView::WebShareTargetPickerView( const std::vector<std::pair<base::string16, GURL>>& targets, - const base::Callback<void(base::Optional<std::string>)>& close_callback) + chrome::WebShareTargetPickerCallback close_callback) : targets_(targets), table_model_(base::MakeUnique<TargetPickerTableModel>(&targets_)), - close_callback_(close_callback) { + close_callback_(std::move(close_callback)) { const int panel_margin = ChromeLayoutProvider::Get()->GetDistanceMetric( DISTANCE_PANEL_CONTENT_MARGIN); views::BoxLayout* layout = @@ -129,7 +129,7 @@ bool WebShareTargetPickerView::Cancel() { if (!close_callback_.is_null()) - close_callback_.Run(base::nullopt); + std::move(close_callback_).Run(base::nullopt); return true; } @@ -137,7 +137,8 @@ bool WebShareTargetPickerView::Accept() { if (!close_callback_.is_null()) { DCHECK(!table_->selection_model().empty()); - close_callback_.Run(targets_[table_->FirstSelectedRow()].second.spec()); + std::move(close_callback_) + .Run(targets_[table_->FirstSelectedRow()].second.spec()); } return true;
diff --git a/chrome/browser/ui/views/webshare/webshare_target_picker_view.h b/chrome/browser/ui/views/webshare/webshare_target_picker_view.h index 6e4db2e..836f13cd 100644 --- a/chrome/browser/ui/views/webshare/webshare_target_picker_view.h +++ b/chrome/browser/ui/views/webshare/webshare_target_picker_view.h
@@ -38,8 +38,7 @@ // the share. WebShareTargetPickerView( const std::vector<std::pair<base::string16, GURL>>& targets, - const base::Callback<void(base::Optional<std::string>)>& - close_callback); + chrome::WebShareTargetPickerCallback close_callback); ~WebShareTargetPickerView() override; // views::View overrides: @@ -68,7 +67,7 @@ const std::vector<std::pair<base::string16, GURL>> targets_; std::unique_ptr<TargetPickerTableModel> table_model_; - base::Callback<void(base::Optional<std::string>)> close_callback_; + chrome::WebShareTargetPickerCallback close_callback_; DISALLOW_COPY_AND_ASSIGN(WebShareTargetPickerView); };
diff --git a/chrome/browser/ui/views/webshare/webshare_target_picker_view_unittest.cc b/chrome/browser/ui/views/webshare/webshare_target_picker_view_unittest.cc index dc57d740..cb598db1 100644 --- a/chrome/browser/ui/views/webshare/webshare_target_picker_view_unittest.cc +++ b/chrome/browser/ui/views/webshare/webshare_target_picker_view_unittest.cc
@@ -61,8 +61,8 @@ // Creates the WebShareTargetPickerView (available as view()). void CreateView(const std::vector<std::pair<base::string16, GURL>>& targets) { view_ = new WebShareTargetPickerView( - targets, base::Bind(&WebShareTargetPickerViewTest::OnCallback, - base::Unretained(this))); + targets, base::BindOnce(&WebShareTargetPickerViewTest::OnCallback, + base::Unretained(this))); constrained_window::CreateBrowserModalDialogViews( view_, parent_widget_->GetNativeWindow()) ->Show(); @@ -83,7 +83,7 @@ const base::Optional<std::string>& result() { return result_; } private: - void OnCallback(base::Optional<std::string> result) { + void OnCallback(const base::Optional<std::string>& result) { result_ = result; if (quit_closure_) quit_closure_.Run();
diff --git a/chrome/browser/webshare/share_service_impl.cc b/chrome/browser/webshare/share_service_impl.cc index 4aca488..67e1353 100644 --- a/chrome/browser/webshare/share_service_impl.cc +++ b/chrome/browser/webshare/share_service_impl.cc
@@ -104,10 +104,10 @@ void ShareServiceImpl::ShowPickerDialog( const std::vector<std::pair<base::string16, GURL>>& targets, - const base::Callback<void(base::Optional<std::string>)>& callback) { -// TODO(mgiuca): Get the browser window as |parent_window|. + chrome::WebShareTargetPickerCallback callback) { + // TODO(mgiuca): Get the browser window as |parent_window|. chrome::ShowWebShareTargetPickerDialog(nullptr /* parent_window */, targets, - callback); + std::move(callback)); } Browser* ShareServiceImpl::GetBrowser() { @@ -186,9 +186,9 @@ ShowPickerDialog( sufficiently_engaged_targets, - base::Bind(&ShareServiceImpl::OnPickerClosed, weak_factory_.GetWeakPtr(), - base::Passed(&share_targets), title, text, share_url, - callback)); + base::BindOnce(&ShareServiceImpl::OnPickerClosed, + weak_factory_.GetWeakPtr(), base::Passed(&share_targets), + title, text, share_url, callback)); } void ShareServiceImpl::OnPickerClosed( @@ -197,7 +197,7 @@ const std::string& text, const GURL& share_url, const ShareCallback& callback, - base::Optional<std::string> result) { + const base::Optional<std::string>& result) { if (!result.has_value()) { callback.Run(blink::mojom::ShareError::CANCELED); return;
diff --git a/chrome/browser/webshare/share_service_impl.h b/chrome/browser/webshare/share_service_impl.h index 7fb160931..db0298c2 100644 --- a/chrome/browser/webshare/share_service_impl.h +++ b/chrome/browser/webshare/share_service_impl.h
@@ -57,7 +57,7 @@ // target, or is null if the user cancelled the share. Virtual for testing. virtual void ShowPickerDialog( const std::vector<std::pair<base::string16, GURL>>& targets, - const base::Callback<void(base::Optional<std::string>)>& callback); + chrome::WebShareTargetPickerCallback callback); // Opens a new tab and navigates to |target_url|. // Virtual for testing purposes. @@ -88,7 +88,7 @@ const std::string& text, const GURL& share_url, const ShareCallback& callback, - base::Optional<std::string> result); + const base::Optional<std::string>& result); base::WeakPtrFactory<ShareServiceImpl> weak_factory_;
diff --git a/chrome/browser/webshare/share_service_impl_unittest.cc b/chrome/browser/webshare/share_service_impl_unittest.cc index 916024c0..b123ba8 100644 --- a/chrome/browser/webshare/share_service_impl_unittest.cc +++ b/chrome/browser/webshare/share_service_impl_unittest.cc
@@ -85,18 +85,17 @@ return targets_in_picker_; } - const base::Callback<void(base::Optional<std::string>)>& picker_callback() { - return picker_callback_; + chrome::WebShareTargetPickerCallback picker_callback() { + return std::move(picker_callback_); } private: void ShowPickerDialog( const std::vector<std::pair<base::string16, GURL>>& targets, - const base::Callback<void(base::Optional<std::string>)>& callback) - override { + chrome::WebShareTargetPickerCallback callback) override { // Store the arguments passed to the picker dialog. targets_in_picker_ = targets; - picker_callback_ = callback; + picker_callback_ = std::move(callback); // Quit the test's run loop. It is the test's responsibility to call the // callback, to simulate the user's choice. @@ -126,7 +125,7 @@ std::vector<std::pair<base::string16, GURL>> targets_in_picker_; // The callback passed to ShowPickerDialog (which is supposed to be called // with the user's chosen result, or nullopt if cancelled). - base::Callback<void(base::Optional<std::string>)> picker_callback_; + chrome::WebShareTargetPickerCallback picker_callback_; }; class ShareServiceImplUnittest : public ChromeRenderViewHostTestHarness { @@ -343,13 +342,13 @@ make_pair(base::UTF8ToUTF16(kTargetName), GURL(kManifestUrlLow))}; EXPECT_EQ(kExpectedTargets, share_service_helper()->GetTargetsInPicker()); - const base::Callback<void(base::Optional<std::string>)> picker_callback = + chrome::WebShareTargetPickerCallback picker_callback = share_service_helper()->picker_callback(); DeleteShareService(); // Pick example-low.com. - picker_callback.Run(base::Optional<std::string>(kManifestUrlLow)); + std::move(picker_callback).Run(base::Optional<std::string>(kManifestUrlLow)); } // Replace various numbers of placeholders in various orders. Placeholders are
diff --git a/chrome/renderer/plugins/chrome_plugin_placeholder.cc b/chrome/renderer/plugins/chrome_plugin_placeholder.cc index 028a70bf..1ce8165 100644 --- a/chrome/renderer/plugins/chrome_plugin_placeholder.cc +++ b/chrome/renderer/plugins/chrome_plugin_placeholder.cc
@@ -233,6 +233,9 @@ if (!GetFrame() || !plugin()) return; + // Checking with GetFrame() is equivalent to checking render_frame(). + DCHECK(render_frame()); + ChromeViewHostMsg_GetPluginInfo_Output output; std::string mime_type(GetPluginParams().mime_type.Utf8()); render_frame()->Send(new ChromeViewHostMsg_GetPluginInfo( @@ -289,6 +292,8 @@ const blink::WebMouseEvent& event) { if (context_menu_request_id_) return; // Don't allow nested context menu requests. + if (!render_frame()) + return; content::ContextMenuParams params; @@ -361,6 +366,7 @@ } void ChromePluginPlaceholder::OnBlockedTinyContent() { + DCHECK(render_frame()); if (did_send_blocked_content_notification_) return;
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index 37bfed4..b40233b 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn
@@ -1512,7 +1512,7 @@ "../browser/renderer_context_menu/spelling_menu_observer_browsertest.cc", "../browser/renderer_host/render_process_host_chrome_browsertest.cc", "../browser/repost_form_warning_browsertest.cc", - "../browser/safe_browsing/srt_fetcher_browsertest_win.cc", + "../browser/safe_browsing/chrome_cleaner/srt_fetcher_browsertest_win.cc", "../browser/safe_json_parser_browsertest.cc", "../browser/search/hotword_installer_browsertest.cc", "../browser/search/suggestions/image_fetcher_impl_browsertest.cc",
diff --git a/chrome/test/android/BUILD.gn b/chrome/test/android/BUILD.gn index e8c0c82dc..8e4de8c 100644 --- a/chrome/test/android/BUILD.gn +++ b/chrome/test/android/BUILD.gn
@@ -91,7 +91,6 @@ "//third_party/android_support_test_runner:runner_java", "//third_party/android_tools:android_support_design_java", "//third_party/android_tools:android_support_transition_java", - "//third_party/android_tools:android_support_v4_java", "//third_party/android_tools:android_support_v7_appcompat_java", "//third_party/android_tools:android_support_v7_recyclerview_java", "//third_party/jsr-305:jsr_305_javalib",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ApplicationData.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ApplicationData.java index cb776fe3..e861035e 100644 --- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ApplicationData.java +++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/ApplicationData.java
@@ -4,10 +4,7 @@ package org.chromium.chrome.test.util; -import android.annotation.SuppressLint; import android.content.Context; -import android.content.SharedPreferences; -import android.support.v4.content.ContextCompat; import static org.chromium.base.test.util.ScalableTimeout.scaleTimeout; @@ -40,29 +37,22 @@ * * @param targetContext the target Context. */ - public static void clearAppData(final Context targetContext) { + public static void clearAppData(Context targetContext) { + final String appDir = getAppDirFromTargetContext(targetContext); CriteriaHelper.pollInstrumentationThread( new Criteria() { private boolean mDataRemoved; - @SuppressLint("CommitPrefEdits") @Override public boolean isSatisfied() { - SharedPreferences multidexPrefs = - targetContext.getSharedPreferences("multidex.version", 0); - if (!mDataRemoved && !removeAppData(targetContext)) { + if (!mDataRemoved && !removeAppData(appDir)) { return false; } mDataRemoved = true; // We have to make sure the cache directory still exists, as the framework // will try to create it otherwise and will fail for sandbox processes with // a NullPointerException. - File cacheDir = new File(ContextCompat.getDataDir(targetContext), "cache"); - // Removing app data cleared out all shared prefs. Multidex uses shared - // prefs to cache hashes of the secondary dexes it has extracted; without - // them, it'll attempt to reextract the dexes the next time the tests - // start up. - multidexPrefs.edit().commit(); + File cacheDir = new File(appDir, "cache"); return cacheDir.exists() || cacheDir.mkdir(); } }, @@ -70,20 +60,32 @@ } /** + * Find the absolute path of the application data directory for the given target context. + * + * When this is invoked from tests, the target context from the instrumentation must be used. + * + * @param targetContext the target Context. + * + * @return the absolute path of the application data directory. + */ + public static String getAppDirFromTargetContext(Context targetContext) { + String cacheDir = targetContext.getCacheDir().getAbsolutePath(); + return cacheDir.substring(0, cacheDir.lastIndexOf('/')); + } + + /** * Remove all files and directories under the given application directory, except 'lib'. * * @param appDir the application directory to remove. * * @return whether removal succeeded. */ - private static boolean removeAppData(final Context targetContext) { - File dataDir = ContextCompat.getDataDir(targetContext); - File codeCacheDir = ContextCompat.getCodeCacheDir(targetContext); - File[] files = dataDir.listFiles(); + private static boolean removeAppData(String appDir) { + File[] files = new File(appDir).listFiles(); if (files == null) return true; for (File file : files) { - if (!(file.getName().equals("lib") || file.getName().equals("incremental-install-files") - || file.getName().equals(codeCacheDir.getName())) + if (!(file.getName().equals("lib") + || file.getName().equals("incremental-install-files")) && !removeFile(file)) { return false; }
diff --git a/chrome/test/data/webui/settings/appearance_page_test.js b/chrome/test/data/webui/settings/appearance_page_test.js index 59ef458c..bc3cb04 100644 --- a/chrome/test/data/webui/settings/appearance_page_test.js +++ b/chrome/test/data/webui/settings/appearance_page_test.js
@@ -181,14 +181,14 @@ assertFalse(!!appearancePage.$$('#useDefault')); assertFalse(!!appearancePage.$$('#useSystem')); // If there's no "USE" buttons, the container should be hidden. - assertTrue(appearancePage.$$('.secondary-action').hidden); + assertTrue(appearancePage.$$('#themesSecondaryActions').hidden); appearanceBrowserProxy.setIsSupervised(false); appearancePage.set(THEME_ID_PREF, 'fake theme id'); Polymer.dom.flush(); // If there's "USE" buttons again, the container should be visible. assertTrue(!!appearancePage.$$('#useDefault')); - assertFalse(appearancePage.$$('.secondary-action').hidden); + assertFalse(appearancePage.$$('#themesSecondaryActions').hidden); var button = appearancePage.$$('#useSystem'); assertTrue(!!button);
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM index f5e4a2e..75a121d 100644 --- a/chromeos/CHROMEOS_LKGM +++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@ -9428.0.0 \ No newline at end of file +9510.0.0 \ No newline at end of file
diff --git a/chromeos/components/tether/ble_advertiser.cc b/chromeos/components/tether/ble_advertiser.cc index e20ecd1..2211410 100644 --- a/chromeos/components/tether/ble_advertiser.cc +++ b/chromeos/components/tether/ble_advertiser.cc
@@ -32,8 +32,7 @@ BleAdvertiser::IndividualAdvertisement::IndividualAdvertisement( scoped_refptr<device::BluetoothAdapter> adapter, - std::unique_ptr<cryptauth::ForegroundEidGenerator::DataWithTimestamp> - advertisement_data, + std::unique_ptr<cryptauth::DataWithTimestamp> advertisement_data, std::shared_ptr<BleAdvertisementUnregisterHandler> unregister_handler) : adapter_(adapter), is_initializing_advertising_(false), @@ -211,9 +210,9 @@ return false; } - std::unique_ptr<cryptauth::ForegroundEidGenerator::DataWithTimestamp> - advertisement = eid_generator_->GenerateAdvertisement( - local_device_public_key, remote_beacon_seeds); + std::unique_ptr<cryptauth::DataWithTimestamp> advertisement = + eid_generator_->GenerateAdvertisement(local_device_public_key, + remote_beacon_seeds); if (!advertisement) { PA_LOG(WARNING) << "Error generating advertisement for device with ID " << remote_device.GetTruncatedDeviceIdForLogs() << ". "
diff --git a/chromeos/components/tether/ble_advertiser.h b/chromeos/components/tether/ble_advertiser.h index fcc10f5..c7c25f7 100644 --- a/chromeos/components/tether/ble_advertiser.h +++ b/chromeos/components/tether/ble_advertiser.h
@@ -70,8 +70,7 @@ public: IndividualAdvertisement( scoped_refptr<device::BluetoothAdapter> adapter, - std::unique_ptr<cryptauth::ForegroundEidGenerator::DataWithTimestamp> - advertisement_data, + std::unique_ptr<cryptauth::DataWithTimestamp> advertisement_data, std::shared_ptr<BleAdvertisementUnregisterHandler> unregister_handler); // device::BluetoothAdapter::Observer @@ -104,8 +103,7 @@ scoped_refptr<device::BluetoothAdapter> adapter_; bool is_initializing_advertising_; - std::unique_ptr<cryptauth::ForegroundEidGenerator::DataWithTimestamp> - advertisement_data_; + std::unique_ptr<cryptauth::DataWithTimestamp> advertisement_data_; std::shared_ptr<BleAdvertisementUnregisterHandler> unregister_handler_; scoped_refptr<device::BluetoothAdvertisement> advertisement_;
diff --git a/chromeos/components/tether/ble_advertiser_unittest.cc b/chromeos/components/tether/ble_advertiser_unittest.cc index d8caac6..78f3cc1f 100644 --- a/chromeos/components/tether/ble_advertiser_unittest.cc +++ b/chromeos/components/tether/ble_advertiser_unittest.cc
@@ -27,7 +27,7 @@ namespace { uint8_t kInvertedConnectionFlag = 0x01; -const std::string fake_public_key = "fakePublicKey"; +const char kFakePublicKey[] = "fakePublicKey"; struct RegisterAdvertisementArgs : public base::RefCounted<RegisterAdvertisementArgs> { @@ -80,15 +80,12 @@ ~MockBluetoothAdapterWithAdvertisements() override {} }; -std::vector<cryptauth::ForegroundEidGenerator::DataWithTimestamp> -GenerateFakeAdvertisements() { - cryptauth::ForegroundEidGenerator::DataWithTimestamp advertisement1( - "advertisement1", 1000L, 2000L); - cryptauth::ForegroundEidGenerator::DataWithTimestamp advertisement2( - "advertisement2", 2000L, 3000L); +std::vector<cryptauth::DataWithTimestamp> GenerateFakeAdvertisements() { + cryptauth::DataWithTimestamp advertisement1("advertisement1", 1000L, 2000L); + cryptauth::DataWithTimestamp advertisement2("advertisement2", 2000L, 3000L); - std::vector<cryptauth::ForegroundEidGenerator::DataWithTimestamp> - advertisements = {advertisement1, advertisement2}; + std::vector<cryptauth::DataWithTimestamp> advertisements = {advertisement1, + advertisement2}; return advertisements; } @@ -106,7 +103,7 @@ std::vector<cryptauth::BeaconSeed> seeds = {seed1, seed2}; return seeds; -}; +} } // namespace @@ -178,7 +175,7 @@ mock_local_data_provider_ = base::MakeUnique<MockLocalDeviceDataProvider>(); mock_local_data_provider_->SetPublicKey( - base::MakeUnique<std::string>(fake_public_key)); + base::MakeUnique<std::string>(kFakePublicKey)); ble_advertiser_ = base::WrapUnique(new BleAdvertiser( mock_adapter_, base::WrapUnique(test_unregister_handler_), @@ -271,8 +268,7 @@ individual_advertisements_; const std::vector<cryptauth::RemoteDevice> fake_devices_; - const std::vector<cryptauth::ForegroundEidGenerator::DataWithTimestamp> - fake_advertisements_; + const std::vector<cryptauth::DataWithTimestamp> fake_advertisements_; private: DISALLOW_COPY_AND_ASSIGN(BleAdvertiserTest); @@ -325,8 +321,7 @@ EXPECT_CALL(*mock_adapter_, IsPowered()).Times(1).WillOnce(Return(false)); mock_eid_generator_->set_advertisement( - base::MakeUnique<cryptauth::ForegroundEidGenerator::DataWithTimestamp>( - fake_advertisements_[0])); + base::MakeUnique<cryptauth::DataWithTimestamp>(fake_advertisements_[0])); EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice(fake_devices_[0])); EXPECT_EQ(static_cast<size_t>(1), individual_advertisements_.size()); @@ -342,8 +337,7 @@ EXPECT_CALL(*mock_adapter_, RegisterAdvertisementWithArgsStruct(_)).Times(1); mock_eid_generator_->set_advertisement( - base::MakeUnique<cryptauth::ForegroundEidGenerator::DataWithTimestamp>( - fake_advertisements_[0])); + base::MakeUnique<cryptauth::DataWithTimestamp>(fake_advertisements_[0])); EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice(fake_devices_[0])); EXPECT_EQ(static_cast<size_t>(1), individual_advertisements_.size()); @@ -362,8 +356,7 @@ EXPECT_CALL(*mock_adapter_, RegisterAdvertisementWithArgsStruct(_)).Times(1); mock_eid_generator_->set_advertisement( - base::MakeUnique<cryptauth::ForegroundEidGenerator::DataWithTimestamp>( - fake_advertisements_[0])); + base::MakeUnique<cryptauth::DataWithTimestamp>(fake_advertisements_[0])); EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice(fake_devices_[0])); EXPECT_EQ(static_cast<size_t>(1), individual_advertisements_.size()); @@ -388,8 +381,7 @@ // First device. mock_eid_generator_->set_advertisement( - base::MakeUnique<cryptauth::ForegroundEidGenerator::DataWithTimestamp>( - fake_advertisements_[0])); + base::MakeUnique<cryptauth::DataWithTimestamp>(fake_advertisements_[0])); EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice(fake_devices_[0])); EXPECT_EQ(static_cast<size_t>(1), individual_advertisements_.size()); @@ -402,8 +394,7 @@ // Second device. mock_eid_generator_->set_advertisement( - base::MakeUnique<cryptauth::ForegroundEidGenerator::DataWithTimestamp>( - fake_advertisements_[1])); + base::MakeUnique<cryptauth::DataWithTimestamp>(fake_advertisements_[1])); EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice(fake_devices_[1])); EXPECT_EQ(static_cast<size_t>(2), individual_advertisements_.size()); @@ -433,8 +424,7 @@ EXPECT_CALL(*mock_adapter_, RegisterAdvertisementWithArgsStruct(_)).Times(3); mock_eid_generator_->set_advertisement( - base::MakeUnique<cryptauth::ForegroundEidGenerator::DataWithTimestamp>( - fake_advertisements_[0])); + base::MakeUnique<cryptauth::DataWithTimestamp>(fake_advertisements_[0])); // Should succeed for the first two devices. EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice(fake_devices_[0])); @@ -464,8 +454,7 @@ EXPECT_CALL(*mock_adapter_, RegisterAdvertisementWithArgsStruct(_)).Times(1); mock_eid_generator_->set_advertisement( - base::MakeUnique<cryptauth::ForegroundEidGenerator::DataWithTimestamp>( - fake_advertisements_[0])); + base::MakeUnique<cryptauth::DataWithTimestamp>(fake_advertisements_[0])); EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice(fake_devices_[0])); EXPECT_EQ(static_cast<size_t>(1), individual_advertisements_.size()); @@ -490,8 +479,7 @@ EXPECT_CALL(*mock_adapter_, RegisterAdvertisementWithArgsStruct(_)).Times(2); mock_eid_generator_->set_advertisement( - base::MakeUnique<cryptauth::ForegroundEidGenerator::DataWithTimestamp>( - fake_advertisements_[0])); + base::MakeUnique<cryptauth::DataWithTimestamp>(fake_advertisements_[0])); EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice(fake_devices_[0])); EXPECT_EQ(static_cast<size_t>(1), individual_advertisements_.size()); @@ -520,4 +508,4 @@ } // namespace tether -} // namespace cryptauth +} // namespace chromeos
diff --git a/chromeos/components/tether/ble_scanner_unittest.cc b/chromeos/components/tether/ble_scanner_unittest.cc index 64a4efc..b3292e3 100644 --- a/chromeos/components/tether/ble_scanner_unittest.cc +++ b/chromeos/components/tether/ble_scanner_unittest.cc
@@ -101,12 +101,11 @@ std::unique_ptr<cryptauth::ForegroundEidGenerator::EidData> CreateFakeBackgroundScanFilter() { - cryptauth::ForegroundEidGenerator::DataWithTimestamp current( - current_eid_data, current_eid_start_ms, current_eid_end_ms); + cryptauth::DataWithTimestamp current(current_eid_data, current_eid_start_ms, + current_eid_end_ms); - std::unique_ptr<cryptauth::ForegroundEidGenerator::DataWithTimestamp> - adjacent = base::MakeUnique< - cryptauth::ForegroundEidGenerator::DataWithTimestamp>( + std::unique_ptr<cryptauth::DataWithTimestamp> adjacent = + base::MakeUnique<cryptauth::DataWithTimestamp>( adjacent_eid_data, adjacent_eid_start_ms, adjacent_eid_end_ms); return base::MakeUnique<cryptauth::ForegroundEidGenerator::EidData>(
diff --git a/components/cryptauth/BUILD.gn b/components/cryptauth/BUILD.gn index 9dca5389..d4df06c 100644 --- a/components/cryptauth/BUILD.gn +++ b/components/cryptauth/BUILD.gn
@@ -40,6 +40,8 @@ "cryptauth_gcm_manager_impl.h", "cryptauth_service.cc", "cryptauth_service.h", + "data_with_timestamp.cc", + "data_with_timestamp.h", "device_to_device_authenticator.cc", "device_to_device_authenticator.h", "device_to_device_initiator_operations.cc",
diff --git a/components/cryptauth/background_eid_generator.cc b/components/cryptauth/background_eid_generator.cc index 1103e8c..a8a1b7bc 100644 --- a/components/cryptauth/background_eid_generator.cc +++ b/components/cryptauth/background_eid_generator.cc
@@ -54,23 +54,24 @@ : raw_eid_generator_(std::move(raw_eid_generator)), clock_(std::move(clock)) {} -std::vector<std::string> BackgroundEidGenerator::GenerateNearestEids( +std::vector<DataWithTimestamp> BackgroundEidGenerator::GenerateNearestEids( const std::vector<BeaconSeed>& beacon_seeds) const { int64_t now_timestamp_ms = clock_->Now().ToJavaTime(); - std::vector<std::string> eids; + std::vector<DataWithTimestamp> eids; - PA_LOG(INFO) << "Generating EIDs:"; for (int i = -kEidLookAhead; i <= kEidLookAhead; ++i) { int64_t timestamp_ms = now_timestamp_ms + i * kEidPeriodMs; - std::unique_ptr<std::string> eid = GenerateEid(timestamp_ms, beacon_seeds); + std::unique_ptr<DataWithTimestamp> eid = + GenerateEid(timestamp_ms, beacon_seeds); if (eid) eids.push_back(*eid); } + PA_LOG(INFO) << "Generated EIDs: " << DataWithTimestamp::ToDebugString(eids); return eids; } -std::unique_ptr<std::string> BackgroundEidGenerator::GenerateEid( +std::unique_ptr<DataWithTimestamp> BackgroundEidGenerator::GenerateEid( int64_t timestamp_ms, const std::vector<BeaconSeed>& beacon_seeds) const { const BeaconSeed* beacon_seed = @@ -88,8 +89,8 @@ std::string eid = raw_eid_generator_->GenerateEid( beacon_seed->data(), start_of_period_ms, nullptr); - PA_LOG(INFO) << " " << start_of_period_ms << ": " << eid; - return base::MakeUnique<std::string>(eid); + return base::MakeUnique<DataWithTimestamp>(eid, start_of_period_ms, + start_of_period_ms + kEidPeriodMs); } } // cryptauth
diff --git a/components/cryptauth/background_eid_generator.h b/components/cryptauth/background_eid_generator.h index 506fa47c..3fc0674 100644 --- a/components/cryptauth/background_eid_generator.h +++ b/components/cryptauth/background_eid_generator.h
@@ -11,6 +11,7 @@ #include "base/macros.h" #include "base/time/clock.h" +#include "components/cryptauth/data_with_timestamp.h" namespace cryptauth { @@ -33,7 +34,7 @@ // Returns a list of the nearest EIDs from the current time. Note that the // list of EIDs is sorted from earliest timestamp to latest. - virtual std::vector<std::string> GenerateNearestEids( + virtual std::vector<DataWithTimestamp> GenerateNearestEids( const std::vector<BeaconSeed>& beacon_seed) const; private: @@ -44,7 +45,7 @@ // Helper function to generate the EID for any |timestamp_ms|, properly // calculating the start of the period. Returns nullptr if |timestamp_ms| is // outside of range of |beacon_seeds|. - std::unique_ptr<std::string> GenerateEid( + std::unique_ptr<DataWithTimestamp> GenerateEid( int64_t timestamp_ms, const std::vector<BeaconSeed>& beacon_seeds) const;
diff --git a/components/cryptauth/background_eid_generator_unittest.cc b/components/cryptauth/background_eid_generator_unittest.cc index da41c66e..3f74a6c55 100644 --- a/components/cryptauth/background_eid_generator_unittest.cc +++ b/components/cryptauth/background_eid_generator_unittest.cc
@@ -48,9 +48,12 @@ return seed; } -std::string CreateEid(const std::string& eid_seed, - int64_t start_of_period_timestamp_ms) { - return eid_seed + "|" + std::to_string(start_of_period_timestamp_ms); +DataWithTimestamp CreateEid(const std::string& eid_seed, + int64_t start_of_period_timestamp_ms) { + std::string data = + eid_seed + "|" + std::to_string(start_of_period_timestamp_ms); + return DataWithTimestamp(data, start_of_period_timestamp_ms, + start_of_period_timestamp_ms + kEidPeriodMs); } class TestRawEidGenerator : public RawEidGenerator { @@ -62,8 +65,8 @@ std::string GenerateEid(const std::string& eid_seed, int64_t start_of_period_timestamp_ms, std::string const* extra_entropy) override { - // ASSERT_FALSE(extra_entropy); - return CreateEid(eid_seed, start_of_period_timestamp_ms); + EXPECT_FALSE(extra_entropy); + return CreateEid(eid_seed, start_of_period_timestamp_ms).data; } private: @@ -111,14 +114,14 @@ TEST_F(CryptAuthBackgroundEidGeneratorTest, BeaconSeedsExpired) { SetTestTime(beacon_seeds_[beacon_seeds_.size() - 1].end_time_millis() + kEidCount * kEidPeriodMs); - std::vector<std::string> eids = + std::vector<DataWithTimestamp> eids = eid_generator_->GenerateNearestEids(beacon_seeds_); EXPECT_EQ(0u, eids.size()); } TEST_F(CryptAuthBackgroundEidGeneratorTest, BeaconSeedsValidInFuture) { SetTestTime(beacon_seeds_[0].start_time_millis() - kEidCount * kEidPeriodMs); - std::vector<std::string> eids = + std::vector<DataWithTimestamp> eids = eid_generator_->GenerateNearestEids(beacon_seeds_); EXPECT_EQ(0u, eids.size()); } @@ -128,7 +131,7 @@ beacon_seeds_[0].start_time_millis() + kEidCount * kEidPeriodMs; SetTestTime(start_period_ms + kEidPeriodMs / 2); - std::vector<std::string> eids = + std::vector<DataWithTimestamp> eids = eid_generator_->GenerateNearestEids(beacon_seeds_); std::string seed = beacon_seeds_[0].data(); @@ -145,7 +148,7 @@ int64_t start_period_ms = beacon_seeds_[1].start_time_millis(); SetTestTime(start_period_ms + kEidPeriodMs / 2); - std::vector<std::string> eids = + std::vector<DataWithTimestamp> eids = eid_generator_->GenerateNearestEids(beacon_seeds_); std::string seed0 = beacon_seeds_[0].data(); @@ -162,7 +165,7 @@ int64_t start_period_ms = beacon_seeds_[0].start_time_millis(); SetTestTime(start_period_ms + kEidPeriodMs / 2); - std::vector<std::string> eids = + std::vector<DataWithTimestamp> eids = eid_generator_->GenerateNearestEids(beacon_seeds_); std::string seed = beacon_seeds_[0].data(); @@ -176,7 +179,7 @@ int64_t start_period_ms = beacon_seeds_[3].end_time_millis() - kEidPeriodMs; SetTestTime(start_period_ms + kEidPeriodMs / 2); - std::vector<std::string> eids = + std::vector<DataWithTimestamp> eids = eid_generator_->GenerateNearestEids(beacon_seeds_); std::string seed = beacon_seeds_[3].data();
diff --git a/components/cryptauth/data_with_timestamp.cc b/components/cryptauth/data_with_timestamp.cc new file mode 100644 index 0000000..7fbc96c --- /dev/null +++ b/components/cryptauth/data_with_timestamp.cc
@@ -0,0 +1,63 @@ +// 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 "components/cryptauth/data_with_timestamp.h" + +#include <iomanip> + +#include "base/logging.h" + +namespace cryptauth { + +DataWithTimestamp::DataWithTimestamp(const std::string& data, + const int64_t start_timestamp_ms, + const int64_t end_timestamp_ms) + : data(data), + start_timestamp_ms(start_timestamp_ms), + end_timestamp_ms(end_timestamp_ms) { + DCHECK(start_timestamp_ms < end_timestamp_ms); + DCHECK(data.size()); +} + +DataWithTimestamp::DataWithTimestamp(const DataWithTimestamp& other) + : data(other.data), + start_timestamp_ms(other.start_timestamp_ms), + end_timestamp_ms(other.end_timestamp_ms) { + DCHECK(start_timestamp_ms < end_timestamp_ms); + DCHECK(data.size()); +} + +// static. +std::string DataWithTimestamp::ToDebugString( + const std::vector<DataWithTimestamp>& data_with_timestamps) { + std::stringstream ss; + ss << "["; + for (const DataWithTimestamp& data : data_with_timestamps) { + ss << "\n (" << data.start_timestamp_ms << ": " << data.DataInHex() + << "),"; + } + ss << "\n]"; + return ss.str(); +} + +bool DataWithTimestamp::ContainsTime(const int64_t timestamp_ms) const { + return start_timestamp_ms <= timestamp_ms && timestamp_ms < end_timestamp_ms; +} + +std::string DataWithTimestamp::DataInHex() const { + std::stringstream ss; + ss << "0x"; + for (uint8_t byte : data) { + ss << std::hex << std::setfill('0') << std::setw(2) + << static_cast<uint64_t>(byte); + } + return ss.str(); +} + +bool DataWithTimestamp::operator==(const DataWithTimestamp& other) const { + return data == other.data && start_timestamp_ms == other.start_timestamp_ms && + end_timestamp_ms == other.end_timestamp_ms; +} + +} // namespace cryptauth
diff --git a/components/cryptauth/data_with_timestamp.h b/components/cryptauth/data_with_timestamp.h new file mode 100644 index 0000000..f4e36a67 --- /dev/null +++ b/components/cryptauth/data_with_timestamp.h
@@ -0,0 +1,43 @@ +// 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 COMPONENTS_CRYPTAUTH_BLE_DATA_WITH_TIMESTAMP_H_ +#define COMPONENTS_CRYPTAUTH_BLE_DATA_WITH_TIMESTAMP_H_ + +#include <string> +#include <vector> + +namespace cryptauth { + +// Stores EID-related data and timestamps at which time this data becomes +// active or inactive. +struct DataWithTimestamp { + DataWithTimestamp(const std::string& data, + const int64_t start_timestamp_ms, + const int64_t end_timestamp_ms); + DataWithTimestamp(const DataWithTimestamp& other); + + // Helper function to convert a vector of DataWithTimestamp objects to a human + // readable debug string. + static std::string ToDebugString( + const std::vector<DataWithTimestamp>& data_with_timestamps); + + bool ContainsTime(const int64_t timestamp_ms) const; + std::string DataInHex() const; + + bool operator==(const DataWithTimestamp& other) const; + + // The data valid for a given time period. + const std::string data; + + // The start time for which the data is valid, inclusive. + const int64_t start_timestamp_ms; + + // The end time for which the data is valid, exclusive. + const int64_t end_timestamp_ms; +}; + +} // namespace + +#endif // COMPONENTS_CRYPTAUTH_BLE_DATA_WITH_TIMESTAMP_H_
diff --git a/components/cryptauth/foreground_eid_generator.cc b/components/cryptauth/foreground_eid_generator.cc index b7d99da..01e269e 100644 --- a/components/cryptauth/foreground_eid_generator.cc +++ b/components/cryptauth/foreground_eid_generator.cc
@@ -59,42 +59,6 @@ return str + "]"; } -ForegroundEidGenerator::DataWithTimestamp::DataWithTimestamp( - const std::string& data, - const int64_t start_timestamp_ms, - const int64_t end_timestamp_ms) - : data(data), - start_timestamp_ms(start_timestamp_ms), - end_timestamp_ms(end_timestamp_ms) { - DCHECK(start_timestamp_ms < end_timestamp_ms); - DCHECK(data.size()); -} - -ForegroundEidGenerator::DataWithTimestamp::DataWithTimestamp( - const DataWithTimestamp& other) - : data(other.data), - start_timestamp_ms(other.start_timestamp_ms), - end_timestamp_ms(other.end_timestamp_ms) { - DCHECK(start_timestamp_ms < end_timestamp_ms); - DCHECK(data.size()); -} - -bool ForegroundEidGenerator::DataWithTimestamp::ContainsTime( - const int64_t timestamp_ms) const { - return start_timestamp_ms <= timestamp_ms && timestamp_ms < end_timestamp_ms; -} - -std::string ForegroundEidGenerator::DataWithTimestamp::DataInHex() const { - std::stringstream ss; - ss << "0x" << std::hex; - - for (size_t i = 0; i < data.size(); i++) { - ss << static_cast<int>(data.data()[i]); - } - - return ss.str(); -} - ForegroundEidGenerator::ForegroundEidGenerator() : ForegroundEidGenerator(base::MakeUnique<RawEidGeneratorImpl>(), base::MakeUnique<base::DefaultClock>()) {} @@ -135,7 +99,7 @@ return base::WrapUnique(new EidData(*current_eid, std::move(adjacent_eid))); } -std::unique_ptr<ForegroundEidGenerator::DataWithTimestamp> +std::unique_ptr<DataWithTimestamp> ForegroundEidGenerator::GenerateAdvertisement( const std::string& advertising_device_public_key, const std::vector<BeaconSeed>& scanning_device_beacon_seeds) const { @@ -211,7 +175,7 @@ return possible_advertisements; } -std::unique_ptr<ForegroundEidGenerator::DataWithTimestamp> +std::unique_ptr<DataWithTimestamp> ForegroundEidGenerator::GenerateAdvertisement( const std::string& advertising_device_public_key, const std::vector<BeaconSeed>& scanning_device_beacon_seeds, @@ -237,7 +201,7 @@ end_of_period_timestamp_ms)); } -std::unique_ptr<ForegroundEidGenerator::DataWithTimestamp> +std::unique_ptr<DataWithTimestamp> ForegroundEidGenerator::GenerateEidDataWithTimestamp( const std::vector<BeaconSeed>& scanning_device_beacon_seeds, const int64_t start_of_period_timestamp_ms, @@ -247,7 +211,7 @@ end_of_period_timestamp_ms, nullptr); } -std::unique_ptr<ForegroundEidGenerator::DataWithTimestamp> +std::unique_ptr<DataWithTimestamp> ForegroundEidGenerator::GenerateEidDataWithTimestamp( const std::vector<BeaconSeed>& scanning_device_beacon_seeds, const int64_t start_of_period_timestamp_ms,
diff --git a/components/cryptauth/foreground_eid_generator.h b/components/cryptauth/foreground_eid_generator.h index 8068078c..ec68de8 100644 --- a/components/cryptauth/foreground_eid_generator.h +++ b/components/cryptauth/foreground_eid_generator.h
@@ -12,6 +12,7 @@ #include "base/gtest_prod_util.h" #include "base/macros.h" #include "base/time/clock.h" +#include "components/cryptauth/data_with_timestamp.h" namespace cryptauth { @@ -40,22 +41,6 @@ // See go/proximity-auth-ble-advertising. class ForegroundEidGenerator { public: - // Stores EID-related data and timestamps at which time this data becomes - // active or inactive. - struct DataWithTimestamp { - DataWithTimestamp(const std::string& data, - const int64_t start_timestamp_ms, - const int64_t end_timestamp_ms); - DataWithTimestamp(const DataWithTimestamp& other); - - bool ContainsTime(const int64_t timestamp_ms) const; - std::string DataInHex() const; - - const std::string data; - const int64_t start_timestamp_ms; - const int64_t end_timestamp_ms; - }; - // Data for both a current and adjacent EID. The current EID *must* be // supplied, but adjacent data may be null. Each EID consists of a 2-byte EID // value paired with the timestamp at which time this value becomes active or
diff --git a/components/cryptauth/foreground_eid_generator_unittest.cc b/components/cryptauth/foreground_eid_generator_unittest.cc index a52b0d8..97e90179 100644 --- a/components/cryptauth/foreground_eid_generator_unittest.cc +++ b/components/cryptauth/foreground_eid_generator_unittest.cc
@@ -359,7 +359,7 @@ TEST_F(CryptAuthForegroundEidGeneratorTest, GenerateAdvertisementData) { SetTestTime(kDefaultCurrentTime); - std::unique_ptr<ForegroundEidGenerator::DataWithTimestamp> data = + std::unique_ptr<DataWithTimestamp> data = eid_generator_->GenerateAdvertisement(kDefaultAdvertisingDevicePublicKey, scanning_device_beacon_seeds_); ASSERT_TRUE(data); @@ -376,7 +376,7 @@ GenerateAdvertisementData_NoSeedForPeriod) { SetTestTime(kDefaultCurrentTime + 4 * kNumMsInEidSeedPeriod); - std::unique_ptr<ForegroundEidGenerator::DataWithTimestamp> data = + std::unique_ptr<DataWithTimestamp> data = eid_generator_->GenerateAdvertisement(kDefaultAdvertisingDevicePublicKey, scanning_device_beacon_seeds_); EXPECT_FALSE(data); @@ -387,7 +387,7 @@ SetTestTime(kDefaultCurrentTime + 4 * kNumMsInEidSeedPeriod); std::vector<BeaconSeed> empty; - std::unique_ptr<ForegroundEidGenerator::DataWithTimestamp> data = + std::unique_ptr<DataWithTimestamp> data = eid_generator_->GenerateAdvertisement(kDefaultAdvertisingDevicePublicKey, empty); EXPECT_FALSE(data); @@ -637,8 +637,8 @@ } TEST_F(CryptAuthForegroundEidGeneratorTest, DataWithTimestamp_ContainsTime) { - ForegroundEidGenerator::DataWithTimestamp data_with_timestamp( - "data", /* start */ 1000L, /* end */ 2000L); + DataWithTimestamp data_with_timestamp("data", /* start */ 1000L, + /* end */ 2000L); EXPECT_FALSE(data_with_timestamp.ContainsTime(999L)); EXPECT_TRUE(data_with_timestamp.ContainsTime(1000L)); EXPECT_TRUE(data_with_timestamp.ContainsTime(1500L));
diff --git a/components/cryptauth/mock_foreground_eid_generator.cc b/components/cryptauth/mock_foreground_eid_generator.cc index 60fc3595..0dc949c 100644 --- a/components/cryptauth/mock_foreground_eid_generator.cc +++ b/components/cryptauth/mock_foreground_eid_generator.cc
@@ -24,7 +24,7 @@ return nullptr; } - std::unique_ptr<ForegroundEidGenerator::DataWithTimestamp> adjacent_data; + std::unique_ptr<DataWithTimestamp> adjacent_data; if (background_scan_filter_->adjacent_data) { adjacent_data = base::MakeUnique<DataWithTimestamp>( background_scan_filter_->adjacent_data->data, @@ -36,7 +36,7 @@ std::move(adjacent_data)); } -std::unique_ptr<ForegroundEidGenerator::DataWithTimestamp> +std::unique_ptr<DataWithTimestamp> MockForegroundEidGenerator::GenerateAdvertisement( const std::string& advertising_device_public_key, const std::vector<BeaconSeed>& scanning_device_beacon_seeds) const {
diff --git a/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/MinidumpUploadCallable.java b/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/MinidumpUploadCallable.java index c333991..349de8e 100644 --- a/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/MinidumpUploadCallable.java +++ b/components/minidump_uploader/android/java/src/org/chromium/components/minidump_uploader/MinidumpUploadCallable.java
@@ -243,6 +243,15 @@ Log.e(TAG, "Ignoring invalidly bound crash dump: '" + mFileToUpload + "'"); return null; } + // Note: The regex allows all alphanumeric characters, as well as dashes. + // This matches the code that generates minidumps boundaries: + // https://chromium.googlesource.com/crashpad/crashpad/+/0c322ecc3f711c34fbf85b2cbe69f38b8dbccf05/util/net/http_multipart_builder.cc#36 + if (!boundary.matches("^[a-zA-Z0-9-]*$")) { + Log.e(TAG, + "Ignoring invalidly bound crash dump '" + mFileToUpload + + "' due to invalid boundary characters: '" + boundary + "'"); + return null; + } boundary = boundary.substring(2); // Remove the initial -- return boundary; }
diff --git a/components/minidump_uploader/android/javatests/src/org/chromium/components/minidump_uploader/MinidumpUploadCallableTest.java b/components/minidump_uploader/android/javatests/src/org/chromium/components/minidump_uploader/MinidumpUploadCallableTest.java index ec0845f6..71380bed 100644 --- a/components/minidump_uploader/android/javatests/src/org/chromium/components/minidump_uploader/MinidumpUploadCallableTest.java +++ b/components/minidump_uploader/android/javatests/src/org/chromium/components/minidump_uploader/MinidumpUploadCallableTest.java
@@ -38,8 +38,9 @@ * minidumps correctly. */ public static class TestHttpURLConnection extends HttpURLConnection { - private static final String EXPECTED_CONTENT_TYPE_VALUE = + static final String DEFAULT_EXPECTED_CONTENT_TYPE = String.format(MinidumpUploadCallable.CONTENT_TYPE_TMPL, BOUNDARY); + private final String mExpectedContentType; /** * The value of the "Content-Type" property if the property has been set. @@ -47,14 +48,19 @@ private String mContentTypePropertyValue = ""; public TestHttpURLConnection(URL url) { + this(url, DEFAULT_EXPECTED_CONTENT_TYPE); + } + + public TestHttpURLConnection(URL url, String contentType) { super(url); + mExpectedContentType = contentType; assertEquals(MinidumpUploadCallable.CRASH_URL_STRING, url.toString()); } @Override public void disconnect() { // Check that the "Content-Type" property has been set and the property's value. - assertEquals(EXPECTED_CONTENT_TYPE_VALUE, mContentTypePropertyValue); + assertEquals(mExpectedContentType, mContentTypePropertyValue); } @Override @@ -99,10 +105,16 @@ * minidumps correctly. */ public static class TestHttpURLConnectionFactory implements HttpURLConnectionFactory { + String mContentType; + + public TestHttpURLConnectionFactory() { + mContentType = TestHttpURLConnection.DEFAULT_EXPECTED_CONTENT_TYPE; + } + @Override public HttpURLConnection createHttpURLConnection(String url) { try { - return new TestHttpURLConnection(new URL(url)); + return new TestHttpURLConnection(new URL(url), mContentType); } catch (IOException e) { return null; } @@ -402,6 +414,54 @@ assertTrue(mExpectedFileAfterUpload.exists()); } + // This is a regression test for http://crbug.com/712420 + @SmallTest + @Feature({"Android-AppBase"}) + public void testCallWithInvalidMinidumpBoundary() throws Exception { + // Include an invalid character, '[', in the test string. + setUpMinidumpFile(mTestUpload, "--InvalidBoundaryWithSpecialCharacter--["); + CrashReportingPermissionManager testPermManager = + new MockCrashReportingPermissionManager() { + { mIsEnabledForTests = true; } + }; + HttpURLConnectionFactory httpURLConnectionFactory = new TestHttpURLConnectionFactory() { + { mContentType = ""; } + }; + + MinidumpUploadCallable minidumpUploadCallable = + new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager); + + assertEquals( + MinidumpUploadCallable.UPLOAD_FAILURE, minidumpUploadCallable.call().intValue()); + assertFalse(mExpectedFileAfterUpload.exists()); + } + + @SmallTest + @Feature({"Android-AppBase"}) + public void testCallWithValidMinidumpBoundary() throws Exception { + // Include all valid characters in the test string. + final String boundary = "--0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + final String expectedContentType = + String.format(MinidumpUploadCallable.CONTENT_TYPE_TMPL, boundary); + CrashReportingPermissionManager testPermManager = + new MockCrashReportingPermissionManager() { + { mIsEnabledForTests = true; } + }; + HttpURLConnectionFactory httpURLConnectionFactory = new TestHttpURLConnectionFactory() { + { mContentType = expectedContentType; } + }; + + setUpMinidumpFile(mTestUpload, boundary); + + MinidumpUploadCallable minidumpUploadCallable = + new MockMinidumpUploadCallable(httpURLConnectionFactory, testPermManager); + + assertEquals( + MinidumpUploadCallable.UPLOAD_SUCCESS, minidumpUploadCallable.call().intValue()); + assertTrue(mExpectedFileAfterUpload.exists()); + assertValidUploadLogEntry(); + } + @SmallTest @Feature({"Android-AppBase"}) public void testReceivingErrorCodes() throws Exception {
diff --git a/components/plugins/renderer/loadable_plugin_placeholder.cc b/components/plugins/renderer/loadable_plugin_placeholder.cc index 3406d1a..dde47c2 100644 --- a/components/plugins/renderer/loadable_plugin_placeholder.cc +++ b/components/plugins/renderer/loadable_plugin_placeholder.cc
@@ -41,6 +41,7 @@ DCHECK(!is_blocked_for_power_saver_poster_); is_blocked_for_power_saver_poster_ = true; + DCHECK(render_frame()); render_frame()->RegisterPeripheralPlugin( url::Origin(GURL(GetPluginParams().url)), base::Bind(&LoadablePluginPlaceholder::MarkPluginEssential, @@ -181,6 +182,8 @@ void LoadablePluginPlaceholder::OnUnobscuredRectUpdate( const gfx::Rect& unobscured_rect) { DCHECK(content::RenderThread::Get()); + if (!render_frame()) + return; if (!plugin() || !finished_loading_) return;
diff --git a/components/prefs/BUILD.gn b/components/prefs/BUILD.gn index e3462a4a..029c712 100644 --- a/components/prefs/BUILD.gn +++ b/components/prefs/BUILD.gn
@@ -43,6 +43,7 @@ "scoped_user_pref_update.h", "value_map_pref_store.cc", "value_map_pref_store.h", + "writeable_pref_store.cc", "writeable_pref_store.h", ]
diff --git a/components/prefs/pref_service.cc b/components/prefs/pref_service.cc index 4ebea53..dccebf4 100644 --- a/components/prefs/pref_service.cc +++ b/components/prefs/pref_service.cc
@@ -482,6 +482,14 @@ user_pref_store_->ReportValueChanged(key, GetWriteFlags(FindPreference(key))); } +void PrefService::ReportUserPrefChanged( + const std::string& key, + std::set<std::vector<std::string>> path_components) { + DCHECK(CalledOnValidThread()); + user_pref_store_->ReportSubValuesChanged(key, std::move(path_components), + GetWriteFlags(FindPreference(key))); +} + void PrefService::SetUserPrefValue(const std::string& path, std::unique_ptr<base::Value> new_value) { DCHECK(CalledOnValidThread());
diff --git a/components/prefs/pref_service.h b/components/prefs/pref_service.h index a08d7f85..a360afa4 100644 --- a/components/prefs/pref_service.h +++ b/components/prefs/pref_service.h
@@ -39,6 +39,10 @@ class FilePath; } +namespace prefs { +class ScopedDictionaryPrefUpdate; +} + namespace subtle { class PrefMemberBase; class ScopedUserPrefUpdateBase; @@ -331,6 +335,7 @@ // Give access to ReportUserPrefChanged() and GetMutableUserPref(). friend class subtle::ScopedUserPrefUpdateBase; friend class PrefServiceTest_WriteablePrefStoreFlags_Test; + friend class prefs::ScopedDictionaryPrefUpdate; // Registration of pref change observers must be done using the // PrefChangeRegistrar, which is declared as a friend here to grant it @@ -354,8 +359,12 @@ virtual void RemovePrefObserver(const std::string& path, PrefObserver* obs); // Sends notification of a changed preference. This needs to be called by - // a ScopedUserPrefUpdate if a DictionaryValue or ListValue is changed. + // a ScopedUserPrefUpdate or ScopedDictionaryPrefUpdate if a DictionaryValue + // or ListValue is changed. void ReportUserPrefChanged(const std::string& key); + void ReportUserPrefChanged( + const std::string& key, + std::set<std::vector<std::string>> path_components); // Sets the value for this pref path in the user pref store and informs the // PrefNotifier of the change.
diff --git a/components/prefs/writeable_pref_store.cc b/components/prefs/writeable_pref_store.cc new file mode 100644 index 0000000..d51985e --- /dev/null +++ b/components/prefs/writeable_pref_store.cc
@@ -0,0 +1,14 @@ +// 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 "components/prefs/writeable_pref_store.h" + +void WriteablePrefStore::ReportSubValuesChanged( + const std::string& key, + std::set<std::vector<std::string>> path_components, + uint32_t flags) { + // Default implementation. Subclasses may use |path_components| to improve + // performance. + ReportValueChanged(key, flags); +}
diff --git a/components/prefs/writeable_pref_store.h b/components/prefs/writeable_pref_store.h index 6cfbcee3..9a69c7cb 100644 --- a/components/prefs/writeable_pref_store.h +++ b/components/prefs/writeable_pref_store.h
@@ -8,7 +8,9 @@ #include <stdint.h> #include <memory> +#include <set> #include <string> +#include <vector> #include "base/macros.h" #include "components/prefs/pref_store.h" @@ -46,13 +48,25 @@ virtual bool GetMutableValue(const std::string& key, base::Value** result) = 0; - // Triggers a value changed notification. This function needs to be called - // if one retrieves a list or dictionary with GetMutableValue and change its - // value. SetValue takes care of notifications itself. Note that - // ReportValueChanged will trigger notifications even if nothing has changed. - // |flags| is a bitmask of PrefWriteFlags. + // Triggers a value changed notification. This function or + // ReportSubValuesChanged needs to be called if one retrieves a list or + // dictionary with GetMutableValue and change its value. SetValue takes care + // of notifications itself. Note that ReportValueChanged will trigger + // notifications even if nothing has changed. |flags| is a bitmask of + // PrefWriteFlags. virtual void ReportValueChanged(const std::string& key, uint32_t flags) = 0; + // Triggers a value changed notification for |path_components| in the |key| + // pref. This function or ReportValueChanged needs to be called if one + // retrieves a list or dictionary with GetMutableValue and change its value. + // SetValue takes care of notifications itself. Note that + // ReportSubValuesChanged will trigger notifications even if nothing has + // changed. |flags| is a bitmask of PrefWriteFlags. + virtual void ReportSubValuesChanged( + const std::string& key, + std::set<std::vector<std::string>> path_components, + uint32_t flags); + // Same as SetValue, but doesn't generate notifications. This is used by // PrefService::GetMutableUserPref() in order to put empty entries // into the user pref store. Using SetValue is not an option since existing
diff --git a/components/proximity_auth/bluetooth_low_energy_connection_finder.cc b/components/proximity_auth/bluetooth_low_energy_connection_finder.cc index 8069b2db..6029eb46 100644 --- a/components/proximity_auth/bluetooth_low_energy_connection_finder.cc +++ b/components/proximity_auth/bluetooth_low_energy_connection_finder.cc
@@ -15,9 +15,11 @@ #include "base/memory/ptr_util.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/thread_task_runner_handle.h" +#include "components/cryptauth/background_eid_generator.h" #include "components/cryptauth/ble/bluetooth_low_energy_weave_client_connection.h" #include "components/cryptauth/bluetooth_throttler.h" #include "components/cryptauth/connection.h" +#include "components/cryptauth/raw_eid_generator.h" #include "components/proximity_auth/logging/logging.h" #include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/bluetooth/bluetooth_common.h" @@ -167,11 +169,11 @@ return false; std::string service_data_string(service_data->begin(), service_data->end()); - std::vector<std::string> nearest_eids = + std::vector<cryptauth::DataWithTimestamp> nearest_eids = eid_generator_->GenerateNearestEids(beacon_seeds_); - for (const std::string& eid : nearest_eids) { - if (eid == service_data_string) { - PA_LOG(INFO) << "Found a matching EID: " << eid; + for (const cryptauth::DataWithTimestamp& eid : nearest_eids) { + if (eid.data == service_data_string) { + PA_LOG(INFO) << "Found a matching EID: " << eid.DataInHex(); return true; } }
diff --git a/components/proximity_auth/bluetooth_low_energy_connection_finder_unittest.cc b/components/proximity_auth/bluetooth_low_energy_connection_finder_unittest.cc index e596b8d..24460f9a 100644 --- a/components/proximity_auth/bluetooth_low_energy_connection_finder_unittest.cc +++ b/components/proximity_auth/bluetooth_low_energy_connection_finder_unittest.cc
@@ -51,6 +51,7 @@ const char kEidForCurrentTimeQuantum[] = "\xab\xcd"; const char kEidForNextTimeQuantum[] = "\x56\x78"; const char kWrongEid[] = "\xff\xff"; +const int64_t kEidPeriodMs = 60 * 1000 * 15; // 15 minutes. std::vector<cryptauth::BeaconSeed> CreateBeaconSeeds() { std::vector<cryptauth::BeaconSeed> beacon_seeds; @@ -69,7 +70,7 @@ : connection_finder_(connection_finder) {} ~FakeEidGenerator() override {} - std::vector<std::string> GenerateNearestEids( + std::vector<cryptauth::DataWithTimestamp> GenerateNearestEids( const std::vector<cryptauth::BeaconSeed>& beacon_seed) const override; private: @@ -131,9 +132,19 @@ // Not declared in-line due to dependency on // MockBluetoothLowEnergyConnectionFinder. -std::vector<std::string> FakeEidGenerator::GenerateNearestEids( +std::vector<cryptauth::DataWithTimestamp> FakeEidGenerator::GenerateNearestEids( const std::vector<cryptauth::BeaconSeed>& beacon_seed) const { - return connection_finder_->nearest_eids(); + std::vector<std::string> nearest_eids = connection_finder_->nearest_eids(); + + std::vector<cryptauth::DataWithTimestamp> eid_data_with_timestamps; + int64_t start_of_period_ms = 0; + for (const std::string& eid : nearest_eids) { + eid_data_with_timestamps.push_back(cryptauth::DataWithTimestamp( + eid, start_of_period_ms, start_of_period_ms + kEidPeriodMs)); + start_of_period_ms += kEidPeriodMs; + } + + return eid_data_with_timestamps; } } // namespace
diff --git a/components/safe_browsing/csd.proto b/components/safe_browsing/csd.proto index 023abe4..a69cd10 100644 --- a/components/safe_browsing/csd.proto +++ b/components/safe_browsing/csd.proto
@@ -489,8 +489,9 @@ // comes last. repeated ReferrerChainEntry referrer_chain = 36; - // Whether DownloadAttribution Finch experiment is enabled for this ping. - optional bool download_attribution_finch_enabled = 39; + // Deprecated. + optional bool DEPRECATED_download_attribution_finch_enabled = 39 + [deprecated = true]; } message ReferrerChainEntry {
diff --git a/components/sync/engine_impl/cycle/sync_cycle.cc b/components/sync/engine_impl/cycle/sync_cycle.cc index c51eb0f..a8cf982 100644 --- a/components/sync/engine_impl/cycle/sync_cycle.cc +++ b/components/sync/engine_impl/cycle/sync_cycle.cc
@@ -13,11 +13,6 @@ namespace syncer { -// static -SyncCycle* SyncCycle::Build(SyncCycleContext* context, Delegate* delegate) { - return new SyncCycle(context, delegate); -} - SyncCycle::SyncCycle(SyncCycleContext* context, Delegate* delegate) : context_(context), delegate_(delegate) { status_controller_ = base::MakeUnique<StatusController>();
diff --git a/components/sync/engine_impl/cycle/sync_cycle.h b/components/sync/engine_impl/cycle/sync_cycle.h index 72c0789..0d8804e 100644 --- a/components/sync/engine_impl/cycle/sync_cycle.h +++ b/components/sync/engine_impl/cycle/sync_cycle.h
@@ -88,9 +88,7 @@ virtual ~Delegate() {} }; - // Build a cycle without a nudge tracker. Used for poll or configure type - // sync cycles. - static SyncCycle* Build(SyncCycleContext* context, Delegate* delegate); + SyncCycle(SyncCycleContext* context, Delegate* delegate); ~SyncCycle(); // Builds a thread-safe and read-only copy of the current cycle state. @@ -117,8 +115,6 @@ } private: - SyncCycle(SyncCycleContext* context, Delegate* delegate); - // The context for this cycle, guaranteed to outlive |this|. SyncCycleContext* const context_;
diff --git a/components/sync/engine_impl/get_updates_delegate.cc b/components/sync/engine_impl/get_updates_delegate.cc index 3af3285..2e54b54 100644 --- a/components/sync/engine_impl/get_updates_delegate.cc +++ b/components/sync/engine_impl/get_updates_delegate.cc
@@ -14,7 +14,7 @@ namespace { -void NonPassiveApplyUpdates(ModelTypeSet gu_types, +void NonPassiveApplyUpdates(const ModelTypeSet& gu_types, StatusController* status_controller, UpdateHandlerMap* update_handler_map) { for (const auto& kv : *update_handler_map) { @@ -24,7 +24,7 @@ } } -void PassiveApplyUpdates(ModelTypeSet gu_types, +void PassiveApplyUpdates(const ModelTypeSet& gu_types, StatusController* status_controller, UpdateHandlerMap* update_handler_map) { for (const auto& kv : *update_handler_map) { @@ -79,7 +79,7 @@ } void NormalGetUpdatesDelegate::ApplyUpdates( - ModelTypeSet gu_types, + const ModelTypeSet& gu_types, StatusController* status_controller, UpdateHandlerMap* update_handler_map) const { NonPassiveApplyUpdates(gu_types, status_controller, update_handler_map); @@ -105,7 +105,7 @@ } void ConfigureGetUpdatesDelegate::ApplyUpdates( - ModelTypeSet gu_types, + const ModelTypeSet& gu_types, StatusController* status_controller, UpdateHandlerMap* update_handler_map) const { PassiveApplyUpdates(gu_types, status_controller, update_handler_map); @@ -155,7 +155,7 @@ } void PollGetUpdatesDelegate::ApplyUpdates( - ModelTypeSet gu_types, + const ModelTypeSet& gu_types, StatusController* status_controller, UpdateHandlerMap* update_handler_map) const { NonPassiveApplyUpdates(gu_types, status_controller, update_handler_map);
diff --git a/components/sync/engine_impl/get_updates_delegate.h b/components/sync/engine_impl/get_updates_delegate.h index 56e986d..f02829e 100644 --- a/components/sync/engine_impl/get_updates_delegate.h +++ b/components/sync/engine_impl/get_updates_delegate.h
@@ -18,7 +18,7 @@ class GetUpdatesProcessor; -// Interface for GetUpdates functionality that dependends on the requested +// Interface for GetUpdates functionality that depends on the requested // GetUpdate type (normal, configuration, poll). The GetUpdatesProcessor is // given an appropriate GetUpdatesDelegate to handle type specific functionality // on construction. @@ -27,12 +27,12 @@ GetUpdatesDelegate(); virtual ~GetUpdatesDelegate() = 0; - // Populates GetUpdate message fields that depende on GetUpdates request type. + // Populates GetUpdate message fields that depend on GetUpdates request type. virtual void HelpPopulateGuMessage( sync_pb::GetUpdatesMessage* get_updates) const = 0; // Applies pending updates to non-control types. - virtual void ApplyUpdates(ModelTypeSet gu_types, + virtual void ApplyUpdates(const ModelTypeSet& gu_types, StatusController* status, UpdateHandlerMap* update_handler_map) const = 0; @@ -52,7 +52,7 @@ sync_pb::GetUpdatesMessage* get_updates) const override; // Applies pending updates on the appropriate data type threads. - void ApplyUpdates(ModelTypeSet gu_types, + void ApplyUpdates(const ModelTypeSet& gu_types, StatusController* status, UpdateHandlerMap* update_handler_map) const override; @@ -77,11 +77,11 @@ void HelpPopulateGuMessage( sync_pb::GetUpdatesMessage* get_updates) const override; - // Applies updates passively (ie. on the sync thread). + // Applies updates passively (i.e. on the sync thread). // // This is safe only if the ChangeProcessor is not listening to changes at // this time. - void ApplyUpdates(ModelTypeSet gu_types, + void ApplyUpdates(const ModelTypeSet& gu_types, StatusController* status, UpdateHandlerMap* update_handler_map) const override; @@ -109,7 +109,7 @@ sync_pb::GetUpdatesMessage* get_updates) const override; // Applies updates on the appropriate data type thread. - void ApplyUpdates(ModelTypeSet gu_types, + void ApplyUpdates(const ModelTypeSet& gu_types, StatusController* status, UpdateHandlerMap* update_handler_map) const override;
diff --git a/components/sync/engine_impl/get_updates_processor.cc b/components/sync/engine_impl/get_updates_processor.cc index b28c0e9e..85e4542 100644 --- a/components/sync/engine_impl/get_updates_processor.cc +++ b/components/sync/engine_impl/get_updates_processor.cc
@@ -90,7 +90,7 @@ // |gu_response| message. The map is returned in the |index_map| parameter. void PartitionProgressMarkersByType( const sync_pb::GetUpdatesResponse& gu_response, - ModelTypeSet request_types, + const ModelTypeSet& request_types, TypeToIndexMap* index_map) { for (int i = 0; i < gu_response.new_progress_marker_size(); ++i) { int field_number = gu_response.new_progress_marker(i).data_type_id(); @@ -111,7 +111,7 @@ void PartitionContextMutationsByType( const sync_pb::GetUpdatesResponse& gu_response, - ModelTypeSet request_types, + const ModelTypeSet& request_types, TypeToIndexMap* index_map) { for (int i = 0; i < gu_response.context_mutations_size(); ++i) { int field_number = gu_response.context_mutations(i).data_type_id(); @@ -181,7 +181,7 @@ } void GetUpdatesProcessor::PrepareGetUpdates( - ModelTypeSet gu_types, + const ModelTypeSet& gu_types, sync_pb::ClientToServerMessage* message) { sync_pb::GetUpdatesMessage* get_updates = message->mutable_get_updates(); @@ -277,7 +277,7 @@ SyncerError GetUpdatesProcessor::ProcessResponse( const sync_pb::GetUpdatesResponse& gu_response, - ModelTypeSet request_types, + const ModelTypeSet& request_types, StatusController* status) { status->increment_num_updates_downloaded_by(gu_response.entries_size()); @@ -300,7 +300,7 @@ } SyncerError GetUpdatesProcessor::ProcessGetUpdatesResponse( - ModelTypeSet gu_types, + const ModelTypeSet& gu_types, const sync_pb::GetUpdatesResponse& gu_response, StatusController* status_controller) { TypeSyncEntityMap updates_by_type; @@ -355,7 +355,7 @@ return SYNCER_OK; } -void GetUpdatesProcessor::ApplyUpdates(ModelTypeSet gu_types, +void GetUpdatesProcessor::ApplyUpdates(const ModelTypeSet& gu_types, StatusController* status_controller) { status_controller->set_get_updates_request_types(gu_types); delegate_.ApplyUpdates(gu_types, status_controller, update_handler_map_);
diff --git a/components/sync/engine_impl/get_updates_processor.h b/components/sync/engine_impl/get_updates_processor.h index ad834f4d..01fc2a0 100644 --- a/components/sync/engine_impl/get_updates_processor.h +++ b/components/sync/engine_impl/get_updates_processor.h
@@ -48,11 +48,12 @@ bool create_mobile_bookmarks_folder); // Applies any downloaded and processed updates. - void ApplyUpdates(ModelTypeSet gu_types, StatusController* status_controller); + void ApplyUpdates(const ModelTypeSet& gu_types, + StatusController* status_controller); private: // Populates a GetUpdates request message with per-type information. - void PrepareGetUpdates(ModelTypeSet gu_types, + void PrepareGetUpdates(const ModelTypeSet& gu_types, sync_pb::ClientToServerMessage* message); // Sends the specified message to the server and stores the response in a @@ -64,12 +65,12 @@ // Helper function for processing responses from the server. Defined here for // testing. SyncerError ProcessResponse(const sync_pb::GetUpdatesResponse& gu_response, - ModelTypeSet proto_request_types, + const ModelTypeSet& proto_request_types, StatusController* status); // Processes a GetUpdates responses for each type. SyncerError ProcessGetUpdatesResponse( - ModelTypeSet gu_types, + const ModelTypeSet& gu_types, const sync_pb::GetUpdatesResponse& gu_response, StatusController* status_controller);
diff --git a/components/sync/engine_impl/sync_scheduler_impl.cc b/components/sync/engine_impl/sync_scheduler_impl.cc index d9d72533..96e6d20 100644 --- a/components/sync/engine_impl/sync_scheduler_impl.cc +++ b/components/sync/engine_impl/sync_scheduler_impl.cc
@@ -239,9 +239,8 @@ void SyncSchedulerImpl::SendInitialSnapshot() { DCHECK(CalledOnValidThread()); - std::unique_ptr<SyncCycle> dummy(SyncCycle::Build(cycle_context_, this)); SyncCycleEvent event(SyncCycleEvent::STATUS_CHANGED); - event.snapshot = dummy->TakeSnapshot(); + event.snapshot = SyncCycle(cycle_context_, this).TakeSnapshot(); for (auto& observer : *cycle_context_->listeners()) observer.OnSyncCycleEvent(event); } @@ -429,9 +428,9 @@ DVLOG(2) << "Will run normal mode sync cycle with types " << ModelTypeSetToString(GetEnabledAndUnblockedTypes()); - std::unique_ptr<SyncCycle> cycle(SyncCycle::Build(cycle_context_, this)); + SyncCycle cycle(cycle_context_, this); bool success = syncer_->NormalSyncShare(GetEnabledAndUnblockedTypes(), - &nudge_tracker_, cycle.get()); + &nudge_tracker_, &cycle); if (success) { // That cycle took care of any outstanding work we had. @@ -447,7 +446,7 @@ AdjustPolling(UPDATE_INTERVAL); } } else { - HandleFailure(cycle->status_controller().model_neutral_state()); + HandleFailure(cycle.status_controller().model_neutral_state()); } } @@ -465,10 +464,10 @@ SDVLOG(2) << "Will run configure SyncShare with types " << ModelTypeSetToString( pending_configure_params_->types_to_download); - std::unique_ptr<SyncCycle> cycle(SyncCycle::Build(cycle_context_, this)); - bool success = syncer_->ConfigureSyncShare( - pending_configure_params_->types_to_download, - pending_configure_params_->source, cycle.get()); + SyncCycle cycle(cycle_context_, this); + bool success = + syncer_->ConfigureSyncShare(pending_configure_params_->types_to_download, + pending_configure_params_->source, &cycle); if (success) { SDVLOG(2) << "Configure succeeded."; @@ -476,7 +475,7 @@ pending_configure_params_.reset(); HandleSuccess(); } else { - HandleFailure(cycle->status_controller().model_neutral_state()); + HandleFailure(cycle.status_controller().model_neutral_state()); // Sync cycle might receive response from server that causes scheduler to // stop and draws pending_configure_params_ invalid. if (started_) @@ -493,10 +492,10 @@ return; } - std::unique_ptr<SyncCycle> cycle(SyncCycle::Build(cycle_context_, this)); - const bool success = syncer_->PostClearServerData(cycle.get()); + SyncCycle cycle(cycle_context_, this); + const bool success = syncer_->PostClearServerData(&cycle); if (!success) { - HandleFailure(cycle->status_controller().model_neutral_state()); + HandleFailure(cycle.status_controller().model_neutral_state()); return; } @@ -538,9 +537,8 @@ void SyncSchedulerImpl::DoPollSyncCycleJob() { SDVLOG(2) << "Polling with types " << ModelTypeSetToString(GetEnabledAndUnblockedTypes()); - std::unique_ptr<SyncCycle> cycle(SyncCycle::Build(cycle_context_, this)); - bool success = - syncer_->PollSyncShare(GetEnabledAndUnblockedTypes(), cycle.get()); + SyncCycle cycle(cycle_context_, this); + bool success = syncer_->PollSyncShare(GetEnabledAndUnblockedTypes(), &cycle); // Only restart the timer if the poll succeeded. Otherwise rely on normal // failure handling to retry with backoff. @@ -548,7 +546,7 @@ AdjustPolling(FORCE_RESET); HandleSuccess(); } else { - HandleFailure(cycle->status_controller().model_neutral_state()); + HandleFailure(cycle.status_controller().model_neutral_state()); } }
diff --git a/components/sync/engine_impl/sync_scheduler_impl_unittest.cc b/components/sync/engine_impl/sync_scheduler_impl_unittest.cc index d1d45bf..0a770bb 100644 --- a/components/sync/engine_impl/sync_scheduler_impl_unittest.cc +++ b/components/sync/engine_impl/sync_scheduler_impl_unittest.cc
@@ -51,7 +51,7 @@ MockSyncer(); MOCK_METHOD3(NormalSyncShare, bool(ModelTypeSet, NudgeTracker*, SyncCycle*)); MOCK_METHOD3(ConfigureSyncShare, - bool(ModelTypeSet, + bool(const ModelTypeSet&, sync_pb::GetUpdatesCallerInfo::GetUpdatesSource, SyncCycle*)); MOCK_METHOD2(PollSyncShare, bool(ModelTypeSet, SyncCycle*));
diff --git a/components/sync/engine_impl/syncer.cc b/components/sync/engine_impl/syncer.cc index 069f61d..ccdcb70 100644 --- a/components/sync/engine_impl/syncer.cc +++ b/components/sync/engine_impl/syncer.cc
@@ -64,7 +64,6 @@ } } - VLOG(1) << "Committing from types " << ModelTypeSetToString(request_types); CommitProcessor commit_processor( cycle->context()->model_type_registry()->commit_contributor_map()); SyncerError commit_result = BuildAndPostCommits(request_types, nudge_tracker, @@ -75,7 +74,7 @@ } bool Syncer::ConfigureSyncShare( - ModelTypeSet request_types, + const ModelTypeSet& request_types, sync_pb::GetUpdatesCallerInfo::GetUpdatesSource source, SyncCycle* cycle) { base::AutoReset<bool> is_syncing(&is_syncing_, true); @@ -86,10 +85,11 @@ // need to be stopped or during shutdown when all datatypes are stopped. When // it happens we should adjust set of types to download to only include // registered types. - request_types.RetainAll(cycle->context()->GetEnabledTypes()); - VLOG(1) << "Configuring types " << ModelTypeSetToString(request_types); + ModelTypeSet still_enabled_types = + Intersection(request_types, cycle->context()->GetEnabledTypes()); + VLOG(1) << "Configuring types " << ModelTypeSetToString(still_enabled_types); HandleCycleBegin(cycle); - DownloadAndApplyUpdates(&request_types, cycle, + DownloadAndApplyUpdates(&still_enabled_types, cycle, ConfigureGetUpdatesDelegate(source), kCreateMobileBookmarksFolder); return HandleCycleEnd(cycle, source); @@ -146,10 +146,12 @@ return !ExitRequested(); } -SyncerError Syncer::BuildAndPostCommits(ModelTypeSet request_types, +SyncerError Syncer::BuildAndPostCommits(const ModelTypeSet& request_types, NudgeTracker* nudge_tracker, SyncCycle* cycle, CommitProcessor* commit_processor) { + VLOG(1) << "Committing from types " << ModelTypeSetToString(request_types); + // The ExitRequested() check is unnecessary, since we should start getting // errors from the ServerConnectionManager if an exist has been requested. // However, it doesn't hurt to check it anyway.
diff --git a/components/sync/engine_impl/syncer.h b/components/sync/engine_impl/syncer.h index 2e86d8e0..455c7590 100644 --- a/components/sync/engine_impl/syncer.h +++ b/components/sync/engine_impl/syncer.h
@@ -58,7 +58,7 @@ // Returns: false if an error occurred and retries should backoff, true // otherwise. virtual bool ConfigureSyncShare( - ModelTypeSet request_types, + const ModelTypeSet& request_types, sync_pb::GetUpdatesCallerInfo::GetUpdatesSource source, SyncCycle* cycle); @@ -85,7 +85,7 @@ // number of unsynced and ready to commit items reaches zero or an error is // encountered. A request to exit early will be treated as an error and will // abort any blocking operations. - SyncerError BuildAndPostCommits(ModelTypeSet request_types, + SyncerError BuildAndPostCommits(const ModelTypeSet& request_types, NudgeTracker* nudge_tracker, SyncCycle* cycle, CommitProcessor* commit_processor);
diff --git a/components/sync/engine_impl/syncer_unittest.cc b/components/sync/engine_impl/syncer_unittest.cc index 01713f58..27702c2 100644 --- a/components/sync/engine_impl/syncer_unittest.cc +++ b/components/sync/engine_impl/syncer_unittest.cc
@@ -238,7 +238,9 @@ void OnBackedOffTypesChanged(ModelTypeSet backed_off_types) override {} void OnMigrationRequested(ModelTypeSet types) override {} - void ResetCycle() { cycle_.reset(SyncCycle::Build(context_.get(), this)); } + void ResetCycle() { + cycle_ = base::MakeUnique<SyncCycle>(context_.get(), this); + } bool SyncShareNudge() { ResetCycle();
diff --git a/components/test/data/update_client/updatecheck_diff_reply_1.xml b/components/test/data/update_client/updatecheck_diff_reply_1.xml index 1808f8d..093cb02c 100644 --- a/components/test/data/update_client/updatecheck_diff_reply_1.xml +++ b/components/test/data/update_client/updatecheck_diff_reply_1.xml
@@ -1,5 +1,5 @@ <?xml version='1.0' encoding='UTF-8'?> -<response protocol='3.0'> +<response protocol='3.1'> <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'> <updatecheck status='ok'> <urls>
diff --git a/components/test/data/update_client/updatecheck_diff_reply_2.xml b/components/test/data/update_client/updatecheck_diff_reply_2.xml index e68ac7da..8ea2f3b 100644 --- a/components/test/data/update_client/updatecheck_diff_reply_2.xml +++ b/components/test/data/update_client/updatecheck_diff_reply_2.xml
@@ -1,5 +1,5 @@ <?xml version='1.0' encoding='UTF-8'?> -<response protocol='3.0'> +<response protocol='3.1'> <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'> <updatecheck status='ok'> <urls> @@ -13,4 +13,4 @@ </manifest> </updatecheck> </app> -</response> \ No newline at end of file +</response>
diff --git a/components/test/data/update_client/updatecheck_diff_reply_3.xml b/components/test/data/update_client/updatecheck_diff_reply_3.xml index 46b39d5..faf14b0 100644 --- a/components/test/data/update_client/updatecheck_diff_reply_3.xml +++ b/components/test/data/update_client/updatecheck_diff_reply_3.xml
@@ -1,5 +1,5 @@ <?xml version='1.0' encoding='UTF-8'?> -<response protocol='3.0'> +<response protocol='3.1'> <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'> <updatecheck status='noupdate'/> </app>
diff --git a/components/test/data/update_client/updatecheck_reply_1.xml b/components/test/data/update_client/updatecheck_reply_1.xml index b741779..b6ff48f 100644 --- a/components/test/data/update_client/updatecheck_reply_1.xml +++ b/components/test/data/update_client/updatecheck_reply_1.xml
@@ -1,5 +1,5 @@ <?xml version='1.0' encoding='UTF-8'?> -<response protocol='3.0'> +<response protocol='3.1'> <app appid='jebgalgnebhfojomionfpkfelancnnkf'> <updatecheck status='ok'> <urls>
diff --git a/components/test/data/update_client/updatecheck_reply_2.xml b/components/test/data/update_client/updatecheck_reply_2.xml index ad539f0..04b39683 100644 --- a/components/test/data/update_client/updatecheck_reply_2.xml +++ b/components/test/data/update_client/updatecheck_reply_2.xml
@@ -1,5 +1,5 @@ <?xml version='1.0' encoding='UTF-8'?> -<response protocol='3.0'> +<response protocol='3.1'> <app appid='jebgalgnebhfojomionfpkfelancnnkf'> <updatecheck status='ok'> <urls> @@ -12,4 +12,4 @@ </manifest> </updatecheck> </app> -</response> \ No newline at end of file +</response>
diff --git a/components/test/data/update_client/updatecheck_reply_3.xml b/components/test/data/update_client/updatecheck_reply_3.xml index 7954003..819ad10 100644 --- a/components/test/data/update_client/updatecheck_reply_3.xml +++ b/components/test/data/update_client/updatecheck_reply_3.xml
@@ -1,5 +1,5 @@ <?xml version='1.0' encoding='UTF-8'?> -<response protocol='3.0'> +<response protocol='3.1'> <app appid='abagagagagagagagagagagagagagagag'> <updatecheck status='noupdate'/> </app>
diff --git a/components/test/data/update_client/updatecheck_reply_4.xml b/components/test/data/update_client/updatecheck_reply_4.xml index 48a2d9c..c2d44625 100644 --- a/components/test/data/update_client/updatecheck_reply_4.xml +++ b/components/test/data/update_client/updatecheck_reply_4.xml
@@ -1,5 +1,5 @@ <?xml version='1.0' encoding='UTF-8'?> -<response protocol='3.0'> +<response protocol='3.1'> <daystart elapsed_days='3383' /> <app appid='jebgalgnebhfojomionfpkfelancnnkf'> <updatecheck status='ok'>
diff --git a/components/test/data/update_client/updatecheck_reply_noupdate.xml b/components/test/data/update_client/updatecheck_reply_noupdate.xml index 7000f4aa..1e18fa1 100644 --- a/components/test/data/update_client/updatecheck_reply_noupdate.xml +++ b/components/test/data/update_client/updatecheck_reply_noupdate.xml
@@ -1,5 +1,5 @@ <?xml version='1.0' encoding='UTF-8'?> -<response protocol='3.0'> +<response protocol='3.1'> <app appid='jebgalgnebhfojomionfpkfelancnnkf'> <updatecheck status='noupdate'> <actions>
diff --git a/components/update_client/update_client_unittest.cc b/components/update_client/update_client_unittest.cc index 8a4d705..051c4f91 100644 --- a/components/update_client/update_client_unittest.cc +++ b/components/update_client/update_client_unittest.cc
@@ -360,7 +360,7 @@ Fake the following response: <?xml version='1.0' encoding='UTF-8'?> - <response protocol='3.0'> + <response protocol='3.1'> <app appid='jebgalgnebhfojomionfpkfelancnnkf'> <updatecheck status='ok'> <urls> @@ -574,7 +574,7 @@ Fake the following response: <?xml version='1.0' encoding='UTF-8'?> - <response protocol='3.0'> + <response protocol='3.1'> <app appid='jebgalgnebhfojomionfpkfelancnnkf'> <updatecheck status='ok'> <urls> @@ -846,7 +846,7 @@ Fake the following response: <?xml version='1.0' encoding='UTF-8'?> - <response protocol='3.0'> + <response protocol='3.1'> <app appid='jebgalgnebhfojomionfpkfelancnnkf'> <updatecheck status='ok'> <urls> @@ -1124,7 +1124,7 @@ /* Fake the following response: <?xml version='1.0' encoding='UTF-8'?> - <response protocol='3.0'> + <response protocol='3.1'> <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'> <updatecheck status='ok'> <urls> @@ -1164,7 +1164,7 @@ /* Fake the following response: <?xml version='1.0' encoding='UTF-8'?> - <response protocol='3.0'> + <response protocol='3.1'> <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'> <updatecheck status='ok'> <urls> @@ -1432,7 +1432,7 @@ Fake the following response: <?xml version='1.0' encoding='UTF-8'?> - <response protocol='3.0'> + <response protocol='3.1'> <app appid='jebgalgnebhfojomionfpkfelancnnkf'> <updatecheck status='ok'> <urls> @@ -1627,7 +1627,7 @@ /* Fake the following response: <?xml version='1.0' encoding='UTF-8'?> - <response protocol='3.0'> + <response protocol='3.1'> <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'> <updatecheck status='ok'> <urls> @@ -1669,7 +1669,7 @@ /* Fake the following response: <?xml version='1.0' encoding='UTF-8'?> - <response protocol='3.0'> + <response protocol='3.1'> <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'> <updatecheck status='ok'> <urls> @@ -2045,7 +2045,7 @@ Fake the following response: <?xml version='1.0' encoding='UTF-8'?> - <response protocol='3.0'> + <response protocol='3.1'> <app appid='jebgalgnebhfojomionfpkfelancnnkf'> <updatecheck status='ok'> <urls> @@ -2681,7 +2681,7 @@ Fake the following response: <?xml version='1.0' encoding='UTF-8'?> - <response protocol='3.0'> + <response protocol='3.1'> <app appid='jebgalgnebhfojomionfpkfelancnnkf'> <updatecheck status='ok'> <urls> @@ -2890,4 +2890,108 @@ update_client->RemoveObserver(&observer); } +// Tests the scenario where the update check fails. +TEST_F(UpdateClientTest, OneCrxUpdateCheckFails) { + class DataCallbackFake { + public: + static void Callback(const std::vector<std::string>& ids, + std::vector<CrxComponent>* components) { + CrxComponent crx; + crx.name = "test_jebg"; + crx.pk_hash.assign(jebg_hash, jebg_hash + arraysize(jebg_hash)); + crx.version = base::Version("0.9"); + crx.installer = base::MakeShared<TestInstaller>(); + components->push_back(crx); + } + }; + + class CompletionCallbackFake { + public: + static void Callback(const base::Closure& quit_closure, Error error) { + EXPECT_EQ(Error::UPDATE_CHECK_ERROR, error); + quit_closure.Run(); + } + }; + + class FakeUpdateChecker : public UpdateChecker { + public: + static std::unique_ptr<UpdateChecker> Create( + const scoped_refptr<Configurator>& config, + PersistedData* metadata) { + return base::MakeUnique<FakeUpdateChecker>(); + } + + bool CheckForUpdates( + const std::vector<std::string>& ids_to_check, + const IdToComponentPtrMap& components, + const std::string& additional_attributes, + bool enabled_component_updates, + const UpdateCheckCallback& update_check_callback) override { + EXPECT_TRUE(enabled_component_updates); + EXPECT_EQ(1u, ids_to_check.size()); + const std::string id = "jebgalgnebhfojomionfpkfelancnnkf"; + EXPECT_EQ(id, ids_to_check.front()); + EXPECT_EQ(1u, components.count(id)); + + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(update_check_callback, -1, 0)); + + return true; + } + }; + + class FakeCrxDownloader : public CrxDownloader { + public: + static std::unique_ptr<CrxDownloader> Create( + bool is_background_download, + net::URLRequestContextGetter* context_getter, + const scoped_refptr<base::SequencedTaskRunner>& task_runner) { + return base::MakeUnique<FakeCrxDownloader>(); + } + + FakeCrxDownloader() + : CrxDownloader(base::ThreadTaskRunnerHandle::Get(), nullptr) {} + + private: + void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); } + }; + + class FakePingManager : public FakePingManagerImpl { + public: + explicit FakePingManager(const scoped_refptr<Configurator>& config) + : FakePingManagerImpl(config) {} + ~FakePingManager() override { EXPECT_TRUE(ping_data().empty()); } + }; + + scoped_refptr<UpdateClient> update_client = + base::MakeShared<UpdateClientImpl>( + config(), base::MakeUnique<FakePingManager>(config()), + &FakeUpdateChecker::Create, &FakeCrxDownloader::Create); + + MockObserver observer; + InSequence seq; + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES, + "jebgalgnebhfojomionfpkfelancnnkf")) + .Times(1); + EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED, + "jebgalgnebhfojomionfpkfelancnnkf")) + .Times(1) + .WillOnce(Invoke([&update_client](Events event, const std::string& id) { + CrxUpdateItem item; + update_client->GetCrxUpdateState(id, &item); + EXPECT_EQ(ComponentState::kUpdateError, item.state); + })); + + update_client->AddObserver(&observer); + + const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"}; + update_client->Update( + ids, base::Bind(&DataCallbackFake::Callback), + base::Bind(&CompletionCallbackFake::Callback, quit_closure())); + + RunThreads(); + + update_client->RemoveObserver(&observer); +} + } // namespace update_client
diff --git a/components/update_client/update_engine.cc b/components/update_client/update_engine.cc index afef909..f56793f 100644 --- a/components/update_client/update_engine.cc +++ b/components/update_client/update_engine.cc
@@ -219,14 +219,6 @@ const auto& update_context = *it; DCHECK(update_context); - if (update_context->update_check_error) { - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, - base::Bind(&UpdateEngine::UpdateComplete, base::Unretained(this), it, - Error::UPDATE_CHECK_ERROR)); - return; - } - for (const auto& id : update_context->ids) update_context->component_queue.push(id); @@ -244,9 +236,12 @@ auto& queue = update_context->component_queue; if (queue.empty()) { + const Error error = update_context->update_check_error + ? Error::UPDATE_CHECK_ERROR + : Error::NONE; base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::Bind(&UpdateEngine::UpdateComplete, - base::Unretained(this), it, Error::NONE)); + base::Unretained(this), it, error)); return; }
diff --git a/components/update_client/update_response.cc b/components/update_client/update_response.cc index c2349c1..8fbc1ec0 100644 --- a/components/update_client/update_response.cc +++ b/components/update_client/update_response.cc
@@ -20,7 +20,6 @@ namespace update_client { -static const char* kExpectedResponseProtocol = "3.0"; const char UpdateResponse::Result::kCohort[] = "cohort"; const char UpdateResponse::Result::kCohortHint[] = "cohorthint"; const char UpdateResponse::Result::kCohortName[] = "cohortname"; @@ -367,11 +366,12 @@ } // Check for the response "protocol" attribute. - if (GetAttribute(root, "protocol") != kExpectedResponseProtocol) { + const auto protocol = GetAttribute(root, "protocol"); + if (protocol != kProtocolVersion) { ParseError( "Missing/incorrect protocol on response tag " - "(expected '%s')", - kExpectedResponseProtocol); + "(expected '%s', found '%s')", + kProtocolVersion, protocol.c_str()); return false; }
diff --git a/components/update_client/update_response.h b/components/update_client/update_response.h index bfce8e4..a517ca2 100644 --- a/components/update_client/update_response.h +++ b/components/update_client/update_response.h
@@ -15,6 +15,11 @@ namespace update_client { +// The protocol versions so far are: +// * Version 3.1: it changes how the run actions are serialized. +// * Version 3.0: it is the version implemented by the desktop updaters. +constexpr char kProtocolVersion[] = "3.1"; + // Parses responses for the update protocol version 3. // (https://github.com/google/omaha/blob/wiki/ServerProtocolV3.md) //
diff --git a/components/update_client/update_response_unittest.cc b/components/update_client/update_response_unittest.cc index 42e844c..c8535e1 100644 --- a/components/update_client/update_response_unittest.cc +++ b/components/update_client/update_response_unittest.cc
@@ -9,7 +9,7 @@ const char* kValidXml = "<?xml version='1.0' encoding='UTF-8'?>" - "<response protocol='3.0'>" + "<response protocol='3.1'>" " <app appid='12345'>" " <updatecheck status='ok'>" " <urls>" @@ -27,7 +27,7 @@ const char* valid_xml_with_hash = "<?xml version='1.0' encoding='UTF-8'?>" - "<response protocol='3.0'>" + "<response protocol='3.1'>" " <app appid='12345'>" " <updatecheck status='ok'>" " <urls>" @@ -45,7 +45,7 @@ const char* valid_xml_with_invalid_sizes = "<?xml version='1.0' encoding='UTF-8'?>" - "<response protocol='3.0'>" + "<response protocol='3.1'>" " <app appid='12345'>" " <updatecheck status='ok'>" " <urls>" @@ -67,7 +67,7 @@ const char* kInvalidValidXmlMissingCodebase = "<?xml version='1.0' encoding='UTF-8'?>" - "<response protocol='3.0'>" + "<response protocol='3.1'>" " <app appid='12345'>" " <updatecheck status='ok'>" " <urls>" @@ -84,7 +84,7 @@ const char* kInvalidValidXmlMissingManifest = "<?xml version='1.0' encoding='UTF-8'?>" - "<response protocol='3.0'>" + "<response protocol='3.1'>" " <app appid='12345'>" " <updatecheck status='ok'>" " <urls>" @@ -96,7 +96,7 @@ const char* kMissingAppId = "<?xml version='1.0'?>" - "<response protocol='3.0'>" + "<response protocol='3.1'>" " <app>" " <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'" " version='1.2.3.4'/>" @@ -105,7 +105,7 @@ const char* kInvalidCodebase = "<?xml version='1.0'?>" - "<response protocol='3.0'>" + "<response protocol='3.1'>" " <app appid='12345' status='ok'>" " <updatecheck codebase='example.com/extension_1.2.3.4.crx'" " version='1.2.3.4'/>" @@ -114,7 +114,7 @@ const char* kMissingVersion = "<?xml version='1.0'?>" - "<response protocol='3.0'>" + "<response protocol='3.1'>" " <app appid='12345' status='ok'>" " <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'/>" " </app>" @@ -122,7 +122,7 @@ const char* kInvalidVersion = "<?xml version='1.0'?>" - "<response protocol='3.0'>" + "<response protocol='3.1'>" " <app appid='12345' status='ok'>" " <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' " " version='1.2.3.a'/>" @@ -134,7 +134,7 @@ const char* kUsesNamespacePrefix = "<?xml version='1.0' encoding='UTF-8'?>" "<g:response xmlns:g='http://www.google.com/update2/response' " - "protocol='3.0'>" + "protocol='3.1'>" " <g:app appid='12345'>" " <g:updatecheck status='ok'>" " <g:urls>" @@ -153,7 +153,7 @@ // not cause problems. const char* kSimilarTagnames = "<?xml version='1.0' encoding='UTF-8'?>" - "<response xmlns:a='http://a' protocol='3.0'>" + "<response xmlns:a='http://a' protocol='3.1'>" " <a:app appid='12345'>" " <updatecheck status='ok'>" " <urls>" @@ -174,7 +174,7 @@ // Includes a <daystart> tag. const char* kWithDaystart = "<?xml version='1.0' encoding='UTF-8'?>" - "<response protocol='3.0'>" + "<response protocol='3.1'>" " <daystart elapsed_seconds='456'/>" " <app appid='12345'>" " <updatecheck status='ok'>" @@ -193,7 +193,7 @@ // Indicates no updates available - this should not be a parse error. const char* kNoUpdate = "<?xml version='1.0' encoding='UTF-8'?>" - "<response protocol='3.0'>" + "<response protocol='3.1'>" " <app appid='12345'>" " <updatecheck status='noupdate'/>" " </app>" @@ -202,7 +202,7 @@ // Includes two <app> tags, one with an error. const char* kTwoAppsOneError = "<?xml version='1.0' encoding='UTF-8'?>" - "<response protocol='3.0'>" + "<response protocol='3.1'>" " <app appid='aaaaaaaa' status='error-unknownApplication'>" " <updatecheck status='error-internal'/>" " </app>" @@ -223,7 +223,7 @@ // Includes two <app> tags, both of which set the cohort. const char* kTwoAppsSetCohort = "<?xml version='1.0' encoding='UTF-8'?>" - "<response protocol='3.0'>" + "<response protocol='3.1'>" " <app appid='aaaaaaaa' cohort='1:2q3/'>" " <updatecheck status='noupdate'/>" " </app>" @@ -244,7 +244,7 @@ // Includes a run action for an update check with status='ok'. const char* kUpdateCheckStatusOkWithRunAction = "<?xml version='1.0' encoding='UTF-8'?>" - "<response protocol='3.0'>" + "<response protocol='3.1'>" " <app appid='12345'>" " <updatecheck status='ok'>" " <urls>" @@ -266,7 +266,7 @@ // Includes a run action for an update check with status='noupdate'. const char* kUpdateCheckStatusNoUpdateWithRunAction = "<?xml version='1.0' encoding='UTF-8'?>" - "<response protocol='3.0'>" + "<response protocol='3.1'>" " <app appid='12345'>" " <updatecheck status='noupdate'>" " <actions>" @@ -279,7 +279,7 @@ // Includes a run action for an update check with status='error'. const char* kUpdateCheckStatusErrorWithRunAction = "<?xml version='1.0' encoding='UTF-8'?>" - "<response protocol='3.0'>" + "<response protocol='3.1'>" " <app appid='12345' status='ok'>" " <updatecheck status='error-osnotsupported'>" " <actions>"
diff --git a/components/update_client/utils.cc b/components/update_client/utils.cc index 93d7ac0..96e15a9 100644 --- a/components/update_client/utils.cc +++ b/components/update_client/utils.cc
@@ -32,6 +32,7 @@ #include "components/update_client/update_client.h" #include "components/update_client/update_client_errors.h" #include "components/update_client/update_query_params.h" +#include "components/update_client/update_response.h" #include "components/update_client/updater_state.h" #include "crypto/secure_hash.h" #include "crypto/sha2.h" @@ -95,9 +96,7 @@ } // namespace -// Builds a protocol message. The protocol versions so far are: -// * Version 3.1: it changes how the run actions are serialized. -// * Version 3.0: it is the version implemented by the desktop updaters. +// Builds a protocol message. std::string BuildProtocolRequest( const std::string& prod_id, const std::string& browser_version, @@ -108,9 +107,10 @@ const std::string& request_body, const std::string& additional_attributes, const std::unique_ptr<UpdaterState::Attributes>& updater_state_attributes) { - std::string request( + std::string request = base::StringPrintf( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" - "<request protocol=\"3.1\" "); + "<request protocol=\"%s\" ", + kProtocolVersion); if (!additional_attributes.empty()) base::StringAppendF(&request, "%s ", additional_attributes.c_str());
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn index b02a346..5916da3 100644 --- a/content/browser/BUILD.gn +++ b/content/browser/BUILD.gn
@@ -995,6 +995,7 @@ "memory/swap_metrics_observer_linux.cc", "memory/swap_metrics_observer_linux.h", "memory/swap_metrics_observer_mac.cc", + "memory/swap_metrics_observer_mac.h", "memory/swap_metrics_observer_win.cc", "message_port_provider.cc", "mime_registry_impl.cc",
diff --git a/content/browser/browser_main_loop.cc b/content/browser/browser_main_loop.cc index b955f885..9f9d0a6 100644 --- a/content/browser/browser_main_loop.cc +++ b/content/browser/browser_main_loop.cc
@@ -1259,88 +1259,86 @@ service_manager_context_.reset(); mojo_ipc_support_.reset(); - { - base::ThreadRestrictions::ScopedAllowWait allow_wait_for_join; - - // Must be size_t so we can subtract from it. - for (size_t thread_id = BrowserThread::ID_COUNT - 1; - thread_id >= (BrowserThread::UI + 1); --thread_id) { - // Find the thread object we want to stop. Looping over all valid - // BrowserThread IDs and DCHECKing on a missing case in the switch - // statement helps avoid a mismatch between this code and the - // BrowserThread::ID enumeration. - // - // The destruction order is the reverse order of occurrence in the - // BrowserThread::ID list. The rationale for the order is as follows (need - // to be filled in a bit): - // - // - The IO thread is the only user of the CACHE thread. - // - // - The PROCESS_LAUNCHER thread must be stopped after IO in case - // the IO thread posted a task to terminate a process on the - // process launcher thread. - // - // - (Not sure why DB stops last.) - switch (thread_id) { - case BrowserThread::DB: { - TRACE_EVENT0("shutdown", "BrowserMainLoop::Subsystem:DBThread"); - ResetThread_DB(std::move(db_thread_)); - break; - } - case BrowserThread::FILE: { - TRACE_EVENT0("shutdown", "BrowserMainLoop::Subsystem:FileThread"); - // Clean up state that lives on or uses the FILE thread before it goes - // away. - save_file_manager_->Shutdown(); - ResetThread_FILE(std::move(file_thread_)); - break; - } - case BrowserThread::FILE_USER_BLOCKING: { - TRACE_EVENT0("shutdown", - "BrowserMainLoop::Subsystem:FileUserBlockingThread"); - ResetThread_FILE_USER_BLOCKING(std::move(file_user_blocking_thread_)); - break; - } - case BrowserThread::PROCESS_LAUNCHER: { - TRACE_EVENT0("shutdown", "BrowserMainLoop::Subsystem:LauncherThread"); - ResetThread_PROCESS_LAUNCHER(std::move(process_launcher_thread_)); - break; - } - case BrowserThread::CACHE: { - TRACE_EVENT0("shutdown", "BrowserMainLoop::Subsystem:CacheThread"); - ResetThread_CACHE(std::move(cache_thread_)); - break; - } - case BrowserThread::IO: { - TRACE_EVENT0("shutdown", "BrowserMainLoop::Subsystem:IOThread"); - ResetThread_IO(std::move(io_thread_)); - break; - } - case BrowserThread::UI: - case BrowserThread::ID_COUNT: - NOTREACHED(); - break; + // Must be size_t so we can subtract from it. + for (size_t thread_id = BrowserThread::ID_COUNT - 1; + thread_id >= (BrowserThread::UI + 1); + --thread_id) { + // Find the thread object we want to stop. Looping over all valid + // BrowserThread IDs and DCHECKing on a missing case in the switch + // statement helps avoid a mismatch between this code and the + // BrowserThread::ID enumeration. + // + // The destruction order is the reverse order of occurrence in the + // BrowserThread::ID list. The rationale for the order is as + // follows (need to be filled in a bit): + // + // + // - The IO thread is the only user of the CACHE thread. + // + // - The PROCESS_LAUNCHER thread must be stopped after IO in case + // the IO thread posted a task to terminate a process on the + // process launcher thread. + // + // - (Not sure why DB stops last.) + switch (thread_id) { + case BrowserThread::DB: { + TRACE_EVENT0("shutdown", "BrowserMainLoop::Subsystem:DBThread"); + ResetThread_DB(std::move(db_thread_)); + break; } + case BrowserThread::FILE: { + TRACE_EVENT0("shutdown", "BrowserMainLoop::Subsystem:FileThread"); + // Clean up state that lives on or uses the FILE thread before it goes + // away. + save_file_manager_->Shutdown(); + ResetThread_FILE(std::move(file_thread_)); + break; + } + case BrowserThread::FILE_USER_BLOCKING: { + TRACE_EVENT0("shutdown", + "BrowserMainLoop::Subsystem:FileUserBlockingThread"); + ResetThread_FILE_USER_BLOCKING(std::move(file_user_blocking_thread_)); + break; + } + case BrowserThread::PROCESS_LAUNCHER: { + TRACE_EVENT0("shutdown", "BrowserMainLoop::Subsystem:LauncherThread"); + ResetThread_PROCESS_LAUNCHER(std::move(process_launcher_thread_)); + break; + } + case BrowserThread::CACHE: { + TRACE_EVENT0("shutdown", "BrowserMainLoop::Subsystem:CacheThread"); + ResetThread_CACHE(std::move(cache_thread_)); + break; + } + case BrowserThread::IO: { + TRACE_EVENT0("shutdown", "BrowserMainLoop::Subsystem:IOThread"); + ResetThread_IO(std::move(io_thread_)); + break; + } + case BrowserThread::UI: + case BrowserThread::ID_COUNT: + NOTREACHED(); + break; } - { - TRACE_EVENT0("shutdown", "BrowserMainLoop::Subsystem:IndexedDBThread"); - ResetThread_IndexedDb(std::move(indexed_db_thread_)); - } + } + { + TRACE_EVENT0("shutdown", "BrowserMainLoop::Subsystem:IndexedDBThread"); + ResetThread_IndexedDb(std::move(indexed_db_thread_)); + } - // Close the blocking I/O pool after the other threads. Other threads such - // as the I/O thread may need to schedule work like closing files or - // flushing data during shutdown, so the blocking pool needs to be - // available. There may also be slow operations pending that will blcok - // shutdown, so closing it here (which will block until required operations - // are complete) gives more head start for those operations to finish. - { - TRACE_EVENT0("shutdown", "BrowserMainLoop::Subsystem:ThreadPool"); - BrowserThreadImpl::ShutdownThreadPool(); - } - { - TRACE_EVENT0("shutdown", "BrowserMainLoop::Subsystem:TaskScheduler"); - base::TaskScheduler::GetInstance()->Shutdown(); - } + // Close the blocking I/O pool after the other threads. Other threads such + // as the I/O thread may need to schedule work like closing files or flushing + // data during shutdown, so the blocking pool needs to be available. There + // may also be slow operations pending that will blcok shutdown, so closing + // it here (which will block until required operations are complete) gives + // more head start for those operations to finish. + { + TRACE_EVENT0("shutdown", "BrowserMainLoop::Subsystem:ThreadPool"); + BrowserThreadImpl::ShutdownThreadPool(); + } + { + TRACE_EVENT0("shutdown", "BrowserMainLoop::Subsystem:TaskScheduler"); + base::TaskScheduler::GetInstance()->Shutdown(); } // Must happen after the IO thread is shutdown since this may be accessed from
diff --git a/content/browser/compositor/gpu_process_transport_factory.cc b/content/browser/compositor/gpu_process_transport_factory.cc index dbd83c60..23ba5e3 100644 --- a/content/browser/compositor/gpu_process_transport_factory.cc +++ b/content/browser/compositor/gpu_process_transport_factory.cc
@@ -884,7 +884,8 @@ data->surface_handle = widget; #else gpu::GpuSurfaceTracker* tracker = gpu::GpuSurfaceTracker::Get(); - data->surface_handle = tracker->AddSurfaceForNativeWidget(widget); + data->surface_handle = tracker->AddSurfaceForNativeWidget( + gpu::GpuSurfaceTracker::SurfaceRecord(widget)); #endif }
diff --git a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc index 111d755..286e78b 100644 --- a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc +++ b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
@@ -961,7 +961,6 @@ IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, CrossSiteCrash) { set_agent_host_can_close(); - host_resolver()->AddRule("*", "127.0.0.1"); content::SetupCrossSiteRedirector(embedded_test_server()); ASSERT_TRUE(embedded_test_server()->Start());
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc index 485c021..24c8b15 100644 --- a/content/browser/frame_host/render_frame_host_impl.cc +++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -105,6 +105,7 @@ #include "media/media_features.h" #include "media/mojo/interfaces/media_service.mojom.h" #include "media/mojo/interfaces/remoting.mojom.h" +#include "media/mojo/services/media_interface_provider.h" #include "mojo/public/cpp/bindings/associated_interface_ptr.h" #include "mojo/public/cpp/bindings/strong_binding.h" #include "mojo/public/cpp/system/data_pipe.h" @@ -3828,23 +3829,40 @@ } #if defined(OS_ANDROID) + +class RenderFrameHostImpl::JavaInterfaceProvider + : public service_manager::mojom::InterfaceProvider { + public: + JavaInterfaceProvider( + const service_manager::BinderRegistry::Binder& bind_callback, + service_manager::mojom::InterfaceProviderRequest request) + : bind_callback_(bind_callback), binding_(this, std::move(request)) {} + ~JavaInterfaceProvider() override = default; + + private: + // service_manager::mojom::INterfaceProvider: + void GetInterface(const std::string& interface_name, + mojo::ScopedMessagePipeHandle handle) override { + bind_callback_.Run(interface_name, std::move(handle)); + } + + service_manager::BinderRegistry::Binder bind_callback_; + mojo::Binding<service_manager::mojom::InterfaceProvider> binding_; + + DISALLOW_COPY_AND_ASSIGN(JavaInterfaceProvider); +}; + base::android::ScopedJavaLocalRef<jobject> RenderFrameHostImpl::GetJavaRenderFrameHost() { RenderFrameHostAndroid* render_frame_host_android = static_cast<RenderFrameHostAndroid*>( GetUserData(kRenderFrameHostAndroidKey)); if (!render_frame_host_android) { - java_interface_registry_ = - base::MakeUnique<service_manager::InterfaceRegistry>( - "RenderFrameHost Java"); service_manager::mojom::InterfaceProviderPtr interface_provider_ptr; - java_interface_registry_->set_default_binder( + java_interface_registry_ = base::MakeUnique<JavaInterfaceProvider>( base::Bind(&RenderFrameHostImpl::ForwardGetInterfaceToRenderFrame, - weak_ptr_factory_.GetWeakPtr())); - java_interface_registry_->Bind( - mojo::MakeRequest(&interface_provider_ptr), service_manager::Identity(), - service_manager::InterfaceProviderSpec(), service_manager::Identity(), - service_manager::InterfaceProviderSpec()); + weak_ptr_factory_.GetWeakPtr()), + mojo::MakeRequest(&interface_provider_ptr)); render_frame_host_android = new RenderFrameHostAndroid(this, std::move(interface_provider_ptr)); SetUserData(kRenderFrameHostAndroidKey, render_frame_host_android);
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h index 43670f135..b34bf56c 100644 --- a/content/browser/frame_host/render_frame_host_impl.h +++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -47,7 +47,6 @@ #include "mojo/public/cpp/system/data_pipe.h" #include "net/http/http_response_headers.h" #include "services/service_manager/public/cpp/interface_factory.h" -#include "services/service_manager/public/cpp/interface_registry.h" #include "third_party/WebKit/public/platform/WebFocusType.h" #include "third_party/WebKit/public/platform/WebInsecureRequestPolicy.h" #include "third_party/WebKit/public/web/WebTextDirection.h" @@ -1165,9 +1164,6 @@ // media::mojom::InterfaceFactory calls to the remote "media" service. std::unique_ptr<MediaInterfaceProxy> media_interface_proxy_; - std::vector<std::unique_ptr<service_manager::InterfaceRegistry>> - media_registries_; - std::unique_ptr<AssociatedInterfaceProviderImpl> remote_associated_interfaces_; @@ -1187,7 +1183,8 @@ // An InterfaceRegistry that forwards interface requests from Java to the // RenderFrame. This provides access to interfaces implemented in the renderer // to Java code in the browser process. - std::unique_ptr<service_manager::InterfaceRegistry> java_interface_registry_; + class JavaInterfaceProvider; + std::unique_ptr<JavaInterfaceProvider> java_interface_registry_; #endif mojo::BindingSet<service_manager::mojom::InterfaceProvider>
diff --git a/content/browser/frame_host/render_frame_host_manager_browsertest.cc b/content/browser/frame_host/render_frame_host_manager_browsertest.cc index 12cac29a..31a2bd2 100644 --- a/content/browser/frame_host/render_frame_host_manager_browsertest.cc +++ b/content/browser/frame_host/render_frame_host_manager_browsertest.cc
@@ -160,7 +160,6 @@ } void StartEmbeddedServer() { - host_resolver()->AddRule("*", "127.0.0.1"); SetupCrossSiteRedirector(embedded_test_server()); ASSERT_TRUE(embedded_test_server()->Start()); }
diff --git a/content/browser/media/android/browser_surface_view_manager.cc b/content/browser/media/android/browser_surface_view_manager.cc index 483ec0a..dc854ee 100644 --- a/content/browser/media/android/browser_surface_view_manager.cc +++ b/content/browser/media/android/browser_surface_view_manager.cc
@@ -41,7 +41,6 @@ if (surface.IsEmpty()) { DCHECK_NE(surface_id_, media::SurfaceManager::kNoSurfaceID); gpu::GpuSurfaceTracker::Get()->RemoveSurface(surface_id_); - gpu::GpuSurfaceTracker::Get()->UnregisterViewSurface(surface_id_); SendDestroyingVideoSurface(surface_id_); surface_id_ = media::SurfaceManager::kNoSurfaceID; } else { @@ -49,9 +48,8 @@ // lookup will go through the Android specific path and get the java // surface directly, so there's no need to add a valid native widget here. surface_id_ = gpu::GpuSurfaceTracker::Get()->AddSurfaceForNativeWidget( - gfx::kNullAcceleratedWidget); - gpu::GpuSurfaceTracker::GetInstance()->RegisterViewSurface( - surface_id_, surface.j_surface().obj()); + gpu::GpuSurfaceTracker::SurfaceRecord(gfx::kNullAcceleratedWidget, + surface.j_surface().obj())); SendSurfaceID(surface_id_); } }
diff --git a/content/browser/media/media_interface_proxy.cc b/content/browser/media/media_interface_proxy.cc index 0c9342d..a7b3be0 100644 --- a/content/browser/media/media_interface_proxy.cc +++ b/content/browser/media/media_interface_proxy.cc
@@ -13,6 +13,7 @@ #include "content/public/common/content_client.h" #include "content/public/common/service_manager_connection.h" #include "media/mojo/interfaces/media_service.mojom.h" +#include "media/mojo/services/media_interface_provider.h" #include "services/service_manager/public/cpp/connector.h" #if defined(ENABLE_MOJO_CDM) @@ -98,29 +99,24 @@ DCHECK(!interface_factory_ptr_); // Register frame services. - auto registry = - base::MakeUnique<service_manager::InterfaceRegistry>(std::string()); + service_manager::mojom::InterfaceProviderPtr interfaces; + // TODO(xhwang): Replace this InterfaceProvider with a dedicated media host + // interface. See http://crbug.com/660573 + auto provider = base::MakeUnique<media::MediaInterfaceProvider>( + mojo::MakeRequest(&interfaces)); #if defined(ENABLE_MOJO_CDM) // TODO(slan): Wrap these into a RenderFrame specific ProvisionFetcher impl. net::URLRequestContextGetter* context_getter = BrowserContext::GetDefaultStoragePartition( render_frame_host_->GetProcess()->GetBrowserContext()) ->GetURLRequestContext(); - registry->AddInterface( + provider->registry()->AddInterface( base::Bind(&ProvisionFetcherImpl::Create, context_getter)); #endif // defined(ENABLE_MOJO_CDM) GetContentClient()->browser()->ExposeInterfacesToMediaService( - registry.get(), render_frame_host_); + provider->registry(), render_frame_host_); - // Get frame service InterfaceProvider. - // TODO(xhwang): Replace this InterfaceProvider with a dedicated media host - // interface. See http://crbug.com/660573 - service_manager::mojom::InterfaceProviderPtr interfaces; - registry->Bind(MakeRequest(&interfaces), service_manager::Identity(), - service_manager::InterfaceProviderSpec(), - service_manager::Identity(), - service_manager::InterfaceProviderSpec()); - media_registries_.push_back(std::move(registry)); + media_registries_.push_back(std::move(provider)); // TODO(slan): Use the BrowserContext Connector instead. See crbug.com/638950. media::mojom::MediaServicePtr media_service;
diff --git a/content/browser/media/media_interface_proxy.h b/content/browser/media/media_interface_proxy.h index 0883700..e79b089f 100644 --- a/content/browser/media/media_interface_proxy.h +++ b/content/browser/media/media_interface_proxy.h
@@ -12,7 +12,10 @@ #include "base/threading/thread_checker.h" #include "media/mojo/interfaces/interface_factory.mojom.h" #include "mojo/public/cpp/bindings/binding.h" -#include "services/service_manager/public/cpp/interface_registry.h" + +namespace media { +class MediaInterfaceProvider; +} namespace content { @@ -51,8 +54,7 @@ // TODO(xhwang): Replace InterfaceProvider with a dedicated host interface. // See http://crbug.com/660573 - std::vector<std::unique_ptr<service_manager::InterfaceRegistry>> - media_registries_; + std::vector<std::unique_ptr<media::MediaInterfaceProvider>> media_registries_; mojo::Binding<media::mojom::InterfaceFactory> binding_;
diff --git a/content/browser/memory/swap_metrics_observer_mac.cc b/content/browser/memory/swap_metrics_observer_mac.cc index d44b50fe..65f5bda 100644 --- a/content/browser/memory/swap_metrics_observer_mac.cc +++ b/content/browser/memory/swap_metrics_observer_mac.cc
@@ -2,14 +2,59 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "content/browser/memory/swap_metrics_observer.h" +#include "content/browser/memory/swap_metrics_observer_mac.h" + +#include <mach/mach.h> +#include <mach/mach_vm.h> + +#include "base/mac/mach_logging.h" +#include "base/metrics/histogram_macros.h" namespace content { // static SwapMetricsObserver* SwapMetricsObserver::GetInstance() { - // TODO(bashi): Implement SwapMetricsObserver for macOS. - return nullptr; + static SwapMetricsObserverMac* instance = new SwapMetricsObserverMac(); + return instance; +} + +SwapMetricsObserverMac::SwapMetricsObserverMac() : host_(mach_host_self()) {} + +SwapMetricsObserverMac::~SwapMetricsObserverMac() {} + +void SwapMetricsObserverMac::UpdateMetricsInternal(base::TimeDelta interval) { + vm_statistics64_data_t statistics; + mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; + kern_return_t result = + host_statistics64(host_.get(), HOST_VM_INFO64, + reinterpret_cast<host_info64_t>(&statistics), &count); + if (result != KERN_SUCCESS) { + MACH_DLOG(WARNING, result) << "host_statistics64"; + Stop(); + return; + } + DCHECK_EQ(HOST_VM_INFO64_COUNT, count); + + double swapins = statistics.swapins - last_swapins_; + double swapouts = statistics.swapouts - last_swapouts_; + double decompressions = statistics.decompressions - last_decompressions_; + double compressions = statistics.compressions - last_compressions_; + last_swapins_ = statistics.swapins; + last_swapouts_ = statistics.swapouts; + last_decompressions_ = statistics.decompressions; + last_compressions_ = statistics.compressions; + + if (interval.is_zero()) + return; + + UMA_HISTOGRAM_COUNTS_10000("Memory.Experimental.SwapInPerSecond", + swapins / interval.InSecondsF()); + UMA_HISTOGRAM_COUNTS_10000("Memory.Experimental.SwapOutPerSecond", + swapouts / interval.InSecondsF()); + UMA_HISTOGRAM_COUNTS_10000("Memory.Experimental.DecompressedPagesPerSecond", + decompressions / interval.InSecondsF()); + UMA_HISTOGRAM_COUNTS_10000("Memory.Experimental.CompressedPagesPerSecond", + compressions / interval.InSecondsF()); } } // namespace content
diff --git a/content/browser/memory/swap_metrics_observer_mac.h b/content/browser/memory/swap_metrics_observer_mac.h new file mode 100644 index 0000000..fd34e0a --- /dev/null +++ b/content/browser/memory/swap_metrics_observer_mac.h
@@ -0,0 +1,33 @@ +// 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 CONTENT_BROWSER_MEMORY_SWAP_METRICS_OBSERVER_MAC_H_ +#define CONTENT_BROWSER_MEMORY_SWAP_METRICS_OBSERVER_MAC_H_ + +#include "content/browser/memory/swap_metrics_observer.h" + +#include "base/mac/scoped_mach_port.h" + +namespace content { + +class SwapMetricsObserverMac : public SwapMetricsObserver { + public: + SwapMetricsObserverMac(); + ~SwapMetricsObserverMac() override; + + protected: + void UpdateMetricsInternal(base::TimeDelta interval) override; + + private: + base::mac::ScopedMachSendRight host_; + + uint64_t last_swapins_ = 0; + uint64_t last_swapouts_ = 0; + uint64_t last_decompressions_ = 0; + uint64_t last_compressions_ = 0; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_MEMORY_SWAP_METRICS_OBSERVER_MAC_H_
diff --git a/content/browser/renderer_host/compositor_impl_android.cc b/content/browser/renderer_host/compositor_impl_android.cc index c3e20e2d..9420ce9d 100644 --- a/content/browser/renderer_host/compositor_impl_android.cc +++ b/content/browser/renderer_host/compositor_impl_android.cc
@@ -486,8 +486,6 @@ tracker->RemoveSurface(surface_handle_); ANativeWindow_release(window_); window_ = NULL; - - tracker->UnregisterViewSurface(surface_handle_); surface_handle_ = gpu::kNullSurfaceHandle; } @@ -503,9 +501,9 @@ if (window) { window_ = window; ANativeWindow_acquire(window); - surface_handle_ = tracker->AddSurfaceForNativeWidget(window); // Register first, SetVisible() might create a CompositorFrameSink. - tracker->RegisterViewSurface(surface_handle_, surface); + surface_handle_ = tracker->AddSurfaceForNativeWidget( + gpu::GpuSurfaceTracker::SurfaceRecord(window, surface)); SetVisible(true); ANativeWindow_release(window); }
diff --git a/content/browser/service_manager/service_manager_context.cc b/content/browser/service_manager/service_manager_context.cc index 42f0624..0aa2176f 100644 --- a/content/browser/service_manager/service_manager_context.cc +++ b/content/browser/service_manager/service_manager_context.cc
@@ -357,6 +357,11 @@ packaged_services_connection_->Start(); ServiceManagerConnection::GetForProcess()->Start(); + + // Start the network service process as soon as possible, since it is critical + // to start up performance. + ServiceManagerConnection::GetForProcess()->GetConnector()->StartService( + mojom::kNetworkServiceName); } ServiceManagerContext::~ServiceManagerContext() {
diff --git a/content/browser/webrtc/webrtc_media_recorder_browsertest.cc b/content/browser/webrtc/webrtc_media_recorder_browsertest.cc index 7d945a9a3..7b9ec9981 100644 --- a/content/browser/webrtc/webrtc_media_recorder_browsertest.cc +++ b/content/browser/webrtc/webrtc_media_recorder_browsertest.cc
@@ -153,9 +153,9 @@ kMediaRecorderHtmlFile); } -#if defined(OS_ANDROID) && defined(ADDRESS_SANITIZER) -// Parametrizations 1/2 (VP8/VP9+disabled) time out under Android ASAN: -// https://crbug.com/693565. +#if defined(OS_ANDROID) +// These tests are flakily timing out on emulators (https://crbug.com/716691) +// and/or under Android ASAN (https://crbug.com/693565); #define MAYBE_PeerConnection DISABLED_PeerConnection #elif defined(OS_LINUX) && defined(THREAD_SANITIZER) // Flaky on Linux TSan, https://crbug.com/694373.
diff --git a/content/public/android/java/src/org/chromium/content/browser/SelectionPopupController.java b/content/public/android/java/src/org/chromium/content/browser/SelectionPopupController.java index df43628..33d1507 100644 --- a/content/public/android/java/src/org/chromium/content/browser/SelectionPopupController.java +++ b/content/public/android/java/src/org/chromium/content/browser/SelectionPopupController.java
@@ -1034,6 +1034,18 @@ return; } + // Do not allow classifier to shorten the selection. If the suggested selection is + // smaller than the original we throw away classification result and show the menu. + // TODO(amaralp): This was added to fix the SelectAll problem in + // http://crbug.com/714106. Once we know the cause of the original selection we can + // remove this check. + if (result.startAdjust > 0 || result.endAdjust < 0) { + mClassificationResult = null; + mPendingShowActionMode = false; + showActionModeOrClearOnFailure(); + return; + } + // The classificationresult is a property of the selection. Keep it even the action // mode has been dismissed. mClassificationResult = result;
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h index 0c587641..5313f43 100644 --- a/content/public/browser/content_browser_client.h +++ b/content/public/browser/content_browser_client.h
@@ -67,7 +67,6 @@ namespace service_manager { class BinderRegistry; -class InterfaceRegistry; class Service; struct ServiceInfo; } @@ -672,7 +671,7 @@ // Called when RenderFrameHostImpl connects to the Media service. Expose // interfaces to the service using |registry|. virtual void ExposeInterfacesToMediaService( - service_manager::InterfaceRegistry* registry, + service_manager::BinderRegistry* registry, RenderFrameHost* render_frame_host) {} // Allows to register browser Mojo interfaces exposed through the
diff --git a/content/renderer/dom_storage/dom_storage_dispatcher.cc b/content/renderer/dom_storage/dom_storage_dispatcher.cc index b693f93..b52367ad 100644 --- a/content/renderer/dom_storage/dom_storage_dispatcher.cc +++ b/content/renderer/dom_storage/dom_storage_dispatcher.cc
@@ -139,9 +139,17 @@ ~ProxyImpl() override {} - // Sudden termination is disabled when there are callbacks pending - // to more reliably commit changes during shutdown. void PushPendingCallback(const CompletionCallback& callback) { + // Terminate the renderer if an excessive number of calls are made, + // This is indicative of script in an infinite loop or being malicious. + // It's better to crash intentionally than by running the system OOM + // and interfering with everything else running in the system. + const int kMaxPendingCompletionCallbacks = 1000000; + if (pending_callbacks_.size() > kMaxPendingCompletionCallbacks) + CHECK(false) << "Too many pending DOMStorage calls."; + + // Sudden termination is disabled when there are callbacks pending + // to more reliably commit changes during shutdown. if (pending_callbacks_.empty()) blink::Platform::Current()->SuddenTerminationChanged(false); pending_callbacks_.push_back(callback);
diff --git a/content/renderer/input/main_thread_event_queue.cc b/content/renderer/input/main_thread_event_queue.cc index 94d1d49..0e2322d0 100644 --- a/content/renderer/input/main_thread_event_queue.cc +++ b/content/renderer/input/main_thread_event_queue.cc
@@ -16,6 +16,8 @@ namespace { const size_t kTenSeconds = 10 * 1000 * 1000; +const base::TimeDelta kMaxRafDelay = + base::TimeDelta::FromMilliseconds(5 * 1000); class QueuedClosure : public MainThreadEventQueueTask { public: @@ -217,7 +219,8 @@ allow_raf_aligned_input && base::FeatureList::IsEnabled(features::kRafAlignedMouseInputEvents)), main_task_runner_(main_task_runner), - renderer_scheduler_(renderer_scheduler) { + renderer_scheduler_(renderer_scheduler), + use_raf_fallback_timer_(true) { if (enable_non_blocking_due_to_main_thread_responsiveness_flag_) { std::string group = base::FieldTrialList::FindFullName( "MainThreadResponsivenessScrollIntervention"); @@ -236,6 +239,7 @@ base::TimeDelta::FromMilliseconds(threshold_ms); } } + raf_fallback_timer_.SetTaskRunner(main_task_runner); } MainThreadEventQueue::~MainThreadEventQueue() {} @@ -406,10 +410,17 @@ return touch_event.moved_beyond_slop_region && !event->originallyCancelable(); } +void MainThreadEventQueue::RafFallbackTimerFired() { + UMA_HISTOGRAM_BOOLEAN("Event.MainThreadEventQueue.FlushQueueNoBeginMainFrame", + true); + DispatchRafAlignedInput(base::TimeTicks::Now()); +} + void MainThreadEventQueue::DispatchRafAlignedInput(base::TimeTicks frame_time) { if (IsRafAlignedInputDisabled()) return; + raf_fallback_timer_.Stop(); size_t queue_size_at_start; // Record the queue size so that we only process @@ -529,6 +540,11 @@ void MainThreadEventQueue::SetNeedsMainFrame() { if (main_task_runner_->BelongsToCurrentThread()) { + if (use_raf_fallback_timer_) { + raf_fallback_timer_.Start( + FROM_HERE, kMaxRafDelay, + base::Bind(&MainThreadEventQueue::RafFallbackTimerFired, this)); + } if (client_) client_->SetNeedsMainFrame(); return;
diff --git a/content/renderer/input/main_thread_event_queue.h b/content/renderer/input/main_thread_event_queue.h index 5001f04..8e1e537 100644 --- a/content/renderer/input/main_thread_event_queue.h +++ b/content/renderer/input/main_thread_event_queue.h
@@ -118,6 +118,11 @@ bool IsRafAlignedInputDisabled() const; bool IsRafAlignedEvent( const std::unique_ptr<MainThreadEventQueueTask>& item) const; + void RafFallbackTimerFired(); + + void set_use_raf_fallback_timer(bool use_timer) { + use_raf_fallback_timer_ = use_timer; + } friend class QueuedWebInputEvent; friend class MainThreadEventQueueTest; @@ -149,6 +154,8 @@ scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; blink::scheduler::RendererScheduler* renderer_scheduler_; + base::OneShotTimer raf_fallback_timer_; + bool use_raf_fallback_timer_; DISALLOW_COPY_AND_ASSIGN(MainThreadEventQueue); };
diff --git a/content/renderer/input/main_thread_event_queue_unittest.cc b/content/renderer/input/main_thread_event_queue_unittest.cc index 7ab0207..d162ca0 100644 --- a/content/renderer/input/main_thread_event_queue_unittest.cc +++ b/content/renderer/input/main_thread_event_queue_unittest.cc
@@ -118,6 +118,7 @@ void SetUp() override { queue_ = new MainThreadEventQueue(this, main_task_runner_, &renderer_scheduler_, true); + queue_->set_use_raf_fallback_timer(false); } bool HandleEvent(WebInputEvent& event, InputEventAckState ack_result) {
diff --git a/content/renderer/media_recorder/video_track_recorder.cc b/content/renderer/media_recorder/video_track_recorder.cc index 49def8d1..96cb15c59 100644 --- a/content/renderer/media_recorder/video_track_recorder.cc +++ b/content/renderer/media_recorder/video_track_recorder.cc
@@ -109,11 +109,6 @@ return; #endif -#if defined(OS_ANDROID) - // See https://crbug.com/653864. - return; -#endif - content::RenderThreadImpl* const render_thread_impl = content::RenderThreadImpl::current(); if (!render_thread_impl) { @@ -131,13 +126,18 @@ const auto vea_supported_profiles = gpu_factories->GetVideoEncodeAcceleratorSupportedProfiles(); for (const auto& supported_profile : vea_supported_profiles) { + const media::VideoCodecProfile codec = supported_profile.profile; +#if defined(OS_ANDROID) + // TODO(mcasas): enable other codecs, https://crbug.com/638664. + if (codec < media::VP8PROFILE_MIN || codec > media::VP8PROFILE_MAX) + continue; +#endif for (auto& codec_id_and_profile : kPreferredCodecIdAndVEAProfiles) { - if (supported_profile.profile >= codec_id_and_profile.min_profile && - supported_profile.profile <= codec_id_and_profile.max_profile) { - DVLOG(2) << "Accelerated codec found: " - << media::GetProfileName(supported_profile.profile); - codec_id_to_profile_.insert(std::make_pair( - codec_id_and_profile.codec_id, supported_profile.profile)); + if (codec >= codec_id_and_profile.min_profile && + codec <= codec_id_and_profile.max_profile) { + DVLOG(2) << "Accelerated codec found: " << media::GetProfileName(codec); + codec_id_to_profile_.insert( + std::make_pair(codec_id_and_profile.codec_id, codec)); } } }
diff --git a/content/renderer/media_recorder/video_track_recorder.h b/content/renderer/media_recorder/video_track_recorder.h index 53a871f..e53cef4 100644 --- a/content/renderer/media_recorder/video_track_recorder.h +++ b/content/renderer/media_recorder/video_track_recorder.h
@@ -32,8 +32,13 @@ } // namespace media namespace video_track_recorder { +#if defined(OS_ANDROID) +const int kVEAEncoderMinResolutionWidth = 176; +const int kVEAEncoderMinResolutionHeight = 144; +#else const int kVEAEncoderMinResolutionWidth = 640; const int kVEAEncoderMinResolutionHeight = 480; +#endif } // namespace video_track_recorder namespace content {
diff --git a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py index 496c156..f628257 100644 --- a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py +++ b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
@@ -719,6 +719,12 @@ self.Flaky('deqp/functional/gles3/multisample.html', ['linux', ('nvidia', 0x104a)], bug=714207) + # Any of these tests become flaky if they follow + # conformance2/samplers/samplers.html and if virtualized GL + # contexts are being used. + self.Flaky('conformance2/textures/canvas_sub_rectangle/*', + ['linux', 'nvidia'], bug=694359) + # This test is flaky both with and without ANGLE. self.Flaky('deqp/functional/gles3/texturespecification/' + 'random_teximage2d_2d.html',
diff --git a/device/udev_linux/BUILD.gn b/device/udev_linux/BUILD.gn index 0b6f60e44..853f58c0 100644 --- a/device/udev_linux/BUILD.gn +++ b/device/udev_linux/BUILD.gn
@@ -18,6 +18,8 @@ "udev_linux.h", "udev_loader.cc", "udev_loader.h", + "udev_watcher.cc", + "udev_watcher.h", ] deps = [
diff --git a/device/udev_linux/udev_watcher.cc b/device/udev_linux/udev_watcher.cc new file mode 100644 index 0000000..d9e490de --- /dev/null +++ b/device/udev_linux/udev_watcher.cc
@@ -0,0 +1,98 @@ +// 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 "device/udev_linux/udev_watcher.h" + +#include "base/bind.h" +#include "base/memory/ptr_util.h" + +namespace device { + +UdevWatcher::Observer::~Observer() = default; + +void UdevWatcher::Observer::OnDeviceAdded(ScopedUdevDevicePtr device) {} + +void UdevWatcher::Observer::OnDeviceRemoved(ScopedUdevDevicePtr device) {} + +std::unique_ptr<UdevWatcher> UdevWatcher::StartWatching(Observer* observer) { + ScopedUdevPtr udev(udev_new()); + if (!udev) { + LOG(ERROR) << "Failed to initialize udev."; + return nullptr; + } + + ScopedUdevMonitorPtr udev_monitor( + udev_monitor_new_from_netlink(udev.get(), "udev")); + if (!udev_monitor) { + LOG(ERROR) << "Failed to initialize a udev monitor."; + return nullptr; + } + + if (udev_monitor_enable_receiving(udev_monitor.get()) != 0) { + LOG(ERROR) << "Failed to enable receiving udev events."; + return nullptr; + } + + int monitor_fd = udev_monitor_get_fd(udev_monitor.get()); + if (monitor_fd < 0) { + LOG(ERROR) << "Udev monitor file descriptor unavailable."; + return nullptr; + } + + return base::WrapUnique(new UdevWatcher( + std::move(udev), std::move(udev_monitor), monitor_fd, observer)); +} + +UdevWatcher::~UdevWatcher() { + DCHECK(sequence_checker_.CalledOnValidSequence()); +}; + +void UdevWatcher::EnumerateExistingDevices() { + DCHECK(sequence_checker_.CalledOnValidSequence()); + ScopedUdevEnumeratePtr enumerate(udev_enumerate_new(udev_.get())); + if (!enumerate) { + LOG(ERROR) << "Failed to initialize a udev enumerator."; + return; + } + + if (udev_enumerate_scan_devices(enumerate.get()) != 0) { + LOG(ERROR) << "Failed to begin udev enumeration."; + return; + } + + udev_list_entry* devices = udev_enumerate_get_list_entry(enumerate.get()); + for (udev_list_entry* i = devices; i != nullptr; + i = udev_list_entry_get_next(i)) { + ScopedUdevDevicePtr device( + udev_device_new_from_syspath(udev_.get(), udev_list_entry_get_name(i))); + if (device) + observer_->OnDeviceAdded(std::move(device)); + } +} + +UdevWatcher::UdevWatcher(ScopedUdevPtr udev, + ScopedUdevMonitorPtr udev_monitor, + int monitor_fd, + Observer* observer) + : udev_(std::move(udev)), + udev_monitor_(std::move(udev_monitor)), + observer_(observer) { + file_watcher_ = base::FileDescriptorWatcher::WatchReadable( + monitor_fd, + base::Bind(&UdevWatcher::OnMonitorReadable, base::Unretained(this))); +} + +void UdevWatcher::OnMonitorReadable() { + ScopedUdevDevicePtr device(udev_monitor_receive_device(udev_monitor_.get())); + if (!device) + return; + + std::string action(udev_device_get_action(device.get())); + if (action == "add") + observer_->OnDeviceAdded(std::move(device)); + else if (action == "remove") + observer_->OnDeviceRemoved(std::move(device)); +} + +} // namespace device
diff --git a/device/udev_linux/udev_watcher.h b/device/udev_linux/udev_watcher.h new file mode 100644 index 0000000..0da372b --- /dev/null +++ b/device/udev_linux/udev_watcher.h
@@ -0,0 +1,55 @@ +// 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 DEVICE_UDEV_LINUX_UDEV_WATCHER_H_ +#define DEVICE_UDEV_LINUX_UDEV_WATCHER_H_ + +#include <memory> + +#include "base/files/file_descriptor_watcher_posix.h" +#include "base/macros.h" +#include "base/sequence_checker.h" +#include "device/udev_linux/scoped_udev.h" + +namespace device { + +// This class wraps an instance of udev_monitor, watching for devices that are +// added and removed from the system. This class has sequence affinity. +class UdevWatcher { + public: + class Observer { + public: + virtual ~Observer(); + virtual void OnDeviceAdded(ScopedUdevDevicePtr device); + virtual void OnDeviceRemoved(ScopedUdevDevicePtr device); + }; + + static std::unique_ptr<UdevWatcher> StartWatching(Observer* observer); + + ~UdevWatcher(); + + // Synchronously enumerates the all devices known to udev, calling + // OnDeviceAdded on the provided Observer for each. + void EnumerateExistingDevices(); + + private: + UdevWatcher(ScopedUdevPtr udev, + ScopedUdevMonitorPtr udev_monitor, + int monitor_fd, + Observer* observer); + + void OnMonitorReadable(); + + ScopedUdevPtr udev_; + ScopedUdevMonitorPtr udev_monitor_; + Observer* observer_; + std::unique_ptr<base::FileDescriptorWatcher::Controller> file_watcher_; + base::SequenceChecker sequence_checker_; + + DISALLOW_COPY_AND_ASSIGN(UdevWatcher); +}; + +} // namespace device + +#endif // DEVICE_UDEV_LINUX_UDEV_WATCHER_H_
diff --git a/device/usb/usb_device_android.cc b/device/usb/usb_device_android.cc index 7f98ec6f..a924a84 100644 --- a/device/usb/usb_device_android.cc +++ b/device/usb/usb_device_android.cc
@@ -27,7 +27,6 @@ scoped_refptr<UsbDeviceAndroid> UsbDeviceAndroid::Create( JNIEnv* env, base::WeakPtr<UsbServiceAndroid> service, - scoped_refptr<base::SequencedTaskRunner> blocking_task_runner, const JavaRef<jobject>& usb_device) { ScopedJavaLocalRef<jobject> wrapper = Java_ChromeUsbDevice_create(env, usb_device); @@ -51,8 +50,7 @@ Java_ChromeUsbDevice_getDeviceProtocol(env, wrapper), Java_ChromeUsbDevice_getVendorId(env, wrapper), Java_ChromeUsbDevice_getProductId(env, wrapper), device_version, - manufacturer_string, product_string, serial_number, blocking_task_runner, - wrapper)); + manufacturer_string, product_string, serial_number, wrapper)); } void UsbDeviceAndroid::RequestPermission(const ResultCallback& callback) { @@ -72,8 +70,7 @@ ScopedJavaLocalRef<jobject> connection = service_->OpenDevice(env, j_object_); if (!connection.is_null()) { - device_handle = UsbDeviceHandleAndroid::Create( - env, this, blocking_task_runner_, connection); + device_handle = UsbDeviceHandleAndroid::Create(env, this, connection); handles().push_back(device_handle.get()); } } @@ -98,7 +95,6 @@ const base::string16& manufacturer_string, const base::string16& product_string, const base::string16& serial_number, - scoped_refptr<base::SequencedTaskRunner> blocking_task_runner, const JavaRef<jobject>& wrapper) : UsbDevice(usb_version, device_class, @@ -110,7 +106,6 @@ manufacturer_string, product_string, serial_number), - blocking_task_runner_(blocking_task_runner), device_id_(Java_ChromeUsbDevice_getDeviceId(env, wrapper)), service_(service), j_object_(wrapper) {
diff --git a/device/usb/usb_device_android.h b/device/usb/usb_device_android.h index 699354e..542052da 100644 --- a/device/usb/usb_device_android.h +++ b/device/usb/usb_device_android.h
@@ -9,10 +9,6 @@ #include "base/memory/weak_ptr.h" #include "device/usb/usb_device.h" -namespace base { -class SequencedTaskRunner; -} - namespace device { class UsbServiceAndroid; @@ -22,7 +18,6 @@ static scoped_refptr<UsbDeviceAndroid> Create( JNIEnv* env, base::WeakPtr<UsbServiceAndroid> service, - scoped_refptr<base::SequencedTaskRunner> blocking_task_runner, const base::android::JavaRef<jobject>& usb_device); // UsbDevice: @@ -47,7 +42,6 @@ const base::string16& manufacturer_string, const base::string16& product_string, const base::string16& serial_number, - scoped_refptr<base::SequencedTaskRunner> blocking_task_runner, const base::android::JavaRef<jobject>& wrapper); ~UsbDeviceAndroid() override; @@ -61,8 +55,6 @@ std::unique_ptr<WebUsbAllowedOrigins> allowed_origins, const GURL& landing_page); - scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_; - const jint device_id_; bool permission_granted_ = false; std::list<ResultCallback> request_permission_callbacks_;
diff --git a/device/usb/usb_device_handle_android.cc b/device/usb/usb_device_handle_android.cc index d5dce7a8..d3d794a 100644 --- a/device/usb/usb_device_handle_android.cc +++ b/device/usb/usb_device_handle_android.cc
@@ -7,6 +7,7 @@ #include "base/bind.h" #include "base/location.h" #include "device/usb/usb_device.h" +#include "device/usb/usb_service.h" #include "jni/ChromeUsbConnection_jni.h" using base::android::ScopedJavaLocalRef; @@ -17,23 +18,23 @@ scoped_refptr<UsbDeviceHandleAndroid> UsbDeviceHandleAndroid::Create( JNIEnv* env, scoped_refptr<UsbDevice> device, - scoped_refptr<base::SequencedTaskRunner> blocking_task_runner, const base::android::JavaRef<jobject>& usb_connection) { ScopedJavaLocalRef<jobject> wrapper = Java_ChromeUsbConnection_create(env, usb_connection); // C++ doesn't own this file descriptor so CloseBlocking() is overridden // below to release it without closing it. base::ScopedFD fd(Java_ChromeUsbConnection_getFileDescriptor(env, wrapper)); - return make_scoped_refptr(new UsbDeviceHandleAndroid( - device, std::move(fd), blocking_task_runner, wrapper)); + return make_scoped_refptr( + new UsbDeviceHandleAndroid(device, std::move(fd), wrapper)); } UsbDeviceHandleAndroid::UsbDeviceHandleAndroid( scoped_refptr<UsbDevice> device, base::ScopedFD fd, - scoped_refptr<base::SequencedTaskRunner> blocking_task_runner, const base::android::JavaRef<jobject>& wrapper) - : UsbDeviceHandleUsbfs(device, std::move(fd), blocking_task_runner), + : UsbDeviceHandleUsbfs(device, + std::move(fd), + UsbService::CreateBlockingTaskRunner()), j_object_(wrapper) {} UsbDeviceHandleAndroid::~UsbDeviceHandleAndroid() {}
diff --git a/device/usb/usb_device_handle_android.h b/device/usb/usb_device_handle_android.h index d6a7cb7..6e7a51c 100644 --- a/device/usb/usb_device_handle_android.h +++ b/device/usb/usb_device_handle_android.h
@@ -7,7 +7,6 @@ #include "base/android/scoped_java_ref.h" #include "base/memory/ref_counted.h" -#include "base/sequenced_task_runner.h" #include "device/usb/usb_device_handle_usbfs.h" namespace device { @@ -21,7 +20,6 @@ static scoped_refptr<UsbDeviceHandleAndroid> Create( JNIEnv* env, scoped_refptr<UsbDevice> device, - scoped_refptr<base::SequencedTaskRunner> blocking_task_runner, const base::android::JavaRef<jobject>& usb_connection); private: @@ -29,7 +27,6 @@ UsbDeviceHandleAndroid( scoped_refptr<UsbDevice> device, base::ScopedFD fd, - scoped_refptr<base::SequencedTaskRunner> blocking_task_runner, const base::android::JavaRef<jobject>& wrapper); ~UsbDeviceHandleAndroid() override;
diff --git a/device/usb/usb_device_handle_unittest.cc b/device/usb/usb_device_handle_unittest.cc index 2eaaadd..6f1b93ee 100644 --- a/device/usb/usb_device_handle_unittest.cc +++ b/device/usb/usb_device_handle_unittest.cc
@@ -12,6 +12,7 @@ #include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" +#include "base/test/scoped_task_environment.h" #include "base/test/test_io_thread.h" #include "device/test/test_device_client.h" #include "device/test/usb_test_gadget.h" @@ -25,18 +26,18 @@ class UsbDeviceHandleTest : public ::testing::Test { public: - void SetUp() override { - message_loop_.reset(new base::MessageLoopForUI); - io_thread_.reset(new base::TestIOThread(base::TestIOThread::kAutoStart)); - device_client_.reset(new TestDeviceClient(io_thread_->task_runner())); - } + UsbDeviceHandleTest() + : io_thread_(base::TestIOThread::kAutoStart), + scoped_task_environment_( + base::test::ScopedTaskEnvironment::MainThreadType::UI), + device_client_(io_thread_.task_runner()) {} protected: - std::unique_ptr<base::TestIOThread> io_thread_; + base::TestIOThread io_thread_; private: - std::unique_ptr<base::MessageLoop> message_loop_; - std::unique_ptr<TestDeviceClient> device_client_; + base::test::ScopedTaskEnvironment scoped_task_environment_; + TestDeviceClient device_client_; }; class TestOpenCallback { @@ -132,7 +133,7 @@ } std::unique_ptr<UsbTestGadget> gadget = - UsbTestGadget::Claim(io_thread_->task_runner()); + UsbTestGadget::Claim(io_thread_.task_runner()); ASSERT_TRUE(gadget.get()); ASSERT_TRUE(gadget->SetType(UsbTestGadget::ECHO)); @@ -200,7 +201,7 @@ } std::unique_ptr<UsbTestGadget> gadget = - UsbTestGadget::Claim(io_thread_->task_runner()); + UsbTestGadget::Claim(io_thread_.task_runner()); ASSERT_TRUE(gadget.get()); ASSERT_TRUE(gadget->SetType(UsbTestGadget::ECHO)); @@ -268,7 +269,7 @@ return; std::unique_ptr<UsbTestGadget> gadget = - UsbTestGadget::Claim(io_thread_->task_runner()); + UsbTestGadget::Claim(io_thread_.task_runner()); ASSERT_TRUE(gadget.get()); TestOpenCallback open_device; @@ -300,7 +301,7 @@ } std::unique_ptr<UsbTestGadget> gadget = - UsbTestGadget::Claim(io_thread_->task_runner()); + UsbTestGadget::Claim(io_thread_.task_runner()); ASSERT_TRUE(gadget.get()); ASSERT_TRUE(gadget->SetType(UsbTestGadget::ECHO)); @@ -330,7 +331,7 @@ } std::unique_ptr<UsbTestGadget> gadget = - UsbTestGadget::Claim(io_thread_->task_runner()); + UsbTestGadget::Claim(io_thread_.task_runner()); ASSERT_TRUE(gadget.get()); ASSERT_TRUE(gadget->SetType(UsbTestGadget::ECHO)); @@ -361,7 +362,7 @@ } std::unique_ptr<UsbTestGadget> gadget = - UsbTestGadget::Claim(io_thread_->task_runner()); + UsbTestGadget::Claim(io_thread_.task_runner()); ASSERT_TRUE(gadget.get()); ASSERT_TRUE(gadget->SetType(UsbTestGadget::ECHO)); @@ -394,7 +395,7 @@ } std::unique_ptr<UsbTestGadget> gadget = - UsbTestGadget::Claim(io_thread_->task_runner()); + UsbTestGadget::Claim(io_thread_.task_runner()); ASSERT_TRUE(gadget.get()); ASSERT_TRUE(gadget->SetType(UsbTestGadget::ECHO)); @@ -425,7 +426,7 @@ return; std::unique_ptr<UsbTestGadget> gadget = - UsbTestGadget::Claim(io_thread_->task_runner()); + UsbTestGadget::Claim(io_thread_.task_runner()); ASSERT_TRUE(gadget.get()); ASSERT_TRUE(gadget->SetType(UsbTestGadget::ECHO));
diff --git a/device/usb/usb_device_linux.cc b/device/usb/usb_device_linux.cc index 08a3329..dc423de1 100644 --- a/device/usb/usb_device_linux.cc +++ b/device/usb/usb_device_linux.cc
@@ -18,7 +18,7 @@ #include "components/device_event_log/device_event_log.h" #include "device/usb/usb_descriptors.h" #include "device/usb/usb_device_handle_usbfs.h" -#include "device/usb/usb_error.h" +#include "device/usb/usb_service.h" #if defined(OS_CHROMEOS) #include "chromeos/dbus/dbus_thread_manager.h" @@ -27,21 +27,17 @@ namespace device { -UsbDeviceLinux::UsbDeviceLinux( - const std::string& device_path, - const UsbDeviceDescriptor& descriptor, - const std::string& manufacturer_string, - const std::string& product_string, - const std::string& serial_number, - uint8_t active_configuration, - scoped_refptr<base::SequencedTaskRunner> blocking_task_runner) +UsbDeviceLinux::UsbDeviceLinux(const std::string& device_path, + const UsbDeviceDescriptor& descriptor, + const std::string& manufacturer_string, + const std::string& product_string, + const std::string& serial_number, + uint8_t active_configuration) : UsbDevice(descriptor, base::UTF8ToUTF16(manufacturer_string), base::UTF8ToUTF16(product_string), base::UTF8ToUTF16(serial_number)), - device_path_(device_path), - task_runner_(base::ThreadTaskRunnerHandle::Get()), - blocking_task_runner_(blocking_task_runner) { + device_path_(device_path) { ActiveConfigurationChanged(active_configuration); } @@ -50,7 +46,7 @@ #if defined(OS_CHROMEOS) void UsbDeviceLinux::CheckUsbAccess(const ResultCallback& callback) { - DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(sequence_checker_.CalledOnValidSequence()); chromeos::PermissionBrokerClient* client = chromeos::DBusThreadManager::Get()->GetPermissionBrokerClient(); DCHECK(client) << "Could not get permission broker client."; @@ -60,7 +56,7 @@ #endif // defined(OS_CHROMEOS) void UsbDeviceLinux::Open(const OpenCallback& callback) { - DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(sequence_checker_.CalledOnValidSequence()); #if defined(OS_CHROMEOS) chromeos::PermissionBrokerClient* client = @@ -71,9 +67,12 @@ base::Bind(&UsbDeviceLinux::OnOpenRequestComplete, this, callback), base::Bind(&UsbDeviceLinux::OnOpenRequestError, this, callback)); #else - blocking_task_runner_->PostTask( + scoped_refptr<base::SequencedTaskRunner> blocking_task_runner = + UsbService::CreateBlockingTaskRunner(); + blocking_task_runner->PostTask( FROM_HERE, - base::Bind(&UsbDeviceLinux::OpenOnBlockingThread, this, callback)); + base::Bind(&UsbDeviceLinux::OpenOnBlockingThread, this, callback, + base::ThreadTaskRunnerHandle::Get(), blocking_task_runner)); #endif // defined(OS_CHROMEOS) } @@ -86,7 +85,7 @@ callback.Run(nullptr); return; } - Opened(std::move(fd), callback); + Opened(std::move(fd), callback, UsbService::CreateBlockingTaskRunner()); } void UsbDeviceLinux::OnOpenRequestError(const OpenCallback& callback, @@ -99,23 +98,30 @@ #else -void UsbDeviceLinux::OpenOnBlockingThread(const OpenCallback& callback) { +void UsbDeviceLinux::OpenOnBlockingThread( + const OpenCallback& callback, + scoped_refptr<base::SequencedTaskRunner> task_runner, + scoped_refptr<base::SequencedTaskRunner> blocking_task_runner) { base::ScopedFD fd(HANDLE_EINTR(open(device_path_.c_str(), O_RDWR))); if (fd.is_valid()) { - task_runner_->PostTask(FROM_HERE, base::Bind(&UsbDeviceLinux::Opened, this, - base::Passed(&fd), callback)); + task_runner->PostTask( + FROM_HERE, base::Bind(&UsbDeviceLinux::Opened, this, base::Passed(&fd), + callback, blocking_task_runner)); } else { USB_PLOG(EVENT) << "Failed to open " << device_path_; - task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr)); + task_runner->PostTask(FROM_HERE, base::Bind(callback, nullptr)); } } #endif // defined(OS_CHROMEOS) -void UsbDeviceLinux::Opened(base::ScopedFD fd, const OpenCallback& callback) { - DCHECK(thread_checker_.CalledOnValidThread()); +void UsbDeviceLinux::Opened( + base::ScopedFD fd, + const OpenCallback& callback, + scoped_refptr<base::SequencedTaskRunner> blocking_task_runner) { + DCHECK(sequence_checker_.CalledOnValidSequence()); scoped_refptr<UsbDeviceHandle> device_handle = - new UsbDeviceHandleUsbfs(this, std::move(fd), blocking_task_runner_); + new UsbDeviceHandleUsbfs(this, std::move(fd), blocking_task_runner); handles().push_back(device_handle.get()); callback.Run(device_handle); }
diff --git a/device/usb/usb_device_linux.h b/device/usb/usb_device_linux.h index 92a19c3..ccbd36e 100644 --- a/device/usb/usb_device_linux.h +++ b/device/usb/usb_device_linux.h
@@ -12,7 +12,7 @@ #include "base/files/scoped_file.h" #include "base/macros.h" -#include "base/threading/thread_checker.h" +#include "base/sequence_checker.h" #include "build/build_config.h" #include "device/usb/usb_device.h" #include "device/usb/webusb_descriptors.h" @@ -52,8 +52,7 @@ const std::string& manufacturer_string, const std::string& product_string, const std::string& serial_number, - uint8_t active_configuration, - scoped_refptr<base::SequencedTaskRunner> blocking_task_runner); + uint8_t active_configuration); ~UsbDeviceLinux() override; @@ -64,17 +63,19 @@ const std::string& error_name, const std::string& error_message); #else - void OpenOnBlockingThread(const OpenCallback& callback); + void OpenOnBlockingThread( + const OpenCallback& callback, + scoped_refptr<base::SequencedTaskRunner> task_runner, + scoped_refptr<base::SequencedTaskRunner> blocking_task_runner); #endif // defined(OS_CHROMEOS) - void Opened(base::ScopedFD fd, const OpenCallback& callback); + void Opened(base::ScopedFD fd, + const OpenCallback& callback, + scoped_refptr<base::SequencedTaskRunner> blocking_task_runner); - base::ThreadChecker thread_checker_; + base::SequenceChecker sequence_checker_; const std::string device_path_; - scoped_refptr<base::SequencedTaskRunner> task_runner_; - scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_; - DISALLOW_COPY_AND_ASSIGN(UsbDeviceLinux); };
diff --git a/device/usb/usb_service.cc b/device/usb/usb_service.cc index b560cd0..1349b73c 100644 --- a/device/usb/usb_service.cc +++ b/device/usb/usb_service.cc
@@ -8,6 +8,7 @@ #include "base/feature_list.h" #include "base/location.h" #include "base/memory/ptr_util.h" +#include "base/task_scheduler/post_task.h" #include "base/threading/thread_task_runner_handle.h" #include "build/build_config.h" #include "components/device_event_log/device_event_log.h" @@ -46,9 +47,9 @@ std::unique_ptr<UsbService> UsbService::Create( scoped_refptr<base::SequencedTaskRunner> blocking_task_runner) { #if defined(OS_ANDROID) - return base::WrapUnique(new UsbServiceAndroid(blocking_task_runner)); + return base::WrapUnique(new UsbServiceAndroid()); #elif defined(USE_UDEV) - return base::WrapUnique(new UsbServiceLinux(blocking_task_runner)); + return base::WrapUnique(new UsbServiceLinux()); #elif defined(OS_WIN) if (base::FeatureList::IsEnabled(kNewUsbBackend)) return base::WrapUnique(new UsbServiceWin(blocking_task_runner)); @@ -61,6 +62,17 @@ #endif } +// static +scoped_refptr<base::SequencedTaskRunner> +UsbService::CreateBlockingTaskRunner() { + return base::CreateSequencedTaskRunnerWithTraits( + base::TaskTraits() + .MayBlock() + .WithPriority(base::TaskPriority::USER_VISIBLE) + .WithShutdownBehavior( + base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN)); +} + UsbService::~UsbService() { #if DCHECK_IS_ON() DCHECK(did_shutdown_);
diff --git a/device/usb/usb_service.h b/device/usb/usb_service.h index 15977dd..6865ca4 100644 --- a/device/usb/usb_service.h +++ b/device/usb/usb_service.h
@@ -54,6 +54,9 @@ static std::unique_ptr<UsbService> Create( scoped_refptr<base::SequencedTaskRunner> blocking_task_runner); + // Creates a SequencedTaskRunner appropriate for blocking I/O operations. + static scoped_refptr<base::SequencedTaskRunner> CreateBlockingTaskRunner(); + virtual ~UsbService(); scoped_refptr<UsbDevice> GetDevice(const std::string& guid);
diff --git a/device/usb/usb_service_android.cc b/device/usb/usb_service_android.cc index 102b33c..8ec2428 100644 --- a/device/usb/usb_service_android.cc +++ b/device/usb/usb_service_android.cc
@@ -26,9 +26,8 @@ return RegisterNativesImpl(env); // Generated in ChromeUsbService_jni.h } -UsbServiceAndroid::UsbServiceAndroid( - scoped_refptr<base::SequencedTaskRunner> blocking_task_runner) - : UsbService(blocking_task_runner), weak_factory_(this) { +UsbServiceAndroid::UsbServiceAndroid() + : UsbService(nullptr), weak_factory_(this) { JNIEnv* env = AttachCurrentThread(); j_object_.Reset( Java_ChromeUsbService_create(env, base::android::GetApplicationContext(), @@ -39,8 +38,8 @@ for (jsize i = 0; i < length; ++i) { ScopedJavaLocalRef<jobject> usb_device( env, env->GetObjectArrayElement(devices.obj(), i)); - scoped_refptr<UsbDeviceAndroid> device(UsbDeviceAndroid::Create( - env, weak_factory_.GetWeakPtr(), blocking_task_runner, usb_device)); + scoped_refptr<UsbDeviceAndroid> device = + UsbDeviceAndroid::Create(env, weak_factory_.GetWeakPtr(), usb_device); AddDevice(device); } } @@ -53,8 +52,8 @@ void UsbServiceAndroid::DeviceAttached(JNIEnv* env, const JavaRef<jobject>& caller, const JavaRef<jobject>& usb_device) { - scoped_refptr<UsbDeviceAndroid> device(UsbDeviceAndroid::Create( - env, weak_factory_.GetWeakPtr(), blocking_task_runner(), usb_device)); + scoped_refptr<UsbDeviceAndroid> device = + UsbDeviceAndroid::Create(env, weak_factory_.GetWeakPtr(), usb_device); AddDevice(device); NotifyDeviceAdded(device); }
diff --git a/device/usb/usb_service_android.h b/device/usb/usb_service_android.h index 6fef48f..ae1ec5a 100644 --- a/device/usb/usb_service_android.h +++ b/device/usb/usb_service_android.h
@@ -5,21 +5,19 @@ #ifndef DEVICE_USB_USB_SERVICE_ANDROID_H_ #define DEVICE_USB_USB_SERVICE_ANDROID_H_ -#include <string> +#include <jni.h> + #include <unordered_map> #include "base/android/scoped_java_ref.h" #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" -#include "device/usb/usb_device_android.h" #include "device/usb/usb_service.h" -namespace base { -class SequencedTaskRunner; -} - namespace device { +class UsbDeviceAndroid; + // USB service implementation for Android. This is a stub implementation that // does not return any devices. class UsbServiceAndroid : public UsbService { @@ -27,8 +25,7 @@ // Register C++ methods exposed to Java using JNI. static bool RegisterJNI(JNIEnv* env); - UsbServiceAndroid( - scoped_refptr<base::SequencedTaskRunner> blocking_task_runner); + UsbServiceAndroid(); ~UsbServiceAndroid() override; // Methods called by Java.
diff --git a/device/usb/usb_service_linux.cc b/device/usb/usb_service_linux.cc index 394da74..1117aff 100644 --- a/device/usb/usb_service_linux.cc +++ b/device/usb/usb_service_linux.cc
@@ -17,14 +17,14 @@ #include "base/memory/ptr_util.h" #include "base/memory/weak_ptr.h" #include "base/scoped_observer.h" -#include "base/single_thread_task_runner.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" +#include "base/threading/sequenced_task_runner_handle.h" #include "base/threading/thread_restrictions.h" #include "build/build_config.h" #include "components/device_event_log/device_event_log.h" -#include "device/base/device_monitor_linux.h" +#include "device/udev_linux/udev_watcher.h" #include "device/usb/usb_device_handle.h" #include "device/usb/usb_device_linux.h" #include "device/usb/webusb_descriptors.h" @@ -67,65 +67,65 @@ } // namespace -class UsbServiceLinux::FileThreadHelper : public DeviceMonitorLinux::Observer { +class UsbServiceLinux::FileThreadHelper : public UdevWatcher::Observer { public: - FileThreadHelper(base::WeakPtr<UsbServiceLinux> service, - scoped_refptr<base::SingleThreadTaskRunner> task_runner); + FileThreadHelper(base::WeakPtr<UsbServiceLinux> service); ~FileThreadHelper() override; void Start(); private: - // DeviceMonitorLinux::Observer: - void OnDeviceAdded(udev_device* udev_device) override; - void OnDeviceRemoved(udev_device* device) override; + // UdevWatcher::Observer + void OnDeviceAdded(ScopedUdevDevicePtr device) override; + void OnDeviceRemoved(ScopedUdevDevicePtr device) override; - base::ThreadChecker thread_checker_; - ScopedObserver<DeviceMonitorLinux, DeviceMonitorLinux::Observer> observer_; + std::unique_ptr<UdevWatcher> watcher_; - // |service_| can only be checked for validity on |task_runner_|'s thread. + // |service_| can only be checked for validity on |task_runner_|'s sequence. base::WeakPtr<UsbServiceLinux> service_; - scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + scoped_refptr<base::SequencedTaskRunner> task_runner_; + + base::SequenceChecker sequence_checker_; DISALLOW_COPY_AND_ASSIGN(FileThreadHelper); }; UsbServiceLinux::FileThreadHelper::FileThreadHelper( - base::WeakPtr<UsbServiceLinux> service, - scoped_refptr<base::SingleThreadTaskRunner> task_runner) - : observer_(this), service_(service), task_runner_(std::move(task_runner)) { - thread_checker_.DetachFromThread(); + base::WeakPtr<UsbServiceLinux> service) + : service_(service), task_runner_(base::SequencedTaskRunnerHandle::Get()) { + // Detaches from the sequence on which this object was created. It will be + // bound to its owning sequence when Start() is called. + sequence_checker_.DetachFromSequence(); } UsbServiceLinux::FileThreadHelper::~FileThreadHelper() { - DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(sequence_checker_.CalledOnValidSequence()); } // static void UsbServiceLinux::FileThreadHelper::Start() { + DCHECK(sequence_checker_.CalledOnValidSequence()); base::ThreadRestrictions::AssertIOAllowed(); - DCHECK(thread_checker_.CalledOnValidThread()); - DeviceMonitorLinux* monitor = DeviceMonitorLinux::GetInstance(); - observer_.Add(monitor); - monitor->Enumerate( - base::Bind(&FileThreadHelper::OnDeviceAdded, base::Unretained(this))); + watcher_ = UdevWatcher::StartWatching(this); + watcher_->EnumerateExistingDevices(); task_runner_->PostTask(FROM_HERE, base::Bind(&UsbServiceLinux::HelperStarted, service_)); } void UsbServiceLinux::FileThreadHelper::OnDeviceAdded( - udev_device* udev_device) { - const char* subsystem = udev_device_get_subsystem(udev_device); + ScopedUdevDevicePtr device) { + DCHECK(sequence_checker_.CalledOnValidSequence()); + const char* subsystem = udev_device_get_subsystem(device.get()); if (!subsystem || strcmp(subsystem, "usb") != 0) return; - const char* value = udev_device_get_devnode(udev_device); + const char* value = udev_device_get_devnode(device.get()); if (!value) return; std::string device_path = value; - const char* sysfs_path = udev_device_get_syspath(udev_device); + const char* sysfs_path = udev_device_get_syspath(device.get()); if (!sysfs_path) return; @@ -147,22 +147,22 @@ } std::string manufacturer; - value = udev_device_get_sysattr_value(udev_device, "manufacturer"); + value = udev_device_get_sysattr_value(device.get(), "manufacturer"); if (value) manufacturer = value; std::string product; - value = udev_device_get_sysattr_value(udev_device, "product"); + value = udev_device_get_sysattr_value(device.get(), "product"); if (value) product = value; std::string serial_number; - value = udev_device_get_sysattr_value(udev_device, "serial"); + value = udev_device_get_sysattr_value(device.get(), "serial"); if (value) serial_number = value; unsigned active_configuration = 0; - value = udev_device_get_sysattr_value(udev_device, "bConfigurationValue"); + value = udev_device_get_sysattr_value(device.get(), "bConfigurationValue"); if (value) base::StringToUint(value, &active_configuration); @@ -172,9 +172,10 @@ serial_number, active_configuration)); } -void UsbServiceLinux::FileThreadHelper::OnDeviceRemoved(udev_device* device) { - DCHECK(thread_checker_.CalledOnValidThread()); - const char* device_path = udev_device_get_devnode(device); +void UsbServiceLinux::FileThreadHelper::OnDeviceRemoved( + ScopedUdevDevicePtr device) { + DCHECK(sequence_checker_.CalledOnValidSequence()); + const char* device_path = udev_device_get_devnode(device.get()); if (device_path) { task_runner_->PostTask( FROM_HERE, base::Bind(&UsbServiceLinux::OnDeviceRemoved, service_, @@ -182,11 +183,9 @@ } } -UsbServiceLinux::UsbServiceLinux( - scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_in) - : UsbService(std::move(blocking_task_runner_in)), weak_factory_(this) { - helper_ = base::MakeUnique<FileThreadHelper>(weak_factory_.GetWeakPtr(), - task_runner()); +UsbServiceLinux::UsbServiceLinux() + : UsbService(CreateBlockingTaskRunner()), weak_factory_(this) { + helper_ = base::MakeUnique<FileThreadHelper>(weak_factory_.GetWeakPtr()); blocking_task_runner()->PostTask( FROM_HERE, base::Bind(&FileThreadHelper::Start, base::Unretained(helper_.get()))); @@ -230,9 +229,9 @@ if (!enumeration_ready()) ++first_enumeration_countdown_; - scoped_refptr<UsbDeviceLinux> device(new UsbDeviceLinux( - device_path, descriptor, manufacturer, product, serial_number, - active_configuration, blocking_task_runner())); + scoped_refptr<UsbDeviceLinux> device( + new UsbDeviceLinux(device_path, descriptor, manufacturer, product, + serial_number, active_configuration)); devices_by_path_[device->device_path()] = device; if (device->usb_version() >= kUsbVersion2_1) { device->Open(base::Bind(&OnDeviceOpenedToReadDescriptors,
diff --git a/device/usb/usb_service_linux.h b/device/usb/usb_service_linux.h index 02fcafc..0b983ee 100644 --- a/device/usb/usb_service_linux.h +++ b/device/usb/usb_service_linux.h
@@ -10,10 +10,6 @@ #include "base/memory/weak_ptr.h" #include "device/usb/usb_service.h" -namespace base { -class SequencedTaskRunner; -} - namespace device { struct UsbDeviceDescriptor; @@ -21,8 +17,7 @@ class UsbServiceLinux : public UsbService { public: - explicit UsbServiceLinux( - scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_in); + UsbServiceLinux(); ~UsbServiceLinux() override; // device::UsbService implementation
diff --git a/device/usb/usb_service_unittest.cc b/device/usb/usb_service_unittest.cc index 559e52a..d3991ad 100644 --- a/device/usb/usb_service_unittest.cc +++ b/device/usb/usb_service_unittest.cc
@@ -5,10 +5,10 @@ #include <memory> #include "base/bind.h" -#include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/strings/utf_string_conversions.h" #include "base/test/scoped_feature_list.h" +#include "base/test/scoped_task_environment.h" #include "base/test/test_io_thread.h" #include "device/base/features.h" #include "device/test/test_device_client.h" @@ -24,15 +24,18 @@ class UsbServiceTest : public ::testing::Test { public: + UsbServiceTest() + : scoped_task_environment_( + base::test::ScopedTaskEnvironment::MainThreadType::UI), + io_thread_(base::TestIOThread::kAutoStart) {} + void SetUp() override { - message_loop_.reset(new base::MessageLoopForUI); - io_thread_.reset(new base::TestIOThread(base::TestIOThread::kAutoStart)); - device_client_.reset(new TestDeviceClient(io_thread_->task_runner())); + device_client_.reset(new TestDeviceClient(io_thread_.task_runner())); } protected: - std::unique_ptr<base::MessageLoop> message_loop_; - std::unique_ptr<base::TestIOThread> io_thread_; + base::test::ScopedTaskEnvironment scoped_task_environment_; + base::TestIOThread io_thread_; std::unique_ptr<TestDeviceClient> device_client_; }; @@ -77,7 +80,7 @@ if (!UsbTestGadget::IsTestEnabled()) return; std::unique_ptr<UsbTestGadget> gadget = - UsbTestGadget::Claim(io_thread_->task_runner()); + UsbTestGadget::Claim(io_thread_.task_runner()); ASSERT_TRUE(gadget); scoped_refptr<UsbDevice> device = gadget->GetDevice(); @@ -90,7 +93,7 @@ if (!UsbTestGadget::IsTestEnabled()) return; std::unique_ptr<UsbTestGadget> gadget = - UsbTestGadget::Claim(io_thread_->task_runner()); + UsbTestGadget::Claim(io_thread_.task_runner()); ASSERT_TRUE(gadget); ASSERT_TRUE(gadget->Disconnect()); ASSERT_TRUE(gadget->Reconnect()); @@ -101,7 +104,7 @@ return; std::unique_ptr<UsbTestGadget> gadget = - UsbTestGadget::Claim(io_thread_->task_runner()); + UsbTestGadget::Claim(io_thread_.task_runner()); ASSERT_TRUE(gadget); base::RunLoop loop;
diff --git a/device/vr/vr_display_impl.cc b/device/vr/vr_display_impl.cc index 46542389..ddbdbb5 100644 --- a/device/vr/vr_display_impl.cc +++ b/device/vr/vr_display_impl.cc
@@ -59,9 +59,7 @@ void VRDisplayImpl::RequestPresent(bool secure_origin, mojom::VRSubmitFrameClientPtr submit_client, const RequestPresentCallback& callback) { - // TODO(mthiesse): Re-enable insecure origin support once webVR content - // warnings are fixed. crbug.com/704937 - if (!device_->IsAccessAllowed(this) || !secure_origin) { + if (!device_->IsAccessAllowed(this)) { callback.Run(false); return; }
diff --git a/docs/closure_compilation.md b/docs/closure_compilation.md index 0c59e50a..81a002b 100644 --- a/docs/closure_compilation.md +++ b/docs/closure_compilation.md
@@ -196,3 +196,21 @@ This file is used by the [Closure compiler bot](https://build.chromium.org/p/chromium.fyi/builders/Closure%20Compilation%20Linux) to automatically compile your code on every commit. + +## Externs + +[Externs files](https://github.com/google/closure-compiler/wiki/FAQ#how-do-i-write-an-externs-file) +define APIs external to your JavaScript. They provide the compiler with the type +information needed to check usage of these APIs in your JavaScript, much like +forward declarations do in C++. + +Third-party libraries like Polymer often provide externs. Chrome must also +provide externs for its extension APIs. Whenever an extension API's `idl` or +`json` schema is updated in Chrome, the corresponding externs file must be +regenerated: + +```shell +./tools/json_schema_compiler/compiler.py -g externs \ + extensions/common/api/your_api_here.idl \ + > third_party/closure_compiler/externs/your_api_here.js +```
diff --git a/extensions/browser/browser_context_keyed_service_factories.cc b/extensions/browser/browser_context_keyed_service_factories.cc index a203fb95..b230e31 100644 --- a/extensions/browser/browser_context_keyed_service_factories.cc +++ b/extensions/browser/browser_context_keyed_service_factories.cc
@@ -33,7 +33,6 @@ #include "extensions/browser/api/usb/usb_event_router.h" #include "extensions/browser/api/usb/usb_guid_map.h" #include "extensions/browser/api/virtual_keyboard_private/virtual_keyboard_private_api.h" -#include "extensions/browser/api/vpn_provider/vpn_service_factory.h" #include "extensions/browser/api/web_request/web_request_api.h" #include "extensions/browser/api/webcam_private/webcam_private_api.h" #include "extensions/browser/declarative_user_script_manager_factory.h" @@ -43,6 +42,10 @@ #include "extensions/browser/process_manager_factory.h" #include "extensions/browser/renderer_startup_helper.h" +#if defined(OS_CHROMEOS) +#include "extensions/browser/api/vpn_provider/vpn_service_factory.h" +#endif // defined(OS_CHROMEOS) + namespace extensions { void EnsureBrowserContextKeyedServiceFactoriesBuilt() {
diff --git a/extensions/renderer/api_invocation_errors.cc b/extensions/renderer/api_invocation_errors.cc index 34d147f..58c0784 100644 --- a/extensions/renderer/api_invocation_errors.cc +++ b/extensions/renderer/api_invocation_errors.cc
@@ -87,6 +87,14 @@ return "Script threw an error."; } +std::string TooManyArguments() { + return "Too many arguments."; +} + +std::string MissingRequiredArgument(const char* argument_name) { + return base::StringPrintf("Missing required argument '%s'.", argument_name); +} + std::string IndexError(uint32_t index, const std::string& error) { return base::StringPrintf("Error at index %u: %s", index, error.c_str()); } @@ -96,5 +104,11 @@ error.c_str()); } +std::string ArgumentError(const std::string& parameter_name, + const std::string& error) { + return base::StringPrintf("Error at parameter '%s': %s", + parameter_name.c_str(), error.c_str()); +} + } // namespace api_errors } // namespace extensions
diff --git a/extensions/renderer/api_invocation_errors.h b/extensions/renderer/api_invocation_errors.h index 4458b6dc..b523406 100644 --- a/extensions/renderer/api_invocation_errors.h +++ b/extensions/renderer/api_invocation_errors.h
@@ -43,6 +43,8 @@ std::string InvalidChoice(); std::string UnserializableValue(); std::string ScriptThrewError(); +std::string TooManyArguments(); +std::string MissingRequiredArgument(const char* argument_name); // Returns an message indicating an error was found while parsing a given index // in an array. @@ -52,6 +54,11 @@ // property on an object. std::string PropertyError(const char* property_name, const std::string& error); +// Returns a message indicating that an error was found while parsing a given +// parameter in an API signature. +std::string ArgumentError(const std::string& parameter_name, + const std::string& error); + } // namespace api_errors } // namespace extensions
diff --git a/extensions/renderer/api_invocation_errors_unittest.cc b/extensions/renderer/api_invocation_errors_unittest.cc index 2149cc5..b6b9f18 100644 --- a/extensions/renderer/api_invocation_errors_unittest.cc +++ b/extensions/renderer/api_invocation_errors_unittest.cc
@@ -22,6 +22,15 @@ "Invalid type: expected string, found integer.", PropertyError("foo", IndexError(1, InvalidType(kTypeString, kTypeInteger)))); + + EXPECT_EQ( + "Error at parameter 'foo': Invalid type: expected string, found integer.", + ArgumentError("foo", InvalidType(kTypeString, kTypeInteger))); + EXPECT_EQ( + "Error at parameter 'foo': Error at index 0: " + "Invalid type: expected string, found integer.", + ArgumentError("foo", + IndexError(0, InvalidType(kTypeString, kTypeInteger)))); } } // namespace api_errors
diff --git a/extensions/renderer/api_signature.cc b/extensions/renderer/api_signature.cc index c83eb91..9fead9a9 100644 --- a/extensions/renderer/api_signature.cc +++ b/extensions/renderer/api_signature.cc
@@ -9,6 +9,7 @@ #include "base/memory/ptr_util.h" #include "base/values.h" #include "content/public/child/v8_value_converter.h" +#include "extensions/renderer/api_invocation_errors.h" #include "extensions/renderer/argument_spec.h" #include "gin/arguments.h" @@ -84,6 +85,10 @@ std::string* error_; size_t current_index_ = 0; + // An error to pass while parsing arguments to avoid having to allocate a new + // std::string on the stack multiple times. + std::string parse_error_; + DISALLOW_COPY_AND_ASSIGN(ArgumentParser); }; @@ -158,6 +163,11 @@ }; bool ArgumentParser::ParseArguments() { + if (arguments_.size() > signature_.size()) { + *error_ = api_errors::TooManyArguments(); + return false; + } + bool signature_has_callback = HasCallback(signature_); size_t end_size = @@ -170,8 +180,15 @@ if (signature_has_callback && !ParseCallback(*signature_.back())) return false; - if (current_index_ != arguments_.size()) + if (current_index_ != arguments_.size()) { + // This can potentially happen even if the check above for too many + // arguments succeeds when optional parameters are omitted. For instance, + // if the signature expects (optional int, function callback) and the caller + // provides (function callback, object random), the first size check and + // callback spec would succeed, but we wouldn't consume all the arguments. + *error_ = api_errors::TooManyArguments(); return false; // Extra arguments aren't allowed. + } return true; } @@ -180,7 +197,7 @@ v8::Local<v8::Value> value = next_argument(); if (value.IsEmpty() || value->IsNull() || value->IsUndefined()) { if (!spec.optional()) { - *error_ = "Missing required argument: " + spec.name(); + *error_ = api_errors::MissingRequiredArgument(spec.name().c_str()); return false; } // This is safe to call even if |arguments| is at the end (which can happen @@ -191,9 +208,10 @@ return true; } - if (!spec.ParseArgument(context_, value, type_refs_, GetBuffer(), error_)) { + if (!spec.ParseArgument(context_, value, type_refs_, GetBuffer(), + &parse_error_)) { if (!spec.optional()) { - *error_ = "Missing required argument: " + spec.name(); + *error_ = api_errors::ArgumentError(spec.name(), parse_error_); return false; } @@ -210,7 +228,7 @@ v8::Local<v8::Value> value = next_argument(); if (value.IsEmpty() || value->IsNull() || value->IsUndefined()) { if (!spec.optional()) { - *error_ = "Missing required argument: " + spec.name(); + *error_ = api_errors::MissingRequiredArgument(spec.name().c_str()); return false; } ConsumeArgument(); @@ -218,8 +236,9 @@ return true; } - if (!value->IsFunction()) { - *error_ = "Argument is wrong type: " + spec.name(); + if (!spec.ParseArgument(context_, value, type_refs_, nullptr, + &parse_error_)) { + *error_ = api_errors::ArgumentError(spec.name(), parse_error_); return false; }
diff --git a/extensions/renderer/api_signature_unittest.cc b/extensions/renderer/api_signature_unittest.cc index 3477f98..fac690777 100644 --- a/extensions/renderer/api_signature_unittest.cc +++ b/extensions/renderer/api_signature_unittest.cc
@@ -8,6 +8,7 @@ #include "base/values.h" #include "extensions/renderer/api_binding_test.h" #include "extensions/renderer/api_binding_test_util.h" +#include "extensions/renderer/api_invocation_errors.h" #include "extensions/renderer/api_type_reference_map.h" #include "extensions/renderer/argument_spec.h" #include "extensions/renderer/argument_spec_builder.h" @@ -143,12 +144,15 @@ base::StringPiece arg_values, base::StringPiece expected_parsed_args, bool expect_callback) { - RunTest(signature, arg_values, expected_parsed_args, expect_callback, true); + RunTest(signature, arg_values, expected_parsed_args, expect_callback, true, + std::string()); } void ExpectFailure(const APISignature& signature, - base::StringPiece arg_values) { - RunTest(signature, arg_values, base::StringPiece(), false, false); + base::StringPiece arg_values, + const std::string& expected_error) { + RunTest(signature, arg_values, base::StringPiece(), false, false, + expected_error); } private: @@ -156,7 +160,8 @@ base::StringPiece arg_values, base::StringPiece expected_parsed_args, bool expect_callback, - bool should_succeed) { + bool should_succeed, + const std::string& expected_error) { SCOPED_TRACE(arg_values); v8::Local<v8::Context> context = MainContext(); v8::Local<v8::Value> v8_args = V8ValueFromScriptSource(context, arg_values); @@ -176,6 +181,8 @@ if (should_succeed) { EXPECT_EQ(ReplaceSingleQuotes(expected_parsed_args), ValueToString(*result)); + } else { + EXPECT_EQ(expected_error, error); } } @@ -185,65 +192,83 @@ }; TEST_F(APISignatureTest, BasicSignatureParsing) { + using namespace api_errors; + v8::HandleScope handle_scope(isolate()); { auto signature = OneString(); ExpectPass(*signature, "['foo']", "['foo']", false); ExpectPass(*signature, "['']", "['']", false); - ExpectFailure(*signature, "[1]"); - ExpectFailure(*signature, "[]"); - ExpectFailure(*signature, "[{}]"); - ExpectFailure(*signature, "['foo', 'bar']"); + ExpectFailure( + *signature, "[1]", + ArgumentError("string", InvalidType(kTypeString, kTypeInteger))); + ExpectFailure(*signature, "[]", MissingRequiredArgument("string")); + ExpectFailure( + *signature, "[{}]", + ArgumentError("string", InvalidType(kTypeString, kTypeObject))); + ExpectFailure(*signature, "['foo', 'bar']", TooManyArguments()); } { auto signature = StringAndInt(); ExpectPass(*signature, "['foo', 42]", "['foo',42]", false); ExpectPass(*signature, "['foo', -1]", "['foo',-1]", false); - ExpectFailure(*signature, "[1]"); - ExpectFailure(*signature, "['foo'];"); - ExpectFailure(*signature, "[1, 'foo']"); - ExpectFailure(*signature, "['foo', 'foo']"); - ExpectFailure(*signature, "['foo', '1']"); - ExpectFailure(*signature, "['foo', 2.3]"); + ExpectFailure( + *signature, "[1]", + ArgumentError("string", InvalidType(kTypeString, kTypeInteger))); + ExpectFailure(*signature, "['foo'];", MissingRequiredArgument("int")); + ExpectFailure( + *signature, "[1, 'foo']", + ArgumentError("string", InvalidType(kTypeString, kTypeInteger))); + ExpectFailure(*signature, "['foo', 'foo']", + ArgumentError("int", InvalidType(kTypeInteger, kTypeString))); + ExpectFailure(*signature, "['foo', '1']", + ArgumentError("int", InvalidType(kTypeInteger, kTypeString))); + ExpectFailure(*signature, "['foo', 2.3]", + ArgumentError("int", InvalidType(kTypeInteger, kTypeDouble))); } { auto signature = StringOptionalIntAndBool(); ExpectPass(*signature, "['foo', 42, true]", "['foo',42,true]", false); ExpectPass(*signature, "['foo', true]", "['foo',null,true]", false); - ExpectFailure(*signature, "['foo', 'bar', true]"); + ExpectFailure( + *signature, "['foo', 'bar', true]", + ArgumentError("bool", InvalidType(kTypeBoolean, kTypeString))); } { auto signature = OneObject(); ExpectPass(*signature, "[{prop1: 'foo'}]", "[{'prop1':'foo'}]", false); ExpectFailure(*signature, - "[{ get prop1() { throw new Error('Badness'); } }]"); + "[{ get prop1() { throw new Error('Badness'); } }]", + ArgumentError("obj", ScriptThrewError())); } { auto signature = NoArgs(); ExpectPass(*signature, "[]", "[]", false); - ExpectFailure(*signature, "[0]"); - ExpectFailure(*signature, "['']"); - ExpectFailure(*signature, "[null]"); - ExpectFailure(*signature, "[undefined]"); + ExpectFailure(*signature, "[0]", TooManyArguments()); + ExpectFailure(*signature, "['']", TooManyArguments()); + ExpectFailure(*signature, "[null]", TooManyArguments()); + ExpectFailure(*signature, "[undefined]", TooManyArguments()); } { auto signature = IntAndCallback(); ExpectPass(*signature, "[1, function() {}]", "[1]", true); - ExpectFailure(*signature, "[function() {}]"); - ExpectFailure(*signature, "[1]"); + ExpectFailure( + *signature, "[function() {}]", + ArgumentError("int", InvalidType(kTypeInteger, kTypeFunction))); + ExpectFailure(*signature, "[1]", MissingRequiredArgument("callback")); } { auto signature = OptionalIntAndCallback(); ExpectPass(*signature, "[1, function() {}]", "[1]", true); ExpectPass(*signature, "[function() {}]", "[null]", true); - ExpectFailure(*signature, "[1]"); + ExpectFailure(*signature, "[1]", MissingRequiredArgument("callback")); } { @@ -251,7 +276,9 @@ ExpectPass(*signature, "[function() {}]", "[]", true); ExpectPass(*signature, "[]", "[]", false); ExpectPass(*signature, "[undefined]", "[]", false); - ExpectFailure(*signature, "[0]"); + ExpectFailure( + *signature, "[0]", + ArgumentError("callback", InvalidType(kTypeFunction, kTypeInteger))); } { @@ -262,12 +289,15 @@ false); ExpectPass(*signature, "[4, {foo: 'bar'}, {}]", "[4,{'foo':'bar'},{}]", false); - ExpectFailure(*signature, "[4, function() {}]"); - ExpectFailure(*signature, "[4]"); + ExpectFailure(*signature, "[4, function() {}]", + ArgumentError("any", UnserializableValue())); + ExpectFailure(*signature, "[4]", MissingRequiredArgument("any")); } } TEST_F(APISignatureTest, TypeRefsTest) { + using namespace api_errors; + v8::HandleScope handle_scope(isolate()); { @@ -275,14 +305,18 @@ ExpectPass(*signature, "[{prop1: 'foo'}]", "[{'prop1':'foo'}]", false); ExpectPass(*signature, "[{prop1: 'foo', prop2: 2}]", "[{'prop1':'foo','prop2':2}]", false); - ExpectFailure(*signature, "[{prop1: 'foo', prop2: 'a'}]"); + ExpectFailure( + *signature, "[{prop1: 'foo', prop2: 'a'}]", + ArgumentError("obj", PropertyError("prop2", InvalidType(kTypeInteger, + kTypeString)))); } { auto signature = RefEnum(); ExpectPass(*signature, "['alpha']", "['alpha']", false); ExpectPass(*signature, "['beta']", "['beta']", false); - ExpectFailure(*signature, "['gamma']"); + ExpectFailure(*signature, "['gamma']", + ArgumentError("enum", InvalidEnumValue({"alpha", "beta"}))); } }
diff --git a/extensions/renderer/argument_spec.cc b/extensions/renderer/argument_spec.cc index 0876859..f56af3f 100644 --- a/extensions/renderer/argument_spec.cc +++ b/extensions/renderer/argument_spec.cc
@@ -420,8 +420,10 @@ // HasOwnProperty() check here in the future, if we desire. // See also comment in ParseArgumentToArray() about passing in custom // crazy values here. - if (!object->Get(context, key).ToLocal(&prop_value)) + if (!object->Get(context, key).ToLocal(&prop_value)) { + *error = api_errors::ScriptThrewError(); return false; + } // Note: We don't serialize undefined or null values. // TODO(devlin): This matches current behavior, but it is correct?
diff --git a/gpu/config/gpu_driver_bug_list.json b/gpu/config/gpu_driver_bug_list.json index 2cd064fac..f8d8d06 100644 --- a/gpu/config/gpu_driver_bug_list.json +++ b/gpu/config/gpu_driver_bug_list.json
@@ -1,6 +1,6 @@ { "name": "gpu driver bug list", - "version": "10.4", + "version": "10.5", "entries": [ { "id": 1, @@ -1738,7 +1738,10 @@ { "id": 174, "description": "Adreno 4xx support for EXT_multisampled_render_to_texture is buggy on Android 7.0", - "cr_bugs": [612474], + "comment": [ + "Disabling EXT_multisampled_render_to_texture triggers the explicit multisample resolve path, which is broken on the same configurations. (See also software rendering list entry #147.)" + ], + "cr_bugs": [612474, 696126], "os": { "type": "android", "version": { @@ -1751,6 +1754,9 @@ "gl_renderer": "Adreno \\(TM\\) 4.*", "disabled_extensions": [ "GL_EXT_multisampled_render_to_texture" + ], + "features": [ + "disable_chromium_framebuffer_multisample" ] }, {
diff --git a/gpu/config/software_rendering_list.json b/gpu/config/software_rendering_list.json index 23cc2b9..0fe91b2 100644 --- a/gpu/config/software_rendering_list.json +++ b/gpu/config/software_rendering_list.json
@@ -1,6 +1,6 @@ { "name": "software rendering list", - "version": "13.3", + "version": "13.4", "entries": [ { "id": 1, @@ -1569,6 +1569,25 @@ "features": [ "webgl2" ] + }, + { + "id": 147, + "description": "Explicit multisample resolve is broken on Adreno 4xx on Android 7.0", + "comment": "Corresponds to GPU driver bug #174", + "cr_bugs": [696126], + "os": { + "type": "android", + "version": { + "op": "between", + "value": "7.0.0", + "value2": "7.0.99", + "comment": "Only initial version of N." + } + }, + "gl_renderer": "Adreno \\(TM\\) 4.*", + "features": [ + "webgl2" + ] } ], "comment": [
diff --git a/gpu/ipc/common/gpu_surface_tracker.cc b/gpu/ipc/common/gpu_surface_tracker.cc index b2b8dac..a93ea2c 100644 --- a/gpu/ipc/common/gpu_surface_tracker.cc +++ b/gpu/ipc/common/gpu_surface_tracker.cc
@@ -14,6 +14,23 @@ namespace gpu { +#if defined(OS_ANDROID) +GpuSurfaceTracker::SurfaceRecord::SurfaceRecord(gfx::AcceleratedWidget widget, + jobject j_surface) + : widget(widget) { + // TODO(liberato): It would be nice to assert |surface != nullptr|, but we + // can't. in_process_context_factory.cc (for tests) actually calls us without + // a Surface from java. Presumably, nobody uses it. crbug.com/712717 . + if (j_surface != nullptr) + surface = gl::ScopedJavaSurface::AcquireExternalSurface(j_surface); +} +#else // defined(OS_ANDROID) +GpuSurfaceTracker::SurfaceRecord::SurfaceRecord(gfx::AcceleratedWidget widget) + : widget(widget) {} +#endif // !defined(OS_ANDROID) + +GpuSurfaceTracker::SurfaceRecord::SurfaceRecord(SurfaceRecord&&) = default; + GpuSurfaceTracker::GpuSurfaceTracker() : next_surface_handle_(1) { gpu::GpuSurfaceLookup::InitInstance(this); @@ -27,11 +44,10 @@ return base::Singleton<GpuSurfaceTracker>::get(); } -int GpuSurfaceTracker::AddSurfaceForNativeWidget( - gfx::AcceleratedWidget widget) { +int GpuSurfaceTracker::AddSurfaceForNativeWidget(SurfaceRecord record) { base::AutoLock lock(surface_map_lock_); gpu::SurfaceHandle surface_handle = next_surface_handle_++; - surface_map_[surface_handle] = widget; + surface_map_.emplace(surface_handle, std::move(record)); return surface_handle; } @@ -55,38 +71,22 @@ return gfx::kNullAcceleratedWidget; #if defined(OS_ANDROID) - if (it->second != gfx::kNullAcceleratedWidget) - ANativeWindow_acquire(it->second); + if (it->second.widget != gfx::kNullAcceleratedWidget) + ANativeWindow_acquire(it->second.widget); #endif // defined(OS_ANDROID) - return it->second; + return it->second.widget; } #if defined(OS_ANDROID) -void GpuSurfaceTracker::RegisterViewSurface( - int surface_id, jobject j_surface) { - base::AutoLock lock(surface_view_map_lock_); - DCHECK(surface_view_map_.find(surface_id) == surface_view_map_.end()); - - surface_view_map_[surface_id] = - gl::ScopedJavaSurface::AcquireExternalSurface(j_surface); - CHECK(surface_view_map_[surface_id].IsValid()); -} - -void GpuSurfaceTracker::UnregisterViewSurface(int surface_id) -{ - base::AutoLock lock(surface_view_map_lock_); - DCHECK(surface_view_map_.find(surface_id) != surface_view_map_.end()); - surface_view_map_.erase(surface_id); -} - -gl::ScopedJavaSurface GpuSurfaceTracker::AcquireJavaSurface(int surface_id) { - base::AutoLock lock(surface_view_map_lock_); - SurfaceViewMap::const_iterator iter = surface_view_map_.find(surface_id); - if (iter == surface_view_map_.end()) +gl::ScopedJavaSurface GpuSurfaceTracker::AcquireJavaSurface( + gpu::SurfaceHandle surface_handle) { + base::AutoLock lock(surface_map_lock_); + SurfaceMap::const_iterator it = surface_map_.find(surface_handle); + if (it == surface_map_.end()) return gl::ScopedJavaSurface(); - const gl::ScopedJavaSurface& j_surface = iter->second; + const gl::ScopedJavaSurface& j_surface = it->second.surface; DCHECK(j_surface.IsValid()); return gl::ScopedJavaSurface::AcquireExternalSurface( j_surface.j_surface().obj());
diff --git a/gpu/ipc/common/gpu_surface_tracker.h b/gpu/ipc/common/gpu_surface_tracker.h index 6d372712..52cf377 100644 --- a/gpu/ipc/common/gpu_surface_tracker.h +++ b/gpu/ipc/common/gpu_surface_tracker.h
@@ -26,12 +26,30 @@ // GpuMemoryBufferManager::CreateGpuMemoryBuffer. // On Android, the handle is used in the GPU process to get a reference to the // ANativeWindow, using GpuSurfaceLookup (implemented by -// ChildProcessSurfaceManager). +// ChildProcessSurfaceManager). We require that an Android Surface is provided +// with the ANativeWindow, so one must provide an explicit GpuSurfaceTracker:: +// SurfaceRecord when adding it. // On Mac, the handle just passes through the GPU process, and is sent back via // GpuCommandBufferMsg_SwapBuffersCompleted to reference the surface. // This class is thread safe. class GPU_EXPORT GpuSurfaceTracker : public gpu::GpuSurfaceLookup { public: + struct SurfaceRecord { +#if defined(OS_ANDROID) + SurfaceRecord(gfx::AcceleratedWidget widget, jobject j_surface); +#else // defined(OS_ANDROID) + explicit SurfaceRecord(gfx::AcceleratedWidget widget); +#endif // !defined(OS_ANDROID) + + SurfaceRecord(SurfaceRecord&&); + SurfaceRecord(const SurfaceRecord&) = delete; + + gfx::AcceleratedWidget widget; +#if defined(OS_ANDROID) + gl::ScopedJavaSurface surface; +#endif + }; + // GpuSurfaceLookup implementation: // Returns the native widget associated with a given surface_handle. // On Android, this adds a reference on the ANativeWindow. @@ -39,16 +57,15 @@ gpu::SurfaceHandle surface_handle) override; #if defined(OS_ANDROID) - void RegisterViewSurface(int surface_id, jobject j_surface); - void UnregisterViewSurface(int surface_id); - gl::ScopedJavaSurface AcquireJavaSurface(int surface_id) override; + gl::ScopedJavaSurface AcquireJavaSurface( + gpu::SurfaceHandle surface_handle) override; #endif // Gets the global instance of the surface tracker. static GpuSurfaceTracker* Get() { return GetInstance(); } // Adds a surface for a native widget. Returns the surface ID. - int AddSurfaceForNativeWidget(gfx::AcceleratedWidget widget); + int AddSurfaceForNativeWidget(SurfaceRecord record); // Return true if the surface handle is registered with the tracker. bool IsValidSurfaceHandle(gpu::SurfaceHandle surface_handle) const; @@ -64,7 +81,7 @@ static GpuSurfaceTracker* GetInstance(); private: - typedef std::map<gpu::SurfaceHandle, gfx::AcceleratedWidget> SurfaceMap; + using SurfaceMap = std::map<gpu::SurfaceHandle, SurfaceRecord>; friend struct base::DefaultSingletonTraits<GpuSurfaceTracker>; @@ -75,12 +92,6 @@ SurfaceMap surface_map_; int next_surface_handle_; -#if defined(OS_ANDROID) - base::Lock surface_view_map_lock_; - typedef std::map<gpu::SurfaceHandle, gl::ScopedJavaSurface> SurfaceViewMap; - SurfaceViewMap surface_view_map_; -#endif - DISALLOW_COPY_AND_ASSIGN(GpuSurfaceTracker); };
diff --git a/ios/web/BUILD.gn b/ios/web/BUILD.gn index 7946208c8..4d30428d 100644 --- a/ios/web/BUILD.gn +++ b/ios/web/BUILD.gn
@@ -161,6 +161,8 @@ "public/web_state/web_state.h", "public/web_state/web_state_delegate.h", "public/web_state/web_state_delegate_bridge.h", + "public/web_state/web_state_interface_provider.cc", + "public/web_state/web_state_interface_provider.h", "public/web_state/web_state_observer.h", "public/web_state/web_state_observer_bridge.h", "public/web_state/web_state_policy_decider.h",
diff --git a/ios/web/DEPS b/ios/web/DEPS index 8f60303..0729576 100644 --- a/ios/web/DEPS +++ b/ios/web/DEPS
@@ -3,6 +3,7 @@ "+crypto", "+ios/net", "+ios/web", + "+mojo/public", "+net", "+services/service_manager/public", "+ui",
diff --git a/ios/web/public/test/fakes/test_web_state.h b/ios/web/public/test/fakes/test_web_state.h index 98af071c..32afd916 100644 --- a/ios/web/public/test/fakes/test_web_state.h +++ b/ios/web/public/test/fakes/test_web_state.h
@@ -74,7 +74,7 @@ void AddPolicyDecider(WebStatePolicyDecider* decider) override {} void RemovePolicyDecider(WebStatePolicyDecider* decider) override {} - service_manager::InterfaceRegistry* GetMojoInterfaceRegistry() override; + WebStateInterfaceProvider* GetWebStateInterfaceProvider() override; bool HasOpener() const override; base::WeakPtr<WebState> AsWeakPtr() override;
diff --git a/ios/web/public/test/fakes/test_web_state.mm b/ios/web/public/test/fakes/test_web_state.mm index 7e348a3e..2a462d70 100644 --- a/ios/web/public/test/fakes/test_web_state.mm +++ b/ios/web/public/test/fakes/test_web_state.mm
@@ -214,7 +214,7 @@ return nullptr; } -service_manager::InterfaceRegistry* TestWebState::GetMojoInterfaceRegistry() { +WebStateInterfaceProvider* TestWebState::GetWebStateInterfaceProvider() { return nullptr; }
diff --git a/ios/web/public/web_state/web_state.h b/ios/web/public/web_state/web_state.h index 62d860e..6974f523 100644 --- a/ios/web/public/web_state/web_state.h +++ b/ios/web/public/web_state/web_state.h
@@ -35,10 +35,6 @@ class Value; } -namespace service_manager { -class InterfaceRegistry; -} - namespace web { class BrowserState; @@ -46,6 +42,7 @@ class SessionCertificatePolicyCache; class WebInterstitial; class WebStateDelegate; +class WebStateInterfaceProvider; class WebStateObserver; class WebStatePolicyDecider; class WebStateWeakPtrFactory; @@ -245,7 +242,7 @@ virtual CRWWebViewProxyType GetWebViewProxy() const = 0; // Returns Mojo interface registry for this WebState. - virtual service_manager::InterfaceRegistry* GetMojoInterfaceRegistry() = 0; + virtual WebStateInterfaceProvider* GetWebStateInterfaceProvider() = 0; // Returns whether this WebState was created with an opener. See // CreateParams::created_with_opener for more details.
diff --git a/ios/web/public/web_state/web_state_interface_provider.cc b/ios/web/public/web_state/web_state_interface_provider.cc new file mode 100644 index 0000000..47542d8c --- /dev/null +++ b/ios/web/public/web_state/web_state_interface_provider.cc
@@ -0,0 +1,26 @@ +// Copyright 2013 The Chromium 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 "ios/web/public/web_state/web_state_interface_provider.h" + +#include "services/service_manager/public/cpp/identity.h" + +namespace web { + +WebStateInterfaceProvider::WebStateInterfaceProvider() : binding_(this) {} +WebStateInterfaceProvider::~WebStateInterfaceProvider() = default; + +void WebStateInterfaceProvider::Bind( + service_manager::mojom::InterfaceProviderRequest request) { + binding_.Bind(std::move(request)); +} + +void WebStateInterfaceProvider::GetInterface( + const std::string& interface_name, + mojo::ScopedMessagePipeHandle handle) { + registry_.BindInterface(service_manager::Identity(), interface_name, + std::move(handle)); +} + +} // namespace web
diff --git a/ios/web/public/web_state/web_state_interface_provider.h b/ios/web/public/web_state/web_state_interface_provider.h new file mode 100644 index 0000000..9ad3dfc --- /dev/null +++ b/ios/web/public/web_state/web_state_interface_provider.h
@@ -0,0 +1,39 @@ +// 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 IOS_WEB_WEB_STATE_WEB_STATE_INTERFACE_PROVIDER_H_ +#define IOS_WEB_WEB_STATE_WEB_STATE_INTERFACE_PROVIDER_H_ + +#include "base/macros.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "services/service_manager/public/cpp/binder_registry.h" +#include "services/service_manager/public/interfaces/interface_provider.mojom.h" + +namespace web { + +class WebStateInterfaceProvider + : public service_manager::mojom::InterfaceProvider { + public: + WebStateInterfaceProvider(); + ~WebStateInterfaceProvider() override; + + void Bind(service_manager::mojom::InterfaceProviderRequest request); + + service_manager::BinderRegistry* registry() { return ®istry_; } + + private: + // service_manager::mojom::InterfaceProvider: + void GetInterface(const std::string& interface_name, + mojo::ScopedMessagePipeHandle handle) override; + + service_manager::BinderRegistry registry_; + + mojo::Binding<service_manager::mojom::InterfaceProvider> binding_; + + DISALLOW_COPY_AND_ASSIGN(WebStateInterfaceProvider); +}; + +} // namespace web + +#endif // IOS_WEB_WEB_STATE_WEB_STATE_INTERFACE_PROVIDER_H_
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm index fef6a997..dcd448a2 100644 --- a/ios/web/web_state/ui/crw_web_controller.mm +++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -74,6 +74,7 @@ #import "ios/web/public/web_state/ui/crw_web_view_scroll_view_proxy.h" #include "ios/web/public/web_state/url_verification_constants.h" #import "ios/web/public/web_state/web_state.h" +#include "ios/web/public/web_state/web_state_interface_provider.h" #include "ios/web/public/webui/web_ui_ios.h" #import "ios/web/web_state/crw_pass_kit_downloader.h" #import "ios/web/web_state/error_translation_util.h" @@ -99,7 +100,6 @@ #import "net/base/mac/url_conversions.h" #include "net/base/net_errors.h" #include "net/ssl/ssl_info.h" -#include "services/service_manager/public/cpp/interface_registry.h" #include "ui/base/page_transition_types.h" #include "url/gurl.h" #include "url/url_constants.h" @@ -2208,7 +2208,7 @@ - (web::MojoFacade*)mojoFacade { if (!_mojoFacade) { service_manager::mojom::InterfaceProvider* interfaceProvider = - _webStateImpl->GetMojoInterfaceRegistry(); + _webStateImpl->GetWebStateInterfaceProvider(); _mojoFacade.reset(new web::MojoFacade(interfaceProvider, self)); } return _mojoFacade.get();
diff --git a/ios/web/web_state/web_state_impl.h b/ios/web/web_state/web_state_impl.h index 25d7d22..5d722b4 100644 --- a/ios/web/web_state/web_state_impl.h +++ b/ios/web/web_state/web_state_impl.h
@@ -44,6 +44,7 @@ class NavigationManager; class SessionCertificatePolicyCacheImpl; class WebInterstitialImpl; +class WebStateInterfaceProvider; class WebStatePolicyDecider; class WebUIIOS; @@ -210,7 +211,7 @@ const std::string& command_prefix) override; void RemoveScriptCommandCallback(const std::string& command_prefix) override; id<CRWWebViewProxy> GetWebViewProxy() const override; - service_manager::InterfaceRegistry* GetMojoInterfaceRegistry() override; + WebStateInterfaceProvider* GetWebStateInterfaceProvider() override; bool HasOpener() const override; base::WeakPtr<WebState> AsWeakPtr() override; @@ -351,7 +352,7 @@ base::WeakPtrFactory<WebState> weak_factory_; // Mojo interface registry for this WebState. - std::unique_ptr<service_manager::InterfaceRegistry> mojo_interface_registry_; + std::unique_ptr<WebStateInterfaceProvider> web_state_interface_provider_; DISALLOW_COPY_AND_ASSIGN(WebStateImpl); };
diff --git a/ios/web/web_state/web_state_impl.mm b/ios/web/web_state/web_state_impl.mm index 5b8cc73..b5a047dc 100644 --- a/ios/web/web_state/web_state_impl.mm +++ b/ios/web/web_state/web_state_impl.mm
@@ -24,6 +24,7 @@ #import "ios/web/public/web_state/context_menu_params.h" #import "ios/web/public/web_state/ui/crw_content_view.h" #import "ios/web/public/web_state/web_state_delegate.h" +#include "ios/web/public/web_state/web_state_interface_provider.h" #include "ios/web/public/web_state/web_state_observer.h" #import "ios/web/public/web_state/web_state_policy_decider.h" #include "ios/web/public/web_thread.h" @@ -36,7 +37,6 @@ #include "ios/web/webui/web_ui_ios_controller_factory_registry.h" #include "ios/web/webui/web_ui_ios_impl.h" #include "net/http/http_response_headers.h" -#include "services/service_manager/public/cpp/interface_registry.h" namespace web { @@ -540,12 +540,12 @@ #pragma mark - RequestTracker management -service_manager::InterfaceRegistry* WebStateImpl::GetMojoInterfaceRegistry() { - if (!mojo_interface_registry_) { - mojo_interface_registry_ = - base::MakeUnique<service_manager::InterfaceRegistry>(std::string()); +WebStateInterfaceProvider* WebStateImpl::GetWebStateInterfaceProvider() { + if (!web_state_interface_provider_) { + web_state_interface_provider_ = + base::MakeUnique<WebStateInterfaceProvider>(); } - return mojo_interface_registry_.get(); + return web_state_interface_provider_.get(); } base::WeakPtr<WebState> WebStateImpl::AsWeakPtr() {
diff --git a/ios/web/webui/mojo_facade_unittest.mm b/ios/web/webui/mojo_facade_unittest.mm index cf7e512..27f6289 100644 --- a/ios/web/webui/mojo_facade_unittest.mm +++ b/ios/web/webui/mojo_facade_unittest.mm
@@ -12,11 +12,12 @@ #import "base/test/ios/wait_util.h" #include "ios/web/public/test/web_test.h" #import "ios/web/public/web_state/js/crw_js_injection_evaluator.h" +#include "ios/web/public/web_state/web_state_interface_provider.h" #include "ios/web/test/mojo_test.mojom.h" +#include "ios/web/web_state/web_state_impl.h" #include "mojo/public/cpp/bindings/binding_set.h" #include "services/service_manager/public/cpp/identity.h" #include "services/service_manager/public/cpp/interface_factory.h" -#include "services/service_manager/public/cpp/interface_registry.h" #import "testing/gtest_mac.h" #import "third_party/ocmock/OCMock/OCMock.h" @@ -61,13 +62,12 @@ class MojoFacadeTest : public WebTest { protected: MojoFacadeTest() { - interface_registry_.reset( - new service_manager::InterfaceRegistry(std::string())); - interface_registry_->AddInterface(&ui_handler_factory_); + interface_provider_ = base::MakeUnique<WebStateInterfaceProvider>(); + interface_provider_->registry()->AddInterface(&ui_handler_factory_); evaluator_.reset([[OCMockObject mockForProtocol:@protocol(CRWJSInjectionEvaluator)] retain]); facade_.reset(new MojoFacade( - interface_registry_.get(), + interface_provider_.get(), static_cast<id<CRWJSInjectionEvaluator>>(evaluator_.get()))); } @@ -76,7 +76,7 @@ private: TestUIHandlerFactory ui_handler_factory_; - std::unique_ptr<service_manager::InterfaceRegistry> interface_registry_; + std::unique_ptr<WebStateInterfaceProvider> interface_provider_; base::scoped_nsobject<OCMockObject> evaluator_; std::unique_ptr<MojoFacade> facade_; };
diff --git a/ios/web/webui/web_ui_mojo_inttest.mm b/ios/web/webui/web_ui_mojo_inttest.mm index d709279c..12d62ce 100644 --- a/ios/web/webui/web_ui_mojo_inttest.mm +++ b/ios/web/webui/web_ui_mojo_inttest.mm
@@ -9,6 +9,7 @@ #import "base/test/ios/wait_util.h" #include "base/threading/thread_task_runner_handle.h" #import "ios/web/public/navigation_manager.h" +#include "ios/web/public/web_state/web_state_interface_provider.h" #include "ios/web/public/web_ui_ios_data_source.h" #include "ios/web/public/webui/web_ui_ios_controller.h" #include "ios/web/public/webui/web_ui_ios_controller_factory.h" @@ -20,7 +21,6 @@ #import "ios/web/web_state/web_state_impl.h" #include "mojo/public/cpp/bindings/binding_set.h" #include "services/service_manager/public/cpp/identity.h" -#include "services/service_manager/public/cpp/interface_registry.h" #include "url/gurl.h" #include "url/scheme_host_port.h" @@ -103,7 +103,8 @@ web::WebState* web_state = web_ui->GetWebState(); web::WebUIIOSDataSource::Add(web_state->GetBrowserState(), source); - web_state->GetMojoInterfaceRegistry()->AddInterface(ui_handler); + web_state->GetWebStateInterfaceProvider()->registry()->AddInterface( + ui_handler); } };
diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc index 19b1879..2df25b2 100644 --- a/media/blink/webmediaplayer_impl.cc +++ b/media/blink/webmediaplayer_impl.cc
@@ -340,6 +340,7 @@ void WebMediaPlayerImpl::EnableOverlay() { overlay_enabled_ = true; if (surface_manager_) { + overlay_surface_id_.reset(); surface_created_cb_.Reset( base::Bind(&WebMediaPlayerImpl::OnSurfaceCreated, AsWeakPtr())); surface_manager_->CreateFullscreenSurface(pipeline_metadata_.natural_size, @@ -358,7 +359,7 @@ if (decoder_requires_restart_for_overlay_) ScheduleRestart(); else if (!set_surface_cb_.is_null()) - set_surface_cb_.Run(overlay_surface_id_); + set_surface_cb_.Run(*overlay_surface_id_); } void WebMediaPlayerImpl::EnteredFullscreen() { @@ -1696,10 +1697,10 @@ // If we're waiting for the surface to arrive, OnSurfaceCreated() will be // called later when it arrives; so do nothing for now. - if (overlay_enabled_ && overlay_surface_id_ == SurfaceManager::kNoSurfaceID) + if (!overlay_surface_id_) return; - OnSurfaceCreated(overlay_surface_id_); + OnSurfaceCreated(*overlay_surface_id_); } std::unique_ptr<Renderer> WebMediaPlayerImpl::CreateRenderer() {
diff --git a/media/blink/webmediaplayer_impl.h b/media/blink/webmediaplayer_impl.h index de05e09..6001102 100644 --- a/media/blink/webmediaplayer_impl.h +++ b/media/blink/webmediaplayer_impl.h
@@ -626,9 +626,10 @@ // For canceling ongoing surface creation requests when exiting fullscreen. base::CancelableCallback<void(int)> surface_created_cb_; - // The current overlay surface id. Populated while in fullscreen once the - // surface is created. - int overlay_surface_id_; + // The current overlay surface id. Populated, possibly with kNoSurfaceID if + // we're not supposed to use an overlay, unless we have an outstanding surface + // request to the SurfaceManager. + base::Optional<int> overlay_surface_id_; // If a surface is requested before it's finished being created, the request // is saved and satisfied once the surface is available. If the decoder does
diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc index 149e1e4..f9db61f3 100644 --- a/media/filters/chunk_demuxer.cc +++ b/media/filters/chunk_demuxer.cc
@@ -284,12 +284,16 @@ AudioDecoderConfig ChunkDemuxerStream::audio_decoder_config() { CHECK_EQ(type_, AUDIO); base::AutoLock auto_lock(lock_); + // Trying to track down crash. http://crbug.com/715761 + CHECK(stream_); return stream_->GetCurrentAudioDecoderConfig(); } VideoDecoderConfig ChunkDemuxerStream::video_decoder_config() { CHECK_EQ(type_, VIDEO); base::AutoLock auto_lock(lock_); + // Trying to track down crash. http://crbug.com/715761 + CHECK(stream_); return stream_->GetCurrentVideoDecoderConfig(); }
diff --git a/media/filters/source_buffer_stream.cc b/media/filters/source_buffer_stream.cc index 895df15..be4f1c1 100644 --- a/media/filters/source_buffer_stream.cc +++ b/media/filters/source_buffer_stream.cc
@@ -1551,12 +1551,18 @@ const AudioDecoderConfig& SourceBufferStream::GetCurrentAudioDecoderConfig() { if (config_change_pending_) CompleteConfigChange(); + // Trying to track down crash. http://crbug.com/715761 + CHECK(current_config_index_ >= 0 && + static_cast<size_t>(current_config_index_) < audio_configs_.size()); return audio_configs_[current_config_index_]; } const VideoDecoderConfig& SourceBufferStream::GetCurrentVideoDecoderConfig() { if (config_change_pending_) CompleteConfigChange(); + // Trying to track down crash. http://crbug.com/715761 + CHECK(current_config_index_ >= 0 && + static_cast<size_t>(current_config_index_) < video_configs_.size()); return video_configs_[current_config_index_]; }
diff --git a/media/gpu/ipc/service/gpu_video_encode_accelerator.cc b/media/gpu/ipc/service/gpu_video_encode_accelerator.cc index 533e2cd95..7d31fcf6 100644 --- a/media/gpu/ipc/service/gpu_video_encode_accelerator.cc +++ b/media/gpu/ipc/service/gpu_video_encode_accelerator.cc
@@ -173,6 +173,9 @@ encode_task_runner_(main_task_runner_), weak_this_factory_for_encoder_worker_(this), weak_this_factory_(this) { + weak_this_for_encoder_worker_ = + weak_this_factory_for_encoder_worker_.GetWeakPtr(); + weak_this_ = weak_this_factory_.GetWeakPtr(); stub_->AddDestructionObserver(this); make_context_current_ = base::Bind(&MakeDecoderContextCurrent, stub_->AsWeakPtr()); @@ -182,13 +185,7 @@ // This class can only be self-deleted from OnWillDestroyStub(), which means // the VEA has already been destroyed in there. DCHECK(!encoder_); - if (encoder_worker_thread_.IsRunning()) { - encoder_worker_task_runner_->PostTask( - FROM_HERE, - base::Bind(&GpuVideoEncodeAccelerator::DestroyOnEncoderWorker, - weak_this_factory_for_encoder_worker_.GetWeakPtr())); - encoder_worker_thread_.Stop(); - } + DCHECK(!encoder_worker_thread_.IsRunning()); } bool GpuVideoEncodeAccelerator::Initialize(VideoPixelFormat input_format, @@ -229,8 +226,8 @@ input_visible_size_ = input_visible_size; // Attempt to set up performing encoding tasks on IO thread, if supported // by the VEA. - if (encoder_->TryToSetupEncodeOnSeparateThread( - weak_this_factory_.GetWeakPtr(), io_task_runner_)) { + if (encoder_->TryToSetupEncodeOnSeparateThread(weak_this_, + io_task_runner_)) { filter_ = new MessageFilter(this, host_route_id_); stub_->channel()->AddFilter(filter_.get()); encode_task_runner_ = io_task_runner_; @@ -325,6 +322,16 @@ filter_removed_.Wait(); } + // We should stop |encoder_worker_thread_| before releasing |encoder_|, see + // crbug.com/715759. + if (encoder_worker_thread_.IsRunning()) { + encoder_worker_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GpuVideoEncodeAccelerator::DestroyOnEncoderWorker, + weak_this_for_encoder_worker_)); + encoder_worker_thread_.Stop(); + } + stub_->channel()->RemoveRoute(host_route_id_); stub_->RemoveDestructionObserver(this); encoder_.reset(); @@ -404,7 +411,7 @@ encoder_worker_task_runner_->PostTask( FROM_HERE, base::Bind(&GpuVideoEncodeAccelerator::CreateEncodeFrameOnEncoderWorker, - weak_this_factory_for_encoder_worker_.GetWeakPtr(), params)); + weak_this_for_encoder_worker_, params)); } void GpuVideoEncodeAccelerator::OnUseOutputBitstreamBuffer( @@ -465,18 +472,18 @@ if (!map_offset.IsValid() || !map_size.IsValid()) { DLOG(ERROR) << __func__ << " invalid map_offset or map_size"; encode_task_runner_->PostTask( - FROM_HERE, base::Bind(&GpuVideoEncodeAccelerator::NotifyError, - weak_this_factory_.GetWeakPtr(), - VideoEncodeAccelerator::kPlatformFailureError)); + FROM_HERE, + base::Bind(&GpuVideoEncodeAccelerator::NotifyError, weak_this_, + VideoEncodeAccelerator::kPlatformFailureError)); return; } if (!shm->MapAt(map_offset.ValueOrDie(), map_size.ValueOrDie())) { DLOG(ERROR) << __func__ << " could not map frame_id=" << params.frame_id; encode_task_runner_->PostTask( - FROM_HERE, base::Bind(&GpuVideoEncodeAccelerator::NotifyError, - weak_this_factory_.GetWeakPtr(), - VideoEncodeAccelerator::kPlatformFailureError)); + FROM_HERE, + base::Bind(&GpuVideoEncodeAccelerator::NotifyError, weak_this_, + VideoEncodeAccelerator::kPlatformFailureError)); return; } @@ -489,9 +496,9 @@ if (!frame) { DLOG(ERROR) << __func__ << " could not create a frame"; encode_task_runner_->PostTask( - FROM_HERE, base::Bind(&GpuVideoEncodeAccelerator::NotifyError, - weak_this_factory_.GetWeakPtr(), - VideoEncodeAccelerator::kPlatformFailureError)); + FROM_HERE, + base::Bind(&GpuVideoEncodeAccelerator::NotifyError, weak_this_, + VideoEncodeAccelerator::kPlatformFailureError)); return; } @@ -500,9 +507,9 @@ frame->AddDestructionObserver( base::Bind(&DropSharedMemory, base::Passed(&shm))); encode_task_runner_->PostTask( - FROM_HERE, base::Bind(&GpuVideoEncodeAccelerator::OnEncodeFrameCreated, - weak_this_factory_.GetWeakPtr(), params.frame_id, - params.force_keyframe, frame)); + FROM_HERE, + base::Bind(&GpuVideoEncodeAccelerator::OnEncodeFrameCreated, weak_this_, + params.frame_id, params.force_keyframe, frame)); } void GpuVideoEncodeAccelerator::DestroyOnEncoderWorker() { @@ -517,6 +524,9 @@ DVLOG(3) << __func__; DCHECK(CheckIfCalledOnCorrectThread()); + if (filter_removed_.IsSignaled()) + return; + if (!frame) { DLOG(ERROR) << __func__ << " could not create a frame"; NotifyError(VideoEncodeAccelerator::kPlatformFailureError);
diff --git a/media/gpu/ipc/service/gpu_video_encode_accelerator.h b/media/gpu/ipc/service/gpu_video_encode_accelerator.h index 351a97c..f6ed332 100644 --- a/media/gpu/ipc/service/gpu_video_encode_accelerator.h +++ b/media/gpu/ipc/service/gpu_video_encode_accelerator.h
@@ -159,12 +159,16 @@ // otherwise |main_thread_task_runner_|. scoped_refptr<base::SingleThreadTaskRunner> encode_task_runner_; + base::WeakPtr<GpuVideoEncodeAccelerator> weak_this_for_encoder_worker_; + base::WeakPtr<GpuVideoEncodeAccelerator> weak_this_; + // Weak pointer for referring back to |this| on |encoder_worker_task_runner_|. base::WeakPtrFactory<GpuVideoEncodeAccelerator> weak_this_factory_for_encoder_worker_; // Weak pointer for VideoFrames that refer back to |this| on - // |main_task_runner| or |io_task_runner_|. + // |main_task_runner| or |io_task_runner_|. |io_task_runner_| is used if and + // only if |filter_| is applied. base::WeakPtrFactory<GpuVideoEncodeAccelerator> weak_this_factory_; DISALLOW_COPY_AND_ASSIGN(GpuVideoEncodeAccelerator);
diff --git a/media/mojo/services/BUILD.gn b/media/mojo/services/BUILD.gn index be11d2a..875eb9fc 100644 --- a/media/mojo/services/BUILD.gn +++ b/media/mojo/services/BUILD.gn
@@ -70,6 +70,8 @@ "gpu_mojo_media_client.h", "interface_factory_impl.cc", "interface_factory_impl.h", + "media_interface_provider.cc", + "media_interface_provider.h", "media_mojo_export.h", "media_resource_shim.cc", "media_resource_shim.h", @@ -179,6 +181,7 @@ catalog = ":media_service_unittests_catalog" deps = [ + ":lib", "//base", "//media", "//media/base:test_support",
diff --git a/media/mojo/services/media_interface_provider.cc b/media/mojo/services/media_interface_provider.cc new file mode 100644 index 0000000..1e090fd --- /dev/null +++ b/media/mojo/services/media_interface_provider.cc
@@ -0,0 +1,24 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/mojo/services/media_interface_provider.h" + +#include "services/service_manager/public/cpp/identity.h" + +namespace media { + +MediaInterfaceProvider::MediaInterfaceProvider( + service_manager::mojom::InterfaceProviderRequest request) + : binding_(this, std::move(request)) {} + +MediaInterfaceProvider::~MediaInterfaceProvider() = default; + +void MediaInterfaceProvider::GetInterface( + const std::string& interface_name, + mojo::ScopedMessagePipeHandle handle) { + registry_.BindInterface(service_manager::Identity(), interface_name, + std::move(handle)); +} + +} // namespace media
diff --git a/media/mojo/services/media_interface_provider.h b/media/mojo/services/media_interface_provider.h new file mode 100644 index 0000000..8c2064eb --- /dev/null +++ b/media/mojo/services/media_interface_provider.h
@@ -0,0 +1,38 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_MOJO_SERVICES_MEDIA_INTERFACE_PROVIDER_H_ +#define MEDIA_MOJO_SERVICES_MEDIA_INTERFACE_PROVIDER_H_ + +#include "media/mojo/services/media_mojo_export.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "services/service_manager/public/cpp/binder_registry.h" +#include "services/service_manager/public/interfaces/interface_provider.mojom.h" + +namespace media { + +class MEDIA_MOJO_EXPORT MediaInterfaceProvider + : public NON_EXPORTED_BASE(service_manager::mojom::InterfaceProvider) { + public: + explicit MediaInterfaceProvider( + service_manager::mojom::InterfaceProviderRequest request); + ~MediaInterfaceProvider() override; + + service_manager::BinderRegistry* registry() { return ®istry_; } + + private: + // service_manager::mojom::InterfaceProvider: + void GetInterface(const std::string& interface_name, + mojo::ScopedMessagePipeHandle handle) override; + + service_manager::BinderRegistry registry_; + + mojo::Binding<service_manager::mojom::InterfaceProvider> binding_; + + DISALLOW_COPY_AND_ASSIGN(MediaInterfaceProvider); +}; + +} // namespace media + +#endif // MEDIA_MOJO_SERVICES_MEDIA_BINDER_REGISTRY_H_
diff --git a/media/mojo/services/media_service_unittest.cc b/media/mojo/services/media_service_unittest.cc index c7e611c..cabe0e3 100644 --- a/media/mojo/services/media_service_unittest.cc +++ b/media/mojo/services/media_service_unittest.cc
@@ -22,9 +22,9 @@ #include "media/mojo/interfaces/interface_factory.mojom.h" #include "media/mojo/interfaces/media_service.mojom.h" #include "media/mojo/interfaces/renderer.mojom.h" +#include "media/mojo/services/media_interface_provider.h" #include "mojo/public/cpp/bindings/associated_binding.h" #include "mojo/public/cpp/bindings/interface_request.h" -#include "services/service_manager/public/cpp/interface_registry.h" #include "services/service_manager/public/cpp/service_test.h" #include "testing/gmock/include/gmock/gmock.h" @@ -80,14 +80,9 @@ media::mojom::MediaServicePtr media_service; connector()->BindInterface("media", &media_service); - auto registry = - base::MakeUnique<service_manager::InterfaceRegistry>(std::string()); service_manager::mojom::InterfaceProviderPtr interfaces; - registry->Bind(MakeRequest(&interfaces), service_manager::Identity(), - service_manager::InterfaceProviderSpec(), - service_manager::Identity(), - service_manager::InterfaceProviderSpec()); - + auto provider = base::MakeUnique<MediaInterfaceProvider>( + mojo::MakeRequest(&interfaces)); media_service->CreateInterfaceFactory( mojo::MakeRequest(&interface_factory_), std::move(interfaces));
diff --git a/mojo/BUILD.gn b/mojo/BUILD.gn index 070e2d1c..1c0914c 100644 --- a/mojo/BUILD.gn +++ b/mojo/BUILD.gn
@@ -35,7 +35,6 @@ "//mojo/edk/test:mojo_public_bindings_unittests", "//mojo/edk/test:mojo_public_system_perftests", "//mojo/edk/test:mojo_public_system_unittests", - "//services/service_manager/public/cpp/tests:mojo_public_application_unittests", "//services/service_manager/tests", ] }
diff --git a/mojo/common/values_struct_traits.cc b/mojo/common/values_struct_traits.cc index 6d392ec..12e5173 100644 --- a/mojo/common/values_struct_traits.cc +++ b/mojo/common/values_struct_traits.cc
@@ -48,9 +48,7 @@ std::unique_ptr<base::DictionaryValue> CloneTraits<std::unique_ptr<base::DictionaryValue>, false>::Clone( const std::unique_ptr<base::DictionaryValue>& input) { - auto result = base::MakeUnique<base::DictionaryValue>(); - result->MergeDictionary(input.get()); - return result; + return input ? input->CreateDeepCopy() : nullptr; } bool UnionTraits<common::mojom::ValueDataView, std::unique_ptr<base::Value>>:: @@ -106,4 +104,10 @@ return false; } +std::unique_ptr<base::Value> +CloneTraits<std::unique_ptr<base::Value>, false>::Clone( + const std::unique_ptr<base::Value>& input) { + return input ? input->CreateDeepCopy() : nullptr; +} + } // namespace mojo
diff --git a/mojo/common/values_struct_traits.h b/mojo/common/values_struct_traits.h index 2613f6bfd5..94faa82c 100644 --- a/mojo/common/values_struct_traits.h +++ b/mojo/common/values_struct_traits.h
@@ -251,6 +251,12 @@ std::unique_ptr<base::Value>* value); }; +template <> +struct CloneTraits<std::unique_ptr<base::Value>, false> { + static std::unique_ptr<base::Value> Clone( + const std::unique_ptr<base::Value>& input); +}; + } // namespace mojo #endif // MOJO_COMMON_VALUES_STRUCT_TRAITS_H_
diff --git a/net/quic/core/crypto/scoped_evp_aead_ctx.cc b/net/quic/core/crypto/scoped_evp_aead_ctx.cc index 4472358..ab9e54ad 100644 --- a/net/quic/core/crypto/scoped_evp_aead_ctx.cc +++ b/net/quic/core/crypto/scoped_evp_aead_ctx.cc
@@ -8,6 +8,7 @@ ScopedEVPAEADCtx::ScopedEVPAEADCtx() { ctx_.aead = nullptr; + ctx_.aead_state = nullptr; } ScopedEVPAEADCtx::~ScopedEVPAEADCtx() {
diff --git a/remoting/host/input_injector_win.cc b/remoting/host/input_injector_win.cc index 9354eda..8f2b7fa1 100644 --- a/remoting/host/input_injector_win.cc +++ b/remoting/host/input_injector_win.cc
@@ -199,6 +199,12 @@ void HandleMouse(const MouseEvent& event); void HandleTouch(const TouchEvent& event); + // Check if the given scan code is caps lock or num lock. + bool IsLockKey(int scancode); + + // Sets the keyboard lock states to those provided. + void SetLockStates(uint32_t states); + scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_; std::unique_ptr<Clipboard> clipboard_; @@ -348,6 +354,10 @@ if (scancode == ui::KeycodeConverter::InvalidNativeKeycode()) return; + if (event.has_lock_states() && !IsLockKey(scancode)) { + SetLockStates(event.lock_states()); + } + uint32_t flags = KEYEVENTF_SCANCODE | (event.pressed() ? 0 : KEYEVENTF_KEYUP); SendKeyboardInput(flags, scancode); } @@ -384,6 +394,38 @@ touch_injector_->InjectTouchEvent(event); } +bool InputInjectorWin::Core::IsLockKey(int scancode) { + UINT virtual_key = MapVirtualKey(scancode, MAPVK_VSC_TO_VK); + return virtual_key == VK_CAPITAL || virtual_key == VK_NUMLOCK; +} + +void InputInjectorWin::Core::SetLockStates(uint32_t states) { + // Can't use SendKeyboardInput because we need to send virtual key codes, not + // scan codes. + INPUT input[2] = {}; + input[0].type = INPUT_KEYBOARD; + input[1].type = INPUT_KEYBOARD; + input[1].ki.dwFlags = KEYEVENTF_KEYUP; + + bool client_capslock_state = + (states & protocol::KeyEvent::LOCK_STATES_CAPSLOCK) != 0; + bool host_capslock_state = (GetKeyState(VK_CAPITAL) & 1) != 0; + if (client_capslock_state != host_capslock_state) { + input[0].ki.wVk = VK_CAPITAL; + input[1].ki.wVk = VK_CAPITAL; + SendInput(arraysize(input), input, sizeof(INPUT)); + } + + bool client_numlock_state = + (states & protocol::KeyEvent::LOCK_STATES_NUMLOCK) != 0; + bool host_numlock_state = (GetKeyState(VK_NUMLOCK) & 1) != 0; + if (client_numlock_state != host_numlock_state) { + input[0].ki.wVk = VK_NUMLOCK; + input[1].ki.wVk = VK_NUMLOCK; + SendInput(arraysize(input), input, sizeof(INPUT)); + } +} + } // namespace // static
diff --git a/services/preferences/BUILD.gn b/services/preferences/BUILD.gn index 12b4182..24e7c71 100644 --- a/services/preferences/BUILD.gn +++ b/services/preferences/BUILD.gn
@@ -19,6 +19,7 @@ deps = [ "//components/prefs", "//services/preferences/public/cpp", + "//services/preferences/public/cpp/lib", "//services/preferences/public/interfaces", "//services/preferences/tracked", "//services/service_manager/public/cpp",
diff --git a/services/preferences/persistent_pref_store_impl.cc b/services/preferences/persistent_pref_store_impl.cc index 959303876..f5b4ae1 100644 --- a/services/preferences/persistent_pref_store_impl.cc +++ b/services/preferences/persistent_pref_store_impl.cc
@@ -11,7 +11,9 @@ #include "base/stl_util.h" #include "base/values.h" #include "components/prefs/persistent_pref_store.h" +#include "mojo/common/values_struct_traits.h" #include "mojo/public/cpp/bindings/binding.h" +#include "services/preferences/public/cpp/lib/util.h" namespace prefs { @@ -41,9 +43,7 @@ std::vector<mojom::PrefUpdatePtr> filtered_updates; for (const auto& update : updates) { if (base::ContainsKey(observed_keys_, update->key)) { - filtered_updates.push_back(mojom::PrefUpdate::New( - update->key, - update->value ? update->value->CreateDeepCopy() : nullptr, 0)); + filtered_updates.push_back(update->Clone()); } } if (!filtered_updates.empty()) @@ -133,11 +133,40 @@ entry.first->OnPrefValuesChanged(updates); for (auto& update : updates) { - if (update->value) { - backing_pref_store_->SetValue(update->key, std::move(update->value), - update->flags); - } else { - backing_pref_store_->RemoveValue(update->key, update->flags); + if (update->value->is_atomic_update()) { + auto& value = update->value->get_atomic_update(); + if (value) { + backing_pref_store_->SetValue(update->key, std::move(value), + update->flags); + } else { + backing_pref_store_->RemoveValue(update->key, update->flags); + } + } else if (update->value->is_split_updates()) { + base::Value* mutable_value = nullptr; + base::DictionaryValue* dictionary_value = nullptr; + std::unique_ptr<base::DictionaryValue> pending_dictionary; + if (!backing_pref_store_->GetMutableValue(update->key, &mutable_value) || + !mutable_value->GetAsDictionary(&dictionary_value)) { + pending_dictionary = base::MakeUnique<base::DictionaryValue>(); + dictionary_value = pending_dictionary.get(); + } + std::set<std::vector<std::string>> updated_paths; + for (auto& split_update : update->value->get_split_updates()) { + if (split_update->path.empty()) + continue; + + SetValue(dictionary_value, + {split_update->path.begin(), split_update->path.end()}, + std::move(split_update->value)); + updated_paths.insert(std::move(split_update->path)); + } + if (pending_dictionary) { + backing_pref_store_->SetValue( + update->key, std::move(pending_dictionary), update->flags); + } else { + backing_pref_store_->ReportSubValuesChanged( + update->key, std::move(updated_paths), update->flags); + } } } }
diff --git a/services/preferences/pref_service_factory_unittest.cc b/services/preferences/pref_service_factory_unittest.cc index 21094e8..9a5a6f6 100644 --- a/services/preferences/pref_service_factory_unittest.cc +++ b/services/preferences/pref_service_factory_unittest.cc
@@ -7,6 +7,7 @@ #include "base/files/scoped_temp_dir.h" #include "base/memory/ptr_util.h" #include "base/message_loop/message_loop.h" +#include "base/strings/utf_string_conversions.h" #include "base/test/sequenced_worker_pool_owner.h" #include "components/prefs/pref_change_registrar.h" #include "components/prefs/pref_registry_simple.h" @@ -14,8 +15,10 @@ #include "components/prefs/value_map_pref_store.h" #include "components/prefs/writeable_pref_store.h" #include "mojo/public/cpp/bindings/binding_set.h" +#include "services/preferences/public/cpp/dictionary_value_update.h" #include "services/preferences/public/cpp/pref_service_main.h" #include "services/preferences/public/cpp/pref_store_impl.h" +#include "services/preferences/public/cpp/scoped_pref_update.h" #include "services/preferences/public/interfaces/preferences.mojom.h" #include "services/service_manager/public/cpp/binder_registry.h" #include "services/service_manager/public/cpp/interface_factory.h" @@ -76,6 +79,7 @@ constexpr int kUpdatedValue = 2; constexpr char kKey[] = "some_key"; constexpr char kOtherKey[] = "some_other_key"; +constexpr char kDictionaryKey[] = "a.dictionary.pref"; class PrefServiceFactoryTest : public base::MessageLoop::DestructionObserver, public service_manager::test::ServiceTest { @@ -131,6 +135,7 @@ auto pref_registry = make_scoped_refptr(new PrefRegistrySimple()); pref_registry->RegisterIntegerPref(kKey, kInitialValue); pref_registry->RegisterIntegerPref(kOtherKey, kInitialValue); + pref_registry->RegisterDictionaryPref(kDictionaryKey); ConnectToPrefService(connector(), pref_registry, std::vector<PrefValueStore::PrefStoreType>(), base::Bind(&PrefServiceFactoryTest::OnCreate, @@ -265,5 +270,228 @@ EXPECT_EQ(2, pref_service->GetInteger(kKey)); } +void Fail(PrefService* pref_service) { + FAIL() << "Unexpected change notification: " + << *pref_service->GetDictionary(kDictionaryKey); +} + +TEST_F(PrefServiceFactoryTest, MultipleClients_SubPrefUpdates_Basic) { + auto pref_service = Create(); + auto pref_service2 = Create(); + + void (*updates[])(ScopedDictionaryPrefUpdate*) = { + [](ScopedDictionaryPrefUpdate* update) { + (*update)->SetInteger("path.to.integer", 1); + int out = 0; + ASSERT_TRUE((*update)->GetInteger("path.to.integer", &out)); + EXPECT_EQ(1, out); + }, + [](ScopedDictionaryPrefUpdate* update) { + (*update)->SetIntegerWithoutPathExpansion("key.for.integer", 2); + int out = 0; + ASSERT_TRUE( + (*update)->GetIntegerWithoutPathExpansion("key.for.integer", &out)); + EXPECT_EQ(2, out); + }, + [](ScopedDictionaryPrefUpdate* update) { + (*update)->SetDouble("path.to.double", 3); + double out = 0; + ASSERT_TRUE((*update)->GetDouble("path.to.double", &out)); + EXPECT_EQ(3, out); + }, + [](ScopedDictionaryPrefUpdate* update) { + (*update)->SetDoubleWithoutPathExpansion("key.for.double", 4); + double out = 0; + ASSERT_TRUE( + (*update)->GetDoubleWithoutPathExpansion("key.for.double", &out)); + EXPECT_EQ(4, out); + }, + [](ScopedDictionaryPrefUpdate* update) { + (*update)->SetBoolean("path.to.boolean", true); + bool out = 0; + ASSERT_TRUE((*update)->GetBoolean("path.to.boolean", &out)); + EXPECT_TRUE(out); + }, + [](ScopedDictionaryPrefUpdate* update) { + (*update)->SetBooleanWithoutPathExpansion("key.for.boolean", false); + bool out = 0; + ASSERT_TRUE( + (*update)->GetBooleanWithoutPathExpansion("key.for.boolean", &out)); + EXPECT_FALSE(out); + }, + [](ScopedDictionaryPrefUpdate* update) { + (*update)->SetString("path.to.string", "hello"); + std::string out; + ASSERT_TRUE((*update)->GetString("path.to.string", &out)); + EXPECT_EQ("hello", out); + }, + [](ScopedDictionaryPrefUpdate* update) { + (*update)->SetStringWithoutPathExpansion("key.for.string", "prefs!"); + std::string out; + ASSERT_TRUE( + (*update)->GetStringWithoutPathExpansion("key.for.string", &out)); + EXPECT_EQ("prefs!", out); + }, + [](ScopedDictionaryPrefUpdate* update) { + (*update)->SetString("path.to.string16", base::ASCIIToUTF16("hello")); + base::string16 out; + ASSERT_TRUE((*update)->GetString("path.to.string16", &out)); + EXPECT_EQ(base::ASCIIToUTF16("hello"), out); + }, + [](ScopedDictionaryPrefUpdate* update) { + (*update)->SetStringWithoutPathExpansion("key.for.string16", + base::ASCIIToUTF16("prefs!")); + base::string16 out; + ASSERT_TRUE( + (*update)->GetStringWithoutPathExpansion("key.for.string16", &out)); + EXPECT_EQ(base::ASCIIToUTF16("prefs!"), out); + }, + [](ScopedDictionaryPrefUpdate* update) { + base::ListValue list; + list.AppendInteger(1); + list.AppendDouble(2); + list.AppendBoolean(true); + list.AppendString("four"); + (*update)->Set("path.to.list", list.CreateDeepCopy()); + const base::ListValue* out = nullptr; + ASSERT_TRUE((*update)->GetList("path.to.list", &out)); + EXPECT_EQ(list, *out); + }, + [](ScopedDictionaryPrefUpdate* update) { + base::ListValue list; + list.AppendInteger(1); + list.AppendDouble(2); + list.AppendBoolean(true); + list.AppendString("four"); + (*update)->SetWithoutPathExpansion("key.for.list", + list.CreateDeepCopy()); + const base::ListValue* out = nullptr; + ASSERT_TRUE( + (*update)->GetListWithoutPathExpansion("key.for.list", &out)); + EXPECT_EQ(list, *out); + }, + [](ScopedDictionaryPrefUpdate* update) { + base::DictionaryValue dict; + dict.SetInteger("int", 1); + dict.SetDouble("double", 2); + dict.SetBoolean("bool", true); + dict.SetString("string", "four"); + (*update)->Set("path.to.dict", dict.CreateDeepCopy()); + const base::DictionaryValue* out = nullptr; + ASSERT_TRUE((*update)->GetDictionary("path.to.dict", &out)); + EXPECT_EQ(dict, *out); + }, + [](ScopedDictionaryPrefUpdate* update) { + base::DictionaryValue dict; + dict.SetInteger("int", 1); + dict.SetDouble("double", 2); + dict.SetBoolean("bool", true); + dict.SetString("string", "four"); + (*update)->SetWithoutPathExpansion("key.for.dict", + dict.CreateDeepCopy()); + const base::DictionaryValue* out = nullptr; + ASSERT_TRUE( + (*update)->GetDictionaryWithoutPathExpansion("key.for.dict", &out)); + EXPECT_EQ(dict, *out); + }, + }; + int current_value = kInitialValue + 1; + for (auto& mutation : updates) { + base::DictionaryValue expected_value; + { + ScopedDictionaryPrefUpdate update(pref_service.get(), kDictionaryKey); + EXPECT_EQ(update->AsConstDictionary()->empty(), update->empty()); + EXPECT_EQ(update->AsConstDictionary()->size(), update->size()); + mutation(&update); + EXPECT_EQ(update->AsConstDictionary()->empty(), update->empty()); + EXPECT_EQ(update->AsConstDictionary()->size(), update->size()); + expected_value = *update->AsConstDictionary(); + } + + EXPECT_EQ(expected_value, *pref_service->GetDictionary(kDictionaryKey)); + WaitForPrefChange(pref_service2.get(), kDictionaryKey); + EXPECT_EQ(expected_value, *pref_service2->GetDictionary(kDictionaryKey)); + + { + // Apply the same mutation again. Each mutation should be idempotent so + // should not trigger a notification. + ScopedDictionaryPrefUpdate update(pref_service.get(), kDictionaryKey); + mutation(&update); + EXPECT_EQ(expected_value, *update->AsConstDictionary()); + } + { + // Watch for an unexpected change to kDictionaryKey. + PrefChangeRegistrar registrar; + registrar.Init(pref_service2.get()); + registrar.Add(kDictionaryKey, base::Bind(&Fail, pref_service2.get())); + + // Make and wait for a change to another pref to ensure an unexpected + // change to kDictionaryKey is detected. + pref_service->SetInteger(kKey, ++current_value); + WaitForPrefChange(pref_service2.get(), kKey); + } + } +} + +TEST_F(PrefServiceFactoryTest, MultipleClients_SubPrefUpdates_Erase) { + auto pref_service = Create(); + auto pref_service2 = Create(); + { + ScopedDictionaryPrefUpdate update(pref_service.get(), kDictionaryKey); + update->SetInteger("path.to.integer", 1); + } + WaitForPrefChange(pref_service2.get(), kDictionaryKey); + EXPECT_FALSE(pref_service2->GetDictionary(kDictionaryKey)->empty()); + + { + ScopedDictionaryPrefUpdate update(pref_service.get(), kDictionaryKey); + ASSERT_TRUE(update->RemovePath("path.to.integer", nullptr)); + } + WaitForPrefChange(pref_service2.get(), kDictionaryKey); + EXPECT_TRUE(pref_service2->GetDictionary(kDictionaryKey)->empty()); +} + +TEST_F(PrefServiceFactoryTest, MultipleClients_SubPrefUpdates_ClearDictionary) { + auto pref_service = Create(); + auto pref_service2 = Create(); + + { + ScopedDictionaryPrefUpdate update(pref_service.get(), kDictionaryKey); + update->SetInteger("path.to.integer", 1); + } + WaitForPrefChange(pref_service2.get(), kDictionaryKey); + EXPECT_FALSE(pref_service2->GetDictionary(kDictionaryKey)->empty()); + + { + ScopedDictionaryPrefUpdate update(pref_service.get(), kDictionaryKey); + update->Clear(); + } + WaitForPrefChange(pref_service2.get(), kDictionaryKey); + EXPECT_TRUE(pref_service2->GetDictionary(kDictionaryKey)->empty()); +} + +TEST_F(PrefServiceFactoryTest, + MultipleClients_SubPrefUpdates_ClearEmptyDictionary) { + auto pref_service = Create(); + auto pref_service2 = Create(); + + { + ScopedDictionaryPrefUpdate update(pref_service.get(), kDictionaryKey); + update.Get(); + } + WaitForPrefChange(pref_service2.get(), kDictionaryKey); + EXPECT_TRUE(pref_service2->GetDictionary(kDictionaryKey)->empty()); + + { + ScopedDictionaryPrefUpdate update(pref_service.get(), kDictionaryKey); + update->Clear(); + } + PrefChangeRegistrar registrar; + registrar.Init(pref_service2.get()); + registrar.Add(kDictionaryKey, base::Bind(&Fail, pref_service2.get())); + pref_service->SetInteger(kKey, kUpdatedValue); + WaitForPrefChange(pref_service2.get(), kKey); +} + } // namespace } // namespace prefs
diff --git a/services/preferences/public/cpp/BUILD.gn b/services/preferences/public/cpp/BUILD.gn index 1499131..53ead75 100644 --- a/services/preferences/public/cpp/BUILD.gn +++ b/services/preferences/public/cpp/BUILD.gn
@@ -4,6 +4,8 @@ source_set("cpp") { sources = [ + "dictionary_value_update.cc", + "dictionary_value_update.h", "persistent_pref_store_client.cc", "persistent_pref_store_client.h", "pref_registry_serializer.cc", @@ -18,6 +20,8 @@ "pref_store_client_mixin.h", "pref_store_impl.cc", "pref_store_impl.h", + "scoped_pref_update.cc", + "scoped_pref_update.h", ] public_deps = [ @@ -29,6 +33,7 @@ deps = [ "//mojo/public/cpp/bindings", + "//services/preferences/public/cpp/lib", ] }
diff --git a/services/preferences/public/cpp/dictionary_value_update.cc b/services/preferences/public/cpp/dictionary_value_update.cc new file mode 100644 index 0000000..513fe61 --- /dev/null +++ b/services/preferences/public/cpp/dictionary_value_update.cc
@@ -0,0 +1,343 @@ +// 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 "services/preferences/public/cpp/dictionary_value_update.h" + +#include <algorithm> +#include <iterator> +#include <utility> + +#include "base/memory/ptr_util.h" +#include "base/strings/string_split.h" +#include "base/values.h" + +namespace prefs { + +DictionaryValueUpdate::DictionaryValueUpdate(UpdateCallback report_update, + base::DictionaryValue* value, + std::vector<std::string> path) + : report_update_(std::move(report_update)), + value_(value), + path_(std::move(path)) {} + +DictionaryValueUpdate::~DictionaryValueUpdate() = default; + +bool DictionaryValueUpdate::HasKey(base::StringPiece key) const { + return value_->HasKey(key); +} + +size_t DictionaryValueUpdate::size() const { + return value_->size(); +} + +bool DictionaryValueUpdate::empty() const { + return value_->empty(); +} + +void DictionaryValueUpdate::Clear() { + if (empty()) + return; + + RecordSplitPath(std::vector<base::StringPiece>()); + value_->Clear(); +} + +void DictionaryValueUpdate::Set(base::StringPiece path, + std::unique_ptr<base::Value> in_value) { + const base::Value* old_value = nullptr; + if (value_->Get(path, &old_value) && *old_value == *in_value) + return; + + RecordPath(path); + value_->Set(path, std::move(in_value)); +} + +void DictionaryValueUpdate::SetBoolean(base::StringPiece path, bool in_value) { + Set(path, base::MakeUnique<base::Value>(in_value)); +} + +void DictionaryValueUpdate::SetInteger(base::StringPiece path, int in_value) { + Set(path, base::MakeUnique<base::Value>(in_value)); +} + +void DictionaryValueUpdate::SetDouble(base::StringPiece path, double in_value) { + Set(path, base::MakeUnique<base::Value>(in_value)); +} + +void DictionaryValueUpdate::SetString(base::StringPiece path, + base::StringPiece in_value) { + Set(path, base::MakeUnique<base::Value>(in_value)); +} + +void DictionaryValueUpdate::SetString(base::StringPiece path, + const base::string16& in_value) { + Set(path, base::MakeUnique<base::Value>(in_value)); +} + +std::unique_ptr<DictionaryValueUpdate> DictionaryValueUpdate::SetDictionary( + base::StringPiece path, + std::unique_ptr<base::DictionaryValue> in_value) { + RecordPath(path); + base::DictionaryValue* dictionary_value = in_value.get(); + value_->Set(path, std::move(in_value)); + + return base::MakeUnique<DictionaryValueUpdate>( + report_update_, dictionary_value, ConcatPath(path_, path)); +} + +void DictionaryValueUpdate::SetWithoutPathExpansion( + base::StringPiece key, + std::unique_ptr<base::Value> in_value) { + const base::Value* old_value = nullptr; + if (value_->GetWithoutPathExpansion(key, &old_value) && + *old_value == *in_value) { + return; + } + RecordKey(key); + value_->SetWithoutPathExpansion(key, std::move(in_value)); +} + +void DictionaryValueUpdate::SetBooleanWithoutPathExpansion( + base::StringPiece path, + bool in_value) { + SetWithoutPathExpansion(path, base::MakeUnique<base::Value>(in_value)); +} + +void DictionaryValueUpdate::SetIntegerWithoutPathExpansion( + base::StringPiece path, + int in_value) { + SetWithoutPathExpansion(path, base::MakeUnique<base::Value>(in_value)); +} + +void DictionaryValueUpdate::SetDoubleWithoutPathExpansion( + base::StringPiece path, + double in_value) { + SetWithoutPathExpansion(path, base::MakeUnique<base::Value>(in_value)); +} + +void DictionaryValueUpdate::SetStringWithoutPathExpansion( + base::StringPiece path, + base::StringPiece in_value) { + SetWithoutPathExpansion(path, base::MakeUnique<base::Value>(in_value)); +} + +void DictionaryValueUpdate::SetStringWithoutPathExpansion( + base::StringPiece path, + const base::string16& in_value) { + SetWithoutPathExpansion(path, base::MakeUnique<base::Value>(in_value)); +} + +std::unique_ptr<DictionaryValueUpdate> +DictionaryValueUpdate::SetDictionaryWithoutPathExpansion( + base::StringPiece path, + std::unique_ptr<base::DictionaryValue> in_value) { + RecordKey(path); + base::DictionaryValue* dictionary_value = in_value.get(); + value_->SetWithoutPathExpansion(path, std::move(in_value)); + + std::vector<std::string> full_path = path_; + full_path.push_back(path.as_string()); + return base::MakeUnique<DictionaryValueUpdate>( + report_update_, dictionary_value, std::move(full_path)); +} + +bool DictionaryValueUpdate::GetBoolean(base::StringPiece path, + bool* out_value) const { + return value_->GetBoolean(path, out_value); +} + +bool DictionaryValueUpdate::GetInteger(base::StringPiece path, + int* out_value) const { + return value_->GetInteger(path, out_value); +} + +bool DictionaryValueUpdate::GetDouble(base::StringPiece path, + double* out_value) const { + return value_->GetDouble(path, out_value); +} + +bool DictionaryValueUpdate::GetString(base::StringPiece path, + std::string* out_value) const { + return value_->GetString(path, out_value); +} + +bool DictionaryValueUpdate::GetString(base::StringPiece path, + base::string16* out_value) const { + return value_->GetString(path, out_value); +} + +bool DictionaryValueUpdate::GetDictionary( + base::StringPiece path, + const base::DictionaryValue** out_value) const { + return AsConstDictionary()->GetDictionary(path, out_value); +} + +bool DictionaryValueUpdate::GetDictionary( + base::StringPiece path, + std::unique_ptr<DictionaryValueUpdate>* out_value) { + base::DictionaryValue* dictionary_value = nullptr; + if (!value_->GetDictionary(path, &dictionary_value)) + return false; + + *out_value = base::MakeUnique<DictionaryValueUpdate>( + report_update_, dictionary_value, ConcatPath(path_, path)); + return true; +} + +bool DictionaryValueUpdate::GetList(base::StringPiece path, + const base::ListValue** out_value) const { + return AsConstDictionary()->GetList(path, out_value); +} + +bool DictionaryValueUpdate::GetList(base::StringPiece path, + base::ListValue** out_value) { + RecordPath(path); + return value_->GetList(path, out_value); +} + +bool DictionaryValueUpdate::GetBooleanWithoutPathExpansion( + base::StringPiece key, + bool* out_value) const { + return value_->GetBooleanWithoutPathExpansion(key, out_value); +} + +bool DictionaryValueUpdate::GetIntegerWithoutPathExpansion( + base::StringPiece key, + int* out_value) const { + return value_->GetIntegerWithoutPathExpansion(key, out_value); +} + +bool DictionaryValueUpdate::GetDoubleWithoutPathExpansion( + base::StringPiece key, + double* out_value) const { + return value_->GetDoubleWithoutPathExpansion(key, out_value); +} + +bool DictionaryValueUpdate::GetStringWithoutPathExpansion( + base::StringPiece key, + std::string* out_value) const { + return value_->GetStringWithoutPathExpansion(key, out_value); +} + +bool DictionaryValueUpdate::GetStringWithoutPathExpansion( + base::StringPiece key, + base::string16* out_value) const { + return value_->GetStringWithoutPathExpansion(key, out_value); +} + +bool DictionaryValueUpdate::GetDictionaryWithoutPathExpansion( + base::StringPiece key, + const base::DictionaryValue** out_value) const { + return value_->GetDictionaryWithoutPathExpansion(key, out_value); +} + +bool DictionaryValueUpdate::GetDictionaryWithoutPathExpansion( + base::StringPiece key, + std::unique_ptr<DictionaryValueUpdate>* out_value) { + base::DictionaryValue* dictionary_value = nullptr; + if (!value_->GetDictionaryWithoutPathExpansion(key, &dictionary_value)) + return false; + + std::vector<std::string> full_path = path_; + full_path.push_back(key.as_string()); + *out_value = base::MakeUnique<DictionaryValueUpdate>( + report_update_, dictionary_value, std::move(full_path)); + return true; +} + +bool DictionaryValueUpdate::GetListWithoutPathExpansion( + base::StringPiece key, + const base::ListValue** out_value) const { + return value_->GetListWithoutPathExpansion(key, out_value); +} + +bool DictionaryValueUpdate::GetListWithoutPathExpansion( + base::StringPiece key, + base::ListValue** out_value) { + RecordKey(key); + return value_->GetListWithoutPathExpansion(key, out_value); +} + +bool DictionaryValueUpdate::Remove(base::StringPiece path, + std::unique_ptr<base::Value>* out_value) { + if (!value_->Remove(path, out_value)) + return false; + + RecordPath(path); + return true; +} + +bool DictionaryValueUpdate::RemoveWithoutPathExpansion( + base::StringPiece key, + std::unique_ptr<base::Value>* out_value) { + if (!value_->RemoveWithoutPathExpansion(key, out_value)) + return false; + + RecordKey(key); + return true; +} + +bool DictionaryValueUpdate::RemovePath( + base::StringPiece path, + std::unique_ptr<base::Value>* out_value) { + if (!value_->RemovePath(path, out_value)) + return false; + + std::vector<base::StringPiece> split_path = SplitPath(path); + base::DictionaryValue* dict = value_; + for (size_t i = 0; i < split_path.size() - 1; ++i) { + if (!dict->GetDictionary(split_path[i], &dict)) { + split_path.resize(i + 1); + break; + } + } + RecordSplitPath(split_path); + return true; +} + +base::DictionaryValue* DictionaryValueUpdate::AsDictionary() { + RecordSplitPath(std::vector<base::StringPiece>()); + return value_; +} + +const base::DictionaryValue* DictionaryValueUpdate::AsConstDictionary() const { + return value_; +} + +void DictionaryValueUpdate::RecordKey(base::StringPiece key) { + RecordSplitPath({key}); +} + +void DictionaryValueUpdate::RecordPath(base::StringPiece path) { + RecordSplitPath(SplitPath(path)); +} + +void DictionaryValueUpdate::RecordSplitPath( + const std::vector<base::StringPiece>& path) { + report_update_.Run(ConcatPath(path_, path)); +} + +std::vector<base::StringPiece> DictionaryValueUpdate::SplitPath( + base::StringPiece path) { + return base::SplitStringPiece(path, ".", base::TRIM_WHITESPACE, + base::SPLIT_WANT_NONEMPTY); +} + +std::vector<std::string> DictionaryValueUpdate::ConcatPath( + const std::vector<std::string>& base_path, + base::StringPiece path) { + return ConcatPath(base_path, SplitPath(path)); +} + +std::vector<std::string> DictionaryValueUpdate::ConcatPath( + const std::vector<std::string>& base_path, + const std::vector<base::StringPiece>& path) { + std::vector<std::string> full_path = base_path; + full_path.reserve(full_path.size() + path.size()); + std::transform(path.begin(), path.end(), std::back_inserter(full_path), + [](base::StringPiece s) { return s.as_string(); }); + return full_path; +} + +} // namespace prefs
diff --git a/services/preferences/public/cpp/dictionary_value_update.h b/services/preferences/public/cpp/dictionary_value_update.h new file mode 100644 index 0000000..fb3dc4d --- /dev/null +++ b/services/preferences/public/cpp/dictionary_value_update.h
@@ -0,0 +1,167 @@ +// 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 SERVICES_PREFERENCES_PUBLIC_CPP_DICTIONARY_VALUE_UPDATE_H_ +#define SERVICES_PREFERENCES_PUBLIC_CPP_DICTIONARY_VALUE_UPDATE_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/strings/string16.h" +#include "base/strings/string_piece.h" +#include "services/preferences/public/cpp/scoped_pref_update.h" + +namespace base { +class DictionaryValue; +class ListValue; +class Value; +} + +namespace prefs { + +// A wrapper around base::DictionaryValue that reports changes to its contents +// via a callback. +class DictionaryValueUpdate { + public: + using UpdateCallback = base::Callback<void(const std::vector<std::string>&)>; + + DictionaryValueUpdate(UpdateCallback report_update, + base::DictionaryValue* value, + std::vector<std::string> path); + + ~DictionaryValueUpdate(); + bool HasKey(base::StringPiece key) const; + + // Returns the number of Values in this dictionary. + size_t size() const; + + // Returns whether the dictionary is empty. + bool empty() const; + + // Clears any current contents of this dictionary. + void Clear(); + + // Sets the Value associated with the given path starting from this object. + // A path has the form "<key>" or "<key>.<key>.[...]", where "." indexes + // into the next DictionaryValue down. Obviously, "." can't be used + // within a key, but there are no other restrictions on keys. + // If the key at any step of the way doesn't exist, or exists but isn't + // a DictionaryValue, a new DictionaryValue will be created and attached + // to the path in that location. |in_value| must be non-null. + void Set(base::StringPiece path, std::unique_ptr<base::Value> in_value); + + // Convenience forms of Set(). These methods will replace any existing + // value at that path, even if it has a different type. + void SetBoolean(base::StringPiece path, bool in_value); + void SetInteger(base::StringPiece path, int in_value); + void SetDouble(base::StringPiece path, double in_value); + void SetString(base::StringPiece path, base::StringPiece in_value); + void SetString(base::StringPiece path, const base::string16& in_value); + std::unique_ptr<DictionaryValueUpdate> SetDictionary( + base::StringPiece path, + std::unique_ptr<base::DictionaryValue> in_value); + + // Like Set(), but without special treatment of '.'. This allows e.g. URLs to + // be used as paths. + void SetWithoutPathExpansion(base::StringPiece key, + std::unique_ptr<base::Value> in_value); + + // Convenience forms of SetWithoutPathExpansion(). + void SetBooleanWithoutPathExpansion(base::StringPiece path, bool in_value); + void SetIntegerWithoutPathExpansion(base::StringPiece path, int in_value); + void SetDoubleWithoutPathExpansion(base::StringPiece path, double in_value); + void SetStringWithoutPathExpansion(base::StringPiece path, + base::StringPiece in_value); + void SetStringWithoutPathExpansion(base::StringPiece path, + const base::string16& in_value); + std::unique_ptr<DictionaryValueUpdate> SetDictionaryWithoutPathExpansion( + base::StringPiece path, + std::unique_ptr<base::DictionaryValue> in_value); + + // These are convenience forms of Get(). The value will be retrieved + // and the return value will be true if the path is valid and the value at + // the end of the path can be returned in the form specified. + // |out_value| is optional and will only be set if non-NULL. + bool GetBoolean(base::StringPiece path, bool* out_value) const; + bool GetInteger(base::StringPiece path, int* out_value) const; + // Values of both type Type::INTEGER and Type::DOUBLE can be obtained as + // doubles. + bool GetDouble(base::StringPiece path, double* out_value) const; + bool GetString(base::StringPiece path, std::string* out_value) const; + bool GetString(base::StringPiece path, base::string16* out_value) const; + bool GetDictionary(base::StringPiece path, + const base::DictionaryValue** out_value) const; + bool GetDictionary(base::StringPiece path, + std::unique_ptr<DictionaryValueUpdate>* out_value); + bool GetList(base::StringPiece path, const base::ListValue** out_value) const; + bool GetList(base::StringPiece path, base::ListValue** out_value); + + // Like Get(), but without special treatment of '.'. This allows e.g. URLs to + // be used as paths. + bool GetBooleanWithoutPathExpansion(base::StringPiece key, + bool* out_value) const; + bool GetIntegerWithoutPathExpansion(base::StringPiece key, + int* out_value) const; + bool GetDoubleWithoutPathExpansion(base::StringPiece key, + double* out_value) const; + bool GetStringWithoutPathExpansion(base::StringPiece key, + std::string* out_value) const; + bool GetStringWithoutPathExpansion(base::StringPiece key, + base::string16* out_value) const; + bool GetDictionaryWithoutPathExpansion( + base::StringPiece key, + const base::DictionaryValue** out_value) const; + bool GetDictionaryWithoutPathExpansion( + base::StringPiece key, + std::unique_ptr<DictionaryValueUpdate>* out_value); + bool GetListWithoutPathExpansion(base::StringPiece key, + const base::ListValue** out_value) const; + bool GetListWithoutPathExpansion(base::StringPiece key, + base::ListValue** out_value); + + // Removes the Value with the specified path from this dictionary (or one + // of its child dictionaries, if the path is more than just a local key). + // If |out_value| is non-NULL, the removed Value will be passed out via + // |out_value|. If |out_value| is NULL, the removed value will be deleted. + // This method returns true if |path| is a valid path; otherwise it will + // return false and the DictionaryValue object will be unchanged. + bool Remove(base::StringPiece path, std::unique_ptr<base::Value>* out_value); + + // Like Remove(), but without special treatment of '.'. This allows e.g. URLs + // to be used as paths. + bool RemoveWithoutPathExpansion(base::StringPiece key, + std::unique_ptr<base::Value>* out_value); + + // Removes a path, clearing out all dictionaries on |path| that remain empty + // after removing the value at |path|. + bool RemovePath(base::StringPiece path, + std::unique_ptr<base::Value>* out_value); + + base::DictionaryValue* AsDictionary(); + const base::DictionaryValue* AsConstDictionary() const; + + private: + void RecordPath(base::StringPiece path); + void RecordSplitPath(const std::vector<base::StringPiece>& path); + void RecordKey(base::StringPiece key); + + std::vector<base::StringPiece> SplitPath(base::StringPiece path); + std::vector<std::string> ConcatPath(const std::vector<std::string>& base_path, + base::StringPiece path); + std::vector<std::string> ConcatPath( + const std::vector<std::string>& base_path, + const std::vector<base::StringPiece>& path); + + UpdateCallback report_update_; + base::DictionaryValue* const value_; + const std::vector<std::string> path_; + + DISALLOW_COPY_AND_ASSIGN(DictionaryValueUpdate); +}; + +} // namespace prefs + +#endif // SERVICES_PREFERENCES_PUBLIC_CPP_DICTIONARY_VALUE_UPDATE_H_
diff --git a/services/preferences/public/cpp/lib/BUILD.gn b/services/preferences/public/cpp/lib/BUILD.gn new file mode 100644 index 0000000..a66e2b8 --- /dev/null +++ b/services/preferences/public/cpp/lib/BUILD.gn
@@ -0,0 +1,16 @@ +# 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. + +source_set("lib") { + visibility = [ "//services/preferences/*" ] + + sources = [ + "util.cc", + "util.h", + ] + + deps = [ + "//base", + ] +}
diff --git a/services/preferences/public/cpp/lib/util.cc b/services/preferences/public/cpp/lib/util.cc new file mode 100644 index 0000000..dcd9f32 --- /dev/null +++ b/services/preferences/public/cpp/lib/util.cc
@@ -0,0 +1,34 @@ +// 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 "services/preferences/public/cpp/lib/util.h" + +#include <utility> + +#include "base/memory/ptr_util.h" +#include "base/values.h" + +namespace prefs { + +void SetValue(base::DictionaryValue* dictionary_value, + const std::vector<base::StringPiece>& path_components, + std::unique_ptr<base::Value> value) { + for (size_t i = 0; i < path_components.size() - 1; ++i) { + if (!dictionary_value->GetDictionaryWithoutPathExpansion( + path_components[i], &dictionary_value)) { + auto new_dict_value_owner = base::MakeUnique<base::DictionaryValue>(); + auto* new_dict_value = new_dict_value_owner.get(); + dictionary_value->SetWithoutPathExpansion( + path_components[i], std::move(new_dict_value_owner)); + dictionary_value = new_dict_value; + } + } + const auto& key = path_components.back(); + if (value) + dictionary_value->SetWithoutPathExpansion(key, std::move(value)); + else + dictionary_value->RemoveWithoutPathExpansion(key, nullptr); +} + +} // namespace prefs
diff --git a/services/preferences/public/cpp/lib/util.h b/services/preferences/public/cpp/lib/util.h new file mode 100644 index 0000000..3e3c88a --- /dev/null +++ b/services/preferences/public/cpp/lib/util.h
@@ -0,0 +1,28 @@ +// 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 SERVICES_PREFERENCES_PUBLIC_CPP_LIB_UTIL_H_ +#define SERVICES_PREFERENCES_PUBLIC_CPP_LIB_UTIL_H_ + +#include <memory> +#include <vector> + +#include "base/strings/string_piece.h" + +namespace base { +class DictionaryValue; +class Value; +} + +namespace prefs { + +// Sets a nested value inside the dictionary, specified by |path_components|. +// Creates nested dictionaries as needed. +void SetValue(base::DictionaryValue* dictionary_value, + const std::vector<base::StringPiece>& path_components, + std::unique_ptr<base::Value> value); + +} // namespace prefs + +#endif // SERVICES_PREFERENCES_PUBLIC_CPP_LIB_UTIL_H_
diff --git a/services/preferences/public/cpp/persistent_pref_store_client.cc b/services/preferences/public/cpp/persistent_pref_store_client.cc index 9bcb92f..baf9aa1a 100644 --- a/services/preferences/public/cpp/persistent_pref_store_client.cc +++ b/services/preferences/public/cpp/persistent_pref_store_client.cc
@@ -4,14 +4,60 @@ #include "services/preferences/public/cpp/persistent_pref_store_client.h" -#include <utility> - #include "base/values.h" #include "components/prefs/pref_registry.h" +#include "mojo/common/values.mojom.h" +#include "mojo/common/values_struct_traits.h" #include "mojo/public/cpp/bindings/sync_call_restrictions.h" #include "services/preferences/public/cpp/pref_registry_serializer.h" namespace prefs { +namespace { + +const base::Value* LookupPath(const base::Value* root, + const std::vector<std::string>& path_components) { + const base::DictionaryValue* dictionary_value = nullptr; + bool success = root->GetAsDictionary(&dictionary_value); + DCHECK(success); + + for (size_t i = 0; i < path_components.size() - 1; ++i) { + if (!dictionary_value->GetDictionaryWithoutPathExpansion( + path_components[i], &dictionary_value)) { + return nullptr; + } + } + const base::Value* result = nullptr; + dictionary_value->GetWithoutPathExpansion(path_components.back(), &result); + return result; +} + +template <typename StringType> +bool IsPrefix(const std::vector<StringType>& prefix, + const std::vector<StringType>& full_path) { + if (prefix.size() >= full_path.size()) + return false; + + for (size_t i = 0; i < prefix.size(); i++) { + if (prefix[i] != full_path[i]) + return false; + } + return true; +} + +// Removes paths that where a prefix is also contained in |updated_paths|. +void RemoveRedundantPaths(std::set<std::vector<std::string>>* updated_paths) { + for (auto it = updated_paths->begin(), previous_it = updated_paths->end(); + it != updated_paths->end();) { + if (previous_it != updated_paths->end() && IsPrefix(*previous_it, *it)) { + it = updated_paths->erase(it); + } else { + previous_it = it; + ++it; + } + } +} + +} // namespace PersistentPrefStoreClient::PersistentPrefStoreClient( mojom::PrefStoreConnectorPtr connector, @@ -57,10 +103,17 @@ void PersistentPrefStoreClient::ReportValueChanged(const std::string& key, uint32_t flags) { DCHECK(pref_store_); - const base::Value* local_value = nullptr; - GetMutableValues().Get(key, &local_value); - QueueWrite(key, flags); + ReportSubValuesChanged( + key, std::set<std::vector<std::string>>{std::vector<std::string>{}}, + flags); +} + +void PersistentPrefStoreClient::ReportSubValuesChanged( + const std::string& key, + std::set<std::vector<std::string>> path_components, + uint32_t flags) { + QueueWrite(key, std::move(path_components), flags); ReportPrefValueChanged(key); } @@ -69,8 +122,10 @@ std::unique_ptr<base::Value> value, uint32_t flags) { DCHECK(pref_store_); - QueueWrite(key, flags); GetMutableValues().Set(key, std::move(value)); + QueueWrite(key, + std::set<std::vector<std::string>>{std::vector<std::string>{}}, + flags); } bool PersistentPrefStoreClient::ReadOnly() const { @@ -88,11 +143,10 @@ prefs::mojom::PrefStoreConnectionPtr> other_pref_stores; mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_calls; - if (!connector_->Connect(SerializePrefRegistry(*pref_registry_), - already_connected_types_, &connection, - &other_pref_stores)) { - NOTREACHED(); - } + bool success = connector_->Connect(SerializePrefRegistry(*pref_registry_), + already_connected_types_, &connection, + &other_pref_stores); + DCHECK(success); pref_registry_ = nullptr; OnConnect(std::move(connection), std::move(other_pref_stores)); return read_error_; @@ -153,29 +207,58 @@ } } -void PersistentPrefStoreClient::QueueWrite(const std::string& key, - uint32_t flags) { +void PersistentPrefStoreClient::QueueWrite( + const std::string& key, + std::set<std::vector<std::string>> path_components, + uint32_t flags) { + DCHECK(!path_components.empty()); if (pending_writes_.empty()) { // Use a weak pointer since a pending write should not prolong the life of - // |this|. Instead, the destruction of |this| will flush any pending writes. + // |this|. Instead, the destruction of |this| will flush any pending + // writes. base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::Bind(&PersistentPrefStoreClient::FlushPendingWrites, weak_factory_.GetWeakPtr())); } - pending_writes_.insert(std::make_pair(key, flags)); + RemoveRedundantPaths(&path_components); + auto& entry = pending_writes_[key]; + entry.second = flags; + for (auto& path : path_components) { + entry.first.insert(std::move(path)); + } } void PersistentPrefStoreClient::FlushPendingWrites() { std::vector<mojom::PrefUpdatePtr> updates; - for (const auto& pref : pending_writes_) { + for (auto& pref : pending_writes_) { + auto update_value = mojom::PrefUpdateValue::New(); const base::Value* value = nullptr; if (GetValue(pref.first, &value)) { - updates.push_back(mojom::PrefUpdate::New( - pref.first, value->CreateDeepCopy(), pref.second)); + std::vector<mojom::SubPrefUpdatePtr> pref_updates; + RemoveRedundantPaths(&pref.second.first); + for (const auto& path : pref.second.first) { + if (path.empty()) { + pref_updates.clear(); + break; + } + const base::Value* nested_value = LookupPath(value, path); + if (nested_value) { + pref_updates.emplace_back(base::in_place, path, + nested_value->CreateDeepCopy()); + } else { + pref_updates.emplace_back(base::in_place, path, nullptr); + } + } + if (pref_updates.empty()) { + update_value->set_atomic_update(value->CreateDeepCopy()); + } else { + update_value->set_split_updates(std::move(pref_updates)); + } } else { - updates.push_back( - mojom::PrefUpdate::New(pref.first, nullptr, pref.second)); + update_value->set_atomic_update(nullptr); } + updates.emplace_back(base::in_place, pref.first, std::move(update_value), + pref.second.second); } pref_store_->SetValues(std::move(updates)); pending_writes_.clear();
diff --git a/services/preferences/public/cpp/persistent_pref_store_client.h b/services/preferences/public/cpp/persistent_pref_store_client.h index e317ec9..67849f42 100644 --- a/services/preferences/public/cpp/persistent_pref_store_client.h +++ b/services/preferences/public/cpp/persistent_pref_store_client.h
@@ -5,8 +5,12 @@ #ifndef SERVICES_PREFERENCES_PUBLIC_CPP_PERSISTENT_PREF_STORE_CLIENT_H_ #define SERVICES_PREFERENCES_PUBLIC_CPP_PERSISTENT_PREF_STORE_CLIENT_H_ +#include <map> #include <memory> +#include <set> #include <string> +#include <unordered_map> +#include <utility> #include <vector> #include "base/macros.h" @@ -43,6 +47,10 @@ void RemoveValue(const std::string& key, uint32_t flags) override; bool GetMutableValue(const std::string& key, base::Value** result) override; void ReportValueChanged(const std::string& key, uint32_t flags) override; + void ReportSubValuesChanged( + const std::string& key, + std::set<std::vector<std::string>> path_components, + uint32_t flags) override; void SetValueSilently(const std::string& key, std::unique_ptr<base::Value> value, uint32_t flags) override; @@ -66,7 +74,9 @@ prefs::mojom::PrefStoreConnectionPtr> other_pref_stores); - void QueueWrite(const std::string& key, uint32_t flags); + void QueueWrite(const std::string& key, + std::set<std::vector<std::string>> path_components, + uint32_t flags); void FlushPendingWrites(); mojom::PrefStoreConnectorPtr connector_; @@ -74,7 +84,8 @@ bool read_only_ = false; PrefReadError read_error_ = PersistentPrefStore::PREF_READ_ERROR_NONE; mojom::PersistentPrefStorePtr pref_store_; - std::map<std::string, uint32_t> pending_writes_; + std::map<std::string, std::pair<std::set<std::vector<std::string>>, uint32_t>> + pending_writes_; std::unique_ptr<ReadErrorDelegate> error_delegate_; std::vector<PrefValueStore::PrefStoreType> already_connected_types_;
diff --git a/services/preferences/public/cpp/pref_store_client_mixin.cc b/services/preferences/public/cpp/pref_store_client_mixin.cc index 0387883..f78cf76 100644 --- a/services/preferences/public/cpp/pref_store_client_mixin.cc +++ b/services/preferences/public/cpp/pref_store_client_mixin.cc
@@ -6,7 +6,9 @@ #include <utility> +#include "base/strings/string_split.h" #include "base/values.h" +#include "services/preferences/public/cpp/lib/util.h" #include "services/preferences/public/cpp/pref_store_client.h" namespace prefs { @@ -101,22 +103,40 @@ template <typename BasePrefStore> void PrefStoreClientMixin<BasePrefStore>::OnPrefChanged( const std::string& key, - std::unique_ptr<base::Value> value) { + mojom::PrefUpdateValuePtr update_value) { DCHECK(cached_prefs_); bool changed = false; - if (!value) { // Delete - if (cached_prefs_->RemovePath(key, nullptr)) - changed = true; - } else { - const base::Value* prev; - if (cached_prefs_->Get(key, &prev)) { - if (!prev->Equals(value.get())) { + if (update_value->is_atomic_update()) { + auto& value = update_value->get_atomic_update(); + if (!value) { // Delete + if (cached_prefs_->RemovePath(key, nullptr)) + changed = true; + } else { + const base::Value* prev; + if (cached_prefs_->Get(key, &prev)) { + if (!prev->Equals(value.get())) { + cached_prefs_->Set(key, std::move(value)); + changed = true; + } + } else { cached_prefs_->Set(key, std::move(value)); changed = true; } - } else { - cached_prefs_->Set(key, std::move(value)); + } + } else if (update_value->is_split_updates()) { + auto& updates = update_value->get_split_updates(); + if (!updates.empty()) changed = true; + for (auto& update : updates) { + // Clients shouldn't send empty paths. + if (update->path.empty()) + continue; + + std::vector<base::StringPiece> full_path = base::SplitStringPiece( + key, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + full_path.insert(full_path.end(), update->path.begin(), + update->path.end()); + prefs::SetValue(cached_prefs_.get(), full_path, std::move(update->value)); } } if (changed && initialized_)
diff --git a/services/preferences/public/cpp/pref_store_client_mixin.h b/services/preferences/public/cpp/pref_store_client_mixin.h index 19cf250..68286ec4 100644 --- a/services/preferences/public/cpp/pref_store_client_mixin.h +++ b/services/preferences/public/cpp/pref_store_client_mixin.h
@@ -7,6 +7,7 @@ #include <memory> #include <string> +#include <vector> #include "base/macros.h" #include "base/observer_list.h" @@ -58,7 +59,7 @@ void OnInitializationCompleted(bool succeeded) override; void OnPrefChanged(const std::string& key, - std::unique_ptr<base::Value> value); + mojom::PrefUpdateValuePtr update_value); // Cached preferences. // If null, indicates that initialization failed.
diff --git a/services/preferences/public/cpp/pref_store_impl.cc b/services/preferences/public/cpp/pref_store_impl.cc index c228962..914d1dd 100644 --- a/services/preferences/public/cpp/pref_store_impl.cc +++ b/services/preferences/public/cpp/pref_store_impl.cc
@@ -6,6 +6,7 @@ #include <memory> #include <unordered_set> +#include <utility> #include "base/stl_util.h" #include "base/values.h" @@ -27,7 +28,9 @@ return; std::vector<mojom::PrefUpdatePtr> updates; - updates.push_back(mojom::PrefUpdate::New(key, value.CreateDeepCopy(), 0)); + updates.push_back(mojom::PrefUpdate::New( + key, mojom::PrefUpdateValue::NewAtomicUpdate(value.CreateDeepCopy()), + 0)); observer_->OnPrefsChanged(std::move(updates)); } @@ -36,7 +39,8 @@ return; std::vector<mojom::PrefUpdatePtr> updates; - updates.push_back(mojom::PrefUpdate::New(key, nullptr, 0)); + updates.push_back(mojom::PrefUpdate::New( + key, mojom::PrefUpdateValue::NewAtomicUpdate(nullptr), 0)); observer_->OnPrefsChanged(std::move(updates)); }
diff --git a/services/preferences/public/cpp/pref_store_impl.h b/services/preferences/public/cpp/pref_store_impl.h index 62146a6..72227af 100644 --- a/services/preferences/public/cpp/pref_store_impl.h +++ b/services/preferences/public/cpp/pref_store_impl.h
@@ -5,6 +5,7 @@ #ifndef SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_IMPL_H_ #define SERVICES_PREFERENCES_PUBLIC_CPP_PREF_STORE_IMPL_H_ +#include <string> #include <vector> #include "base/macros.h"
diff --git a/services/preferences/public/cpp/scoped_pref_update.cc b/services/preferences/public/cpp/scoped_pref_update.cc new file mode 100644 index 0000000..f56424d --- /dev/null +++ b/services/preferences/public/cpp/scoped_pref_update.cc
@@ -0,0 +1,44 @@ +// 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 "services/preferences/public/cpp/scoped_pref_update.h" + +#include <utility> + +#include "base/bind.h" +#include "base/memory/ptr_util.h" +#include "components/prefs/pref_service.h" +#include "services/preferences/public/cpp/dictionary_value_update.h" + +namespace prefs { + +ScopedDictionaryPrefUpdate::ScopedDictionaryPrefUpdate(PrefService* service, + base::StringPiece path) + : service_(service), path_(path.as_string()) {} + +ScopedDictionaryPrefUpdate::~ScopedDictionaryPrefUpdate() { + if (!updated_paths_.empty()) + service_->ReportUserPrefChanged(path_, std::move(updated_paths_)); +} + +std::unique_ptr<DictionaryValueUpdate> ScopedDictionaryPrefUpdate::Get() { + return base::MakeUnique<DictionaryValueUpdate>( + base::Bind(&ScopedDictionaryPrefUpdate::RecordPath, + base::Unretained(this)), + static_cast<base::DictionaryValue*>( + service_->GetMutableUserPref(path_, base::Value::Type::DICTIONARY)), + std::vector<std::string>()); +} + +std::unique_ptr<DictionaryValueUpdate> ScopedDictionaryPrefUpdate:: +operator->() { + return Get(); +} + +void ScopedDictionaryPrefUpdate::RecordPath( + const std::vector<std::string>& path) { + updated_paths_.insert(std::move(path)); +} + +} // namespace prefs
diff --git a/services/preferences/public/cpp/scoped_pref_update.h b/services/preferences/public/cpp/scoped_pref_update.h new file mode 100644 index 0000000..e2475e0f --- /dev/null +++ b/services/preferences/public/cpp/scoped_pref_update.h
@@ -0,0 +1,72 @@ +// 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 SERVICES_PREFERENCES_PUBLIC_CPP_SCOPED_PREF_UPDATE_H_ +#define SERVICES_PREFERENCES_PUBLIC_CPP_SCOPED_PREF_UPDATE_H_ + +#include <memory> +#include <set> +#include <string> +#include <vector> + +#include "base/strings/string_piece.h" + +class PrefService; + +namespace prefs { + +class DictionaryValueUpdate; + +// An update to a dictionary value pref. +// +// For example: +// prefs::ScopedDictionaryPrefUpdate update(pref_service(), kPrefKey); +// int next_id = 0; +// update->GetInteger("next_id", &next_id); +// +// std::unique_ptr<prefs::DictionaryValueUpdate> nested_dictionary; +// if (!update->GetDictionaryWithoutPathExpansion(url.spec(), +// &nested_dictionary)) { +// nested_dictionary = update->SetDictionaryWithoutPathExpansion( +// url.spec(), base::MakeUnique<base::DictionaryValue>()); +// } +// +// nested_dictionary->Set("metadata", std::move(metadata)); +// nested_dictionary->SetInteger("id", next_id++); +// update->SetInteger("next_id", next_id); +// +class ScopedDictionaryPrefUpdate { + public: + ScopedDictionaryPrefUpdate(PrefService* service, base::StringPiece path); + + // Notifies if necessary. + virtual ~ScopedDictionaryPrefUpdate(); + + // The caller should not keep the returned object or any further objects + // obtained from it around for any longer than the lifetime of the + // ScopedDictionaryPrefUpdate. + virtual std::unique_ptr<DictionaryValueUpdate> Get(); + + // The caller should not keep the returned object or any further objects + // obtained from it around for any longer than the lifetime of the + // ScopedDictionaryPrefUpdate. + std::unique_ptr<DictionaryValueUpdate> operator->(); + + private: + void RecordPath(const std::vector<std::string>& path); + + // Weak pointer. + PrefService* const service_; + // Path of the preference being updated. + const std::string path_; + + // The paths that have been modified. + std::set<std::vector<std::string>> updated_paths_; + + DISALLOW_COPY_AND_ASSIGN(ScopedDictionaryPrefUpdate); +}; + +} // namespace prefs + +#endif // SERVICES_PREFERENCES_PUBLIC_CPP_SCOPED_PREF_UPDATE_H_
diff --git a/services/preferences/public/cpp/tests/BUILD.gn b/services/preferences/public/cpp/tests/BUILD.gn index bbe7e02..3f0a9c2 100644 --- a/services/preferences/public/cpp/tests/BUILD.gn +++ b/services/preferences/public/cpp/tests/BUILD.gn
@@ -5,6 +5,7 @@ source_set("tests") { testonly = true sources = [ + "persistent_pref_store_client_unittest.cc", "pref_store_client_unittest.cc", "pref_store_impl_unittest.cc", ]
diff --git a/services/preferences/public/cpp/tests/persistent_pref_store_client_unittest.cc b/services/preferences/public/cpp/tests/persistent_pref_store_client_unittest.cc new file mode 100644 index 0000000..a8aaf46 --- /dev/null +++ b/services/preferences/public/cpp/tests/persistent_pref_store_client_unittest.cc
@@ -0,0 +1,502 @@ +// 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 "services/preferences/public/cpp/persistent_pref_store_client.h" + +#include <utility> + +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/values.h" +#include "components/prefs/pref_notifier_impl.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "services/preferences/public/cpp/dictionary_value_update.h" +#include "services/preferences/public/cpp/scoped_pref_update.h" +#include "services/preferences/public/interfaces/preferences.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace prefs { +namespace { + +constexpr char kDictionaryKey[] = "path.to.key"; +constexpr char kUninitializedDictionaryKey[] = "path.to.an.uninitialized.dict"; + +void DoNothingWithReadError(::PersistentPrefStore::PrefReadError read_error) {} + +class PersistentPrefStoreClientTest : public testing::Test, + public mojom::PersistentPrefStore { + public: + PersistentPrefStoreClientTest() : binding_(this) {} + + // testing::Test: + void SetUp() override { + auto persistent_pref_store_client = make_scoped_refptr( + new PersistentPrefStoreClient(mojom::PersistentPrefStoreConnection::New( + mojom::PrefStoreConnection::New( + mojom::PrefStoreObserverRequest(), + base::MakeUnique<base::DictionaryValue>(), true), + binding_.CreateInterfacePtrAndBind(), + ::PersistentPrefStore::PREF_READ_ERROR_NONE, false))); + auto pref_registry = make_scoped_refptr(new PrefRegistrySimple()); + pref_registry->RegisterDictionaryPref(kDictionaryKey); + pref_registry->RegisterDictionaryPref(kUninitializedDictionaryKey); + PrefNotifierImpl* pref_notifier = new PrefNotifierImpl; + pref_service_ = base::MakeUnique<PrefService>( + pref_notifier, + new PrefValueStore(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, pref_notifier), + persistent_pref_store_client.get(), pref_registry.get(), + base::Bind(&DoNothingWithReadError), false); + // The first update to a pref will write the entire dictionary as it would + // previously be missing. Do this here to avoid individual tests needing to + // deal with those updates. + ScopedDictionaryPrefUpdate(pref_service(), kDictionaryKey).Get(); + auto update = WaitForUpdate(); + } + + void TearDown() override { + pref_service_ = nullptr; + base::RunLoop().RunUntilIdle(); + binding_.Close(); + base::RunLoop().RunUntilIdle(); + } + + PrefService* pref_service() { return pref_service_.get(); } + + mojom::PrefUpdateValuePtr WaitForUpdate() { + base::RunLoop run_loop; + on_update_ = run_loop.QuitClosure(); + run_loop.Run(); + EXPECT_EQ(1u, last_updates_.size()); + auto result = std::move(last_updates_[0]->value); + last_updates_.clear(); + return result; + } + + void ExpectNoUpdate() { + binding_.FlushForTesting(); + EXPECT_TRUE(last_updates_.empty()); + } + + private: + void SetValues(std::vector<mojom::PrefUpdatePtr> updates) override { + last_updates_ = std::move(updates); + if (on_update_) + std::move(on_update_).Run(); + } + + void CommitPendingWrite() override {} + void SchedulePendingLossyWrites() override {} + void ClearMutableValues() override {} + + base::MessageLoop message_loop_; + + std::unique_ptr<PrefService> pref_service_; + + mojo::Binding<mojom::PersistentPrefStore> binding_; + + std::vector<mojom::PrefUpdatePtr> last_updates_; + base::OnceClosure on_update_; + + DISALLOW_COPY_AND_ASSIGN(PersistentPrefStoreClientTest); +}; + +TEST_F(PersistentPrefStoreClientTest, SubPrefUpdates_Basic) { + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->SetInteger("path.to.integer", 1); + } + auto update = WaitForUpdate(); + ASSERT_TRUE(update->is_split_updates()); + auto& split_updates = update->get_split_updates(); + ASSERT_EQ(1u, split_updates.size()); + EXPECT_EQ(base::Value(1), *split_updates[0]->value); + EXPECT_EQ((std::vector<std::string>{"path", "to", "integer"}), + split_updates[0]->path); +} + +TEST_F(PersistentPrefStoreClientTest, + SubPrefUpdates_BasicWithoutPathExpansion) { + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->SetIntegerWithoutPathExpansion("key.for.integer", 1); + } + auto update = WaitForUpdate(); + ASSERT_TRUE(update->is_split_updates()); + auto& split_updates = update->get_split_updates(); + EXPECT_EQ(1u, split_updates.size()); + EXPECT_EQ(base::Value(1), *split_updates[0]->value); + EXPECT_EQ((std::vector<std::string>{"key.for.integer"}), + split_updates[0]->path); +} + +TEST_F(PersistentPrefStoreClientTest, SubPrefUpdates_Remove) { + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->SetInteger("path.to.another_integer", 1); + update->SetInteger("path.to.integer", 1); + } + WaitForUpdate(); + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->Remove("path.to.integer", nullptr); + } + auto update = WaitForUpdate(); + ASSERT_TRUE(update->is_split_updates()); + auto& split_updates = update->get_split_updates(); + ASSERT_EQ(1u, split_updates.size()); + EXPECT_FALSE(split_updates[0]->value); + EXPECT_EQ((std::vector<std::string>{"path", "to", "integer"}), + split_updates[0]->path); +} + +TEST_F(PersistentPrefStoreClientTest, + SubPrefUpdates_RemoveWithoutPathExpansion) { + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->SetIntegerWithoutPathExpansion("path.to.another_integer", 1); + update->SetIntegerWithoutPathExpansion("path.to.integer", 1); + } + WaitForUpdate(); + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->RemoveWithoutPathExpansion("path.to.integer", nullptr); + } + auto update = WaitForUpdate(); + ASSERT_TRUE(update->is_split_updates()); + auto& split_updates = update->get_split_updates(); + ASSERT_EQ(1u, split_updates.size()); + EXPECT_FALSE(split_updates[0]->value); + EXPECT_EQ((std::vector<std::string>{"path.to.integer"}), + split_updates[0]->path); +} + +TEST_F(PersistentPrefStoreClientTest, SubPrefUpdates_MultipleUpdates) { + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->SetDoubleWithoutPathExpansion("a.double", 1); + } + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->SetInteger("path.to.integer", 2); + } + auto update = WaitForUpdate(); + ASSERT_TRUE(update->is_split_updates()); + auto& split_updates = update->get_split_updates(); + ASSERT_EQ(2u, split_updates.size()); + EXPECT_EQ(base::Value(1.0), *split_updates[0]->value); + EXPECT_EQ((std::vector<std::string>{"a.double"}), split_updates[0]->path); + EXPECT_EQ(base::Value(2), *split_updates[1]->value); + EXPECT_EQ((std::vector<std::string>{"path", "to", "integer"}), + split_updates[1]->path); +} + +TEST_F(PersistentPrefStoreClientTest, SubPrefUpdates_NestedUpdateAfterSet) { + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->SetInteger("path.to.integer", 1); + } + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + std::unique_ptr<DictionaryValueUpdate> dict; + ASSERT_TRUE(update->GetDictionary("path.to", &dict)); + dict->Clear(); + } + auto update = WaitForUpdate(); + ASSERT_TRUE(update->is_split_updates()); + auto& split_updates = update->get_split_updates(); + ASSERT_EQ(1u, split_updates.size()); + EXPECT_EQ(base::DictionaryValue(), *split_updates[0]->value); + EXPECT_EQ((std::vector<std::string>{"path", "to"}), split_updates[0]->path); +} + +TEST_F(PersistentPrefStoreClientTest, SubPrefUpdates_NestedUpdateBeforeSet) { + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->Set("path.to", base::MakeUnique<base::DictionaryValue>()); + } + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->SetInteger("path.to.integer", 1); + } + auto update = WaitForUpdate(); + ASSERT_TRUE(update->is_split_updates()); + auto& split_updates = update->get_split_updates(); + ASSERT_EQ(1u, split_updates.size()); + base::DictionaryValue expected_value; + expected_value.SetInteger("integer", 1); + EXPECT_EQ(expected_value, *split_updates[0]->value); + EXPECT_EQ((std::vector<std::string>{"path", "to"}), split_updates[0]->path); +} + +TEST_F(PersistentPrefStoreClientTest, SubPrefUpdates_DoubleNestedUpdate) { + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->SetInteger("path.to.integer", 1); + } + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + std::unique_ptr<DictionaryValueUpdate> dict; + ASSERT_TRUE(update->GetDictionary("path", &dict)); + dict->Clear(); + } + auto update = WaitForUpdate(); + ASSERT_TRUE(update->is_split_updates()); + auto& split_updates = update->get_split_updates(); + ASSERT_EQ(1u, split_updates.size()); + EXPECT_EQ(base::DictionaryValue(), *split_updates[0]->value); + EXPECT_EQ((std::vector<std::string>{"path"}), split_updates[0]->path); +} + +TEST_F(PersistentPrefStoreClientTest, SubPrefUpdates_ManualNesting) { + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->SetInteger("path.to.integer", 1); + } + WaitForUpdate(); + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + std::unique_ptr<DictionaryValueUpdate> dict; + ASSERT_TRUE(update->GetDictionary("path.to", &dict)); + dict->SetString("string", "string value"); + } + auto update = WaitForUpdate(); + ASSERT_TRUE(update->is_split_updates()); + auto& split_updates = update->get_split_updates(); + ASSERT_EQ(1u, split_updates.size()); + EXPECT_EQ(base::Value("string value"), *split_updates[0]->value); + EXPECT_EQ((std::vector<std::string>{"path", "to", "string"}), + split_updates[0]->path); +} + +TEST_F(PersistentPrefStoreClientTest, + SubPrefUpdates_ManualDictCreationAndNesting) { + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + auto dict = update->SetDictionary( + "path.to", base::MakeUnique<base::DictionaryValue>()); + dict->SetString("string", "string value"); + } + auto update = WaitForUpdate(); + ASSERT_TRUE(update->is_split_updates()); + auto& split_updates = update->get_split_updates(); + ASSERT_EQ(1u, split_updates.size()); + base::DictionaryValue expected_value; + expected_value.SetString("string", "string value"); + EXPECT_EQ(expected_value, *split_updates[0]->value); + EXPECT_EQ((std::vector<std::string>{"path", "to"}), split_updates[0]->path); +} + +TEST_F(PersistentPrefStoreClientTest, + SubPrefUpdates_ManualDictCreationWithoutPathExpansionAndNesting) { + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + auto dict = update->SetDictionaryWithoutPathExpansion( + "a.dictionary", base::MakeUnique<base::DictionaryValue>()); + dict->SetStringWithoutPathExpansion("a.string", "string value"); + } + auto update = WaitForUpdate(); + ASSERT_TRUE(update->is_split_updates()); + auto& split_updates = update->get_split_updates(); + ASSERT_EQ(1u, split_updates.size()); + base::DictionaryValue expected_value; + expected_value.SetStringWithoutPathExpansion("a.string", "string value"); + EXPECT_EQ(expected_value, *split_updates[0]->value); + EXPECT_EQ((std::vector<std::string>{"a.dictionary"}), split_updates[0]->path); +} + +TEST_F(PersistentPrefStoreClientTest, + SubPrefUpdates_AsDictionaryTriggersFullWrite) { + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->SetInteger("path.to.integer", 1); + update->AsDictionary(); + } + auto update = WaitForUpdate(); + ASSERT_TRUE(update->is_atomic_update()); + base::DictionaryValue expected_value; + expected_value.SetInteger("path.to.integer", 1); + EXPECT_EQ(expected_value, *update->get_atomic_update()); +} + +TEST_F(PersistentPrefStoreClientTest, SubPrefUpdates_AsConstDictionaryIsNoOp) { + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->SetInteger("path.to.integer", 1); + update->AsConstDictionary(); + } + auto update = WaitForUpdate(); + ASSERT_TRUE(update->is_split_updates()); + auto& split_updates = update->get_split_updates(); + ASSERT_EQ(1u, split_updates.size()); + EXPECT_EQ(base::Value(1), *split_updates[0]->value); + EXPECT_EQ((std::vector<std::string>{"path", "to", "integer"}), + split_updates[0]->path); +} + +TEST_F(PersistentPrefStoreClientTest, SubPrefUpdates_RemovePath_Basic) { + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->SetInteger("path.to.integer", 1); + update->SetInteger("path.to.something.else", 1); + } + WaitForUpdate(); + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->RemovePath("path.to.integer", nullptr); + } + auto update = WaitForUpdate(); + ASSERT_TRUE(update->is_split_updates()); + auto& split_updates = update->get_split_updates(); + ASSERT_EQ(1u, split_updates.size()); + EXPECT_FALSE(split_updates[0]->value); + EXPECT_EQ((std::vector<std::string>{"path", "to", "integer"}), + split_updates[0]->path); +} + +TEST_F(PersistentPrefStoreClientTest, + SubPrefUpdates_RemovePath_RemoveContainingDict) { + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->SetInteger("path.to.integer", 1); + update->SetInteger("path.for.something.else", 1); + } + WaitForUpdate(); + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->RemovePath("path.to.integer", nullptr); + } + auto update = WaitForUpdate(); + ASSERT_TRUE(update->is_split_updates()); + auto& split_updates = update->get_split_updates(); + ASSERT_EQ(1u, split_updates.size()); + EXPECT_FALSE(split_updates[0]->value); + EXPECT_EQ((std::vector<std::string>{"path", "to"}), split_updates[0]->path); +} + +TEST_F(PersistentPrefStoreClientTest, + SubPrefUpdates_RemovePath_SinglePathComponent) { + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->SetInteger("integer", 1); + update->SetInteger("something_else", 1); + } + WaitForUpdate(); + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->RemovePath("integer", nullptr); + } + auto update = WaitForUpdate(); + ASSERT_TRUE(update->is_split_updates()); + auto& split_updates = update->get_split_updates(); + ASSERT_EQ(1u, split_updates.size()); + EXPECT_FALSE(split_updates[0]->value); + EXPECT_EQ((std::vector<std::string>{"integer"}), split_updates[0]->path); +} + +TEST_F(PersistentPrefStoreClientTest, SubPrefUpdates_RemovePath_FullPref) { + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->SetInteger("path.to.integer", 1); + } + WaitForUpdate(); + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->RemovePath("path.to.integer", nullptr); + } + auto update = WaitForUpdate(); + ASSERT_TRUE(update->is_split_updates()); + auto& split_updates = update->get_split_updates(); + ASSERT_EQ(1u, split_updates.size()); + EXPECT_FALSE(split_updates[0]->value); + EXPECT_EQ((std::vector<std::string>{"path"}), split_updates[0]->path); +} + +TEST_F(PersistentPrefStoreClientTest, + SubPrefUpdates_RemovePathSinglePathComponent_FullPref) { + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->SetInteger("integer", 1); + } + WaitForUpdate(); + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->RemovePath("integer", nullptr); + } + auto update = WaitForUpdate(); + ASSERT_TRUE(update->is_split_updates()); + auto& split_updates = update->get_split_updates(); + ASSERT_EQ(1u, split_updates.size()); + EXPECT_FALSE(split_updates[0]->value); + EXPECT_EQ((std::vector<std::string>{"integer"}), split_updates[0]->path); +} + +TEST_F(PersistentPrefStoreClientTest, SubPrefUpdates_NoChange) { + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->SetInteger("path.to.integer", 1); + } + WaitForUpdate(); + ScopedDictionaryPrefUpdate(pref_service(), kDictionaryKey).Get(); + ExpectNoUpdate(); +} + +TEST_F(PersistentPrefStoreClientTest, SubPrefUpdates_SetToExistingValue) { + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->SetInteger("path.to.integer", 1); + } + WaitForUpdate(); + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->SetInteger("path.to.integer", 1); + } + ExpectNoUpdate(); +} + +TEST_F(PersistentPrefStoreClientTest, SubPrefUpdates_ClearEmptyDictionary) { + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->Clear(); + } + ExpectNoUpdate(); +} + +TEST_F(PersistentPrefStoreClientTest, SubPrefUpdates_ReplaceDictionary) { + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->SetInteger("path.to.integer", 1); + } + WaitForUpdate(); + { + ScopedDictionaryPrefUpdate update(pref_service(), kDictionaryKey); + update->SetInteger("path", 2); + } + auto update = WaitForUpdate(); + ASSERT_TRUE(update->is_split_updates()); + auto& split_updates = update->get_split_updates(); + ASSERT_EQ(1u, split_updates.size()); + EXPECT_EQ(base::Value(2), *split_updates[0]->value); + EXPECT_EQ((std::vector<std::string>{"path"}), split_updates[0]->path); +} + +TEST_F(PersistentPrefStoreClientTest, SubPrefUpdates_Uninitialized) { + { + ScopedDictionaryPrefUpdate update(pref_service(), + kUninitializedDictionaryKey); + update->SetInteger("path.to.integer", 1); + } + auto update = WaitForUpdate(); + ASSERT_TRUE(update->is_atomic_update()); + base::DictionaryValue expected_value; + expected_value.SetInteger("path.to.integer", 1); + EXPECT_EQ(expected_value, *update->get_atomic_update()); +} + +} // namespace +} // namespace prefs
diff --git a/services/preferences/public/cpp/tests/pref_store_client_unittest.cc b/services/preferences/public/cpp/tests/pref_store_client_unittest.cc index dbf86d4..8952cbf5 100644 --- a/services/preferences/public/cpp/tests/pref_store_client_unittest.cc +++ b/services/preferences/public/cpp/tests/pref_store_client_unittest.cc
@@ -41,7 +41,9 @@ bool initialized() { return store_->IsInitializationComplete(); } void OnPrefChanged(const std::string& key, const base::Value& value) { std::vector<mojom::PrefUpdatePtr> updates; - updates.push_back(mojom::PrefUpdate::New(key, value.CreateDeepCopy(), 0)); + updates.push_back(mojom::PrefUpdate::New( + key, mojom::PrefUpdateValue::NewAtomicUpdate(value.CreateDeepCopy()), + 0)); observer_ptr_->OnPrefsChanged(std::move(updates)); } void OnInitializationCompleted() {
diff --git a/services/preferences/public/interfaces/preferences.mojom b/services/preferences/public/interfaces/preferences.mojom index 47a79dc..ca3f37ad 100644 --- a/services/preferences/public/interfaces/preferences.mojom +++ b/services/preferences/public/interfaces/preferences.mojom
@@ -99,13 +99,29 @@ map<PrefStoreType, PrefStoreConnection> connections); }; +// An update to a subcomponent of a pref. +struct SubPrefUpdate { + // The path to the changed value within the pref. + array<string> path; + // The new value; a null |value| indicates a delete. + mojo.common.mojom.Value? value; +}; + +union PrefUpdateValue { + // Updates to several values within a pref (e.g. inside a dictionary stored + // under the pref key). + array<SubPrefUpdate> split_updates; + // An atomic update to the pref. A null |atomic_update| indicates a delete. + mojo.common.mojom.Value? atomic_update; +}; + // An update to a pref. struct PrefUpdate { // The key of the pref being updated. string key; - // The new value; a null |value| indicates a delete. - mojo.common.mojom.Value? value; - //|flags| is a bitmask of WritablePrefStore::PrefWriteFlags. + // The value update. + PrefUpdateValue value; + // |flags| is a bitmask of WritablePrefStore::PrefWriteFlags. uint32 flags; };
diff --git a/services/service_manager/public/cpp/BUILD.gn b/services/service_manager/public/cpp/BUILD.gn index a2452f5c..578444c3 100644 --- a/services/service_manager/public/cpp/BUILD.gn +++ b/services/service_manager/public/cpp/BUILD.gn
@@ -20,7 +20,6 @@ "interface_factory_impl.h", "interface_provider.h", "interface_provider_spec.h", - "interface_registry.h", "lib/binder_registry.cc", "lib/callback_binder.cc", "lib/callback_binder.h", @@ -30,7 +29,6 @@ "lib/interface_factory_binder.h", "lib/interface_provider.cc", "lib/interface_provider_spec.cc", - "lib/interface_registry.cc", "lib/service.cc", "lib/service_context.cc", "lib/service_context_ref.cc",
diff --git a/services/service_manager/public/cpp/interface_registry.h b/services/service_manager/public/cpp/interface_registry.h deleted file mode 100644 index 96e23a76..0000000 --- a/services/service_manager/public/cpp/interface_registry.h +++ /dev/null
@@ -1,240 +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 SERVICES_SERVICE_MANAGER_PUBLIC_CPP_INTERFACE_REGISTRY_H_ -#define SERVICES_SERVICE_MANAGER_PUBLIC_CPP_INTERFACE_REGISTRY_H_ - -#include <list> -#include <memory> -#include <queue> -#include <set> -#include <utility> - -#include "base/callback.h" -#include "base/memory/ptr_util.h" -#include "mojo/public/cpp/bindings/binding.h" -#include "services/service_manager/public/cpp/identity.h" -#include "services/service_manager/public/cpp/interface_provider_spec.h" -#include "services/service_manager/public/cpp/lib/callback_binder.h" -#include "services/service_manager/public/cpp/lib/interface_factory_binder.h" -#include "services/service_manager/public/interfaces/interface_provider.mojom.h" - -namespace service_manager { -class Connection; -class InterfaceBinder; - -// Returns the set of capabilities required from the target. -CapabilitySet GetRequestedCapabilities(const InterfaceProviderSpec& source_spec, - const Identity& target); - -// Generates a single set of interfaces that is the union of all interfaces -// exposed by the target for the capabilities requested by the source. -InterfaceSet GetInterfacesToExpose(const InterfaceProviderSpec& source_spec, - const Identity& target, - const InterfaceProviderSpec& target_spec); - -// An implementation of mojom::InterfaceProvider that allows the user to -// register services to be exposed to another application. -// -// To use, define a class that implements your specific interface. Then -// implement an InterfaceFactory<Foo> that binds instances of FooImpl to -// InterfaceRequest<Foo>s and register that on the registry like this: -// -// registry.AddInterface(&factory); -// -// Or, if you have multiple factories implemented by the same type, explicitly -// specify the interface to register the factory for: -// -// registry.AddInterface<Foo>(&my_foo_and_bar_factory_); -// registry.AddInterface<Bar>(&my_foo_and_bar_factory_); -// -// The InterfaceFactory must outlive the InterfaceRegistry. -// -// Additionally you may specify a default InterfaceBinder to handle requests for -// interfaces unhandled by any registered InterfaceFactory. Just as with -// InterfaceFactory, the default InterfaceBinder supplied must outlive -// InterfaceRegistry. -// -class InterfaceRegistry : public mojom::InterfaceProvider { - public: - using Binder = base::Callback<void(const std::string&, - mojo::ScopedMessagePipeHandle)>; - - class TestApi { - public: - explicit TestApi(InterfaceRegistry* registry) : registry_(registry) {} - ~TestApi() {} - - void SetInterfaceBinderForName(InterfaceBinder* binder, - const std::string& interface_name) { - registry_->SetInterfaceBinderForName( - base::WrapUnique(binder), interface_name); - } - - template <typename Interface> - void GetLocalInterface(mojo::InterfaceRequest<Interface> request) { - GetLocalInterface(Interface::Name_, request.PassMessagePipe()); - } - - void GetLocalInterface(const std::string& name, - mojo::ScopedMessagePipeHandle handle) { - registry_->GetInterface(name, std::move(handle)); - } - - private: - InterfaceRegistry* registry_; - DISALLOW_COPY_AND_ASSIGN(TestApi); - }; - - // Construct an unbound InterfaceRegistry. This object will not bind requests - // for interfaces until Bind() is called. |name| is used for error reporting - // and should reflect the name of the InterfaceProviderSpec pair that controls - // which interfaces can be bound via this InterfaceRegistry. - explicit InterfaceRegistry(const std::string& name); - ~InterfaceRegistry() override; - - // Sets a default handler for incoming interface requests which are allowed by - // capability filters but have no registered handler in this registry. - void set_default_binder(const Binder& binder) { default_binder_ = binder; } - - // Binds a request for an InterfaceProvider from a remote source. - // |remote_info| contains the the identity of the remote, and the remote's - // InterfaceProviderSpec, which will be intersected with the local's exports - // to determine what interfaces may be bound. - void Bind(mojom::InterfaceProviderRequest request, - const Identity& local_identity, - const InterfaceProviderSpec& local_interface_provider_spec, - const Identity& remote_identity, - const InterfaceProviderSpec& remote_interface_provider_spec); - - // Serializes the contents of the registry (including the local and remote - // specs) to a stringstream. - void Serialize(std::stringstream* stream); - - base::WeakPtr<InterfaceRegistry> GetWeakPtr(); - - // Allows |Interface| to be exposed via this registry. Requests to bind will - // be handled by |factory|. Returns true if the interface was exposed, false - // if Connection policy prevented exposure. - template <typename Interface> - bool AddInterface(InterfaceFactory<Interface>* factory) { - return SetInterfaceBinderForName( - base::MakeUnique<internal::InterfaceFactoryBinder<Interface>>(factory), - Interface::Name_); - } - - // Like AddInterface above, except supplies a callback to bind the MP instead - // of an InterfaceFactory, and optionally provides a task runner where the - // callback will be run. - template <typename Interface> - bool AddInterface( - const base::Callback<void(mojo::InterfaceRequest<Interface>)>& callback, - const scoped_refptr<base::SingleThreadTaskRunner>& task_runner = - nullptr) { - return SetInterfaceBinderForName( - base::MakeUnique<internal::CallbackBinder<Interface>>(callback, - task_runner), - Interface::Name_); - } - bool AddInterface( - const std::string& name, - const base::Callback<void(mojo::ScopedMessagePipeHandle)>& callback, - const scoped_refptr<base::SingleThreadTaskRunner>& task_runner = - nullptr); - - template <typename Interface> - void RemoveInterface() { - RemoveInterface(Interface::Name_); - } - void RemoveInterface(const std::string& name); - - // Temporarily prevent incoming interface requests from being bound. Incoming - // requests will be queued internally and dispatched once ResumeBinding() is - // called. - void PauseBinding(); - - // Resumes incoming interface request binding. - void ResumeBinding(); - - // Populates a set with the interface names this registry can bind. - void GetInterfaceNames(std::set<std::string>* interface_names); - - // Sets a closure to be run when the InterfaceProvider pipe is closed. Note - // that by the time any added closure is invoked, the InterfaceRegistry may - // have been deleted. - void AddConnectionLostClosure(const base::Closure& connection_lost_closure); - - // Binds a local interface request. - void BindInterface(const std::string& name, - mojo::ScopedMessagePipeHandle handle); - - private: - using InterfaceNameToBinderMap = - std::map<std::string, std::unique_ptr<InterfaceBinder>>; - - // mojom::InterfaceProvider: - void GetInterface(const std::string& interface_name, - mojo::ScopedMessagePipeHandle handle) override; - - // Returns true if the binder was set, false if it was not set (e.g. by - // some filtering policy preventing this interface from being exposed). - bool SetInterfaceBinderForName(std::unique_ptr<InterfaceBinder> binder, - const std::string& name); - - // Returns true if |remote_identity_| is allowed to bind |interface_name|, - // according to capability policy. - bool CanBindRequestForInterface(const std::string& interface_name) const; - - // Called whenever |remote_interface_provider_spec_| changes to rebuild the - // contents of |exposed_interfaces_| and |expose_all_interfaces_|. - void RebuildExposedInterfaces(); - - void OnConnectionError(); - - mojom::InterfaceProviderRequest pending_request_; - - mojo::Binding<mojom::InterfaceProvider> binding_; - - std::string name_; - - // Initialized from static metadata in the host service's manifest. - Identity local_identity_; - InterfaceProviderSpec local_interface_provider_spec_; - - // Initialized from static metadata in the remote service's manifest. - Identity remote_identity_; - // Initialized from static metadata in the remote service's manifest. May be - // mutated after the fact when a capability is dynamically granted via a call - // to GrantCapability(). - InterfaceProviderSpec remote_interface_provider_spec_; - - // Metadata computed whenever |remote_interface_provider_spec_| changes. - InterfaceSet exposed_interfaces_; - bool expose_all_interfaces_ = false; - - // Contains every interface binder that has been registered with this - // InterfaceRegistry. Not all binders may be reachable depending on the - // capabilities requested by the remote. Only interfaces in - // exposed_interfaces_ may be bound. When |expose_all_interfaces_| is true, - // any interface may be bound. - InterfaceNameToBinderMap name_to_binder_; - Binder default_binder_; - - bool is_paused_ = false; - - // Pending interface requests which can accumulate if GetInterface() is called - // while binding is paused. - std::queue<std::pair<std::string, mojo::ScopedMessagePipeHandle>> - pending_interface_requests_; - - std::list<base::Closure> connection_lost_closures_; - - base::WeakPtrFactory<InterfaceRegistry> weak_factory_; - - DISALLOW_COPY_AND_ASSIGN(InterfaceRegistry); -}; - -} // namespace service_manager - -#endif // SERVICES_SERVICE_MANAGER_PUBLIC_CPP_INTERFACE_REGISTRY_H_
diff --git a/services/service_manager/public/cpp/lib/interface_registry.cc b/services/service_manager/public/cpp/lib/interface_registry.cc deleted file mode 100644 index e74df3c..0000000 --- a/services/service_manager/public/cpp/lib/interface_registry.cc +++ /dev/null
@@ -1,267 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "services/service_manager/public/cpp/interface_registry.h" - -#include <iterator> -#include <sstream> - -#include "base/memory/ptr_util.h" -#include "mojo/public/cpp/bindings/message.h" - -namespace service_manager { -namespace { - -void SerializeIdentity(const Identity& identity, std::stringstream* stream) { - *stream << identity.name() << "@" << identity.instance() << " run as: " - << identity.user_id(); -} - -void SerializeSpec(const InterfaceProviderSpec& spec, - std::stringstream* stream) { - *stream << " Providing:\n"; - for (const auto& entry : spec.provides) { - *stream << " capability: " << entry.first << " containing interfaces:\n"; - for (const auto& interface_name : entry.second) - *stream << " " << interface_name << "\n"; - } - *stream << "\n Requiring:\n"; - for (const auto& entry : spec.requires) { - *stream << " From: " << entry.first << ":\n"; - for (const auto& capability_name : entry.second) - *stream << " " << capability_name << "\n"; - } -} - -} // namespace - -CapabilitySet GetRequestedCapabilities(const InterfaceProviderSpec& source_spec, - const Identity& target) { - CapabilitySet capabilities; - - // Start by looking for specs specific to the supplied identity. - auto it = source_spec.requires.find(target.name()); - if (it != source_spec.requires.end()) { - std::copy(it->second.begin(), it->second.end(), - std::inserter(capabilities, capabilities.begin())); - } - - // Apply wild card rules too. - it = source_spec.requires.find("*"); - if (it != source_spec.requires.end()) { - std::copy(it->second.begin(), it->second.end(), - std::inserter(capabilities, capabilities.begin())); - } - return capabilities; -} - -InterfaceSet GetInterfacesToExpose( - const InterfaceProviderSpec& source_spec, - const Identity& target, - const InterfaceProviderSpec& target_spec) { - InterfaceSet exposed_interfaces; - // TODO(beng): remove this once we can assert that an InterfaceRegistry must - // always be constructed with a valid identity. - if (!target.IsValid()) { - exposed_interfaces.insert("*"); - return exposed_interfaces; - } - CapabilitySet capabilities = GetRequestedCapabilities(source_spec, target); - for (const auto& capability : capabilities) { - auto it = target_spec.provides.find(capability); - if (it != target_spec.provides.end()) { - for (const auto& interface_name : it->second) - exposed_interfaces.insert(interface_name); - } - } - return exposed_interfaces; -} - -InterfaceRegistry::InterfaceRegistry(const std::string& name) - : binding_(this), - name_(name), - weak_factory_(this) {} -InterfaceRegistry::~InterfaceRegistry() {} - -void InterfaceRegistry::Bind( - mojom::InterfaceProviderRequest local_interfaces_request, - const Identity& local_identity, - const InterfaceProviderSpec& local_interface_provider_spec, - const Identity& remote_identity, - const InterfaceProviderSpec& remote_interface_provider_spec) { - DCHECK(!binding_.is_bound()); - local_identity_ = local_identity; - local_interface_provider_spec_ = local_interface_provider_spec; - remote_identity_ = remote_identity; - remote_interface_provider_spec_ = remote_interface_provider_spec; - RebuildExposedInterfaces(); - binding_.Bind(std::move(local_interfaces_request)); - binding_.set_connection_error_handler(base::Bind( - &InterfaceRegistry::OnConnectionError, base::Unretained(this))); -} - -void InterfaceRegistry::Serialize(std::stringstream* stream) { - *stream << "\n\nInterfaceRegistry(" << name_ << "):\n"; - if (!binding_.is_bound()) { - *stream << "\n --> InterfaceRegistry is not yet bound to a pipe.\n\n"; - return; - } - - *stream << "Owned by:\n "; - SerializeIdentity(local_identity_, stream); - *stream << "\n\n"; - SerializeSpec(local_interface_provider_spec_, stream); - - *stream << "\n"; - - *stream << "Bound to:\n "; - SerializeIdentity(remote_identity_, stream); - *stream << "\n\n"; - SerializeSpec(remote_interface_provider_spec_, stream); - - *stream << "\nBinders registered for:\n"; - bool found_exposed = false; - for (const auto& entry : name_to_binder_) { - bool exposed = exposed_interfaces_.count(entry.first) > 0; - found_exposed |= exposed; - *stream << " " << (exposed ? "* " : " ") << entry.first << "\n"; - } - if (found_exposed) - *stream << "\n * - denotes an interface exposed to remote per policy.\n"; - - *stream << "\n\n"; - if (expose_all_interfaces_) - *stream << "All interfaces exposed.\n\n"; -} - -base::WeakPtr<InterfaceRegistry> InterfaceRegistry::GetWeakPtr() { - return weak_factory_.GetWeakPtr(); -} - -bool InterfaceRegistry::AddInterface( - const std::string& name, - const base::Callback<void(mojo::ScopedMessagePipeHandle)>& callback, - const scoped_refptr<base::SingleThreadTaskRunner>& task_runner) { - return SetInterfaceBinderForName( - base::MakeUnique<internal::GenericCallbackBinder>(callback, task_runner), - name); -} - -void InterfaceRegistry::RemoveInterface(const std::string& name) { - auto it = name_to_binder_.find(name); - if (it != name_to_binder_.end()) - name_to_binder_.erase(it); -} - -void InterfaceRegistry::PauseBinding() { - DCHECK(!is_paused_); - is_paused_ = true; -} - -void InterfaceRegistry::ResumeBinding() { - DCHECK(is_paused_); - is_paused_ = false; - - while (!pending_interface_requests_.empty()) { - auto& request = pending_interface_requests_.front(); - GetInterface(request.first, std::move(request.second)); - pending_interface_requests_.pop(); - } -} - -void InterfaceRegistry::GetInterfaceNames( - std::set<std::string>* interface_names) { - DCHECK(interface_names); - for (auto& entry : name_to_binder_) - interface_names->insert(entry.first); -} - -void InterfaceRegistry::AddConnectionLostClosure( - const base::Closure& connection_lost_closure) { - connection_lost_closures_.push_back(connection_lost_closure); -} - -void InterfaceRegistry::BindInterface(const std::string& name, - mojo::ScopedMessagePipeHandle handle) { - // NOTE: We don't expose GetInterface() publicly so as to avoid confusion - // with local and remote binding requests. - GetInterface(name, std::move(handle)); -} - -// mojom::InterfaceProvider: -void InterfaceRegistry::GetInterface(const std::string& interface_name, - mojo::ScopedMessagePipeHandle handle) { - if (is_paused_) { - pending_interface_requests_.emplace(interface_name, std::move(handle)); - return; - } - - if (CanBindRequestForInterface(interface_name)) { - auto iter = name_to_binder_.find(interface_name); - if (iter != name_to_binder_.end()) { - iter->second->BindInterface(remote_identity_, - interface_name, - std::move(handle)); - } else if (!default_binder_.is_null()) { - default_binder_.Run(interface_name, std::move(handle)); - } else { - LOG(ERROR) << "Failed to locate a binder for interface: " - << interface_name << " requested by: " << remote_identity_.name() - << " exposed by: " << local_identity_.name() - << " via InterfaceProviderSpec \"" << name_ << "\"."; - - std::stringstream details; - Serialize(&details); - DVLOG(1) << details.str(); - } - } else { - std::stringstream error; - error << "InterfaceProviderSpec \"" << name_ << "\" prevented service: " - << remote_identity_.name() << " from binding interface: " - << interface_name << " exposed by: " << local_identity_.name(); - mojo::ReportBadMessage(error.str()); - LOG(ERROR) << error.str(); - - std::stringstream details; - Serialize(&details); - DVLOG(1) << details.str(); - } -} - -bool InterfaceRegistry::SetInterfaceBinderForName( - std::unique_ptr<InterfaceBinder> binder, - const std::string& interface_name) { - if (CanBindRequestForInterface(interface_name)) { - RemoveInterface(interface_name); - name_to_binder_[interface_name] = std::move(binder); - return true; - } - return false; -} - -bool InterfaceRegistry::CanBindRequestForInterface( - const std::string& interface_name) const { - // Any interface may be registered before the registry is bound to a pipe. At - // bind time, the interfaces exposed will be intersected with the requirements - // of the source. - if (!binding_.is_bound()) - return true; - return expose_all_interfaces_ || exposed_interfaces_.count(interface_name); -} - -void InterfaceRegistry::RebuildExposedInterfaces() { - exposed_interfaces_ = GetInterfacesToExpose(remote_interface_provider_spec_, - local_identity_, - local_interface_provider_spec_); - expose_all_interfaces_ = - exposed_interfaces_.size() == 1 && exposed_interfaces_.count("*") == 1; -} - -void InterfaceRegistry::OnConnectionError() { - std::list<base::Closure> closures = connection_lost_closures_; - for (const auto& closure : closures) - closure.Run(); -} - -} // namespace service_manager
diff --git a/services/service_manager/public/cpp/tests/BUILD.gn b/services/service_manager/public/cpp/tests/BUILD.gn deleted file mode 100644 index 3a5a834b..0000000 --- a/services/service_manager/public/cpp/tests/BUILD.gn +++ /dev/null
@@ -1,18 +0,0 @@ -# Copyright 2014 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -import("//testing/test.gni") - -test("mojo_public_application_unittests") { - sources = [ - "interface_registry_unittest.cc", - ] - - deps = [ - "//base", - "//mojo/edk/test:run_all_unittests", - "//services/service_manager/public/cpp", - "//testing/gtest", - ] -}
diff --git a/services/service_manager/public/cpp/tests/interface_registry_unittest.cc b/services/service_manager/public/cpp/tests/interface_registry_unittest.cc deleted file mode 100644 index ab9496e..0000000 --- a/services/service_manager/public/cpp/tests/interface_registry_unittest.cc +++ /dev/null
@@ -1,70 +0,0 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "services/service_manager/public/cpp/interface_registry.h" - -#include "base/memory/ptr_util.h" -#include "base/message_loop/message_loop.h" -#include "services/service_manager/public/cpp/interface_binder.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace service_manager { - -class TestBinder : public InterfaceBinder { - public: - explicit TestBinder(int* delete_count) : delete_count_(delete_count) {} - ~TestBinder() override { (*delete_count_)++; } - void BindInterface(const Identity& remote_identity, - const std::string& interface_name, - mojo::ScopedMessagePipeHandle client_handle) override {} - - private: - int* delete_count_; -}; - -TEST(InterfaceRegistryTest, Ownership) { - base::MessageLoop message_loop_; - int delete_count = 0; - - // Destruction. - { - auto registry = base::MakeUnique<InterfaceRegistry>(std::string()); - InterfaceRegistry::TestApi test_api(registry.get()); - test_api.SetInterfaceBinderForName(new TestBinder(&delete_count), "TC1"); - } - EXPECT_EQ(1, delete_count); - - // Removal. - { - auto registry = - base::MakeUnique<InterfaceRegistry>(std::string()); - InterfaceBinder* b = new TestBinder(&delete_count); - InterfaceRegistry::TestApi test_api(registry.get()); - test_api.SetInterfaceBinderForName(b, "TC1"); - registry->RemoveInterface("TC1"); - registry.reset(); - EXPECT_EQ(2, delete_count); - } - - // Multiple. - { - auto registry = base::MakeUnique<InterfaceRegistry>(std::string()); - InterfaceRegistry::TestApi test_api(registry.get()); - test_api.SetInterfaceBinderForName(new TestBinder(&delete_count), "TC1"); - test_api.SetInterfaceBinderForName(new TestBinder(&delete_count), "TC2"); - } - EXPECT_EQ(4, delete_count); - - // Re-addition. - { - auto registry = base::MakeUnique<InterfaceRegistry>(std::string()); - InterfaceRegistry::TestApi test_api(registry.get()); - test_api.SetInterfaceBinderForName(new TestBinder(&delete_count), "TC1"); - test_api.SetInterfaceBinderForName(new TestBinder(&delete_count), "TC1"); - EXPECT_EQ(5, delete_count); - } - EXPECT_EQ(6, delete_count); -} - -} // namespace service_manager
diff --git a/services/video_capture/service_impl.cc b/services/video_capture/service_impl.cc index 74def48..f3b4585f 100644 --- a/services/video_capture/service_impl.cc +++ b/services/video_capture/service_impl.cc
@@ -5,7 +5,6 @@ #include "services/video_capture/service_impl.h" #include "mojo/public/cpp/bindings/strong_binding.h" -#include "services/service_manager/public/cpp/interface_registry.h" #include "services/service_manager/public/cpp/service_context.h" #include "services/video_capture/device_factory_provider_impl.h" #include "services/video_capture/public/interfaces/constants.mojom.h"
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json index 1a34b3e..942b7c42 100644 --- a/testing/buildbot/chromium.android.fyi.json +++ b/testing/buildbot/chromium.android.fyi.json
@@ -2607,19 +2607,6 @@ "test": "mojo_common_unittests" }, { - "override_isolate_target": "mojo_public_application_unittests", - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "android_devices": "1", - "device_type": "coho" - } - ] - }, - "test": "mojo_public_application_unittests" - }, - { "override_isolate_target": "mojo_public_bindings_unittests", "swarming": { "can_use_on_swarming_builders": true, @@ -3151,20 +3138,6 @@ "test": "mojo_common_unittests" }, { - "override_isolate_target": "mojo_public_application_unittests", - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "android_devices": "4", - "device_type": "gce_x86" - } - ], - "hard_timeout": 60 - }, - "test": "mojo_public_application_unittests" - }, - { "override_isolate_target": "mojo_public_bindings_unittests", "swarming": { "can_use_on_swarming_builders": true,
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json index 4ff354e..9b5599ae 100644 --- a/testing/buildbot/chromium.android.json +++ b/testing/buildbot/chromium.android.json
@@ -809,38 +809,6 @@ "test": "mojo_common_unittests" }, { - "override_isolate_target": "mojo_public_application_unittests", - "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": [ - { - "android_devices": "4", - "device_os": "MMB29Q", - "device_type": "bullhead" - } - ], - "hard_timeout": 60, - "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": "mojo_public_application_unittests" - }, - { "override_isolate_target": "mojo_public_bindings_unittests", "swarming": { "can_use_on_swarming_builders": true,
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json index d06725f..57fbf20 100644 --- a/testing/buildbot/chromium.fyi.json +++ b/testing/buildbot/chromium.fyi.json
@@ -3135,19 +3135,6 @@ "test": "mojo_common_unittests" }, { - "override_isolate_target": "mojo_public_application_unittests", - "swarming": { - "can_use_on_swarming_builders": true, - "dimension_sets": [ - { - "android_devices": "1", - "device_type": "coho" - } - ] - }, - "test": "mojo_public_application_unittests" - }, - { "override_isolate_target": "mojo_public_bindings_unittests", "swarming": { "can_use_on_swarming_builders": true, @@ -11783,6 +11770,21 @@ } ] }, + "Mojo Linux": { + "gtest_tests": [ + { + "args": [ + "--enable-network-service", + "--test-launcher-filter-file=../../testing/buildbot/filters/mojo.fyi.network_content_browsertests.filter" + ], + "name": "network_service_content_browsertests", + "swarming": { + "can_use_on_swarming_builders": true + }, + "test": "content_browsertests" + } + ] + }, "Mojo Windows": { "additional_compile_targets": [ "mash:all"
diff --git a/testing/buildbot/chromium.linux.json b/testing/buildbot/chromium.linux.json index d6d955d..3ff5be8 100644 --- a/testing/buildbot/chromium.linux.json +++ b/testing/buildbot/chromium.linux.json
@@ -823,38 +823,6 @@ "test": "mojo_common_unittests" }, { - "override_isolate_target": "mojo_public_application_unittests", - "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": [ - { - "android_devices": "4", - "device_os": "KTU84P", - "device_type": "hammerhead" - } - ], - "hard_timeout": 60, - "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": "mojo_public_application_unittests" - }, - { "override_isolate_target": "mojo_public_bindings_unittests", "swarming": { "can_use_on_swarming_builders": true, @@ -2105,38 +2073,6 @@ "test": "mojo_common_unittests" }, { - "override_isolate_target": "mojo_public_application_unittests", - "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": [ - { - "android_devices": "4", - "device_os": "KTU84P", - "device_type": "hammerhead" - } - ], - "hard_timeout": 60, - "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": "mojo_public_application_unittests" - }, - { "override_isolate_target": "mojo_public_bindings_unittests", "swarming": { "can_use_on_swarming_builders": true, @@ -3379,12 +3315,6 @@ "swarming": { "can_use_on_swarming_builders": true }, - "test": "mojo_public_application_unittests" - }, - { - "swarming": { - "can_use_on_swarming_builders": true - }, "test": "mojo_public_bindings_unittests" }, { @@ -4073,12 +4003,6 @@ "swarming": { "can_use_on_swarming_builders": true }, - "test": "mojo_public_application_unittests" - }, - { - "swarming": { - "can_use_on_swarming_builders": true - }, "test": "mojo_public_bindings_unittests" }, {
diff --git a/testing/buildbot/chromium.win.json b/testing/buildbot/chromium.win.json index 53c3926..fef8bbe 100644 --- a/testing/buildbot/chromium.win.json +++ b/testing/buildbot/chromium.win.json
@@ -271,12 +271,6 @@ "swarming": { "can_use_on_swarming_builders": true }, - "test": "mojo_public_application_unittests" - }, - { - "swarming": { - "can_use_on_swarming_builders": true - }, "test": "mojo_public_bindings_unittests" }, { @@ -1324,12 +1318,6 @@ "swarming": { "can_use_on_swarming_builders": true }, - "test": "mojo_public_application_unittests" - }, - { - "swarming": { - "can_use_on_swarming_builders": true - }, "test": "mojo_public_bindings_unittests" }, {
diff --git a/testing/buildbot/filters/BUILD.gn b/testing/buildbot/filters/BUILD.gn index ac9af62..b814a18 100644 --- a/testing/buildbot/filters/BUILD.gn +++ b/testing/buildbot/filters/BUILD.gn
@@ -35,6 +35,7 @@ data = [ "//testing/buildbot/filters/cast-linux.content_browsertests.filter", "//testing/buildbot/filters/isolate-extensions.content_browsertests.filter", + "//testing/buildbot/filters/mojo.fyi.network_content_browsertests.filter", "//testing/buildbot/filters/site-per-process.content_browsertests.filter", ] }
diff --git a/testing/buildbot/filters/ash_mus_unittests.filter b/testing/buildbot/filters/ash_mus_unittests.filter index 02224f2..cc074a1 100644 --- a/testing/buildbot/filters/ash_mus_unittests.filter +++ b/testing/buildbot/filters/ash_mus_unittests.filter
@@ -343,7 +343,6 @@ -TrayIMETest.PerformActionOnDetailedView -TrayRotationLockTest.CreateDefaultView -TrayRotationLockTest.CreateDefaultViewDuringMaximizeMode --TrayRotationLockTest.CreateTrayViewDuringMaximizeModeAndRotationLock -TrayRotationLockTest.DefaultViewVisibilityChangesDuringMaximizeMode -TrayRotationLockTest.InternalDisplayNotAvailableAtCreation -TrayRotationLockTest.PerformActionOnDefaultView
diff --git a/testing/buildbot/filters/mash.browser_tests.filter b/testing/buildbot/filters/mash.browser_tests.filter index 93388f3..f928258 100644 --- a/testing/buildbot/filters/mash.browser_tests.filter +++ b/testing/buildbot/filters/mash.browser_tests.filter
@@ -48,7 +48,6 @@ ChromeServiceWorkerTest.FailRegisterServiceWorkerWhenJSDisabled ChromeServiceWorkerTest.FallbackMainResourceRequestWhenJSDisabled ConstrainedWebDialogBrowserTest.ReleaseWebContents -ContentVerifierPolicyTest.FailedUpdateRetries ContentVerifierPolicyTest.PRE_PolicyCorruptedOnStartup ContentVerifierPolicyTest.PolicyCorruptedOnStartup CreateNewFolder/FileManagerBrowserTest.Test/0 @@ -58,7 +57,6 @@ DefaultTaskDialog/FileManagerBrowserTest.Test/0 DefaultTaskDialog/FileManagerBrowserTest.Test/1 DefaultTaskDialog/FileManagerBrowserTest.Test/2 -Delete/FileManagerBrowserTest.Test/0 Delete/FileManagerBrowserTest.Test/1 DeviceStatusCollectorNetworkInterfacesTest.NetworkInterfaces DeviceStatusCollectorNetworkInterfacesTest.ReportIfPublicSession @@ -104,7 +102,6 @@ DirectoryTreeContextMenu/FileManagerBrowserTest.Test/20 DirectoryTreeContextMenu/FileManagerBrowserTest.Test/21 DirectoryTreeContextMenu/FileManagerBrowserTest.Test/22 -DirectoryTreeContextMenu/FileManagerBrowserTest.Test/23 DirectoryTreeContextMenu/FileManagerBrowserTest.Test/24 DirectoryTreeContextMenu/FileManagerBrowserTest.Test/3 DirectoryTreeContextMenu/FileManagerBrowserTest.Test/4 @@ -169,13 +166,8 @@ OpenAudioFiles/FileManagerBrowserTest.Test/4 OpenAudioFiles/FileManagerBrowserTest.Test/5 OpenAudioFiles/FileManagerBrowserTest.Test/6 -OpenAudioFiles/FileManagerBrowserTest.Test/7 -OpenAudioFiles/FileManagerBrowserTest.Test/8 OpenFileDialog/FileManagerBrowserTest.Test/0 -OpenFileDialog/FileManagerBrowserTest.Test/1 OpenFileDialog/FileManagerBrowserTest.Test/2 -OpenFileDialog/FileManagerBrowserTest.Test/3 -OpenFileDialog/FileManagerBrowserTest.Test/4 OpenFileDialog/FileManagerBrowserTest.Test/5 PageClickTrackerTest.PageClickTrackerClickDisabledInputDoesNotResetClickCounter PageClickTrackerTest.PageClickTrackerDisabledInputClickedNoEvent @@ -226,8 +218,6 @@ PolicyCertVerifierTest.VerifyTrustedCert PolicyCertVerifierTest.VerifyUntrustedCert PolicyCertVerifierTest.VerifyUsingAdditionalTrustAnchor -PrerenderBrowserTestWithNaCl.PrerenderNaClPluginEnabled -PrintPreviewDestinationSearchTest.Select Providers/FileManagerBrowserTest.Test/0 Providers/FileManagerBrowserTest.Test/1 Providers/FileManagerBrowserTest.Test/2
diff --git a/testing/buildbot/filters/mojo.fyi.network_content_browsertests.filter b/testing/buildbot/filters/mojo.fyi.network_content_browsertests.filter new file mode 100644 index 0000000..7c8040c --- /dev/null +++ b/testing/buildbot/filters/mojo.fyi.network_content_browsertests.filter
@@ -0,0 +1,9 @@ +# http://crbug.com/715640 +BackgroundSyncBrowserTest* +ServiceWorker* + +# http://crbug.com/715677 +Blob* + +# http://crbug.com/715630 +DownloadContentTest.* \ No newline at end of file
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl index b661a94..fa8dff84 100644 --- a/testing/buildbot/gn_isolate_map.pyl +++ b/testing/buildbot/gn_isolate_map.pyl
@@ -712,10 +712,6 @@ "label": "//mojo/edk/js/tests:mojo_js_unittests", "type": "console_test_launcher", }, - "mojo_public_application_unittests": { - "label": "//services/service_manager/public/cpp/tests:mojo_public_application_unittests", - "type": "windowed_test_launcher", - }, "mojo_public_bindings_unittests": { "label": "//mojo/edk/test:mojo_public_bindings_unittests", "type": "console_test_launcher",
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json index 593fcf7..8390885 100644 --- a/testing/variations/fieldtrial_testing_config.json +++ b/testing/variations/fieldtrial_testing_config.json
@@ -365,7 +365,6 @@ { "name": "Default", "params": { - "RedirectNonUINonIOBrowserThreads": "true", "RedirectSequencedWorkerPools": "true" } }, @@ -781,24 +780,6 @@ ] } ], - "DownloadAttributionPerformanceTest": [ - { - "platforms": [ - "chromeos", - "linux", - "mac", - "win" - ], - "experiments": [ - { - "name": "DownloadAttributionEnabled", - "enable_features": [ - "DownloadAttribution" - ] - } - ] - } - ], "DownloadsUi": [ { "platforms": [
diff --git a/third_party/WebKit/LayoutTests/TestExpectations b/third_party/WebKit/LayoutTests/TestExpectations index 366f05e..2080654 100644 --- a/third_party/WebKit/LayoutTests/TestExpectations +++ b/third_party/WebKit/LayoutTests/TestExpectations
@@ -416,11 +416,12 @@ crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats-clear/margin-collapse-clear-015.xht [ Skip ] ### external/wpt/css/CSS2/floats -#### Passed: 19 48% -#### Skipped: 23 +#### Passed: 18 43% +#### Skipped: 24 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-placement-vertical-001a.xht [ Skip ] crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-placement-vertical-001b.xht [ Skip ] crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-placement-vertical-001c.xht [ Skip ] +crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-placement-vertical-003.xht [ Skip ] crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-wrap-bfc-001-left-overflow.xht [ Skip ] crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-wrap-bfc-001-right-overflow.xht [ Skip ] crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-wrap-bfc-002-left-overflow.xht [ Skip ] @@ -699,8 +700,8 @@ crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/abspos/abspos-containing-block-initial-009a.xht [ Skip ] #### external/wpt/css/CSS2/normal-flow -#### Passed: 492 70% -#### Skipped: 216 +#### Passed: 495 70% +#### Skipped: 213 crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/block-formatting-context-height-001.xht [ Skip ] crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/block-formatting-context-height-002.xht [ Skip ] crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/block-formatting-contexts-008.xht [ Skip ] @@ -711,10 +712,6 @@ crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/block-formatting-contexts-015.xht [ Skip ] crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/block-in-inline-insert-001f.xht [ Skip ] crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/block-in-inline-insert-002f.xht [ Skip ] -crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/block-in-inline-margins-001a.xht [ Skip ] -crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/block-in-inline-margins-001b.xht [ Skip ] -crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/block-in-inline-margins-002a.xht [ Skip ] -crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/block-in-inline-margins-002b.xht [ Skip ] crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/block-in-inline-percents-001.xht [ Skip ] crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/block-non-replaced-height-001.xht [ Skip ] crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/block-non-replaced-height-005.xht [ Skip ] @@ -914,6 +911,7 @@ crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/replaced-intrinsic-001.xht [ Skip ] crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/replaced-intrinsic-002.xht [ Skip ] crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/root-box-001.xht [ Skip ] +crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/table-in-inline-001.xht [ Skip ] crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-applies-to-012.xht [ Skip ] crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-inherit-001.xht [ Skip ] crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/normal-flow/width-percentage-001.xht [ Skip ] @@ -951,8 +949,8 @@ crbug.com/635619 virtual/layout_ng/fast/block/basic/percent-height-inside-anonymous-block.html [ Failure Crash ] #### fast/block/margin-collapse -#### Passed: 31 -#### Skipped: 59 +#### Passed: 33 +#### Skipped: 57 crbug.com/635619 virtual/layout_ng/fast/block/margin-collapse/004.html [ Skip ] crbug.com/635619 virtual/layout_ng/fast/block/margin-collapse/006.html [ Skip ] crbug.com/635619 virtual/layout_ng/fast/block/margin-collapse/016.html [ Skip ] @@ -984,14 +982,12 @@ crbug.com/635619 virtual/layout_ng/fast/block/margin-collapse/103.html [ Skip ] crbug.com/635619 virtual/layout_ng/fast/block/margin-collapse/104.html [ Skip ] crbug.com/635619 virtual/layout_ng/fast/block/margin-collapse/bfc-beside-float-complex-margin-collapsing.html [ Skip ] -crbug.com/635619 virtual/layout_ng/fast/block/margin-collapse/block-inside-inline/001.html [ Skip ] -crbug.com/635619 virtual/layout_ng/fast/block/margin-collapse/block-inside-inline/002.html [ Skip ] -crbug.com/635619 virtual/layout_ng/fast/block/margin-collapse/block-inside-inline/003.html [ Skip ] -crbug.com/635619 virtual/layout_ng/fast/block/margin-collapse/block-inside-inline/004.html [ Skip ] -crbug.com/635619 virtual/layout_ng/fast/block/margin-collapse/block-inside-inline/005.html [ Skip ] crbug.com/635619 virtual/layout_ng/fast/block/margin-collapse/block-inside-inline/006.html [ Skip ] +crbug.com/635619 virtual/layout_ng/fast/block/margin-collapse/block-inside-inline/010.html [ Skip ] +crbug.com/635619 virtual/layout_ng/fast/block/margin-collapse/block-inside-inline/011.html [ Skip ] +crbug.com/635619 virtual/layout_ng/fast/block/margin-collapse/block-inside-inline/012.html [ Skip ] +crbug.com/635619 virtual/layout_ng/fast/block/margin-collapse/block-inside-inline/015.html [ Skip ] crbug.com/635619 virtual/layout_ng/fast/block/margin-collapse/block-inside-inline/016.html [ Skip ] -crbug.com/635619 virtual/layout_ng/fast/block/margin-collapse/block-inside-inline/018.html [ Skip ] crbug.com/635619 virtual/layout_ng/fast/block/margin-collapse/block-inside-inline/021.html [ Skip ] crbug.com/635619 virtual/layout_ng/fast/block/margin-collapse/block-inside-inline/022.html [ Skip ] crbug.com/635619 virtual/layout_ng/fast/block/margin-collapse/block-inside-inline/025.html [ Skip ]
diff --git a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/OWNERS b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/OWNERS index 261aa8c..8c39d70 100644 --- a/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/OWNERS +++ b/third_party/WebKit/LayoutTests/external/wpt/html/rendering/non-replaced-elements/tables/OWNERS
@@ -1 +1,2 @@ # TEAM: layout-dev@chromium.org +# COMPONENT: Blink>Layout>Table
diff --git a/third_party/WebKit/LayoutTests/fast/table/OWNERS b/third_party/WebKit/LayoutTests/fast/table/OWNERS index 261aa8c..8c39d70 100644 --- a/third_party/WebKit/LayoutTests/fast/table/OWNERS +++ b/third_party/WebKit/LayoutTests/fast/table/OWNERS
@@ -1 +1,2 @@ # TEAM: layout-dev@chromium.org +# COMPONENT: Blink>Layout>Table
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector/console-test.js b/third_party/WebKit/LayoutTests/http/tests/inspector/console-test.js index c4ac59cd..2001694 100644 --- a/third_party/WebKit/LayoutTests/http/tests/inspector/console-test.js +++ b/third_party/WebKit/LayoutTests/http/tests/inspector/console-test.js
@@ -425,4 +425,31 @@ } } +InspectorTest.selectConsoleMessages = function(fromMessage, fromTextOffset, toMessage, toTextOffset, useTextContainer) +{ + var consoleView = Console.ConsoleView.instance(); + var from = selectionContainerAndOffset(consoleView.itemElement(fromMessage).element(), fromTextOffset); + var to = selectionContainerAndOffset(consoleView.itemElement(toMessage).element(), toTextOffset); + window.getSelection().setBaseAndExtent(from.container, from.offset, to.container, to.offset); + + function selectionContainerAndOffset(container, offset) + { + if (offset === 0 && container.nodeType !== Node.TEXT_NODE) + container = container.traverseNextTextNode(); + var charCount = 0; + var node = container; + while (node = node.traverseNextTextNode(true)) { + var length = node.textContent.length; + if (charCount + length >= offset) { + return { + container: node, + offset: offset - charCount + }; + } + charCount += length; + } + return null; + } +} + }
diff --git a/third_party/WebKit/LayoutTests/http/tests/inspector/inspector-test.js b/third_party/WebKit/LayoutTests/http/tests/inspector/inspector-test.js index bee51ac1..3f06fbf7 100644 --- a/third_party/WebKit/LayoutTests/http/tests/inspector/inspector-test.js +++ b/third_party/WebKit/LayoutTests/http/tests/inspector/inspector-test.js
@@ -469,6 +469,57 @@ } } +InspectorTest.waitForTarget = function(filter) { + filter = filter || (target => true); + for (var target of SDK.targetManager.targets()) { + if (filter(target)) + return Promise.resolve(target); + } + var fulfill; + var promise = new Promise(callback => fulfill = callback); + var observer = { + targetAdded: function(target) { + if (filter(target)) { + SDK.targetManager.unobserveTargets(observer); + fulfill(target); + } + }, + targetRemoved: function() { + }, + }; + SDK.targetManager.observeTargets(observer); + return promise; +} + +InspectorTest.waitForExecutionContext = function(runtimeModel) { + if (runtimeModel.executionContexts().length) + return Promise.resolve(runtimeModel.executionContexts()[0]); + var fulfill; + var promise = new Promise(callback => fulfill = callback); + function onContext(event) { + runtimeModel.removeEventListener(SDK.RuntimeModel.Events.ExecutionContextCreated, onContext); + fulfill(event.data); + } + runtimeModel.addEventListener(SDK.RuntimeModel.Events.ExecutionContextCreated, onContext); + return promise; +} + +InspectorTest.waitForExecutionContextDestroyed = function(context) { + var runtimeModel = context.runtimeModel; + if (runtimeModel.executionContexts().indexOf(context) === -1) + return Promise.resolve(); + var fulfill; + var promise = new Promise(callback => fulfill = callback); + function onContext(event) { + if (event.data === context) { + runtimeModel.removeEventListener(SDK.RuntimeModel.Events.ExecutionContextDestroyed, onContext); + fulfill(); + } + } + runtimeModel.addEventListener(SDK.RuntimeModel.Events.ExecutionContextDestroyed, onContext); + return promise; +} + InspectorTest.assertGreaterOrEqual = function(a, b, message) { if (a < b)
diff --git a/third_party/WebKit/LayoutTests/inspector/console/console-context-selector-expected.txt b/third_party/WebKit/LayoutTests/inspector/console/console-context-selector-expected.txt new file mode 100644 index 0000000..b5ca162 --- /dev/null +++ b/third_party/WebKit/LayoutTests/inspector/console/console-context-selector-expected.txt
@@ -0,0 +1,60 @@ +Tests console execution context selector. + +Console context selector: +* top + ____myframe (data:text/html,chro…) + ____âš™ worker-pause.js + +Selected worker +Console context selector: + top + ____myframe (data:text/html,chro…) +* ____âš™ worker-pause.js + +Selected iframe +Console context selector: + top +* ____myframe (data:text/html,chro…) + ____âš™ worker-pause.js + +Paused in main +Console context selector: +* top + ____myframe (data:text/html,chro…) [disabled] + ____âš™ worker-pause.js + +Resumed +Console context selector: +* top + ____myframe (data:text/html,chro…) + ____âš™ worker-pause.js + +Paused in worker +Console context selector: + top + ____myframe (data:text/html,chro…) +* ____âš™ worker-pause.js + +Resumed +Console context selector: + top + ____myframe (data:text/html,chro…) +* ____âš™ worker-pause.js + +Paused in iframe +Console context selector: + top [disabled] +* ____myframe (data:text/html,chro…) + ____âš™ worker-pause.js + +Resumed +Console context selector: + top +* ____myframe (data:text/html,chro…) + ____âš™ worker-pause.js + +Destroyed iframe +Console context selector: +* top + ____âš™ worker-pause.js +
diff --git a/third_party/WebKit/LayoutTests/inspector/console/console-context-selector.html b/third_party/WebKit/LayoutTests/inspector/console/console-context-selector.html new file mode 100644 index 0000000..21c236a3 --- /dev/null +++ b/third_party/WebKit/LayoutTests/inspector/console/console-context-selector.html
@@ -0,0 +1,115 @@ +<html> +<head> +<script src="../../http/tests/inspector/inspector-test.js"></script> +<script src="../../http/tests/inspector/console-test.js"></script> +<script src="../../http/tests/inspector/debugger-test.js"></script> +<script> +function setup() { + window.worker = new Worker('resources/worker-pause.js'); + window.iframe = document.createElement('iframe'); + window.iframe.src = 'data:text/html;<script>window.foo=1;<' + '/script>'; + window.iframe.name = 'myframe'; + document.body.appendChild(window.iframe); + return new Promise(f => window.iframe.onload = f); +} + +function pauseInWorker() { + window.worker.postMessage('pause'); +} + +function pauseInIframe() { + window.iframe.contentWindow.eval('debugger;'); +} + +function pauseInMain() { + debugger; +} + +function destroyIframe() { + window.iframe.parentElement.removeChild(window.iframe); + window.iframe = null; +} + +async function test() +{ + await new Promise(f => InspectorTest.startDebuggerTest(f, true)); + + await InspectorTest.evaluateInPageAsync('setup()'); + var workerTarget = await InspectorTest.waitForTarget(target => target.parentTarget() === InspectorTest.mainTarget); + var workerExecutionContext = await InspectorTest.waitForExecutionContext(workerTarget.model(SDK.RuntimeModel)); + dump(); + + InspectorTest.addResult(''); + InspectorTest.addResult('Selected worker'); + UI.context.setFlavor(SDK.Target, workerTarget); + dump(); + + var mainFrame = InspectorTest.resourceTreeModel.mainFrame; + var mainExecutionContext = InspectorTest.runtimeModel.executionContexts().find(context => context.frameId === mainFrame.id); + var childFrame = InspectorTest.resourceTreeModel.frames().find(frame => frame !== InspectorTest.resourceTreeModel.mainFrame); + var childExecutionContext = InspectorTest.runtimeModel.executionContexts().find(context => context.frameId === childFrame.id); + InspectorTest.addResult(''); + InspectorTest.addResult('Selected iframe'); + UI.context.setFlavor(SDK.ExecutionContext, childExecutionContext); + dump(); + + InspectorTest.evaluateInPage('pauseInMain()'); + await InspectorTest.waitUntilPausedPromise(); + InspectorTest.addResult(''); + InspectorTest.addResult('Paused in main'); + dump(); + + await new Promise(f => InspectorTest.resumeExecution(f)); + InspectorTest.addResult(''); + InspectorTest.addResult('Resumed'); + dump(); + + InspectorTest.evaluateInPage('pauseInWorker()'); + await InspectorTest.waitUntilPausedPromise(); + InspectorTest.addResult(''); + InspectorTest.addResult('Paused in worker'); + dump(); + + await new Promise(f => InspectorTest.resumeExecution(f)); + InspectorTest.addResult(''); + InspectorTest.addResult('Resumed'); + dump(); + + InspectorTest.evaluateInPage('pauseInIframe()'); + await InspectorTest.waitUntilPausedPromise(); + InspectorTest.addResult(''); + InspectorTest.addResult('Paused in iframe'); + dump(); + + await new Promise(f => InspectorTest.resumeExecution(f)); + InspectorTest.addResult(''); + InspectorTest.addResult('Resumed'); + dump(); + + InspectorTest.evaluateInPage('destroyIframe()'); + await InspectorTest.waitForExecutionContextDestroyed(childExecutionContext); + InspectorTest.addResult(''); + InspectorTest.addResult('Destroyed iframe'); + dump(); + + InspectorTest.completeDebuggerTest(); + + function dump() { + var consoleView = Console.ConsoleView.instance(); + var select = consoleView._executionContextComboBox.selectElement(); + InspectorTest.addResult('Console context selector:'); + for (var i = 0; i < select.options.length; i++) { + var option = select.options[i]; + var selected = select.selectedIndex === i; + var text = option.text.replace(new RegExp('\u00a0', 'g'), '_'); + InspectorTest.addResult(`${selected ? '*' : ' '} ${text} ${option.disabled ? '[disabled]' : ''}`); + } + } +} +</script> +</head> +<body onload="runTest()"> +<p> Tests console execution context selector. +</p> +</body> +</html>
diff --git a/third_party/WebKit/LayoutTests/inspector/console/console-copy-truncated-text-expected.txt b/third_party/WebKit/LayoutTests/inspector/console/console-copy-truncated-text-expected.txt new file mode 100644 index 0000000..298dd4b --- /dev/null +++ b/third_party/WebKit/LayoutTests/inspector/console/console-copy-truncated-text-expected.txt
@@ -0,0 +1,225 @@ +CONSOLE MESSAGE: line 3: www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com +CONSOLE MESSAGE: line 5: www.bar.com +CONSOLE MESSAGE: line 6: www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com +CONSOLE MESSAGE: line 7: www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com www.bar.com www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com +CONSOLE MESSAGE: line 8: www.01234567890123456789zfoobarz01234567890123456789 +CONSOLE MESSAGE: line 9: www.01234567890123456789zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz01234567890123456789 +CONSOLE MESSAGE: line 10: www.zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.com +Tests that console copies tree outline messages properly. + +Message count: 8 +Long url has max length: 150, text: www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z…z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com + +Running: testSelectWithinTruncatedUrl + +Making selection: 1, 0, 1, 75 +Selection length: 75, text: www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z + +Making selection: 1, 0, 1, 76 +Selection length: 84, text: www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789 + +Making selection: 1, 0, 1, 150 +Selection length: 158, text: www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com + +Making selection: 1, 75, 1, 76 +Selection length: 9, text: 123456789 + +Making selection: 1, 75, 1, 150 +Selection length: 83, text: 123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com + +Making selection: 1, 76, 1, 150 +Selection length: 74, text: z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com + +Running: testSelectAcrossMultipleMessages + +Making selection: 1, 0, 2, 11 +Selection length: 177, text: www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com +VM:5 www.bar.com + +Making selection: 1, 75, 2, 11 +Selection length: 102, text: 123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com +VM:5 www.bar.com + +Making selection: 1, 76, 2, 11 +Selection length: 93, text: z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com +VM:5 www.bar.com + +Running: testSelectAcrossMultipleMessagesWithTruncatedUrls + +Making selection: 1, 0, 3, 75 +Selection length: 260, text: www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com +VM:5 www.bar.com +VM:6 www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z + +Making selection: 1, 0, 3, 76 +Selection length: 269, text: www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com +VM:5 www.bar.com +VM:6 www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789 + +Making selection: 1, 0, 3, 150 +Selection length: 343, text: www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com +VM:5 www.bar.com +VM:6 www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com + +Running: testSelectWithinMessageWithMultipleTruncatedUrls + +Making selection: 4, 0, 4, 75 +Selection length: 75, text: www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z + +Making selection: 4, 0, 4, 76 +Selection length: 84, text: www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789 + +Making selection: 4, 0, 4, 163 +Selection length: 171, text: www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com www.bar.com + +Making selection: 4, 0, 4, 238 +Selection length: 246, text: www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com www.bar.com www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z + +Making selection: 4, 0, 4, 239 +Selection length: 255, text: www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com www.bar.com www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789 + +Making selection: 4, 0, 4, 313 +Selection length: 329, text: www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com www.bar.com www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com + +Making selection: 4, 75, 4, 76 +Selection length: 9, text: 123456789 + +Making selection: 4, 75, 4, 163 +Selection length: 96, text: 123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com www.bar.com + +Making selection: 4, 75, 4, 238 +Selection length: 171, text: 123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com www.bar.com www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z + +Making selection: 4, 75, 4, 239 +Selection length: 180, text: 123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com www.bar.com www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789 + +Making selection: 4, 75, 4, 313 +Selection length: 254, text: 123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com www.bar.com www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com + +Making selection: 4, 76, 4, 163 +Selection length: 87, text: z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com www.bar.com + +Making selection: 4, 76, 4, 238 +Selection length: 162, text: z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com www.bar.com www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z + +Making selection: 4, 76, 4, 239 +Selection length: 171, text: z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com www.bar.com www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789 + +Making selection: 4, 76, 4, 313 +Selection length: 245, text: z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com www.bar.com www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com + +Making selection: 4, 163, 4, 238 +Selection length: 75, text: www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z + +Making selection: 4, 163, 4, 239 +Selection length: 84, text: www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789 + +Making selection: 4, 163, 4, 313 +Selection length: 158, text: www.z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com + +Making selection: 4, 238, 4, 239 +Selection length: 9, text: 123456789 + +Making selection: 4, 238, 4, 313 +Selection length: 83, text: 123456789z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com + +Making selection: 4, 239, 4, 313 +Selection length: 74, text: z123456789z123456789z123456789z123456789z123456789z123456789z123456789.com + +Running: testSelectWithinShortUrlWithHashes + +Making selection: 5, 0, 5, 14 +Selection length: 26, text: www.01234567890123456789zf + +Making selection: 5, 0, 5, 28 +Selection length: 52, text: www.01234567890123456789zfoobarz01234567890123456789 + +Making selection: 5, 14, 5, 28 +Selection length: 26, text: oobarz01234567890123456789 + +Running: testSelectWithinUrlWithHashes + +Making selection: 6, 0, 6, 63 +Selection length: 75, text: www.01234567890123456789zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + +Making selection: 6, 0, 6, 64 +Selection length: 120, text: www.01234567890123456789zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + +Making selection: 6, 0, 6, 126 +Selection length: 194, text: www.01234567890123456789zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz01234567890123456789 + +Making selection: 6, 63, 6, 64 +Selection length: 45, text: zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + +Making selection: 6, 63, 6, 126 +Selection length: 119, text: zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz01234567890123456789 + +Making selection: 6, 64, 6, 126 +Selection length: 74, text: zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz01234567890123456789 + +Running: testSelectWithinHighlightedUrlBeginning +Searching for text: www. +Highlighted 18 matches + +Making selection: 7, 0, 7, 75 +Selection length: 75, text: www.zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + +Making selection: 7, 0, 7, 76 +Selection length: 134, text: www.zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + +Making selection: 7, 0, 7, 150 +Selection length: 208, text: www.zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.com + +Making selection: 7, 75, 7, 76 +Selection length: 59, text: zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + +Making selection: 7, 75, 7, 150 +Selection length: 133, text: zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.com + +Making selection: 7, 76, 7, 150 +Selection length: 74, text: zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.com + +Running: testSelectWithinHighlightedUrlMiddle +Searching for text: zzzzz +Highlighted 118 matches + +Making selection: 7, 0, 7, 75 +Selection length: 75, text: www.zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + +Making selection: 7, 0, 7, 76 +Selection length: 134, text: www.zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + +Making selection: 7, 0, 7, 150 +Selection length: 208, text: www.zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.com + +Making selection: 7, 75, 7, 76 +Selection length: 59, text: zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + +Making selection: 7, 75, 7, 150 +Selection length: 133, text: zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.com + +Making selection: 7, 76, 7, 150 +Selection length: 74, text: zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.com + +Running: testSelectWithinHighlightedUrlEnd +Searching for text: .com +Highlighted 14 matches + +Making selection: 7, 0, 7, 75 +Selection length: 75, text: www.zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + +Making selection: 7, 0, 7, 76 +Selection length: 134, text: www.zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + +Making selection: 7, 0, 7, 150 +Selection length: 208, text: www.zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.com + +Making selection: 7, 75, 7, 76 +Selection length: 59, text: zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + +Making selection: 7, 75, 7, 150 +Selection length: 133, text: zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.com + +Making selection: 7, 76, 7, 150 +Selection length: 74, text: zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.com +
diff --git a/third_party/WebKit/LayoutTests/inspector/console/console-copy-truncated-text.html b/third_party/WebKit/LayoutTests/inspector/console/console-copy-truncated-text.html new file mode 100644 index 0000000..6cd9529 --- /dev/null +++ b/third_party/WebKit/LayoutTests/inspector/console/console-copy-truncated-text.html
@@ -0,0 +1,198 @@ +<html> +<head> +<script src="../../http/tests/inspector/inspector-test.js"></script> +<script src="../../http/tests/inspector/console-test.js"></script> +<script> + +function test() +{ + var longUrl = "www." + "z123456789".repeat(15) + ".com"; + var shortUrl = "www.bar.com"; + var mixedUrl = longUrl + " " + shortUrl + " " + longUrl; + var shortUrlWithHashes = "www." + "0123456789".repeat(2) + "zfoobarz" + "0123456789".repeat(2); + var urlWithHashes = "www." + "0123456789".repeat(2) + "z".repeat(150) + "0123456789".repeat(2); + var highlightedUrl = "www." + "z".repeat(200) + ".com"; + var prepareCode = ` + // Keep this as the first url logged to record the max truncated length. + console.log("${longUrl}"); + + console.log("${shortUrl}"); + console.log("${longUrl}"); + console.log("${mixedUrl}"); + console.log("${shortUrlWithHashes}"); + console.log("${urlWithHashes}"); + console.log("${highlightedUrl}"); + `; + + var expectedMessageCount = 8; + var consoleView = Console.ConsoleView.instance(); + var viewport = Console.ConsoleView.instance()._viewport; + var maxLength; + var halfMaxLength; + var secondLongUrlIndexInMixedUrl; + + var tests = [ + function testSelectWithinTruncatedUrl(next) + { + makeSelectionAndDump(1, 0, 1, halfMaxLength); + makeSelectionAndDump(1, 0, 1, halfMaxLength + 1); + makeSelectionAndDump(1, 0, 1, maxLength); + makeSelectionAndDump(1, halfMaxLength, 1, halfMaxLength + 1); + makeSelectionAndDump(1, halfMaxLength, 1, maxLength); + makeSelectionAndDump(1, halfMaxLength + 1, 1, maxLength); + next(); + }, + + function testSelectAcrossMultipleMessages(next) { + makeSelectionAndDump(1, 0, 2, shortUrl.length); + makeSelectionAndDump(1, halfMaxLength, 2, shortUrl.length); + makeSelectionAndDump(1, halfMaxLength + 1, 2, shortUrl.length); + next() + }, + + function testSelectAcrossMultipleMessagesWithTruncatedUrls(next) { + makeSelectionAndDump(1, 0, 3, halfMaxLength); + makeSelectionAndDump(1, 0, 3, halfMaxLength + 1); + makeSelectionAndDump(1, 0, 3, maxLength); + next() + }, + + function testSelectWithinMessageWithMultipleTruncatedUrls(next) { + makeSelectionAndDump(4, 0, 4, halfMaxLength); + makeSelectionAndDump(4, 0, 4, halfMaxLength + 1); + makeSelectionAndDump(4, 0, 4, secondLongUrlIndexInMixedUrl); + makeSelectionAndDump(4, 0, 4, secondLongUrlIndexInMixedUrl + halfMaxLength); + makeSelectionAndDump(4, 0, 4, secondLongUrlIndexInMixedUrl + halfMaxLength + 1); + makeSelectionAndDump(4, 0, 4, secondLongUrlIndexInMixedUrl + maxLength); + + makeSelectionAndDump(4, halfMaxLength, 4, halfMaxLength + 1); + makeSelectionAndDump(4, halfMaxLength, 4, secondLongUrlIndexInMixedUrl); + makeSelectionAndDump(4, halfMaxLength, 4, secondLongUrlIndexInMixedUrl + halfMaxLength); + makeSelectionAndDump(4, halfMaxLength, 4, secondLongUrlIndexInMixedUrl + halfMaxLength + 1); + makeSelectionAndDump(4, halfMaxLength, 4, secondLongUrlIndexInMixedUrl + maxLength); + + makeSelectionAndDump(4, halfMaxLength + 1, 4, secondLongUrlIndexInMixedUrl); + makeSelectionAndDump(4, halfMaxLength + 1, 4, secondLongUrlIndexInMixedUrl + halfMaxLength); + makeSelectionAndDump(4, halfMaxLength + 1, 4, secondLongUrlIndexInMixedUrl + halfMaxLength + 1); + makeSelectionAndDump(4, halfMaxLength + 1, 4, secondLongUrlIndexInMixedUrl + maxLength); + + makeSelectionAndDump(4, secondLongUrlIndexInMixedUrl, 4, secondLongUrlIndexInMixedUrl + halfMaxLength); + makeSelectionAndDump(4, secondLongUrlIndexInMixedUrl, 4, secondLongUrlIndexInMixedUrl + halfMaxLength + 1); + makeSelectionAndDump(4, secondLongUrlIndexInMixedUrl, 4, secondLongUrlIndexInMixedUrl + maxLength); + + makeSelectionAndDump(4, secondLongUrlIndexInMixedUrl + halfMaxLength, 4, secondLongUrlIndexInMixedUrl + halfMaxLength + 1); + makeSelectionAndDump(4, secondLongUrlIndexInMixedUrl + halfMaxLength, 4, secondLongUrlIndexInMixedUrl + maxLength); + + makeSelectionAndDump(4, secondLongUrlIndexInMixedUrl + halfMaxLength + 1, 4, secondLongUrlIndexInMixedUrl + maxLength); + next() + }, + + function testSelectWithinShortUrlWithHashes(next) + { + var hashedUrlMaxLength = consoleMessageText(5).length; + var hashedUrlHalfMaxLength = Math.ceil(hashedUrlMaxLength / 2); + makeSelectionAndDump(5, 0, 5, hashedUrlHalfMaxLength); + makeSelectionAndDump(5, 0, 5, hashedUrlMaxLength); + makeSelectionAndDump(5, hashedUrlHalfMaxLength, 5, hashedUrlMaxLength); + next(); + }, + + function testSelectWithinUrlWithHashes(next) + { + var hashedUrlMaxLength = consoleMessageText(6).length; + var hashedUrlHalfMaxLength = Math.ceil(hashedUrlMaxLength / 2); + makeSelectionAndDump(6, 0, 6, hashedUrlHalfMaxLength); + makeSelectionAndDump(6, 0, 6, hashedUrlHalfMaxLength + 1); + makeSelectionAndDump(6, 0, 6, hashedUrlMaxLength); + makeSelectionAndDump(6, hashedUrlHalfMaxLength, 6, hashedUrlHalfMaxLength + 1); + makeSelectionAndDump(6, hashedUrlHalfMaxLength, 6, hashedUrlMaxLength); + makeSelectionAndDump(6, hashedUrlHalfMaxLength + 1, 6, hashedUrlMaxLength); + next(); + }, + + function testSelectWithinHighlightedUrlBeginning(next) { + testHighlightedUrlWithSearchQuery("www.", next); + }, + + function testSelectWithinHighlightedUrlMiddle(next) { + testHighlightedUrlWithSearchQuery("zzzzz", next); + }, + + function testSelectWithinHighlightedUrlEnd(next) { + testHighlightedUrlWithSearchQuery(".com", next); + } + ]; + + InspectorTest.waitForConsoleMessages(expectedMessageCount, () => { + viewport.invalidate(); + + // Get the max truncated length from the first longUrl logged. + try { + var longUrlMessageText = consoleMessageText(1); + maxLength = longUrlMessageText.length; + halfMaxLength = Math.ceil(maxLength / 2); + secondLongUrlIndexInMixedUrl = maxLength + 1 + shortUrl.length + 1; + InspectorTest.addResult("Long url has max length: " + maxLength + ", text: " + longUrlMessageText); + } catch (e) { + InspectorTest.addResult("FAIL: Could not get max truncation length from first longUrl message."); + InspectorTest.completeTest(); + return; + } + InspectorTest.runTestSuite(tests); + }); + InspectorTest.evaluateInConsole(prepareCode); + + function consoleMessageText(index) { + var messageElement = consoleView._visibleViewMessages[index].element(); + return messageElement.querySelector('.console-message-text').deepTextContent(); + } + + function makeSelectionAndDump(fromMessage, fromTextOffset, toMessage, toTextOffset, includeAnchor, useTextContainer) { + InspectorTest.addResult("\nMaking selection: " + fromMessage + ", " + fromTextOffset + ", " + toMessage + ", " + toTextOffset); + + // Ignore the anchor text on the start/end message, just use their contents. + if (!includeAnchor) { + var fromAnchor = consoleView.itemElement(fromMessage).element().querySelector('.console-message-anchor'); + var toAnchor = consoleView.itemElement(toMessage).element().querySelector('.console-message-anchor'); + fromTextOffset += fromAnchor ? fromAnchor.deepTextContent().length : 0; + toTextOffset += toAnchor ? toAnchor.deepTextContent().length : 0; + } + InspectorTest.selectConsoleMessages(fromMessage, fromTextOffset, toMessage, toTextOffset, useTextContainer); + var selectedText = viewport._selectedText(); + if (selectedText) + InspectorTest.addResult("Selection length: " + selectedText.length + ", " + "text: " + selectedText.replace(/\bVM\d+/g, "VM")); + else + InspectorTest.addResult("No selection"); + } + + function testHighlightedUrlWithSearchQuery(query, next) { + // Clear any existing ranges to avoid using them as the query. + window.getSelection().removeAllRanges(); + InspectorTest.addSniffer(consoleView, "_searchFinishedForTests", onSearch); + consoleView._searchableView._searchInputElement.value = query; + consoleView._searchableView._regexButton.setToggled(false); + consoleView._searchableView._caseSensitiveButton.setToggled(false); + consoleView._searchableView.showSearchField(); + InspectorTest.addResult("Searching for text: " + query); + + function onSearch() { + var matches = consoleView.element.childTextNodes().filter(node => node.parentElement.classList.contains("highlighted-search-result")).map(node => node.parentElement); + InspectorTest.addResult("Highlighted " + matches.length + " matches"); + + // Use TextNodes for containers to get inside the highlighted match element. + makeSelectionAndDump(7, 0, 7, halfMaxLength, false, true); + makeSelectionAndDump(7, 0, 7, halfMaxLength + 1, false, true); + makeSelectionAndDump(7, 0, 7, maxLength, false, true); + makeSelectionAndDump(7, halfMaxLength, 7, halfMaxLength + 1); + makeSelectionAndDump(7, halfMaxLength, 7, maxLength); + makeSelectionAndDump(7, halfMaxLength + 1, 7, maxLength); + next(); + } + } +} +</script> +</head> +<body onload="runTest()"> +<p>Tests that console copies tree outline messages properly.</p> +</body> +</html>
diff --git a/third_party/WebKit/LayoutTests/inspector/console/console-viewport-selection.html b/third_party/WebKit/LayoutTests/inspector/console/console-viewport-selection.html index 83909bd6..f6a6e66e 100644 --- a/third_party/WebKit/LayoutTests/inspector/console/console-viewport-selection.html +++ b/third_party/WebKit/LayoutTests/inspector/console/console-viewport-selection.html
@@ -259,23 +259,6 @@ viewport.refresh(); } - function selectionContainerAndOffset(container, offset) - { - var charCount = 0; - var node = container; - while (node = node.traverseNextTextNode(true)) { - var length = node.textContent.length; - if (charCount + length >= offset) { - return { - container: node, - offset: offset - charCount - }; - } - charCount += length; - } - return null; - } - function selectMessages(fromMessage, fromTextOffset, toMessage, toTextOffset) { if (Math.abs(toMessage - fromMessage) > minimumViewportMessagesCount) { @@ -285,9 +268,7 @@ } viewport.forceScrollItemToBeFirst(Math.min(fromMessage, toMessage)); - var from = selectionContainerAndOffset(consoleView.itemElement(fromMessage).element(), fromTextOffset); - var to = selectionContainerAndOffset(consoleView.itemElement(toMessage).element(), toTextOffset); - window.getSelection().setBaseAndExtent(from.container, from.offset, to.container, to.offset); + InspectorTest.selectConsoleMessages(fromMessage, fromTextOffset, toMessage, toTextOffset); viewport.refresh(); } }
diff --git a/third_party/WebKit/LayoutTests/inspector/console/resources/worker-pause.js b/third_party/WebKit/LayoutTests/inspector/console/resources/worker-pause.js new file mode 100644 index 0000000..b1f08070 --- /dev/null +++ b/third_party/WebKit/LayoutTests/inspector/console/resources/worker-pause.js
@@ -0,0 +1 @@ +self.onmessage = function(e) { debugger; }
diff --git a/third_party/WebKit/LayoutTests/tables/OWNERS b/third_party/WebKit/LayoutTests/tables/OWNERS index 261aa8c..8c39d70 100644 --- a/third_party/WebKit/LayoutTests/tables/OWNERS +++ b/third_party/WebKit/LayoutTests/tables/OWNERS
@@ -1 +1,2 @@ # TEAM: layout-dev@chromium.org +# COMPONENT: Blink>Layout>Table
diff --git a/third_party/WebKit/LayoutTests/tables/mozilla/bugs/OWNERS b/third_party/WebKit/LayoutTests/tables/mozilla/bugs/OWNERS deleted file mode 100644 index 261aa8c..0000000 --- a/third_party/WebKit/LayoutTests/tables/mozilla/bugs/OWNERS +++ /dev/null
@@ -1 +0,0 @@ -# TEAM: layout-dev@chromium.org
diff --git a/third_party/WebKit/LayoutTests/tables/mozilla/collapsing_borders/OWNERS b/third_party/WebKit/LayoutTests/tables/mozilla/collapsing_borders/OWNERS deleted file mode 100644 index 261aa8c..0000000 --- a/third_party/WebKit/LayoutTests/tables/mozilla/collapsing_borders/OWNERS +++ /dev/null
@@ -1 +0,0 @@ -# TEAM: layout-dev@chromium.org
diff --git a/third_party/WebKit/LayoutTests/tables/mozilla/core/OWNERS b/third_party/WebKit/LayoutTests/tables/mozilla/core/OWNERS deleted file mode 100644 index 261aa8c..0000000 --- a/third_party/WebKit/LayoutTests/tables/mozilla/core/OWNERS +++ /dev/null
@@ -1 +0,0 @@ -# TEAM: layout-dev@chromium.org
diff --git a/third_party/WebKit/LayoutTests/tables/mozilla/dom/OWNERS b/third_party/WebKit/LayoutTests/tables/mozilla/dom/OWNERS deleted file mode 100644 index 261aa8c..0000000 --- a/third_party/WebKit/LayoutTests/tables/mozilla/dom/OWNERS +++ /dev/null
@@ -1 +0,0 @@ -# TEAM: layout-dev@chromium.org
diff --git a/third_party/WebKit/LayoutTests/tables/mozilla/marvin/OWNERS b/third_party/WebKit/LayoutTests/tables/mozilla/marvin/OWNERS deleted file mode 100644 index 261aa8c..0000000 --- a/third_party/WebKit/LayoutTests/tables/mozilla/marvin/OWNERS +++ /dev/null
@@ -1 +0,0 @@ -# TEAM: layout-dev@chromium.org
diff --git a/third_party/WebKit/LayoutTests/tables/mozilla/other/OWNERS b/third_party/WebKit/LayoutTests/tables/mozilla/other/OWNERS deleted file mode 100644 index 261aa8c..0000000 --- a/third_party/WebKit/LayoutTests/tables/mozilla/other/OWNERS +++ /dev/null
@@ -1 +0,0 @@ -# TEAM: layout-dev@chromium.org
diff --git a/third_party/WebKit/LayoutTests/tables/mozilla_expected_failures/bugs/OWNERS b/third_party/WebKit/LayoutTests/tables/mozilla_expected_failures/bugs/OWNERS deleted file mode 100644 index 261aa8c..0000000 --- a/third_party/WebKit/LayoutTests/tables/mozilla_expected_failures/bugs/OWNERS +++ /dev/null
@@ -1 +0,0 @@ -# TEAM: layout-dev@chromium.org
diff --git a/third_party/WebKit/LayoutTests/tables/mozilla_expected_failures/collapsing_borders/OWNERS b/third_party/WebKit/LayoutTests/tables/mozilla_expected_failures/collapsing_borders/OWNERS deleted file mode 100644 index 261aa8c..0000000 --- a/third_party/WebKit/LayoutTests/tables/mozilla_expected_failures/collapsing_borders/OWNERS +++ /dev/null
@@ -1 +0,0 @@ -# TEAM: layout-dev@chromium.org
diff --git a/third_party/WebKit/LayoutTests/tables/mozilla_expected_failures/core/OWNERS b/third_party/WebKit/LayoutTests/tables/mozilla_expected_failures/core/OWNERS deleted file mode 100644 index 261aa8c..0000000 --- a/third_party/WebKit/LayoutTests/tables/mozilla_expected_failures/core/OWNERS +++ /dev/null
@@ -1 +0,0 @@ -# TEAM: layout-dev@chromium.org
diff --git a/third_party/WebKit/LayoutTests/tables/mozilla_expected_failures/dom/OWNERS b/third_party/WebKit/LayoutTests/tables/mozilla_expected_failures/dom/OWNERS deleted file mode 100644 index 261aa8c..0000000 --- a/third_party/WebKit/LayoutTests/tables/mozilla_expected_failures/dom/OWNERS +++ /dev/null
@@ -1 +0,0 @@ -# TEAM: layout-dev@chromium.org
diff --git a/third_party/WebKit/LayoutTests/tables/mozilla_expected_failures/marvin/OWNERS b/third_party/WebKit/LayoutTests/tables/mozilla_expected_failures/marvin/OWNERS deleted file mode 100644 index 261aa8c..0000000 --- a/third_party/WebKit/LayoutTests/tables/mozilla_expected_failures/marvin/OWNERS +++ /dev/null
@@ -1 +0,0 @@ -# TEAM: layout-dev@chromium.org
diff --git a/third_party/WebKit/LayoutTests/tables/mozilla_expected_failures/other/OWNERS b/third_party/WebKit/LayoutTests/tables/mozilla_expected_failures/other/OWNERS deleted file mode 100644 index 261aa8c..0000000 --- a/third_party/WebKit/LayoutTests/tables/mozilla_expected_failures/other/OWNERS +++ /dev/null
@@ -1 +0,0 @@ -# TEAM: layout-dev@chromium.org
diff --git a/third_party/WebKit/Source/bindings/modules/v8/V8BindingForModules.cpp b/third_party/WebKit/Source/bindings/modules/v8/V8BindingForModules.cpp index 2c84b44..7d45f46 100644 --- a/third_party/WebKit/Source/bindings/modules/v8/V8BindingForModules.cpp +++ b/third_party/WebKit/Source/bindings/modules/v8/V8BindingForModules.cpp
@@ -93,7 +93,7 @@ switch (key->GetType()) { case IDBKey::kInvalidType: case IDBKey::kTypeEnumMax: - ASSERT_NOT_REACHED(); + NOTREACHED(); return V8Undefined(); case IDBKey::kNumberType: return v8::Number::New(isolate, key->Number());
diff --git a/third_party/WebKit/Source/build/scripts/make_css_property_apis.py b/third_party/WebKit/Source/build/scripts/make_css_property_apis.py index c092ae9..d94aa915 100755 --- a/third_party/WebKit/Source/build/scripts/make_css_property_apis.py +++ b/third_party/WebKit/Source/build/scripts/make_css_property_apis.py
@@ -94,11 +94,11 @@ self._invalid_descriptor_index = 0 # Initialize the whole thing to the invalid descriptor to handle gaps num_indices = self.last_unresolved_property_id + 1 - self._descriptor_indices = [self._invalid_descriptor_index] * num_indices + self._descriptor_indices = dict.fromkeys(xrange(num_indices), {'id': self._invalid_descriptor_index, 'api': None}) # Now populate all entries for which there exists a class, i.e. that aren't gaps for api_class in self._api_classes: for property_enum in property_enums_for_class[api_class.classname]: - self._descriptor_indices[property_enum] = api_class.index + self._descriptor_indices[property_enum] = {'id': api_class.index, 'api': api_class.classname} @template_expander.use_jinja('CSSPropertyDescriptor.cpp.tmpl') def generate_property_descriptor_cpp(self):
diff --git a/third_party/WebKit/Source/build/scripts/templates/CSSPropertyDescriptor.cpp.tmpl b/third_party/WebKit/Source/build/scripts/templates/CSSPropertyDescriptor.cpp.tmpl index b78f752..7903c3b 100644 --- a/third_party/WebKit/Source/build/scripts/templates/CSSPropertyDescriptor.cpp.tmpl +++ b/third_party/WebKit/Source/build/scripts/templates/CSSPropertyDescriptor.cpp.tmpl
@@ -1,3 +1,4 @@ +{% from 'macros.tmpl' import license, print_if %} // 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. @@ -45,8 +46,8 @@ // Lookup table mapping CSSPropertyID to index in the cssPropertyDescriptors // table static size_t CSSDescriptorIndices[] = { - {% for id in descriptor_indices %} - {{id}}, + {% for item in descriptor_indices %} + {{descriptor_indices[item].id}},{{print_if(descriptor_indices[item].api != None, ' // ' ~ descriptor_indices[item].api)}} {% endfor %} };
diff --git a/third_party/WebKit/Source/build/scripts/templates/StyleBuilder.cpp.tmpl b/third_party/WebKit/Source/build/scripts/templates/StyleBuilder.cpp.tmpl index f615e4d4..0d38a409 100644 --- a/third_party/WebKit/Source/build/scripts/templates/StyleBuilder.cpp.tmpl +++ b/third_party/WebKit/Source/build/scripts/templates/StyleBuilder.cpp.tmpl
@@ -50,7 +50,7 @@ {% endfor %} return; default: - ASSERT_NOT_REACHED(); + NOTREACHED(); } }
diff --git a/third_party/WebKit/Source/core/clipboard/DataObject.cpp b/third_party/WebKit/Source/core/clipboard/DataObject.cpp index 3a28e5b0..68e324a 100644 --- a/third_party/WebKit/Source/core/clipboard/DataObject.cpp +++ b/third_party/WebKit/Source/core/clipboard/DataObject.cpp
@@ -168,7 +168,7 @@ void DataObject::SetData(const String& type, const String& data) { ClearData(type); if (!Add(data, type)) - ASSERT_NOT_REACHED(); + NOTREACHED(); } void DataObject::UrlAndTitle(String& url, String* title) const { @@ -355,13 +355,13 @@ item.string_data = file->name(); } } else { - ASSERT_NOT_REACHED(); + NOTREACHED(); } } else { - ASSERT_NOT_REACHED(); + NOTREACHED(); } } else { - ASSERT_NOT_REACHED(); + NOTREACHED(); } item_list[i] = item; }
diff --git a/third_party/WebKit/Source/core/clipboard/DataTransferItem.cpp b/third_party/WebKit/Source/core/clipboard/DataTransferItem.cpp index 902cde3..46bde54 100644 --- a/third_party/WebKit/Source/core/clipboard/DataTransferItem.cpp +++ b/third_party/WebKit/Source/core/clipboard/DataTransferItem.cpp
@@ -59,7 +59,7 @@ case DataObjectItem::kFileKind: return kind_file; } - ASSERT_NOT_REACHED(); + NOTREACHED(); return String(); }
diff --git a/third_party/WebKit/Source/core/css/ComputedStyleCSSValueMapping.cpp b/third_party/WebKit/Source/core/css/ComputedStyleCSSValueMapping.cpp index a01d69e..9d4e5c14 100644 --- a/third_party/WebKit/Source/core/css/ComputedStyleCSSValueMapping.cpp +++ b/third_party/WebKit/Source/core/css/ComputedStyleCSSValueMapping.cpp
@@ -179,7 +179,7 @@ return CSSIdentifierValue::Create(CSSValueLuminance); } - ASSERT_NOT_REACHED(); + NOTREACHED(); return nullptr; } @@ -284,7 +284,7 @@ (layout_box->OffsetHeight() + client_offset.Height()); break; default: - ASSERT_NOT_REACHED(); + NOTREACHED(); } return ZoomAdjustedPixelValue(position, style); }
diff --git a/third_party/WebKit/Source/core/css/resolver/FontBuilder.cpp b/third_party/WebKit/Source/core/css/resolver/FontBuilder.cpp index e4bc94b..d62baaf 100644 --- a/third_party/WebKit/Source/core/css/resolver/FontBuilder.cpp +++ b/third_party/WebKit/Source/core/css/resolver/FontBuilder.cpp
@@ -79,7 +79,7 @@ FontDescription::GenericFamilyType generic_family) const { switch (generic_family) { default: - ASSERT_NOT_REACHED(); + NOTREACHED(); case FontDescription::kNoFamily: return AtomicString(); case FontDescription::kStandardFamily: @@ -249,7 +249,7 @@ case kTextOrientationSideways: return FontOrientation::kVerticalRotated; default: - ASSERT_NOT_REACHED(); + NOTREACHED(); return FontOrientation::kVerticalMixed; } }
diff --git a/third_party/WebKit/Source/core/dom/TaskRunnerHelper.cpp b/third_party/WebKit/Source/core/dom/TaskRunnerHelper.cpp index 30c377d..631c5de 100644 --- a/third_party/WebKit/Source/core/dom/TaskRunnerHelper.cpp +++ b/third_party/WebKit/Source/core/dom/TaskRunnerHelper.cpp
@@ -31,24 +31,24 @@ // TODO(nhiroki): Throttle them again after we're convinced that it's safe // or provide a mechanism that web pages can opt-out it if throttling is not // desirable. - case TaskType::kDOMManipulation: case TaskType::kDatabaseAccess: - case TaskType::kFileReading: - case TaskType::kHistoryTraversal: - case TaskType::kPerformanceTimeline: - case TaskType::kPostedMessage: - case TaskType::kRemoteEvent: - case TaskType::kSensor: - case TaskType::kUnshippedPortMessage: - case TaskType::kWebSocket: return frame ? frame->FrameScheduler()->SuspendableTaskRunner() : Platform::Current()->CurrentThread()->GetWebTaskRunner(); + case TaskType::kDOMManipulation: case TaskType::kUserInteraction: + case TaskType::kHistoryTraversal: case TaskType::kEmbed: case TaskType::kMediaElementEvent: case TaskType::kCanvasBlobSerialization: + case TaskType::kRemoteEvent: + case TaskType::kWebSocket: case TaskType::kMicrotask: + case TaskType::kPostedMessage: + case TaskType::kUnshippedPortMessage: + case TaskType::kFileReading: case TaskType::kPresentation: + case TaskType::kSensor: + case TaskType::kPerformanceTimeline: case TaskType::kWebGL: case TaskType::kUnspecedTimer: case TaskType::kMiscPlatformAPI:
diff --git a/third_party/WebKit/Source/core/frame/FrameView.cpp b/third_party/WebKit/Source/core/frame/FrameView.cpp index 0a34de2e..9200ecf 100644 --- a/third_party/WebKit/Source/core/frame/FrameView.cpp +++ b/third_party/WebKit/Source/core/frame/FrameView.cpp
@@ -1362,7 +1362,7 @@ CheckDoesNotNeedLayout(); } -void FrameView::InvalidateTreeIfNeeded( +void FrameView::DeprecatedInvalidateTree( const PaintInvalidationState& paint_invalidation_state) { DCHECK(!RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled()); @@ -1379,7 +1379,8 @@ root_for_paint_invalidation.DebugName().Ascii()); InvalidatePaint(paint_invalidation_state); - root_for_paint_invalidation.InvalidateTreeIfNeeded(paint_invalidation_state); + root_for_paint_invalidation.DeprecatedInvalidateTree( + paint_invalidation_state); #if DCHECK_IS_ON() GetLayoutView()->AssertSubtreeClearedPaintInvalidationFlags(); @@ -3149,7 +3150,7 @@ if (target_state >= DocumentLifecycle::kPrePaintClean) { if (!RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled()) - InvalidateTreeIfNeededRecursive(); + DeprecatedInvalidateTreeRecursive(); if (!RuntimeEnabledFeatures::slimmingPaintV2Enabled()) { if (view.Compositor()->InCompositingMode()) @@ -3442,16 +3443,16 @@ GetFrame().GetPage()->GetDragCaret().UpdateStyleAndLayoutIfNeeded(); } -void FrameView::InvalidateTreeIfNeededRecursive() { +void FrameView::DeprecatedInvalidateTreeRecursive() { SCOPED_BLINK_UMA_HISTOGRAM_TIMER("Blink.PaintInvalidation.UpdateTime"); { // For comparison to SlimmingPaintInvalidation. SCOPED_BLINK_UMA_HISTOGRAM_TIMER("Blink.PrePaint.UpdateTime"); - InvalidateTreeIfNeededRecursiveInternal(); + DeprecatedInvalidateTreeRecursiveInternal(); } } -void FrameView::InvalidateTreeIfNeededRecursiveInternal() { +void FrameView::DeprecatedInvalidateTreeRecursiveInternal() { DCHECK(!RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled()); CHECK(GetLayoutView()); @@ -3466,9 +3467,9 @@ *GetLayoutView(), pending_delayed_paint_invalidations); if (Lifecycle().GetState() < DocumentLifecycle::kPaintInvalidationClean) - InvalidateTreeIfNeeded(root_paint_invalidation_state); + DeprecatedInvalidateTree(root_paint_invalidation_state); - // Some frames may be not reached during the above invalidateTreeIfNeeded + // Some frames may be not reached during the above DeprecatedInvalidateTree // because // - the frame is a detached frame; or // - it didn't need paint invalidation. @@ -3483,7 +3484,7 @@ // invalidation onto them here. if (!child_frame_view.GetLayoutView()) continue; - child_frame_view.InvalidateTreeIfNeededRecursiveInternal(); + child_frame_view.DeprecatedInvalidateTreeRecursiveInternal(); } }
diff --git a/third_party/WebKit/Source/core/frame/FrameView.h b/third_party/WebKit/Source/core/frame/FrameView.h index 80a588093..71129095 100644 --- a/third_party/WebKit/Source/core/frame/FrameView.h +++ b/third_party/WebKit/Source/core/frame/FrameView.h
@@ -893,7 +893,7 @@ }; // Only for LayoutPart to traverse into sub frames during paint invalidation. - void InvalidateTreeIfNeeded(const PaintInvalidationState&); + void DeprecatedInvalidateTree(const PaintInvalidationState&); private: explicit FrameView(LocalFrame&, IntRect); @@ -927,7 +927,7 @@ void UpdateLifecyclePhasesInternal( DocumentLifecycle::LifecycleState target_state); - void InvalidateTreeIfNeededRecursive(); + void DeprecatedInvalidateTreeRecursive(); void ScrollContentsIfNeededRecursive(); void UpdateStyleAndLayoutIfNeededRecursive(); void PrePaint(); @@ -935,7 +935,7 @@ void PaintGraphicsLayerRecursively(GraphicsLayer*); void UpdateStyleAndLayoutIfNeededRecursiveInternal(); - void InvalidateTreeIfNeededRecursiveInternal(); + void DeprecatedInvalidateTreeRecursiveInternal(); // TODO(wangxianzhu): Remove the parameter and use m_paintController for SPv2. void NotifyPaint(const PaintController&) const;
diff --git a/third_party/WebKit/Source/core/imagebitmap/ImageBitmapFactories.cpp b/third_party/WebKit/Source/core/imagebitmap/ImageBitmapFactories.cpp index a3c4e95..482ec20 100644 --- a/third_party/WebKit/Source/core/imagebitmap/ImageBitmapFactories.cpp +++ b/third_party/WebKit/Source/core/imagebitmap/ImageBitmapFactories.cpp
@@ -105,7 +105,7 @@ return value.getAsImageBitmap(); if (value.isOffscreenCanvas()) return value.getAsOffscreenCanvas(); - ASSERT_NOT_REACHED(); + NOTREACHED(); return nullptr; }
diff --git a/third_party/WebKit/Source/core/input/EventHandler.cpp b/third_party/WebKit/Source/core/input/EventHandler.cpp index 4d9a51e..af01d1b 100644 --- a/third_party/WebKit/Source/core/input/EventHandler.cpp +++ b/third_party/WebKit/Source/core/input/EventHandler.cpp
@@ -1755,7 +1755,7 @@ *hit_test_result, adjusted_point, adjusted_node); break; default: - ASSERT_NOT_REACHED(); + NOTREACHED(); } // Update the hit-test result to be a point-based result instead of a
diff --git a/third_party/WebKit/Source/core/input/TouchEventManager.cpp b/third_party/WebKit/Source/core/input/TouchEventManager.cpp index 99328b9..e1015508 100644 --- a/third_party/WebKit/Source/core/input/TouchEventManager.cpp +++ b/third_party/WebKit/Source/core/input/TouchEventManager.cpp
@@ -50,7 +50,7 @@ case WebTouchPoint::kStateStationary: // Fall through to default default: - ASSERT_NOT_REACHED(); + NOTREACHED(); return g_empty_atom; } }
diff --git a/third_party/WebKit/Source/core/inspector/InspectorCSSAgent.cpp b/third_party/WebKit/Source/core/inspector/InspectorCSSAgent.cpp index 8453a752..581e0a26 100644 --- a/third_party/WebKit/Source/core/inspector/InspectorCSSAgent.cpp +++ b/third_party/WebKit/Source/core/inspector/InspectorCSSAgent.cpp
@@ -486,7 +486,7 @@ return style_sheet_->SetKeyframeKey(new_range_, old_text_, nullptr, nullptr, exception_state); default: - ASSERT_NOT_REACHED(); + NOTREACHED(); } return false; } @@ -510,7 +510,7 @@ old_range_, new_text_, &new_range_, &old_text_, exception_state); break; default: - ASSERT_NOT_REACHED(); + NOTREACHED(); } return css_rule_; }
diff --git a/third_party/WebKit/Source/core/inspector/InspectorDOMAgent.cpp b/third_party/WebKit/Source/core/inspector/InspectorDOMAgent.cpp index ed98379..4a86b26 100644 --- a/third_party/WebKit/Source/core/inspector/InspectorDOMAgent.cpp +++ b/third_party/WebKit/Source/core/inspector/InspectorDOMAgent.cpp
@@ -1344,7 +1344,7 @@ case ShadowRootType::kClosed: return protocol::DOM::ShadowRootTypeEnum::Closed; } - ASSERT_NOT_REACHED(); + NOTREACHED(); return protocol::DOM::ShadowRootTypeEnum::UserAgent; }
diff --git a/third_party/WebKit/Source/core/inspector/InspectorStyleSheet.cpp b/third_party/WebKit/Source/core/inspector/InspectorStyleSheet.cpp index 5fcefaf..0d713d8 100644 --- a/third_party/WebKit/Source/core/inspector/InspectorStyleSheet.cpp +++ b/third_party/WebKit/Source/core/inspector/InspectorStyleSheet.cpp
@@ -636,7 +636,7 @@ j -= 1; break; default: - ASSERT_NOT_REACHED(); + NOTREACHED(); } }
diff --git a/third_party/WebKit/Source/core/inspector/InspectorTraceEvents.cpp b/third_party/WebKit/Source/core/inspector/InspectorTraceEvents.cpp index 1481a91..752be0b 100644 --- a/third_party/WebKit/Source/core/inspector/InspectorTraceEvents.cpp +++ b/third_party/WebKit/Source/core/inspector/InspectorTraceEvents.cpp
@@ -315,7 +315,7 @@ #undef DEFINE_STRING_MAPPING } - ASSERT_NOT_REACHED(); + NOTREACHED(); return ""; } @@ -568,7 +568,7 @@ CreateQuad(value.get(), "root", quads[0]); SetGeneratingNodeInfo(value.get(), root_for_this_layout, "rootNode"); } else { - ASSERT_NOT_REACHED(); + NOTREACHED(); } return value; }
diff --git a/third_party/WebKit/Source/core/inspector/WorkerThreadDebugger.cpp b/third_party/WebKit/Source/core/inspector/WorkerThreadDebugger.cpp index f0a2cc7..48cffed 100644 --- a/third_party/WebKit/Source/core/inspector/WorkerThreadDebugger.cpp +++ b/third_party/WebKit/Source/core/inspector/WorkerThreadDebugger.cpp
@@ -220,7 +220,7 @@ v8::MaybeLocal<v8::Value> WorkerThreadDebugger::memoryInfo( v8::Isolate*, v8::Local<v8::Context>) { - ASSERT_NOT_REACHED(); + NOTREACHED(); return v8::MaybeLocal<v8::Value>(); }
diff --git a/third_party/WebKit/Source/core/layout/LayoutBlock.cpp b/third_party/WebKit/Source/core/layout/LayoutBlock.cpp index 7817876..496270a 100644 --- a/third_party/WebKit/Source/core/layout/LayoutBlock.cpp +++ b/third_party/WebKit/Source/core/layout/LayoutBlock.cpp
@@ -991,11 +991,6 @@ } PaintInvalidationReason LayoutBlock::InvalidatePaint( - const PaintInvalidationState& paint_invalidation_state) { - return LayoutBox::InvalidatePaint(paint_invalidation_state); -} - -PaintInvalidationReason LayoutBlock::InvalidatePaint( const PaintInvalidatorContext& context) const { return BlockPaintInvalidator(*this).InvalidatePaint(context); }
diff --git a/third_party/WebKit/Source/core/layout/LayoutBlock.h b/third_party/WebKit/Source/core/layout/LayoutBlock.h index 0b77005..e15f132 100644 --- a/third_party/WebKit/Source/core/layout/LayoutBlock.h +++ b/third_party/WebKit/Source/core/layout/LayoutBlock.h
@@ -494,8 +494,6 @@ protected: PaintInvalidationReason InvalidatePaint( - const PaintInvalidationState&) override; - PaintInvalidationReason InvalidatePaint( const PaintInvalidatorContext&) const override; void ClearPreviousVisualRects() override;
diff --git a/third_party/WebKit/Source/core/layout/LayoutBlockFlow.cpp b/third_party/WebKit/Source/core/layout/LayoutBlockFlow.cpp index f7dff3ea..540dbd3 100644 --- a/third_party/WebKit/Source/core/layout/LayoutBlockFlow.cpp +++ b/third_party/WebKit/Source/core/layout/LayoutBlockFlow.cpp
@@ -49,6 +49,7 @@ #include "core/layout/line/InlineIterator.h" #include "core/layout/line/InlineTextBox.h" #include "core/layout/line/LineWidth.h" +#include "core/layout/ng/layout_ng_block_flow.h" #include "core/layout/shapes/ShapeOutsideInfo.h" #include "core/paint/BlockFlowPaintInvalidator.h" #include "core/paint/PaintLayer.h" @@ -256,7 +257,9 @@ LayoutBlockFlow::~LayoutBlockFlow() {} LayoutBlockFlow* LayoutBlockFlow::CreateAnonymous(Document* document) { - LayoutBlockFlow* layout_block_flow = new LayoutBlockFlow(nullptr); + LayoutBlockFlow* layout_block_flow = RuntimeEnabledFeatures::layoutNGEnabled() + ? new LayoutNGBlockFlow(nullptr) + : new LayoutBlockFlow(nullptr); layout_block_flow->SetDocumentForAnonymous(document); return layout_block_flow; } @@ -689,7 +692,6 @@ // mark it for layout. LayoutUnit lowest_float = std::max(previous_float_logical_bottom, LowestFloatLogicalBottom()); - lowest_float = std::max(lowest_float, child.LowestFloatLogicalBottom()); if (lowest_float > new_logical_top) mark_descendants_with_floats = true; } @@ -3715,6 +3717,7 @@ } } + LayoutUnit old_logical_top = child.LogicalTop(); if (child.NeedsLayout()) { if (is_paginated) { // Before we can lay out the float, we need to estimate a position for @@ -3764,6 +3767,17 @@ MarkChildForPaginationRelayoutIfNeeded(child, layout_scope); child.LayoutIfNeeded(); + // If negative margin pushes the child completely out of its old position + // mark for layout siblings that may have it in its float lists. + if (child.LogicalBottom() <= old_logical_top) { + LayoutObject* next = child.NextSibling(); + if (next && next->IsLayoutBlockFlow()) { + LayoutBlockFlow* nextBlock = ToLayoutBlockFlow(next); + if (!nextBlock->AvoidsFloats() || nextBlock->ShrinkToAvoidFloats()) + nextBlock->MarkAllDescendantsWithFloatsForLayout(); + } + } + if (is_paginated) { PaginatedContentWasLaidOut(child.LogicalBottom()); @@ -4647,12 +4661,12 @@ include_block_overflows); } -PaintInvalidationReason LayoutBlockFlow::InvalidatePaint( +PaintInvalidationReason LayoutBlockFlow::DeprecatedInvalidatePaint( const PaintInvalidationState& paint_invalidation_state) { if (ContainsFloats()) paint_invalidation_state.PaintingLayer().SetNeedsPaintPhaseFloat(); - return LayoutBlock::InvalidatePaint(paint_invalidation_state); + return LayoutBlock::DeprecatedInvalidatePaint(paint_invalidation_state); } void LayoutBlockFlow::InvalidateDisplayItemClients(
diff --git a/third_party/WebKit/Source/core/layout/LayoutBlockFlow.h b/third_party/WebKit/Source/core/layout/LayoutBlockFlow.h index 732e23c7..3950925 100644 --- a/third_party/WebKit/Source/core/layout/LayoutBlockFlow.h +++ b/third_party/WebKit/Source/core/layout/LayoutBlockFlow.h
@@ -477,7 +477,7 @@ IncludeBlockVisualOverflowOrNot) const override; bool PaintedOutputOfObjectHasNoEffectRegardlessOfSize() const override; - PaintInvalidationReason InvalidatePaint( + PaintInvalidationReason DeprecatedInvalidatePaint( const PaintInvalidationState&) override; void InvalidateDisplayItemClients(PaintInvalidationReason) const override;
diff --git a/third_party/WebKit/Source/core/layout/LayoutBox.cpp b/third_party/WebKit/Source/core/layout/LayoutBox.cpp index 548907dd..5719e404 100644 --- a/third_party/WebKit/Source/core/layout/LayoutBox.cpp +++ b/third_party/WebKit/Source/core/layout/LayoutBox.cpp
@@ -1885,7 +1885,7 @@ SetShouldDoFullPaintInvalidationWithoutGeometryChange(kPaintInvalidationFull); } -PaintInvalidationReason LayoutBox::InvalidatePaint( +PaintInvalidationReason LayoutBox::DeprecatedInvalidatePaint( const PaintInvalidationState& paint_invalidation_state) { if (HasBoxDecorationBackground() // We also paint overflow controls in background phase. @@ -1895,7 +1895,8 @@ layer.SetNeedsPaintPhaseDescendantBlockBackgrounds(); } - return LayoutBoxModelObject::InvalidatePaint(paint_invalidation_state); + return LayoutBoxModelObject::DeprecatedInvalidatePaint( + paint_invalidation_state); } PaintInvalidationReason LayoutBox::InvalidatePaint(
diff --git a/third_party/WebKit/Source/core/layout/LayoutBox.h b/third_party/WebKit/Source/core/layout/LayoutBox.h index f792d13b..79d3f08 100644 --- a/third_party/WebKit/Source/core/layout/LayoutBox.h +++ b/third_party/WebKit/Source/core/layout/LayoutBox.h
@@ -1469,7 +1469,7 @@ void ComputeSelfHitTestRects(Vector<LayoutRect>&, const LayoutPoint& layer_offset) const override; - PaintInvalidationReason InvalidatePaint( + PaintInvalidationReason DeprecatedInvalidatePaint( const PaintInvalidationState&) override; PaintInvalidationReason InvalidatePaint( const PaintInvalidatorContext&) const override;
diff --git a/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp b/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp index da8607bd..e8aab8d 100644 --- a/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp +++ b/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.cpp
@@ -331,8 +331,7 @@ SetChildNeedsLayout(); CreateLayerAfterStyleChange(); if (Parent() && !NeedsLayout()) { - // FIXME: We should call specialized versions of these functions. - Layer()->UpdateScrollingAfterLayout(); + // FIXME: We should call a specialized versions of this function. Layer()->UpdateLayerPositionsAfterLayout(); } } @@ -550,7 +549,7 @@ } DISABLE_CFI_PERF -void LayoutBoxModelObject::InvalidateTreeIfNeeded( +void LayoutBoxModelObject::DeprecatedInvalidateTree( const PaintInvalidationState& paint_invalidation_state) { DCHECK(!RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled()); EnsureIsReadyForPaintInvalidation(); @@ -569,7 +568,7 @@ LayoutRect previous_visual_rect = VisualRect(); LayoutPoint previous_location = paint_invalidator.LocationInBacking(); PaintInvalidationReason reason = - InvalidatePaint(new_paint_invalidation_state); + DeprecatedInvalidatePaint(new_paint_invalidation_state); if (previous_location != paint_invalidator.LocationInBacking()) { new_paint_invalidation_state @@ -589,7 +588,7 @@ } new_paint_invalidation_state.UpdateForChildren(reason); - InvalidatePaintOfSubtreesIfNeeded(new_paint_invalidation_state); + DeprecatedInvalidatePaintOfSubtrees(new_paint_invalidation_state); ClearPaintInvalidationFlags(); }
diff --git a/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.h b/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.h index bcf3fc4..f3bbba2 100644 --- a/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.h +++ b/third_party/WebKit/Source/core/layout/LayoutBoxModelObject.h
@@ -423,7 +423,7 @@ return false; } - void InvalidateTreeIfNeeded(const PaintInvalidationState&) override; + void DeprecatedInvalidateTree(const PaintInvalidationState&) override; // http://www.w3.org/TR/css3-background/#body-background // <html> root element with no background steals background from its first
diff --git a/third_party/WebKit/Source/core/layout/LayoutObject.cpp b/third_party/WebKit/Source/core/layout/LayoutObject.cpp index ba11080..f9da27f 100644 --- a/third_party/WebKit/Source/core/layout/LayoutObject.cpp +++ b/third_party/WebKit/Source/core/layout/LayoutObject.cpp
@@ -1134,7 +1134,7 @@ dirty_rect, display_item_client); } -void LayoutObject::InvalidateTreeIfNeeded( +void LayoutObject::DeprecatedInvalidateTree( const PaintInvalidationState& paint_invalidation_state) { DCHECK(!RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled()); EnsureIsReadyForPaintInvalidation(); @@ -1154,20 +1154,20 @@ } PaintInvalidationReason reason = - InvalidatePaint(new_paint_invalidation_state); + DeprecatedInvalidatePaint(new_paint_invalidation_state); new_paint_invalidation_state.UpdateForChildren(reason); - InvalidatePaintOfSubtreesIfNeeded(new_paint_invalidation_state); + DeprecatedInvalidatePaintOfSubtrees(new_paint_invalidation_state); ClearPaintInvalidationFlags(); } DISABLE_CFI_PERF -void LayoutObject::InvalidatePaintOfSubtreesIfNeeded( +void LayoutObject::DeprecatedInvalidatePaintOfSubtrees( const PaintInvalidationState& child_paint_invalidation_state) { DCHECK(!RuntimeEnabledFeatures::slimmingPaintInvalidationEnabled()); for (auto* child = SlowFirstChild(); child; child = child->NextSibling()) - child->InvalidateTreeIfNeeded(child_paint_invalidation_state); + child->DeprecatedInvalidateTree(child_paint_invalidation_state); } LayoutRect LayoutObject::SelectionRectInViewCoordinates() const { @@ -1177,7 +1177,7 @@ return selection_rect; } -PaintInvalidationReason LayoutObject::InvalidatePaint( +PaintInvalidationReason LayoutObject::DeprecatedInvalidatePaint( const PaintInvalidationState& paint_invalidation_state) { DCHECK_EQ(&paint_invalidation_state.CurrentObject(), this);
diff --git a/third_party/WebKit/Source/core/layout/LayoutObject.h b/third_party/WebKit/Source/core/layout/LayoutObject.h index 7d808bd..da27779 100644 --- a/third_party/WebKit/Source/core/layout/LayoutObject.h +++ b/third_party/WebKit/Source/core/layout/LayoutObject.h
@@ -267,8 +267,8 @@ LayoutObject* start_point, bool check_parent = true); - // Returns the layer that will paint this object. If possible, use the faster - // PaintInvalidationState::paintingLayer() instead. + // Returns the layer that will paint this object. During paint invalidation, + // we should use the faster PaintInvalidatorContext::painting_layer instead. PaintLayer* PaintingLayer() const; // Scrolling is a LayoutBox concept, however some code just cares about @@ -1389,7 +1389,7 @@ // Walk the tree after layout issuing paint invalidations for layoutObjects // that have changed or moved, updating bounds that have changed, and clearing // paint invalidation state. - virtual void InvalidateTreeIfNeeded(const PaintInvalidationState&); + virtual void DeprecatedInvalidateTree(const PaintInvalidationState&); void SetShouldDoFullPaintInvalidationIncludingNonCompositingDescendants(); @@ -2077,13 +2077,13 @@ // // The function is overridden to handle special children (e.g. percentage // height descendants or reflections). - virtual void InvalidatePaintOfSubtreesIfNeeded( + virtual void DeprecatedInvalidatePaintOfSubtrees( const PaintInvalidationState& child_paint_invalidation_state); // This function generates the invalidation for this object only. // It doesn't recurse into other object, as this is handled by - // invalidatePaintOfSubtreesIfNeeded. - virtual PaintInvalidationReason InvalidatePaint( + // DeprecatedInvalidatePaintOfSubtrees. + virtual PaintInvalidationReason DeprecatedInvalidatePaint( const PaintInvalidationState&); void SetIsBackgroundAttachmentFixedObject(bool);
diff --git a/third_party/WebKit/Source/core/layout/LayoutPart.cpp b/third_party/WebKit/Source/core/layout/LayoutPart.cpp index bf5ba84..5caf854 100644 --- a/third_party/WebKit/Source/core/layout/LayoutPart.cpp +++ b/third_party/WebKit/Source/core/layout/LayoutPart.cpp
@@ -402,7 +402,7 @@ frame_or_plugin.SetFrameRect(frame_rect); } -void LayoutPart::InvalidatePaintOfSubtreesIfNeeded( +void LayoutPart::DeprecatedInvalidatePaintOfSubtrees( const PaintInvalidationState& paint_invalidation_state) { FrameView* frame_view = ChildFrameView(); if (frame_view && !IsThrottledFrameView()) { @@ -413,11 +413,11 @@ LayoutAPIShim::LayoutObjectFrom(frame_view->GetLayoutViewItem()))) { PaintInvalidationState child_view_paint_invalidation_state( paint_invalidation_state, *child_layout_view); - frame_view->InvalidateTreeIfNeeded(child_view_paint_invalidation_state); + frame_view->DeprecatedInvalidateTree(child_view_paint_invalidation_state); } } - LayoutReplaced::InvalidatePaintOfSubtreesIfNeeded(paint_invalidation_state); + LayoutReplaced::DeprecatedInvalidatePaintOfSubtrees(paint_invalidation_state); } bool LayoutPart::IsThrottledFrameView() const {
diff --git a/third_party/WebKit/Source/core/layout/LayoutPart.h b/third_party/WebKit/Source/core/layout/LayoutPart.h index f34be6d..9a87ca1 100644 --- a/third_party/WebKit/Source/core/layout/LayoutPart.h +++ b/third_party/WebKit/Source/core/layout/LayoutPart.h
@@ -77,7 +77,7 @@ CursorDirective GetCursor(const LayoutPoint&, Cursor&) const final; // Overridden to invalidate the child frame if any. - void InvalidatePaintOfSubtreesIfNeeded( + void DeprecatedInvalidatePaintOfSubtrees( const PaintInvalidationState&) override; private:
diff --git a/third_party/WebKit/Source/core/layout/LayoutTable.cpp b/third_party/WebKit/Source/core/layout/LayoutTable.cpp index 817cf19..39a9e325 100644 --- a/third_party/WebKit/Source/core/layout/LayoutTable.cpp +++ b/third_party/WebKit/Source/core/layout/LayoutTable.cpp
@@ -81,8 +81,8 @@ old_style ? old_style->IsFixedTableLayout() : false; // In the collapsed border model, there is no cell spacing. - h_spacing_ = CollapseBorders() ? 0 : Style()->HorizontalBorderSpacing(); - v_spacing_ = CollapseBorders() ? 0 : Style()->VerticalBorderSpacing(); + h_spacing_ = ShouldCollapseBorders() ? 0 : Style()->HorizontalBorderSpacing(); + v_spacing_ = ShouldCollapseBorders() ? 0 : Style()->VerticalBorderSpacing(); effective_column_positions_[0] = h_spacing_; if (!table_layout_ || @@ -383,10 +383,11 @@ bool is_css_table = !isHTMLTableElement(GetNode()); if (is_css_table && style_logical_width.IsSpecified() && style_logical_width.IsPositive() && - Style()->BoxSizing() == EBoxSizing::kContentBox) - borders = - BorderStart() + BorderEnd() + - (CollapseBorders() ? LayoutUnit() : PaddingStart() + PaddingEnd()); + Style()->BoxSizing() == EBoxSizing::kContentBox) { + borders = BorderStart() + BorderEnd() + + (ShouldCollapseBorders() ? LayoutUnit() + : PaddingStart() + PaddingEnd()); + } return MinimumValueForLength(style_logical_width, available_width) + borders; } @@ -394,9 +395,10 @@ LayoutUnit LayoutTable::ConvertStyleLogicalHeightToComputedHeight( const Length& style_logical_height) const { LayoutUnit border_and_padding_before = - BorderBefore() + (CollapseBorders() ? LayoutUnit() : PaddingBefore()); + BorderBefore() + + (ShouldCollapseBorders() ? LayoutUnit() : PaddingBefore()); LayoutUnit border_and_padding_after = - BorderAfter() + (CollapseBorders() ? LayoutUnit() : PaddingAfter()); + BorderAfter() + (ShouldCollapseBorders() ? LayoutUnit() : PaddingAfter()); LayoutUnit border_and_padding = border_and_padding_before + border_and_padding_after; LayoutUnit computed_logical_height; @@ -622,7 +624,7 @@ // https://www.w3.org/TR/2011/REC-CSS2-20110607/tables.html#model LayoutUnit table_box_logical_top = LogicalHeight(); - bool collapsing = CollapseBorders(); + bool collapsing = ShouldCollapseBorders(); if (collapsing) { // Need to set up the table borders before we can position the sections. for (LayoutTableSection* section = top_section; section; @@ -783,7 +785,7 @@ } void LayoutTable::InvalidateCollapsedBordersForAllCellsIfNeeded() { - DCHECK(CollapseBorders()); + DCHECK(ShouldCollapseBorders()); if (!needs_invalidate_collapsed_borders_for_all_cells_) return; @@ -809,7 +811,7 @@ // cache of its containing section, and invalidates itself if any border // changes. This method doesn't affect layout. void LayoutTable::RecalcCollapsedBordersIfNeeded() { - if (collapsed_borders_valid_ || !CollapseBorders()) + if (collapsed_borders_valid_ || !ShouldCollapseBorders()) return; collapsed_borders_valid_ = true; collapsed_borders_.clear(); @@ -835,7 +837,7 @@ // overflow, which is only supposed to be about overflow from our // descendant objects, but since tables don't support overflow:auto, this // works out fine. - if (CollapseBorders()) { + if (ShouldCollapseBorders()) { LayoutUnit right_border_overflow = Size().Width() + OuterBorderRight() - BorderRight(); LayoutUnit left_border_overflow = BorderLeft() - OuterBorderLeft(); @@ -1178,7 +1180,7 @@ } int LayoutTable::CalcBorderStart() const { - if (!CollapseBorders()) + if (!ShouldCollapseBorders()) return LayoutBlock::BorderStart().ToInt(); // Determined by the first cell of the first row. See the CSS 2.1 spec, @@ -1245,7 +1247,7 @@ } int LayoutTable::CalcBorderEnd() const { - if (!CollapseBorders()) + if (!ShouldCollapseBorders()) return LayoutBlock::BorderEnd().ToInt(); // Determined by the last cell of the first row. See the CSS 2.1 spec, section @@ -1321,7 +1323,7 @@ } LayoutUnit LayoutTable::BorderBefore() const { - if (CollapseBorders()) { + if (ShouldCollapseBorders()) { RecalcSectionsIfNeeded(); return LayoutUnit(OuterBorderBefore()); } @@ -1329,7 +1331,7 @@ } LayoutUnit LayoutTable::BorderAfter() const { - if (CollapseBorders()) { + if (ShouldCollapseBorders()) { RecalcSectionsIfNeeded(); return LayoutUnit(OuterBorderAfter()); } @@ -1337,7 +1339,7 @@ } int LayoutTable::OuterBorderBefore() const { - if (!CollapseBorders()) + if (!ShouldCollapseBorders()) return 0; int border_width = 0; if (LayoutTableSection* top_section = this->TopSection()) { @@ -1354,7 +1356,7 @@ } int LayoutTable::OuterBorderAfter() const { - if (!CollapseBorders()) + if (!ShouldCollapseBorders()) return 0; int border_width = 0; @@ -1372,7 +1374,7 @@ } int LayoutTable::OuterBorderStart() const { - if (!CollapseBorders()) + if (!ShouldCollapseBorders()) return 0; int border_width = 0; @@ -1400,7 +1402,7 @@ } int LayoutTable::OuterBorderEnd() const { - if (!CollapseBorders()) + if (!ShouldCollapseBorders()) return 0; int border_width = 0; @@ -1723,13 +1725,13 @@ RecalcCollapsedBordersIfNeeded(); } -PaintInvalidationReason LayoutTable::InvalidatePaint( +PaintInvalidationReason LayoutTable::DeprecatedInvalidatePaint( const PaintInvalidationState& paint_invalidation_state) { - if (CollapseBorders() && !collapsed_borders_.IsEmpty()) + if (ShouldCollapseBorders() && !collapsed_borders_.IsEmpty()) paint_invalidation_state.PaintingLayer() .SetNeedsPaintPhaseDescendantBlockBackgrounds(); - return LayoutBlock::InvalidatePaint(paint_invalidation_state); + return LayoutBlock::DeprecatedInvalidatePaint(paint_invalidation_state); } PaintInvalidationReason LayoutTable::InvalidatePaint( @@ -1738,28 +1740,28 @@ } LayoutUnit LayoutTable::PaddingTop() const { - if (CollapseBorders()) + if (ShouldCollapseBorders()) return LayoutUnit(); return LayoutBlock::PaddingTop(); } LayoutUnit LayoutTable::PaddingBottom() const { - if (CollapseBorders()) + if (ShouldCollapseBorders()) return LayoutUnit(); return LayoutBlock::PaddingBottom(); } LayoutUnit LayoutTable::PaddingLeft() const { - if (CollapseBorders()) + if (ShouldCollapseBorders()) return LayoutUnit(); return LayoutBlock::PaddingLeft(); } LayoutUnit LayoutTable::PaddingRight() const { - if (CollapseBorders()) + if (ShouldCollapseBorders()) return LayoutUnit(); return LayoutBlock::PaddingRight();
diff --git a/third_party/WebKit/Source/core/layout/LayoutTable.h b/third_party/WebKit/Source/core/layout/LayoutTable.h index 8d676e1..fe3b67a 100644 --- a/third_party/WebKit/Source/core/layout/LayoutTable.h +++ b/third_party/WebKit/Source/core/layout/LayoutTable.h
@@ -145,7 +145,7 @@ int HBorderSpacing() const { return h_spacing_; } int VBorderSpacing() const { return v_spacing_; } - bool CollapseBorders() const { + bool ShouldCollapseBorders() const { return Style()->BorderCollapse() == EBorderCollapse::kCollapse; } @@ -349,9 +349,9 @@ // 'border-spacing' only applies to separate borders (see 17.6.1 The // separated borders model). return BorderStart() + BorderEnd() + - (CollapseBorders() ? LayoutUnit() - : (PaddingStart() + PaddingEnd() + - BorderSpacingInRowDirection())); + (ShouldCollapseBorders() ? LayoutUnit() + : (PaddingStart() + PaddingEnd() + + BorderSpacingInRowDirection())); } // Return the first column or column-group. @@ -474,7 +474,7 @@ void SimplifiedNormalFlowLayout() override; bool RecalcChildOverflowAfterStyleChange() override; void EnsureIsReadyForPaintInvalidation() override; - PaintInvalidationReason InvalidatePaint( + PaintInvalidationReason DeprecatedInvalidatePaint( const PaintInvalidationState&) override; PaintInvalidationReason InvalidatePaint( const PaintInvalidatorContext&) const override;
diff --git a/third_party/WebKit/Source/core/layout/LayoutTableBoxComponent.cpp b/third_party/WebKit/Source/core/layout/LayoutTableBoxComponent.cpp index f48eca2..ab978d38 100644 --- a/third_party/WebKit/Source/core/layout/LayoutTableBoxComponent.cpp +++ b/third_party/WebKit/Source/core/layout/LayoutTableBoxComponent.cpp
@@ -15,7 +15,7 @@ LayoutTable& table, const StyleDifference& diff, const ComputedStyle& old_style) { - if (!table.CollapseBorders()) + if (!table.ShouldCollapseBorders()) return; if (old_style.Border() != table_part.StyleRef().Border() || (diff.TextDecorationOrColorChanged() && @@ -35,7 +35,7 @@ // optimization but now it seems that diff.needsFullLayout() implies // tablePart.needsLayout(). return diff.NeedsFullLayout() && table_part.NeedsLayout() && - table.CollapseBorders() && + table.ShouldCollapseBorders() && !old_style.Border().SizeEquals(table_part.Style()->Border()); }
diff --git a/third_party/WebKit/Source/core/layout/LayoutTableCell.cpp b/third_party/WebKit/Source/core/layout/LayoutTableCell.cpp index 3c7cca01..d6b7298 100644 --- a/third_party/WebKit/Source/core/layout/LayoutTableCell.cpp +++ b/third_party/WebKit/Source/core/layout/LayoutTableCell.cpp
@@ -107,7 +107,7 @@ // neighboring cells. LayoutTable* enclosing_table = Table(); DCHECK(enclosing_table); - if (!enclosing_table->CollapseBorders()) + if (!enclosing_table->ShouldCollapseBorders()) return; if (PreviousCell()) { // TODO(dgrogan): Should this be setChildNeedsLayout or setNeedsLayout? @@ -415,7 +415,7 @@ // because it means that the table is going to recalculate the grid, relayout // and issue a paint invalidation of its current rect, which includes any // outside borders of this cell. - if (!Table()->CollapseBorders() || Table()->NeedsSectionRecalc()) + if (!Table()->ShouldCollapseBorders() || Table()->NeedsSectionRecalc()) return LayoutBlockFlow::LocalVisualRect(); bool rtl = !StyleForCellFlow().IsLeftToRightDirection(); @@ -1102,46 +1102,46 @@ } LayoutUnit LayoutTableCell::BorderLeft() const { - return Table()->CollapseBorders() ? CollapsedBorderHalfLeft(false) - : LayoutBlockFlow::BorderLeft(); + return Table()->ShouldCollapseBorders() ? CollapsedBorderHalfLeft(false) + : LayoutBlockFlow::BorderLeft(); } LayoutUnit LayoutTableCell::BorderRight() const { - return Table()->CollapseBorders() ? CollapsedBorderHalfRight(false) - : LayoutBlockFlow::BorderRight(); + return Table()->ShouldCollapseBorders() ? CollapsedBorderHalfRight(false) + : LayoutBlockFlow::BorderRight(); } LayoutUnit LayoutTableCell::BorderTop() const { - return Table()->CollapseBorders() ? CollapsedBorderHalfTop(false) - : LayoutBlockFlow::BorderTop(); + return Table()->ShouldCollapseBorders() ? CollapsedBorderHalfTop(false) + : LayoutBlockFlow::BorderTop(); } LayoutUnit LayoutTableCell::BorderBottom() const { - return Table()->CollapseBorders() ? CollapsedBorderHalfBottom(false) - : LayoutBlockFlow::BorderBottom(); + return Table()->ShouldCollapseBorders() ? CollapsedBorderHalfBottom(false) + : LayoutBlockFlow::BorderBottom(); } // FIXME: https://bugs.webkit.org/show_bug.cgi?id=46191, make the collapsed // border drawing work with different block flow values instead of being // hard-coded to top-to-bottom. LayoutUnit LayoutTableCell::BorderStart() const { - return Table()->CollapseBorders() ? CollapsedBorderHalfStart(false) - : LayoutBlockFlow::BorderStart(); + return Table()->ShouldCollapseBorders() ? CollapsedBorderHalfStart(false) + : LayoutBlockFlow::BorderStart(); } LayoutUnit LayoutTableCell::BorderEnd() const { - return Table()->CollapseBorders() ? CollapsedBorderHalfEnd(false) - : LayoutBlockFlow::BorderEnd(); + return Table()->ShouldCollapseBorders() ? CollapsedBorderHalfEnd(false) + : LayoutBlockFlow::BorderEnd(); } LayoutUnit LayoutTableCell::BorderBefore() const { - return Table()->CollapseBorders() ? CollapsedBorderHalfBefore(false) - : LayoutBlockFlow::BorderBefore(); + return Table()->ShouldCollapseBorders() ? CollapsedBorderHalfBefore(false) + : LayoutBlockFlow::BorderBefore(); } LayoutUnit LayoutTableCell::BorderAfter() const { - return Table()->CollapseBorders() ? CollapsedBorderHalfAfter(false) - : LayoutBlockFlow::BorderAfter(); + return Table()->ShouldCollapseBorders() ? CollapsedBorderHalfAfter(false) + : LayoutBlockFlow::BorderAfter(); } LayoutUnit LayoutTableCell::CollapsedBorderHalfLeft(bool outer) const { @@ -1268,7 +1268,7 @@ collapsed_border_values_valid_ = true; - if (!Table()->CollapseBorders()) { + if (!Table()->ShouldCollapseBorders()) { if (collapsed_border_values_) { collapsed_borders_visually_changed_ = true; collapsed_border_values_ = nullptr; @@ -1426,7 +1426,7 @@ // If this object has layer, the area of collapsed borders should be // transparent to expose the collapsed borders painted on the underlying // layer. - if (HasLayer() && Table()->CollapseBorders()) + if (HasLayer() && Table()->ShouldCollapseBorders()) return false; return LayoutBlockFlow::BackgroundIsKnownToBeOpaqueInRect(local_rect); } @@ -1483,9 +1483,4 @@ return TableCellPaintInvalidator(*this, context).InvalidatePaint(); } -PaintInvalidationReason LayoutTableCell::InvalidatePaint( - const PaintInvalidationState& state) { - return LayoutBlockFlow::InvalidatePaint(state); -} - } // namespace blink
diff --git a/third_party/WebKit/Source/core/layout/LayoutTableCell.h b/third_party/WebKit/Source/core/layout/LayoutTableCell.h index f13b9a4b..f420854 100644 --- a/third_party/WebKit/Source/core/layout/LayoutTableCell.h +++ b/third_party/WebKit/Source/core/layout/LayoutTableCell.h
@@ -354,8 +354,6 @@ PaintInvalidationReason InvalidatePaint( const PaintInvalidatorContext&) const override; - PaintInvalidationReason InvalidatePaint( - const PaintInvalidationState&) override; private: friend class LayoutTableCellTest;
diff --git a/third_party/WebKit/Source/core/layout/LayoutTableRow.cpp b/third_party/WebKit/Source/core/layout/LayoutTableRow.cpp index 7df3acb5..a2b8f5d 100644 --- a/third_party/WebKit/Source/core/layout/LayoutTableRow.cpp +++ b/third_party/WebKit/Source/core/layout/LayoutTableRow.cpp
@@ -170,7 +170,7 @@ // When borders collapse, adding a cell can affect the the width of // neighboring cells. LayoutTable* enclosing_table = Table(); - if (enclosing_table && enclosing_table->CollapseBorders()) { + if (enclosing_table && enclosing_table->ShouldCollapseBorders()) { enclosing_table->InvalidateCollapsedBorders(); if (LayoutTableCell* previous_cell = cell->PreviousCell()) previous_cell->SetNeedsLayoutAndPrefWidthsRecalc(
diff --git a/third_party/WebKit/Source/core/layout/LayoutView.cpp b/third_party/WebKit/Source/core/layout/LayoutView.cpp index eba570f..288b0a5 100644 --- a/third_party/WebKit/Source/core/layout/LayoutView.cpp +++ b/third_party/WebKit/Source/core/layout/LayoutView.cpp
@@ -442,11 +442,6 @@ } PaintInvalidationReason LayoutView::InvalidatePaint( - const PaintInvalidationState& paint_invalidation_state) { - return LayoutBlockFlow::InvalidatePaint(paint_invalidation_state); -} - -PaintInvalidationReason LayoutView::InvalidatePaint( const PaintInvalidatorContext& context) const { return ViewPaintInvalidator(*this, context).InvalidatePaint(); }
diff --git a/third_party/WebKit/Source/core/layout/LayoutView.h b/third_party/WebKit/Source/core/layout/LayoutView.h index 3c3b59e..afbeeaf 100644 --- a/third_party/WebKit/Source/core/layout/LayoutView.h +++ b/third_party/WebKit/Source/core/layout/LayoutView.h
@@ -140,8 +140,6 @@ void InvalidatePaintForViewAndCompositedLayers(); PaintInvalidationReason InvalidatePaint( - const PaintInvalidationState&) override; - PaintInvalidationReason InvalidatePaint( const PaintInvalidatorContext&) const override; void Paint(const PaintInfo&, const LayoutPoint&) const override;
diff --git a/third_party/WebKit/Source/core/layout/PaintInvalidationState.h b/third_party/WebKit/Source/core/layout/PaintInvalidationState.h index 75cee297..b647d95 100644 --- a/third_party/WebKit/Source/core/layout/PaintInvalidationState.h +++ b/third_party/WebKit/Source/core/layout/PaintInvalidationState.h
@@ -20,6 +20,9 @@ class LayoutView; class PaintLayer; +// This is deprecated by PaintInvalidator when SlimmingPaintInvalidation is +// enabled. +// // PaintInvalidationState is an optimization used during the paint // invalidation phase. //
diff --git a/third_party/WebKit/Source/core/layout/api/LayoutItem.h b/third_party/WebKit/Source/core/layout/api/LayoutItem.h index 083fa55..303513f 100644 --- a/third_party/WebKit/Source/core/layout/api/LayoutItem.h +++ b/third_party/WebKit/Source/core/layout/api/LayoutItem.h
@@ -201,8 +201,8 @@ return layout_object_->NeedsOverflowRecalcAfterStyleChange(); } - void InvalidateTreeIfNeeded(const PaintInvalidationState& state) { - layout_object_->InvalidateTreeIfNeeded(state); + void DeprecatedInvalidateTree(const PaintInvalidationState& state) { + layout_object_->DeprecatedInvalidateTree(state); } CompositingState GetCompositingState() const {
diff --git a/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.cc b/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.cc index 62492de..33c0b9f 100644 --- a/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.cc +++ b/third_party/WebKit/Source/core/layout/ng/inline/ng_inline_layout_algorithm.cc
@@ -76,10 +76,6 @@ Initialize(break_token->ItemIndex(), break_token->TextOffset()); else Initialize(0, 0); - - // BFC offset is known for inline fragments. - MaybeUpdateFragmentBfcOffset(ConstraintSpace(), ConstraintSpace().BfcOffset(), - &container_builder_); } bool NGInlineLayoutAlgorithm::IsFirstLine() const { @@ -250,6 +246,16 @@ bool NGInlineLayoutAlgorithm::CreateLineUpToLastBreakOpportunity() { const Vector<NGInlineItem>& items = Node()->Items(); + // TODO(crbug.com/716930): We may be an empty LayoutInline due to splitting. + // Only resolve our BFC offset if we know that we are non-empty as we may + // need to pass through our margin strut. + if (!items.IsEmpty()) { + NGLogicalOffset bfc_offset = ConstraintSpace().BfcOffset(); + bfc_offset.block_offset += ConstraintSpace().MarginStrut().Sum(); + MaybeUpdateFragmentBfcOffset(ConstraintSpace(), bfc_offset, + &container_builder_); + } + // Create a list of LineItemChunk from |start| and |last_break_opportunity|. // TODO(kojii): Consider refactoring LineItemChunk once NGLineBuilder's public // API is more finalized. It does not fit well with the current API. @@ -571,6 +577,13 @@ NGLogicalSize size(max_inline_size_, content_size_); container_builder_.SetSize(size).SetOverflowSize(size); + // TODO(crbug.com/716930): We may be an empty LayoutInline due to splitting. + // Margin struts shouldn't need to be passed through like this once we've + // removed LayoutInline splitting. + if (!container_builder_.BfcOffset()) { + container_builder_.SetEndMarginStrut(ConstraintSpace().MarginStrut()); + } + return container_builder_.ToBoxFragment(); }
diff --git a/third_party/WebKit/Source/core/layout/ng/ng_block_layout_algorithm.cc b/third_party/WebKit/Source/core/layout/ng/ng_block_layout_algorithm.cc index 90b6555..cf98fe68 100644 --- a/third_party/WebKit/Source/core/layout/ng/ng_block_layout_algorithm.cc +++ b/third_party/WebKit/Source/core/layout/ng/ng_block_layout_algorithm.cc
@@ -347,8 +347,11 @@ bool is_legacy_block = child->IsBlock() && !ToNGBlockNode(child)->CanUseNewLayout(); - // Should collapse margins if inline or legacy block - if (child->IsInline() || is_legacy_block) { + // TODO(crbug.com/716930): We should also collapse margins below once we + // remove LayoutInline splitting. + + // Should collapse margins if our child is a legacy block. + if (is_legacy_block) { curr_bfc_offset_.block_offset += curr_margin_strut_.Sum(); MaybeUpdateFragmentBfcOffset(ConstraintSpace(), curr_bfc_offset_, &container_builder_); @@ -563,6 +566,12 @@ space_builder_.SetIsNewFormattingContext(is_new_bfc) .SetBfcOffset(child_bfc_offset); + // Float's margins are not included in child's space because: + // 1) Floats do not participate in margins collapsing. + // 2) Floats margins are used separately to calculate floating exclusions. + space_builder_.SetMarginStrut(child->IsFloating() ? NGMarginStrut() + : curr_margin_strut_); + if (child->IsInline()) { // TODO(kojii): Setup space_builder_ appropriately for inline child. space_builder_.SetClearanceOffset(ConstraintSpace().ClearanceOffset()); @@ -576,12 +585,6 @@ .SetIsShrinkToFit(ShouldShrinkToFit(Style(), child_style)) .SetTextDirection(child_style.Direction()); - // Float's margins are not included in child's space because: - // 1) Floats do not participate in margins collapsing. - // 2) Floats margins are used separately to calculate floating exclusions. - space_builder_.SetMarginStrut(child->IsFloating() ? NGMarginStrut() - : curr_margin_strut_); - LayoutUnit space_available; if (constraint_space_->HasBlockFragmentation()) { space_available = ConstraintSpace().FragmentainerSpaceAvailable();
diff --git a/third_party/WebKit/Source/core/layout/svg/LayoutSVGInline.cpp b/third_party/WebKit/Source/core/layout/svg/LayoutSVGInline.cpp index c3f411b..cb727c755 100644 --- a/third_party/WebKit/Source/core/layout/svg/LayoutSVGInline.cpp +++ b/third_party/WebKit/Source/core/layout/svg/LayoutSVGInline.cpp
@@ -149,12 +149,12 @@ LayoutInline::RemoveChild(child); } -void LayoutSVGInline::InvalidateTreeIfNeeded( +void LayoutSVGInline::DeprecatedInvalidateTree( const PaintInvalidationState& paint_invalidation_state) { // TODO(wangxianzhu): Verify if the inherited // LayoutBoxModelObject::invalidateTreeIfNeeded() is applicable here. // If yes, remove this overriding method. - LayoutObject::InvalidateTreeIfNeeded(paint_invalidation_state); + LayoutObject::DeprecatedInvalidateTree(paint_invalidation_state); } } // namespace blink
diff --git a/third_party/WebKit/Source/core/layout/svg/LayoutSVGInline.h b/third_party/WebKit/Source/core/layout/svg/LayoutSVGInline.h index 277806d..3aaf3ecb 100644 --- a/third_party/WebKit/Source/core/layout/svg/LayoutSVGInline.h +++ b/third_party/WebKit/Source/core/layout/svg/LayoutSVGInline.h
@@ -61,7 +61,7 @@ private: InlineFlowBox* CreateInlineFlowBox() final; - void InvalidateTreeIfNeeded(const PaintInvalidationState&) final; + void DeprecatedInvalidateTree(const PaintInvalidationState&) final; void WillBeDestroyed() final; void StyleDidChange(StyleDifference, const ComputedStyle* old_style) final;
diff --git a/third_party/WebKit/Source/core/layout/svg/LayoutSVGText.cpp b/third_party/WebKit/Source/core/layout/svg/LayoutSVGText.cpp index bea009f5..e5c80c34 100644 --- a/third_party/WebKit/Source/core/layout/svg/LayoutSVGText.cpp +++ b/third_party/WebKit/Source/core/layout/svg/LayoutSVGText.cpp
@@ -409,12 +409,12 @@ LayoutSVGBlock::RemoveChild(child); } -void LayoutSVGText::InvalidateTreeIfNeeded( +void LayoutSVGText::DeprecatedInvalidateTree( const PaintInvalidationState& paint_invalidation_state) { // TODO(wangxianzhu): Verify if the inherited // LayoutBoxModelObject::invalidateTreeIfNeeded() // is applicable here. If yes, remove this overriding method. - LayoutObject::InvalidateTreeIfNeeded(paint_invalidation_state); + LayoutObject::DeprecatedInvalidateTree(paint_invalidation_state); } } // namespace blink
diff --git a/third_party/WebKit/Source/core/layout/svg/LayoutSVGText.h b/third_party/WebKit/Source/core/layout/svg/LayoutSVGText.h index 390c000d..6043c52 100644 --- a/third_party/WebKit/Source/core/layout/svg/LayoutSVGText.h +++ b/third_party/WebKit/Source/core/layout/svg/LayoutSVGText.h
@@ -85,7 +85,7 @@ void RemoveChild(LayoutObject*) override; void WillBeDestroyed() override; - void InvalidateTreeIfNeeded(const PaintInvalidationState&) override; + void DeprecatedInvalidateTree(const PaintInvalidationState&) override; RootInlineBox* CreateRootInlineBox() override;
diff --git a/third_party/WebKit/Source/core/paint/BoxPainter.cpp b/third_party/WebKit/Source/core/paint/BoxPainter.cpp index 0bb07bb..b3f7cb9 100644 --- a/third_party/WebKit/Source/core/paint/BoxPainter.cpp +++ b/third_party/WebKit/Source/core/paint/BoxPainter.cpp
@@ -199,7 +199,7 @@ LayoutTheme::GetTheme().Painter().PaintBorderOnly( layout_box_, paint_info, snapped_paint_rect))) && !(layout_box_.IsTable() && - ToLayoutTable(&layout_box_)->CollapseBorders())) { + ToLayoutTable(&layout_box_)->ShouldCollapseBorders())) { PaintBorder(layout_box_, paint_info, paint_rect, style, box_decoration_data.bleed_avoidance); }
diff --git a/third_party/WebKit/Source/core/paint/PaintInvalidator.cpp b/third_party/WebKit/Source/core/paint/PaintInvalidator.cpp index df2ac59..5317ac9 100644 --- a/third_party/WebKit/Source/core/paint/PaintInvalidator.cpp +++ b/third_party/WebKit/Source/core/paint/PaintInvalidator.cpp
@@ -227,7 +227,7 @@ // on the table's layer. if (object.IsTable()) { const LayoutTable& table = ToLayoutTable(object); - if (table.CollapseBorders() && !table.CollapsedBorders().IsEmpty()) + if (table.ShouldCollapseBorders() && !table.CollapsedBorders().IsEmpty()) context.painting_layer->SetNeedsPaintPhaseDescendantBlockBackgrounds(); }
diff --git a/third_party/WebKit/Source/core/paint/README.md b/third_party/WebKit/Source/core/paint/README.md index b30e782..02f283a2 100644 --- a/third_party/WebKit/Source/core/paint/README.md +++ b/third_party/WebKit/Source/core/paint/README.md
@@ -1,7 +1,7 @@ # `Source/core/paint` This directory contains implementation of painters of layout objects. It covers -the following document lifecycle states: +the following document lifecycle phases: * PaintInvalidation (`InPaintInvalidation` and `PaintInvalidationClean`) * PrePaint (`InPrePaint` and `PrePaintClean`) @@ -90,18 +90,17 @@ container. * Paint invalidation container: the nearest object on the compositing - container chain which is composited. + container chain which is composited. Slimming paint V2 doesn't have this + concept. * Visual rect: the bounding box of all pixels that will be painted by a display item client. -## Paint invalidation +## PaintInvalidation (Deprecated by [PrePaint](#PrePaint)) Paint invalidation marks anything that need to be painted differently from the original cached painting. -### Slimming paint v1 - Paint invalidation is a document cycle stage after compositing update and before paint. During the previous stages, objects are marked for needing paint invalidation checking if needed by style change, layout change, compositing @@ -116,7 +115,7 @@ pixels. They are generated based on visual rects of invalidated display item clients. -#### `PaintInvalidationState` +### `PaintInvalidationState` `PaintInvalidationState` is an optimization used during the paint invalidation phase. Before the paint invalidation tree walk, a root `PaintInvalidationState` @@ -185,10 +184,10 @@ `LayoutInline`s and `LayoutText`s are marked for full paint invalidation if needed when new style is set on them. During paint invalidation, we invalidate the `InlineFlowBox`s directly contained by the `LayoutInline` in -`LayoutInline::invalidateDisplayItemClients()` and `InlineTextBox`s contained by -the `LayoutText` in `LayoutText::invalidateDisplayItemClients()`. We don't need +`LayoutInline::InvalidateDisplayItemClients()` and `InlineTextBox`s contained by +the `LayoutText` in `LayoutText::InvalidateDisplayItemClients()`. We don't need to traverse into the subtree of `InlineFlowBox`s in -`LayoutInline::invalidateDisplayItemClients()` because the descendant +`LayoutInline::InvalidateDisplayItemClients()` because the descendant `InlineFlowBox`s and `InlineTextBox`s will be handled by their owning `LayoutInline`s and `LayoutText`s, respectively, when changed style is propagated. @@ -205,21 +204,18 @@ with `FIRST_LINE_INHERITED` pseudo ID. The normal paint invalidation of texts doesn't work for first line because -* `ComputedStyle::visualInvalidationDiff()` can't detect first line style +* `ComputedStyle::VisualInvalidationDiff()` can't detect first line style changes; * The normal paint invalidation is based on whole LayoutObject's, not aware of the first line. We have a special path for first line style change: the style system informs the layout system when the computed first-line style changes through -`LayoutObject::firstLineStyleDidChange()`. When this happens, we invalidate all +`LayoutObject::FirstLineStyleDidChange()`. When this happens, we invalidate all `InlineBox`es in the first line. -### Slimming paint v2 - -TODO(wangxianzhu): add details - -## [`PrePaintTreeWalk`](PrePaintTreeWalk.h) (Slimming Paint invalidation/v2 only) +## PrePaint (Slimming paint invalidation/v2 only) +[`PrePaintTreeWalk`](PrePaintTreeWalk.h) During `InPrePaint` document lifecycle state, this class is called to walk the whole layout tree, beginning from the root FrameView, across frame boundaries. @@ -241,7 +237,7 @@ `DescendantNeedsPaintPropertyUpdate` dirty bits on `LayoutObject` control how much of the layout tree is traversed during each `PrePaintTreeWalk`. -### Fragments +#### Fragments In the absence of multicolumn/pagination, there is a 1:1 correspondence between self-painting `PaintLayer`s and `FragmentData`. If there is @@ -257,41 +253,45 @@ See [`LayoutMultiColumnFlowThread.h`](../layout/LayoutMultiColumnFlowThread.h) for a much more detail about multicolumn/pagination. -### Paint invalidation: `PaintInvalidator` implements a tree walk that -performs paint invalidation. TODO(wangxianzhu): expand on this. +### Paint invalidation +[`PaintInvalidator`](PaintInvalidator.h) -### [`PaintPropertyTreeBuilder`](PaintPropertyTreeBuilder.h) (Slimming Paint invalidation only) +This class replaces [`PaintInvalidationState`] for SlimmingPaintInvalidation. +The main difference is that in PaintInvalidator, visual rects and locations +are computed by `GeometryMapper`(../../platform/graphics/paint/GeometryMapper.h), +based on paint properties produced by `PaintPropertyTreeBuilder`. -## Paint result caching +TODO(wangxianzhu): Combine documentation of PaintInvalidation phase into here. + +## Paint + +### Paint result caching `PaintController` holds the previous painting result as a cache of display items. If some painter would generate results same as those of the previous painting, we'll skip the painting and reuse the display items from cache. -### Display item caching +#### Display item caching When a painter would create a `DrawingDisplayItem` exactly the same as the display item created in the previous painting, we'll reuse the previous one instead of repainting it. -### Subsequence caching +#### Subsequence caching -When possible, we enclose the display items that -`PaintLayerPainter::paintContents()` generates (including display items -generated by sublayers) in a pair of `BeginSubsequence/EndSubsequence` display -items. - -In a subsequence paint, if the layer would generate exactly the same display -items, we'll get the whole subsequence from the cache instead of repainting -them. +When possible, we create a scoped `SubsequenceRecorder` in +`PaintLayerPainter::PaintContents()` to record all display items generated in +the scope as a "subsequence". Before painting a layer, if we are sure that the +layer will generate exactly the same display items as the previous paint, we'll +get the whole subsequence from the cache instead of repainting them. There are many conditions affecting * whether we need to generate subsequence for a PaintLayer; * whether we can use cached subsequence for a PaintLayer. -See `shouldCreateSubsequence()` and `shouldRepaintSubsequence()` in +See `ShouldCreateSubsequence()` and `shouldRepaintSubsequence()` in `PaintLayerPainter.cpp` for the conditions. -## Empty paint phase optimization +### Empty paint phase optimization During painting, we walk the layout tree multiple times for multiple paint phases. Sometimes a layer contain nothing needing a certain paint phase and we @@ -300,32 +300,31 @@ and `PaintPhaseFloat` for empty paint phases. During paint invalidation, we set the containing self-painting layer's -`needsPaintPhaseXXX` flag if the object has something needing to be painted in +`NeedsPaintPhaseXXX` flag if the object has something needing to be painted in the paint phase. During painting, we check the flag before painting a paint phase and skip the tree walk if the flag is not set. -It's hard to clear a `needsPaintPhaseXXX` flag when a layer no longer needs the +It's hard to clear a `NeedsPaintPhaseXXX` flag when a layer no longer needs the paint phase, so we never clear the flags. Instead, we use another set of flags -(`previousPaintPhaseXXXWasEmpty`) to record if a painting of a phase actually +(`PreviousPaintPhaseXXXWasEmpty`) to record if a painting of a phase actually produced nothing. We'll skip the next painting of the phase if the flag is set, -regardless of the corresponding `needsPaintPhaseXXX` flag. We will clear the -`previousPaintPhaseXXXWasEmpty` flags when we paint with different clipping, +regardless of the corresponding `NeedsPaintPhaseXXX` flag. We will clear the +`PreviousPaintPhaseXXXWasEmpty` flags when we paint with different clipping, scroll offset or interest rect from the previous paint. -We don't clear the `previousPaintPhaseXXXWasEmpty` flags when the layer is -marked `needsRepaint`. Instead we clear the flag when the corresponding -`needsPaintPhaseXXX` is set. This ensures that we won't clear -`previousPaintPhaseXXXWasEmpty` flags when unrelated things changed which won't -cause the paint phases to become non-empty. +We don't clear the `PreviousPaintPhaseXXXWasEmpty` flags when the layer is +marked `NeedsRepaint`. Instead we clear the flag when the corresponding +`NeedsPaintPhaseXXX` is set. This ensures that we won't clear +`PreviousPaintPhaseXXXWasEmpty` flags when unrelated things changed which won't When layer structure changes, and we are not invalidate paint of the changed -subtree, we need to manually update the `needsPaintPhaseXXX` flags. For example, +subtree, we need to manually update the `NeedsPaintPhaseXXX` flags. For example, if an object changes style and creates a self-painting-layer, we copy the flags from its containing self-painting layer to this layer, assuming that this layer needs all paint phases that its container self-painting layer needs. -We could update the `needsPaintPhaseXXX` flags in a separate tree walk, but that +We could update the `NeedsPaintPhaseXXX` flags in a separate tree walk, but that would regress performance of the first paint. For slimming paint v2, we can update the flags during the pre-painting tree walk to simplify the logics.
diff --git a/third_party/WebKit/Source/core/paint/TableCellPainter.cpp b/third_party/WebKit/Source/core/paint/TableCellPainter.cpp index 1ec7020..888b8df3 100644 --- a/third_party/WebKit/Source/core/paint/TableCellPainter.cpp +++ b/third_party/WebKit/Source/core/paint/TableCellPainter.cpp
@@ -186,7 +186,7 @@ return; LayoutTable* table = layout_table_cell_.Table(); - if (!table->CollapseBorders() && + if (!table->ShouldCollapseBorders() && layout_table_cell_.Style()->EmptyCells() == EEmptyCells::kHide && !layout_table_cell_.FirstChild()) return; @@ -210,7 +210,7 @@ bool should_clip = background_object.HasLayer() && (background_object == layout_table_cell_ || background_object == layout_table_cell_.Parent()) && - layout_table_cell_.Table()->CollapseBorders(); + layout_table_cell_.Table()->ShouldCollapseBorders(); GraphicsContextStateSaver state_saver(paint_info.context, should_clip); if (should_clip) { LayoutRect clip_rect(paint_rect.Location(), layout_table_cell_.Size()); @@ -229,12 +229,13 @@ const LayoutPoint& paint_offset) { LayoutTable* table = layout_table_cell_.Table(); const ComputedStyle& style = layout_table_cell_.StyleRef(); - if (!table->CollapseBorders() && style.EmptyCells() == EEmptyCells::kHide && + if (!table->ShouldCollapseBorders() && + style.EmptyCells() == EEmptyCells::kHide && !layout_table_cell_.FirstChild()) return; bool needs_to_paint_border = - style.HasBorderDecoration() && !table->CollapseBorders(); + style.HasBorderDecoration() && !table->ShouldCollapseBorders(); if (!style.HasBackground() && !style.BoxShadow() && !needs_to_paint_border) return; @@ -272,7 +273,7 @@ return; LayoutTable* table_elt = layout_table_cell_.Table(); - if (!table_elt->CollapseBorders() && + if (!table_elt->ShouldCollapseBorders() && layout_table_cell_.Style()->EmptyCells() == EEmptyCells::kHide && !layout_table_cell_.FirstChild()) return;
diff --git a/third_party/WebKit/Source/core/paint/TablePainter.cpp b/third_party/WebKit/Source/core/paint/TablePainter.cpp index cbdd040..7dd5f23 100644 --- a/third_party/WebKit/Source/core/paint/TablePainter.cpp +++ b/third_party/WebKit/Source/core/paint/TablePainter.cpp
@@ -44,7 +44,7 @@ } } - if (layout_table_.CollapseBorders() && + if (layout_table_.ShouldCollapseBorders() && ShouldPaintDescendantBlockBackgrounds(paint_phase) && layout_table_.Style()->Visibility() == EVisibility::kVisible) { // Using our cached sorted styles, we then do individual passes,
diff --git a/third_party/WebKit/Source/devtools/BUILD.gn b/third_party/WebKit/Source/devtools/BUILD.gn index f350f94..ac3e9d8 100644 --- a/third_party/WebKit/Source/devtools/BUILD.gn +++ b/third_party/WebKit/Source/devtools/BUILD.gn
@@ -44,8 +44,10 @@ "front_end/audits2_worker/lighthouse/lighthouse-background.js", "front_end/audits2_worker/module.json", "front_end/audits2/Audits2Panel.js", + "front_end/audits2/lighthouse/renderer/util.js", "front_end/audits2/lighthouse/renderer/dom.js", "front_end/audits2/lighthouse/renderer/details-renderer.js", + "front_end/audits2/lighthouse/renderer/category-renderer.js", "front_end/audits2/lighthouse/renderer/report-renderer.js", "front_end/audits2/module.json", "front_end/bindings/BlackboxManager.js",
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2/Audits2Panel.js b/third_party/WebKit/Source/devtools/front_end/audits2/Audits2Panel.js index a60b2ea..603a5c5 100644 --- a/third_party/WebKit/Source/devtools/front_end/audits2/Audits2Panel.js +++ b/third_party/WebKit/Source/devtools/front_end/audits2/Audits2Panel.js
@@ -61,9 +61,8 @@ */ _createStatusView(launcherUIElement) { var statusView = launcherUIElement.createChild('div', 'audits2-status hbox hidden'); - statusView.createChild('span', 'icon'); - this._statusElement = createElement('p'); - statusView.appendChild(this._statusElement); + this._statusIcon = statusView.createChild('span', 'icon'); + this._statusElement = statusView.createChild('p'); this._updateStatus(Common.UIString('Loading...')); return statusView; } @@ -87,6 +86,8 @@ .then(lighthouseResult => { this._finish(lighthouseResult); return this._stop(); + }).catch(err => { + if (err instanceof Error) this._renderBugReport(err); }); } @@ -148,16 +149,47 @@ } /** + * @param {!Error} err + */ + _renderBugReport(err) { + console.error(err); + this._statusElement.textContent = ''; + this._statusIcon.classList.add('error'); + this._statusElement.createTextChild(Common.UIString('We ran into an error. ')); + this._createBugReportLink(err, this._statusElement); + } + + /** + * @param {!Error} err + * @param {!Element} parentElem + */ + _createBugReportLink(err, parentElem) { + var baseURI = 'https://github.com/GoogleChrome/lighthouse/issues/new?'; + var title = encodeURI('title=DevTools Error: ' + err.message.substring(0, 60)); + + var qsBody = ''; + qsBody += '**Error Message**: ' + err.message + '\n'; + qsBody += '**Stack Trace**:\n ```' + err.stack + '```'; + var body = '&body=' + encodeURI(qsBody); + + var reportErrorEl = parentElem.createChild('a', 'audits2-link audits2-report-error'); + reportErrorEl.href = baseURI + title + body; + reportErrorEl.textContent = Common.UIString('Report this bug'); + reportErrorEl.target = '_blank'; + } + + /** * @param {!Element} resultsView * @param {!ReportRenderer.ReportJSON} lighthouseResult * @suppressGlobalPropertiesCheck */ _renderReport(resultsView, lighthouseResult) { - var reportContainer = resultsView.createChild('div', 'report-container'); + var reportContainer = resultsView.createChild('div', 'report-container lh-root'); var dom = new DOM(document); var detailsRenderer = new DetailsRenderer(dom); - var renderer = new ReportRenderer(dom, detailsRenderer); + var categoryRenderer = new CategoryRenderer(dom, detailsRenderer); + var renderer = new Audits2.ReportRenderer(dom, categoryRenderer); var templatesHTML = Runtime.cachedResources['audits2/lighthouse/templates.html']; var templatesDOM = new DOMParser().parseFromString(templatesHTML, 'text/html'); @@ -165,7 +197,7 @@ return; renderer.setTemplateContext(templatesDOM); - reportContainer.appendChild(renderer.renderReport(lighthouseResult)); + renderer.renderReport(lighthouseResult, reportContainer); } /** @@ -192,6 +224,28 @@ } }; +/** + * @override + */ +Audits2.ReportRenderer = class extends ReportRenderer { + /** + * Provides empty element for left nav + * @override + * @returns {!DocumentFragment} + */ + _renderReportNav() { + return createDocumentFragment(); + } +}; + + +class ReportUIFeatures { + /** + * @param {!ReportRenderer.ReportJSON} report + */ + initFeatures(report) {} +} + /** @typedef {{id: string, configID: string, description: string}} */ Audits2.Audits2Panel.Preset;
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2/audits2Panel.css b/third_party/WebKit/Source/devtools/front_end/audits2/audits2Panel.css index a6394e3..80ee08d 100644 --- a/third_party/WebKit/Source/devtools/front_end/audits2/audits2Panel.css +++ b/third_party/WebKit/Source/devtools/front_end/audits2/audits2Panel.css
@@ -163,6 +163,17 @@ content: ""; } +.audits2-status .icon.error, +.audits2-status .icon.error::before, +.audits2-status .icon.error::after { + display:none; +} + +.lh-root { + --report-menu-width: 0 !important; +} + + @keyframes spinner-animation { from { transform: rotate(0); } to { transform: rotate(360deg); }
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/category-renderer.js b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/category-renderer.js new file mode 100644 index 0000000..fa662da --- /dev/null +++ b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/category-renderer.js
@@ -0,0 +1,183 @@ +/** + * Copyright 2017 Google Inc. 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. + */ +'use strict'; + +/* globals self, Util */ + +class CategoryRenderer { + /** + * @param {!DOM} dom + * @param {!DetailsRenderer} detailsRenderer + */ + constructor(dom, detailsRenderer) { + /** @private {!DOM} */ + this._dom = dom; + /** @private {!DetailsRenderer} */ + this._detailsRenderer = detailsRenderer; + /** @private {!Document|!Element} */ + this._templateContext = this._dom.document(); + } + + /** + * @param {!ReportRenderer.AuditJSON} audit + * @return {!Element} + */ + _renderAuditScore(audit) { + const tmpl = this._dom.cloneTemplate('#tmpl-lh-audit-score', this._templateContext); + + const scoringMode = audit.result.scoringMode; + const description = audit.result.helpText; + let title = audit.result.description; + + if (audit.result.displayValue) { + title += `: ${audit.result.displayValue}`; + } + if (audit.result.optimalValue) { + title += ` (target: ${audit.result.optimalValue})`; + } + + if (audit.result.debugString) { + const debugStrEl = tmpl.appendChild(this._dom.createElement('div', 'lh-debug')); + debugStrEl.textContent = audit.result.debugString; + } + + // Append audit details to header section so the entire audit is within a <details>. + const header = /** @type {!HTMLDetailsElement} */ (this._dom.find('.lh-score__header', tmpl)); + header.open = audit.score < 100; // expand failed audits + if (audit.result.details) { + header.appendChild(this._detailsRenderer.render(audit.result.details)); + } + + return this._populateScore(tmpl, audit.score, scoringMode, title, description); + } + + /** + * @param {!DocumentFragment|!Element} element DOM node to populate with values. + * @param {number} score + * @param {string} scoringMode + * @param {string} title + * @param {string} description + * @return {!Element} + */ + _populateScore(element, score, scoringMode, title, description) { + // Fill in the blanks. + const valueEl = this._dom.find('.lh-score__value', element); + valueEl.textContent = Util.formatNumber(score); + valueEl.classList.add(`lh-score__value--${Util.calculateRating(score)}`, + `lh-score__value--${scoringMode}`); + + this._dom.find('.lh-score__title', element).textContent = title; + this._dom.find('.lh-score__description', element) + .appendChild(this._dom.createSpanFromMarkdown(description)); + + return /** @type {!Element} **/ (element); + } + + /** + * @param {!ReportRenderer.CategoryJSON} category + * @return {!Element} + */ + _renderCategoryScore(category) { + const tmpl = this._dom.cloneTemplate('#tmpl-lh-category-score', this._templateContext); + const score = Math.round(category.score); + return this._populateScore(tmpl, score, 'numeric', category.name, category.description); + } + + /** + * @param {!ReportRenderer.AuditJSON} audit + * @return {!Element} + */ + _renderAudit(audit) { + const element = this._dom.createElement('div', 'lh-audit'); + element.appendChild(this._renderAuditScore(audit)); + return element; + } + + /** + * @param {!Document|!Element} context + */ + setTemplateContext(context) { + this._templateContext = context; + } + + /** + * @param {!ReportRenderer.CategoryJSON} category + * @return {!DocumentFragment} + */ + renderScoreGauge(category) { + const tmpl = this._dom.cloneTemplate('#tmpl-lh-gauge', this._templateContext); + this._dom.find('.lh-gauge__wrapper', tmpl).href = `#${category.id}`; + this._dom.find('.lh-gauge__label', tmpl).textContent = category.name; + + const score = Math.round(category.score); + const fillRotation = Math.floor((score / 100) * 180); + + const gauge = this._dom.find('.lh-gauge', tmpl); + gauge.setAttribute('data-progress', score); // .dataset not supported in jsdom. + gauge.classList.add(`lh-gauge--${Util.calculateRating(score)}`); + + this._dom.findAll('.lh-gauge__fill', gauge).forEach(el => { + el.style.transform = `rotate(${fillRotation}deg)`; + }); + + this._dom.find('.lh-gauge__mask--full', gauge).style.transform = + `rotate(${fillRotation}deg)`; + this._dom.find('.lh-gauge__fill--fix', gauge).style.transform = + `rotate(${fillRotation * 2}deg)`; + this._dom.find('.lh-gauge__percentage', gauge).textContent = score; + + return tmpl; + } + + /** + * @param {!ReportRenderer.CategoryJSON} category + * @return {!Element} + */ + render(category) { + const element = this._dom.createElement('div', 'lh-category'); + element.id = category.id; + element.appendChild(this._renderCategoryScore(category)); + + const passedAudits = category.audits.filter(audit => audit.score === 100); + const nonPassedAudits = category.audits.filter(audit => !passedAudits.includes(audit)); + + for (const audit of nonPassedAudits) { + element.appendChild(this._renderAudit(audit)); + } + + // Don't create a passed section if there are no passed. + if (!passedAudits.length) { + return element; + } + + const passedElem = this._dom.createElement('details', 'lh-passed-audits'); + const passedSummary = this._dom.createElement('summary', 'lh-passed-audits-summary'); + passedSummary.textContent = `View ${passedAudits.length} passed items`; + passedElem.appendChild(passedSummary); + + for (const audit of passedAudits) { + passedElem.appendChild(this._renderAudit(audit)); + } + element.appendChild(passedElem); + return element; + } +} + +if (typeof module !== 'undefined' && module.exports) { + module.exports = CategoryRenderer; +} else { + self.CategoryRenderer = CategoryRenderer; +}
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/details-renderer.js b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/details-renderer.js index b9b53227..98c9375d 100644 --- a/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/details-renderer.js +++ b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/details-renderer.js
@@ -15,28 +15,29 @@ */ 'use strict'; +/* globals self */ + class DetailsRenderer { /** * @param {!DOM} dom */ constructor(dom) { + /** @private {!DOM} */ this._dom = dom; } /** - * @param {(!DetailsRenderer.DetailsJSON|!DetailsRenderer.CardsDetailsJSON)} details + * @param {!DetailsRenderer.DetailsJSON} details * @return {!Element} */ render(details) { switch (details.type) { case 'text': return this._renderText(details); - case 'block': - return this._renderBlock(details); case 'cards': return this._renderCards(/** @type {!DetailsRenderer.CardsDetailsJSON} */ (details)); case 'list': - return this._renderList(details); + return this._renderList(/** @type {!DetailsRenderer.ListDetailsJSON} */ (details)); default: throw new Error(`Unknown type: ${details.type}`); } @@ -53,20 +54,7 @@ } /** - * @param {!DetailsRenderer.DetailsJSON} block - * @return {!Element} - */ - _renderBlock(block) { - const element = this._dom.createElement('div', 'lh-block'); - const items = block.items || []; - for (const item of items) { - element.appendChild(this.render(item)); - } - return element; - } - - /** - * @param {!DetailsRenderer.DetailsJSON} list + * @param {!DetailsRenderer.ListDetailsJSON} list * @return {!Element} */ _renderList(list) { @@ -78,8 +66,7 @@ } const itemsElem = this._dom.createElement('div', 'lh-list__items'); - const items = list.items || []; - for (const item of items) { + for (const item of list.items) { itemsElem.appendChild(this.render(item)); } element.appendChild(itemsElem); @@ -119,22 +106,30 @@ if (typeof module !== 'undefined' && module.exports) { module.exports = DetailsRenderer; +} else { + self.DetailsRenderer = DetailsRenderer; } /** * @typedef {{ * type: string, - * text: (string|undefined), - * header: (!DetailsRenderer.DetailsJSON|undefined), - * items: (!Array<!DetailsRenderer.DetailsJSON>|undefined) + * text: (string|undefined) * }} */ DetailsRenderer.DetailsJSON; // eslint-disable-line no-unused-expressions +/** + * @typedef {{ + * type: string, + * header: ({text: string}|undefined), + * items: !Array<{type: string, text: (string|undefined)}> + * }} + */ +DetailsRenderer.ListDetailsJSON; // eslint-disable-line no-unused-expressions + /** @typedef {{ * type: string, - * text: string, - * header: !DetailsRenderer.DetailsJSON, + * header: ({text: string}|undefined), * items: !Array<{title: string, value: string, snippet: (string|undefined), target: string}> * }} */
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/dom.js b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/dom.js index 6989856..412087a 100644 --- a/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/dom.js +++ b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/dom.js
@@ -15,13 +15,14 @@ */ 'use strict'; -/* globals URL */ +/* globals URL self */ class DOM { /** * @param {!Document} document */ constructor(document) { + /** @private {!Document} */ this._document = document; } @@ -33,9 +34,7 @@ * set the attribute on the node. * @return {!Element} */ - createElement(name, className, attrs) { - // TODO(all): adopt `attrs` default arg when https://codereview.chromium.org/2821773002/ lands - attrs = attrs || {}; + createElement(name, className, attrs = {}) { const element = this._document.createElement(name); if (className) { element.className = className; @@ -51,16 +50,36 @@ /** * @param {string} selector - * @param {!Document|!Element} context + * @param {!Node} context * @return {!DocumentFragment} A clone of the template content. * @throws {Error} */ cloneTemplate(selector, context) { - const template = context.querySelector(selector); + const template = /** @type {?HTMLTemplateElement} */ (context.querySelector(selector)); if (!template) { throw new Error(`Template not found: template${selector}`); } - return /** @type {!DocumentFragment} */ (this._document.importNode(template.content, true)); + + const clone = /** @type {!DocumentFragment} */ ( + this._document.importNode(template.content, true)); + + // Prevent duplicate styles in the DOM. After a template has been stamped + // for the first time, remove the clone's styles so they're not re-added. + if (template.hasAttribute('data-stamped')) { + this.findAll('style', clone).forEach(style => style.remove()); + } + template.setAttribute('data-stamped', true); + + return clone; + } + + /** + * Resets the "stamped" state of the templates. + */ + resetTemplates() { + this.findAll('template[data-stamped]', this._document).forEach(t => { + t.removeAttribute('data-stamped'); + }); } /** @@ -80,7 +99,7 @@ // Append link if there are any. if (linkText && linkHref) { - const a = this.createElement('a'); + const a = /** @type {!HTMLAnchorElement} */ (this.createElement('a')); a.rel = 'noopener'; a.target = '_blank'; a.textContent = linkText; @@ -98,8 +117,35 @@ document() { return this._document; } + + /** + * Guaranteed context.querySelector. Always returns an element or throws if + * nothing matches query. + * @param {string} query + * @param {!Node} context + * @return {!Element} + */ + find(query, context) { + const result = context.querySelector(query); + if (result === null) { + throw new Error(`query ${query} not found`); + } + return result; + } + + /** + * Helper for context.querySelectorAll. Returns an Array instead of a NodeList. + * @param {string} query + * @param {!Node} context + * @return {!Array<!Element>} + */ + findAll(query, context) { + return Array.from(context.querySelectorAll(query)); + } } if (typeof module !== 'undefined' && module.exports) { module.exports = DOM; +} else { + self.DOM = DOM; }
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/report-renderer.js b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/report-renderer.js index 88c28fb..f5318cda 100644 --- a/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/report-renderer.js +++ b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/report-renderer.js
@@ -22,78 +22,38 @@ * Dummy text for ensuring report robustness: </script> pre$`post %%LIGHTHOUSE_JSON%% */ -const RATINGS = { - PASS: {label: 'pass', minScore: 75}, - AVERAGE: {label: 'average', minScore: 45}, - FAIL: {label: 'fail'} -}; - -/** - * Convert a score to a rating label. - * @param {number} score - * @return {string} - */ -function calculateRating(score) { - let rating = RATINGS.FAIL.label; - if (score >= RATINGS.PASS.minScore) { - rating = RATINGS.PASS.label; - } else if (score >= RATINGS.AVERAGE.minScore) { - rating = RATINGS.AVERAGE.label; - } - return rating; -} - -/** - * Format number. - * @param {number} number - * @return {string} - */ -function formatNumber(number) { - return number.toLocaleString(undefined, {maximumFractionDigits: 1}); -} +/* globals self, Util */ class ReportRenderer { /** * @param {!DOM} dom - * @param {!DetailsRenderer} detailsRenderer + * @param {!CategoryRenderer} categoryRenderer + * @param {?ReportUIFeatures=} uiFeatures */ - constructor(dom, detailsRenderer) { + constructor(dom, categoryRenderer, uiFeatures = null) { + /** @private {!DOM} */ this._dom = dom; - this._detailsRenderer = detailsRenderer; - + /** @private {!CategoryRenderer} */ + this._categoryRenderer = categoryRenderer; + /** @private {!Document|!Element} */ this._templateContext = this._dom.document(); + /** @private {ReportUIFeatures} */ + this._uiFeatures = uiFeatures; } /** * @param {!ReportRenderer.ReportJSON} report - * @return {!Element} + * @param {!Element} container Parent element to render the report into. */ - renderReport(report) { - try { - return this._renderReport(report); - } catch (e) { - return this._renderException(e); + renderReport(report, container) { + container.textContent = ''; // Remove previous report. + const element = container.appendChild(this._renderReport(report)); + + // Hook in JS features and page-level event listeners after the report + // is in the document. + if (this._uiFeatures) { + this._uiFeatures.initFeatures(report); } - } - - /** - * @param {!DocumentFragment|!Element} element DOM node to populate with values. - * @param {number} score - * @param {string} scoringMode - * @param {string} title - * @param {string} description - * @return {!Element} - */ - _populateScore(element, score, scoringMode, title, description) { - // Fill in the blanks. - const valueEl = element.querySelector('.lh-score__value'); - valueEl.textContent = formatNumber(score); - valueEl.classList.add(`lh-score__value--${calculateRating(score)}`, - `lh-score__value--${scoringMode}`); - - element.querySelector('.lh-score__title').textContent = title; - element.querySelector('.lh-score__description') - .appendChild(this._dom.createSpanFromMarkdown(description)); return /** @type {!Element} **/ (element); } @@ -105,54 +65,70 @@ */ setTemplateContext(context) { this._templateContext = context; + this._categoryRenderer.setTemplateContext(context); } /** - * @param {!ReportRenderer.AuditJSON} audit - * @return {!Element} + * @param {!ReportRenderer.ReportJSON} report + * @return {!DocumentFragment} */ - _renderAuditScore(audit) { - const tmpl = this._dom.cloneTemplate('#tmpl-lh-audit-score', this._templateContext); + _renderReportHeader(report) { + const header = this._dom.cloneTemplate('#tmpl-lh-heading', this._templateContext); + this._dom.find('.lh-config__timestamp', header).textContent = + Util.formatDateTime(report.generatedTime); + const url = this._dom.find('.lh-metadata__url', header); + url.href = report.url; + url.textContent = report.url; - const scoringMode = audit.result.scoringMode; - const description = audit.result.helpText; - let title = audit.result.description; + const env = this._dom.find('.lh-env__items', header); + report.runtimeConfig.environment.forEach(runtime => { + const item = this._dom.cloneTemplate('#tmpl-lh-env__items', env); + this._dom.find('.lh-env__name', item).textContent = runtime.name; + this._dom.find('.lh-env__description', item).textContent = runtime.description; + this._dom.find('.lh-env__enabled', item).textContent = + runtime.enabled ? 'Enabled' : 'Disabled'; + env.appendChild(item); + }); - if (audit.result.displayValue) { - title += `: ${audit.result.displayValue}`; - } - if (audit.result.optimalValue) { - title += ` (target: ${audit.result.optimalValue})`; - } - - // Append audit details to header section so the entire audit is within a <details>. - const header = tmpl.querySelector('.lh-score__header'); - header.open = audit.score < 100; // expand failed audits - if (audit.result.details) { - header.appendChild(this._detailsRenderer.render(audit.result.details)); - } - - return this._populateScore(tmpl, audit.score, scoringMode, title, description); + return header; } /** - * @param {!ReportRenderer.CategoryJSON} category - * @return {!Element} + * @param {!ReportRenderer.ReportJSON} report + * @return {!DocumentFragment} */ - _renderCategoryScore(category) { - const tmpl = this._dom.cloneTemplate('#tmpl-lh-category-score', this._templateContext); - const score = Math.round(category.score); - return this._populateScore(tmpl, score, 'numeric', category.name, category.description); + _renderReportFooter(report) { + const footer = this._dom.cloneTemplate('#tmpl-lh-footer', this._templateContext); + this._dom.find('.lh-footer__version', footer).textContent = report.lighthouseVersion; + this._dom.find('.lh-footer__timestamp', footer).textContent = + Util.formatDateTime(report.generatedTime); + return footer; } /** - * @param {!Error} e - * @return {!Element} + * @param {!ReportRenderer.ReportJSON} report + * @return {!DocumentFragment} */ - _renderException(e) { - const element = this._dom.createElement('div', 'lh-exception'); - element.textContent = String(e.stack); - return element; + _renderReportNav(report) { + const leftNav = this._dom.cloneTemplate('#tmpl-lh-leftnav', this._templateContext); + + this._dom.find('.leftnav__header__version', leftNav).textContent = + `Version: ${report.lighthouseVersion}`; + + const nav = this._dom.find('.lh-leftnav', leftNav); + for (const category of report.reportCategories) { + const itemsTmpl = this._dom.cloneTemplate('#tmpl-lh-leftnav__items', leftNav); + + const navItem = this._dom.find('.lh-leftnav__item', itemsTmpl); + navItem.href = `#${category.id}`; + + this._dom.find('.leftnav-item__category', navItem).textContent = category.name; + const score = this._dom.find('.leftnav-item__score', navItem); + score.classList.add(`lh-score__value--${Util.calculateRating(category.score)}`); + score.textContent = Math.round(Util.formatNumber(category.score)); + nav.appendChild(navItem); + } + return leftNav; } /** @@ -160,69 +136,48 @@ * @return {!Element} */ _renderReport(report) { - const element = this._dom.createElement('div', 'lh-report'); + const container = this._dom.createElement('div', 'lh-container'); + + container.appendChild(this._renderReportHeader(report)); // sticky header goes at the top. + container.appendChild(this._renderReportNav(report)); + + const reportSection = container.appendChild(this._dom.createElement('div', 'lh-report')); + + const scoreHeader = reportSection.appendChild( + this._dom.createElement('div', 'lh-scores-header')); + + const categories = reportSection.appendChild(this._dom.createElement('div', 'lh-categories')); for (const category of report.reportCategories) { - element.appendChild(this._renderCategory(category)); - } - return element; - } - - /** - * @param {!ReportRenderer.CategoryJSON} category - * @return {!Element} - */ - _renderCategory(category) { - const element = this._dom.createElement('div', 'lh-category'); - element.appendChild(this._renderCategoryScore(category)); - - const passedAudits = category.audits.filter(audit => audit.score === 100); - const nonPassedAudits = category.audits.filter(audit => !passedAudits.includes(audit)); - - for (const audit of nonPassedAudits) { - element.appendChild(this._renderAudit(audit)); + scoreHeader.appendChild(this._categoryRenderer.renderScoreGauge(category)); + categories.appendChild(this._categoryRenderer.render(category)); } - // don't create a passed section if there are no passed - if (!passedAudits.length) return element; + reportSection.appendChild(this._renderReportFooter(report)); - const passedElem = this._dom.createElement('details', 'lh-passed-audits'); - const passedSummary = this._dom.createElement('summary', 'lh-passed-audits-summary'); - passedSummary.textContent = `View ${passedAudits.length} passed items`; - passedElem.appendChild(passedSummary); - - for (const audit of passedAudits) { - passedElem.appendChild(this._renderAudit(audit)); - } - element.appendChild(passedElem); - return element; - } - - /** - * @param {!ReportRenderer.AuditJSON} audit - * @return {!Element} - */ - _renderAudit(audit) { - const element = this._dom.createElement('div', 'lh-audit'); - element.appendChild(this._renderAuditScore(audit)); - return element; + return container; } } if (typeof module !== 'undefined' && module.exports) { module.exports = ReportRenderer; +} else { + self.ReportRenderer = ReportRenderer; } /** * @typedef {{ - * id: string, weight: - * number, score: number, + * id: string, + * weight: number, + * score: number, * result: { * description: string, + * debugString: string, * displayValue: string, * helpText: string, * score: (number|boolean), * scoringMode: string, - * details: (!DetailsRenderer.DetailsJSON|!DetailsRenderer.CardsDetailsJSON|undefined) + * optimalValue: number, + * details: (!DetailsRenderer.DetailsJSON|undefined) * } * }} */ @@ -231,6 +186,7 @@ /** * @typedef {{ * name: string, + * id: string, * weight: number, * score: number, * description: string, @@ -241,12 +197,15 @@ /** * @typedef {{ - * lighthouseVersion: !string, - * generatedTime: !string, - * initialUrl: !string, - * url: !string, - * audits: ?Object, - * reportCategories: !Array<!ReportRenderer.CategoryJSON> + * lighthouseVersion: string, + * generatedTime: string, + * initialUrl: string, + * url: string, + * reportCategories: !Array<!ReportRenderer.CategoryJSON>, + * runtimeConfig: { + * blockedUrlPatterns: !Array<string>, + * environment: !Array<{description: string, enabled: boolean, name: string}> + * } * }} */ ReportRenderer.ReportJSON; // eslint-disable-line no-unused-expressions
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/util.js b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/util.js new file mode 100644 index 0000000..d92c65c --- /dev/null +++ b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/renderer/util.js
@@ -0,0 +1,78 @@ +/** + * Copyright 2017 Google Inc. 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. + */ +'use strict'; + +/* globals self */ + +const RATINGS = { + PASS: {label: 'pass', minScore: 75}, + AVERAGE: {label: 'average', minScore: 45}, + FAIL: {label: 'fail'} +}; + +class Util { + /** + * Convert a score to a rating label. + * @param {number} score + * @return {string} + */ + static calculateRating(score) { + let rating = RATINGS.FAIL.label; + if (score >= RATINGS.PASS.minScore) { + rating = RATINGS.PASS.label; + } else if (score >= RATINGS.AVERAGE.minScore) { + rating = RATINGS.AVERAGE.label; + } + return rating; + } + + /** + * Format number. + * @param {number} number + * @return {string} + */ + static formatNumber(number) { + return number.toLocaleString(undefined, {maximumFractionDigits: 1}); + } + + /** + * Format time. + * @param {string} date + * @return {string} + */ + static formatDateTime(date) { + const options = { + month: 'short', day: 'numeric', year: 'numeric', + hour: 'numeric', minute: 'numeric', timeZoneName: 'short' + }; + let formatter = new Intl.DateTimeFormat('en-US', options); + + // Force UTC if runtime timezone could not be detected. + // See https://github.com/GoogleChrome/lighthouse/issues/1056 + const tz = formatter.resolvedOptions().timeZone; + if (!tz || tz.toLowerCase() === 'etc/unknown') { + options.timeZone = 'UTC'; + formatter = new Intl.DateTimeFormat('en-US', options); + } + return formatter.format(new Date(date)); + } +} + +if (typeof module !== 'undefined' && module.exports) { + module.exports = Util; +} else { + self.Util = Util; +}
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/report-styles.css b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/report-styles.css index 5967638..9fa0003 100644 --- a/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/report-styles.css +++ b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/report-styles.css
@@ -14,9 +14,11 @@ * limitations under the License. */ - :root { +.lh-root { --text-font-family: '.SFNSDisplay-Regular', 'Helvetica Neue', 'Lucida Grande', sans-serif; --body-font-size: 13px; + --header-font-size: 16px; + --body-line-height: 1.5; --default-padding: 16px; --secondary-text-color: #565656; @@ -26,7 +28,11 @@ --average-color: #ef6c00; /* md orange 800 */ --warning-color: #757575; /* md grey 600 */ - --report-border-color: #ebebeb; + --report-border-color: #ccc; + --report-secondary-border-color: #ebebeb; + --report-width: 850px; + --report-menu-width: 280px; + --report-content-width: calc(var(--report-width) + var(--report-menu-width)); --lh-score-highlight-bg: #fafafa; --lh-score-icon-background-size: 17px; @@ -35,35 +41,75 @@ --lh-category-score-width: 50px; } -* { +.lh-root * { box-sizing: border-box; } -body { +.lh-root { font-family: var(--text-font-family); font-size: var(--body-font-size); margin: 0; line-height: var(--body-line-height); + background: #f5f5f5; + scroll-behavior: smooth; } -[hidden] { +.lh-root [hidden] { display: none !important; } +a { + color: #0c50c7; +} + +summary { + cursor: pointer; +} + .lh-details { font-size: smaller; margin-top: var(--default-padding); } -.lh-details summary { - cursor: pointer; -} - .lh-details[open] summary { margin-bottom: var(--default-padding); } +/* Report header */ + +.report-icon { + opacity: 0.7; +} +.report-icon:hover { + opacity: 1; +} +.report-icon[disabled] { + opacity: 0.3; + pointer-events: none; +} + +.report-icon--share { + background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z"/></svg>'); +} +.report-icon--print { + background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z"/><path fill="none" d="M0 0h24v24H0z"/></svg>'); +} +.report-icon--copy { + background-image: url('data:image/svg+xml;utf8,<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>'); +} +.report-icon--open { + background-image: url('data:image/svg+xml;utf8,<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h4v-2H5V8h14v10h-4v2h4c1.1 0 2-.9 2-2V6c0-1.1-.89-2-2-2zm-7 6l-4 4h3v6h2v-6h3l-4-4z"/></svg>'); +} +.report-icon--download { + background-image: url('data:image/svg+xml;utf8,<svg height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/><path d="M0 0h24v24H0z" fill="none"/></svg>'); +} + /* List */ +.lh-list { + font-size: smaller; + margin-top: var(--default-padding); +} + .lh-list__items { padding-left: var(--default-padding); } @@ -143,7 +189,7 @@ } .lh-score__value--binary { - text-indent: -500px; + text-indent: -5000px; } /* No icon for audits with number scores. */ @@ -200,26 +246,27 @@ margin-top: 2px; } -.lh-score__header[open] .lh-score__arrow { +.lh-score__header[open] .lh-toggle-arrow { transform: rotateZ(90deg); } -.lh-score__arrow { +.lh-toggle-arrow { background: url('data:image/svg+xml;utf8,<svg fill="black" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z"/><path d="M0-.25h24v24H0z" fill="none"/></svg>') no-repeat 50% 50%; background-size: contain; background-color: transparent; width: 24px; height: 24px; flex: none; - margin: 0 8px 0 8px; + margin-left: calc(var(--default-padding) / 2); transition: transform 150ms ease-in-out; + cursor: pointer; + border: none; } .lh-score__snippet { display: flex; align-items: center; justify-content: space-between; - cursor: pointer; /*outline: none;*/ } @@ -243,26 +290,48 @@ .lh-audit > .lh-score { font-size: 16px; + font-size: var(--header-font-size); +} + +.lh-debug { + margin-top: calc(var(--default-padding) / 2); + margin-left: calc(var(--lh-audit-score-width) + var(--lh-score-margin)); + color: var(--fail-color); } /* Report */ +.lh-container { + display: flex; + max-width: var(--report-content-width); + margin: 0 auto; + /*border-right: 1px solid var(--report-border-color);*/ + /*border-left: 1px solid var(--report-border-color);*/ +} + +.lh-report { + margin-left: var(--report-menu-width); + background-color: #fff; +} + .lh-exception { font-size: large; } -.lh-report { - padding: var(--default-padding); +.lh-scores-header { + display: flex; + margin: var(--report-header-height) 0 0 0; + padding: calc(var(--default-padding) * 2) var(--default-padding); + border-bottom: 1px solid var(--report-border-color); } .lh-category { - padding: 24px 0; + padding: 24px calc(var(--default-padding) * 2); border-top: 1px solid var(--report-border-color); } .lh-category:first-of-type { border: none; - padding-top: 0; } .lh-category > .lh-audit, @@ -289,16 +358,70 @@ } summary.lh-passed-audits-summary { - margin: 10px 5px; + margin: var(--default-padding); font-size: 15px; + display: flex; + align-items: center; } summary.lh-passed-audits-summary::-webkit-details-marker { - background: rgb(66, 175, 69); + background: var(--pass-color); color: white; - position:relative; + position: relative; content: ''; - padding: 3px; + padding: 3px 3px 3px 6px; + border-radius: 2px; +} + +.lh-passed-audits[open] summary.lh-passed-audits-summary::-webkit-details-marker { + padding: 3px 5px 3px 4px; +} + +#lh-log { + position: fixed; + background-color: #323232; + color: #fff; + min-height: 48px; + min-width: 288px; + padding: 16px 24px; + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26); + border-radius: 2px; + margin: 12px; + font-size: 14px; + cursor: default; + transition: transform 0.3s, opacity 0.3s; + transform: translateY(100px); + opacity: 0; + -webkit-font-smoothing: antialiased; + bottom: 0; + left: 0; + z-index: 3; +} + +#lh-log.show { + opacity: 1; + transform: translateY(0); +} + +@media screen and (max-width: 767px) { + .lh-report { + margin-left: 0; + } + .lh-category { + padding: 24px var(--default-padding); + } +} + +@media print { + body { + -webkit-print-color-adjust: exact; /* print background colors */ + } + .lh-report { + margin-left: 0; + } + .lh-categories { + margin-top: 0; + } } /*# sourceURL=report.styles.css */
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/templates.html b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/templates.html index 32aff33..f58a3124 100644 --- a/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/templates.html +++ b/third_party/WebKit/Source/devtools/front_end/audits2/lighthouse/templates.html
@@ -18,9 +18,404 @@ <details class="lh-score__header"> <summary class="lh-score__snippet"> <span class="lh-score__title"><!-- fill me --></span> - <div class="lh-score__arrow" title="See audits"></div> + <div class="lh-toggle-arrow" title="See audits"></div> </summary> <div class="lh-score__description"><!-- fill me --></div> </details> </div> </template> + +<!-- Lighthouse left nav --> +<template id="tmpl-lh-leftnav"> + <style> + .lh-leftnav { + width: var(--report-menu-width); + border-right: 1px solid var(--report-secondary-border-color); + position: fixed; + height: 100%; + background: #fff; + will-change: transform; /* prevent excessive paints */ + z-index: 2; + } + .lh-leftnav__item { + padding: var(--default-padding); + color: var(--secondary-text-color); + font-size: 16px; + display: flex; + justify-content: space-between; + text-decoration: none; + color: inherit; + } + .leftnav-item__score { + background: transparent; + } + .leftnav-item__score.lh-score__value--pass { + color: var(--pass-color); + } + .leftnav-item__score.lh-score__value--average { + color: var(--average-color); + } + .leftnav-item__score.lh-score__value--fail { + color: var(--fail-color); + } + .leftnav__header { + padding: 0 20px; + height: 115px; + font-size: 18px; + display: flex; + flex-direction: column; + justify-content: center; + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZoAAADeCAYAAAAEuMatAABPH0lEQVR4Ae3dBZyk1ZX38d+59z5PSfv0uMPg7iQB4oRkIbrZbGSz7Js368mbrLtE1903nuzGVuIGgQgxCBbIAAPjru1d8sg9b091fxhBMsN0T1V13+/ncz5VNRaFP+ee89zi8QRBEARBEARBEARBEARBIARB0BTZuru7Be0HOoFYlcirAqgIiaoMGKtbAaWNBYFkD9/JzAuC4H3/57KeZKB0Wb/jea9YVrowFbNC0KVAj4Cr50olzTACCKCy11j/QLG/tlmEb5bXp18GdtFmgkDSy3lS7qxzLKsWzSfVcWCMIAiOzxfvuIZV3S+lp/hSUk5DYSzzCIcIkORKJcsQDqOg3gBgUj8qyP8YlU8DnyEI2oTsfc4ynkjvm65eaJ9z8cuMdQvwmoCpgAwiOlH2AERDCONABZUxICUIgob6R7/ynOz7D/+yjlRuEE8BryAccixBczQBUXCj+Xc73difhcAJ2oFUn1PmcV19dnfxZ1/yM2CXkucJIEeWGsCD1IAKSBUvIyCDGD0AphFEUz9fb9QcEATZvetPSd/7uT/2ef56CpFgDIecYNBMUSOAYES+YJ37XeA+WlQQyN6Xr+bxzP+XX3upJEPPIPdVnpwB5LBXEBQa5UHGgFGQUXIzBDIETFakg6B1IGMWCILq337+NfnajX8lxcISjDBl2oPmcCIMl7fXfh/4R1pQEMjwhX0cLfq5a1eUXn3lGxgZtSCeE2MeLcEAOZABOaIJyDAwAAxh4iFERqaCaQzrhwGlxQWBfv3+qP4/t/xZVo9+RZxhykkJGgCNDEZ4rxV5C1ChtQSho1nO0fr/6NWvMPN6Lif3dWaGABzVCZmpSoAaUMdSRRgGBkEGcbof0RGgAlIBMoKgSe5ev6KcRoVC18Z1C0758Hve7p39SZwBOMlBc4hFPmeM3AgM0iKCwEmqHK7nRef1miXzz6JST5k5etir50gCFIAiOX3AUkBAIUUQqaIyBhMlOlGMAIMoQzg9gGUMJQFSQDlOQbB7sLsDm5f/4+tXdW7b1rM0sqxQdInAotTbRV+9/8z+pEp3LnQV8aU/tz+39PSC70kkotly9MWo/9SY6XtNWIVuFYFsePHZHG78rTc+/fxz6jdQI6U1CWAOKwV8o+RgSRUYRhhBGUAZQnQIzBCqw+BrIYACgA994+m9Y2OF00yUnXLHnlWrvn3XmhVdsEpFVwBLKnWzMMuwIkxSEIFyQfEiGFL+2Pw6L+E/qVFGEYCmdjQABWrcaZ73wG8W/uPm3fPjnRbdirLJCI+IMAgoJ1EQyIpfyJnCaZ25fOqtf/36nrR6FpiE9iOPE0QeSBBJUFJEx0GGEBkE9qN+GBgDxhEZAZRgVvnSHWeVXSlddd8jq9d89vbzzi0V9FxVOXvTvu4VWUqXCOWCgUIBlENElMejQBXhzfLX/LK8m+qhkGmJoBGUAhU+ZH6Pf+U36aChKjAK7BR4QIW1ueUhYKsxbAH2MUOCQC55hTKFC/vW9r3/Tz76C4wXSoBn9pAnKIOQg1SBg1UBHQUZRBgAOYD3gwg1aFRK0NLW7Z7fsXt/39L33HTVeXGUP21guPviezb2L7OWJc7SFztAaYic8lRUEJ4nX+Ov5KcxeDIcQMsEDYAhx+L5Q/cRviYvpIjyBKoi7B4fZHNW415V+a6HtcBuYIBpEASy6gplCl/9t387/7SF219LEtWZO44MHhAmKQ2SIkxtwukoMAIyBAwjcgCfDwFZo0664LaHTu/64t1nnke9+MzP337uBanlTIFzvacIYA0UI0WZHhlCHwO817yMNTxEjRJAywUNQEyNHXIab7Zf4YD0EaE8EWMBYQqKss5Y1lq4R+EbKfJDYATwBMFxcvNW8ah0xK5iIcrcolMFkPMYKih9QD9gaNAcyFFyRGogI8AwRgbI/QDCKDCKyBAwzrQJ/ufWS1099pd86juXPX3z7r6nZXn89OFxWWYNrqOoOB5LmT4Z8Fp5L2ewlgqdtLKEIqv0QV6tf8/fyR8R8cR8zuEEOMvnnJXCjwO5ovtR7rQi30G402V8BxgjCI6BXH6DAvCMNevkz3//Yz8fj8kKkJTgyQjA46xoWwBEEqAO1EArIMPAIEYOkPv9wDhQmXr1BE/qextWnvuhW64+f/POeS9Yv2PeVRksLxcoOwMIGFFOhgThNDbwPvNiuhgmwwG0bEcDYMmoSwe/aG9ho5xKAWUaeOPZauCutCZf3rOOO1S4H1CC4HHIolUKwOtf84P+v/iNj7+RkVInkBNM53Hc4T+mIBXQcZAxiMeBySCSxkzoAFADkkbNQVv29/R84JZrTt+8e97131q79PkYLoosncZA7JRmUIQa8LvyB/y0/DPjdAK0fNCA0MEwn7Rv4S/MuyijTBcVEEAMVRV+oMLtqvIZ4F5gkCCYIhf8uALwyivvOeMPbvyv11MpekCZSYE5VCqAAh5BQXJgMnxERlAdBIanPg+hfgDVFPDMIg9tX1z87D1nPfPOh8684f7Ni5/rvZwLUC4orSBBWM5OPmReSB8HyHBtEjQQkTDAYt4U3cxOlhChzLBHVLnVeG4e7JObgRGCOc2NlGm48fnfWUg9ioAaMy3wUwUIj1IABOgCelA1gAFyIJ0skyCMggwh/gCqEyUjj86FoEKb+M7Dqwp/d8fVz9r24MIbSAo/tmsgXl0uYouRAkoryYBnyK0sZCdVOmgnGRGL2cIl+nU2y2uImHGni3A6lp/vqOpWb/nynsXyhTp8ExgimHPcvnk0dEWVhSCeoNl0qjyHSKNUC0AR6AFdgWJABEhAqqBVvKsiZnJF2+T7yQr70KgC1BBt+rU9P9i+oPjNe8666LaHlr30we2LXpqmnB1FIAK9nUorUoQIz/Pkc4DQbhQhw/Ec/2m+Yn8SxSAoM02BOGEl8HOnbNSf8ykbxg9wi4p8TOD7wDhzQuCWfRdWnlkR63QB4AlakR56fVyCahnoQFIBBAAPmBRE6iBjiB/BuxGIRoAhxA+QR/ungigDUmbIr9/03FOHHu77iVsfWPby2HIlQCGCUlFpdSmwWrazhnVkRLSjjIg1ej+djDBML5aTQ4WGXIAiazpXsgb05wTuxvNFKcgnpcBawDNrBXL5jcrPveBrpTdee/P/Y7wYFgFmJzlsJmSYlCPkqOSIVEGGER3EuwF8NAwMoYxQYBio8xR8/BsXlbfumn/DR799/ktG6/bFkdJdLihKe6kgvEBu5m/lddQoAgLQNjMaAINHEd7t3sPNcgNFlJYg1FX4qop8PEr57Oyc5wTOD0BhWHuBCFBmo0CBfLKERykCWFS7QHtQVkNisIkH6igJSp2IKsgAogOo3U9eGAYqCOPAKEf59HfPP+O9X73iVQ9unPdTKGd2laA7VgCU9mOg8dyMJQOEduQxdDDKGl3LFxtB0yKUgijXC3q9wmbj+S9V+ShwL7NG4Mgh7ZBefAiaNjQDt2jLoQCCMjllMuYBy0EEvEGq+VTQVIARjB1Mk6jy/YdPWfr7n7/2RYPbup9jnfbP68ypp5YkAxHBCIiAESaJIrQ2RXDASjbgsbQzwVPWYQytyRtW55bfENU3E3MzVt6rwteAUYK25rIesOQ9gANSggB0qiYJRxOMdwhd1Wph2cM7F527bseiC/YNdS99zXn34s8Xxusx47UCo9UiY7WDFTNaKTJUKTFWLZB7g5+ozINXEMAYpoJIaSUGWC5byLG0s5SYFWygm4wEh0VpNaIAFKnzYtCJ4h6FD8WpfBTYR9CWXDQEL7/qni6qBfukQRMEAhifgeZDo539D25dcvEjuxZfPBEgfaA46/EIAnQV6/SUapj+IUDJvWlU5i311DbCZqRSYvhg8NQKVA4G00SNVYtU6hGqNCiThEPdkIgy01KEjEkCdDLMyaAzfHzWowcQlCpgESLAoTwV3gtJDtZAZJVpJTQIXHywfKS/Jrl8RHM+DKwjaCtyyQuV7/7du14ex/kVQJ2jBYGgGJ+j6IGhrkVrty29aP2uhReN1wpdzipGPD+KAIgCIAKCYmSyEEgyO1GOWhJRT10jfIbGywxO1ESQUU0d9SSi2vh5g+pU6BxeAJx4ECUI8xnkeXyOpbINUF4qH6OL4RntanJVMq/MFEPOKPO41byCGmU2s4Zv6I8xQgcRyvHIvFBwGacu2sS+4QXsHemd+VsbBIADAh8zKv8KrCVoC/K6X97p3vuWD766GKVnAylBcDibZ6joroG+ZfdvXnbl5j3zz03SqGBNjjHK9DgUPiL66PsGARTqWcR4bbLrqUzUwSAaHi9PVImRarERTlluSHNLltMgAuY4u6AEYTWb+DPzc5zH9/E4QKhRQhFmmjBzFMHgialiUIScb3Mtv+3/hSH6j7mz8SqU4grv+Mm38dwLb2HrvlX84cf/mLs2nk/BKSfJuCqfVZW/B75H0NLkNW/a2fH+t3zotUVXXwmSEwQA1mcounugZ8n9m5dfuWnPgguT1EXOekSUk01EMVMloiiC9wfL4FWoJlFjHjRSKTJaK07OhyZq4nPjOC7NLKrgG3XUUZxRBFCEFOWP5Dd5lfwb4/Qx23UwxN/r2/lnfQsllGNRqQsvvuwW/vINb4U8gtIoX/r2T/LWD7yTcgFAOYkyVfkE8A/A7QQtyWW5iVHKIEoAhIBBNN83ETD3bFz19C17+8+vp67gjCdyOc2iKuQHiyMJYIzSWazTXaqxfP5kGOVeGt1NmrmJspMdUKV0MHgar9WDnVESUZnqkNIccqBb6pxu7ieVInAokWYrT8xF8j1U38KxEgOj1S7II3AJoAxXulGawono64BXCHxCkb8FfkDQUuR3fmv9oj964yd/puDSMqDMTYFRj/Hp0Ejn/B9sXHHlwzsXX1JLXCma6mDakYgicMRxXONVlCw31NKoETjVg4GTxAxVyoyOxfxK9Te5OPkmtawAaQZpDl7BCFjDbNLBKJ/hdfym/wfKKMdCAVXPG5/3YW648lNs2H42f/6ZX2fX4AKcVZpsTOAjxjQC52GCliB619+eyryR14I65p5AVLF5MjZe7vnBppVXrtu2+NLxetzlrMeIMlsdCqBDCwkopMScyoM8N/8spl4nq+ZQT9BaSj5Sw+8agiwHYQYICDPKyKH/XWPqjNHFm/3HuIOnUUCPq8OsZ1CMlCQTjNAImRYyCLzHO/kbYDdNFYhu/aOLyM0NgGOOCQHj06QeFddtW3reDzYvv3pwrGOBszlGlLlL8Ahnyf2ca+6lJFUQQUUoZGP4u9eh1QREmHbqwecgwkwQYLxeppoUEFF2sZz/0F/kq/pCIkBQjpeqtHTHK45NQzvkr8cH+AAwTlMEolvecTXePwewzClhk2z9jkVn3b1h9bP2DXetEFGs8QQAQoajSIUCdRTwavixp9/N/Pm7IQcQpp/C4B6ojoIYppUo2Ix//tKNfPS7z6cjVoboY5QCRQCU2cpYEOFuQd4BfJrjpB30qNIjUGRSAowCBwiOiejmt12HckUImjkyhxGf7RvsWXz3+tXXbNi94CJVcNYTPJbHoAgAuTe86nnfY9GqPaAWVJl2YiCrw96tkCUgMu1B8+7/eTP/dut1dBXAABZlLokyPm28vAu4kyfgu+jP+rjCC9dV53GONSxVZZ4IJSbVUYaBnSiPqHKLV24DdhI8LgdSBhWC2UsAmyfVaqF8z4aVz3pg67Kn1ZKo7GyOGJ5AYPAcouA9ZB48M8SDRFDug4HdIExv0KhifUYMRChzURLxMtDnCfKP3vCXwABTagu4uL6AnykrL1FhNYA9+iraQxYBZwDPFvhZa9iP50uqfBC4leBxg4bZLTwPs2H74jPvePjU5+8f6Vwa2ZzI5bSiQKHcA2NDkNRAhGB68xboAv0d43mJyeV3h5awTubzK7HwUx05HQoIx0mZj/B6EV4twmeBdwL3EjQ4lCIgBLNx2J8MT6wr377u1Gdt2LXgYo+Y2GW0sEAVrJ0Mm6QKCDMgUMBzbjqPTxfnMaDQjwflhEUKP64511W28Bfe86dAEoJGNEYJZhPjM/XG3Ld+9ZV3r1/17PFa3OOsx4knaJOwKXXAqIN82rfQAgUMZIuFvB9B6UeZPgpi6Ow6g7chXK7KzwK753jQMMuCJnQxBwa7F333oTXXbtk7/xwjvh2PyULQRAVwMeRVgukPmXQZ+D4gZ8b4FARu8I7Pbt3OK4GtzFEOJAYlaP9ZjM+NTHQxT7vnkVXPHU/izsjmBG0sLkK9AgjBNBHIlgq+F8hPUq55Ll9R5n+yUW4A9jAHORQHCG0tPHg5ONzV/50HTnvBxj0LznPG0+4hEwi4AiggBNPBQ7ZEyOcBOSeNerC9XObm8T6ElwEZc4wDNQTtyfgcFX1w87KLvrduzbXj1UJvGPbPEgJYByjBNPDgeyCfD3hOOvWNul7ht4B3Mcc4wBC0H5cnlWqh4ztrz3jBup2LLjNomMXMNiKNOkGBAhFkiwQE8DSNgd/0yufn2g3TDhFBlaBNiHpsnm7dvXDNtx847cf2j3QueZIbloMgUMjmgZaAnKZS6Eb4A+CVzCFhPtNOrM99buWuB0951l0bVj0nz20cu5xgFlLA+xOd0QQKWgDfJ+BpCQIvF8MzgW8yRzi8KiC0tsBlyeh4ufub9591/cbd88+LrMfZnGAWy1NOTCAKebegMa0UNCaHX8wM95cMw4CfC0dnHlVDSwvPxmzbveDU2x444/qBkY4lc6GLCRTSOgjBU6WgFnwXLUUB4/mJ2PNMEWoC6wVuQ7kF+C6zkAP1tKbAaK6ov/fh1U+fuKfsusybeI4M/AP1kNQAIXjqNAZfAjytxgJLMwWFU4EXAG+zwje85W+AzzGLOIQcpQWFBzBr9Tj+9trTrntw29IrrPU445kDAjFQG4P8hL4qINCpkLGAp2UJjzJeeY5mPAflw1h+HdjHLOBAUtBwsWYribL64GD3gq/df9ZLdg70rpljXUwgQHUMcg/G8AQCzyHCIXroVUtCuxEA4adRzspyXgVsoc050DrQRcsI85ituxas+fp9Z71spFrqD0/4zzEikNahMgJGCB6HBwxoGXwn+JKgEQ2iQB1MVbEjoBHtS7kisnwmHeF6YAdtzKEkBK0RMqLZ2k0rLv3W2tNfnHsTz8WQCQRGDkCeghiCo3jwXZD3C74TMBxBATrAi5AnoAJ42pfhQp/wntoOXgYk7dzRJDRXYHyuKnz3gdOfc+/Glc8VVKzxzDGBMVAZhfFhQDhMoIBAtkjIFwAG8I16QuoApa1pDoWFvKi0hJ8F/ok25RBTQb0CQnDy2TxLkjj6+n1n3vDwzsWXOBOe8p+TxEBah6E9oAoiBEeFzFIhnwco4PnRlFlBc8hzfgvhk8C+dn1gswIoQTNCJh2vljq/es85r9i6r/+MyOUISvMEXs0T/7gIGAGEaSUCSQ0GdkKegDVMOwGMoGLIgRzhaAYQlJajU7cu9wM5c9UK8bwC+DfakEO0gtIE4VLM/YM9C2/9wdmv3DvUtaz5ty4HRjwFk/B4cjFInkEtA2+YPgK1MRjci9brIMcwVJCncDONKNiMOB2nm1E6UQ5RFKFGGY9tzVuX+wHPnKbCq9s3aKwbJ8sITiKX1Xbt61/51XvPeeVIpbSg+evLQeojTu3cwrVLv0nuHQACIAKAihDtSknv2AtpBiJMH0VrCdkdD0Oag/Ckqt6TAsLxe3n9d3mReRuS8ShLTpUyb7cf4CE5nwilZRjI5wtICBrgfIFlwI42DJp0hEyU4OSIstq2ifXlm+8991XVetzd3PvKAhFFAGs8TnIilxIhAKgCuQfvIVd8LYPUQy4gTB9jIFG0kkGW/8gQ0zTDe0U4fmWp0SEKemTQVOjAkaAt2M34MuAJoFuFs9oyaP7s6y8e+ZUrv5LHJo0AZeYELq9v3Lb43FvuO+fHk8yWQsic1AfgAMXIZIkoAGnuqGeWSlZmODHQMUYuRUgydKLIPZr7Q4EjAobpZQSt1EEVjAHhyRnhqfJYHkvIcShCSxHIOwED5AQQAYtpQ+62HSuSt6jUgRKgBDMWMg9tWX7xN+4/4yW5N4Vwncz0E0DMkUHiveD1YBnSzDFSLTI6UePVmNFakZFKkbFagX3VLq5KDvATV27Gz18GWU6DMEVAhBkhgg6NQZaDs0wJDGhJwDMlUKVIG3IlmyagM3p7X3jaP68/sGnFZd/44ZkvQ8WGZ2ROIEhEpwoE5VECWW4Yqxao1GOqScxItcBwpcToeInhaolqPSLNLVluJkoeveHFCFQF8B72DcHC5WCEk0IEkhS/fxiMcJhAAMdhAoE6bcgZpA4yDgjB9BIU45O1G1de/s0fnvFSBGueNGQCEQA9LFR4tEPJvVBPo4ly1DPXCJShgyEyXn60M6klEbXUkUxUmoMRkEPVIICzekTzYBAwAjsH0NU1pFQA75lx1uB3DaDDVTCGKYGCGlDhkCATwx7akJNIEzSvAIZgehmf3rd+1dO//eDpP4aoNaIcEkwFCOZgGcWrkOV2srw5GBqNIBmplBrHXaO1AuMTVanFjNdjkswggB7R8RyqQqTHP5Sv1sk37sadtwoEUGaOEbSekm/YxWMF4kEAJUBAc4aTAzxAG3J33r9UudZVcLkhmM7LMev3r1/59G89ePr1ImrmYMgc6kamCkBVUAWvQiWJGKsWGas3AmSi4smjrkZ3UqSWOLwKuRe8BwWMHOpSIqtMO2PwW/fh+7swy/ohzZkxIuTrd6JDFXCGwwQCKJADjkBAPWtdDztoQ871wKc3Xjj66gtvU5ICJygQYGom860HTn+xiIoRneUzE4DJV+HQ5zS3jNViKvVCIzTGapMzk6GxcmMoX6nHjV+TZpYkE1TBmENBIgCiWHOwTmI6eiX/4VYoxpj+rukPGxFwQr5xD37THp7gP1zgQaqgRUDDkXJ9Fx+nTbn6Ltj5YHGIyzUlOeFONbB5/cGpwT+CGNFZtBoMcth6cJabRkjUs4gks42OZGi8fLAaQVKtxxMVUUliaqnF50fNSmSqgNgpLcPI5AOUd2/AXXQKZn4P5B5UOWFTKZpv2E3+4DYahODxKJhRxfdJyFzYVjX8D23KVQ2YNX6ElAxwIWhO7AvL1m1efuFEyLwUsEa0PWcmRhEUr2bq2MqQedvoQEarxcbcZOJ1alZSaAziD75muQCgOlkcdszljIKhfdjJeU1253rsmcuxKxeAtZDnPCUiYA1aT/HrtpNv2QcCiPAEAgN2DPIaaBHwzFmJ5y86lrOXNuU6lkOn+CGYCpqnLDzxv2nH4rO+PhEyCq4FQ+bILS4UAAUEIVeoJVEjMMarxcaR13C12JiXTA3jSb0hb6wGG3IPHD4zMYo1yqxiDGQ5+Q83o/uGsWuWIH2dYAAPqAfliclhSZvl+O37G52MDo+BtRyDIAV3QEmXC3ORCkSGr3QY3kMbc/McfHn9paOvPueu8c64WsIbguPksmT77vmn3nLv2a/Mc1O0xrfMarCgGFEUoZ5Z6knUCJTqRI1MBcnkcVeJetZYC278fJo/9u+VwiTTCBXmBpFG+d2D+P0jmAU9mGXzkJ4OpBhDZEAB1aN+D5Bk6HiC7h/B7ziAHxoD5XhCJrBgBsB2QN4H5MwdBiTlIfH8dAI12phLKvBIpS9PE7ePAguBnOMSrvrfvb9/2c33nPeqehp1nKxrZUSOnJmAkuWWqQcSG6ExVi0wWikx2JiZFKgmcWNuMvm8iUUfM9g/9HrUzCSwBrzH7xpolJQLSFcJKRehFCOxo0EVradotY6OTdRoBerpVGIbMARPgdulqBN8F5Az+1mQcYh2aJlElgF7aWNO1tNQNPk+wAIpwTGHzPBoZ9/BTqZSj3uczWfkKXgjCnJoNVhVSHLbWAkeO1jVIsOVIqPVwkQVD1YjVLyXRuUKqiACZqqcVYKnMmcRALSaoON1GgwgQoMCKPjD/gt3lhMQCJBBtFVJlwq+F9BGzT4GUDAD4HYrkrIS0f8GuRbY2L5B42lYu/b0PZc9424lNxyDwPqsXi8Uv3rv2T8+0TEsjI4zZARAjlwJFhQEBMi8aWxs1dJoagg/GSgjjWF8ufF5qoMhzQzZYU/BGwMCMDXYNwTTbSqxn3hNzxBMJwFyiLYr+biQ94MWOURpX8IkDzIG9gDYEaXBAHCqop/0yHXAAdqQy4WG/3rw9AOXPev2cepRDHieWGDU+8zYiZB5xc6B3tWxy580TI5+eNFMXaeSZo4ks40aqxcad3KN1A52JxOv40VqU9etVBtPwYPIVHHke2OUgiEIZjehwR5Q7DD4TvBdgi8CBlRAaDMeJAVTBRnVxiseMBxB4FKDfsyovAyo0GacUxpKqe73uR01sOBJgiYQnYD/+v1n3rBx98JzYpcBIKJHBIki5I3trIMlVJP40WtURiuNMGmsB1eSyZlJNXGgNCgcCieOuE4lCAID5GCGwAwrCKgFDO1FQXLAA8ok06jHJXCtyfWfXcYbAE8bcXFdAfjy7efVX3vdyr1nrN6+iCTiMQJB1OCy2n0Pn3rV+p2LrywXEqZQSx3j1UIjOMZrcePZkpFqibHGVfSFxtPxuZep4AFVMHLYMb5RjlEQBDJVUySjPcmhOhaZ48aJ2gC8gzbiakUeFTu/Gbhgzv9fVxFQAcyhH9c6pfruu3549iWfvf3C68brMaNTYTI8PvnAYpJaktyRpIbcHxkkIjSIKJGlBQUGD9SJKfB4shxUAWFGGQEB9InHgxOlyFN+KJcjWHIyHIKnbQlzgtDwNlFZD3yMNuGMClP4yNev2PIHr/9Uwuy+NFWYZB4twQAZUAcSIptgzSjKAdBBusoH6JAD7/i7Z57xpW+f87bhasEAiDz+avDUFfRBGxGgoh3s4HQqdPAYCj09OZEDVWaMAEkqZDlPqFY2pIBwfERgvOao1CKMKFMw5NQoUaMDIWgDoqLvSXLZCnybNiDn3KBM4fzVO6OP/+E/vpGx4nKElPZ2eJAI4B8tLznoGCoDGB0hZQjPCDCI6DAdpWEg5TB37Ty9+6ff+fJbNOOyMC+ZvQTlaKqQ5cJvvWGQs9ck1BNhpkRWueehIht2uCdZQReeCmeVBzYWeHBDTOR4HErQVjYoPBvYTotzh1+1/tC2JemB8d4t/XZ8Jd7S4g4dbSkCCJMEUESqwDDoOCmjwADIAIYBhnSIROuIJkDO0QarHO7OTSvNL3zs+vdIzmVxCJlZzWM4mgLWgnUCGBBhxhgFDNooZVoJGAMKqADa5qdPwRoR3q/Ii4E6LcwhwuE+e9OlP/yZF996pdStAErzCMBRQWIapQCSAOOI1ihQRWQYZB/4ARIGQSvAOGiVCE+D0tDPcfm/7/6pt2d59Kq5+7R86GjiWImsoqqgzBxVQJGpmk6C4owigKgSzALKtaL6V8CbaGFOvHK4j9125fb/88LbtiF6CirpSR7EW8CACkKGkjZeY6kBQygDCIP06jCiYyhDGB0FasyQt7//xT8+Xo1+p7usKHNRoAjOeVwjaGhbChijiBDMJsIvA3cD76dFOYQj7Bvt8A9tX37XWadsOYV6NF1dzZEdCQqgh90AmYKMgQ6BDONLI8DBGsDoAGUdATLAA5BwUrzjo88/7QP/e+k/9HaqUdpLkgkCRE45QYFC5MA52p41IKJMJ++FegJewVmIoxBmTfCXGLkXuJsW5DDC0f7iw1ev/dff3XxlZHQlXpLj3whXATFTr4KQA1NHWTKOxiMIA6gOYO0A3Z3DoAlQAzJawL++75LyP3/y0g8sXswSlLahCEkGK/r34L1lx8D8E3zYM1DAWW2UqtC2VLAWjIACwolLUqFYUC48c4zOzpRde4us21zGWbBGOTkCEfryTD+wa508Bxigxbgda3mMHWvPSn7r515w8xnLv/jTjJcdQgYIU47qTgyCAlNBIXXUjTVCRMwBvB8kYxiRCqpjwNgRWZLkUBmg1Xzq3vP+on8+V6NKO0kz5bVX/y8/+4J/w6vwL196E//zvRefwG3MgSrEUx2N0r4UsFYRAVVATjxkli+q80e/uJ5nXDIINmdsLOaTX1jKP3xsJV4FI8rMC1TBGC5Yfq7+A/A6Woxbfq7yeF79zms2/O/v3PXF1Wt2v4jUxkA+WeJBKogMgg7hoyHUjQIjiA6jdgCo8yhLu/n5v3rda9bt7PqleZ2KajuFjLC49wC/cO2/M3/eThDPm174L9x83/Op1EoYowTHTxUiB1Gbz2hQsAaMQK6cEK+Cs57ffeNGnnH5fsgsJI7Ocs4bfnIzewciPvDppZRLnFzBa9Nxvg38My3EpeM8oRf//ltv/9Nf+d+9jNpTERlD9AA+HkRdBUiBjFlmy76+0773yLK/6i0rqrQXgdw7kiwGkwNKksZ4b0A4AYFzOlG0PWMUkenoZuC8NVWedsEQeAteaMgEYuGFV+/nEzctIc/lZB+hhc4m5h1JVb4F3EeLcFkuPJlf/8sf3wRsYg5QT4zjX4pllghKu4mssm+kh7/5/Fv55Rf9UyNg/v6Lb2asVghHZyfc0SjOKr7dt86sRYQTHtKoh45yTqHgwXMUoaOUUyp4RsctJ1GgYCzzSt3670bluUCFFuA6ugim5J7fRvT5tLHYKV+4+3nc9tBVqAqj1UJTlwFUBQARbf+gcUo9EdqRGosKdGYDRMwjoYCgJ9DhwZZdRXbvK7B4aRUS4RDPI1vLDAxbirFy8gXqudKj7wbeSgtwXhUIRLka4bdpf41gqdSLyNT7Zq5YR/bQ+3buqiIH1tBmBDUWUOLxAboGNtAxvJWSXs8YBU5E5JSde2Pe8z/L+f1fegSJcyYpe3aXed//rsAaECFoEoU3Gy83AV+kyZx4Ya5TodOL/iNQYpawRpu+mPDMs2/nLTf8Lbka/v7zb+FbD13RnmEjUIg9SpsQwYtF1FMc20PngY2Uhndg8xqIwZkMPCesVFA+/uXFHBiOecXz9tDVnbJpa5kPf3YpD28pU4iVoKmMN/q3InInsJcmcmqZ88aL+q6OCheqMA0Cr0JXqcrvv/JdLFvyCAj89sv+gtf/wwcYrnTirNJOBCjGCtoOAeMwPqU8sp2u/esbQWN8hheLNxEGTyzptKw3i0DklC9/ex5fmag4gmodIkcImRahcHqk+hfAjU1eplHmMoe8IK/ySypMk0CByGV0FschiwDoKIwT2RSl/YhAIVaU1qQiYCwmS+gc3krnwEaKY3tBPdoIGAdTRJRIUqaLyGRnowqqQkdJCVqHACm83iBfAD5Jk7gcYa6q7qdHO/UvSiWmMW8DIzAw1sW/3/xGfunH/hFV4X23/h/2jvYRGdpSK/4TuopBxWKzKh0HNtF5YD1xdRDUo8aBGB5PbFJUpj+MRZSg9SgI6J/1WfkGsIcmcH2WOcsu5I9S5QJVgmm/JRg+/I3X8I0HngkKW/YvI7KKtHNHo7QEFYuKIUrG6BjcQsfAJqLa0KHhvxieiAAFk4ICAhB4FbwHY8CIMtsI4GH1/lz/FPg/NIHbnytz0f7N8rwFp+ibUGZAIALWwMa9yxAgsiAA7Ro0BW2JFWUQotownQObKQ9uJqqPosagxnFslILJmBTkXjAC/V1DDFV6yf3sfcBU4MahnfIZ4NOcZG54pzDXdPTTOREyfw5EzJhARCm42RGahQhQmmIqRIgrA435S3loKy6tomLwNuJ4CBwxowlLK2P81kv+mqed9W1uf/jp/PlnfoXhSs9sDRuZt1L/DOQbwCAnkZu3kjnH57wJ5RKC4JgoxYKiJ3uDzFhEtTHYn1xR3o7N6/ipAf9TFZGiBJU6/NTVn+cl13wU0iIvfuZ/sm7Hmfz7La+ho8Cs5DPOUOG3gd/iJHJ5ztxiOcegv6UEwY+mj3Y0Hj1J8xdvY0RzyiO76dr3CMWx3YetKDtOlCNlUhBFCQigFlAil6DKrCaqb/bCfwPf5yRxijJXjOzDdHbxp1KglyA4RgKNjgZlxqiZDJhiMkDf7g1UNhygo7bniBXl6aE4MiAoxfCZ79/AFWu+z0Vn3M4P7n0hn73reooRs5ooJYG/3LNIrgUSTgK3d6EwVxQjXhlV9cVKEBwjBZGZu8rH2wg1jrhygAWbv8Oy9V+gsm0F++rnoNaAGKaTAFYzILBGOTA6j//3gb+lv3sfB0bmU88cziqzmQoIPHPeADcC7zk5M5oB5oRinT4VfZtaguC4OAvOgSrTRPDWocZRHNnFwo3fYNGGWykObQeTomY1ahyQMt0UsJJjhGAqbNLcsvPAYowBZ5S5opDo74nwOWA3M8wVU2VOsLxVlbMIguOgCnGsWKOATEPARCDSeP5l4fpbWbDpNuLR3WAcuBhEmFmCJcOhKIKghAeMFWOZeyyr6qP8LvD/mGGuPs6sVx2Rs3qW6VtQguC4qApx5DEG9EQCxsWI+sb9Y4sfuZn5W76DrQyCdRCVOFkUsOKxeDIsc1egHlyZ/ytePgjczQxyUVGY7Uq9/F6W0kMQHCcF4miqo1GOi4ppBIzxGT277mPJRMDM2/Z9TG0EXAGiIiefYMmxkpOqRZjLAhHK4vhD4GXMICeO2U24Jkv11TwFQaAKhUgx5ngDpoBLK/RtuYOl675Ez64fIGl1KmBKNIsqOMlxkqEaEwSq+lLgBuDzzBCnqsxahkhy/hjBcYIUQEGEOSRQnepoLOixPAPjYqLaKAs33dY4Iuva8wD4DGzcCJhmUwRnPFY8KCAQBAp/LCq3AhVmgEOF2ao6yMvLffpcPE+ZKtQTwTmInFJPBfXh+zbm1tEZGDmWFeUB5j/y3YmA+Sod+x8B9eBiMI5W4iRvFEEwxSiX1or6U8C/MwNcvajMRuOdUuqp6++gJxYyWS68/Ln7eNWLdtE/r86mbR188H+X8937u8P3oc8FCoVDM5rHX1Ee3cPCTd9k0SO3UBzeCgjYCBBajQKWyaBRDgkCl/Mb1ZJ8Ehhimrk0FmajZQO8rlriIlWesloi3PiSXfzOLz5Cgworlla54txh/t+fns037+qlVFCC2b7e7LEWcn/kinJ5aBuL1t/C/E23URjZBcaBjQGhdQmWvFHKpCBQAZdyWlfK/wX+imnmukaYdayntyb66xiesjwXFs5L+ZmX7gAEEgNTip2TP37H/d14bzBGCWb31pkxkJoCeN+44LKxorz5W7jKAJipFeU2oIAjx5ATBIdTAUHfIiofAfYyjZww+9QtP2XhTE5AlsOKRTX6ulPIhSOosLLxczn7Bg3GEMxSquAKESZy9Gy9n4WPfJX5Ww+uKA+BmxrwtxXBkGPwIATBERRW5OjPA+9gGrkMZTYxnl5n+FUVTogxMDgSUU8MxY4ccg4RZWjMMV41GCGYzSwsGl3H2V/7AMUN9yFp5dCKctvyODIeTxAY4Ze95z3AbqaJQ5lVROQNKnoKJyhyyqadRb7y7QW86sXbwFjwAk4B4dO3LmJ4zFIuKsHspAhxDOft/SKlka+B6W0ETPsTXPg65ycWLDoYNsAfME2cEWYNL9Kj8ItME2fhbz6yEms91z9rH8VSxsBQzEc+s5z/umkxpQKzXTg2M9ARZ+BKoIbZIibjCQWB8EaQfwZ2MQ0cIswWBm4EPW36gkYZqzre9i+n8YFPL6e7K2XfQMyOPQWiSBEhmOWcKJ1RFVSYTSJJEeGJBMFihJ8F3j5NQcOs4A09NtdfUqaXs4pX2LSjiGoRYyCeEyETKOAkp2xq4IXZQoFIwtFZ8ORU9Y058i/APk6Qy5RZwRpepjlnMgOMTIbL3BNYySnNsqABIZYMEZ5QEAisMJbXAH/PCXLG0vb21Ijnj+mb4m5Qz7QIAgUMOUVqgDCbxCYlCJ6MKCj6i3sXynuBCifA7VlI2+sf4kVxlcvUM62CwOIpzLKgUaAgKUHwZFTA5py1bDs/AXyIE+CWbaetqSBi9Jc80y8IDDkFnX0dTWRShCD40bzTX1DkY0DCU+RyR3tTrhR4LtMsCBQQ9UR+tgWNEJERBMdEeRpWnw3cxFPkMEo7k1zeCDhmQBA4UqxPQZhVnKYEwbEynp8/oaAxnrY1ulNWdyzWV4ow7YJAgbJUEfGzbkZjJUNQguBYeOX6eUbOA37IU+B6RWhXbon+dAI9zIAgUKDLVTEos43B4yQnUxcCJ/iRBApDXt8A/CpPgRvySjvKPB3G8JPCzAgCVeiJK4goKLOCqEd8huCxeDKC4Ngo/DiedwCDHCennrZUynhuvcA5KDMiCBTojiogHlRmQcB48qhIre8UtsqZ1IcjBCUIjoUKKwuZ3AB8hOPk4kxoR2msbxRlxgSBBzpdFVBA2jZg0Jw87mS8dxXjfavJu/pIBgyqIEIQHBNRSGJ9AyL/ASjHwSUF2o7COaI8nxkUBKrQYaogSrsRnwNKVuhuhMvYvNVkhS5AcXlGJAYhIgiO09UevRL4HsfBeZR2EyfyijSmLMqMCoKSVAEPWFqfIt4DSlrqY2zeqYz3riCPO2BqNgOgBqwFhDYQqAoAIkqzCTjNedVxB43mtBeRUhbrT4oyo4JAgRLtcHSmiM9BDPWO+Yz1r6HSs4w8KjV+XHzG0Ywo1kDuQQhaVT0VjAHvQUSInTZ/Nd7wciPydmCIY+QiK7QTD89COY8ZFgQKFLUK+JYOGDWOWtcSRvtPo9azhNzEiOaYPOWJiIA1Sp4LCEELSjLhGWfexY3P+RC1pMQ/f+UXeGTXKURWaSphdZZyHfAJjpHLUtqKi/Q1npMjCCKtAdqCA36PGkeldxVj/adS61yEGjsZMD7lR5gKGoIWleXCgu5B/vAn3snKFWtBlN7SCG/8139GVZp6jGYE6jV97XEFTVpT2kWlLIvmwbWcDEEgEOU1UG2ZgBHNyV2RavcyxuafRq3cD2IOHZEdCwURMEYBIWg9uUJXcZwlvbugVgaXsWrBFiKXk+YOoXm8h2I31xRgNbCZY+B6umkfRZ7rKywRZlYQKIIViKUOSEsETBZ1UOld2ehgklLfoeG/ZhwPBYwBawkrzi0qsrDtwGL+5/ZX8MqrPkFWL/CRb76OauKIndJs6unLM7kO+DeOgcsToV10JPqaNAKUGRUECsQCJUlApWkryoKSxl2Mz1vdWFNOit0IiviMEyEyFTQErciIknvHn336N7jl/udRSwrcu/k8Iqu0iiTSVx9z0CSR0g6sl9UKzxBlVlIVVMEYJWg+VShaT9nVOal0MkTMRKWlHsbnncJ43yqyyRXlxo9PB0GxRglalzWKqvCtBy9FBAqR0kpEuRzkHOABfgQnKrSDzPI84+mfrYO/3EMhUqqJUIxaYmc+bJzZlLKtA4YZpx7yFGze2CA7sPAK0r7FZIetKE8nY8AaQAVQgtYkohRjWlWH8bwQeGDWfE2AoC9VZp/MC/O7Bnnri/+O5Qs3cfNdN/Cxb78SmrpZEqhCbNLJjsYLM0chq4MrMLz0Qg6cex2bRq9idE8nkWSYPGUmiIAxoDx1QaBWX6xe/hbwPAnnhZZnhGUqXI3Oxm4GfuY5H+alz/wPyGIuXXUfa7efxZ3rz6cY00xhRmMyimaGgkZzyBKISgysega7z7iWoaUXYYoF/P3SCBixyoxQEAPWKKBND/R6ImQ5GAPFOBwftxNVnqY5pwPr2v5mAF/gOnL6mIVEYGnfLvAOkhKURpnXMYQSND1oJKUgCSBMG59BnuALPQyccs1EwLyA4YVn402EzevYtIpoETAz/MVnYG1zj85yL3gPF505xtLFNUZGHfc+1M14zVCIlLYQFK3R639k0LT6QDDOReqi1ymzlMLHv/NKLjr1Hnq7DvD9e6/jro0XEzmCJlIgIqUgdVABmY6ASck6+tm36ir2nH4tY/PXAGDyFJvVTvrZvzWK0hzeC3Hk+fUbN/Gya/dQLKXgLXf8oJc//MfT2La7SBzCpi0oXBfJkx+fOSdCK6sYXWAyns0sVYiU7667jBv/4QMs6dvND7eeS6VewFklaC5HRsQJdjR5CupJuhaz99RnsWfNsxvPwogqJksApVmMBRGaolqHN7x8F69+yXbILSQODFxx8QF+942GN/3J2WiYU7YHy9N3bNCVwGaegNuxSWll3QvlilKPLvQ5zOaw2bp/KZv2LCWOCCHTAhSwpIgmIBwnfTRgaj0r2HPac9k7ETAHr4kRzRvdSytwTZrR5F7o6fQ8/8r9gEAuNHggt1xx/jCnr6zwwMaOMKdsA6p09S3l6U8aNH1LaWmurNf7nFkvsjpRBM2ioHrYew9RWod8CLQAGBALGECefEUZqCw4g12nP58Dq55GvTz/0PFYi9AmdjSqUIg9pWIOCEdQKERKuehRpU0ErsSLgY/xBJwr0bKMl06vejUzJAjUAwomAhuDLYEpCJGF8bifz5Vfy7xsiFXZVvrzfZT8IGBAYkBoUIW8DjZmdMn57DrzOg6suIIs7sLkSWPA33JUcFYx0pwHEQ8MOR7e0sGpp44Chkc5z/adJbbsLIY5ZTtRno5IHzDI43AgtKrhbs7sGuEMhGkXhIARIOqCeL4Q94DrBAyI0DDEIv5KfwUBYvWsTjdxcf1erqp9k/OT+8HXwAu4AgMrn8aeM17A4NKLyKMSJjsYMJXWPhq0IAIKCCePCIiBf//vFVxwxihLl40DAihp3fHPn1jJnoGIclFpE4GwMk64CPgaj8PFCS2rXOFpCDFtJvdCmoM1tNLdRMHhx2LdUFoO8TzBRICC6mGvgAARk3IMD8VruH+iPtXxEi6r3cWra//N6kUR209/IQMLz8Xb+NEV5XbgDJMUkJM/l3xoU5mff/u5vO76XaxeMc7wcMz/3rKI2+7qDSHTbhSTW572hEGTW1qWy/Q6lfa7TqYUJ5yxZBv7Ruazf7SHglOUoNnUgzFQPkUoLwOJQD1ozo8kQKwQA5lEfKP8NO6YqCsXJTx3YY0uWyfNarQNBWMUEVClKQqxsmlHiT/8p1MpxlBPwQgUC0r7CbzRFwB/wuNw3mirzmfmqeEStL06md6OYd792j/kmnNuY/2u0/id/3wXD2w/vQWu9g4hYwvQebpQWACaN+opMUBJIQe+sTlm+5DwE+emLOrISHKhHRx+dNZMkdOJAq9CqaCI0KYChQuNyjJgR9tcqpkZvUyURbSRegrPPe+bXHPBVyEtcdqp9/CKKz/N2m2/gSIIStCkkClC95kQzQPNmBYWsBY2DkV8+Ae9vP6CIRZ3tk/YWAMitAQjStsLenOjzwD+i6O43CgtyculiDraiAiMVLtALbgEUMaqXShB0yiYCLrOEKI+0IxpV7Cwa8zysR/2cONFQ/QVPKmn5VmryPQNaIJAQC5/3KABoQWJiD6DNlOI4Btrr+bfv/RLvOiyL/DDTRfxn996Nc4QupkmUYXOU4S4HzRjxhQsbBtxfOahbl53/hBGwCstSxWcDUdV0ysQ1SuACEg5jBNVWo0aehQuF23Db8VTx99+8Rf4l5t+niQXjEBkFeVkCzSHwgIoLQbNmXGxhR/ujbljR4lrVlWoZ0IrsyFoplkgcImKLDx6TuMQodWo5yKDLqQNGVEKDlSF2CoAStAMxkF5pYAAnhkngBG4bUsH5y6s013w5J6WJQLGMG2CQKEL1UseGzSqtJoCPD0BEdqXiBI0j+ZQXCJEXaCek8YZ2Fsx3LO7yAvXjJHkglfwKqiC0jrksK8LFmG6BMHVwOc4jKMFiXARylMTBArGQmERIJx0kYE7dpQpO09sob+c0V/K6Yg8CmReaAUi4CzTKghUuIijOBVai5euOnq28NQFgSkz2c3kzVkbHqwa/vfBbkTACI2gOXN+nYsX11jenTbCxitNZ4yiTJ8gEOV0PPOB/UxxktNajC4BziAIjpVyiDB1xYyABTxNYQQQUCZr77hlx1iZ7+8ocdXKKs9ePUZktHndjYaOZsYEq3CsOiJocLQUUTlPVQsEwZNQD3gQe7AO/Zjmk+U6QQyop6nksC7HArVc+PL6MttHHK88e4SuQt6UsFFABKxTQABlmgSBQeVC4C6mOFRoJap6OcGTCQEjU7cu9wuuA0x02LclVyAZVGwBUFqOFbBucg069T381PlDlCJP7oVmsAZUmV5BoHoZ8P6W3ToT5QIVguAxNAfbAR0rIZ4vGMckZZLQUFoqAGhOyyo6WLc/4qYNnbz8rBF8E3JRRDDWoEy7ILigZbfOjJcONXoqjyMIIRP3Q9fpgi3/iEsxDaC0vNjC7TtKnLewxhn9yUm8I01QY1HxdMkoTroAwzQKghWozAMGWu7oLDe6WmApRwtCyPRBz9mCONCMJ6e0BSNQz+FbWztYMy89aQGDeopje+k58DDl8Tob7fOoaBGDMk2CYKEYPR24HcCJUVpFlLIyjehGaQgC1anr/U8TJALNmVUiA1uGI/aMWRZ15swIEVQs4jNKwzvpPLCB0uhOnE9ItA8RBWU6BUExyzn10aDJclqGWDkdVYLgUR6KS8B1gubMOiIwlghbhmOWd1fImT4qBhWL8QkdQzvoPPAIhfF9GJ/jjWuU9YrBo0yvIBAjpzHFiRFaRS6cI8qkIFAwMRQXCeqZlQTIFfZXLF4B4cSJwRuDTauNgOkY2Eihsh9UUWMbAQOgCE4yLDkoIEybIDCiZzHFGVFahnI2QTBFPcTdgikwq1mBSmLIvOCsnlAHgxhsUqFraAudBzYS14ZQpmYzwmM0gkY8yjQLAuVsFRyQOaU1uFzKip7qDUETeS8T1TpXyLvyoQcvZzPlqVNjUQxRbZjOwc10TJSrjwHgjeOJKIIjx+CZAUGw0ov0AvudF6EVpJaVRukmaAqvQq0GnWWlsytj/1CEVyjGStMoiAMM4Jm1vEIpUqwoelwbZAaAuDpE54FNlIe24NIKKgY1lmNhJMdJxgwIgk6Tc2ojaExOSxDR1UAHJ13gPYDyhpfv5seevZeuUsb23UXe898ruWNtV1PDRhVQZjUR6C9lWAM5P5oaB+opjO2j6+AG2fAObF5DZWr+cpxiSZgBQVCY+vv6HU5EaQUCKxQsJ11QT4W3vHYrv/j6zUwSVq8a44IzR/nld57LnQ80L2x8Cujs7mY6ImV5T0rmBTH6I1aUc0oju+gcmAwYyTMwBm8inqqCpCjTLwgUVgO0zowmlZVprIhyEgVpJpyytMZrfmw3qEBqmGTo7k14/Yt3cPeDZ+IVjHByGchGFc0FDKDMOpmH1X0Zyzszcg/OPP6KsvUppeFtdB1YT2FsL+Iz1FjUOk6UI2O6BYEA3rMSwKmnJWSOlaKcZEGWw8oldXq7MsiFI8nEz9Xo6coZHXcYq5xMYiAbm6yoB1SZVRQwwFUrKjirJLkcETDeTq4ol4d3NI7I4vEDgJ8MGOOYLpGkTLcgUMAalgM4a2gB4lR0GcpJFlihMfiv1gylTg+ewygDQxGVqsUITaE5VHcqUY/Mutvs6xlcuqTOuQvqpF4A8DZqlEvG6TmwlY7BLcTVAfTR4b9jmp3kZYBwglBPQBWcg2KsiDBrKSxFKDkVmk6ML5PLYk66IIqUdZvLfP3OebzouTvBOfCAU/DCp29dRK0ulIpKM4iB+j6o90NxMfiMWaGew4runOvPGEWA3MSoGLoHN7Fo407qG8YoZSMogjeOmeQkZ+YF1bpw5uoq11wySKGY8+AjnXzr3j6YepxgllqoaIdTlGarDkix2MVSMZxkgchkvfu9p5Dm8OwrB+gqerbvjPnwp5bzpW/Np1hQmkYAhdENiikIUR9oDihtySskOazqyXj1+SN0dTqSXOjcv55F629h4ZavMbLvPHZkF854wAAIEEnGzApqdeG6Zwzwx7+0nr7+KgA+t/zXF5bwp+87FVUQYTZaVEil7Aqp0Gx5Kn0q2iMEzeCsMjQS8bt/dwanrajS1ZGxY0+BHftiSoUWaO0FfAIjDygda4TiQsCAekBpeQqoQuohtvC0FXWuOzOh23lKOx5iybovMW/7nZjaCDgldzGaGyBnmoX15ibIcmHZooTf/7kN9PXXIHEAGKf85Et2sHZjF5/48kLKRWUWKuYq/S5XodmiBSxTxRA0jXOKKjy8pYRXcJaW+j++GPAJjD6kJAegtERw3YCACCBMqyQFryCcGCOTVYqU8+alXLLSc3b3CL0772X+uluYt+MuSKtgY4hKIAknWyQpCDMkSFK49OxhFiyoQ2p5VCYQw9UXD/DpWxfgVTCizDZZzDKXxTSdKIsImk4ECrHSsgwNtT1Q36+4TiZKMDGIZVqoghG48oIaXR0e709s0aJgPf2dwpI+Yb4ZZuHOO5l3+0307F4LeQKu0AiYZookm8FLNQNVsJYnZI1iBJTZyaALnUFpNoWFBMExEgsopCOQDiso00YVIgfXvniMJfMzslxO6A6y3BZw4weYt/l7LHz4Zsr714PPwcbgirSCiJRg5hRiuOehLoYGY3r765AYGqwCyvfu76Val7Y9OvMqpBkgEFsQUQ6nqgucqtJsgixUjkMQCIgw7VTBRAoGqpkhzzluOvVdL8WRvSzcfBuLHrmF0uBmwICNwDlahU51NJEoiiBo697Fl4AIFCMQ0baagW7eUeQvPria3/+FDZQ6EiYJX7x1CZ+6ZRHFuH1DxpmcS864jyQp8sPtZ4OCCIeRBQ4kdDRBMAWFOAJrAeW4eBujYhoPWC7a8DUWTlQ8uhtEwBVpRYpgG0GTkmhMK8q9UIrrXHfhbVSTIt966Kq2m2cUC8r/3rKQrbtKPPfKA8RFz9p1nXzl2wtIMmmEUbtRBMj5rZf9Ba+6+uNkueNvPverfOjrr8VZEJQpCwwtwAvzaAFBoEAhVkSUYyIG7woTVaRzYCNrbn8PF375d1l+z38Sj+8DVwAb08ocOZFkKK3He8GahD985bv48zf8Cn//c2/iTS/8F9KMtlOMlbse6OLd71nN2/7xVP7nqwvJPW0ZMgB5Dsv79/LyKz4DanBRwque/t/0livknkkCqvQ6VZpOlE6EpgsCVaEQ+R89nBUhtwWMz+je+wBLHvoK87bdga0NgW0M+NvmP6+TqaBRQGgpaQ5nLNrNtRfcArlDTMZLLv8c//7V/0s9LSCibTav0YmaHbsXIjBaLbN/tJ8lS9aDydk1uIRa6jBMUUAoO4QmE2PQDqX5gkAV4lgx5skDxmZ1+rd9n8WP3Dy5opxUplaUy7QbQ44lR2k91sC+kXls3HsKZ53+PQAe2nEmlSTGGaVdCe3PGmVgrIff/eg7ef1zPkStXubfbv55amlMZJXDFB1NJlBSKNMigiCOwIg+ZoPM2xiXjLNg27dY/PBN9O66D/LkUAfTpiweR9a6DxOPd/JHn/wDXnfNR6kmZT74tRtRFUBpriCyyh3rL+LeTRfhFTIPBacohwiUndBcAkWFDlpAEKhCIVKMARTUOHIbE9cGGwP+JetuomP/w+CzyYBxRdqZIhjxWPEorSl2yg+3nslvfPhtKFBwEFtFCVrlfx+vgsjke+UxSo4mU7QAlFrlvFoEQAnmcNDECs6R2YjC6B7mb/oWi9Z/jfLABkDAxuAcs4WhETQt/zezyAkCQKuFTGBEeRKFVgiaSJC4Fb5l0hqmtkAEZxQlmItBExUs5epeFjz8NRZMBExhZCeIAVdkNjIohpxWJyhtKYgdTScRUKCJ0ly45uzvc8MVn2HX/uV88OuvZ6xWxhqCOSazcNaBW7ngy/+AHRoEE4ErMnsJIh5BUYJgRkSOJhOIgJgmSTJhzaJt/PXP/Bod3XvBZhSiOn/26bdgjRLMLWpgyeiDWLsbXC+znQIGj8UTBDMkNjRbk4Mm97Codw8dPXvx1R5IC5y9/CGMIZhjFCESiF0GJmKuEJTIJAhBMCMiQ/NZwNEkkYWHdpzO/euejimNkGcFPvv9G8g9wRwUCZRNDRDmCgVKUiMIZog1NJmCaf6efg9vfv/f8Jvv/1ve+I8f4LN3Xk/BKcEcXAQwSperg86loBFKUscQBDPCOppMwLTCQ2EHRnv57B3XYQRiByIEc4wCkcnocFVQw9whlEwVEYJgJhhH85lWeQLZWYK5HjSS02Frc6yjgYIkCMz0VwUEIWjCtT9B4CSnaOqQC8dFOUQf53OD8BjKkUQopoZC6ojE84QElEkqAPrYzwIKqChPToglQVAUYZoFgTiCIGhQwJFRliqoHBkEhz4fWQgIYISalamC1CmpUfKp8gdLFDUKoiQGcgEz9T4VsCi5y/lObZBNtW3E3gNgVbC5AcB4wXmDUcH6g2UO/djUZ+uFKDdEmcUdfM0doqACioKAF300hIxRSmSIF1QABESZLkHgaD5PEDSjiVYQBXJQD5qDoYDzOVVxJNaS2cmQGImVfSXYV1b2d+SMFXOqsSePcnKXkUQ5SexJo4xarNScklgltZ7MKrmdChvjwSgCgGJQhElGPE48t4449td3gXiekBfIDW6qotzi/KHPcWYppG6ibKNKqaNUjykljnI9oqtaaFR3NaazbqknVTSuQx4juWASAQU1gAGVqfcCoByHIPBy0Us9TXY5cAfTLQhUjggSD6iZLBHAebLOAbRnN653F65zP/0927h26acYLtXY36GMFjOSQoZzKc5mWJtOdSegoqiAoFgFg2JRppoWBBAVBEBpECbpobA7QiQ5Xxw+n021hSA5T0oO/0OPei+H/7wCwmFnaeAFUcGoNDqfqNJFPj4fV+0iGu8hGp2HrXQTjfcRjfbhKj1EI/3YpAAK4gEFODqIlCA4StoKQXMpcCdB8BSJCuoBD6qThUIeQVauYnp2E3Xtw3bvxS7YSLz0IQp92ykVR+gojBAVhzGFCiauYiQn80UiBTNVqIAeHg7CTIkl40vD57GpthgkY8Yc3ZmIB+NB/KH3KpAWkaSAyQrYtIAb7yUeWExxcDHx4BKikXlElV5cpQs31odNQAVo1KH3agCUYE6qt0LQXAzczY8UBALKZOVANvVSBDqHiUpDaP827Mr7KC9eR0fXHsqd+3A9uxvdSsHVKOIxKqgaUEER8FPvMYcCpUkKJuNLQ+excSpomk50KnwA/NRnPRRGWYSt9GCrnUQHQ2hwEcV9KylNVGFwSePHG1W3iIJaUAMqc+gYLqi2QtBcANwFOILgKOIFMvAecgsaK6Y4Sr70YeIV99HZu43SvK24JQ9T7N1BVzxKwSYgHgGMN6h34C2qgkdoYY2g+crQuayvLQHJaHmHh47kIBw6R0sj4pEFFBrdz2KK+1dQ2rOK4oEVuGoHkllMBmqYCiAlmJVGWyFozgLuJHz5WaACGaiCB0Qg7R6Fxesp9m+iOBEm8cp76Vn4CD3lfdjiGNYmRCqQOdQ7vBoUoV0VJOOmkXN4pLoEJKetiYLJD1UeIUkRk5Qp7l9KefepFPespjiwbOLzcqKxTlAmayp8VJS2Fww4mk1JEeohaOYgL6Cg+WRlRWDRdgrzdhCvvpPO075Lb/8GCr27iMqDlCTD5g4/FShkEaQxGS0pfO+KCuSuUQ2iqE3Jy0OMrz7A+Jp7AEEqXVPHbkvo2H4GHdvOojjxPh5eiK2aqW4HkDbteoKkFTqalcD3gCUEs5yABzLIPeRlj+3eg5x6Nx2n3E7vwocpLn2Acu8OOkyCqIC3jdKJ8gizXUEybhk5i4eqy0By5gSTH7aIoKBgR+dT3L+M0t5T6Nx6Dh07zsCN9+CqEQqoa6MFg2Cro9lEE1TqBLOTCqTgFdSBdowhp9xF54r76FjzXbpW3k1v+QDW1bCAZDE+dXgi5iIFDMqc4m2jDpeXhxk/ZT/jp/6A/Zd/HlProLTnFDq3nUV55xl0bD+LaLQbyQUEvANEaUlB6mg2lQSoE8wScsQDkHkBdPWDlFffQ/ncm5i/9Id09OxqrBTH3qBZAX/wtd5BTgAg4W+YR4WP4uMq46fcx/iauyEpNVapO3aeRtfGi+nYfnqj87F1QQ2TZQGUlhDUHU0nVdBx2legAgpkkCn4vmGi+ZspnPtV+k+/je5l99PRtZeCF/zUBpjWOsgQfrQQNIGACqQxEIN4su79DPfuYfi8b2LGeynuW9E4Yut+5DJKe1cRjXaCCGpb4IgtqDiaTJWawChCOwoBk0BmQDoqmDV3MO/8r9B12rfoXfQwHa6KKJDF+EawBNNwnXmgBvKD5QAa3U5l5QNUVq1l7zM+RXEiaDq2nU3PI5fSsfVcXKWMeEGjJm2xBVVHk4moIlRQ2kAgXvAZKJCX69izv0fvWd+k57yvMH/eBuLiKDaL8AfDJS8TnNyts3DMBrVFm6ktXc+BS25q3GLQs/5SujZdSOeW83DjRZCT+dxOIFBxQvPFhtFaDkLQumuqoBkkJcWdeg+dl36KBWd+na4F6ykXRjBpgdw7tNZJRvDUCQYFlOApyqLJEiXp38m+ieDZd8XnKexfTvdE6PSuu7KxUOCqFu9m+o62wAvjcuHLPE2n/JvAz9F6QsDUIY3ALNxG8cIv0H/el+k75Xa641HwDs0ivFqmRxBLzu3jp3DX2GoQzzQJxIPNwWaQxo2g6XnoSvoefAaFgcVIBj4KgTMTBD7ojNJ0CntoHUEm5DloZ43okptYfP5EuJzzVXo792BRNC2Q1ztpG+HoLFADmXm006mseIDKyrXsuep/6N54EX1rr6Z7/WW4aoQ3oE6ZHoHCPqe0At0DQhMFXtAMvIJfvIPOi77I/Cs+2lhHLkZVSErkB4tgJikgBDP/bFfh0UWCofO/ztDZ36G4dzXz7ns2vQ9dSXHfYhBQd+ILBIHucaA0ncouhGYJAZNAVlSiM+6m7+kfZdE5N9HTvwmXOfI8Jq91cvIERjwnSaAGkhKIUlu8kZ3L1rH3Gf9L9yNX0H/v8+ncdiYmOaGNtUDNbocamk2F3YJyEgVe8An4jhR74VdZds0HWXjm1+hwFchj/Jwb6oejs3Cha9SorGOYgUu/yMAFtza21frveR49D18xeawWh8A5XiLsdSI0XVHYU1e8giGYWbngM8g7a5Se/jn6n/FhFq2+nZKtomk5zF5C0AS5myxRRs+4g9E1dzeuvOm/+zp6H3wG0VgBHx3renSQ5exxWU7TeWHYGPYDCwlmjgc/byelSz7L4qf/J/1L7ydWJc+K5HkHrSEwBK3zQHIRUMZXPNCovU/7DP33PJ++tc/AjfeBEDy5cTE6+P8BW01E00mieO8AAAAASUVORK5CYII=) no-repeat 150% 100%; + background-color: #2238b3; + background-size: 205px; + background-blend-mode: luminosity; + } + .leftnav__header__title { + font-family: var(--text-font-family); + font-weight: 300; + color: #fff; + margin: 0; + padding: 0; + line-height: 1.5; + } + .leftnav__header__version { + color: #aab3ed; + font-family: var(--text-font-family); + font-size: 14px; + line-height: 1.5; + } + @media screen and (max-width: 767px) { + .lh-leftnav { + display: none; + } + } + @media print { + .lh-leftnav { + display: none; + } + } + </style> + <nav class="lh-leftnav"> + <div class="leftnav__header"> + <h1 class="leftnav__header__title">Lighthouse</h1> + <div class="leftnav__header__version"><!-- fill me --></div> + </div> + <template id="tmpl-lh-leftnav__items"> + <a href="#" class="lh-leftnav__item"> + <span class="leftnav-item__category"><!-- fill me --></span> + <span class="leftnav-item__score"><!-- fill me --></span> + </a> + </template> + </nav> +</template> + +<!-- Lighthouse header --> +<template id="tmpl-lh-heading"> + <style> + :root { + --report-header-height: 58px; + --report-header-bg-color: #fafafa; + } + .lh-header { + display: flex; + height: var(--report-header-height); + width: var(--report-width); + max-width: 100%; /* support text-overflow on url */ + border-bottom: 1px solid var(--report-secondary-border-color); + position: fixed; + z-index: 1; + will-change: transform; + background-color: var(--report-header-bg-color); + margin-left: var(--report-menu-width); + align-items: center; + padding: 0 calc(var(--default-padding) * 2); + } + .lh-metadata { + flex: 1 1 0; + padding-right: calc(var(--default-padding) / 2); + line-height: 20px; + color: var(--secondary-text-color); + overflow-x: hidden; + } + .lh-metadata__results { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .lh-metadata__url { + color: currentColor; + } + .lh-export { + position: relative; + } + .lh-export__button { + background-color: #fff; + border: 1px solid var(--report-border-color); + border-radius: 3px; + cursor: pointer; + outline: none; + height: 32px; + width: 48px; + background-repeat: no-repeat; + background-size: 20px; + background-position: 50% 50%; + } + .lh-export__button:focus, + .lh-export__button.active { + box-shadow: 1px 1px 3px #ccc; + } + .lh-export__button.active + .lh-export__dropdown { + opacity: 1; + clip: rect(0, 164px, 200px, 0); + } + .lh-export__dropdown { + position: absolute; + background-color: #fff; + border: 1px solid var(--report-border-color); + border-radius: 3px; + padding: calc(var(--default-padding) / 2) 0; + cursor: pointer; + top: 36px; + right: 0; + box-shadow: 1px 1px 3px #ccc; + min-width: 125px; + clip: rect(0, 164px, 0, 0); + opacity: 0; + transition: all 200ms cubic-bezier(0,0,0.2,1); + } + .lh-export__dropdown a { + display: block; + color: currentColor; + text-decoration: none; + white-space: nowrap; + padding: 0 12px; + line-height: 2; + } + .lh-export__dropdown a:hover, + .lh-export__dropdown a:focus { + background-color: #efefef; + outline: none; + } + .lh-export__dropdown .report-icon { + cursor: pointer; + background-repeat: no-repeat; + background-position: 8px 50%; + background-size: 18px; + background-color: transparent; + text-indent: 18px; + } + /* copy icon needs slight adjustments to look great */ + .lh-export__dropdown .report-icon--copy { + background-size: 16px; + background-position: 9px 50%; + } + .lh-config { + display: flex; + } + .lh-env { + padding: var(--default-padding) 0 var(--default-padding) calc(var(--default-padding) * 2); + left: 0; + top: 100%; + position: absolute; + width: 100%; + background-color: var(--report-header-bg-color); + border-top: 1px solid var(--report-secondary-border-color); + border-bottom: 1px solid var(--report-secondary-border-color); + } + .lh-env__title { + font-size: var(--header-font-size); + } + .lh-env__items { + margin: var(--default-padding) 0 0 0; + } + .lh-config__timestamp { + margin-right: 6px; + } + .lh-config__settings-toggle { + margin-left: 6px; + } + .lh-config__timestamp, + .lh-config__settings-toggle summary { + color: var(--secondary-text-color); + } + .lh-config__settings-toggle summary { + display: flex; + align-items: center; + } + .lh-config__settings-toggle .lh-toggle-arrow { + width: 16px; + height: 16px; + margin-left: 2px; + } + .lh-config__settings-toggle[open] .lh-toggle-arrow { + transform: rotateZ(90deg); + } + .lh-config__settings-toggle summary::-moz-list-bullet { + display: none; + } + .lh-config__settings-toggle summary::-webkit-details-marker { + display: none; + } + @media screen and (max-width: 767px) { + .lh-export__dropdown { + right: 0; + left: initial; + } + .lh-header { + padding: 0 var(--default-padding); + margin-left: 0; + } + } + @media print { + .lh-header { + display: none; + margin-left: 0; + } + } + </style> + <div class="lh-header"> + <div class="lh-metadata"> + <div class="lh-metadata__results">Results for: <a href="" class="lh-metadata__url" target="_blank" rel="noopener"><!-- fill me --></a></div> + <div class="lh-config"> + <span class="lh-config__timestamp"><!-- fill me --></span> • + <details class="lh-config__settings-toggle"> + <summary> + <span>Runtime settings</span> + <span class="lh-toggle-arrow" title="See report's runtime settings"></span> + </summary> + <div class="lh-env"> + <div class="lh-env__title">Runtime environment</div> + <ul class="lh-env__items"> + <template id="tmpl-lh-env__items"> + <li class="lh-env__item"> + <span class="lh-env__name"><!-- fill me --></span> + <span class="lh-env__description"><!-- fill me --></span>: + <b class="lh-env__enabled"><!-- fill me --></b> + </li> + </template> + </ul> + </div> + </details> + </div> + </div> + <div class="lh-export"> + <button class="report-icon report-icon--share lh-export__button" title="Export report"></button> + <div class="lh-export__dropdown"> + <a href="#" class="report-icon report-icon--print" data-action="print">Print...</a> + <a href="#" class="report-icon report-icon--copy" data-action="copy">Copy JSON</a> + <a href="#" class="report-icon report-icon--download" data-action="save-html">Save as HTML</a> + <a href="#" class="report-icon report-icon--download" data-action="save-json">Save as JSON</a> + </div> + </div> + </div> +</template> + +<!-- Lighthouse footer --> +<template id="tmpl-lh-footer"> + <style> + .lh-footer { + text-align: center; + line-height: 90px; + background-color: var(--report-header-bg-color); + border-top: 1px solid var(--report-secondary-border-color); + } + </style> + <footer class="lh-footer"> + Generated by <b>Lighthouse</b> <span class="lh-footer__version"><!-- fill me --></span> on + <span class="lh-footer__timestamp"><!-- fill me --></span> | + <a href="https://github.com/GoogleChrome/Lighthouse/issues" target="_blank" rel="noopener">File an issue</a> + </footer> +</template> + +<!-- Lighthouse score gauge --> +<template id="tmpl-lh-gauge"> + <style> + .lh-gauge { + --circle-size: 80px; + --circle-size-half: calc(var(--circle-size) / 2); + --circle-background: #eee; + --circle-border-width: 8px; + --inset-size: calc(var(--circle-size) - var(--circle-border-width)); + --inset-color: #fff; + --transition-length: 1s; + width: var(--circle-size); + height: var(--circle-size); + background-color: var(--circle-background); + border-radius: 50%; + } + .lh-gauge--pass { + --circle-color: var(--pass-color); + color: var(--circle-color); + } + .lh-gauge--average { + --circle-color: var(--average-color); + color: var(--circle-color); + } + .lh-gauge--fail { + --circle-color: var(--fail-color); + color: var(--circle-color); + } + .lh-gauge__mask, + .lh-gauge__fill { + width: var(--circle-size); + height: var(--circle-size); + position: absolute; + transition: transform var(--transition-length); + border-radius: 50%; + } + .lh-gauge__mask { + clip: rect(0px, var(--circle-size), var(--circle-size), var(--circle-size-half)); + } + .lh-gauge__mask .lh-gauge__fill { + clip: rect(0px, var(--circle-size-half), var(--circle-size), 0px); + background-color: var(--circle-color); + backface-visibility: hidden; + } + .lh-gauge__percentage { + --spacer: calc((var(--circle-size) - var(--inset-size)) / 2); + width: var(--inset-size); + height: var(--inset-size); + position: absolute; + margin-left: var(--spacer); + margin-top: var(--spacer); + background-color: var(--inset-color); + border-radius: inherit; + display: flex; + align-items: center; + justify-content: center; + font-size: calc(var(--circle-size) / 3); + } + .lh-gauge__wrapper { + display: inline-flex; + align-items: center; + flex-direction: column; + text-decoration: none; + color: inherit; + flex: 1; + } + .lh-gauge__label { + font-size: 16px; + margin-top: var(--default-padding); + } + @media screen and (max-width: 767px) { + .lh-gauge__label { + font-size: 12px; + } + } + </style> + <a href="#" class="lh-gauge__wrapper"> + <div class="lh-gauge" data-progress="0"> + <div class="lh-gauge__circle"> + <div class="lh-gauge__mask lh-gauge__mask--full"> + <div class="lh-gauge__fill"></div> + </div> + <div class="lh-gauge__mask lh-gauge__mask--half"> + <div class="lh-gauge__fill"></div> + <div class="lh-gauge__fill lh-gauge__fill--fix"></div> + </div> + </div> + <div class="lh-gauge__percentage"></div> + </div> + <div class="lh-gauge__label"><!-- fill me --></div> + </a> +</template>
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2/module.json b/third_party/WebKit/Source/devtools/front_end/audits2/module.json index 4cd18338d..f5f79547 100644 --- a/third_party/WebKit/Source/devtools/front_end/audits2/module.json +++ b/third_party/WebKit/Source/devtools/front_end/audits2/module.json
@@ -18,14 +18,16 @@ ], "experiment": "audits2", "scripts": [ + "lighthouse/renderer/util.js", "lighthouse/renderer/dom.js", "lighthouse/renderer/details-renderer.js", + "lighthouse/renderer/category-renderer.js", "lighthouse/renderer/report-renderer.js", "Audits2Panel.js" ], "resources": [ - "audits2Panel.css", "lighthouse/report-styles.css", + "audits2Panel.css", "lighthouse/templates.html" ] }
diff --git a/third_party/WebKit/Source/devtools/front_end/audits2_worker/lighthouse/lighthouse-background.js b/third_party/WebKit/Source/devtools/front_end/audits2_worker/lighthouse/lighthouse-background.js index e7a2b44c..cd5b7b9 100644 --- a/third_party/WebKit/Source/devtools/front_end/audits2_worker/lighthouse/lighthouse-background.js +++ b/third_party/WebKit/Source/devtools/front_end/audits2_worker/lighthouse/lighthouse-background.js
@@ -1619,8 +1619,7 @@ */ 'use strict'; -const Audit = require('./byte-efficiency-audit'); -const TTIAudit = require('../time-to-interactive'); +const ByteEfficiencyAudit = require('./byte-efficiency-audit'); const URL = require('../../lib/url-shim'); const ALLOWABLE_OFFSCREEN_X = 100; @@ -1629,7 +1628,7 @@ const IGNORE_THRESHOLD_IN_BYTES = 2048; const IGNORE_THRESHOLD_IN_PERCENT = 75; -class OffscreenImages extends Audit { +class OffscreenImages extends ByteEfficiencyAudit { /** * @return {!AuditMeta} */ @@ -1701,6 +1700,7 @@ static audit_(artifacts) { const images = artifacts.ImageUsage; const viewportDimensions = artifacts.ViewportDimensions; + const trace = artifacts.traces[ByteEfficiencyAudit.DEFAULT_PASS]; let debugString; const resultsMap = images.reduce((results, image) => { @@ -1723,8 +1723,8 @@ return results; }, new Map()); - return TTIAudit.audit(artifacts).then(ttiResult => { - const ttiTimestamp = ttiResult.extendedInfo.value.timestamps.timeToInteractive / 1000000; + return artifacts.requestFirstInteractive(trace).then(firstInteractive => { + const ttiTimestamp = firstInteractive.timestamp / 1000000; const results = Array.from(resultsMap.values()).filter(item => { const isWasteful = item.wastedBytes > IGNORE_THRESHOLD_IN_BYTES && item.wastedPercent > IGNORE_THRESHOLD_IN_PERCENT; @@ -1747,7 +1747,7 @@ module.exports = OffscreenImages; -},{"../../lib/url-shim":25,"../time-to-interactive":"../audits/time-to-interactive","./byte-efficiency-audit":3}],"../audits/byte-efficiency/total-byte-weight":[function(require,module,exports){ +},{"../../lib/url-shim":25,"./byte-efficiency-audit":3}],"../audits/byte-efficiency/total-byte-weight":[function(require,module,exports){ /** * @license * Copyright 2017 Google Inc. All rights reserved. @@ -1769,7 +1769,7 @@ */ 'use strict'; -const Audit = require('./byte-efficiency-audit'); +const ByteEfficiencyAudit = require('./byte-efficiency-audit'); const Formatter = require('../../report/formatter'); const TracingProcessor = require('../../lib/traces/tracing-processor'); const URL = require('../../lib/url-shim'); @@ -1780,7 +1780,7 @@ const SCORING_POINT_OF_DIMINISHING_RETURNS = 2500 * 1024; const SCORING_MEDIAN = 4000 * 1024; -class TotalByteWeight extends Audit { +class TotalByteWeight extends ByteEfficiencyAudit { /** * @return {!AuditMeta} */ @@ -1795,7 +1795,7 @@ 'Network transfer size [costs users real dollars](https://whatdoesmysitecost.com/) ' + 'and is [highly correlated](http://httparchive.org/interesting.php#onLoad) with long load times. ' + 'Try to find ways to reduce the size of required files.', - scoringMode: Audit.SCORING_MODES.NUMERIC, + scoringMode: ByteEfficiencyAudit.SCORING_MODES.NUMERIC, requiredArtifacts: ['networkRecords'] }; } @@ -1805,7 +1805,7 @@ * @return {!Promise<!AuditResult>} */ static audit(artifacts) { - const networkRecords = artifacts.networkRecords[Audit.DEFAULT_PASS]; + const networkRecords = artifacts.networkRecords[ByteEfficiencyAudit.DEFAULT_PASS]; return artifacts.requestNetworkThroughput(networkRecords).then(networkThroughput => { let totalBytes = 0; const results = networkRecords.reduce((prev, record) => { @@ -1838,7 +1838,7 @@ return { rawValue: totalBytes, optimalValue: this.meta.optimalValue, - displayValue: `Total size was ${Audit.bytesToKbString(totalBytes)}`, + displayValue: `Total size was ${ByteEfficiencyAudit.bytesToKbString(totalBytes)}`, score: Math.round(Math.max(0, Math.min(score, 100))), extendedInfo: { formatter: Formatter.SUPPORTED_FORMATS.TABLE, @@ -1877,12 +1877,12 @@ */ 'use strict'; -const Audit = require('./byte-efficiency-audit'); +const ByteEfficiencyAudit = require('./byte-efficiency-audit'); const URL = require('../../lib/url-shim'); const PREVIEW_LENGTH = 100; -class UnusedCSSRules extends Audit { +class UnusedCSSRules extends ByteEfficiencyAudit { /** * @return {!AuditMeta} */ @@ -2032,7 +2032,7 @@ const styles = artifacts.Styles; const usage = artifacts.CSSUsage; const pageUrl = artifacts.URL.finalUrl; - const networkRecords = artifacts.networkRecords[Audit.DEFAULT_PASS]; + const networkRecords = artifacts.networkRecords[ByteEfficiencyAudit.DEFAULT_PASS]; const indexedSheets = UnusedCSSRules.indexStylesheetsById(styles, networkRecords); UnusedCSSRules.countUnusedRules(usage, indexedSheets); @@ -2081,7 +2081,7 @@ */ 'use strict'; -const Audit = require('./byte-efficiency-audit'); +const ByteEfficiencyAudit = require('./byte-efficiency-audit'); const URL = require('../../lib/url-shim'); const IGNORE_THRESHOLD_IN_BYTES = 2048; @@ -2089,7 +2089,7 @@ const JPEG_ALREADY_OPTIMIZED_THRESHOLD_IN_BYTES = 25 * 1024; const WEBP_ALREADY_OPTIMIZED_THRESHOLD_IN_BYTES = 100 * 1024; -class UsesOptimizedImages extends Audit { +class UsesOptimizedImages extends ByteEfficiencyAudit { /** * @return {!AuditMeta} */ @@ -2218,14 +2218,14 @@ */ 'use strict'; -const Audit = require('./byte-efficiency-audit'); +const ByteEfficiencyAudit = require('./byte-efficiency-audit'); const URL = require('../../lib/url-shim'); const IGNORE_THRESHOLD_IN_BYTES = 1400; const IGNORE_THRESHOLD_IN_PERCENT = 0.1; const TOTAL_WASTED_BYTES_THRESHOLD = 10 * 1024; // 10KB -class ResponsesAreCompressed extends Audit { +class ResponsesAreCompressed extends ByteEfficiencyAudit { /** * @return {!AuditMeta} */ @@ -2329,13 +2329,13 @@ */ 'use strict'; -const Audit = require('./byte-efficiency-audit'); +const ByteEfficiencyAudit = require('./byte-efficiency-audit'); const URL = require('../../lib/url-shim'); const IGNORE_THRESHOLD_IN_BYTES = 2048; const WASTEFUL_THRESHOLD_IN_BYTES = 25 * 1024; -class UsesResponsiveImages extends Audit { +class UsesResponsiveImages extends ByteEfficiencyAudit { /** * @return {!AuditMeta} */ @@ -4164,6 +4164,80 @@ module.exports = EstimatedInputLatency; +},{"../lib/traces/tracing-processor":24,"../report/formatter":27,"./audit":2}],"../audits/first-interactive":[function(require,module,exports){ +/** + * @license + * Copyright 2017 Google Inc. 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. + */ + +'use strict'; + +const Audit = require('./audit'); +const TracingProcessor = require('../lib/traces/tracing-processor'); +const Formatter = require('../report/formatter'); + +// Parameters (in ms) for log-normal CDF scoring. To see the curve: +// https://www.desmos.com/calculator/rjp0lbit8y +const SCORING_POINT_OF_DIMINISHING_RETURNS = 1700; +const SCORING_MEDIAN = 10000; + +const distribution = TracingProcessor.getLogNormalDistribution( + SCORING_MEDIAN, + SCORING_POINT_OF_DIMINISHING_RETURNS +); + +class FirstInteractiveMetric extends Audit { + /** + * @return {!AuditMeta} + */ + static get meta() { + return { + category: 'Performance', + name: 'first-interactive', + description: 'First Interactive (beta)', + helpText: 'The first point at which necessary scripts of the page have loaded ' + + 'and the CPU is idle enough to handle most user input.', + scoringMode: Audit.SCORING_MODES.NUMERIC, + requiredArtifacts: ['traces'] + }; + } + + /** + * Identify the time the page is "first interactive" + * @see https://docs.google.com/document/d/1GGiI9-7KeY3TPqS3YT271upUVimo-XiL5mwWorDUD4c/edit# + * + * @param {!Artifacts} artifacts + * @return {!Promise<!AuditResult>} + */ + static audit(artifacts) { + const trace = artifacts.traces[Audit.DEFAULT_PASS]; + return artifacts.requestFirstInteractive(trace) + .then(firstInteractive => { + let score = 100 * distribution.computeComplementaryPercentile(firstInteractive.timeInMs); + // Clamp the score to 0 <= x <= 100. + score = Math.min(100, score); + score = Math.max(0, score); + score = Math.round(score); + + const displayValue = Math.round(firstInteractive.timeInMs / 10) * 10; + return { + score, + rawValue: firstInteractive.timeInMs, + displayValue: `${displayValue.toLocaleString()}ms`, + extendedInfo: { + value: firstInteractive, + formatter: Formatter.SUPPORTED_FORMATS.NULL, + } + }; + }); + } +} + +module.exports = FirstInteractiveMetric; + },{"../lib/traces/tracing-processor":24,"../report/formatter":27,"./audit":2}],"../audits/first-meaningful-paint":[function(require,module,exports){ /** * @license @@ -4409,18 +4483,17 @@ /** @fileoverview * This audit evaluates if a page's load performance is fast enough for it to be considered a PWA. * We are doublechecking that the network requests were throttled (or slow on their own) - * Afterwards, we report if the TTI is less than 10 seconds. + * Afterwards, we report if the TTFI is less than 10 seconds. */ const Audit = require('./audit'); -const TTIMetric = require('./time-to-interactive'); const Emulation = require('../lib/emulation'); const Formatter = require('../report/formatter'); -// Maximum TTI to be considered "fast" for PWA baseline checklist +// Maximum TTFI to be considered "fast" for PWA baseline checklist // https://developers.google.com/web/progressive-web-apps/checklist -const MAXIMUM_TTI = 10 * 1000; +const MAXIMUM_TTFI = 10 * 1000; class LoadFastEnough4Pwa extends Audit { /** @@ -4452,20 +4525,21 @@ const areLatenciesAll3G = allRequestLatencies.every(val => val === undefined || val > latency3gMin); - return TTIMetric.audit(artifacts).then(ttiResult => { - const timeToInteractive = ttiResult.extendedInfo.value.timings.timeToInteractive; - const isFast = timeToInteractive < MAXIMUM_TTI; + const trace = artifacts.traces[Audit.DEFAULT_PASS]; + return artifacts.requestFirstInteractive(trace).then(firstInteractive => { + const timeToFirstInteractive = firstInteractive.timeInMs; + const isFast = timeToFirstInteractive < MAXIMUM_TTFI; const extendedInfo = { formatter: Formatter.SUPPORTED_FORMATS.NULL, - value: {areLatenciesAll3G, allRequestLatencies, isFast, timeToInteractive} + value: {areLatenciesAll3G, allRequestLatencies, isFast, timeToFirstInteractive} }; if (!areLatenciesAll3G) { return { rawValue: false, // eslint-disable-next-line max-len - debugString: `The Time To Interactive was found at ${ttiResult.displayValue}, however, the network request latencies were not sufficiently realistic, so the performance measurements cannot be trusted.`, + debugString: `First Interactive was found at ${timeToFirstInteractive.toLocaleString()}, however, the network request latencies were not sufficiently realistic, so the performance measurements cannot be trusted.`, extendedInfo }; } @@ -4474,7 +4548,7 @@ return { rawValue: false, // eslint-disable-next-line max-len - debugString: `Under 3G conditions, the Time To Interactive was at ${ttiResult.displayValue}. More details in the "Performance" section.`, + debugString: `Under 3G conditions, First Interactive was at ${timeToFirstInteractive.toLocaleString()}. More details in the "Performance" section.`, extendedInfo }; } @@ -4489,7 +4563,7 @@ module.exports = LoadFastEnough4Pwa; -},{"../lib/emulation":16,"../report/formatter":27,"./audit":2,"./time-to-interactive":"../audits/time-to-interactive"}],"../audits/manifest-short-name-length":[function(require,module,exports){ +},{"../lib/emulation":16,"../report/formatter":27,"./audit":2}],"../audits/manifest-short-name-length":[function(require,module,exports){ /** * @license * Copyright 2016 Google Inc. All rights reserved. @@ -4805,7 +4879,7 @@ 'use strict'; -const Audit = require('./multi-check-audit'); +const MultiCheckAudit = require('./multi-check-audit'); /** * @fileoverview @@ -4820,7 +4894,7 @@ * * manifest contains icon that's a png and size >= 512px */ -class SplashScreen extends Audit { +class SplashScreen extends MultiCheckAudit { /** * @return {!AuditMeta} @@ -4883,7 +4957,7 @@ 'use strict'; -const Audit = require('./multi-check-audit'); +const MultiCheckAudit = require('./multi-check-audit'); const validColor = require('../lib/web-inspector').Color.parse; /** @@ -4896,7 +4970,7 @@ * * HTML has a valid theme-color meta */ -class ThemedOmnibox extends Audit { +class ThemedOmnibox extends MultiCheckAudit { /** * @return {!AuditMeta} @@ -5431,7 +5505,7 @@ 'use strict'; -const Audit = require('./multi-check-audit'); +const MultiCheckAudit = require('./multi-check-audit'); const SWAudit = require('./service-worker'); /** @@ -5454,7 +5528,7 @@ * * it doesn't consider the site engagement score (naturally) */ -class WebappInstallBanner extends Audit { +class WebappInstallBanner extends MultiCheckAudit { /** * @return {!AuditMeta} @@ -5464,7 +5538,7 @@ category: 'PWA', name: 'webapp-install-banner', description: 'User can be prompted to Install the Web App', - helpText: 'While users can manually add your site to their homescreen, the [prompt (aka app install banner)](https://developers.google.com/web/updates/2015/03/increasing-engagement-with-app-install-banners-in-chrome-for-android) will proactively prompt the user to install the app if the various requirements are met and the user has moderate engagement with your site.', + helpText: 'While users can manually add your site to their homescreen, the [prompt (aka app install banner)](https://developers.google.com/web/fundamentals/engage-and-retain/app-install-banners/) will proactively prompt the user to install the app if the various requirements are met and the user has moderate engagement with your site.', requiredArtifacts: ['URL', 'ServiceWorker', 'Manifest'] }; } @@ -5635,7 +5709,7 @@ module.exports = WorksOffline; -},{"../lib/url-shim":25,"./audit":2}],"./computed/computed-artifact":[function(require,module,exports){ +},{"../lib/url-shim":25,"./audit":2}],"./gather/computed/computed-artifact":[function(require,module,exports){ /** * @license * Copyright 2016 Google Inc. All rights reserved. @@ -5656,8 +5730,15 @@ 'use strict'; class ComputedArtifact { - constructor() { - this.cache = new Map(); + /** + * @param {!ComputedArtifacts} allComputedArtifacts + */ + constructor(allComputedArtifacts) { + /** @private {!Map} */ + this._cache = new Map(); + + /** @private {!ComputedArtifacts} */ + this._allComputedArtifacts = allComputedArtifacts; } /* eslint-disable no-unused-vars */ @@ -5665,10 +5746,12 @@ /** * Override to implement a computed artifact. Can return a Promise or the * computed artifact itself. - * @param {!Object} artifact Input to computation. + * @param {*} artifact Input to computation. + * @param {!ComputedArtifacts} allComputedArtifacts Access to all computed artifacts. + * @return {*} * @throws {Error} */ - compute_(artifact) { + compute_(artifact, allComputedArtifacts) { throw new Error('compute_() not implemented for computed artifact ' + this.name); } @@ -5676,24 +5759,26 @@ /** * Request a computed artifact, caching the result on the input artifact. - * @param {!OBject} artifact - * @return {!Promise} + * @param {*} artifact + * @return {!Promise<*>} */ request(artifact) { - if (this.cache.has(artifact)) { - return Promise.resolve(this.cache.get(artifact)); + if (this._cache.has(artifact)) { + return Promise.resolve(this._cache.get(artifact)); } - return Promise.resolve().then(_ => this.compute_(artifact)).then(computedArtifact => { - this.cache.set(artifact, computedArtifact); - return computedArtifact; - }); + return Promise.resolve() + .then(_ => this.compute_(artifact, this._allComputedArtifacts)) + .then(computedArtifact => { + this._cache.set(artifact, computedArtifact); + return computedArtifact; + }); } } module.exports = ComputedArtifact; -},{}],"./computed/critical-request-chains":[function(require,module,exports){ +},{}],"./gather/computed/critical-request-chains":[function(require,module,exports){ /** * @license * Copyright 2016 Google Inc. All rights reserved. @@ -5832,7 +5917,231 @@ module.exports = CriticalRequestChains; -},{"../../lib/web-inspector":26,"./computed-artifact":"./computed/computed-artifact"}],"./computed/manifest-values":[function(require,module,exports){ +},{"../../lib/web-inspector":26,"./computed-artifact":"./gather/computed/computed-artifact"}],"./gather/computed/first-interactive":[function(require,module,exports){ +/** + * @license + * Copyright 2017 Google Inc. 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. + */ +'use strict'; + +const ComputedArtifact = require('./computed-artifact'); +const TracingProcessor = require('../../lib/traces/tracing-processor'); + +const LONG_TASK_THRESHOLD = 50; + +const MAX_TASK_CLUSTER_DURATION = 250; +const MIN_TASK_CLUSTER_PADDING = 1000; +const MIN_TASK_CLUSTER_FMP_DISTANCE = 5000; + +const MAX_QUIET_WINDOW_SIZE = 5000; +const TRACE_BUSY_MSG = 'trace was busy the entire time'; + +// Window size should be three seconds at 15 seconds after FMP +const EXPONENTIATION_COEFFICIENT = -Math.log(3 - 1) / 15; + +/** + * @fileoverview This artifact identifies the time the page is "first interactive" as defined below + * @see https://docs.google.com/document/d/1GGiI9-7KeY3TPqS3YT271upUVimo-XiL5mwWorDUD4c/edit# + * + * First Interactive marks the first moment when a website is minimally interactive: + * > Enough (but maybe not all) UI components shown on the screen are interactive + * DISCLAIMER: This is assumed by virtue of the fact that the CPU is idle; actual event + * listeners are not examined. Server-side rendering and extreme network latency can trick this + * definition. + * > The page responds to user input in a reasonable time on average, but it’s ok if this + * response is not always immediate. + * + * First Interactive is defined as the first period after FMP of N-seconds that has no bad task + * clusters. + * + * > t = time in seconds since FMP + * > N = f(t) = 4 * e^(-0.045 * t) + 1 + * 5 = f(0) = 4 + 1 + * 3 ~= f(15) ~= 2 + 1 + * 1 ~= f(∞) ~= 0 + 1 + * > a "bad task cluster" is a cluster of 1 or more long tasks with less than 1s of idle time + * between each task that does one of the following + * > Starts within the first 5s after FMP. + * > Spans more than 250ms from the start of the earliest task in the cluster to the end of the + * latest task in the cluster. + * + * If this timestamp is earlier than DOMContentLoaded, use DOMContentLoaded as firstInteractive. + */ +class FirstInteractive extends ComputedArtifact { + get name() { + return 'FirstInteractive'; + } + + /** + * @param {number} t The time passed since FMP in miliseconds. + * @return {number} + */ + static getRequiredWindowSizeInMs(t) { + const tInSeconds = t / 1000; + const exponentiationComponent = Math.exp(EXPONENTIATION_COEFFICIENT * tInSeconds); + return (4 * exponentiationComponent + 1) * 1000; + } + + /** + * Clusters tasks after startIndex that are in the specified window if they are within + * MIN_TASK_CLUSTER_PADDING ms of each other. Can return tasks that start outside of the window, + * but all clusters are guaranteed to have started before windowEnd. + * @param {!Array<{start: number, end: number}>} tasks + * @param {number} startIndex + * @param {number} windowEnd + * @return {!Array<{start: number, end: number, duration: number}>} + */ + static getTaskClustersInWindow(tasks, startIndex, windowEnd) { + const clusters = []; + + let previousTaskEndTime = -Infinity; + let currentCluster = null; + + // Examine all tasks that could possibly be part of a cluster starting before windowEnd. + // Consider the case where window end is 15s, there's a 100ms task from 14.9-15s and a 500ms + // task from 15.5-16s, we need that later task to be clustered with the first so we can properly + // identify that main thread isn't quiet. + const clusteringWindowEnd = windowEnd + MIN_TASK_CLUSTER_PADDING; + const isInClusteringWindow = task => task.start < clusteringWindowEnd; + for (let i = startIndex; i < tasks.length; i++) { + if (!isInClusteringWindow(tasks[i])) { + break; + } + + const task = tasks[i]; + + // if enough time has elapsed, we'll create a new cluster + if (task.start - previousTaskEndTime > MIN_TASK_CLUSTER_PADDING) { + currentCluster = []; + clusters.push(currentCluster); + } + + currentCluster.push(task); + previousTaskEndTime = task.end; + } + + return clusters + // add some useful information about the cluster + .map(tasks => { + const start = tasks[0].start; + const end = tasks[tasks.length - 1].end; + const duration = end - start; + return {start, end, duration}; + }) + // filter out clusters that started after the window because of our clusteringWindowEnd + .filter(cluster => cluster.start < windowEnd); + } + + /** + * Finds the timeInMs of the start of the first quiet window as defined by the firstInteractive + * conditions above. Throws an error if no acceptable quiet window could be found before the end + * of the trace. + * @param {number} FMP + * @param {number} traceEnd + * @param {!Array<{start: number, end: number>}} longTasks + * @return {number} + */ + static findQuietWindow(FMP, traceEnd, longTasks) { + // If we have an empty window at the very beginning, just return FMP early + if (longTasks.length === 0 || + longTasks[0].start > FMP + FirstInteractive.getRequiredWindowSizeInMs(0)) { + return FMP; + } + + const isTooCloseToFMP = cluster => cluster.start < FMP + MIN_TASK_CLUSTER_FMP_DISTANCE; + const isTooLong = cluster => cluster.duration > MAX_TASK_CLUSTER_DURATION; + const isBadCluster = cluster => isTooCloseToFMP(cluster) || isTooLong(cluster); + + // FirstInteractive must start at the end of a long task, consider each long task and + // examine the window that follows it. + for (let i = 0; i < longTasks.length; i++) { + const windowStart = longTasks[i].end; + const windowSize = FirstInteractive.getRequiredWindowSizeInMs(windowStart - FMP); + const windowEnd = windowStart + windowSize; + + // Check that we have a long enough trace + if (windowEnd > traceEnd) { + throw new Error(TRACE_BUSY_MSG); + } + + // Check that this task isn't the beginning of a cluster + if (i + 1 < longTasks.length && + longTasks[i + 1].start - windowStart <= MIN_TASK_CLUSTER_PADDING) { + continue; + } + + const taskClusters = FirstInteractive.getTaskClustersInWindow(longTasks, i + 1, windowEnd); + const hasBadTaskClusters = taskClusters.some(isBadCluster); + + if (!hasBadTaskClusters) { + return windowStart; + } + } + + throw new Error(TRACE_BUSY_MSG); + } + + /** + * @param {!Trace} trace + * @param {!tr.Model} traceModel + * @param {!TraceOfTabArtifact} traceOfTab + * @return {{timeInMs: number, timestamp: number}} + */ + computeWithArtifacts(trace, traceModel, traceOfTab) { + const navStart = traceOfTab.timestamps.navigationStart; + const FMP = traceOfTab.timings.firstMeaningfulPaint; + const DCL = traceOfTab.timings.domContentLoaded; + const traceEnd = traceOfTab.timings.traceEnd; + + if (traceEnd - FMP < MAX_QUIET_WINDOW_SIZE) { + throw new Error('trace not at least 5 seconds longer than FMP'); + } + + if (!FMP || !DCL) { + throw new Error(`No ${FMP ? 'domContentLoaded' : 'firstMeaningfulPaint'} event in trace`); + } + + const longTasksAfterFMP = TracingProcessor.getMainThreadTopLevelEvents(traceModel, trace, FMP) + .filter(evt => evt.duration >= LONG_TASK_THRESHOLD); + const firstInteractive = FirstInteractive.findQuietWindow(FMP, traceEnd, longTasksAfterFMP); + + const valueInMs = Math.max(firstInteractive, DCL); + return { + timeInMs: valueInMs, + timestamp: (valueInMs + navStart) * 1000, + }; + } + + /** + * @param {!Trace} trace + * @param {!ComputedArtifacts} artifacts + * @return {{timeInMs: number, timestamp: number}} + */ + compute_(trace, artifacts) { + return Promise.all([ + artifacts.requestTracingModel(trace), + artifacts.requestTraceOfTab(trace), + ]).then(([traceModel, traceOfTab]) => { + return this.computeWithArtifacts(trace, traceModel, traceOfTab); + }); + } +} + +module.exports = FirstInteractive; + +},{"../../lib/traces/tracing-processor":24,"./computed-artifact":"./gather/computed/computed-artifact"}],"./gather/computed/manifest-values":[function(require,module,exports){ /** * @license Copyright 2016 Google Inc. 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 @@ -5953,7 +6262,7 @@ module.exports = ManifestValues; -},{"../../lib/icons":18,"./computed-artifact":"./computed/computed-artifact"}],"./computed/network-throughput":[function(require,module,exports){ +},{"../../lib/icons":18,"./computed-artifact":"./gather/computed/computed-artifact"}],"./gather/computed/network-throughput":[function(require,module,exports){ /** * @license * Copyright 2017 Google Inc. All rights reserved. @@ -6028,7 +6337,7 @@ module.exports = NetworkThroughput; -},{"./computed-artifact":"./computed/computed-artifact"}],"./computed/pushed-requests":[function(require,module,exports){ +},{"./computed-artifact":"./gather/computed/computed-artifact"}],"./gather/computed/pushed-requests":[function(require,module,exports){ /** * @license * Copyright 2016 Google Inc. All rights reserved. @@ -6069,7 +6378,7 @@ module.exports = PushedRequests; -},{"./computed-artifact":"./computed/computed-artifact"}],"./computed/screenshots":[function(require,module,exports){ +},{"./computed-artifact":"./gather/computed/computed-artifact"}],"./gather/computed/screenshots":[function(require,module,exports){ /** * @license * Copyright 2016 Google Inc. All rights reserved. @@ -6125,7 +6434,7 @@ module.exports = ScreenshotFilmstrip; -},{"../../lib/traces/devtools-timeline-model":23,"./computed-artifact":"./computed/computed-artifact"}],"./computed/speedline":[function(require,module,exports){ +},{"../../lib/traces/devtools-timeline-model":23,"./computed-artifact":"./gather/computed/computed-artifact"}],"./gather/computed/speedline":[function(require,module,exports){ /** * @license * Copyright 2016 Google Inc. All rights reserved. @@ -6169,7 +6478,7 @@ module.exports = Speedline; -},{"./computed-artifact":"./computed/computed-artifact","speedline":302}],"./computed/trace-of-tab":[function(require,module,exports){ +},{"./computed-artifact":"./gather/computed/computed-artifact","speedline":302}],"./gather/computed/trace-of-tab":[function(require,module,exports){ /** * @license * Copyright 2017 Google Inc. All rights reserved. @@ -6260,7 +6569,10 @@ firstMeaningfulPaint = lastCandidate; } - const onLoad = keyEvents.find(e => e.name === 'MarkLoad' && e.ts > navigationStart.ts); + const onLoad = frameEvents.find(e => e.name === 'loadEventEnd' && e.ts > navigationStart.ts); + const domContentLoaded = frameEvents.find( + e => e.name === 'domContentLoadedEventEnd' && e.ts > navigationStart.ts + ); // subset all trace events to just our tab's process (incl threads other than main) const processEvents = trace.traceEvents @@ -6278,6 +6590,7 @@ firstMeaningfulPaint, traceEnd, onLoad, + domContentLoaded, }; const timings = {}; @@ -6304,7 +6617,7 @@ module.exports = TraceOfTab; -},{"../../lib/log":19,"./computed-artifact":"./computed/computed-artifact"}],"./computed/tracing-model":[function(require,module,exports){ +},{"../../lib/log":19,"./computed-artifact":"./gather/computed/computed-artifact"}],"./gather/computed/tracing-model":[function(require,module,exports){ /** * @license * Copyright 2017 Google Inc. All rights reserved. @@ -6347,8 +6660,7 @@ module.exports = TracingModel; -},{"../../lib/traces/tracing-processor":24,"./computed-artifact":"./computed/computed-artifact"}],"./gatherers/accessibility":[function(require,module,exports){ -(function (Buffer){ +},{"../../lib/traces/tracing-processor":24,"./computed-artifact":"./gather/computed/computed-artifact"}],"./gatherers/accessibility":[function(require,module,exports){ /** * @license * Copyright 2016 Google Inc. All rights reserved. @@ -6367,18 +6679,18 @@ */ 'use strict'; -/* global document */ +/* global window, document */ const Gatherer = require('./gatherer'); -const axe = Buffer("LyohIGFYZSB2Mi4xLjcKICogQ29weXJpZ2h0IChjKSAyMDE2IERlcXVlIFN5c3RlbXMsIEluYy4KICoKICogWW91ciB1c2Ugb2YgdGhpcyBTb3VyY2UgQ29kZSBGb3JtIGlzIHN1YmplY3QgdG8gdGhlIHRlcm1zIG9mIHRoZSBNb3ppbGxhIFB1YmxpYwogKiBMaWNlbnNlLCB2LiAyLjAuIElmIGEgY29weSBvZiB0aGUgTVBMIHdhcyBub3QgZGlzdHJpYnV0ZWQgd2l0aCB0aGlzCiAqIGZpbGUsIFlvdSBjYW4gb2J0YWluIG9uZSBhdCBodHRwOi8vbW96aWxsYS5vcmcvTVBMLzIuMC8uCiAqCiAqIFRoaXMgZW50aXJlIGNvcHlyaWdodCBub3RpY2UgbXVzdCBhcHBlYXIgaW4gZXZlcnkgY29weSBvZiB0aGlzIGZpbGUgeW91CiAqIGRpc3RyaWJ1dGUgb3IgaW4gYW55IGZpbGUgdGhhdCBjb250YWlucyBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGlzIHNvdXJjZQogKiBjb2RlLgogKi8KIWZ1bmN0aW9uIGEod2luZG93KXtmdW5jdGlvbiBiKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYjtyZXR1cm4gYT8oYj1heGUudXRpbHMuY2xvbmUoYSksYi5jb21tb25zPWEuY29tbW9ucyk6Yj17fSxiLnJlcG9ydGVyPWIucmVwb3J0ZXJ8fG51bGwsYi5ydWxlcz1iLnJ1bGVzfHxbXSxiLmNoZWNrcz1iLmNoZWNrc3x8W10sYi5kYXRhPU9iamVjdC5hc3NpZ24oe2NoZWNrczp7fSxydWxlczp7fX0sYi5kYXRhKSxifWZ1bmN0aW9uIGMoYSxiLGMpeyJ1c2Ugc3RyaWN0Ijt2YXIgZCxlO2ZvcihkPTAsZT1hLmxlbmd0aDtkPGU7ZCsrKWJbY10oYVtkXSl9ZnVuY3Rpb24gZChhKXt0aGlzLmJyYW5kPSJheGUiLHRoaXMuYXBwbGljYXRpb249ImF4ZUFQSSIsdGhpcy50YWdFeGNsdWRlPVsiZXhwZXJpbWVudGFsIl0sdGhpcy5kZWZhdWx0Q29uZmlnPWEsdGhpcy5faW5pdCgpfWZ1bmN0aW9uIGUoYSl7InVzZSBzdHJpY3QiO3RoaXMuaWQ9YS5pZCx0aGlzLmRhdGE9bnVsbCx0aGlzLnJlbGF0ZWROb2Rlcz1bXSx0aGlzLnJlc3VsdD1udWxsfWZ1bmN0aW9uIGYoYSl7InVzZSBzdHJpY3QiO3JldHVybiJzdHJpbmciPT10eXBlb2YgYT9uZXcgRnVuY3Rpb24oInJldHVybiAiK2ErIjsiKSgpOmF9ZnVuY3Rpb24gZyhhKXthJiYodGhpcy5pZD1hLmlkLHRoaXMuY29uZmlndXJlKGEpKX1mdW5jdGlvbiBoKGEsYil7InVzZSBzdHJpY3QiO2lmKCFheGUudXRpbHMuaXNIaWRkZW4oYikpe3ZhciBjPWF4ZS51dGlscy5maW5kQnkoYSwibm9kZSIsYik7Y3x8YS5wdXNoKHtub2RlOmIsaW5jbHVkZTpbXSxleGNsdWRlOltdfSl9fWZ1bmN0aW9uIGkoYSxiLGMpeyJ1c2Ugc3RyaWN0IjthLmZyYW1lcz1hLmZyYW1lc3x8W107dmFyIGQsZSxmPWRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoYy5zaGlmdCgpKTthOmZvcih2YXIgZz0wLGg9Zi5sZW5ndGg7ZzxoO2crKyl7ZT1mW2ddO2Zvcih2YXIgaT0wLGo9YS5mcmFtZXMubGVuZ3RoO2k8ajtpKyspaWYoYS5mcmFtZXNbaV0ubm9kZT09PWUpe2EuZnJhbWVzW2ldW2JdLnB1c2goYyk7YnJlYWsgYX1kPXtub2RlOmUsaW5jbHVkZTpbXSxleGNsdWRlOltdfSxjJiZkW2JdLnB1c2goYyksYS5mcmFtZXMucHVzaChkKX19ZnVuY3Rpb24gaihhKXsidXNlIHN0cmljdCI7aWYoYSYmIm9iamVjdCI9PT0oInVuZGVmaW5lZCI9PXR5cGVvZiBhPyJ1bmRlZmluZWQiOlgoYSkpfHxhIGluc3RhbmNlb2YgTm9kZUxpc3Qpe2lmKGEgaW5zdGFuY2VvZiBOb2RlKXJldHVybntpbmNsdWRlOlthXSxleGNsdWRlOltdfTtpZihhLmhhc093blByb3BlcnR5KCJpbmNsdWRlIil8fGEuaGFzT3duUHJvcGVydHkoImV4Y2x1ZGUiKSlyZXR1cm57aW5jbHVkZTphLmluY2x1ZGV8fFtkb2N1bWVudF0sZXhjbHVkZTphLmV4Y2x1ZGV8fFtdfTtpZihhLmxlbmd0aD09PSthLmxlbmd0aClyZXR1cm57aW5jbHVkZTphLGV4Y2x1ZGU6W119fXJldHVybiJzdHJpbmciPT10eXBlb2YgYT97aW5jbHVkZTpbYV0sZXhjbHVkZTpbXX06e2luY2x1ZGU6W2RvY3VtZW50XSxleGNsdWRlOltdfX1mdW5jdGlvbiBrKGEsYil7InVzZSBzdHJpY3QiO2Zvcih2YXIgYyxkPVtdLGU9MCxmPWFbYl0ubGVuZ3RoO2U8ZjtlKyspe2lmKGM9YVtiXVtlXSwic3RyaW5nIj09dHlwZW9mIGMpe2Q9ZC5jb25jYXQoYXhlLnV0aWxzLnRvQXJyYXkoZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbChjKSkpO2JyZWFrfSFjfHwhYy5sZW5ndGh8fGMgaW5zdGFuY2VvZiBOb2RlP2QucHVzaChjKTpjLmxlbmd0aD4xP2koYSxiLGMpOmQ9ZC5jb25jYXQoYXhlLnV0aWxzLnRvQXJyYXkoZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbChjWzBdKSkpfXJldHVybiBkLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4gYX0pfWZ1bmN0aW9uIGwoYSl7InVzZSBzdHJpY3QiO2lmKDA9PT1hLmluY2x1ZGUubGVuZ3RoKXtpZigwPT09YS5mcmFtZXMubGVuZ3RoKXt2YXIgYj1heGUudXRpbHMucmVzcG9uZGFibGUuaXNJbkZyYW1lKCk/ImZyYW1lIjoicGFnZSI7cmV0dXJuIG5ldyBFcnJvcigiTm8gZWxlbWVudHMgZm91bmQgZm9yIGluY2x1ZGUgaW4gIitiKyIgQ29udGV4dCIpfWEuZnJhbWVzLmZvckVhY2goZnVuY3Rpb24oYSxiKXtpZigwPT09YS5pbmNsdWRlLmxlbmd0aClyZXR1cm4gbmV3IEVycm9yKCJObyBlbGVtZW50cyBmb3VuZCBmb3IgaW5jbHVkZSBpbiBDb250ZXh0IG9mIGZyYW1lICIrYil9KX19ZnVuY3Rpb24gbShhKXsidXNlIHN0cmljdCI7dmFyIGI9dGhpczt0aGlzLmZyYW1lcz1bXSx0aGlzLmluaXRpYXRvcj0hYXx8ImJvb2xlYW4iIT10eXBlb2YgYS5pbml0aWF0b3J8fGEuaW5pdGlhdG9yLHRoaXMucGFnZT0hMSxhPWooYSksdGhpcy5leGNsdWRlPWEuZXhjbHVkZSx0aGlzLmluY2x1ZGU9YS5pbmNsdWRlLHRoaXMuaW5jbHVkZT1rKHRoaXMsImluY2x1ZGUiKSx0aGlzLmV4Y2x1ZGU9ayh0aGlzLCJleGNsdWRlIiksYXhlLnV0aWxzLnNlbGVjdCgiZnJhbWUsIGlmcmFtZSIsdGhpcykuZm9yRWFjaChmdW5jdGlvbihhKXtWKGEsYikmJmgoYi5mcmFtZXMsYSl9KSwxPT09dGhpcy5pbmNsdWRlLmxlbmd0aCYmdGhpcy5pbmNsdWRlWzBdPT09ZG9jdW1lbnQmJih0aGlzLnBhZ2U9ITApO3ZhciBjPWwodGhpcyk7aWYoYyBpbnN0YW5jZW9mIEVycm9yKXRocm93IGN9ZnVuY3Rpb24gbihhKXsidXNlIHN0cmljdCI7dGhpcy5pZD1hLmlkLHRoaXMucmVzdWx0PWF4ZS5jb25zdGFudHMuTkEsdGhpcy5wYWdlTGV2ZWw9YS5wYWdlTGV2ZWwsdGhpcy5pbXBhY3Q9bnVsbCx0aGlzLm5vZGVzPVtdfWZ1bmN0aW9uIG8oYSxiKXsidXNlIHN0cmljdCI7dGhpcy5fYXVkaXQ9Yix0aGlzLmlkPWEuaWQsdGhpcy5zZWxlY3Rvcj1hLnNlbGVjdG9yfHwiKiIsdGhpcy5leGNsdWRlSGlkZGVuPSJib29sZWFuIiE9dHlwZW9mIGEuZXhjbHVkZUhpZGRlbnx8YS5leGNsdWRlSGlkZGVuLHRoaXMuZW5hYmxlZD0iYm9vbGVhbiIhPXR5cGVvZiBhLmVuYWJsZWR8fGEuZW5hYmxlZCx0aGlzLnBhZ2VMZXZlbD0iYm9vbGVhbiI9PXR5cGVvZiBhLnBhZ2VMZXZlbCYmYS5wYWdlTGV2ZWwsdGhpcy5hbnk9YS5hbnl8fFtdLHRoaXMuYWxsPWEuYWxsfHxbXSx0aGlzLm5vbmU9YS5ub25lfHxbXSx0aGlzLnRhZ3M9YS50YWdzfHxbXSxhLm1hdGNoZXMmJih0aGlzLm1hdGNoZXM9ZihhLm1hdGNoZXMpKX1mdW5jdGlvbiBwKGEpeyJ1c2Ugc3RyaWN0IjtyZXR1cm4gYXhlLnV0aWxzLmdldEFsbENoZWNrcyhhKS5tYXAoZnVuY3Rpb24oYil7dmFyIGM9YS5fYXVkaXQuY2hlY2tzW2IuaWR8fGJdO3JldHVybiBjJiYiZnVuY3Rpb24iPT10eXBlb2YgYy5hZnRlcj9jOm51bGx9KS5maWx0ZXIoQm9vbGVhbil9ZnVuY3Rpb24gcShhLGIpeyJ1c2Ugc3RyaWN0Ijt2YXIgYz1bXTtyZXR1cm4gYS5mb3JFYWNoKGZ1bmN0aW9uKGEpe3ZhciBkPWF4ZS51dGlscy5nZXRBbGxDaGVja3MoYSk7ZC5mb3JFYWNoKGZ1bmN0aW9uKGEpe2EuaWQ9PT1iJiZjLnB1c2goYSl9KX0pLGN9ZnVuY3Rpb24gcihhKXsidXNlIHN0cmljdCI7cmV0dXJuIGEuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBhLmZpbHRlcmVkIT09ITB9KX1mdW5jdGlvbiBzKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1bImFueSIsImFsbCIsIm5vbmUiXSxjPWEubm9kZXMuZmlsdGVyKGZ1bmN0aW9uKGEpe3ZhciBjPTA7cmV0dXJuIGIuZm9yRWFjaChmdW5jdGlvbihiKXthW2JdPXIoYVtiXSksYys9YVtiXS5sZW5ndGh9KSxjPjB9KTtyZXR1cm4gYS5wYWdlTGV2ZWwmJmMubGVuZ3RoJiYoYz1bYy5yZWR1Y2UoZnVuY3Rpb24oYSxjKXtpZihhKXJldHVybiBiLmZvckVhY2goZnVuY3Rpb24oYil7YVtiXS5wdXNoLmFwcGx5KGFbYl0sY1tiXSl9KSxhfSldKSxjfWZ1bmN0aW9uIHQoYSxiKXsidXNlIHN0cmljdCI7aWYoIWF4ZS5fYXVkaXQpdGhyb3cgbmV3IEVycm9yKCJObyBhdWRpdCBjb25maWd1cmVkIik7dmFyIGM9YXhlLnV0aWxzLnF1ZXVlKCksZD1bXTtPYmplY3Qua2V5cyhheGUucGx1Z2lucykuZm9yRWFjaChmdW5jdGlvbihhKXtjLmRlZmVyKGZ1bmN0aW9uKGIpe3ZhciBjPWZ1bmN0aW9uKGEpe2QucHVzaChhKSxiKCl9O3RyeXtheGUucGx1Z2luc1thXS5jbGVhbnVwKGIsYyl9Y2F0Y2goZSl7YyhlKX19KX0pLGF4ZS51dGlscy50b0FycmF5KGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoImZyYW1lLCBpZnJhbWUiKSkuZm9yRWFjaChmdW5jdGlvbihhKXtjLmRlZmVyKGZ1bmN0aW9uKGIsYyl7cmV0dXJuIGF4ZS51dGlscy5zZW5kQ29tbWFuZFRvRnJhbWUoYSx7Y29tbWFuZDoiY2xlYW51cC1wbHVnaW4ifSxiLGMpfSl9KSxjLnRoZW4oZnVuY3Rpb24oYyl7MD09PWQubGVuZ3RoP2EoYyk6YihkKX0pWyJjYXRjaCJdKGIpfWZ1bmN0aW9uIHUoYSl7InVzZSBzdHJpY3QiO3ZhciBiO2lmKGI9YXhlLl9hdWRpdCwhYil0aHJvdyBuZXcgRXJyb3IoIk5vIGF1ZGl0IGNvbmZpZ3VyZWQiKTthLnJlcG9ydGVyJiYoImZ1bmN0aW9uIj09dHlwZW9mIGEucmVwb3J0ZXJ8fCRbYS5yZXBvcnRlcl0pJiYoYi5yZXBvcnRlcj1hLnJlcG9ydGVyKSxhLmNoZWNrcyYmYS5jaGVja3MuZm9yRWFjaChmdW5jdGlvbihhKXtiLmFkZENoZWNrKGEpfSksYS5ydWxlcyYmYS5ydWxlcy5mb3JFYWNoKGZ1bmN0aW9uKGEpe2IuYWRkUnVsZShhKX0pLCJ1bmRlZmluZWQiIT10eXBlb2YgYS5icmFuZGluZz9iLnNldEJyYW5kaW5nKGEuYnJhbmRpbmcpOmIuX2NvbnN0cnVjdEhlbHBVcmxzKCksYS50YWdFeGNsdWRlJiYoYi50YWdFeGNsdWRlPWEudGFnRXhjbHVkZSl9ZnVuY3Rpb24gdihhLGIsYyl7InVzZSBzdHJpY3QiO3ZhciBkPWMsZT1mdW5jdGlvbihhKXthIGluc3RhbmNlb2YgRXJyb3I9PSExJiYoYT1uZXcgRXJyb3IoYSkpLGMoYSl9LGY9YSYmYS5jb250ZXh0fHx7fTtmLmluY2x1ZGUmJiFmLmluY2x1ZGUubGVuZ3RoJiYoZi5pbmNsdWRlPVtkb2N1bWVudF0pO3ZhciBnPWEmJmEub3B0aW9uc3x8e307c3dpdGNoKGEuY29tbWFuZCl7Y2FzZSJydWxlcyI6cmV0dXJuIHkoZixnLGQsZSk7Y2FzZSJjbGVhbnVwLXBsdWdpbiI6cmV0dXJuIHQoZCxlKTtkZWZhdWx0OmlmKGF4ZS5fYXVkaXQmJmF4ZS5fYXVkaXQuY29tbWFuZHMmJmF4ZS5fYXVkaXQuY29tbWFuZHNbYS5jb21tYW5kXSlyZXR1cm4gYXhlLl9hdWRpdC5jb21tYW5kc1thLmNvbW1hbmRdKGEsYyl9fWZ1bmN0aW9uIHcoYSl7InVzZSBzdHJpY3QiO3RoaXMuX3J1bj1hLnJ1bix0aGlzLl9jb2xsZWN0PWEuY29sbGVjdCx0aGlzLl9yZWdpc3RyeT17fSxhLmNvbW1hbmRzLmZvckVhY2goZnVuY3Rpb24oYSl7YXhlLl9hdWRpdC5yZWdpc3RlckNvbW1hbmQoYSl9KX1mdW5jdGlvbiB4KCl7InVzZSBzdHJpY3QiO3ZhciBhPWF4ZS5fYXVkaXQ7aWYoIWEpdGhyb3cgbmV3IEVycm9yKCJObyBhdWRpdCBjb25maWd1cmVkIik7YS5yZXNldFJ1bGVzQW5kQ2hlY2tzKCl9ZnVuY3Rpb24geShhLGIsYyxkKXsidXNlIHN0cmljdCI7YT1uZXcgbShhKTt2YXIgZT1heGUudXRpbHMucXVldWUoKSxmPWF4ZS5fYXVkaXQ7YS5mcmFtZXMubGVuZ3RoJiZlLmRlZmVyKGZ1bmN0aW9uKGMsZCl7YXhlLnV0aWxzLmNvbGxlY3RSZXN1bHRzRnJvbUZyYW1lcyhhLGIsInJ1bGVzIixudWxsLGMsZCl9KSxlLmRlZmVyKGZ1bmN0aW9uKGMsZCl7Zi5ydW4oYSxiLGMsZCl9KSxlLnRoZW4oZnVuY3Rpb24oZSl7dHJ5e3ZhciBnPWF4ZS51dGlscy5tZXJnZVJlc3VsdHMoZS5tYXAoZnVuY3Rpb24oYSl7cmV0dXJue3Jlc3VsdHM6YX19KSk7YS5pbml0aWF0b3ImJihnPWYuYWZ0ZXIoZyxiKSxnLmZvckVhY2goYXhlLnV0aWxzLnB1Ymxpc2hNZXRhRGF0YSksZz1nLm1hcChheGUudXRpbHMuZmluYWxpemVSdWxlUmVzdWx0KSk7dHJ5e2MoZyl9Y2F0Y2goaCl7YXhlLmxvZyhoKX19Y2F0Y2goaCl7ZChoKX19KVsiY2F0Y2giXShkKX1mdW5jdGlvbiB6KGEpeyJ1c2Ugc3RyaWN0Ijtzd2l0Y2goITApe2Nhc2Uic3RyaW5nIj09dHlwZW9mIGE6Y2FzZSBBcnJheS5pc0FycmF5KGEpOmNhc2UgTm9kZSYmYSBpbnN0YW5jZW9mIE5vZGU6Y2FzZSBOb2RlTGlzdCYmYSBpbnN0YW5jZW9mIE5vZGVMaXN0OnJldHVybiEwO2Nhc2Uib2JqZWN0IiE9PSgidW5kZWZpbmVkIj09dHlwZW9mIGE/InVuZGVmaW5lZCI6WChhKSk6cmV0dXJuITE7Y2FzZSB2b2lkIDAhPT1hLmluY2x1ZGU6Y2FzZSB2b2lkIDAhPT1hLmV4Y2x1ZGU6Y2FzZSJudW1iZXIiPT10eXBlb2YgYS5sZW5ndGg6cmV0dXJuITA7ZGVmYXVsdDpyZXR1cm4hMX19ZnVuY3Rpb24gQShhLGIsYyl7InVzZSBzdHJpY3QiO3ZhciBkPW5ldyBUeXBlRXJyb3IoImF4ZS5ydW4gYXJndW1lbnRzIGFyZSBpbnZhbGlkIik7aWYoIXooYSkpe2lmKHZvaWQgMCE9PWMpdGhyb3cgZDtjPWIsYj1hLGE9ZG9jdW1lbnR9aWYoIm9iamVjdCIhPT0oInVuZGVmaW5lZCI9PXR5cGVvZiBiPyJ1bmRlZmluZWQiOlgoYikpKXtpZih2b2lkIDAhPT1jKXRocm93IGQ7Yz1iLGI9e319aWYoImZ1bmN0aW9uIiE9dHlwZW9mIGMmJnZvaWQgMCE9PWMpdGhyb3cgZDtyZXR1cm57Y29udGV4dDphLG9wdGlvbnM6YixjYWxsYmFjazpjfHxffX1mdW5jdGlvbiBCKGEsYil7InVzZSBzdHJpY3QiO1siYW55IiwiYWxsIiwibm9uZSJdLmZvckVhY2goZnVuY3Rpb24oYyl7QXJyYXkuaXNBcnJheShhW2NdKSYmYVtjXS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuIEFycmF5LmlzQXJyYXkoYS5yZWxhdGVkTm9kZXMpfSkuZm9yRWFjaChmdW5jdGlvbihhKXthLnJlbGF0ZWROb2Rlcz1hLnJlbGF0ZWROb2Rlcy5tYXAoZnVuY3Rpb24oYSl7dmFyIGM9e2h0bWw6YS5zb3VyY2UsdGFyZ2V0OmEuc2VsZWN0b3J9O3JldHVybiBiJiYoYy54cGF0aD1hLnhwYXRoKSxjfSl9KX0pfWZ1bmN0aW9uIEMoYSxiKXtyZXR1cm4gY2EucmVkdWNlKGZ1bmN0aW9uKGMsZCl7cmV0dXJuIGNbZF09KGFbZF18fFtdKS5tYXAoZnVuY3Rpb24oYSl7cmV0dXJuIGIoYSxkKX0pLGN9LHt9KX1mdW5jdGlvbiBEKGEsYixjKXt2YXIgZD1PYmplY3QuYXNzaWduKHt9LGIpO2Qubm9kZXM9KGRbY118fFtdKS5jb25jYXQoKSxheGUuY29uc3RhbnRzLnJlc3VsdEdyb3Vwcy5mb3JFYWNoKGZ1bmN0aW9uKGEpe2RlbGV0ZSBkW2FdfSksYVtjXS5wdXNoKGQpfWZ1bmN0aW9uIEUoYSxiLGMpeyJ1c2Ugc3RyaWN0Ijt2YXIgZD13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShhLG51bGwpLGU9ITE7cmV0dXJuISFkJiYoYi5mb3JFYWNoKGZ1bmN0aW9uKGEpe2QuZ2V0UHJvcGVydHlWYWx1ZShhLnByb3BlcnR5KT09PWEudmFsdWUmJihlPSEwKX0pLCEhZXx8IShhLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCk9PT1jLnRvVXBwZXJDYXNlKCl8fCFhLnBhcmVudE5vZGUpJiZFKGEucGFyZW50Tm9kZSxiLGMpKX1mdW5jdGlvbiBGKGEsYil7InVzZSBzdHJpY3QiO3JldHVybiBuZXcgRXJyb3IoYSsiOiAiK2F4ZS51dGlscy5nZXRTZWxlY3RvcihiKSl9ZnVuY3Rpb24gRyhhLGIsYyxkLGUsZil7InVzZSBzdHJpY3QiO3ZhciBnPWF4ZS51dGlscy5xdWV1ZSgpLGg9YS5mcmFtZXM7aC5mb3JFYWNoKGZ1bmN0aW9uKGUpe3ZhciBmPXtvcHRpb25zOmIsY29tbWFuZDpjLHBhcmFtZXRlcjpkLGNvbnRleHQ6e2luaXRpYXRvcjohMSxwYWdlOmEucGFnZSxpbmNsdWRlOmUuaW5jbHVkZXx8W10sZXhjbHVkZTplLmV4Y2x1ZGV8fFtdfX07Zy5kZWZlcihmdW5jdGlvbihhLGIpe3ZhciBjPWUubm9kZTtheGUudXRpbHMuc2VuZENvbW1hbmRUb0ZyYW1lKGMsZixmdW5jdGlvbihiKXtyZXR1cm4gYj9hKHtyZXN1bHRzOmIsZnJhbWVFbGVtZW50OmMsZnJhbWU6YXhlLnV0aWxzLmdldFNlbGVjdG9yKGMpfSk6dm9pZCBhKG51bGwpfSxiKX0pfSksZy50aGVuKGZ1bmN0aW9uKGEpe2UoYXhlLnV0aWxzLm1lcmdlUmVzdWx0cyhhKSl9KVsiY2F0Y2giXShmKX1mdW5jdGlvbiBIKGEsYil7InVzZSBzdHJpY3QiO2lmKGI9Ynx8MzAwLGEubGVuZ3RoPmIpe3ZhciBjPWEuaW5kZXhPZigiPiIpO2E9YS5zdWJzdHJpbmcoMCxjKzEpfXJldHVybiBhfWZ1bmN0aW9uIEkoYSl7InVzZSBzdHJpY3QiO3ZhciBiPWEub3V0ZXJIVE1MO3JldHVybiBifHwiZnVuY3Rpb24iIT10eXBlb2YgWE1MU2VyaWFsaXplcnx8KGI9KG5ldyBYTUxTZXJpYWxpemVyKS5zZXJpYWxpemVUb1N0cmluZyhhKSksSChifHwiIil9ZnVuY3Rpb24gSihhLGIpeyJ1c2Ugc3RyaWN0IjtiPWJ8fHt9LHRoaXMuc2VsZWN0b3I9Yi5zZWxlY3Rvcnx8W2F4ZS51dGlscy5nZXRTZWxlY3RvcihhKV0sdGhpcy54cGF0aD1iLnhwYXRofHxbYXhlLnV0aWxzLmdldFhwYXRoKGEpXSx0aGlzLnNvdXJjZT12b2lkIDAhPT1iLnNvdXJjZT9iLnNvdXJjZTpJKGEpLHRoaXMuZWxlbWVudD1hfWZ1bmN0aW9uIEsoYSl7InVzZSBzdHJpY3QiO3ZhciBiPTEsYz1hLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCk7Zm9yKGE9YS5wcmV2aW91c0VsZW1lbnRTaWJsaW5nO2E7KWEubm9kZU5hbWUudG9VcHBlckNhc2UoKT09PWMmJmIrKyxhPWEucHJldmlvdXNFbGVtZW50U2libGluZztyZXR1cm4gYn1mdW5jdGlvbiBMKGEsYil7InVzZSBzdHJpY3QiO3ZhciBjLGQsZT1hLnBhcmVudE5vZGUuY2hpbGRyZW47aWYoIWUpcmV0dXJuITE7dmFyIGY9ZS5sZW5ndGg7Zm9yKGM9MDtjPGY7YysrKWlmKGQ9ZVtjXSxkIT09YSYmYXhlLnV0aWxzLm1hdGNoZXNTZWxlY3RvcihkLGIpKXJldHVybiEwO3JldHVybiExfWZ1bmN0aW9uIE0oYSxiKXt2YXIgYyxkO2lmKCFhKXJldHVybltdO2lmKCFiJiY5PT09YS5ub2RlVHlwZSlyZXR1cm4gYj1be3N0cjoiaHRtbCJ9XTtpZihiPWJ8fFtdLGEucGFyZW50Tm9kZSYmYS5wYXJlbnROb2RlIT09YSYmKGI9TShhLnBhcmVudE5vZGUsYikpLGEucHJldmlvdXNTaWJsaW5nKXtkPTEsYz1hLnByZXZpb3VzU2libGluZztkbyAxPT09Yy5ub2RlVHlwZSYmYy5ub2RlTmFtZT09PWEubm9kZU5hbWUmJmQrKyxjPWMucHJldmlvdXNTaWJsaW5nO3doaWxlKGMpOzE9PT1kJiYoZD1udWxsKX1lbHNlIGlmKGEubmV4dFNpYmxpbmcpe2M9YS5uZXh0U2libGluZztkbyAxPT09Yy5ub2RlVHlwZSYmYy5ub2RlTmFtZT09PWEubm9kZU5hbWU/KGQ9MSxjPW51bGwpOihkPW51bGwsYz1jLnByZXZpb3VzU2libGluZyk7d2hpbGUoYyl9aWYoMT09PWEubm9kZVR5cGUpe3ZhciBlPXt9O2Uuc3RyPWEubm9kZU5hbWUudG9Mb3dlckNhc2UoKSxhLmdldEF0dHJpYnV0ZSYmYS5nZXRBdHRyaWJ1dGUoImlkIikmJjE9PT1hLm93bmVyRG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgiIyIrYXhlLnV0aWxzLmVzY2FwZVNlbGVjdG9yKGEuaWQpKS5sZW5ndGgmJihlLmlkPWEuZ2V0QXR0cmlidXRlKCJpZCIpKSxkPjEmJihlLmNvdW50PWQpLGIucHVzaChlKX1yZXR1cm4gYn1mdW5jdGlvbiBOKGEpe3JldHVybiBhLnJlZHVjZShmdW5jdGlvbihhLGIpe3JldHVybiBiLmlkPyIvIitiLnN0cisiW0BpZD0nIitiLmlkKyInXSI6YSsoIi8iK2Iuc3RyKSsoYi5jb3VudD4wPyJbIitiLmNvdW50KyJdIjoiIil9LCIiKX1mdW5jdGlvbiBPKGEpeyJ1c2Ugc3RyaWN0IjtpZihkYSYmZGEucGFyZW50Tm9kZSlyZXR1cm4gdm9pZCAwPT09ZGEuc3R5bGVTaGVldD9kYS5hcHBlbmRDaGlsZChkb2N1bWVudC5jcmVhdGVUZXh0Tm9kZShhKSk6ZGEuc3R5bGVTaGVldC5jc3NUZXh0Kz1hLGRhO2lmKGEpe3ZhciBiPWRvY3VtZW50LmhlYWR8fGRvY3VtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCJoZWFkIilbMF07cmV0dXJuIGRhPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInN0eWxlIiksZGEudHlwZT0idGV4dC9jc3MiLHZvaWQgMD09PWRhLnN0eWxlU2hlZXQ/ZGEuYXBwZW5kQ2hpbGQoZG9jdW1lbnQuY3JlYXRlVGV4dE5vZGUoYSkpOmRhLnN0eWxlU2hlZXQuY3NzVGV4dD1hLGIuYXBwZW5kQ2hpbGQoZGEpLGRhfX1mdW5jdGlvbiBQKGEsYixjKXsidXNlIHN0cmljdCI7dmFyIGQ9YXhlLnV0aWxzLmdldFhwYXRoKGIpLGU9e2VsZW1lbnQ6YixzZWxlY3RvcjpjLHhwYXRoOmR9O2EuZm9yRWFjaChmdW5jdGlvbihhKXthLm5vZGU9YXhlLnV0aWxzLkRxRWxlbWVudC5mcm9tRnJhbWUoYS5ub2RlLGUpO3ZhciBiPWF4ZS51dGlscy5nZXRBbGxDaGVja3MoYSk7Yi5sZW5ndGgmJmIuZm9yRWFjaChmdW5jdGlvbihhKXthLnJlbGF0ZWROb2Rlcz1hLnJlbGF0ZWROb2Rlcy5tYXAoZnVuY3Rpb24oYSl7cmV0dXJuIGF4ZS51dGlscy5EcUVsZW1lbnQuZnJvbUZyYW1lKGEsZSl9KX0pfSl9ZnVuY3Rpb24gUShhLGIpeyJ1c2Ugc3RyaWN0Ijtmb3IodmFyIGMsZCxlPWJbMF0ubm9kZSxmPTAsZz1hLmxlbmd0aDtmPGc7ZisrKWlmKGQ9YVtmXS5ub2RlLGM9YXhlLnV0aWxzLm5vZGVTb3J0ZXIoZC5lbGVtZW50LGUuZWxlbWVudCksYz4wfHwwPT09YyYmZS5zZWxlY3Rvci5sZW5ndGg8ZC5zZWxlY3Rvci5sZW5ndGgpcmV0dXJuIHZvaWQgYS5zcGxpY2UuYXBwbHkoYSxbZiwwXS5jb25jYXQoYikpO2EucHVzaC5hcHBseShhLGIpfWZ1bmN0aW9uIFIoYSl7InVzZSBzdHJpY3QiO3JldHVybiBhJiZhLnJlc3VsdHM/QXJyYXkuaXNBcnJheShhLnJlc3VsdHMpP2EucmVzdWx0cy5sZW5ndGg/YS5yZXN1bHRzOm51bGw6W2EucmVzdWx0c106bnVsbH1mdW5jdGlvbiBTKGEsYil7InVzZSBzdHJpY3QiO3JldHVybiBmdW5jdGlvbihjKXt2YXIgZD1hW2MuaWRdfHx7fSxlPWQubWVzc2FnZXN8fHt9LGY9T2JqZWN0LmFzc2lnbih7fSxkKTtkZWxldGUgZi5tZXNzYWdlcyxmLm1lc3NhZ2U9Yy5yZXN1bHQ9PT1iP2UucGFzczplLmZhaWwsYXhlLnV0aWxzLmV4dGVuZE1ldGFEYXRhKGMsZil9fWZ1bmN0aW9uIFQoYSxiKXsidXNlIHN0cmljdCI7dmFyIGMsZCxlLGY9YXhlLl9hdWRpdCYmYXhlLl9hdWRpdC50YWdFeGNsdWRlP2F4ZS5fYXVkaXQudGFnRXhjbHVkZTpbXTtyZXR1cm4gYi5pbmNsdWRlfHxiLmV4Y2x1ZGU/KGM9Yi5pbmNsdWRlfHxbXSxjPUFycmF5LmlzQXJyYXkoYyk/YzpbY10sZD1iLmV4Y2x1ZGV8fFtdLGQ9QXJyYXkuaXNBcnJheShkKT9kOltkXSxkPWQuY29uY2F0KGYuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBjLmluZGV4T2YoYSk9PT0tMX0pKSk6KGM9QXJyYXkuaXNBcnJheShiKT9iOltiXSxkPWYuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBjLmluZGV4T2YoYSk9PT0tMX0pKSxlPWMuc29tZShmdW5jdGlvbihiKXtyZXR1cm4gYS50YWdzLmluZGV4T2YoYikhPT0tMX0pLCEhKGV8fDA9PT1jLmxlbmd0aCYmYS5lbmFibGVkIT09ITEpJiZkLmV2ZXJ5KGZ1bmN0aW9uKGIpe3JldHVybiBhLnRhZ3MuaW5kZXhPZihiKT09PS0xfSl9ZnVuY3Rpb24gVShhKXsidXNlIHN0cmljdCI7cmV0dXJuIGEuc29ydChmdW5jdGlvbihhLGIpe3JldHVybiBheGUudXRpbHMuY29udGFpbnMoYSxiKT8xOi0xfSlbMF19ZnVuY3Rpb24gVihhLGIpeyJ1c2Ugc3RyaWN0Ijt2YXIgYz1iLmluY2x1ZGUmJlUoYi5pbmNsdWRlLmZpbHRlcihmdW5jdGlvbihiKXtyZXR1cm4gYXhlLnV0aWxzLmNvbnRhaW5zKGIsYSl9KSksZD1iLmV4Y2x1ZGUmJlUoYi5leGNsdWRlLmZpbHRlcihmdW5jdGlvbihiKXtyZXR1cm4gYXhlLnV0aWxzLmNvbnRhaW5zKGIsYSl9KSk7cmV0dXJuISEoIWQmJmN8fGQmJmF4ZS51dGlscy5jb250YWlucyhkLGMpKX1mdW5jdGlvbiBXKGEsYixjKXsidXNlIHN0cmljdCI7Zm9yKHZhciBkPTAsZT1iLmxlbmd0aDtkPGU7ZCsrKWEuaW5kZXhPZihiW2RdKT09PS0xJiZWKGJbZF0sYykmJmEucHVzaChiW2RdKX12YXIgZG9jdW1lbnQ9d2luZG93LmRvY3VtZW50LFg9ImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmInN5bWJvbCI9PXR5cGVvZiBTeW1ib2wuaXRlcmF0b3I/ZnVuY3Rpb24oYSl7cmV0dXJuIHR5cGVvZiBhfTpmdW5jdGlvbihhKXtyZXR1cm4gYSYmImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmYS5jb25zdHJ1Y3Rvcj09PVN5bWJvbCYmYSE9PVN5bWJvbC5wcm90b3R5cGU/InN5bWJvbCI6dHlwZW9mIGF9LGF4ZT1heGV8fHt9O2F4ZS52ZXJzaW9uPSIyLjEuNyIsImZ1bmN0aW9uIj09dHlwZW9mIGRlZmluZSYmZGVmaW5lLmFtZCYmZGVmaW5lKFtdLGZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO3JldHVybiBheGV9KSwib2JqZWN0Ij09PSgidW5kZWZpbmVkIj09dHlwZW9mIG1vZHVsZT8idW5kZWZpbmVkIjpYKG1vZHVsZSkpJiZtb2R1bGUuZXhwb3J0cyYmImZ1bmN0aW9uIj09dHlwZW9mIGEudG9TdHJpbmcmJihheGUuc291cmNlPSIoIithLnRvU3RyaW5nKCkrIikodGhpcywgdGhpcy5kb2N1bWVudCk7Iixtb2R1bGUuZXhwb3J0cz1heGUpLCJmdW5jdGlvbiI9PXR5cGVvZiB3aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZSYmKHdpbmRvdy5heGU9YXhlKTt2YXIgY29tbW9ucyx1dGlscz1heGUudXRpbHM9e30sWT17fSxYPSJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJiJzeW1ib2wiPT10eXBlb2YgU3ltYm9sLml0ZXJhdG9yP2Z1bmN0aW9uKGEpe3JldHVybiB0eXBlb2YgYX06ZnVuY3Rpb24oYSl7cmV0dXJuIGEmJiJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJmEuY29uc3RydWN0b3I9PT1TeW1ib2wmJmEhPT1TeW1ib2wucHJvdG90eXBlPyJzeW1ib2wiOnR5cGVvZiBhfTtkLnByb3RvdHlwZS5faW5pdD1mdW5jdGlvbigpe3ZhciBhPWIodGhpcy5kZWZhdWx0Q29uZmlnKTtheGUuY29tbW9ucz1jb21tb25zPWEuY29tbW9ucyx0aGlzLnJlcG9ydGVyPWEucmVwb3J0ZXIsdGhpcy5jb21tYW5kcz17fSx0aGlzLnJ1bGVzPVtdLHRoaXMuY2hlY2tzPXt9LGMoYS5ydWxlcyx0aGlzLCJhZGRSdWxlIiksYyhhLmNoZWNrcyx0aGlzLCJhZGRDaGVjayIpLHRoaXMuZGF0YT17fSx0aGlzLmRhdGEuY2hlY2tzPWEuZGF0YSYmYS5kYXRhLmNoZWNrc3x8e30sdGhpcy5kYXRhLnJ1bGVzPWEuZGF0YSYmYS5kYXRhLnJ1bGVzfHx7fSx0aGlzLmRhdGEuZmFpbHVyZVN1bW1hcmllcz1hLmRhdGEmJmEuZGF0YS5mYWlsdXJlU3VtbWFyaWVzfHx7fSx0aGlzLl9jb25zdHJ1Y3RIZWxwVXJscygpfSxkLnByb3RvdHlwZS5yZWdpc3RlckNvbW1hbmQ9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3RoaXMuY29tbWFuZHNbYS5pZF09YS5jYWxsYmFja30sZC5wcm90b3R5cGUuYWRkUnVsZT1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7YS5tZXRhZGF0YSYmKHRoaXMuZGF0YS5ydWxlc1thLmlkXT1hLm1ldGFkYXRhKTt2YXIgYj10aGlzLmdldFJ1bGUoYS5pZCk7Yj9iLmNvbmZpZ3VyZShhKTp0aGlzLnJ1bGVzLnB1c2gobmV3IG8oYSx0aGlzKSl9LGQucHJvdG90eXBlLmFkZENoZWNrPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1hLm1ldGFkYXRhOyJvYmplY3QiPT09KCJ1bmRlZmluZWQiPT10eXBlb2YgYj8idW5kZWZpbmVkIjpYKGIpKSYmKHRoaXMuZGF0YS5jaGVja3NbYS5pZF09Yiwib2JqZWN0Ij09PVgoYi5tZXNzYWdlcykmJk9iamVjdC5rZXlzKGIubWVzc2FnZXMpLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4gYi5tZXNzYWdlcy5oYXNPd25Qcm9wZXJ0eShhKSYmInN0cmluZyI9PXR5cGVvZiBiLm1lc3NhZ2VzW2FdfSkuZm9yRWFjaChmdW5jdGlvbihhKXswPT09Yi5tZXNzYWdlc1thXS5pbmRleE9mKCJmdW5jdGlvbiIpJiYoYi5tZXNzYWdlc1thXT1uZXcgRnVuY3Rpb24oInJldHVybiAiK2IubWVzc2FnZXNbYV0rIjsiKSgpKX0pKSx0aGlzLmNoZWNrc1thLmlkXT90aGlzLmNoZWNrc1thLmlkXS5jb25maWd1cmUoYSk6dGhpcy5jaGVja3NbYS5pZF09bmV3IGcoYSl9LGQucHJvdG90eXBlLnJ1bj1mdW5jdGlvbihhLGIsYyxkKXsidXNlIHN0cmljdCI7dGhpcy52YWxpZGF0ZU9wdGlvbnMoYik7dmFyIGU9YXhlLnV0aWxzLnF1ZXVlKCk7dGhpcy5ydWxlcy5mb3JFYWNoKGZ1bmN0aW9uKGMpe2F4ZS51dGlscy5ydWxlU2hvdWxkUnVuKGMsYSxiKSYmZS5kZWZlcihmdW5jdGlvbihkLGUpe2MucnVuKGEsYixkLGZ1bmN0aW9uKGEpe2lmKGIuZGVidWcpZShhKTtlbHNle3ZhciBmPU9iamVjdC5hc3NpZ24obmV3IG4oYykse3Jlc3VsdDpheGUuY29uc3RhbnRzLkNBTlRURUxMLGRlc2NyaXB0aW9uOiJBbiBlcnJvciBvY2N1cmVkIHdoaWxlIHJ1bm5pbmcgdGhpcyBydWxlIixtZXNzYWdlOmEubWVzc2FnZSxoZWxwOmEuc3RhY2t8fGEubWVzc2FnZSxlcnJvcjphfSk7ZChmKX19KX0pfSksZS50aGVuKGZ1bmN0aW9uKGEpe2MoYS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuISFhfSkpfSlbImNhdGNoIl0oZCl9LGQucHJvdG90eXBlLmFmdGVyPWZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO3ZhciBjPXRoaXMucnVsZXM7cmV0dXJuIGEubWFwKGZ1bmN0aW9uKGEpe3ZhciBkPWF4ZS51dGlscy5maW5kQnkoYywiaWQiLGEuaWQpO3JldHVybiBkLmFmdGVyKGEsYil9KX0sZC5wcm90b3R5cGUuZ2V0UnVsZT1mdW5jdGlvbihhKXtyZXR1cm4gdGhpcy5ydWxlcy5maW5kKGZ1bmN0aW9uKGIpe3JldHVybiBiLmlkPT09YX0pfSxkLnByb3RvdHlwZS52YWxpZGF0ZU9wdGlvbnM9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPXRoaXM7aWYoIm9iamVjdCI9PT1YKGEucnVuT25seSkpe3ZhciBjPWEucnVuT25seTtpZigicnVsZSI9PT1jLnR5cGUmJkFycmF5LmlzQXJyYXkoYy52YWx1ZSkpYy52YWx1ZS5mb3JFYWNoKGZ1bmN0aW9uKGEpe2lmKCFiLmdldFJ1bGUoYSkpdGhyb3cgbmV3IEVycm9yKCJ1bmtub3duIHJ1bGUgYCIrYSsiYCBpbiBvcHRpb25zLnJ1bk9ubHkiKX0pO2Vsc2UgaWYoQXJyYXkuaXNBcnJheShjLnZhbHVlKSYmYy52YWx1ZS5sZW5ndGg+MCl7dmFyIGQ9W10uY29uY2F0KGMudmFsdWUpO2lmKGIucnVsZXMuZm9yRWFjaChmdW5jdGlvbihhKXt2YXIgYixjLGU7aWYoZClmb3IoYz0wLGU9YS50YWdzLmxlbmd0aDtjPGU7YysrKWI9ZC5pbmRleE9mKGEudGFnc1tjXSksYiE9PS0xJiZkLnNwbGljZShiLDEpfSksMCE9PWQubGVuZ3RoKXRocm93IG5ldyBFcnJvcigiY291bGQgbm90IGZpbmQgdGFncyBgIitkLmpvaW4oImAsIGAiKSsiYCIpfX1yZXR1cm4ib2JqZWN0Ij09PVgoYS5ydWxlcykmJk9iamVjdC5rZXlzKGEucnVsZXMpLmZvckVhY2goZnVuY3Rpb24oYSl7aWYoIWIuZ2V0UnVsZShhKSl0aHJvdyBuZXcgRXJyb3IoInVua25vd24gcnVsZSBgIithKyJgIGluIG9wdGlvbnMucnVsZXMiKX0pLGF9LGQucHJvdG90eXBlLnNldEJyYW5kaW5nPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjthJiZhLmhhc093blByb3BlcnR5KCJicmFuZCIpJiZhLmJyYW5kJiYic3RyaW5nIj09dHlwZW9mIGEuYnJhbmQmJih0aGlzLmJyYW5kPWEuYnJhbmQpLGEmJmEuaGFzT3duUHJvcGVydHkoImFwcGxpY2F0aW9uIikmJmEuYXBwbGljYXRpb24mJiJzdHJpbmciPT10eXBlb2YgYS5hcHBsaWNhdGlvbiYmKHRoaXMuYXBwbGljYXRpb249YS5hcHBsaWNhdGlvbiksdGhpcy5fY29uc3RydWN0SGVscFVybHMoKX0sZC5wcm90b3R5cGUuX2NvbnN0cnVjdEhlbHBVcmxzPWZ1bmN0aW9uKCl7dmFyIGE9dGhpcyxiPWF4ZS52ZXJzaW9uLnN1YnN0cmluZygwLGF4ZS52ZXJzaW9uLmxhc3RJbmRleE9mKCIuIikpO3RoaXMucnVsZXMuZm9yRWFjaChmdW5jdGlvbihjKXthLmRhdGEucnVsZXNbYy5pZF09YS5kYXRhLnJ1bGVzW2MuaWRdfHx7fSxhLmRhdGEucnVsZXNbYy5pZF0uaGVscFVybD0iaHR0cHM6Ly9kZXF1ZXVuaXZlcnNpdHkuY29tL3J1bGVzLyIrYS5icmFuZCsiLyIrYisiLyIrYy5pZCsiP2FwcGxpY2F0aW9uPSIrYS5hcHBsaWNhdGlvbn0pfSxkLnByb3RvdHlwZS5yZXNldFJ1bGVzQW5kQ2hlY2tzPWZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiO3RoaXMuX2luaXQoKX0sZy5wcm90b3R5cGUuZW5hYmxlZD0hMCxnLnByb3RvdHlwZS5ydW49ZnVuY3Rpb24oYSxiLGMsZCl7InVzZSBzdHJpY3QiO2I9Ynx8e307dmFyIGY9Yi5oYXNPd25Qcm9wZXJ0eSgiZW5hYmxlZCIpP2IuZW5hYmxlZDp0aGlzLmVuYWJsZWQsZz1iLm9wdGlvbnN8fHRoaXMub3B0aW9ucztpZihmKXt2YXIgaCxpPW5ldyBlKHRoaXMpLGo9YXhlLnV0aWxzLmNoZWNrSGVscGVyKGksYyxkKTt0cnl7aD10aGlzLmV2YWx1YXRlLmNhbGwoaixhLGcpfWNhdGNoKGspe3JldHVybiB2b2lkIGQoayl9ai5pc0FzeW5jfHwoaS5yZXN1bHQ9aCxzZXRUaW1lb3V0KGZ1bmN0aW9uKCl7YyhpKX0sMCkpfWVsc2UgYyhudWxsKX0sZy5wcm90b3R5cGUuY29uZmlndXJlPWZ1bmN0aW9uKGEpe3ZhciBiPXRoaXM7WyJvcHRpb25zIiwiZW5hYmxlZCJdLmZpbHRlcihmdW5jdGlvbihiKXtyZXR1cm4gYS5oYXNPd25Qcm9wZXJ0eShiKX0pLmZvckVhY2goZnVuY3Rpb24oYyl7cmV0dXJuIGJbY109YVtjXX0pLFsiZXZhbHVhdGUiLCJhZnRlciJdLmZpbHRlcihmdW5jdGlvbihiKXtyZXR1cm4gYS5oYXNPd25Qcm9wZXJ0eShiKX0pLmZvckVhY2goZnVuY3Rpb24oYyl7cmV0dXJuIGJbY109ZihhW2NdKX0pfTt2YXIgWD0iZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiYic3ltYm9sIj09dHlwZW9mIFN5bWJvbC5pdGVyYXRvcj9mdW5jdGlvbihhKXtyZXR1cm4gdHlwZW9mIGF9OmZ1bmN0aW9uKGEpe3JldHVybiBhJiYiZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiZhLmNvbnN0cnVjdG9yPT09U3ltYm9sJiZhIT09U3ltYm9sLnByb3RvdHlwZT8ic3ltYm9sIjp0eXBlb2YgYX07by5wcm90b3R5cGUubWF0Y2hlcz1mdW5jdGlvbigpeyJ1c2Ugc3RyaWN0IjtyZXR1cm4hMH0sby5wcm90b3R5cGUuZ2F0aGVyPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1heGUudXRpbHMuc2VsZWN0KHRoaXMuc2VsZWN0b3IsYSk7cmV0dXJuIHRoaXMuZXhjbHVkZUhpZGRlbj9iLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4hYXhlLnV0aWxzLmlzSGlkZGVuKGEpfSk6Yn0sby5wcm90b3R5cGUucnVuQ2hlY2tzPWZ1bmN0aW9uKGEsYixjLGQsZSl7InVzZSBzdHJpY3QiO3ZhciBmPXRoaXMsZz1heGUudXRpbHMucXVldWUoKTt0aGlzW2FdLmZvckVhY2goZnVuY3Rpb24oYSl7dmFyIGQ9Zi5fYXVkaXQuY2hlY2tzW2EuaWR8fGFdLGU9YXhlLnV0aWxzLmdldENoZWNrT3B0aW9uKGQsZi5pZCxjKTtnLmRlZmVyKGZ1bmN0aW9uKGEsYyl7ZC5ydW4oYixlLGEsYyl9KX0pLGcudGhlbihmdW5jdGlvbihiKXtiPWIuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBhfSksZCh7dHlwZTphLHJlc3VsdHM6Yn0pfSlbImNhdGNoIl0oZSl9LG8ucHJvdG90eXBlLnJ1bj1mdW5jdGlvbihhLGIsYyxkKXsidXNlIHN0cmljdCI7dmFyIGUsZj10aGlzLmdhdGhlcihhKSxnPWF4ZS51dGlscy5xdWV1ZSgpLGg9dGhpcztlPW5ldyBuKHRoaXMpLGYuZm9yRWFjaChmdW5jdGlvbihhKXtoLm1hdGNoZXMoYSkmJmcuZGVmZXIoZnVuY3Rpb24oYyxkKXt2YXIgZj1heGUudXRpbHMucXVldWUoKTtmLmRlZmVyKGZ1bmN0aW9uKGMsZCl7aC5ydW5DaGVja3MoImFueSIsYSxiLGMsZCl9KSxmLmRlZmVyKGZ1bmN0aW9uKGMsZCl7aC5ydW5DaGVja3MoImFsbCIsYSxiLGMsZCl9KSxmLmRlZmVyKGZ1bmN0aW9uKGMsZCl7aC5ydW5DaGVja3MoIm5vbmUiLGEsYixjLGQpfSksZi50aGVuKGZ1bmN0aW9uKGIpe2lmKGIubGVuZ3RoKXt2YXIgZD0hMSxmPXt9O2IuZm9yRWFjaChmdW5jdGlvbihhKXt2YXIgYj1hLnJlc3VsdHMuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBhfSk7ZlthLnR5cGVdPWIsYi5sZW5ndGgmJihkPSEwKX0pLGQmJihmLm5vZGU9bmV3IGF4ZS51dGlscy5EcUVsZW1lbnQoYSksZS5ub2Rlcy5wdXNoKGYpKX1jKCl9KVsiY2F0Y2giXShkKX0pfSksZy50aGVuKGZ1bmN0aW9uKCl7YyhlKX0pWyJjYXRjaCJdKGQpfSxvLnByb3RvdHlwZS5hZnRlcj1mdW5jdGlvbihhLGIpeyJ1c2Ugc3RyaWN0Ijt2YXIgYz1wKHRoaXMpLGQ9dGhpcy5pZDtyZXR1cm4gYy5mb3JFYWNoKGZ1bmN0aW9uKGMpe3ZhciBlPXEoYS5ub2RlcyxjLmlkKSxmPWF4ZS51dGlscy5nZXRDaGVja09wdGlvbihjLGQsYiksZz1jLmFmdGVyKGUsZik7ZS5mb3JFYWNoKGZ1bmN0aW9uKGEpe2cuaW5kZXhPZihhKT09PS0xJiYoYS5maWx0ZXJlZD0hMCl9KX0pLGEubm9kZXM9cyhhKSxhfSxvLnByb3RvdHlwZS5jb25maWd1cmU9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO2EuaGFzT3duUHJvcGVydHkoInNlbGVjdG9yIikmJih0aGlzLnNlbGVjdG9yPWEuc2VsZWN0b3IpLGEuaGFzT3duUHJvcGVydHkoImV4Y2x1ZGVIaWRkZW4iKSYmKHRoaXMuZXhjbHVkZUhpZGRlbj0iYm9vbGVhbiIhPXR5cGVvZiBhLmV4Y2x1ZGVIaWRkZW58fGEuZXhjbHVkZUhpZGRlbiksYS5oYXNPd25Qcm9wZXJ0eSgiZW5hYmxlZCIpJiYodGhpcy5lbmFibGVkPSJib29sZWFuIiE9dHlwZW9mIGEuZW5hYmxlZHx8YS5lbmFibGVkKSxhLmhhc093blByb3BlcnR5KCJwYWdlTGV2ZWwiKSYmKHRoaXMucGFnZUxldmVsPSJib29sZWFuIj09dHlwZW9mIGEucGFnZUxldmVsJiZhLnBhZ2VMZXZlbCksYS5oYXNPd25Qcm9wZXJ0eSgiYW55IikmJih0aGlzLmFueT1hLmFueSksYS5oYXNPd25Qcm9wZXJ0eSgiYWxsIikmJih0aGlzLmFsbD1hLmFsbCksYS5oYXNPd25Qcm9wZXJ0eSgibm9uZSIpJiYodGhpcy5ub25lPWEubm9uZSksYS5oYXNPd25Qcm9wZXJ0eSgidGFncyIpJiYodGhpcy50YWdzPWEudGFncyksYS5oYXNPd25Qcm9wZXJ0eSgibWF0Y2hlcyIpJiYoInN0cmluZyI9PXR5cGVvZiBhLm1hdGNoZXM/dGhpcy5tYXRjaGVzPW5ldyBGdW5jdGlvbigicmV0dXJuICIrYS5tYXRjaGVzKyI7IikoKTp0aGlzLm1hdGNoZXM9YS5tYXRjaGVzKX0sZnVuY3Rpb24oYXhlKXt2YXIgYT1be25hbWU6Ik5BIix2YWx1ZToiaW5hcHBsaWNhYmxlIixwcmlvcml0eTowLGdyb3VwOiJpbmFwcGxpY2FibGUifSx7bmFtZToiUEFTUyIsdmFsdWU6InBhc3NlZCIscHJpb3JpdHk6MSxncm91cDoicGFzc2VzIn0se25hbWU6IkNBTlRURUxMIix2YWx1ZToiY2FudFRlbGwiLHByaW9yaXR5OjIsZ3JvdXA6ImluY29tcGxldGUifSx7bmFtZToiRkFJTCIsdmFsdWU6ImZhaWxlZCIscHJpb3JpdHk6Myxncm91cDoidmlvbGF0aW9ucyJ9XSxiPXtyZXN1bHRzOltdLHJlc3VsdEdyb3VwczpbXSxyZXN1bHRHcm91cE1hcDp7fSxpbXBhY3Q6T2JqZWN0LmZyZWV6ZShbIm1pbm9yIiwibW9kZXJhdGUiLCJzZXJpb3VzIiwiY3JpdGljYWwiXSl9O2EuZm9yRWFjaChmdW5jdGlvbihhKXt2YXIgYz1hLm5hbWUsZD1hLnZhbHVlLGU9YS5wcmlvcml0eSxmPWEuZ3JvdXA7YltjXT1kLGJbYysiX1BSSU8iXT1lLGJbYysiX0dST1VQIl09ZixiLnJlc3VsdHNbZV09ZCxiLnJlc3VsdEdyb3Vwc1tlXT1mLGIucmVzdWx0R3JvdXBNYXBbZF09Zn0pLE9iamVjdC5mcmVlemUoYi5yZXN1bHRzKSxPYmplY3QuZnJlZXplKGIucmVzdWx0R3JvdXBzKSxPYmplY3QuZnJlZXplKGIucmVzdWx0R3JvdXBNYXApLE9iamVjdC5mcmVlemUoYiksT2JqZWN0LmRlZmluZVByb3BlcnR5KGF4ZSwiY29uc3RhbnRzIix7dmFsdWU6YixlbnVtZXJhYmxlOiEwLGNvbmZpZ3VyYWJsZTohMSx3cml0YWJsZTohMX0pfShheGUpO3ZhciBYPSJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJiJzeW1ib2wiPT10eXBlb2YgU3ltYm9sLml0ZXJhdG9yP2Z1bmN0aW9uKGEpe3JldHVybiB0eXBlb2YgYX06ZnVuY3Rpb24oYSl7cmV0dXJuIGEmJiJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJmEuY29uc3RydWN0b3I9PT1TeW1ib2wmJmEhPT1TeW1ib2wucHJvdG90eXBlPyJzeW1ib2wiOnR5cGVvZiBhfTtheGUubG9nPWZ1bmN0aW9uKCl7InVzZSBzdHJpY3QiOyJvYmplY3QiPT09KCJ1bmRlZmluZWQiPT10eXBlb2YgY29uc29sZT8idW5kZWZpbmVkIjpYKGNvbnNvbGUpKSYmY29uc29sZS5sb2cmJkZ1bmN0aW9uLnByb3RvdHlwZS5hcHBseS5jYWxsKGNvbnNvbGUubG9nLGNvbnNvbGUsYXJndW1lbnRzKX07dmFyIFg9ImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmInN5bWJvbCI9PXR5cGVvZiBTeW1ib2wuaXRlcmF0b3I/ZnVuY3Rpb24oYSl7cmV0dXJuIHR5cGVvZiBhfTpmdW5jdGlvbihhKXtyZXR1cm4gYSYmImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmYS5jb25zdHJ1Y3Rvcj09PVN5bWJvbCYmYSE9PVN5bWJvbC5wcm90b3R5cGU/InN5bWJvbCI6dHlwZW9mIGF9O2F4ZS5hMTF5Q2hlY2s9ZnVuY3Rpb24oYSxiLGMpeyJ1c2Ugc3RyaWN0IjsiZnVuY3Rpb24iPT10eXBlb2YgYiYmKGM9YixiPXt9KSxiJiYib2JqZWN0Ij09PSgidW5kZWZpbmVkIj09dHlwZW9mIGI/InVuZGVmaW5lZCI6WChiKSl8fChiPXt9KTt2YXIgZD1heGUuX2F1ZGl0O2lmKCFkKXRocm93IG5ldyBFcnJvcigiTm8gYXVkaXQgY29uZmlndXJlZCIpO2IucmVwb3J0ZXI9Yi5yZXBvcnRlcnx8ZC5yZXBvcnRlcnx8InYyIjt2YXIgZT1heGUuZ2V0UmVwb3J0ZXIoYi5yZXBvcnRlcik7YXhlLl9ydW5SdWxlcyhhLGIsZnVuY3Rpb24oYSl7dmFyIGQ9ZShhLGIsYyk7dm9pZCAwIT09ZCYmYyhkKX0sYXhlLmxvZyl9LGF4ZS5jbGVhbnVwPXQsYXhlLmNvbmZpZ3VyZT11LGF4ZS5nZXRSdWxlcz1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7YT1hfHxbXTt2YXIgYj1hLmxlbmd0aD9heGUuX2F1ZGl0LnJ1bGVzLmZpbHRlcihmdW5jdGlvbihiKXtyZXR1cm4hIWEuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBiLnRhZ3MuaW5kZXhPZihhKSE9PS0xfSkubGVuZ3RofSk6YXhlLl9hdWRpdC5ydWxlcyxjPWF4ZS5fYXVkaXQuZGF0YS5ydWxlc3x8e307cmV0dXJuIGIubWFwKGZ1bmN0aW9uKGEpe3ZhciBiPWNbYS5pZF18fHt9O3JldHVybntydWxlSWQ6YS5pZCxkZXNjcmlwdGlvbjpiLmRlc2NyaXB0aW9uLGhlbHA6Yi5oZWxwLGhlbHBVcmw6Yi5oZWxwVXJsLHRhZ3M6YS50YWdzfX0pfSxheGUuX2xvYWQ9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO2F4ZS51dGlscy5yZXNwb25kYWJsZS5zdWJzY3JpYmUoImF4ZS5waW5nIixmdW5jdGlvbihhLGIsYyl7Yyh7YXhlOiEwfSl9KSxheGUudXRpbHMucmVzcG9uZGFibGUuc3Vic2NyaWJlKCJheGUuc3RhcnQiLHYpLGF4ZS5fYXVkaXQ9bmV3IGQoYSl9O3ZhciBheGU9YXhlfHx7fTtheGUucGx1Z2lucz17fSx3LnByb3RvdHlwZS5ydW49ZnVuY3Rpb24oKXsidXNlIHN0cmljdCI7cmV0dXJuIHRoaXMuX3J1bi5hcHBseSh0aGlzLGFyZ3VtZW50cyl9LHcucHJvdG90eXBlLmNvbGxlY3Q9ZnVuY3Rpb24oKXsidXNlIHN0cmljdCI7cmV0dXJuIHRoaXMuX2NvbGxlY3QuYXBwbHkodGhpcyxhcmd1bWVudHMpfSx3LnByb3RvdHlwZS5jbGVhbnVwPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1heGUudXRpbHMucXVldWUoKSxjPXRoaXM7T2JqZWN0LmtleXModGhpcy5fcmVnaXN0cnkpLmZvckVhY2goZnVuY3Rpb24oYSl7Yi5kZWZlcihmdW5jdGlvbihiKXtjLl9yZWdpc3RyeVthXS5jbGVhbnVwKGIpfSl9KSxiLnRoZW4oZnVuY3Rpb24oKXthKCl9KX0sdy5wcm90b3R5cGUuYWRkPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt0aGlzLl9yZWdpc3RyeVthLmlkXT1hfSxheGUucmVnaXN0ZXJQbHVnaW49ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO2F4ZS5wbHVnaW5zW2EuaWRdPW5ldyB3KGEpfTt2YXIgWiwkPXt9O2F4ZS5nZXRSZXBvcnRlcj1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7cmV0dXJuInN0cmluZyI9PXR5cGVvZiBhJiYkW2FdPyRbYV06ImZ1bmN0aW9uIj09dHlwZW9mIGE/YTpafSxheGUuYWRkUmVwb3J0ZXI9ZnVuY3Rpb24oYSxiLGMpeyJ1c2Ugc3RyaWN0IjskW2FdPWIsYyYmKFo9Yil9LGF4ZS5yZXNldD14LGF4ZS5fcnVuUnVsZXM9eTt2YXIgWD0iZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiYic3ltYm9sIj09dHlwZW9mIFN5bWJvbC5pdGVyYXRvcj9mdW5jdGlvbihhKXtyZXR1cm4gdHlwZW9mIGF9OmZ1bmN0aW9uKGEpe3JldHVybiBhJiYiZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiZhLmNvbnN0cnVjdG9yPT09U3ltYm9sJiZhIT09U3ltYm9sLnByb3RvdHlwZT8ic3ltYm9sIjp0eXBlb2YgYX0sXz1mdW5jdGlvbigpe307YXhlLnJ1bj1mdW5jdGlvbihhLGIsYyl7InVzZSBzdHJpY3QiO2lmKCFheGUuX2F1ZGl0KXRocm93IG5ldyBFcnJvcigiTm8gYXVkaXQgY29uZmlndXJlZCIpO3ZhciBkPUEoYSxiLGMpO2E9ZC5jb250ZXh0LGI9ZC5vcHRpb25zLGM9ZC5jYWxsYmFjayxiLnJlcG9ydGVyPWIucmVwb3J0ZXJ8fGF4ZS5fYXVkaXQucmVwb3J0ZXJ8fCJ2MSI7dmFyIGU9dm9pZCAwLGY9XyxnPV87cmV0dXJuIHdpbmRvdy5Qcm9taXNlJiZjPT09XyYmKGU9bmV3IFByb21pc2UoZnVuY3Rpb24oYSxiKXtmPWIsZz1hfSkpLGF4ZS5fcnVuUnVsZXMoYSxiLGZ1bmN0aW9uKGEpe3ZhciBkPWZ1bmN0aW9uKGEpe3RyeXtjKG51bGwsYSl9Y2F0Y2goYil7YXhlLmxvZyhiKX1nKGEpfTt0cnl7dmFyIGU9YXhlLmdldFJlcG9ydGVyKGIucmVwb3J0ZXIpLGg9ZShhLGIsZCk7dm9pZCAwIT09aCYmZChoKX1jYXRjaChpKXtjKGkpLGYoaSl9fSxmdW5jdGlvbihhKXtjKGEpLGYoYSl9KSxlfSxZLmZhaWx1cmVTdW1tYXJ5PWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj17fTtyZXR1cm4gYi5ub25lPWEubm9uZS5jb25jYXQoYS5hbGwpLGIuYW55PWEuYW55LE9iamVjdC5rZXlzKGIpLm1hcChmdW5jdGlvbihhKXtpZihiW2FdLmxlbmd0aCl7dmFyIGM9YXhlLl9hdWRpdC5kYXRhLmZhaWx1cmVTdW1tYXJpZXNbYV07cmV0dXJuIGMmJiJmdW5jdGlvbiI9PXR5cGVvZiBjLmZhaWx1cmVNZXNzYWdlP2MuZmFpbHVyZU1lc3NhZ2UoYlthXS5tYXAoZnVuY3Rpb24oYSl7cmV0dXJuIGEubWVzc2FnZXx8IiJ9KSk6dm9pZCAwfX0pLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4gdm9pZCAwIT09YX0pLmpvaW4oIlxuXG4iKX07dmFyIFg9ImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmInN5bWJvbCI9PXR5cGVvZiBTeW1ib2wuaXRlcmF0b3I/ZnVuY3Rpb24oYSl7cmV0dXJuIHR5cGVvZiBhfTpmdW5jdGlvbihhKXtyZXR1cm4gYSYmImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmYS5jb25zdHJ1Y3Rvcj09PVN5bWJvbCYmYSE9PVN5bWJvbC5wcm90b3R5cGU/InN5bWJvbCI6dHlwZW9mIGF9LGFhPWF4ZS5jb25zdGFudHMucmVzdWx0R3JvdXBzO1kucHJvY2Vzc0FnZ3JlZ2F0ZT1mdW5jdGlvbihhLGIpe3ZhciBjPWF4ZS51dGlscy5hZ2dyZWdhdGVSZXN1bHQoYSk7cmV0dXJuIGMudGltZXN0YW1wPShuZXcgRGF0ZSkudG9JU09TdHJpbmcoKSxjLnVybD13aW5kb3cubG9jYXRpb24uaHJlZixhYS5mb3JFYWNoKGZ1bmN0aW9uKGEpe2NbYV09KGNbYV18fFtdKS5tYXAoZnVuY3Rpb24oYSl7cmV0dXJuIGE9T2JqZWN0LmFzc2lnbih7fSxhKSxBcnJheS5pc0FycmF5KGEubm9kZXMpJiZhLm5vZGVzLmxlbmd0aD4wJiYoYS5ub2Rlcz1hLm5vZGVzLm1hcChmdW5jdGlvbihhKXtyZXR1cm4ib2JqZWN0Ij09PVgoYS5ub2RlKSYmKGEuaHRtbD1hLm5vZGUuc291cmNlLGEudGFyZ2V0PWEubm9kZS5zZWxlY3RvcixiLnhwYXRoJiYoYS54cGF0aD1hLm5vZGUueHBhdGgpKSxkZWxldGUgYS5yZXN1bHQsZGVsZXRlIGEubm9kZSxCKGEsYi54cGF0aCksYX0pKSxhYS5mb3JFYWNoKGZ1bmN0aW9uKGIpe3JldHVybiBkZWxldGUgYVtiXX0pLGRlbGV0ZSBhLnBhZ2VMZXZlbCxkZWxldGUgYS5yZXN1bHQsYX0pfSksY30sYXhlLmFkZFJlcG9ydGVyKCJuYSIsZnVuY3Rpb24oYSxiLGMpeyJ1c2Ugc3RyaWN0IjsiZnVuY3Rpb24iPT10eXBlb2YgYiYmKGM9YixiPXt9KTt2YXIgZD1ZLnByb2Nlc3NBZ2dyZWdhdGUoYSxiKTtjKHt2aW9sYXRpb25zOmQudmlvbGF0aW9ucyxwYXNzZXM6ZC5wYXNzZXMsaW5jb21wbGV0ZTpkLmluY29tcGxldGUsaW5hcHBsaWNhYmxlOmQuaW5hcHBsaWNhYmxlLHRpbWVzdGFtcDpkLnRpbWVzdGFtcCx1cmw6ZC51cmx9KX0pLGF4ZS5hZGRSZXBvcnRlcigibm8tcGFzc2VzIixmdW5jdGlvbihhLGIsYyl7InVzZSBzdHJpY3QiOyJmdW5jdGlvbiI9PXR5cGVvZiBiJiYoYz1iLGI9e30pO3ZhciBkPVkucHJvY2Vzc0FnZ3JlZ2F0ZShhLGIpO2Moe3Zpb2xhdGlvbnM6ZC52aW9sYXRpb25zLHRpbWVzdGFtcDpkLnRpbWVzdGFtcCx1cmw6ZC51cmx9KX0pLGF4ZS5hZGRSZXBvcnRlcigicmF3IixmdW5jdGlvbihhLGIsYyl7InVzZSBzdHJpY3QiOyJmdW5jdGlvbiI9PXR5cGVvZiBiJiYoYz1iLGI9e30pLGMoYSl9KSxheGUuYWRkUmVwb3J0ZXIoInYxIixmdW5jdGlvbihhLGIsYyl7InVzZSBzdHJpY3QiOyJmdW5jdGlvbiI9PXR5cGVvZiBiJiYoYz1iLGI9e30pO3ZhciBkPVkucHJvY2Vzc0FnZ3JlZ2F0ZShhLGIpO2QudmlvbGF0aW9ucy5mb3JFYWNoKGZ1bmN0aW9uKGEpe3JldHVybiBhLm5vZGVzLmZvckVhY2goZnVuY3Rpb24oYSl7YS5mYWlsdXJlU3VtbWFyeT1ZLmZhaWx1cmVTdW1tYXJ5KGEpfSl9KSxjKHt2aW9sYXRpb25zOmQudmlvbGF0aW9ucyxwYXNzZXM6ZC5wYXNzZXMsaW5jb21wbGV0ZTpkLmluY29tcGxldGUsaW5hcHBsaWNhYmxlOmQuaW5hcHBsaWNhYmxlLHRpbWVzdGFtcDpkLnRpbWVzdGFtcCx1cmw6ZC51cmx9KX0pLGF4ZS5hZGRSZXBvcnRlcigidjIiLGZ1bmN0aW9uKGEsYixjKXsidXNlIHN0cmljdCI7ImZ1bmN0aW9uIj09dHlwZW9mIGImJihjPWIsYj17fSk7dmFyIGQ9WS5wcm9jZXNzQWdncmVnYXRlKGEsYik7Yyh7dmlvbGF0aW9uczpkLnZpb2xhdGlvbnMscGFzc2VzOmQucGFzc2VzLGluY29tcGxldGU6ZC5pbmNvbXBsZXRlLGluYXBwbGljYWJsZTpkLmluYXBwbGljYWJsZSx0aW1lc3RhbXA6ZC50aW1lc3RhbXAsdXJsOmQudXJsfSl9LCEwKSxheGUudXRpbHMuYWdncmVnYXRlPWZ1bmN0aW9uKGEsYixjKXtiPWIuc2xpY2UoKSxjJiZiLnB1c2goYyk7dmFyIGQ9Yi5tYXAoZnVuY3Rpb24oYil7cmV0dXJuIGEuaW5kZXhPZihiKX0pLnNvcnQoKTtyZXR1cm4gYVtkLnBvcCgpXX07dmFyIGJhPVtdO2JhW2F4ZS5jb25zdGFudHMuUEFTU19QUklPXT0hMCxiYVtheGUuY29uc3RhbnRzLkNBTlRURUxMX1BSSU9dPW51bGwsYmFbYXhlLmNvbnN0YW50cy5GQUlMX1BSSU9dPSExO3ZhciBjYT1bImFueSIsImFsbCIsIm5vbmUiXTtheGUudXRpbHMuYWdncmVnYXRlQ2hlY2tzPWZ1bmN0aW9uKGEpe3ZhciBiPU9iamVjdC5hc3NpZ24oe30sYSk7QyhiLGZ1bmN0aW9uKGEsYil7dmFyIGM9YmEuaW5kZXhPZihhLnJlc3VsdCk7YS5wcmlvcml0eT1jIT09LTE/YzpheGUuY29uc3RhbnRzLkNBTlRURUxMX1BSSU8sIm5vbmUiPT09YiYmKGEucHJpb3JpdHk9NC1hLnByaW9yaXR5KX0pO3ZhciBjPUMoYixmdW5jdGlvbihhKXtyZXR1cm4gYS5wcmlvcml0eX0pO2IucHJpb3JpdHk9TWF0aC5tYXgoYy5hbGwucmVkdWNlKGZ1bmN0aW9uKGEsYil7cmV0dXJuIE1hdGgubWF4KGEsYil9LDApLGMubm9uZS5yZWR1Y2UoZnVuY3Rpb24oYSxiKXtyZXR1cm4gTWF0aC5tYXgoYSxiKX0sMCksYy5hbnkucmVkdWNlKGZ1bmN0aW9uKGEsYil7cmV0dXJuIE1hdGgubWluKGEsYil9LDQpJTQpO3ZhciBkPVtdO3JldHVybiBjYS5mb3JFYWNoKGZ1bmN0aW9uKGEpe2JbYV09YlthXS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuIGEucHJpb3JpdHk9PT1iLnByaW9yaXR5fSksYlthXS5mb3JFYWNoKGZ1bmN0aW9uKGEpe3JldHVybiBkLnB1c2goYS5pbXBhY3QpfSl9KSxiLnByaW9yaXR5PT09YXhlLmNvbnN0YW50cy5GQUlMX1BSSU8/Yi5pbXBhY3Q9YXhlLnV0aWxzLmFnZ3JlZ2F0ZShheGUuY29uc3RhbnRzLmltcGFjdCxkKTpiLmltcGFjdD1udWxsLEMoYixmdW5jdGlvbihhKXtkZWxldGUgYS5yZXN1bHQsZGVsZXRlIGEucHJpb3JpdHl9KSxiLnJlc3VsdD1heGUuY29uc3RhbnRzLnJlc3VsdHNbYi5wcmlvcml0eV0sZGVsZXRlIGIucHJpb3JpdHksYn0sYXhlLnV0aWxzLmFnZ3JlZ2F0ZVJlc3VsdD1mdW5jdGlvbihhKXt2YXIgYj17fTtyZXR1cm4gYXhlLmNvbnN0YW50cy5yZXN1bHRHcm91cHMuZm9yRWFjaChmdW5jdGlvbihhKXtyZXR1cm4gYlthXT1bXX0pLGEuZm9yRWFjaChmdW5jdGlvbihhKXthLmVycm9yP0QoYixhLGF4ZS5jb25zdGFudHMuQ0FOVFRFTExfR1JPVVApOmEucmVzdWx0PT09YXhlLmNvbnN0YW50cy5OQT9EKGIsYSxheGUuY29uc3RhbnRzLk5BX0dST1VQKTpheGUuY29uc3RhbnRzLnJlc3VsdEdyb3Vwcy5mb3JFYWNoKGZ1bmN0aW9uKGMpe0FycmF5LmlzQXJyYXkoYVtjXSkmJmFbY10ubGVuZ3RoPjAmJkQoYixhLGMpfSl9KSxifSxmdW5jdGlvbigpe2F4ZS51dGlscy5hZ2dyZWdhdGVSdWxlPWZ1bmN0aW9uKGEpe3ZhciBiPXt9O2E9YS5tYXAoZnVuY3Rpb24oYSl7aWYoYS5hbnkmJmEuYWxsJiZhLm5vbmUpcmV0dXJuIGF4ZS51dGlscy5hZ2dyZWdhdGVDaGVja3MoYSk7aWYoQXJyYXkuaXNBcnJheShhLm5vZGUpKXJldHVybiBheGUudXRpbHMuZmluYWxpemVSdWxlUmVzdWx0KGEpO3Rocm93IG5ldyBUeXBlRXJyb3IoIkludmFsaWQgUmVzdWx0IHR5cGUiKX0pO3ZhciBjPWEubWFwKGZ1bmN0aW9uKGEpe3JldHVybiBhLnJlc3VsdH0pO2IucmVzdWx0PWF4ZS51dGlscy5hZ2dyZWdhdGUoYXhlLmNvbnN0YW50cy5yZXN1bHRzLGMsYi5yZXN1bHQpLGF4ZS5jb25zdGFudHMucmVzdWx0R3JvdXBzLmZvckVhY2goZnVuY3Rpb24oYSl7cmV0dXJuIGJbYV09W119KSxhLmZvckVhY2goZnVuY3Rpb24oYSl7dmFyIGM9YXhlLmNvbnN0YW50cy5yZXN1bHRHcm91cE1hcFthLnJlc3VsdF07YltjXS5wdXNoKGEpfSk7dmFyIGQ9YXhlLmNvbnN0YW50cy5GQUlMX0dST1VQO2lmKGJbZF0ubGVuZ3RoPjApe3ZhciBlPWJbZF0ubWFwKGZ1bmN0aW9uKGEpe3JldHVybiBhLmltcGFjdH0pO2IuaW1wYWN0PWF4ZS51dGlscy5hZ2dyZWdhdGUoYXhlLmNvbnN0YW50cy5pbXBhY3QsZSl8fG51bGx9ZWxzZSBiLmltcGFjdD1udWxsO3JldHVybiBifX0oKSxheGUudXRpbHMuYXJlU3R5bGVzU2V0PUUsYXhlLnV0aWxzLmNoZWNrSGVscGVyPWZ1bmN0aW9uKGEsYixjKXsidXNlIHN0cmljdCI7cmV0dXJue2lzQXN5bmM6ITEsYXN5bmM6ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5pc0FzeW5jPSEwLGZ1bmN0aW9uKGQpe2QgaW5zdGFuY2VvZiBFcnJvcj09ITE/KGEudmFsdWU9ZCxiKGEpKTpjKGQpfX0sZGF0YTpmdW5jdGlvbihiKXthLmRhdGE9Yn0scmVsYXRlZE5vZGVzOmZ1bmN0aW9uKGIpe2I9YiBpbnN0YW5jZW9mIE5vZGU/W2JdOmF4ZS51dGlscy50b0FycmF5KGIpLGEucmVsYXRlZE5vZGVzPWIubWFwKGZ1bmN0aW9uKGEpe3JldHVybiBuZXcgYXhlLnV0aWxzLkRxRWxlbWVudChhKX0pfX19O3ZhciBYPSJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJiJzeW1ib2wiPT10eXBlb2YgU3ltYm9sLml0ZXJhdG9yP2Z1bmN0aW9uKGEpe3JldHVybiB0eXBlb2YgYX06ZnVuY3Rpb24oYSl7cmV0dXJuIGEmJiJmdW5jdGlvbiI9PXR5cGVvZiBTeW1ib2wmJmEuY29uc3RydWN0b3I9PT1TeW1ib2wmJmEhPT1TeW1ib2wucHJvdG90eXBlPyJzeW1ib2wiOnR5cGVvZiBhfTtheGUudXRpbHMuY2xvbmU9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiLGMsZD1hO2lmKG51bGwhPT1hJiYib2JqZWN0Ij09PSgidW5kZWZpbmVkIj09dHlwZW9mIGE/InVuZGVmaW5lZCI6WChhKSkpaWYoQXJyYXkuaXNBcnJheShhKSlmb3IoZD1bXSxiPTAsYz1hLmxlbmd0aDtiPGM7YisrKWRbYl09YXhlLnV0aWxzLmNsb25lKGFbYl0pO2Vsc2V7ZD17fTtmb3IoYiBpbiBhKWRbYl09YXhlLnV0aWxzLmNsb25lKGFbYl0pfXJldHVybiBkfSxheGUudXRpbHMuc2VuZENvbW1hbmRUb0ZyYW1lPWZ1bmN0aW9uKGEsYixjLGQpeyJ1c2Ugc3RyaWN0Ijt2YXIgZT1hLmNvbnRlbnRXaW5kb3c7aWYoIWUpcmV0dXJuIGF4ZS5sb2coIkZyYW1lIGRvZXMgbm90IGhhdmUgYSBjb250ZW50IHdpbmRvdyIsYSksdm9pZCBjKG51bGwpO3ZhciBmPXNldFRpbWVvdXQoZnVuY3Rpb24oKXtmPXNldFRpbWVvdXQoZnVuY3Rpb24oKXt2YXIgZT1GKCJObyByZXNwb25zZSBmcm9tIGZyYW1lIixhKTtiLmRlYnVnP2QoZSk6KGF4ZS5sb2coZSksYyhudWxsKSl9LDApfSw1MDApO2F4ZS51dGlscy5yZXNwb25kYWJsZShlLCJheGUucGluZyIsbnVsbCx2b2lkIDAsZnVuY3Rpb24oKXtjbGVhclRpbWVvdXQoZiksZj1zZXRUaW1lb3V0KGZ1bmN0aW9uKCl7ZChGKCJBeGUgaW4gZnJhbWUgdGltZWQgb3V0IixhKSl9LDNlNCksYXhlLnV0aWxzLnJlc3BvbmRhYmxlKGUsImF4ZS5zdGFydCIsYix2b2lkIDAsZnVuY3Rpb24oYSl7Y2xlYXJUaW1lb3V0KGYpLGEgaW5zdGFuY2VvZiBFcnJvcj09ITE/YyhhKTpkKGEpfSl9KX0sYXhlLnV0aWxzLmNvbGxlY3RSZXN1bHRzRnJvbUZyYW1lcz1HLGF4ZS51dGlscy5jb250YWlucz1mdW5jdGlvbihhLGIpeyJ1c2Ugc3RyaWN0IjtyZXR1cm4iZnVuY3Rpb24iPT10eXBlb2YgYS5jb250YWlucz9hLmNvbnRhaW5zKGIpOiEhKDE2JmEuY29tcGFyZURvY3VtZW50UG9zaXRpb24oYikpfSxKLnByb3RvdHlwZS50b0pTT049ZnVuY3Rpb24oKXsidXNlIHN0cmljdCI7cmV0dXJue3NlbGVjdG9yOnRoaXMuc2VsZWN0b3Isc291cmNlOnRoaXMuc291cmNlLHhwYXRoOnRoaXMueHBhdGh9fSxKLmZyb21GcmFtZT1mdW5jdGlvbihhLGIpe3JldHVybiBhLnNlbGVjdG9yLnVuc2hpZnQoYi5zZWxlY3RvciksYS54cGF0aC51bnNoaWZ0KGIueHBhdGgpLG5ldyBheGUudXRpbHMuRHFFbGVtZW50KGIuZWxlbWVudCxhKX0sYXhlLnV0aWxzLkRxRWxlbWVudD1KLGF4ZS51dGlscy5tYXRjaGVzU2VsZWN0b3I9ZnVuY3Rpb24oKXsidXNlIHN0cmljdCI7ZnVuY3Rpb24gYShhKXt2YXIgYixjLGQ9YS5FbGVtZW50LnByb3RvdHlwZSxlPVsibWF0Y2hlcyIsIm1hdGNoZXNTZWxlY3RvciIsIm1vek1hdGNoZXNTZWxlY3RvciIsIndlYmtpdE1hdGNoZXNTZWxlY3RvciIsIm1zTWF0Y2hlc1NlbGVjdG9yIl0sZj1lLmxlbmd0aDtmb3IoYj0wO2I8ZjtiKyspaWYoYz1lW2JdLGRbY10pcmV0dXJuIGN9dmFyIGI7cmV0dXJuIGZ1bmN0aW9uKGMsZCl7cmV0dXJuIGImJmNbYl18fChiPWEoYy5vd25lckRvY3VtZW50LmRlZmF1bHRWaWV3KSksY1tiXShkKX19KCksYXhlLnV0aWxzLmVzY2FwZVNlbGVjdG9yPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijtmb3IodmFyIGIsYz1TdHJpbmcoYSksZD1jLmxlbmd0aCxlPS0xLGY9IiIsZz1jLmNoYXJDb2RlQXQoMCk7KytlPGQ7KXtpZihiPWMuY2hhckNvZGVBdChlKSwwPT1iKXRocm93IG5ldyBFcnJvcigiSU5WQUxJRF9DSEFSQUNURVJfRVJSIik7Zis9Yj49MSYmYjw9MzF8fGI+PTEyNyYmYjw9MTU5fHwwPT1lJiZiPj00OCYmYjw9NTd8fDE9PWUmJmI+PTQ4JiZiPD01NyYmNDU9PWc/IlxcIitiLnRvU3RyaW5nKDE2KSsiICI6KDEhPWV8fDQ1IT1ifHw0NSE9ZykmJihiPj0xMjh8fDQ1PT1ifHw5NT09Ynx8Yj49NDgmJmI8PTU3fHxiPj02NSYmYjw9OTB8fGI+PTk3JiZiPD0xMjIpP2MuY2hhckF0KGUpOiJcXCIrYy5jaGFyQXQoZSl9cmV0dXJuIGZ9LGF4ZS51dGlscy5leHRlbmRNZXRhRGF0YT1mdW5jdGlvbihhLGIpe09iamVjdC5hc3NpZ24oYSxiKSxPYmplY3Qua2V5cyhiKS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuImZ1bmN0aW9uIj09dHlwZW9mIGJbYV19KS5mb3JFYWNoKGZ1bmN0aW9uKGMpe2FbY109bnVsbDt0cnl7YVtjXT1iW2NdKGEpfWNhdGNoKGQpe319KX0sYXhlLnV0aWxzLmZpbmFsaXplUnVsZVJlc3VsdD1mdW5jdGlvbihhKXtyZXR1cm4gT2JqZWN0LmFzc2lnbihhLGF4ZS51dGlscy5hZ2dyZWdhdGVSdWxlKGEubm9kZXMpKSxkZWxldGUgYS5ub2RlcyxhfTt2YXIgWD0iZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiYic3ltYm9sIj09dHlwZW9mIFN5bWJvbC5pdGVyYXRvcj9mdW5jdGlvbihhKXtyZXR1cm4gdHlwZW9mIGF9OmZ1bmN0aW9uKGEpe3JldHVybiBhJiYiZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiZhLmNvbnN0cnVjdG9yPT09U3ltYm9sJiZhIT09U3ltYm9sLnByb3RvdHlwZT8ic3ltYm9sIjp0eXBlb2YgYX07YXhlLnV0aWxzLmZpbmRCeT1mdW5jdGlvbihhLGIsYyl7aWYoQXJyYXkuaXNBcnJheShhKSlyZXR1cm4gYS5maW5kKGZ1bmN0aW9uKGEpe3JldHVybiJvYmplY3QiPT09KCJ1bmRlZmluZWQiPT10eXBlb2YgYT8idW5kZWZpbmVkIjpYKGEpKSYmYVtiXT09PWN9KX0sYXhlLnV0aWxzLmdldEFsbENoZWNrcz1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7dmFyIGI9W107cmV0dXJuIGIuY29uY2F0KGEuYW55fHxbXSkuY29uY2F0KGEuYWxsfHxbXSkuY29uY2F0KGEubm9uZXx8W10pfSxheGUudXRpbHMuZ2V0Q2hlY2tPcHRpb249ZnVuY3Rpb24oYSxiLGMpeyJ1c2Ugc3RyaWN0Ijt2YXIgZD0oKGMucnVsZXMmJmMucnVsZXNbYl18fHt9KS5jaGVja3N8fHt9KVthLmlkXSxlPShjLmNoZWNrc3x8e30pW2EuaWRdLGY9YS5lbmFibGVkLGc9YS5vcHRpb25zO3JldHVybiBlJiYoZS5oYXNPd25Qcm9wZXJ0eSgiZW5hYmxlZCIpJiYoZj1lLmVuYWJsZWQpLGUuaGFzT3duUHJvcGVydHkoIm9wdGlvbnMiKSYmKGc9ZS5vcHRpb25zKSksZCYmKGQuaGFzT3duUHJvcGVydHkoImVuYWJsZWQiKSYmKGY9ZC5lbmFibGVkKSxkLmhhc093blByb3BlcnR5KCJvcHRpb25zIikmJihnPWQub3B0aW9ucykpLHtlbmFibGVkOmYsb3B0aW9uczpnfX0sYXhlLnV0aWxzLmdldFNlbGVjdG9yPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiBiKGEpe3JldHVybiBheGUudXRpbHMuZXNjYXBlU2VsZWN0b3IoYSl9Zm9yKHZhciBjLGQ9W107YS5wYXJlbnROb2RlOyl7aWYoYz0iIixhLmlkJiYxPT09ZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgiIyIrYXhlLnV0aWxzLmVzY2FwZVNlbGVjdG9yKGEuaWQpKS5sZW5ndGgpe2QudW5zaGlmdCgiIyIrYXhlLnV0aWxzLmVzY2FwZVNlbGVjdG9yKGEuaWQpKTticmVha31pZihhLmNsYXNzTmFtZSYmInN0cmluZyI9PXR5cGVvZiBhLmNsYXNzTmFtZSYmKGM9Ii4iK2EuY2xhc3NOYW1lLnRyaW0oKS5zcGxpdCgvXHMrLykubWFwKGIpLmpvaW4oIi4iKSwoIi4iPT09Y3x8TChhLGMpKSYmKGM9IiIpKSwhYyl7aWYoYz1heGUudXRpbHMuZXNjYXBlU2VsZWN0b3IoYS5ub2RlTmFtZSkudG9Mb3dlckNhc2UoKSwiaHRtbCI9PT1jfHwiYm9keSI9PT1jKXtkLnVuc2hpZnQoYyk7YnJlYWt9TChhLGMpJiYoYys9IjpudGgtb2YtdHlwZSgiK0soYSkrIikiKX1kLnVuc2hpZnQoYyksYT1hLnBhcmVudE5vZGV9cmV0dXJuIGQuam9pbigiID4gIil9LGF4ZS51dGlscy5nZXRYcGF0aD1mdW5jdGlvbihhKXt2YXIgYj1NKGEpO3JldHVybiBOKGIpfTt2YXIgZGE7YXhlLnV0aWxzLmluamVjdFN0eWxlPU8sYXhlLnV0aWxzLmlzSGlkZGVuPWZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO2lmKDk9PT1hLm5vZGVUeXBlKXJldHVybiExO3ZhciBjPXdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGEsbnVsbCk7cmV0dXJuIWN8fCFhLnBhcmVudE5vZGV8fCJub25lIj09PWMuZ2V0UHJvcGVydHlWYWx1ZSgiZGlzcGxheSIpfHwhYiYmImhpZGRlbiI9PT1jLmdldFByb3BlcnR5VmFsdWUoInZpc2liaWxpdHkiKXx8InRydWUiPT09YS5nZXRBdHRyaWJ1dGUoImFyaWEtaGlkZGVuIil8fGF4ZS51dGlscy5pc0hpZGRlbihhLnBhcmVudE5vZGUsITApfSxheGUudXRpbHMubWVyZ2VSZXN1bHRzPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj1bXTtyZXR1cm4gYS5mb3JFYWNoKGZ1bmN0aW9uKGEpe3ZhciBjPVIoYSk7YyYmYy5sZW5ndGgmJmMuZm9yRWFjaChmdW5jdGlvbihjKXtjLm5vZGVzJiZhLmZyYW1lJiZQKGMubm9kZXMsYS5mcmFtZUVsZW1lbnQsYS5mcmFtZSk7dmFyIGQ9YXhlLnV0aWxzLmZpbmRCeShiLCJpZCIsYy5pZCk7ZD9jLm5vZGVzLmxlbmd0aCYmUShkLm5vZGVzLGMubm9kZXMpOmIucHVzaChjKX0pfSksYn0sYXhlLnV0aWxzLm5vZGVTb3J0ZXI9ZnVuY3Rpb24oYSxiKXsidXNlIHN0cmljdCI7cmV0dXJuIGE9PT1iPzA6NCZhLmNvbXBhcmVEb2N1bWVudFBvc2l0aW9uKGIpPy0xOjF9LCJmdW5jdGlvbiIhPXR5cGVvZiBPYmplY3QuYXNzaWduJiYhZnVuY3Rpb24oKXtPYmplY3QuYXNzaWduPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtpZih2b2lkIDA9PT1hfHxudWxsPT09YSl0aHJvdyBuZXcgVHlwZUVycm9yKCJDYW5ub3QgY29udmVydCB1bmRlZmluZWQgb3IgbnVsbCB0byBvYmplY3QiKTtmb3IodmFyIGI9T2JqZWN0KGEpLGM9MTtjPGFyZ3VtZW50cy5sZW5ndGg7YysrKXt2YXIgZD1hcmd1bWVudHNbY107aWYodm9pZCAwIT09ZCYmbnVsbCE9PWQpZm9yKHZhciBlIGluIGQpZC5oYXNPd25Qcm9wZXJ0eShlKSYmKGJbZV09ZFtlXSl9cmV0dXJuIGJ9fSgpLEFycmF5LnByb3RvdHlwZS5maW5kfHwoQXJyYXkucHJvdG90eXBlLmZpbmQ9ZnVuY3Rpb24oYSl7aWYobnVsbD09PXRoaXMpdGhyb3cgbmV3IFR5cGVFcnJvcigiQXJyYXkucHJvdG90eXBlLmZpbmQgY2FsbGVkIG9uIG51bGwgb3IgdW5kZWZpbmVkIik7aWYoImZ1bmN0aW9uIiE9dHlwZW9mIGEpdGhyb3cgbmV3IFR5cGVFcnJvcigicHJlZGljYXRlIG11c3QgYmUgYSBmdW5jdGlvbiIpOwpmb3IodmFyIGIsYz1PYmplY3QodGhpcyksZD1jLmxlbmd0aD4+PjAsZT1hcmd1bWVudHNbMV0sZj0wO2Y8ZDtmKyspaWYoYj1jW2ZdLGEuY2FsbChlLGIsZixjKSlyZXR1cm4gYn0pLGF4ZS51dGlscy5wb2xseWZpbGxFbGVtZW50c0Zyb21Qb2ludD1mdW5jdGlvbigpe2lmKGRvY3VtZW50LmVsZW1lbnRzRnJvbVBvaW50KXJldHVybiBkb2N1bWVudC5lbGVtZW50c0Zyb21Qb2ludDtpZihkb2N1bWVudC5tc0VsZW1lbnRzRnJvbVBvaW50KXJldHVybiBkb2N1bWVudC5tc0VsZW1lbnRzRnJvbVBvaW50O3ZhciBhPWZ1bmN0aW9uKCl7dmFyIGE9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgieCIpO3JldHVybiBhLnN0eWxlLmNzc1RleHQ9InBvaW50ZXItZXZlbnRzOmF1dG8iLCJhdXRvIj09PWEuc3R5bGUucG9pbnRlckV2ZW50c30oKSxiPWE/InBvaW50ZXItZXZlbnRzIjoidmlzaWJpbGl0eSIsYz1hPyJub25lIjoiaGlkZGVuIixkPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInN0eWxlIik7cmV0dXJuIGQuaW5uZXJIVE1MPWE/IiogeyBwb2ludGVyLWV2ZW50czogYWxsIH0iOiIqIHsgdmlzaWJpbGl0eTogdmlzaWJsZSB9IixmdW5jdGlvbihhLGUpe3ZhciBmLGcsaCxpPVtdLGo9W107Zm9yKGRvY3VtZW50LmhlYWQuYXBwZW5kQ2hpbGQoZCk7KGY9ZG9jdW1lbnQuZWxlbWVudEZyb21Qb2ludChhLGUpKSYmaS5pbmRleE9mKGYpPT09LTE7KWkucHVzaChmKSxqLnB1c2goe3ZhbHVlOmYuc3R5bGUuZ2V0UHJvcGVydHlWYWx1ZShiKSxwcmlvcml0eTpmLnN0eWxlLmdldFByb3BlcnR5UHJpb3JpdHkoYil9KSxmLnN0eWxlLnNldFByb3BlcnR5KGIsYywiaW1wb3J0YW50Iik7Zm9yKGc9ai5sZW5ndGg7aD1qWy0tZ107KWlbZ10uc3R5bGUuc2V0UHJvcGVydHkoYixoLnZhbHVlP2gudmFsdWU6IiIsaC5wcmlvcml0eSk7cmV0dXJuIGRvY3VtZW50LmhlYWQucmVtb3ZlQ2hpbGQoZCksaX19LCJmdW5jdGlvbiI9PXR5cGVvZiB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lciYmKGRvY3VtZW50LmVsZW1lbnRzRnJvbVBvaW50PWF4ZS51dGlscy5wb2xseWZpbGxFbGVtZW50c0Zyb21Qb2ludCgpKSxBcnJheS5wcm90b3R5cGUuaW5jbHVkZXN8fChBcnJheS5wcm90b3R5cGUuaW5jbHVkZXM9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPU9iamVjdCh0aGlzKSxjPXBhcnNlSW50KGIubGVuZ3RoLDEwKXx8MDtpZigwPT09YylyZXR1cm4hMTt2YXIgZCxlPXBhcnNlSW50KGFyZ3VtZW50c1sxXSwxMCl8fDA7ZT49MD9kPWU6KGQ9YytlLGQ8MCYmKGQ9MCkpO2Zvcih2YXIgZjtkPGM7KXtpZihmPWJbZF0sYT09PWZ8fGEhPT1hJiZmIT09ZilyZXR1cm4hMDtkKyt9cmV0dXJuITF9KSxBcnJheS5wcm90b3R5cGUuc29tZXx8KEFycmF5LnByb3RvdHlwZS5zb21lPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtpZihudWxsPT10aGlzKXRocm93IG5ldyBUeXBlRXJyb3IoIkFycmF5LnByb3RvdHlwZS5zb21lIGNhbGxlZCBvbiBudWxsIG9yIHVuZGVmaW5lZCIpO2lmKCJmdW5jdGlvbiIhPXR5cGVvZiBhKXRocm93IG5ldyBUeXBlRXJyb3I7Zm9yKHZhciBiPU9iamVjdCh0aGlzKSxjPWIubGVuZ3RoPj4+MCxkPWFyZ3VtZW50cy5sZW5ndGg+PTI/YXJndW1lbnRzWzFdOnZvaWQgMCxlPTA7ZTxjO2UrKylpZihlIGluIGImJmEuY2FsbChkLGJbZV0sZSxiKSlyZXR1cm4hMDtyZXR1cm4hMX0pLGF4ZS51dGlscy5wdWJsaXNoTWV0YURhdGE9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPWF4ZS5fYXVkaXQuZGF0YS5jaGVja3N8fHt9LGM9YXhlLl9hdWRpdC5kYXRhLnJ1bGVzfHx7fSxkPWF4ZS51dGlscy5maW5kQnkoYXhlLl9hdWRpdC5ydWxlcywiaWQiLGEuaWQpfHx7fTthLnRhZ3M9YXhlLnV0aWxzLmNsb25lKGQudGFnc3x8W10pO3ZhciBlPVMoYiwhMCksZj1TKGIsITEpO2Eubm9kZXMuZm9yRWFjaChmdW5jdGlvbihhKXthLmFueS5mb3JFYWNoKGUpLGEuYWxsLmZvckVhY2goZSksYS5ub25lLmZvckVhY2goZil9KSxheGUudXRpbHMuZXh0ZW5kTWV0YURhdGEoYSxheGUudXRpbHMuY2xvbmUoY1thLmlkXXx8e30pKX07dmFyIFg9ImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmInN5bWJvbCI9PXR5cGVvZiBTeW1ib2wuaXRlcmF0b3I/ZnVuY3Rpb24oYSl7cmV0dXJuIHR5cGVvZiBhfTpmdW5jdGlvbihhKXtyZXR1cm4gYSYmImZ1bmN0aW9uIj09dHlwZW9mIFN5bWJvbCYmYS5jb25zdHJ1Y3Rvcj09PVN5bWJvbCYmYSE9PVN5bWJvbC5wcm90b3R5cGU/InN5bWJvbCI6dHlwZW9mIGF9OyFmdW5jdGlvbigpeyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiBhKCl7fWZ1bmN0aW9uIGIoYSl7aWYoImZ1bmN0aW9uIiE9dHlwZW9mIGEpdGhyb3cgbmV3IFR5cGVFcnJvcigiUXVldWUgbWV0aG9kcyByZXF1aXJlIGZ1bmN0aW9ucyBhcyBhcmd1bWVudHMiKX1mdW5jdGlvbiBjKCl7ZnVuY3Rpb24gYyhiKXtyZXR1cm4gZnVuY3Rpb24oYyl7Z1tiXT1jLGktPTEsaXx8aj09PWF8fChrPSEwLGooZykpfX1mdW5jdGlvbiBkKGIpe3JldHVybiBqPWEsbShiKSxnfWZ1bmN0aW9uIGUoKXtmb3IodmFyIGE9Zy5sZW5ndGg7aDxhO2grKyl7dmFyIGI9Z1toXTt0cnl7Yi5jYWxsKG51bGwsYyhoKSxkKX1jYXRjaChlKXtkKGUpfX19dmFyIGYsZz1bXSxoPTAsaT0wLGo9YSxrPSExLGw9ZnVuY3Rpb24oYSl7Zj1hLHNldFRpbWVvdXQoZnVuY3Rpb24oKXt2b2lkIDAhPT1mJiZudWxsIT09ZiYmYXhlLmxvZygiVW5jYXVnaHQgZXJyb3IgKG9mIHF1ZXVlKSIsZil9LDEpfSxtPWwsbj17ZGVmZXI6ZnVuY3Rpb24gbyhhKXtpZigib2JqZWN0Ij09PSgidW5kZWZpbmVkIj09dHlwZW9mIGE/InVuZGVmaW5lZCI6WChhKSkmJmEudGhlbiYmYVsiY2F0Y2giXSl7dmFyIG89YTthPWZ1bmN0aW9uKGEsYil7by50aGVuKGEpWyJjYXRjaCJdKGIpfX1pZihiKGEpLHZvaWQgMD09PWYpe2lmKGspdGhyb3cgbmV3IEVycm9yKCJRdWV1ZSBhbHJlYWR5IGNvbXBsZXRlZCIpO3JldHVybiBnLnB1c2goYSksKytpLGUoKSxufX0sdGhlbjpmdW5jdGlvbihjKXtpZihiKGMpLGohPT1hKXRocm93IG5ldyBFcnJvcigicXVldWUgYHRoZW5gIGFscmVhZHkgc2V0Iik7cmV0dXJuIGZ8fChqPWMsaXx8KGs9ITAsaihnKSkpLG59LCJjYXRjaCI6ZnVuY3Rpb24oYSl7aWYoYihhKSxtIT09bCl0aHJvdyBuZXcgRXJyb3IoInF1ZXVlIGBjYXRjaGAgYWxyZWFkeSBzZXQiKTtyZXR1cm4gZj8oYShmKSxmPW51bGwpOm09YSxufSxhYm9ydDpkfTtyZXR1cm4gbn1heGUudXRpbHMucXVldWU9Y30oKTt2YXIgWD0iZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiYic3ltYm9sIj09dHlwZW9mIFN5bWJvbC5pdGVyYXRvcj9mdW5jdGlvbihhKXtyZXR1cm4gdHlwZW9mIGF9OmZ1bmN0aW9uKGEpe3JldHVybiBhJiYiZnVuY3Rpb24iPT10eXBlb2YgU3ltYm9sJiZhLmNvbnN0cnVjdG9yPT09U3ltYm9sJiZhIT09U3ltYm9sLnByb3RvdHlwZT8ic3ltYm9sIjp0eXBlb2YgYX07IWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtmdW5jdGlvbiBiKCl7dmFyIGEsYj0iYXhlIixjPSIiO3JldHVybiJ1bmRlZmluZWQiIT10eXBlb2YgYXhlJiZheGUuX2F1ZGl0JiYhYXhlLl9hdWRpdC5hcHBsaWNhdGlvbiYmKGI9YXhlLl9hdWRpdC5hcHBsaWNhdGlvbiksInVuZGVmaW5lZCIhPXR5cGVvZiBheGUmJihjPWF4ZS52ZXJzaW9uKSxhPWIrIi4iK2N9ZnVuY3Rpb24gYyhhKXtpZigib2JqZWN0Ij09PSgidW5kZWZpbmVkIj09dHlwZW9mIGE/InVuZGVmaW5lZCI6WChhKSkmJiJzdHJpbmciPT10eXBlb2YgYS51dWlkJiZhLl9yZXNwb25kYWJsZT09PSEwKXt2YXIgYz1iKCk7cmV0dXJuIGEuX3NvdXJjZT09PWN8fCJheGUueC55LnoiPT09YS5fc291cmNlfHwiYXhlLngueS56Ij09PWN9cmV0dXJuITF9ZnVuY3Rpb24gZChhLGMsZCxlLGYsZyl7dmFyIGg7ZCBpbnN0YW5jZW9mIEVycm9yJiYoaD17bmFtZTpkLm5hbWUsbWVzc2FnZTpkLm1lc3NhZ2Usc3RhY2s6ZC5zdGFja30sZD12b2lkIDApO3ZhciBpPXt1dWlkOmUsdG9waWM6YyxtZXNzYWdlOmQsZXJyb3I6aCxfcmVzcG9uZGFibGU6ITAsX3NvdXJjZTpiKCksX2tlZXBhbGl2ZTpmfTsiZnVuY3Rpb24iPT10eXBlb2YgZyYmKGpbZV09ZyksYS5wb3N0TWVzc2FnZShKU09OLnN0cmluZ2lmeShpKSwiKiIpfWZ1bmN0aW9uIGUoYSxiLGMsZSxmKXt2YXIgZz1lYS52MSgpO2QoYSxiLGMsZyxlLGYpfWZ1bmN0aW9uIGYoYSxiLGMpe3JldHVybiBmdW5jdGlvbihlLGYsZyl7ZChhLGIsZSxjLGYsZyl9fWZ1bmN0aW9uIGcoYSxiLGMpe3ZhciBkPWIudG9waWMsZT1rW2RdO2lmKGUpe3ZhciBnPWYoYSxudWxsLGIudXVpZCk7ZShiLm1lc3NhZ2UsYyxnKX19ZnVuY3Rpb24gaChhKXt2YXIgYj1hLm1lc3NhZ2V8fCJVbmtub3duIGVycm9yIG9jY3VycmVkIixjPXdpbmRvd1thLm5hbWVdfHxFcnJvcjtyZXR1cm4gYS5zdGFjayYmKGIrPSJcbiIrYS5zdGFjay5yZXBsYWNlKGEubWVzc2FnZSwiIikpLG5ldyBjKGIpfWZ1bmN0aW9uIGkoYSl7dmFyIGI7aWYoInN0cmluZyI9PXR5cGVvZiBhKXt0cnl7Yj1KU09OLnBhcnNlKGEpfWNhdGNoKGQpe31pZihjKGIpKXJldHVybiJvYmplY3QiPT09WChiLmVycm9yKT9iLmVycm9yPWgoYi5lcnJvcik6Yi5lcnJvcj12b2lkIDAsYn19dmFyIGo9e30saz17fTtlLnN1YnNjcmliZT1mdW5jdGlvbihhLGIpe2tbYV09Yn0sZS5pc0luRnJhbWU9ZnVuY3Rpb24oYSl7cmV0dXJuIGE9YXx8d2luZG93LCEhYS5mcmFtZUVsZW1lbnR9LCJmdW5jdGlvbiI9PXR5cGVvZiB3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lciYmd2luZG93LmFkZEV2ZW50TGlzdGVuZXIoIm1lc3NhZ2UiLGZ1bmN0aW9uKGEpe3ZhciBiPWkoYS5kYXRhKTtpZihiKXt2YXIgYz1iLnV1aWQsZT1iLl9rZWVwYWxpdmUsaD1qW2NdO2lmKGgpe3ZhciBrPWIuZXJyb3J8fGIubWVzc2FnZSxsPWYoYS5zb3VyY2UsYi50b3BpYyxjKTtoKGssZSxsKSxlfHxkZWxldGUgaltjXX1pZighYi5lcnJvcil0cnl7ZyhhLnNvdXJjZSxiLGUpfWNhdGNoKG0pe2QoYS5zb3VyY2UsYi50b3BpYyxtLGMsITEpfX19LCExKSxhLnJlc3BvbmRhYmxlPWV9KHV0aWxzKSxheGUudXRpbHMucnVsZVNob3VsZFJ1bj1mdW5jdGlvbihhLGIsYyl7InVzZSBzdHJpY3QiO3ZhciBkPWMucnVuT25seXx8e30sZT0oYy5ydWxlc3x8e30pW2EuaWRdO3JldHVybiEoYS5wYWdlTGV2ZWwmJiFiLnBhZ2UpJiYoInJ1bGUiPT09ZC50eXBlP2QudmFsdWVzLmluZGV4T2YoYS5pZCkhPT0tMTplJiYiYm9vbGVhbiI9PXR5cGVvZiBlLmVuYWJsZWQ/ZS5lbmFibGVkOiJ0YWciPT09ZC50eXBlJiZkLnZhbHVlcz9UKGEsZC52YWx1ZXMpOlQoYSxbXSkpfSxheGUudXRpbHMuc2VsZWN0PWZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO2Zvcih2YXIgYyxkPVtdLGU9MCxmPWIuaW5jbHVkZS5sZW5ndGg7ZTxmO2UrKyljPWIuaW5jbHVkZVtlXSxjLm5vZGVUeXBlPT09Yy5FTEVNRU5UX05PREUmJmF4ZS51dGlscy5tYXRjaGVzU2VsZWN0b3IoYyxhKSYmVyhkLFtjXSxiKSxXKGQsYy5xdWVyeVNlbGVjdG9yQWxsKGEpLGIpO3JldHVybiBkLnNvcnQoYXhlLnV0aWxzLm5vZGVTb3J0ZXIpfSxheGUudXRpbHMudG9BcnJheT1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7cmV0dXJuIEFycmF5LnByb3RvdHlwZS5zbGljZS5jYWxsKGEpfTt2YXIgZWE7IWZ1bmN0aW9uKGEpe2Z1bmN0aW9uIGIoYSxiLGMpe3ZhciBkPWImJmN8fDAsZT0wO2ZvcihiPWJ8fFtdLGEudG9Mb3dlckNhc2UoKS5yZXBsYWNlKC9bMC05YS1mXXsyfS9nLGZ1bmN0aW9uKGEpe2U8MTYmJihiW2QrZSsrXT1sW2FdKX0pO2U8MTY7KWJbZCtlKytdPTA7cmV0dXJuIGJ9ZnVuY3Rpb24gYyhhLGIpe3ZhciBjPWJ8fDAsZD1rO3JldHVybiBkW2FbYysrXV0rZFthW2MrK11dK2RbYVtjKytdXStkW2FbYysrXV0rIi0iK2RbYVtjKytdXStkW2FbYysrXV0rIi0iK2RbYVtjKytdXStkW2FbYysrXV0rIi0iK2RbYVtjKytdXStkW2FbYysrXV0rIi0iK2RbYVtjKytdXStkW2FbYysrXV0rZFthW2MrK11dK2RbYVtjKytdXStkW2FbYysrXV0rZFthW2MrK11dfWZ1bmN0aW9uIGQoYSxiLGQpe3ZhciBlPWImJmR8fDAsZj1ifHxbXTthPWF8fHt9O3ZhciBnPW51bGwhPWEuY2xvY2tzZXE/YS5jbG9ja3NlcTpwLGg9bnVsbCE9YS5tc2Vjcz9hLm1zZWNzOihuZXcgRGF0ZSkuZ2V0VGltZSgpLGk9bnVsbCE9YS5uc2Vjcz9hLm5zZWNzOnIrMSxqPWgtcSsoaS1yKS8xZTQ7aWYoajwwJiZudWxsPT1hLmNsb2Nrc2VxJiYoZz1nKzEmMTYzODMpLChqPDB8fGg+cSkmJm51bGw9PWEubnNlY3MmJihpPTApLGk+PTFlNCl0aHJvdyBuZXcgRXJyb3IoInV1aWQudjEoKTogQ2FuJ3QgY3JlYXRlIG1vcmUgdGhhbiAxME0gdXVpZHMvc2VjIik7cT1oLHI9aSxwPWcsaCs9MTIyMTkyOTI4ZTU7dmFyIGs9KDFlNCooMjY4NDM1NDU1JmgpK2kpJTQyOTQ5NjcyOTY7ZltlKytdPWs+Pj4yNCYyNTUsZltlKytdPWs+Pj4xNiYyNTUsZltlKytdPWs+Pj44JjI1NSxmW2UrK109MjU1Jms7dmFyIGw9aC80Mjk0OTY3Mjk2KjFlNCYyNjg0MzU0NTU7ZltlKytdPWw+Pj44JjI1NSxmW2UrK109MjU1JmwsZltlKytdPWw+Pj4yNCYxNXwxNixmW2UrK109bD4+PjE2JjI1NSxmW2UrK109Zz4+Pjh8MTI4LGZbZSsrXT0yNTUmZztmb3IodmFyIG09YS5ub2RlfHxvLG49MDtuPDY7bisrKWZbZStuXT1tW25dO3JldHVybiBiP2I6YyhmKX1mdW5jdGlvbiBlKGEsYixkKXt2YXIgZT1iJiZkfHwwOyJzdHJpbmciPT10eXBlb2YgYSYmKGI9ImJpbmFyeSI9PWE/bmV3IGooMTYpOm51bGwsYT1udWxsKSxhPWF8fHt9O3ZhciBnPWEucmFuZG9tfHwoYS5ybmd8fGYpKCk7aWYoZ1s2XT0xNSZnWzZdfDY0LGdbOF09NjMmZ1s4XXwxMjgsYilmb3IodmFyIGg9MDtoPDE2O2grKyliW2UraF09Z1toXTtyZXR1cm4gYnx8YyhnKX12YXIgZixnPWEuY3J5cHRvfHxhLm1zQ3J5cHRvO2lmKCFmJiZnJiZnLmdldFJhbmRvbVZhbHVlcyl7dmFyIGg9bmV3IFVpbnQ4QXJyYXkoMTYpO2Y9ZnVuY3Rpb24oKXtyZXR1cm4gZy5nZXRSYW5kb21WYWx1ZXMoaCksaH19aWYoIWYpe3ZhciBpPW5ldyBBcnJheSgxNik7Zj1mdW5jdGlvbigpe2Zvcih2YXIgYSxiPTA7YjwxNjtiKyspMD09PSgzJmIpJiYoYT00Mjk0OTY3Mjk2Kk1hdGgucmFuZG9tKCkpLGlbYl09YT4+PigoMyZiKTw8MykmMjU1O3JldHVybiBpfX1mb3IodmFyIGo9ImZ1bmN0aW9uIj09dHlwZW9mIGEuQnVmZmVyP2EuQnVmZmVyOkFycmF5LGs9W10sbD17fSxtPTA7bTwyNTY7bSsrKWtbbV09KG0rMjU2KS50b1N0cmluZygxNikuc3Vic3RyKDEpLGxba1ttXV09bTt2YXIgbj1mKCksbz1bMXxuWzBdLG5bMV0sblsyXSxuWzNdLG5bNF0sbls1XV0scD0xNjM4MyYobls2XTw8OHxuWzddKSxxPTAscj0wO2VhPWUsZWEudjE9ZCxlYS52ND1lLGVhLnBhcnNlPWIsZWEudW5wYXJzZT1jLGVhLkJ1ZmZlckNsYXNzPWp9KHdpbmRvdyksYXhlLl9sb2FkKHtkYXRhOntydWxlczp7YWNjZXNza2V5czp7ZGVzY3JpcHRpb246IkVuc3VyZXMgZXZlcnkgYWNjZXNza2V5IGF0dHJpYnV0ZSB2YWx1ZSBpcyB1bmlxdWUiLGhlbHA6ImFjY2Vzc2tleSBhdHRyaWJ1dGUgdmFsdWUgbXVzdCBiZSB1bmlxdWUifSwiYXJlYS1hbHQiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyA8YXJlYT4gZWxlbWVudHMgb2YgaW1hZ2UgbWFwcyBoYXZlIGFsdGVybmF0ZSB0ZXh0IixoZWxwOiJBY3RpdmUgPGFyZWE+IGVsZW1lbnRzIG11c3QgaGF2ZSBhbHRlcm5hdGUgdGV4dCJ9LCJhcmlhLWFsbG93ZWQtYXR0ciI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIEFSSUEgYXR0cmlidXRlcyBhcmUgYWxsb3dlZCBmb3IgYW4gZWxlbWVudCdzIHJvbGUiLGhlbHA6IkVsZW1lbnRzIG11c3Qgb25seSB1c2UgYWxsb3dlZCBBUklBIGF0dHJpYnV0ZXMifSwiYXJpYS1yZXF1aXJlZC1hdHRyIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgZWxlbWVudHMgd2l0aCBBUklBIHJvbGVzIGhhdmUgYWxsIHJlcXVpcmVkIEFSSUEgYXR0cmlidXRlcyIsaGVscDoiUmVxdWlyZWQgQVJJQSBhdHRyaWJ1dGVzIG11c3QgYmUgcHJvdmlkZWQifSwiYXJpYS1yZXF1aXJlZC1jaGlsZHJlbiI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGVsZW1lbnRzIHdpdGggYW4gQVJJQSByb2xlIHRoYXQgcmVxdWlyZSBjaGlsZCByb2xlcyBjb250YWluIHRoZW0iLGhlbHA6IkNlcnRhaW4gQVJJQSByb2xlcyBtdXN0IGNvbnRhaW4gcGFydGljdWxhciBjaGlsZHJlbiJ9LCJhcmlhLXJlcXVpcmVkLXBhcmVudCI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGVsZW1lbnRzIHdpdGggYW4gQVJJQSByb2xlIHRoYXQgcmVxdWlyZSBwYXJlbnQgcm9sZXMgYXJlIGNvbnRhaW5lZCBieSB0aGVtIixoZWxwOiJDZXJ0YWluIEFSSUEgcm9sZXMgbXVzdCBiZSBjb250YWluZWQgYnkgcGFydGljdWxhciBwYXJlbnRzIn0sImFyaWEtcm9sZXMiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBhbGwgZWxlbWVudHMgd2l0aCBhIHJvbGUgYXR0cmlidXRlIHVzZSBhIHZhbGlkIHZhbHVlIixoZWxwOiJBUklBIHJvbGVzIHVzZWQgbXVzdCBjb25mb3JtIHRvIHZhbGlkIHZhbHVlcyJ9LCJhcmlhLXZhbGlkLWF0dHItdmFsdWUiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBhbGwgQVJJQSBhdHRyaWJ1dGVzIGhhdmUgdmFsaWQgdmFsdWVzIixoZWxwOiJBUklBIGF0dHJpYnV0ZXMgbXVzdCBjb25mb3JtIHRvIHZhbGlkIHZhbHVlcyJ9LCJhcmlhLXZhbGlkLWF0dHIiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBhdHRyaWJ1dGVzIHRoYXQgYmVnaW4gd2l0aCBhcmlhLSBhcmUgdmFsaWQgQVJJQSBhdHRyaWJ1dGVzIixoZWxwOiJBUklBIGF0dHJpYnV0ZXMgbXVzdCBjb25mb3JtIHRvIHZhbGlkIG5hbWVzIn0sImF1ZGlvLWNhcHRpb24iOntkZXNjcmlwdGlvbjoiRW5zdXJlcyA8YXVkaW8+IGVsZW1lbnRzIGhhdmUgY2FwdGlvbnMiLGhlbHA6IjxhdWRpbz4gZWxlbWVudHMgbXVzdCBoYXZlIGEgY2FwdGlvbnMgdHJhY2sifSxibGluazp7ZGVzY3JpcHRpb246IkVuc3VyZXMgPGJsaW5rPiBlbGVtZW50cyBhcmUgbm90IHVzZWQiLGhlbHA6IjxibGluaz4gZWxlbWVudHMgYXJlIGRlcHJlY2F0ZWQgYW5kIG11c3Qgbm90IGJlIHVzZWQifSwiYnV0dG9uLW5hbWUiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBidXR0b25zIGhhdmUgZGlzY2VybmlibGUgdGV4dCIsaGVscDoiQnV0dG9ucyBtdXN0IGhhdmUgZGlzY2VybmlibGUgdGV4dCJ9LGJ5cGFzczp7ZGVzY3JpcHRpb246IkVuc3VyZXMgZWFjaCBwYWdlIGhhcyBhdCBsZWFzdCBvbmUgbWVjaGFuaXNtIGZvciBhIHVzZXIgdG8gYnlwYXNzIG5hdmlnYXRpb24gYW5kIGp1bXAgc3RyYWlnaHQgdG8gdGhlIGNvbnRlbnQiLGhlbHA6IlBhZ2UgbXVzdCBoYXZlIG1lYW5zIHRvIGJ5cGFzcyByZXBlYXRlZCBibG9ja3MifSxjaGVja2JveGdyb3VwOntkZXNjcmlwdGlvbjonRW5zdXJlcyByZWxhdGVkIDxpbnB1dCB0eXBlPSJjaGVja2JveCI+IGVsZW1lbnRzIGhhdmUgYSBncm91cCBhbmQgdGhhdCB0aGF0IGdyb3VwIGRlc2lnbmF0aW9uIGlzIGNvbnNpc3RlbnQnLGhlbHA6IkNoZWNrYm94IGlucHV0cyB3aXRoIHRoZSBzYW1lIG5hbWUgYXR0cmlidXRlIHZhbHVlIG11c3QgYmUgcGFydCBvZiBhIGdyb3VwIn0sImNvbG9yLWNvbnRyYXN0Ijp7ZGVzY3JpcHRpb246IkVuc3VyZXMgdGhlIGNvbnRyYXN0IGJldHdlZW4gZm9yZWdyb3VuZCBhbmQgYmFja2dyb3VuZCBjb2xvcnMgbWVldHMgV0NBRyAyIEFBIGNvbnRyYXN0IHJhdGlvIHRocmVzaG9sZHMiLGhlbHA6IkVsZW1lbnRzIG11c3QgaGF2ZSBzdWZmaWNpZW50IGNvbG9yIGNvbnRyYXN0In0sImRlZmluaXRpb24tbGlzdCI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDxkbD4gZWxlbWVudHMgYXJlIHN0cnVjdHVyZWQgY29ycmVjdGx5IixoZWxwOiI8ZGw+IGVsZW1lbnRzIG11c3Qgb25seSBkaXJlY3RseSBjb250YWluIHByb3Blcmx5LW9yZGVyZWQgPGR0PiBhbmQgPGRkPiBncm91cHMsIDxzY3JpcHQ+IG9yIDx0ZW1wbGF0ZT4gZWxlbWVudHMifSxkbGl0ZW06e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDxkdD4gYW5kIDxkZD4gZWxlbWVudHMgYXJlIGNvbnRhaW5lZCBieSBhIDxkbD4iLGhlbHA6IjxkdD4gYW5kIDxkZD4gZWxlbWVudHMgbXVzdCBiZSBjb250YWluZWQgYnkgYSA8ZGw+In0sImRvY3VtZW50LXRpdGxlIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgZWFjaCBIVE1MIGRvY3VtZW50IGNvbnRhaW5zIGEgbm9uLWVtcHR5IDx0aXRsZT4gZWxlbWVudCIsaGVscDoiRG9jdW1lbnRzIG11c3QgaGF2ZSA8dGl0bGU+IGVsZW1lbnQgdG8gYWlkIGluIG5hdmlnYXRpb24ifSwiZHVwbGljYXRlLWlkIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgZXZlcnkgaWQgYXR0cmlidXRlIHZhbHVlIGlzIHVuaXF1ZSIsaGVscDoiaWQgYXR0cmlidXRlIHZhbHVlIG11c3QgYmUgdW5pcXVlIn0sImVtcHR5LWhlYWRpbmciOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBoZWFkaW5ncyBoYXZlIGRpc2Nlcm5pYmxlIHRleHQiLGhlbHA6IkhlYWRpbmdzIG11c3Qgbm90IGJlIGVtcHR5In0sImZyYW1lLXRpdGxlLXVuaXF1ZSI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDxpZnJhbWU+IGFuZCA8ZnJhbWU+IGVsZW1lbnRzIGNvbnRhaW4gYSB1bmlxdWUgdGl0bGUgYXR0cmlidXRlIixoZWxwOiJGcmFtZXMgbXVzdCBoYXZlIGEgdW5pcXVlIHRpdGxlIGF0dHJpYnV0ZSJ9LCJmcmFtZS10aXRsZSI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDxpZnJhbWU+IGFuZCA8ZnJhbWU+IGVsZW1lbnRzIGNvbnRhaW4gYSBub24tZW1wdHkgdGl0bGUgYXR0cmlidXRlIixoZWxwOiJGcmFtZXMgbXVzdCBoYXZlIHRpdGxlIGF0dHJpYnV0ZSJ9LCJoZWFkaW5nLW9yZGVyIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgdGhlIG9yZGVyIG9mIGhlYWRpbmdzIGlzIHNlbWFudGljYWxseSBjb3JyZWN0IixoZWxwOiJIZWFkaW5nIGxldmVscyBzaG91bGQgb25seSBpbmNyZWFzZSBieSBvbmUifSwiaHJlZi1uby1oYXNoIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgdGhhdCBocmVmIHZhbHVlcyBhcmUgdmFsaWQgbGluayByZWZlcmVuY2VzIHRvIHByb21vdGUgb25seSB1c2luZyBhbmNob3JzIGFzIGxpbmtzIixoZWxwOiJBbmNob3JzIG11c3Qgb25seSBiZSB1c2VkIGFzIGxpbmtzIGFuZCBtdXN0IHRoZXJlZm9yZSBoYXZlIGFuIGhyZWYgdmFsdWUgdGhhdCBpcyBhIHZhbGlkIHJlZmVyZW5jZS4gT3RoZXJ3aXNlIHlvdSBzaG91bGQgcHJvYmFibHkgdXNhIGEgYnV0dG9uIn0sImh0bWwtaGFzLWxhbmciOntkZXNjcmlwdGlvbjoiRW5zdXJlcyBldmVyeSBIVE1MIGRvY3VtZW50IGhhcyBhIGxhbmcgYXR0cmlidXRlIixoZWxwOiI8aHRtbD4gZWxlbWVudCBtdXN0IGhhdmUgYSBsYW5nIGF0dHJpYnV0ZSJ9LCJodG1sLWxhbmctdmFsaWQiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyB0aGUgbGFuZyBhdHRyaWJ1dGUgb2YgdGhlIDxodG1sPiBlbGVtZW50IGhhcyBhIHZhbGlkIHZhbHVlIixoZWxwOiI8aHRtbD4gZWxlbWVudCBtdXN0IGhhdmUgYSB2YWxpZCB2YWx1ZSBmb3IgdGhlIGxhbmcgYXR0cmlidXRlIn0sImltYWdlLWFsdCI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDxpbWc+IGVsZW1lbnRzIGhhdmUgYWx0ZXJuYXRlIHRleHQgb3IgYSByb2xlIG9mIG5vbmUgb3IgcHJlc2VudGF0aW9uIixoZWxwOiJJbWFnZXMgbXVzdCBoYXZlIGFsdGVybmF0ZSB0ZXh0In0sImltYWdlLXJlZHVuZGFudC1hbHQiOntkZXNjcmlwdGlvbjoiRW5zdXJlIGJ1dHRvbiBhbmQgbGluayB0ZXh0IGlzIG5vdCByZXBlYXRlZCBhcyBpbWFnZSBhbHRlcm5hdGl2ZSIsaGVscDoiVGV4dCBvZiBidXR0b25zIGFuZCBsaW5rcyBzaG91bGQgbm90IGJlIHJlcGVhdGVkIGluIHRoZSBpbWFnZSBhbHRlcm5hdGl2ZSJ9LCJpbnB1dC1pbWFnZS1hbHQiOntkZXNjcmlwdGlvbjonRW5zdXJlcyA8aW5wdXQgdHlwZT0iaW1hZ2UiPiBlbGVtZW50cyBoYXZlIGFsdGVybmF0ZSB0ZXh0JyxoZWxwOiJJbWFnZSBidXR0b25zIG11c3QgaGF2ZSBhbHRlcm5hdGUgdGV4dCJ9LCJsYWJlbC10aXRsZS1vbmx5Ijp7ZGVzY3JpcHRpb246IkVuc3VyZXMgdGhhdCBldmVyeSBmb3JtIGVsZW1lbnQgaXMgbm90IHNvbGVseSBsYWJlbGVkIHVzaW5nIHRoZSB0aXRsZSBvciBhcmlhLWRlc2NyaWJlZGJ5IGF0dHJpYnV0ZXMiLGhlbHA6IkZvcm0gZWxlbWVudHMgc2hvdWxkIGhhdmUgYSB2aXNpYmxlIGxhYmVsIn0sbGFiZWw6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGV2ZXJ5IGZvcm0gZWxlbWVudCBoYXMgYSBsYWJlbCIsaGVscDoiRm9ybSBlbGVtZW50cyBtdXN0IGhhdmUgbGFiZWxzIn0sImxheW91dC10YWJsZSI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIHByZXNlbnRhdGlvbmFsIDx0YWJsZT4gZWxlbWVudHMgZG8gbm90IHVzZSA8dGg+LCA8Y2FwdGlvbj4gZWxlbWVudHMgb3IgdGhlIHN1bW1hcnkgYXR0cmlidXRlIixoZWxwOiJMYXlvdXQgdGFibGVzIG11c3Qgbm90IHVzZSBkYXRhIHRhYmxlIGVsZW1lbnRzIn0sImxpbmstaW4tdGV4dC1ibG9jayI6e2Rlc2NyaXB0aW9uOiJMaW5rcyBjYW4gYmUgZGlzdGluZ3Vpc2hlZCB3aXRob3V0IHJlbHlpbmcgb24gY29sb3IiLGhlbHA6IkxpbmtzIG11c3QgYmUgZGlzdGluZ3Vpc2hlZCBmcm9tIHN1cnJvdW5kaW5nIHRleHQgaW4gYSB3YXkgdGhhdCBkb2VzIG5vdCByZWx5IG9uIGNvbG9yIn0sImxpbmstbmFtZSI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGxpbmtzIGhhdmUgZGlzY2VybmlibGUgdGV4dCIsaGVscDoiTGlua3MgbXVzdCBoYXZlIGRpc2Nlcm5pYmxlIHRleHQifSxsaXN0OntkZXNjcmlwdGlvbjoiRW5zdXJlcyB0aGF0IGxpc3RzIGFyZSBzdHJ1Y3R1cmVkIGNvcnJlY3RseSIsaGVscDoiPHVsPiBhbmQgPG9sPiBtdXN0IG9ubHkgZGlyZWN0bHkgY29udGFpbiA8bGk+LCA8c2NyaXB0PiBvciA8dGVtcGxhdGU+IGVsZW1lbnRzIn0sbGlzdGl0ZW06e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDxsaT4gZWxlbWVudHMgYXJlIHVzZWQgc2VtYW50aWNhbGx5IixoZWxwOiI8bGk+IGVsZW1lbnRzIG11c3QgYmUgY29udGFpbmVkIGluIGEgPHVsPiBvciA8b2w+In0sbWFycXVlZTp7ZGVzY3JpcHRpb246IkVuc3VyZXMgPG1hcnF1ZWU+IGVsZW1lbnRzIGFyZSBub3QgdXNlZCIsaGVscDoiPG1hcnF1ZWU+IGVsZW1lbnRzIGFyZSBkZXByZWNhdGVkIGFuZCBtdXN0IG5vdCBiZSB1c2VkIn0sIm1ldGEtcmVmcmVzaCI6e2Rlc2NyaXB0aW9uOidFbnN1cmVzIDxtZXRhIGh0dHAtZXF1aXY9InJlZnJlc2giPiBpcyBub3QgdXNlZCcsaGVscDoiVGltZWQgcmVmcmVzaCBtdXN0IG5vdCBleGlzdCJ9LCJtZXRhLXZpZXdwb3J0LWxhcmdlIjp7ZGVzY3JpcHRpb246J0Vuc3VyZXMgPG1ldGEgbmFtZT0idmlld3BvcnQiPiBjYW4gc2NhbGUgYSBzaWduaWZpY2FudCBhbW91bnQnLGhlbHA6IlVzZXJzIHNob3VsZCBiZSBhYmxlIHRvIHpvb20gYW5kIHNjYWxlIHRoZSB0ZXh0IHVwIHRvIDUwMCUifSwibWV0YS12aWV3cG9ydCI6e2Rlc2NyaXB0aW9uOidFbnN1cmVzIDxtZXRhIG5hbWU9InZpZXdwb3J0Ij4gZG9lcyBub3QgZGlzYWJsZSB0ZXh0IHNjYWxpbmcgYW5kIHpvb21pbmcnLGhlbHA6Ilpvb21pbmcgYW5kIHNjYWxpbmcgbXVzdCBub3QgYmUgZGlzYWJsZWQifSwib2JqZWN0LWFsdCI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDxvYmplY3Q+IGVsZW1lbnRzIGhhdmUgYWx0ZXJuYXRlIHRleHQiLGhlbHA6IjxvYmplY3Q+IGVsZW1lbnRzIG11c3QgaGF2ZSBhbHRlcm5hdGUgdGV4dCJ9LHJhZGlvZ3JvdXA6e2Rlc2NyaXB0aW9uOidFbnN1cmVzIHJlbGF0ZWQgPGlucHV0IHR5cGU9InJhZGlvIj4gZWxlbWVudHMgaGF2ZSBhIGdyb3VwIGFuZCB0aGF0IHRoZSBncm91cCBkZXNpZ25hdGlvbiBpcyBjb25zaXN0ZW50JyxoZWxwOiJSYWRpbyBpbnB1dHMgd2l0aCB0aGUgc2FtZSBuYW1lIGF0dHJpYnV0ZSB2YWx1ZSBtdXN0IGJlIHBhcnQgb2YgYSBncm91cCJ9LHJlZ2lvbjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgYWxsIGNvbnRlbnQgaXMgY29udGFpbmVkIHdpdGhpbiBhIGxhbmRtYXJrIHJlZ2lvbiIsaGVscDoiQ29udGVudCBzaG91bGQgYmUgY29udGFpbmVkIGluIGEgbGFuZG1hcmsgcmVnaW9uIn0sInNjb3BlLWF0dHItdmFsaWQiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyB0aGUgc2NvcGUgYXR0cmlidXRlIGlzIHVzZWQgY29ycmVjdGx5IG9uIHRhYmxlcyIsaGVscDoic2NvcGUgYXR0cmlidXRlIHNob3VsZCBiZSB1c2VkIGNvcnJlY3RseSJ9LCJzZXJ2ZXItc2lkZS1pbWFnZS1tYXAiOntkZXNjcmlwdGlvbjoiRW5zdXJlcyB0aGF0IHNlcnZlci1zaWRlIGltYWdlIG1hcHMgYXJlIG5vdCB1c2VkIixoZWxwOiJTZXJ2ZXItc2lkZSBpbWFnZSBtYXBzIG11c3Qgbm90IGJlIHVzZWQifSwic2tpcC1saW5rIjp7ZGVzY3JpcHRpb246IkVuc3VyZXMgdGhlIGZpcnN0IGxpbmsgb24gdGhlIHBhZ2UgaXMgYSBza2lwIGxpbmsiLGhlbHA6IlRoZSBwYWdlIHNob3VsZCBoYXZlIGEgc2tpcCBsaW5rIGFzIGl0cyBmaXJzdCBsaW5rIn0sdGFiaW5kZXg6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIHRhYmluZGV4IGF0dHJpYnV0ZSB2YWx1ZXMgYXJlIG5vdCBncmVhdGVyIHRoYW4gMCIsaGVscDoiRWxlbWVudHMgc2hvdWxkIG5vdCBoYXZlIHRhYmluZGV4IGdyZWF0ZXIgdGhhbiB6ZXJvIn0sInRhYmxlLWR1cGxpY2F0ZS1uYW1lIjp7ZGVzY3JpcHRpb246IkVuc3VyZSB0aGF0IHRhYmxlcyBkbyBub3QgaGF2ZSB0aGUgc2FtZSBzdW1tYXJ5IGFuZCBjYXB0aW9uIixoZWxwOiJUaGUgPGNhcHRpb24+IGVsZW1lbnQgc2hvdWxkIG5vdCBjb250YWluIHRoZSBzYW1lIHRleHQgYXMgdGhlIHN1bW1hcnkgYXR0cmlidXRlIn0sInRhYmxlLWZha2UtY2FwdGlvbiI6e2Rlc2NyaXB0aW9uOiJFbnN1cmUgdGhhdCB0YWJsZXMgd2l0aCBhIGNhcHRpb24gdXNlIHRoZSA8Y2FwdGlvbj4gZWxlbWVudC4iLGhlbHA6IkRhdGEgb3IgaGVhZGVyIGNlbGxzIHNob3VsZCBub3QgYmUgdXNlZCB0byBnaXZlIGNhcHRpb24gdG8gYSBkYXRhIHRhYmxlLiJ9LCJ0ZC1oYXMtaGVhZGVyIjp7ZGVzY3JpcHRpb246IkVuc3VyZSB0aGF0IGVhY2ggbm9uLWVtcHR5IGRhdGEgY2VsbCBpbiBhIGxhcmdlIHRhYmxlIGhhcyBvbmUgb3IgbW9yZSB0YWJsZSBoZWFkZXJzIixoZWxwOiJBbGwgbm9uLWVtcHR5IHRkIGVsZW1lbnQgaW4gdGFibGUgbGFyZ2VyIHRoYW4gMyBieSAzIG11c3QgaGF2ZSBhbiBhc3NvY2lhdGVkIHRhYmxlIGhlYWRlciJ9LCJ0ZC1oZWFkZXJzLWF0dHIiOntkZXNjcmlwdGlvbjoiRW5zdXJlIHRoYXQgZWFjaCBjZWxsIGluIGEgdGFibGUgdXNpbmcgdGhlIGhlYWRlcnMgcmVmZXJzIHRvIGFub3RoZXIgY2VsbCBpbiB0aGF0IHRhYmxlIixoZWxwOiJBbGwgY2VsbHMgaW4gYSB0YWJsZSBlbGVtZW50IHRoYXQgdXNlIHRoZSBoZWFkZXJzIGF0dHJpYnV0ZSBtdXN0IG9ubHkgcmVmZXIgdG8gb3RoZXIgY2VsbHMgb2YgdGhhdCBzYW1lIHRhYmxlIn0sInRoLWhhcy1kYXRhLWNlbGxzIjp7ZGVzY3JpcHRpb246IkVuc3VyZSB0aGF0IGVhY2ggdGFibGUgaGVhZGVyIGluIGEgZGF0YSB0YWJsZSByZWZlcnMgdG8gZGF0YSBjZWxscyIsaGVscDoiQWxsIHRoIGVsZW1lbnQgYW5kIGVsZW1lbnRzIHdpdGggcm9sZT1jb2x1bW5oZWFkZXIvcm93aGVhZGVyIG11c3QgZGF0YSBjZWxscyB3aGljaCBpdCBkZXNjcmliZXMifSwidmFsaWQtbGFuZyI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIGxhbmcgYXR0cmlidXRlcyBoYXZlIHZhbGlkIHZhbHVlcyIsaGVscDoibGFuZyBhdHRyaWJ1dGUgbXVzdCBoYXZlIGEgdmFsaWQgdmFsdWUifSwidmlkZW8tY2FwdGlvbiI6e2Rlc2NyaXB0aW9uOiJFbnN1cmVzIDx2aWRlbz4gZWxlbWVudHMgaGF2ZSBjYXB0aW9ucyIsaGVscDoiPHZpZGVvPiBlbGVtZW50cyBtdXN0IGhhdmUgY2FwdGlvbnMifSwidmlkZW8tZGVzY3JpcHRpb24iOntkZXNjcmlwdGlvbjoiRW5zdXJlcyA8dmlkZW8+IGVsZW1lbnRzIGhhdmUgYXVkaW8gZGVzY3JpcHRpb25zIixoZWxwOiI8dmlkZW8+IGVsZW1lbnRzIG11c3QgaGF2ZSBhbiBhdWRpbyBkZXNjcmlwdGlvbiB0cmFjayJ9fSxjaGVja3M6e2FjY2Vzc2tleXM6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJBY2Nlc3NrZXkgYXR0cmlidXRlIHZhbHVlIGlzIHVuaXF1ZSI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkRvY3VtZW50IGhhcyBtdWx0aXBsZSBlbGVtZW50cyB3aXRoIHRoZSBzYW1lIGFjY2Vzc2tleSI7cmV0dXJuIGJ9fX0sIm5vbi1lbXB0eS1hbHQiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBoYXMgYSBub24tZW1wdHkgYWx0IGF0dHJpYnV0ZSI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaGFzIG5vIGFsdCBhdHRyaWJ1dGUgb3IgdGhlIGFsdCBhdHRyaWJ1dGUgaXMgZW1wdHkiO3JldHVybiBifX19LCJub24tZW1wdHktdGl0bGUiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBoYXMgYSB0aXRsZSBhdHRyaWJ1dGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGhhcyBubyB0aXRsZSBhdHRyaWJ1dGUgb3IgdGhlIHRpdGxlIGF0dHJpYnV0ZSBpcyBlbXB0eSI7cmV0dXJuIGJ9fX0sImFyaWEtbGFiZWwiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iYXJpYS1sYWJlbCBhdHRyaWJ1dGUgZXhpc3RzIGFuZCBpcyBub3QgZW1wdHkiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJhcmlhLWxhYmVsIGF0dHJpYnV0ZSBkb2VzIG5vdCBleGlzdCBvciBpcyBlbXB0eSI7cmV0dXJuIGJ9fX0sImFyaWEtbGFiZWxsZWRieSI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJhcmlhLWxhYmVsbGVkYnkgYXR0cmlidXRlIGV4aXN0cyBhbmQgcmVmZXJlbmNlcyBlbGVtZW50cyB0aGF0IGFyZSB2aXNpYmxlIHRvIHNjcmVlbiByZWFkZXJzIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iYXJpYS1sYWJlbGxlZGJ5IGF0dHJpYnV0ZSBkb2VzIG5vdCBleGlzdCwgcmVmZXJlbmNlcyBlbGVtZW50cyB0aGF0IGRvIG5vdCBleGlzdCBvciByZWZlcmVuY2VzIGVsZW1lbnRzIHRoYXQgYXJlIGVtcHR5IG9yIG5vdCB2aXNpYmxlIjtyZXR1cm4gYn19fSwiYXJpYS1hbGxvd2VkLWF0dHIiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQVJJQSBhdHRyaWJ1dGVzIGFyZSB1c2VkIGNvcnJlY3RseSBmb3IgdGhlIGRlZmluZWQgcm9sZSI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkFSSUEgYXR0cmlidXRlIisoYS5kYXRhJiZhLmRhdGEubGVuZ3RoPjE/InMgYXJlIjoiIGlzIikrIiBub3QgYWxsb3dlZDoiLGM9YS5kYXRhO2lmKGMpZm9yKHZhciBkLGU9LTEsZj1jLmxlbmd0aC0xO2U8ZjspZD1jW2UrPTFdLGIrPSIgIitkO3JldHVybiBifX19LCJhcmlhLXJlcXVpcmVkLWF0dHIiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQWxsIHJlcXVpcmVkIEFSSUEgYXR0cmlidXRlcyBhcmUgcHJlc2VudCI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlJlcXVpcmVkIEFSSUEgYXR0cmlidXRlIisoYS5kYXRhJiZhLmRhdGEubGVuZ3RoPjE/InMiOiIiKSsiIG5vdCBwcmVzZW50OiIsYz1hLmRhdGE7aWYoYylmb3IodmFyIGQsZT0tMSxmPWMubGVuZ3RoLTE7ZTxmOylkPWNbZSs9MV0sYis9IiAiK2Q7cmV0dXJuIGJ9fX0sImFyaWEtcmVxdWlyZWQtY2hpbGRyZW4iOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iUmVxdWlyZWQgQVJJQSBjaGlsZHJlbiBhcmUgcHJlc2VudCI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlJlcXVpcmVkIEFSSUEgIisoYS5kYXRhJiZhLmRhdGEubGVuZ3RoPjE/ImNoaWxkcmVuIjoiY2hpbGQiKSsiIHJvbGUgbm90IHByZXNlbnQ6IixjPWEuZGF0YTtpZihjKWZvcih2YXIgZCxlPS0xLGY9Yy5sZW5ndGgtMTtlPGY7KWQ9Y1tlKz0xXSxiKz0iICIrZDtyZXR1cm4gYn19fSwiYXJpYS1yZXF1aXJlZC1wYXJlbnQiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iUmVxdWlyZWQgQVJJQSBwYXJlbnQgcm9sZSBwcmVzZW50IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iUmVxdWlyZWQgQVJJQSBwYXJlbnQiKyhhLmRhdGEmJmEuZGF0YS5sZW5ndGg+MT8icyI6IiIpKyIgcm9sZSBub3QgcHJlc2VudDoiLGM9YS5kYXRhO2lmKGMpZm9yKHZhciBkLGU9LTEsZj1jLmxlbmd0aC0xO2U8ZjspZD1jW2UrPTFdLGIrPSIgIitkO3JldHVybiBifX19LGludmFsaWRyb2xlOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQVJJQSByb2xlIGlzIHZhbGlkIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iUm9sZSBtdXN0IGJlIG9uZSBvZiB0aGUgdmFsaWQgQVJJQSByb2xlcyI7cmV0dXJuIGJ9fX0sYWJzdHJhY3Ryb2xlOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJBYnN0cmFjdCByb2xlcyBhcmUgbm90IHVzZWQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJBYnN0cmFjdCByb2xlcyBjYW5ub3QgYmUgZGlyZWN0bHkgdXNlZCI7cmV0dXJuIGJ9fX0sImFyaWEtdmFsaWQtYXR0ci12YWx1ZSI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJBUklBIGF0dHJpYnV0ZSB2YWx1ZXMgYXJlIHZhbGlkIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iSW52YWxpZCBBUklBIGF0dHJpYnV0ZSB2YWx1ZSIrKGEuZGF0YSYmYS5kYXRhLmxlbmd0aD4xPyJzIjoiIikrIjoiLGM9YS5kYXRhO2lmKGMpZm9yKHZhciBkLGU9LTEsZj1jLmxlbmd0aC0xO2U8ZjspZD1jW2UrPTFdLGIrPSIgIitkO3JldHVybiBifX19LCJhcmlhLXZhbGlkLWF0dHIiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQVJJQSBhdHRyaWJ1dGUgbmFtZSIrKGEuZGF0YSYmYS5kYXRhLmxlbmd0aD4xPyJzIjoiIikrIiBhcmUgdmFsaWQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJJbnZhbGlkIEFSSUEgYXR0cmlidXRlIG5hbWUiKyhhLmRhdGEmJmEuZGF0YS5sZW5ndGg+MT8icyI6IiIpKyI6IixjPWEuZGF0YTtpZihjKWZvcih2YXIgZCxlPS0xLGY9Yy5sZW5ndGgtMTtlPGY7KWQ9Y1tlKz0xXSxiKz0iICIrZDtyZXR1cm4gYn19fSxjYXB0aW9uOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iVGhlIG11bHRpbWVkaWEgZWxlbWVudCBoYXMgYSBjYXB0aW9ucyB0cmFjayI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlRoZSBtdWx0aW1lZGlhIGVsZW1lbnQgZG9lcyBub3QgaGF2ZSBhIGNhcHRpb25zIHRyYWNrIjtyZXR1cm4gYn19fSwiaXMtb24tc2NyZWVuIjp7aW1wYWN0OiJtaW5vciIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaXMgbm90IHZpc2libGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGlzIHZpc2libGUiO3JldHVybiBifX19LCJub24tZW1wdHktaWYtcHJlc2VudCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50ICI7cmV0dXJuIGIrPWEuZGF0YT8iaGFzIGEgbm9uLWVtcHR5IHZhbHVlIGF0dHJpYnV0ZSI6ImRvZXMgbm90IGhhdmUgYSB2YWx1ZSBhdHRyaWJ1dGUifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGhhcyBhIHZhbHVlIGF0dHJpYnV0ZSBhbmQgdGhlIHZhbHVlIGF0dHJpYnV0ZSBpcyBlbXB0eSI7cmV0dXJuIGJ9fX0sIm5vbi1lbXB0eS12YWx1ZSI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGhhcyBhIG5vbi1lbXB0eSB2YWx1ZSBhdHRyaWJ1dGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGhhcyBubyB2YWx1ZSBhdHRyaWJ1dGUgb3IgdGhlIHZhbHVlIGF0dHJpYnV0ZSBpcyBlbXB0eSI7cmV0dXJuIGJ9fX0sImJ1dHRvbi1oYXMtdmlzaWJsZS10ZXh0Ijp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaGFzIGlubmVyIHRleHQgdGhhdCBpcyB2aXNpYmxlIHRvIHNjcmVlbiByZWFkZXJzIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBkb2VzIG5vdCBoYXZlIGlubmVyIHRleHQgdGhhdCBpcyB2aXNpYmxlIHRvIHNjcmVlbiByZWFkZXJzIjtyZXR1cm4gYn19fSwicm9sZS1wcmVzZW50YXRpb24iOntpbXBhY3Q6Im1vZGVyYXRlIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0nRWxlbWVudFwncyBkZWZhdWx0IHNlbWFudGljcyB3ZXJlIG92ZXJyaWRlbiB3aXRoIHJvbGU9InByZXNlbnRhdGlvbiInO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSdFbGVtZW50XCdzIGRlZmF1bHQgc2VtYW50aWNzIHdlcmUgbm90IG92ZXJyaWRkZW4gd2l0aCByb2xlPSJwcmVzZW50YXRpb24iJztyZXR1cm4gYn19fSwicm9sZS1ub25lIjp7aW1wYWN0OiJtb2RlcmF0ZSIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9J0VsZW1lbnRcJ3MgZGVmYXVsdCBzZW1hbnRpY3Mgd2VyZSBvdmVycmlkZW4gd2l0aCByb2xlPSJub25lIic7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9J0VsZW1lbnRcJ3MgZGVmYXVsdCBzZW1hbnRpY3Mgd2VyZSBub3Qgb3ZlcnJpZGRlbiB3aXRoIHJvbGU9Im5vbmUiJztyZXR1cm4gYn19fSwiZm9jdXNhYmxlLW5vLW5hbWUiOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGlzIG5vdCBpbiB0YWIgb3JkZXIgb3IgaGFzIGFjY2Vzc2libGUgdGV4dCI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaXMgaW4gdGFiIG9yZGVyIGFuZCBkb2VzIG5vdCBoYXZlIGFjY2Vzc2libGUgdGV4dCI7cmV0dXJuIGJ9fX0sImludGVybmFsLWxpbmstcHJlc2VudCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJWYWxpZCBza2lwIGxpbmsgZm91bmQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJObyB2YWxpZCBza2lwIGxpbmsgZm91bmQiO3JldHVybiBifX19LCJoZWFkZXItcHJlc2VudCI6e2ltcGFjdDoibW9kZXJhdGUiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJQYWdlIGhhcyBhIGhlYWRlciI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlBhZ2UgZG9lcyBub3QgaGF2ZSBhIGhlYWRlciI7cmV0dXJuIGJ9fX0sbGFuZG1hcms6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IlBhZ2UgaGFzIGEgbGFuZG1hcmsgcmVnaW9uIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iUGFnZSBkb2VzIG5vdCBoYXZlIGEgbGFuZG1hcmsgcmVnaW9uIjtyZXR1cm4gYn19fSwiZ3JvdXAtbGFiZWxsZWRieSI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSdBbGwgZWxlbWVudHMgd2l0aCB0aGUgbmFtZSAiJythLmRhdGEubmFtZSsnIiByZWZlcmVuY2UgdGhlIHNhbWUgZWxlbWVudCB3aXRoIGFyaWEtbGFiZWxsZWRieSc7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9J0FsbCBlbGVtZW50cyB3aXRoIHRoZSBuYW1lICInK2EuZGF0YS5uYW1lKyciIGRvIG5vdCByZWZlcmVuY2UgdGhlIHNhbWUgZWxlbWVudCB3aXRoIGFyaWEtbGFiZWxsZWRieSc7cmV0dXJuIGJ9fX0sZmllbGRzZXQ6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGlzIGNvbnRhaW5lZCBpbiBhIGZpZWxkc2V0IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iIixjPWEuZGF0YSYmYS5kYXRhLmZhaWx1cmVDb2RlO3JldHVybiBiKz0ibm8tbGVnZW5kIj09PWM/IkZpZWxkc2V0IGRvZXMgbm90IGhhdmUgYSBsZWdlbmQgYXMgaXRzIGZpcnN0IGNoaWxkIjoiZW1wdHktbGVnZW5kIj09PWM/IkxlZ2VuZCBkb2VzIG5vdCBoYXZlIHRleHQgdGhhdCBpcyB2aXNpYmxlIHRvIHNjcmVlbiByZWFkZXJzIjoibWl4ZWQtaW5wdXRzIj09PWM/IkZpZWxkc2V0IGNvbnRhaW5zIHVucmVsYXRlZCBpbnB1dHMiOiJuby1ncm91cC1sYWJlbCI9PT1jPyJBUklBIGdyb3VwIGRvZXMgbm90IGhhdmUgYXJpYS1sYWJlbCBvciBhcmlhLWxhYmVsbGVkYnkiOiJncm91cC1taXhlZC1pbnB1dHMiPT09Yz8iQVJJQSBncm91cCBjb250YWlucyB1bnJlbGF0ZWQgaW5wdXRzIjoiRWxlbWVudCBkb2VzIG5vdCBoYXZlIGEgY29udGFpbmluZyBmaWVsZHNldCBvciBBUklBIGdyb3VwIn19fSwiY29sb3ItY29udHJhc3QiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iIjtyZXR1cm4gYis9YS5kYXRhJiZhLmRhdGEuY29udHJhc3RSYXRpbz8iRWxlbWVudCBoYXMgc3VmZmljaWVudCBjb2xvciBjb250cmFzdCBvZiAiK2EuZGF0YS5jb250cmFzdFJhdGlvOiJVbmFibGUgdG8gZGV0ZXJtaW5lIGNvbnRyYXN0IHJhdGlvIn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBoYXMgaW5zdWZmaWNpZW50IGNvbG9yIGNvbnRyYXN0IG9mICIrYS5kYXRhLmNvbnRyYXN0UmF0aW8rIiAoZm9yZWdyb3VuZCBjb2xvcjogIithLmRhdGEuZmdDb2xvcisiLCBiYWNrZ3JvdW5kIGNvbG9yOiAiK2EuZGF0YS5iZ0NvbG9yKyIsIGZvbnQgc2l6ZTogIithLmRhdGEuZm9udFNpemUrIiwgZm9udCB3ZWlnaHQ6ICIrYS5kYXRhLmZvbnRXZWlnaHQrIikiO3JldHVybiBifX19LCJzdHJ1Y3R1cmVkLWRsaXRlbXMiOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJXaGVuIG5vdCBlbXB0eSwgZWxlbWVudCBoYXMgYm90aCA8ZHQ+IGFuZCA8ZGQ+IGVsZW1lbnRzIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iV2hlbiBub3QgZW1wdHksIGVsZW1lbnQgZG9lcyBub3QgaGF2ZSBhdCBsZWFzdCBvbmUgPGR0PiBlbGVtZW50IGZvbGxvd2VkIGJ5IGF0IGxlYXN0IG9uZSA8ZGQ+IGVsZW1lbnQiO3JldHVybiBifX19LCJvbmx5LWRsaXRlbXMiOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJMaXN0IGVsZW1lbnQgb25seSBoYXMgZGlyZWN0IGNoaWxkcmVuIHRoYXQgYXJlIGFsbG93ZWQgaW5zaWRlIDxkdD4gb3IgPGRkPiBlbGVtZW50cyI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9Ikxpc3QgZWxlbWVudCBoYXMgZGlyZWN0IGNoaWxkcmVuIHRoYXQgYXJlIG5vdCBhbGxvd2VkIGluc2lkZSA8ZHQ+IG9yIDxkZD4gZWxlbWVudHMiO3JldHVybiBifX19LGRsaXRlbTp7aW1wYWN0OiJzZXJpb3VzIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iRGVzY3JpcHRpb24gbGlzdCBpdGVtIGhhcyBhIDxkbD4gcGFyZW50IGVsZW1lbnQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJEZXNjcmlwdGlvbiBsaXN0IGl0ZW0gZG9lcyBub3QgaGF2ZSBhIDxkbD4gcGFyZW50IGVsZW1lbnQiO3JldHVybiBifX19LCJkb2MtaGFzLXRpdGxlIjp7aW1wYWN0OiJtb2RlcmF0ZSIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkRvY3VtZW50IGhhcyBhIG5vbi1lbXB0eSA8dGl0bGU+IGVsZW1lbnQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJEb2N1bWVudCBkb2VzIG5vdCBoYXZlIGEgbm9uLWVtcHR5IDx0aXRsZT4gZWxlbWVudCI7cmV0dXJuIGJ9fX0sImR1cGxpY2F0ZS1pZCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJEb2N1bWVudCBoYXMgbm8gZWxlbWVudHMgdGhhdCBzaGFyZSB0aGUgc2FtZSBpZCBhdHRyaWJ1dGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJEb2N1bWVudCBoYXMgbXVsdGlwbGUgZWxlbWVudHMgd2l0aCB0aGUgc2FtZSBpZCBhdHRyaWJ1dGU6ICIrYS5kYXRhO3JldHVybiBifX19LCJoYXMtdmlzaWJsZS10ZXh0Ijp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaGFzIHRleHQgdGhhdCBpcyB2aXNpYmxlIHRvIHNjcmVlbiByZWFkZXJzIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBkb2VzIG5vdCBoYXZlIHRleHQgdGhhdCBpcyB2aXNpYmxlIHRvIHNjcmVlbiByZWFkZXJzIjtyZXR1cm4gYn19fSwidW5pcXVlLWZyYW1lLXRpdGxlIjp7aW1wYWN0OiJzZXJpb3VzIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCdzIHRpdGxlIGF0dHJpYnV0ZSBpcyB1bmlxdWUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50J3MgdGl0bGUgYXR0cmlidXRlIGlzIG5vdCB1bmlxdWUiO3JldHVybiBifX19LCJoZWFkaW5nLW9yZGVyIjp7aW1wYWN0OiJtaW5vciIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkhlYWRpbmcgb3JkZXIgdmFsaWQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJIZWFkaW5nIG9yZGVyIGludmFsaWQiO3JldHVybiBifX19LCJocmVmLW5vLWhhc2giOntpbXBhY3Q6Im1vZGVyYXRlIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQW5jaG9yIGRvZXMgbm90IGhhdmUgYSBocmVmIHF1YWxzICMiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJBbmNob3IgaGFzIGEgaHJlZiBxdWFscyAjIjtyZXR1cm4gYn19fSwiaGFzLWxhbmciOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJUaGUgPGh0bWw+IGVsZW1lbnQgaGFzIGEgbGFuZyBhdHRyaWJ1dGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJUaGUgPGh0bWw+IGVsZW1lbnQgZG9lcyBub3QgaGF2ZSBhIGxhbmcgYXR0cmlidXRlIjtyZXR1cm4gYn19fSwidmFsaWQtbGFuZyI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IlZhbHVlIG9mIGxhbmcgYXR0cmlidXRlIGlzIGluY2x1ZGVkIGluIHRoZSBsaXN0IG9mIHZhbGlkIGxhbmd1YWdlcyI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlZhbHVlIG9mIGxhbmcgYXR0cmlidXRlIG5vdCBpbmNsdWRlZCBpbiB0aGUgbGlzdCBvZiB2YWxpZCBsYW5ndWFnZXMiO3JldHVybiBifX19LCJoYXMtYWx0Ijp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaGFzIGFuIGFsdCBhdHRyaWJ1dGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGRvZXMgbm90IGhhdmUgYW4gYWx0IGF0dHJpYnV0ZSI7cmV0dXJuIGJ9fX0sImR1cGxpY2F0ZS1pbWctbGFiZWwiOntpbXBhY3Q6Im1pbm9yIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iRWxlbWVudCBkb2VzIG5vdCBkdXBsaWNhdGUgZXhpc3RpbmcgdGV4dCBpbiA8aW1nPiBhbHQgdGV4dCI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgY29udGFpbnMgPGltZz4gZWxlbWVudCB3aXRoIGFsdCB0ZXh0IHRoYXQgZHVwbGljYXRlcyBleGlzdGluZyB0ZXh0IjtyZXR1cm4gYn19fSwidGl0bGUtb25seSI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkZvcm0gZWxlbWVudCBkb2VzIG5vdCBzb2xlbHkgdXNlIHRpdGxlIGF0dHJpYnV0ZSBmb3IgaXRzIGxhYmVsIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iT25seSB0aXRsZSB1c2VkIHRvIGdlbmVyYXRlIGxhYmVsIGZvciBmb3JtIGVsZW1lbnQiO3JldHVybiBifX19LCJpbXBsaWNpdC1sYWJlbCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJGb3JtIGVsZW1lbnQgaGFzIGFuIGltcGxpY2l0ICh3cmFwcGVkKSA8bGFiZWw+IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iRm9ybSBlbGVtZW50IGRvZXMgbm90IGhhdmUgYW4gaW1wbGljaXQgKHdyYXBwZWQpIDxsYWJlbD4iO3JldHVybiBifX19LCJleHBsaWNpdC1sYWJlbCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJGb3JtIGVsZW1lbnQgaGFzIGFuIGV4cGxpY2l0IDxsYWJlbD4iO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJGb3JtIGVsZW1lbnQgZG9lcyBub3QgaGF2ZSBhbiBleHBsaWNpdCA8bGFiZWw+IjtyZXR1cm4gYn19fSwiaGVscC1zYW1lLWFzLWxhYmVsIjp7aW1wYWN0OiJtaW5vciIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkhlbHAgdGV4dCAodGl0bGUgb3IgYXJpYS1kZXNjcmliZWRieSkgZG9lcyBub3QgZHVwbGljYXRlIGxhYmVsIHRleHQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJIZWxwIHRleHQgKHRpdGxlIG9yIGFyaWEtZGVzY3JpYmVkYnkpIHRleHQgaXMgdGhlIHNhbWUgYXMgdGhlIGxhYmVsIHRleHQiO3JldHVybiBifX19LCJtdWx0aXBsZS1sYWJlbCI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkZvcm0gZWxlbWVudCBkb2VzIG5vdCBoYXZlIG11bHRpcGxlIDxsYWJlbD4gZWxlbWVudHMiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJGb3JtIGVsZW1lbnQgaGFzIG11bHRpcGxlIDxsYWJlbD4gZWxlbWVudHMiO3JldHVybiBifX19LCJoYXMtdGgiOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJMYXlvdXQgdGFibGUgZG9lcyBub3QgdXNlIDx0aD4gZWxlbWVudHMiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJMYXlvdXQgdGFibGUgdXNlcyA8dGg+IGVsZW1lbnRzIjtyZXR1cm4gYn19fSwiaGFzLWNhcHRpb24iOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJMYXlvdXQgdGFibGUgZG9lcyBub3QgdXNlIDxjYXB0aW9uPiBlbGVtZW50IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iTGF5b3V0IHRhYmxlIHVzZXMgPGNhcHRpb24+IGVsZW1lbnQiO3JldHVybiBifX19LCJoYXMtc3VtbWFyeSI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkxheW91dCB0YWJsZSBkb2VzIG5vdCB1c2Ugc3VtbWFyeSBhdHRyaWJ1dGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJMYXlvdXQgdGFibGUgdXNlcyBzdW1tYXJ5IGF0dHJpYnV0ZSI7cmV0dXJuIGJ9fX0sImxpbmstaW4tdGV4dC1ibG9jayI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJMaW5rcyBjYW4gYmUgZGlzdGluZ3Vpc2hlZCBmcm9tIHN1cnJvdW5kaW5nIHRleHQgaW4gYSB3YXkgdGhhdCBkb2VzIG5vdCByZWx5IG9uIGNvbG9yIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iTGlua3MgY2FuIG5vdCBiZSBkaXN0aW5ndWlzaGVkIGZyb20gc3Vycm91bmRpbmcgdGV4dCBpbiBhIHdheSB0aGF0IGRvZXMgbm90IHJlbHkgb24gY29sb3IiO3JldHVybiBifX19LCJvbmx5LWxpc3RpdGVtcyI6e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9Ikxpc3QgZWxlbWVudCBvbmx5IGhhcyBkaXJlY3QgY2hpbGRyZW4gdGhhdCBhcmUgYWxsb3dlZCBpbnNpZGUgPGxpPiBlbGVtZW50cyI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9Ikxpc3QgZWxlbWVudCBoYXMgZGlyZWN0IGNoaWxkcmVuIHRoYXQgYXJlIG5vdCBhbGxvd2VkIGluc2lkZSA8bGk+IGVsZW1lbnRzIjtyZXR1cm4gYn19fSxsaXN0aXRlbTp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9J0xpc3QgaXRlbSBoYXMgYSA8dWw+LCA8b2w+IG9yIHJvbGU9Imxpc3QiIHBhcmVudCBlbGVtZW50JztyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0nTGlzdCBpdGVtIGRvZXMgbm90IGhhdmUgYSA8dWw+LCA8b2w+IG9yIHJvbGU9Imxpc3QiIHBhcmVudCBlbGVtZW50JztyZXR1cm4gYn19fSwibWV0YS1yZWZyZXNoIjp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IjxtZXRhPiB0YWcgZG9lcyBub3QgaW1tZWRpYXRlbHkgcmVmcmVzaCB0aGUgcGFnZSI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IjxtZXRhPiB0YWcgZm9yY2VzIHRpbWVkIHJlZnJlc2ggb2YgcGFnZSI7cmV0dXJuIGJ9fX0sIm1ldGEtdmlld3BvcnQtbGFyZ2UiOntpbXBhY3Q6Im1pbm9yIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iPG1ldGE+IHRhZyBkb2VzIG5vdCBwcmV2ZW50IHNpZ25pZmljYW50IHpvb21pbmciO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSI8bWV0YT4gdGFnIGxpbWl0cyB6b29taW5nIjtyZXR1cm4gYn19fSwibWV0YS12aWV3cG9ydCI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSI8bWV0YT4gdGFnIGRvZXMgbm90IGRpc2FibGUgem9vbWluZyI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IjxtZXRhPiB0YWcgZGlzYWJsZXMgem9vbWluZyI7cmV0dXJuIGJ9fX0scmVnaW9uOntpbXBhY3Q6Im1vZGVyYXRlIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQ29udGVudCBjb250YWluZWQgYnkgQVJJQSBsYW5kbWFyayI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkNvbnRlbnQgbm90IGNvbnRhaW5lZCBieSBhbiBBUklBIGxhbmRtYXJrIjtyZXR1cm4gYn19fSwiaHRtbDUtc2NvcGUiOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJTY29wZSBhdHRyaWJ1dGUgaXMgb25seSB1c2VkIG9uIHRhYmxlIGhlYWRlciBlbGVtZW50cyAoPHRoPikiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJJbiBIVE1MIDUsIHNjb3BlIGF0dHJpYnV0ZXMgbWF5IG9ubHkgYmUgdXNlZCBvbiB0YWJsZSBoZWFkZXIgZWxlbWVudHMgKDx0aD4pIjtyZXR1cm4gYn19fSwic2NvcGUtdmFsdWUiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iU2NvcGUgYXR0cmlidXRlIGlzIHVzZWQgY29ycmVjdGx5IjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iVGhlIHZhbHVlIG9mIHRoZSBzY29wZSBhdHRyaWJ1dGUgbWF5IG9ubHkgYmUgJ3Jvdycgb3IgJ2NvbCciO3JldHVybiBifX19LGV4aXN0czp7aW1wYWN0OiJtaW5vciIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgZG9lcyBub3QgZXhpc3QiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGV4aXN0cyI7cmV0dXJuIGJ9fX0sInNraXAtbGluayI6e2ltcGFjdDoiY3JpdGljYWwiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJWYWxpZCBza2lwIGxpbmsgZm91bmQiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJObyB2YWxpZCBza2lwIGxpbmsgZm91bmQiO3JldHVybiBifX19LHRhYmluZGV4OntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJFbGVtZW50IGRvZXMgbm90IGhhdmUgYSB0YWJpbmRleCBncmVhdGVyIHRoYW4gMCI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IkVsZW1lbnQgaGFzIGEgdGFiaW5kZXggZ3JlYXRlciB0aGFuIDAiO3JldHVybiBifX19LCJzYW1lLWNhcHRpb24tc3VtbWFyeSI6e2ltcGFjdDoibW9kZXJhdGUiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJDb250ZW50IG9mIHN1bW1hcnkgYXR0cmlidXRlIGFuZCA8Y2FwdGlvbj4gYXJlIG5vdCBkdXBsaWNhdGVkIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iQ29udGVudCBvZiBzdW1tYXJ5IGF0dHJpYnV0ZSBhbmQgPGNhcHRpb24+IGVsZW1lbnQgYXJlIGlkZW50aWNhbCI7cmV0dXJuIGJ9fX0sImNhcHRpb24tZmFrZWQiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iVGhlIGZpcnN0IHJvdyBvZiBhIHRhYmxlIGlzIG5vdCB1c2VkIGFzIGEgY2FwdGlvbiI7cmV0dXJuIGJ9LGZhaWw6ZnVuY3Rpb24oYSl7dmFyIGI9IlRoZSBmaXJzdCByb3cgb2YgdGhlIHRhYmxlIHNob3VsZCBiZSBhIGNhcHRpb24gaW5zdGVhZCBvZiBhIHRhYmxlIGNlbGwiO3JldHVybiBifX19LCJ0ZC1oYXMtaGVhZGVyIjp7aW1wYWN0OiJjcml0aWNhbCIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IkFsbCBub24tZW1wdHkgZGF0YSBjZWxscyBoYXZlIHRhYmxlIGhlYWRlcnMiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJTb21lIG5vbi1lbXB0eSBkYXRhIGNlbGxzIGRvIG5vdCBoYXZlIHRhYmxlIGhlYWRlcnMiO3JldHVybiBifX19LCJ0ZC1oZWFkZXJzLWF0dHIiOntpbXBhY3Q6InNlcmlvdXMiLG1lc3NhZ2VzOntwYXNzOmZ1bmN0aW9uKGEpe3ZhciBiPSJUaGUgaGVhZGVycyBhdHRyaWJ1dGUgaXMgZXhjbHVzaXZlbHkgdXNlZCB0byByZWZlciB0byBvdGhlciBjZWxscyBpbiB0aGUgdGFibGUiO3JldHVybiBifSxmYWlsOmZ1bmN0aW9uKGEpe3ZhciBiPSJUaGUgaGVhZGVycyBhdHRyaWJ1dGUgaXMgbm90IGV4Y2x1c2l2ZWx5IHVzZWQgdG8gcmVmZXIgdG8gb3RoZXIgY2VsbHMgaW4gdGhlIHRhYmxlIjtyZXR1cm4gYn19fSwidGgtaGFzLWRhdGEtY2VsbHMiOntpbXBhY3Q6ImNyaXRpY2FsIixtZXNzYWdlczp7cGFzczpmdW5jdGlvbihhKXt2YXIgYj0iQWxsIHRhYmxlIGhlYWRlciBjZWxscyByZWZlciB0byBkYXRhIGNlbGxzIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iTm90IGFsbCB0YWJsZSBoZWFkZXIgY2VsbHMgcmVmZXIgdG8gZGF0YSBjZWxscyI7cmV0dXJuIGJ9fX0sZGVzY3JpcHRpb246e2ltcGFjdDoic2VyaW91cyIsbWVzc2FnZXM6e3Bhc3M6ZnVuY3Rpb24oYSl7dmFyIGI9IlRoZSBtdWx0aW1lZGlhIGVsZW1lbnQgaGFzIGFuIGF1ZGlvIGRlc2NyaXB0aW9uIHRyYWNrIjtyZXR1cm4gYn0sZmFpbDpmdW5jdGlvbihhKXt2YXIgYj0iVGhlIG11bHRpbWVkaWEgZWxlbWVudCBkb2VzIG5vdCBoYXZlIGFuIGF1ZGlvIGRlc2NyaXB0aW9uIHRyYWNrIjtyZXR1cm4gYn19fX0sZmFpbHVyZVN1bW1hcmllczp7YW55OntmYWlsdXJlTWVzc2FnZTpmdW5jdGlvbihhKXt2YXIgYj0iRml4IGFueSBvZiB0aGUgZm9sbG93aW5nOiIsYz1hO2lmKGMpZm9yKHZhciBkLGU9LTEsZj1jLmxlbmd0aC0xO2U8ZjspZD1jW2UrPTFdLGIrPSJcbiAgIitkLnNwbGl0KCJcbiIpLmpvaW4oIlxuICAiKTtyZXR1cm4gYn19LG5vbmU6e2ZhaWx1cmVNZXNzYWdlOmZ1bmN0aW9uKGEpe3ZhciBiPSJGaXggYWxsIG9mIHRoZSBmb2xsb3dpbmc6IixjPWE7aWYoYylmb3IodmFyIGQsZT0tMSxmPWMubGVuZ3RoLTE7ZTxmOylkPWNbZSs9MV0sYis9IlxuICAiK2Quc3BsaXQoIlxuIikuam9pbigiXG4gICIpO3JldHVybiBifX19fSxydWxlczpbe2lkOiJhY2Nlc3NrZXlzIixzZWxlY3RvcjoiW2FjY2Vzc2tleV0iLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYSIsIndjYWcyMTEiXSxhbGw6W10sYW55OltdLG5vbmU6WyJhY2Nlc3NrZXlzIl19LHtpZDoiYXJlYS1hbHQiLApzZWxlY3RvcjoibWFwIGFyZWFbaHJlZl0iLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYSIsIndjYWcxMTEiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOC4yMi5hIl0sYWxsOltdLGFueTpbIm5vbi1lbXB0eS1hbHQiLCJub24tZW1wdHktdGl0bGUiLCJhcmlhLWxhYmVsIiwiYXJpYS1sYWJlbGxlZGJ5Il0sbm9uZTpbXX0se2lkOiJhcmlhLWFsbG93ZWQtYXR0ciIsbWF0Y2hlczpmdW5jdGlvbihhKXt2YXIgYj1hLmdldEF0dHJpYnV0ZSgicm9sZSIpO2J8fChiPWF4ZS5jb21tb25zLmFyaWEuaW1wbGljaXRSb2xlKGEpKTt2YXIgYz1heGUuY29tbW9ucy5hcmlhLmFsbG93ZWRBdHRyKGIpO2lmKGImJmMpe3ZhciBkPS9eYXJpYS0vO2lmKGEuaGFzQXR0cmlidXRlcygpKWZvcih2YXIgZT1hLmF0dHJpYnV0ZXMsZj0wLGc9ZS5sZW5ndGg7ZjxnO2YrKylpZihkLnRlc3QoZVtmXS5uYW1lKSlyZXR1cm4hMH1yZXR1cm4hMX0sdGFnczpbIndjYWcyYSIsIndjYWc0MTEiLCJ3Y2FnNDEyIl0sYWxsOltdLGFueTpbImFyaWEtYWxsb3dlZC1hdHRyIl0sbm9uZTpbXX0se2lkOiJhcmlhLXJlcXVpcmVkLWF0dHIiLHNlbGVjdG9yOiJbcm9sZV0iLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnNDExIiwid2NhZzQxMiJdLGFsbDpbXSxhbnk6WyJhcmlhLXJlcXVpcmVkLWF0dHIiXSxub25lOltdfSx7aWQ6ImFyaWEtcmVxdWlyZWQtY2hpbGRyZW4iLHNlbGVjdG9yOiJbcm9sZV0iLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTMxIl0sYWxsOltdLGFueTpbImFyaWEtcmVxdWlyZWQtY2hpbGRyZW4iXSxub25lOltdfSx7aWQ6ImFyaWEtcmVxdWlyZWQtcGFyZW50IixzZWxlY3RvcjoiW3JvbGVdIix0YWdzOlsid2NhZzJhIiwid2NhZzEzMSJdLGFsbDpbXSxhbnk6WyJhcmlhLXJlcXVpcmVkLXBhcmVudCJdLG5vbmU6W119LHtpZDoiYXJpYS1yb2xlcyIsc2VsZWN0b3I6Iltyb2xlXSIsdGFnczpbIndjYWcyYSIsIndjYWcxMzEiLCJ3Y2FnNDExIiwid2NhZzQxMiJdLGFsbDpbXSxhbnk6W10sbm9uZTpbImludmFsaWRyb2xlIiwiYWJzdHJhY3Ryb2xlIl19LHtpZDoiYXJpYS12YWxpZC1hdHRyLXZhbHVlIixtYXRjaGVzOmZ1bmN0aW9uKGEpe3ZhciBiPS9eYXJpYS0vO2lmKGEuaGFzQXR0cmlidXRlcygpKWZvcih2YXIgYz1hLmF0dHJpYnV0ZXMsZD0wLGU9Yy5sZW5ndGg7ZDxlO2QrKylpZihiLnRlc3QoY1tkXS5uYW1lKSlyZXR1cm4hMDtyZXR1cm4hMX0sdGFnczpbIndjYWcyYSIsIndjYWcxMzEiLCJ3Y2FnNDExIiwid2NhZzQxMiJdLGFsbDpbXSxhbnk6W3tvcHRpb25zOltdLGlkOiJhcmlhLXZhbGlkLWF0dHItdmFsdWUifV0sbm9uZTpbXX0se2lkOiJhcmlhLXZhbGlkLWF0dHIiLG1hdGNoZXM6ZnVuY3Rpb24oYSl7dmFyIGI9L15hcmlhLS87aWYoYS5oYXNBdHRyaWJ1dGVzKCkpZm9yKHZhciBjPWEuYXR0cmlidXRlcyxkPTAsZT1jLmxlbmd0aDtkPGU7ZCsrKWlmKGIudGVzdChjW2RdLm5hbWUpKXJldHVybiEwO3JldHVybiExfSx0YWdzOlsid2NhZzJhIiwid2NhZzQxMSJdLGFsbDpbXSxhbnk6W3tvcHRpb25zOltdLGlkOiJhcmlhLXZhbGlkLWF0dHIifV0sbm9uZTpbXX0se2lkOiJhdWRpby1jYXB0aW9uIixzZWxlY3RvcjoiYXVkaW8iLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYSIsIndjYWcxMjIiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOC4yMi5hIl0sYWxsOltdLGFueTpbXSxub25lOlsiY2FwdGlvbiJdfSx7aWQ6ImJsaW5rIixzZWxlY3RvcjoiYmxpbmsiLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYSIsIndjYWcyMjIiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOC4yMi5qIl0sYWxsOltdLGFueTpbXSxub25lOlsiaXMtb24tc2NyZWVuIl19LHtpZDoiYnV0dG9uLW5hbWUiLHNlbGVjdG9yOididXR0b24sIFtyb2xlPSJidXR0b24iXSwgaW5wdXRbdHlwZT0iYnV0dG9uIl0sIGlucHV0W3R5cGU9InN1Ym1pdCJdLCBpbnB1dFt0eXBlPSJyZXNldCJdJyx0YWdzOlsid2NhZzJhIiwid2NhZzQxMiIsInNlY3Rpb241MDgiLCJzZWN0aW9uNTA4LjIyLmEiXSxhbGw6W10sYW55Olsibm9uLWVtcHR5LWlmLXByZXNlbnQiLCJub24tZW1wdHktdmFsdWUiLCJidXR0b24taGFzLXZpc2libGUtdGV4dCIsImFyaWEtbGFiZWwiLCJhcmlhLWxhYmVsbGVkYnkiLCJyb2xlLXByZXNlbnRhdGlvbiIsInJvbGUtbm9uZSJdLG5vbmU6WyJmb2N1c2FibGUtbm8tbmFtZSJdfSx7aWQ6ImJ5cGFzcyIsc2VsZWN0b3I6Imh0bWwiLHBhZ2VMZXZlbDohMCxtYXRjaGVzOmZ1bmN0aW9uKGEpe3JldHVybiEhYS5xdWVyeVNlbGVjdG9yKCJhW2hyZWZdIil9LHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMjQxIiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDguMjIubyJdLGFsbDpbXSxhbnk6WyJpbnRlcm5hbC1saW5rLXByZXNlbnQiLCJoZWFkZXItcHJlc2VudCIsImxhbmRtYXJrIl0sbm9uZTpbXX0se2lkOiJjaGVja2JveGdyb3VwIixzZWxlY3RvcjoiaW5wdXRbdHlwZT1jaGVja2JveF1bbmFtZV0iLHRhZ3M6WyJiZXN0LXByYWN0aWNlIl0sYWxsOltdLGFueTpbImdyb3VwLWxhYmVsbGVkYnkiLCJmaWVsZHNldCJdLG5vbmU6W119LHtpZDoiY29sb3ItY29udHJhc3QiLG1hdGNoZXM6ZnVuY3Rpb24oYSl7dmFyIGI9YS5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpLGM9YS50eXBlLGQ9ZG9jdW1lbnQ7aWYoInRydWUiPT09YS5nZXRBdHRyaWJ1dGUoImFyaWEtZGlzYWJsZWQiKSlyZXR1cm4hMTtpZigiSU5QVVQiPT09YilyZXR1cm5bImhpZGRlbiIsInJhbmdlIiwiY29sb3IiLCJjaGVja2JveCIsInJhZGlvIiwiaW1hZ2UiXS5pbmRleE9mKGMpPT09LTEmJiFhLmRpc2FibGVkO2lmKCJTRUxFQ1QiPT09YilyZXR1cm4hIWEub3B0aW9ucy5sZW5ndGgmJiFhLmRpc2FibGVkO2lmKCJURVhUQVJFQSI9PT1iKXJldHVybiFhLmRpc2FibGVkO2lmKCJPUFRJT04iPT09YilyZXR1cm4hMTtpZigiQlVUVE9OIj09PWImJmEuZGlzYWJsZWQpcmV0dXJuITE7aWYoIkxBQkVMIj09PWIpe3ZhciBlPWEuaHRtbEZvciYmZC5nZXRFbGVtZW50QnlJZChhLmh0bWxGb3IpO2lmKGUmJmUuZGlzYWJsZWQpcmV0dXJuITE7dmFyIGU9YS5xdWVyeVNlbGVjdG9yKCdpbnB1dDpub3QoW3R5cGU9ImhpZGRlbiJdKTpub3QoW3R5cGU9ImltYWdlIl0pOm5vdChbdHlwZT0iYnV0dG9uIl0pOm5vdChbdHlwZT0ic3VibWl0Il0pOm5vdChbdHlwZT0icmVzZXQiXSksIHNlbGVjdCwgdGV4dGFyZWEnKTtpZihlJiZlLmRpc2FibGVkKXJldHVybiExfWlmKGEuaWQpe3ZhciBlPWQucXVlcnlTZWxlY3RvcigiW2FyaWEtbGFiZWxsZWRieX49IitheGUuY29tbW9ucy51dGlscy5lc2NhcGVTZWxlY3RvcihhLmlkKSsiXSIpO2lmKGUmJmUuZGlzYWJsZWQpcmV0dXJuITF9aWYoIiI9PT1heGUuY29tbW9ucy50ZXh0LnZpc2libGUoYSwhMSwhMCkpcmV0dXJuITE7dmFyIGYsZyxoPWRvY3VtZW50LmNyZWF0ZVJhbmdlKCksaT1hLmNoaWxkTm9kZXMsaj1pLmxlbmd0aDtmb3IoZz0wO2c8ajtnKyspZj1pW2ddLDM9PT1mLm5vZGVUeXBlJiYiIiE9PWF4ZS5jb21tb25zLnRleHQuc2FuaXRpemUoZi5ub2RlVmFsdWUpJiZoLnNlbGVjdE5vZGVDb250ZW50cyhmKTt2YXIgaz1oLmdldENsaWVudFJlY3RzKCk7Zm9yKGo9ay5sZW5ndGgsZz0wO2c8ajtnKyspaWYoYXhlLmNvbW1vbnMuZG9tLnZpc3VhbGx5T3ZlcmxhcHMoa1tnXSxhKSlyZXR1cm4hMDtyZXR1cm4hMX0sZXhjbHVkZUhpZGRlbjohMSxvcHRpb25zOntub1Njcm9sbDohMX0sdGFnczpbIndjYWcyYWEiLCJ3Y2FnMTQzIl0sYWxsOltdLGFueTpbImNvbG9yLWNvbnRyYXN0Il0sbm9uZTpbXX0se2lkOiJkZWZpbml0aW9uLWxpc3QiLHNlbGVjdG9yOiJkbDpub3QoW3JvbGVdKSIsdGFnczpbIndjYWcyYSIsIndjYWcxMzEiXSxhbGw6W10sYW55OltdLG5vbmU6WyJzdHJ1Y3R1cmVkLWRsaXRlbXMiLCJvbmx5LWRsaXRlbXMiXX0se2lkOiJkbGl0ZW0iLHNlbGVjdG9yOiJkZDpub3QoW3JvbGVdKSwgZHQ6bm90KFtyb2xlXSkiLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTMxIl0sYWxsOltdLGFueTpbImRsaXRlbSJdLG5vbmU6W119LHtpZDoiZG9jdW1lbnQtdGl0bGUiLHNlbGVjdG9yOiJodG1sIixtYXRjaGVzOmZ1bmN0aW9uKGEpe3JldHVybiB3aW5kb3cuc2VsZj09PXdpbmRvdy50b3B9LHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMjQyIl0sYWxsOltdLGFueTpbImRvYy1oYXMtdGl0bGUiXSxub25lOltdfSx7aWQ6ImR1cGxpY2F0ZS1pZCIsc2VsZWN0b3I6IltpZF0iLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYSIsIndjYWc0MTEiXSxhbGw6W10sYW55OlsiZHVwbGljYXRlLWlkIl0sbm9uZTpbXX0se2lkOiJlbXB0eS1oZWFkaW5nIixzZWxlY3RvcjonaDEsIGgyLCBoMywgaDQsIGg1LCBoNiwgW3JvbGU9ImhlYWRpbmciXScsZW5hYmxlZDohMCx0YWdzOlsiYmVzdC1wcmFjdGljZSJdLGFsbDpbXSxhbnk6WyJoYXMtdmlzaWJsZS10ZXh0Iiwicm9sZS1wcmVzZW50YXRpb24iLCJyb2xlLW5vbmUiXSxub25lOltdfSx7aWQ6ImZyYW1lLXRpdGxlLXVuaXF1ZSIsc2VsZWN0b3I6ImZyYW1lW3RpdGxlXTpub3QoW3RpdGxlPScnXSksIGlmcmFtZVt0aXRsZV06bm90KFt0aXRsZT0nJ10pIixtYXRjaGVzOmZ1bmN0aW9uKGEpe3ZhciBiPWEuZ2V0QXR0cmlidXRlKCJ0aXRsZSIpO3JldHVybiEhKGI/YXhlLmNvbW1vbnMudGV4dC5zYW5pdGl6ZShiKS50cmltKCk6IiIpfSx0YWdzOlsiYmVzdC1wcmFjdGljZSJdLGFsbDpbXSxhbnk6W10sbm9uZTpbInVuaXF1ZS1mcmFtZS10aXRsZSJdfSx7aWQ6ImZyYW1lLXRpdGxlIixzZWxlY3RvcjoiZnJhbWUsIGlmcmFtZSIsdGFnczpbIndjYWcyYSIsIndjYWcyNDEiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOC4yMi5pIl0sYWxsOltdLGFueTpbImFyaWEtbGFiZWwiLCJhcmlhLWxhYmVsbGVkYnkiLCJub24tZW1wdHktdGl0bGUiLCJyb2xlLXByZXNlbnRhdGlvbiIsInJvbGUtbm9uZSJdLG5vbmU6W119LHtpZDoiaGVhZGluZy1vcmRlciIsc2VsZWN0b3I6ImgxLGgyLGgzLGg0LGg1LGg2LFtyb2xlPWhlYWRpbmddIixlbmFibGVkOiExLHRhZ3M6WyJiZXN0LXByYWN0aWNlIl0sYWxsOltdLGFueTpbImhlYWRpbmctb3JkZXIiXSxub25lOltdfSx7aWQ6ImhyZWYtbm8taGFzaCIsc2VsZWN0b3I6ImFbaHJlZl0iLGVuYWJsZWQ6ITEsdGFnczpbImJlc3QtcHJhY3RpY2UiXSxhbGw6W10sYW55OlsiaHJlZi1uby1oYXNoIl0sbm9uZTpbXX0se2lkOiJodG1sLWhhcy1sYW5nIixzZWxlY3RvcjoiaHRtbCIsdGFnczpbIndjYWcyYSIsIndjYWczMTEiXSxhbGw6W10sYW55OlsiaGFzLWxhbmciXSxub25lOltdfSx7aWQ6Imh0bWwtbGFuZy12YWxpZCIsc2VsZWN0b3I6Imh0bWxbbGFuZ10iLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMzExIl0sYWxsOltdLGFueTpbXSxub25lOlt7b3B0aW9uczpbImFhIiwiYWIiLCJhZSIsImFmIiwiYWsiLCJhbSIsImFuIiwiYXIiLCJhcyIsImF2IiwiYXkiLCJheiIsImJhIiwiYmUiLCJiZyIsImJoIiwiYmkiLCJibSIsImJuIiwiYm8iLCJiciIsImJzIiwiY2EiLCJjZSIsImNoIiwiY28iLCJjciIsImNzIiwiY3UiLCJjdiIsImN5IiwiZGEiLCJkZSIsImR2IiwiZHoiLCJlZSIsImVsIiwiZW4iLCJlbyIsImVzIiwiZXQiLCJldSIsImZhIiwiZmYiLCJmaSIsImZqIiwiZm8iLCJmciIsImZ5IiwiZ2EiLCJnZCIsImdsIiwiZ24iLCJndSIsImd2IiwiaGEiLCJoZSIsImhpIiwiaG8iLCJociIsImh0IiwiaHUiLCJoeSIsImh6IiwiaWEiLCJpZCIsImllIiwiaWciLCJpaSIsImlrIiwiaW4iLCJpbyIsImlzIiwiaXQiLCJpdSIsIml3IiwiamEiLCJqaSIsImp2IiwianciLCJrYSIsImtnIiwia2kiLCJraiIsImtrIiwia2wiLCJrbSIsImtuIiwia28iLCJrciIsImtzIiwia3UiLCJrdiIsImt3Iiwia3kiLCJsYSIsImxiIiwibGciLCJsaSIsImxuIiwibG8iLCJsdCIsImx1IiwibHYiLCJtZyIsIm1oIiwibWkiLCJtayIsIm1sIiwibW4iLCJtbyIsIm1yIiwibXMiLCJtdCIsIm15IiwibmEiLCJuYiIsIm5kIiwibmUiLCJuZyIsIm5sIiwibm4iLCJubyIsIm5yIiwibnYiLCJueSIsIm9jIiwib2oiLCJvbSIsIm9yIiwib3MiLCJwYSIsInBpIiwicGwiLCJwcyIsInB0IiwicXUiLCJybSIsInJuIiwicm8iLCJydSIsInJ3Iiwic2EiLCJzYyIsInNkIiwic2UiLCJzZyIsInNoIiwic2kiLCJzayIsInNsIiwic20iLCJzbiIsInNvIiwic3EiLCJzciIsInNzIiwic3QiLCJzdSIsInN2Iiwic3ciLCJ0YSIsInRlIiwidGciLCJ0aCIsInRpIiwidGsiLCJ0bCIsInRuIiwidG8iLCJ0ciIsInRzIiwidHQiLCJ0dyIsInR5IiwidWciLCJ1ayIsInVyIiwidXoiLCJ2ZSIsInZpIiwidm8iLCJ3YSIsIndvIiwieGgiLCJ5aSIsInlvIiwiemEiLCJ6aCIsInp1Il0saWQ6InZhbGlkLWxhbmcifV19LHtpZDoiaW1hZ2UtYWx0IixzZWxlY3RvcjoiaW1nIix0YWdzOlsid2NhZzJhIiwid2NhZzExMSIsInNlY3Rpb241MDgiLCJzZWN0aW9uNTA4LjIyLmEiXSxhbGw6W10sYW55OlsiaGFzLWFsdCIsImFyaWEtbGFiZWwiLCJhcmlhLWxhYmVsbGVkYnkiLCJub24tZW1wdHktdGl0bGUiLCJyb2xlLXByZXNlbnRhdGlvbiIsInJvbGUtbm9uZSJdLG5vbmU6W119LHtpZDoiaW1hZ2UtcmVkdW5kYW50LWFsdCIsc2VsZWN0b3I6J2J1dHRvbiwgW3JvbGU9ImJ1dHRvbiJdLCBhW2hyZWZdLCBwLCBsaSwgdGQsIHRoJyx0YWdzOlsiYmVzdC1wcmFjdGljZSJdLGFsbDpbXSxhbnk6W10sbm9uZTpbImR1cGxpY2F0ZS1pbWctbGFiZWwiXX0se2lkOiJpbnB1dC1pbWFnZS1hbHQiLHNlbGVjdG9yOidpbnB1dFt0eXBlPSJpbWFnZSJdJyx0YWdzOlsid2NhZzJhIiwid2NhZzExMSIsInNlY3Rpb241MDgiLCJzZWN0aW9uNTA4LjIyLmEiXSxhbGw6W10sYW55Olsibm9uLWVtcHR5LWFsdCIsImFyaWEtbGFiZWwiLCJhcmlhLWxhYmVsbGVkYnkiLCJub24tZW1wdHktdGl0bGUiXSxub25lOltdfSx7aWQ6ImxhYmVsLXRpdGxlLW9ubHkiLHNlbGVjdG9yOiJpbnB1dDpub3QoW3R5cGU9J2hpZGRlbiddKTpub3QoW3R5cGU9J2ltYWdlJ10pOm5vdChbdHlwZT0nYnV0dG9uJ10pOm5vdChbdHlwZT0nc3VibWl0J10pOm5vdChbdHlwZT0ncmVzZXQnXSksIHNlbGVjdCwgdGV4dGFyZWEiLGVuYWJsZWQ6ITEsdGFnczpbImJlc3QtcHJhY3RpY2UiXSxhbGw6W10sYW55OltdLG5vbmU6WyJ0aXRsZS1vbmx5Il19LHtpZDoibGFiZWwiLHNlbGVjdG9yOiJpbnB1dDpub3QoW3R5cGU9J2hpZGRlbiddKTpub3QoW3R5cGU9J2ltYWdlJ10pOm5vdChbdHlwZT0nYnV0dG9uJ10pOm5vdChbdHlwZT0nc3VibWl0J10pOm5vdChbdHlwZT0ncmVzZXQnXSksIHNlbGVjdCwgdGV4dGFyZWEiLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMzMyIiwid2NhZzEzMSIsInNlY3Rpb241MDgiLCJzZWN0aW9uNTA4LjIyLm4iXSxhbGw6W10sYW55OlsiYXJpYS1sYWJlbCIsImFyaWEtbGFiZWxsZWRieSIsImltcGxpY2l0LWxhYmVsIiwiZXhwbGljaXQtbGFiZWwiLCJub24tZW1wdHktdGl0bGUiXSxub25lOlsiaGVscC1zYW1lLWFzLWxhYmVsIiwibXVsdGlwbGUtbGFiZWwiXX0se2lkOiJsYXlvdXQtdGFibGUiLHNlbGVjdG9yOiJ0YWJsZSIsbWF0Y2hlczpmdW5jdGlvbihhKXtyZXR1cm4hYXhlLmNvbW1vbnMudGFibGUuaXNEYXRhVGFibGUoYSl9LHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTMxIl0sYWxsOltdLGFueTpbXSxub25lOlsiaGFzLXRoIiwiaGFzLWNhcHRpb24iLCJoYXMtc3VtbWFyeSJdfSx7aWQ6ImxpbmstaW4tdGV4dC1ibG9jayIsc2VsZWN0b3I6ImFbaHJlZl06bm90KFtyb2xlXSksICpbcm9sZT1saW5rXSIsbWF0Y2hlczpmdW5jdGlvbihhKXt2YXIgYj1heGUuY29tbW9ucy50ZXh0LnNhbml0aXplKGEudGV4dENvbnRlbnQpO3JldHVybiEhYiYmKCEhYXhlLmNvbW1vbnMuZG9tLmlzVmlzaWJsZShhLCExKSYmYXhlLmNvbW1vbnMuZG9tLmlzSW5UZXh0QmxvY2soYSkpfSxleGNsdWRlSGlkZGVuOiExLGVuYWJsZWQ6ITEsdGFnczpbImV4cGVyaW1lbnRhbCIsIndjYWcyYSIsIndjYWcxNDEiXSxhbGw6WyJsaW5rLWluLXRleHQtYmxvY2siXSxhbnk6W10sbm9uZTpbXX0se2lkOiJsaW5rLW5hbWUiLHNlbGVjdG9yOidhW2hyZWZdOm5vdChbcm9sZT0iYnV0dG9uIl0pLCBbcm9sZT1saW5rXVtocmVmXScsdGFnczpbIndjYWcyYSIsIndjYWcxMTEiLCJ3Y2FnNDEyIiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDguMjIuYSJdLGFsbDpbXSxhbnk6WyJoYXMtdmlzaWJsZS10ZXh0IiwiYXJpYS1sYWJlbCIsImFyaWEtbGFiZWxsZWRieSIsInJvbGUtcHJlc2VudGF0aW9uIiwicm9sZS1ub25lIl0sbm9uZTpbImZvY3VzYWJsZS1uby1uYW1lIl19LHtpZDoibGlzdCIsc2VsZWN0b3I6InVsOm5vdChbcm9sZV0pLCBvbDpub3QoW3JvbGVdKSIsdGFnczpbIndjYWcyYSIsIndjYWcxMzEiXSxhbGw6W10sYW55OltdLG5vbmU6WyJvbmx5LWxpc3RpdGVtcyJdfSx7aWQ6Imxpc3RpdGVtIixzZWxlY3RvcjoibGk6bm90KFtyb2xlXSkiLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTMxIl0sYWxsOltdLGFueTpbImxpc3RpdGVtIl0sbm9uZTpbXX0se2lkOiJtYXJxdWVlIixzZWxlY3RvcjoibWFycXVlZSIsZXhjbHVkZUhpZGRlbjohMSx0YWdzOlsid2NhZzJhIiwid2NhZzIyMiJdLGFsbDpbXSxhbnk6W10sbm9uZTpbImlzLW9uLXNjcmVlbiJdfSx7aWQ6Im1ldGEtcmVmcmVzaCIsc2VsZWN0b3I6J21ldGFbaHR0cC1lcXVpdj0icmVmcmVzaCJdJyxleGNsdWRlSGlkZGVuOiExLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMmFhYSIsIndjYWcyMjEiLCJ3Y2FnMjI0Iiwid2NhZzMyNSJdLGFsbDpbXSxhbnk6WyJtZXRhLXJlZnJlc2giXSxub25lOltdfSx7aWQ6Im1ldGEtdmlld3BvcnQtbGFyZ2UiLHNlbGVjdG9yOidtZXRhW25hbWU9InZpZXdwb3J0Il0nLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbImJlc3QtcHJhY3RpY2UiXSxhbGw6W10sYW55Olt7b3B0aW9uczp7c2NhbGVNaW5pbXVtOjUsbG93ZXJCb3VuZDoyfSxpZDoibWV0YS12aWV3cG9ydC1sYXJnZSJ9XSxub25lOltdfSx7aWQ6Im1ldGEtdmlld3BvcnQiLHNlbGVjdG9yOidtZXRhW25hbWU9InZpZXdwb3J0Il0nLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYWEiLCJ3Y2FnMTQ0Il0sYWxsOltdLGFueTpbe29wdGlvbnM6e3NjYWxlTWluaW11bToyfSxpZDoibWV0YS12aWV3cG9ydCJ9XSxub25lOltdfSx7aWQ6Im9iamVjdC1hbHQiLHNlbGVjdG9yOiJvYmplY3QiLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTExIiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDguMjIuYSJdLGFsbDpbXSxhbnk6WyJoYXMtdmlzaWJsZS10ZXh0IiwiYXJpYS1sYWJlbCIsImFyaWEtbGFiZWxsZWRieSIsIm5vbi1lbXB0eS10aXRsZSJdLG5vbmU6W119LHtpZDoicmFkaW9ncm91cCIsc2VsZWN0b3I6ImlucHV0W3R5cGU9cmFkaW9dW25hbWVdIix0YWdzOlsiYmVzdC1wcmFjdGljZSJdLGFsbDpbXSxhbnk6WyJncm91cC1sYWJlbGxlZGJ5IiwiZmllbGRzZXQiXSxub25lOltdfSx7aWQ6InJlZ2lvbiIsc2VsZWN0b3I6Imh0bWwiLHBhZ2VMZXZlbDohMCxlbmFibGVkOiExLHRhZ3M6WyJiZXN0LXByYWN0aWNlIl0sYWxsOltdLGFueTpbInJlZ2lvbiJdLG5vbmU6W119LHtpZDoic2NvcGUtYXR0ci12YWxpZCIsc2VsZWN0b3I6InRkW3Njb3BlXSwgdGhbc2NvcGVdIixlbmFibGVkOiEwLHRhZ3M6WyJiZXN0LXByYWN0aWNlIl0sYWxsOlsiaHRtbDUtc2NvcGUiLCJzY29wZS12YWx1ZSJdLGFueTpbXSxub25lOltdfSx7aWQ6InNlcnZlci1zaWRlLWltYWdlLW1hcCIsc2VsZWN0b3I6ImltZ1tpc21hcF0iLHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMjExIiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDguMjIuZiJdLGFsbDpbXSxhbnk6W10sbm9uZTpbImV4aXN0cyJdfSx7aWQ6InNraXAtbGluayIsc2VsZWN0b3I6ImFbaHJlZl0iLHBhZ2VMZXZlbDohMCxlbmFibGVkOiExLHRhZ3M6WyJiZXN0LXByYWN0aWNlIl0sYWxsOltdLGFueTpbInNraXAtbGluayJdLG5vbmU6W119LHtpZDoidGFiaW5kZXgiLHNlbGVjdG9yOiJbdGFiaW5kZXhdIix0YWdzOlsiYmVzdC1wcmFjdGljZSJdLGFsbDpbXSxhbnk6WyJ0YWJpbmRleCJdLG5vbmU6W119LHtpZDoidGFibGUtZHVwbGljYXRlLW5hbWUiLHNlbGVjdG9yOiJ0YWJsZSIsdGFnczpbImJlc3QtcHJhY3RpY2UiXSxhbGw6W10sYW55OltdLG5vbmU6WyJzYW1lLWNhcHRpb24tc3VtbWFyeSJdfSx7aWQ6InRhYmxlLWZha2UtY2FwdGlvbiIsc2VsZWN0b3I6InRhYmxlIixtYXRjaGVzOmZ1bmN0aW9uKGEpe3JldHVybiBheGUuY29tbW9ucy50YWJsZS5pc0RhdGFUYWJsZShhKX0sdGFnczpbImV4cGVyaW1lbnRhbCIsIndjYWcyYSIsIndjYWcxMzEiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOC4yMi5nIl0sYWxsOlsiY2FwdGlvbi1mYWtlZCJdLGFueTpbXSxub25lOltdfSx7aWQ6InRkLWhhcy1oZWFkZXIiLHNlbGVjdG9yOiJ0YWJsZSIsbWF0Y2hlczpmdW5jdGlvbihhKXtpZihheGUuY29tbW9ucy50YWJsZS5pc0RhdGFUYWJsZShhKSl7dmFyIGI9YXhlLmNvbW1vbnMudGFibGUudG9BcnJheShhKTtyZXR1cm4gYi5sZW5ndGg+PTMmJmJbMF0ubGVuZ3RoPj0zJiZiWzFdLmxlbmd0aD49MyYmYlsyXS5sZW5ndGg+PTN9cmV0dXJuITF9LHRhZ3M6WyJleHBlcmltZW50YWwiLCJ3Y2FnMmEiLCJ3Y2FnMTMxIiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDguMjIuZyJdLGFsbDpbInRkLWhhcy1oZWFkZXIiXSxhbnk6W10sbm9uZTpbXX0se2lkOiJ0ZC1oZWFkZXJzLWF0dHIiLHNlbGVjdG9yOiJ0YWJsZSIsdGFnczpbIndjYWcyYSIsIndjYWcxMzEiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOC4yMi5nIl0sYWxsOlsidGQtaGVhZGVycy1hdHRyIl0sYW55OltdLG5vbmU6W119LHtpZDoidGgtaGFzLWRhdGEtY2VsbHMiLHNlbGVjdG9yOiJ0YWJsZSIsbWF0Y2hlczpmdW5jdGlvbihhKXtyZXR1cm4gYXhlLmNvbW1vbnMudGFibGUuaXNEYXRhVGFibGUoYSl9LHRhZ3M6WyJ3Y2FnMmEiLCJ3Y2FnMTMxIiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDguMjIuZyJdLGFsbDpbInRoLWhhcy1kYXRhLWNlbGxzIl0sYW55OltdLG5vbmU6W119LHtpZDoidmFsaWQtbGFuZyIsc2VsZWN0b3I6IltsYW5nXTpub3QoaHRtbCksIFt4bWxcXDpsYW5nXTpub3QoaHRtbCkiLHRhZ3M6WyJ3Y2FnMmFhIiwid2NhZzMxMiJdLGFsbDpbXSxhbnk6W10sbm9uZTpbe29wdGlvbnM6WyJhYSIsImFiIiwiYWUiLCJhZiIsImFrIiwiYW0iLCJhbiIsImFyIiwiYXMiLCJhdiIsImF5IiwiYXoiLCJiYSIsImJlIiwiYmciLCJiaCIsImJpIiwiYm0iLCJibiIsImJvIiwiYnIiLCJicyIsImNhIiwiY2UiLCJjaCIsImNvIiwiY3IiLCJjcyIsImN1IiwiY3YiLCJjeSIsImRhIiwiZGUiLCJkdiIsImR6IiwiZWUiLCJlbCIsImVuIiwiZW8iLCJlcyIsImV0IiwiZXUiLCJmYSIsImZmIiwiZmkiLCJmaiIsImZvIiwiZnIiLCJmeSIsImdhIiwiZ2QiLCJnbCIsImduIiwiZ3UiLCJndiIsImhhIiwiaGUiLCJoaSIsImhvIiwiaHIiLCJodCIsImh1IiwiaHkiLCJoeiIsImlhIiwiaWQiLCJpZSIsImlnIiwiaWkiLCJpayIsImluIiwiaW8iLCJpcyIsIml0IiwiaXUiLCJpdyIsImphIiwiamkiLCJqdiIsImp3Iiwia2EiLCJrZyIsImtpIiwia2oiLCJrayIsImtsIiwia20iLCJrbiIsImtvIiwia3IiLCJrcyIsImt1Iiwia3YiLCJrdyIsImt5IiwibGEiLCJsYiIsImxnIiwibGkiLCJsbiIsImxvIiwibHQiLCJsdSIsImx2IiwibWciLCJtaCIsIm1pIiwibWsiLCJtbCIsIm1uIiwibW8iLCJtciIsIm1zIiwibXQiLCJteSIsIm5hIiwibmIiLCJuZCIsIm5lIiwibmciLCJubCIsIm5uIiwibm8iLCJuciIsIm52IiwibnkiLCJvYyIsIm9qIiwib20iLCJvciIsIm9zIiwicGEiLCJwaSIsInBsIiwicHMiLCJwdCIsInF1Iiwicm0iLCJybiIsInJvIiwicnUiLCJydyIsInNhIiwic2MiLCJzZCIsInNlIiwic2ciLCJzaCIsInNpIiwic2siLCJzbCIsInNtIiwic24iLCJzbyIsInNxIiwic3IiLCJzcyIsInN0Iiwic3UiLCJzdiIsInN3IiwidGEiLCJ0ZSIsInRnIiwidGgiLCJ0aSIsInRrIiwidGwiLCJ0biIsInRvIiwidHIiLCJ0cyIsInR0IiwidHciLCJ0eSIsInVnIiwidWsiLCJ1ciIsInV6IiwidmUiLCJ2aSIsInZvIiwid2EiLCJ3byIsInhoIiwieWkiLCJ5byIsInphIiwiemgiLCJ6dSJdLGlkOiJ2YWxpZC1sYW5nIn1dfSx7aWQ6InZpZGVvLWNhcHRpb24iLHNlbGVjdG9yOiJ2aWRlbyIsZXhjbHVkZUhpZGRlbjohMSx0YWdzOlsid2NhZzJhIiwid2NhZzEyMiIsIndjYWcxMjMiLCJzZWN0aW9uNTA4Iiwic2VjdGlvbjUwOC4yMi5hIl0sYWxsOltdLGFueTpbXSxub25lOlsiY2FwdGlvbiJdfSx7aWQ6InZpZGVvLWRlc2NyaXB0aW9uIixzZWxlY3RvcjoidmlkZW8iLGV4Y2x1ZGVIaWRkZW46ITEsdGFnczpbIndjYWcyYWEiLCJ3Y2FnMTI1Iiwic2VjdGlvbjUwOCIsInNlY3Rpb241MDguMjIuYiJdLGFsbDpbXSxhbnk6W10sbm9uZTpbImRlc2NyaXB0aW9uIl19XSxjaGVja3M6W3tpZDoiYWJzdHJhY3Ryb2xlIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiJhYnN0cmFjdCI9PT1heGUuY29tbW9ucy5hcmlhLmdldFJvbGVUeXBlKGEuZ2V0QXR0cmlidXRlKCJyb2xlIikpfX0se2lkOiJhcmlhLWFsbG93ZWQtYXR0ciIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYyxkLGUsZj1bXSxnPWEuZ2V0QXR0cmlidXRlKCJyb2xlIiksaD1hLmF0dHJpYnV0ZXM7aWYoZ3x8KGc9YXhlLmNvbW1vbnMuYXJpYS5pbXBsaWNpdFJvbGUoYSkpLGU9YXhlLmNvbW1vbnMuYXJpYS5hbGxvd2VkQXR0cihnKSxnJiZlKWZvcih2YXIgaT0wLGo9aC5sZW5ndGg7aTxqO2krKyljPWhbaV0sZD1jLm5hbWUsYXhlLmNvbW1vbnMuYXJpYS52YWxpZGF0ZUF0dHIoZCkmJmUuaW5kZXhPZihkKT09PS0xJiZmLnB1c2goZCsnPSInK2Mubm9kZVZhbHVlKyciJyk7cmV0dXJuIWYubGVuZ3RofHwodGhpcy5kYXRhKGYpLCExKX19LHtpZDoiaW52YWxpZHJvbGUiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuIWF4ZS5jb21tb25zLmFyaWEuaXNWYWxpZFJvbGUoYS5nZXRBdHRyaWJ1dGUoInJvbGUiKSl9fSx7aWQ6ImFyaWEtcmVxdWlyZWQtYXR0ciIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1bXTtpZihhLmhhc0F0dHJpYnV0ZXMoKSl7dmFyIGQsZT1hLmdldEF0dHJpYnV0ZSgicm9sZSIpLGY9YXhlLmNvbW1vbnMuYXJpYS5yZXF1aXJlZEF0dHIoZSk7aWYoZSYmZilmb3IodmFyIGc9MCxoPWYubGVuZ3RoO2c8aDtnKyspZD1mW2ddLGEuZ2V0QXR0cmlidXRlKGQpfHxjLnB1c2goZCl9cmV0dXJuIWMubGVuZ3RofHwodGhpcy5kYXRhKGMpLCExKX19LHtpZDoiYXJpYS1yZXF1aXJlZC1jaGlsZHJlbiIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtmdW5jdGlvbiBjKGEsYixjKXtpZihudWxsPT09YSlyZXR1cm4hMTt2YXIgZD1nKGIpLGU9Wydbcm9sZT0iJytiKyciXSddO3JldHVybiBkJiYoZT1lLmNvbmNhdChkKSksZT1lLmpvaW4oIiwiKSxjP2goYSxlKXx8ISFhLnF1ZXJ5U2VsZWN0b3IoZSk6ISFhLnF1ZXJ5U2VsZWN0b3IoZSl9ZnVuY3Rpb24gZChhLGIpe3ZhciBkLGU7Zm9yKGQ9MCxlPWEubGVuZ3RoO2Q8ZTtkKyspaWYobnVsbCE9PWFbZF0mJmMoYVtkXSxiLCEwKSlyZXR1cm4hMDtyZXR1cm4hMX1mdW5jdGlvbiBlKGEsYixlKXt2YXIgZixnPWIubGVuZ3RoLGg9W10saj1pKGEsImFyaWEtb3ducyIpO2ZvcihmPTA7ZjxnO2YrKyl7dmFyIGs9YltmXTtpZihjKGEsayl8fGQoaixrKSl7aWYoIWUpcmV0dXJuIG51bGx9ZWxzZSBlJiZoLnB1c2goayl9cmV0dXJuIGgubGVuZ3RoP2g6IWUmJmIubGVuZ3RoP2I6bnVsbH12YXIgZj1heGUuY29tbW9ucy5hcmlhLnJlcXVpcmVkT3duZWQsZz1heGUuY29tbW9ucy5hcmlhLmltcGxpY2l0Tm9kZXMsaD1heGUuY29tbW9ucy51dGlscy5tYXRjaGVzU2VsZWN0b3IsaT1heGUuY29tbW9ucy5kb20uaWRyZWZzLGo9YS5nZXRBdHRyaWJ1dGUoInJvbGUiKSxrPWYoaik7aWYoIWspcmV0dXJuITA7dmFyIGw9ITEsbT1rLm9uZTtpZighbSl7dmFyIGw9ITA7bT1rLmFsbH12YXIgbj1lKGEsbSxsKTtyZXR1cm4hbnx8KHRoaXMuZGF0YShuKSwhMSl9fSx7aWQ6ImFyaWEtcmVxdWlyZWQtcGFyZW50IixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2Z1bmN0aW9uIGMoYSl7dmFyIGI9YXhlLmNvbW1vbnMuYXJpYS5pbXBsaWNpdE5vZGVzKGEpfHxbXTtyZXR1cm4gYi5jb25jYXQoJ1tyb2xlPSInK2ErJyJdJykuam9pbigiLCIpfWZ1bmN0aW9uIGQoYSxiLGQpe3ZhciBlLGYsZz1hLmdldEF0dHJpYnV0ZSgicm9sZSIpLGg9W107aWYoYnx8KGI9YXhlLmNvbW1vbnMuYXJpYS5yZXF1aXJlZENvbnRleHQoZykpLCFiKXJldHVybiBudWxsO2ZvcihlPTAsZj1iLmxlbmd0aDtlPGY7ZSsrKXtpZihkJiZheGUudXRpbHMubWF0Y2hlc1NlbGVjdG9yKGEsYyhiW2VdKSkpcmV0dXJuIG51bGw7aWYoYXhlLmNvbW1vbnMuZG9tLmZpbmRVcChhLGMoYltlXSkpKXJldHVybiBudWxsO2gucHVzaChiW2VdKX1yZXR1cm4gaH1mdW5jdGlvbiBlKGEpe2Zvcih2YXIgYj1bXSxjPW51bGw7YTspYS5pZCYmKGM9ZG9jdW1lbnQucXVlcnlTZWxlY3RvcigiW2FyaWEtb3duc349IitheGUuY29tbW9ucy51dGlscy5lc2NhcGVTZWxlY3RvcihhLmlkKSsiXSIpLGMmJmIucHVzaChjKSksYT1hLnBhcmVudE5vZGU7cmV0dXJuIGIubGVuZ3RoP2I6bnVsbH12YXIgZj1kKGEpO2lmKCFmKXJldHVybiEwO3ZhciBnPWUoYSk7aWYoZylmb3IodmFyIGg9MCxpPWcubGVuZ3RoO2g8aTtoKyspaWYoZj1kKGdbaF0sZiwhMCksIWYpcmV0dXJuITA7cmV0dXJuIHRoaXMuZGF0YShmKSwhMX19LHtpZDoiYXJpYS12YWxpZC1hdHRyLXZhbHVlIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2I9QXJyYXkuaXNBcnJheShiKT9iOltdO2Zvcih2YXIgYyxkLGU9W10sZj0vXmFyaWEtLyxnPWEuYXR0cmlidXRlcyxoPTAsaT1nLmxlbmd0aDtoPGk7aCsrKWM9Z1toXSxkPWMubmFtZSxiLmluZGV4T2YoZCk9PT0tMSYmZi50ZXN0KGQpJiYhYXhlLmNvbW1vbnMuYXJpYS52YWxpZGF0ZUF0dHJWYWx1ZShhLGQpJiZlLnB1c2goZCsnPSInK2Mubm9kZVZhbHVlKyciJyk7cmV0dXJuIWUubGVuZ3RofHwodGhpcy5kYXRhKGUpLCExKX0sb3B0aW9uczpbXX0se2lkOiJhcmlhLXZhbGlkLWF0dHIiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7Yj1BcnJheS5pc0FycmF5KGIpP2I6W107Zm9yKHZhciBjLGQ9W10sZT0vXmFyaWEtLyxmPWEuYXR0cmlidXRlcyxnPTAsaD1mLmxlbmd0aDtnPGg7ZysrKWM9ZltnXS5uYW1lLGIuaW5kZXhPZihjKT09PS0xJiZlLnRlc3QoYykmJiFheGUuY29tbW9ucy5hcmlhLnZhbGlkYXRlQXR0cihjKSYmZC5wdXNoKGMpO3JldHVybiFkLmxlbmd0aHx8KHRoaXMuZGF0YShkKSwhMSl9LG9wdGlvbnM6W119LHtpZDoiY29sb3ItY29udHJhc3QiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7aWYoIWF4ZS5jb21tb25zLmRvbS5pc1Zpc2libGUoYSwhMSkpcmV0dXJuITA7dmFyIGM9ISEoYnx8e30pLm5vU2Nyb2xsLGQ9W10sZT1heGUuY29tbW9ucy5jb2xvci5nZXRCYWNrZ3JvdW5kQ29sb3IoYSxkLGMpLGY9YXhlLmNvbW1vbnMuY29sb3IuZ2V0Rm9yZWdyb3VuZENvbG9yKGEsYyk7aWYobnVsbCE9PWYmJm51bGwhPT1lKXt2YXIgZz13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShhKSxoPXBhcnNlRmxvYXQoZy5nZXRQcm9wZXJ0eVZhbHVlKCJmb250LXNpemUiKSksaT1nLmdldFByb3BlcnR5VmFsdWUoImZvbnQtd2VpZ2h0Iiksaj1bImJvbGQiLCJib2xkZXIiLCI2MDAiLCI3MDAiLCI4MDAiLCI5MDAiXS5pbmRleE9mKGkpIT09LTEsaz1heGUuY29tbW9ucy5jb2xvci5oYXNWYWxpZENvbnRyYXN0UmF0aW8oZSxmLGgsaik7cmV0dXJuIHRoaXMuZGF0YSh7ZmdDb2xvcjpmLnRvSGV4U3RyaW5nKCksYmdDb2xvcjplLnRvSGV4U3RyaW5nKCksY29udHJhc3RSYXRpbzprLmNvbnRyYXN0UmF0aW8udG9GaXhlZCgyKSxmb250U2l6ZTooNzIqaC85NikudG9GaXhlZCgxKSsicHQiLGZvbnRXZWlnaHQ6aj8iYm9sZCI6Im5vcm1hbCJ9KSxrLmlzVmFsaWR8fHRoaXMucmVsYXRlZE5vZGVzKGQpLGsuaXNWYWxpZH19fSx7aWQ6ImxpbmstaW4tdGV4dC1ibG9jayIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtmdW5jdGlvbiBjKGEsYil7dmFyIGM9YS5nZXRSZWxhdGl2ZUx1bWluYW5jZSgpLGQ9Yi5nZXRSZWxhdGl2ZUx1bWluYW5jZSgpO3JldHVybihNYXRoLm1heChjLGQpKy4wNSkvKE1hdGgubWluKGMsZCkrLjA1KX1mdW5jdGlvbiBkKGEpe3ZhciBiPXdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGEpLmdldFByb3BlcnR5VmFsdWUoImRpc3BsYXkiKTtyZXR1cm4gZi5pbmRleE9mKGIpIT09LTF8fCJ0YWJsZS0iPT09Yi5zdWJzdHIoMCw2KX12YXIgZT1heGUuY29tbW9ucy5jb2xvcixmPVsiYmxvY2siLCJsaXN0LWl0ZW0iLCJ0YWJsZSIsImZsZXgiLCJncmlkIiwiaW5saW5lLWJsb2NrIl07aWYoZChhKSlyZXR1cm4hMTtmb3IodmFyIGc9YS5wYXJlbnROb2RlOzE9PT1nLm5vZGVUeXBlJiYhZChnKTspZz1nLnBhcmVudE5vZGU7aWYoZS5lbGVtZW50SXNEaXN0aW5jdChhLGcpKXJldHVybiEwO3ZhciBoLGk7aWYoaD1lLmdldEZvcmVncm91bmRDb2xvcihhKSxpPWUuZ2V0Rm9yZWdyb3VuZENvbG9yKGcpLGgmJmkpe3ZhciBqPWMoaCxpKTtpZigxPT09ailyZXR1cm4hMDtpZighKGo+PTMpJiYoaD1lLmdldEJhY2tncm91bmRDb2xvcihhKSxpPWUuZ2V0QmFja2dyb3VuZENvbG9yKGcpLGgmJmkmJiEoYyhoLGkpPj0zKSkpcmV0dXJuITF9fX0se2lkOiJmaWVsZHNldCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtmdW5jdGlvbiBjKGEsYil7cmV0dXJuIGF4ZS5jb21tb25zLnV0aWxzLnRvQXJyYXkoYS5xdWVyeVNlbGVjdG9yQWxsKCdzZWxlY3QsdGV4dGFyZWEsYnV0dG9uLGlucHV0Om5vdChbbmFtZT0iJytiKyciXSk6bm90KFt0eXBlPSJoaWRkZW4iXSknKSl9ZnVuY3Rpb24gZChhLGIpe3ZhciBkPWEuZmlyc3RFbGVtZW50Q2hpbGQ7aWYoIWR8fCJMRUdFTkQiIT09ZC5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpKXJldHVybiBpLnJlbGF0ZWROb2RlcyhbYV0pLGg9Im5vLWxlZ2VuZCIsITE7aWYoIWF4ZS5jb21tb25zLnRleHQuYWNjZXNzaWJsZVRleHQoZCkpcmV0dXJuIGkucmVsYXRlZE5vZGVzKFtkXSksaD0iZW1wdHktbGVnZW5kIiwhMTt2YXIgZT1jKGEsYik7cmV0dXJuIWUubGVuZ3RofHwoaS5yZWxhdGVkTm9kZXMoZSksaD0ibWl4ZWQtaW5wdXRzIiwhMSl9ZnVuY3Rpb24gZShhLGIpe3ZhciBkPWF4ZS5jb21tb25zLmRvbS5pZHJlZnMoYSwiYXJpYS1sYWJlbGxlZGJ5Iikuc29tZShmdW5jdGlvbihhKXtyZXR1cm4gYSYmYXhlLmNvbW1vbnMudGV4dC5hY2Nlc3NpYmxlVGV4dChhKX0pLGU9YS5nZXRBdHRyaWJ1dGUoImFyaWEtbGFiZWwiKTtpZighKGR8fGUmJmF4ZS5jb21tb25zLnRleHQuc2FuaXRpemUoZSkpKXJldHVybiBpLnJlbGF0ZWROb2RlcyhhKSxoPSJuby1ncm91cC1sYWJlbCIsITE7dmFyIGY9YyhhLGIpO3JldHVybiFmLmxlbmd0aHx8KGkucmVsYXRlZE5vZGVzKGYpLGg9Imdyb3VwLW1peGVkLWlucHV0cyIsITEpfWZ1bmN0aW9uIGYoYSxiKXtyZXR1cm4gYXhlLmNvbW1vbnMudXRpbHMudG9BcnJheShhKS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuIGEhPT1ifSl9ZnVuY3Rpb24gZyhiKXt2YXIgYz1heGUuY29tbW9ucy51dGlscy5lc2NhcGVTZWxlY3RvcihhLm5hbWUpLGc9ZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnaW5wdXRbdHlwZT0iJytheGUuY29tbW9ucy51dGlscy5lc2NhcGVTZWxlY3RvcihhLnR5cGUpKyciXVtuYW1lPSInK2MrJyJdJyk7aWYoZy5sZW5ndGg8MilyZXR1cm4hMDt2YXIgaj1heGUuY29tbW9ucy5kb20uZmluZFVwKGIsImZpZWxkc2V0Iiksaz1heGUuY29tbW9ucy5kb20uZmluZFVwKGIsJ1tyb2xlPSJncm91cCJdJysoInJhZGlvIj09PWEudHlwZT8nLFtyb2xlPSJyYWRpb2dyb3VwIl0nOiIiKSk7cmV0dXJuIGt8fGo/aj9kKGosYyk6ZShrLGMpOihoPSJuby1ncm91cCIsaS5yZWxhdGVkTm9kZXMoZihnLGIpKSwhMSl9dmFyIGgsaT10aGlzLGo9e25hbWU6YS5nZXRBdHRyaWJ1dGUoIm5hbWUiKSx0eXBlOmEuZ2V0QXR0cmlidXRlKCJ0eXBlIil9LGs9ZyhhKTtyZXR1cm4ga3x8KGouZmFpbHVyZUNvZGU9aCksdGhpcy5kYXRhKGopLGt9LGFmdGVyOmZ1bmN0aW9uKGEsYil7dmFyIGM9e307cmV0dXJuIGEuZmlsdGVyKGZ1bmN0aW9uKGEpe2lmKGEucmVzdWx0KXJldHVybiEwO3ZhciBiPWEuZGF0YTtpZihiKXtpZihjW2IudHlwZV09Y1tiLnR5cGVdfHx7fSwhY1tiLnR5cGVdW2IubmFtZV0pcmV0dXJuIGNbYi50eXBlXVtiLm5hbWVdPVtiXSwhMDt2YXIgZD1jW2IudHlwZV1bYi5uYW1lXS5zb21lKGZ1bmN0aW9uKGEpe3JldHVybiBhLmZhaWx1cmVDb2RlPT09Yi5mYWlsdXJlQ29kZX0pO3JldHVybiBkfHxjW2IudHlwZV1bYi5uYW1lXS5wdXNoKGIpLCFkfXJldHVybiExfSl9fSx7aWQ6Imdyb3VwLWxhYmVsbGVkYnkiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dGhpcy5kYXRhKHtuYW1lOmEuZ2V0QXR0cmlidXRlKCJuYW1lIiksdHlwZTphLmdldEF0dHJpYnV0ZSgidHlwZSIpfSk7dmFyIGM9ZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnaW5wdXRbdHlwZT0iJytheGUuY29tbW9ucy51dGlscy5lc2NhcGVTZWxlY3RvcihhLnR5cGUpKyciXVtuYW1lPSInK2F4ZS5jb21tb25zLnV0aWxzLmVzY2FwZVNlbGVjdG9yKGEubmFtZSkrJyJdJyk7cmV0dXJuIGMubGVuZ3RoPD0xfHwwIT09W10ubWFwLmNhbGwoYyxmdW5jdGlvbihhKXt2YXIgYj1hLmdldEF0dHJpYnV0ZSgiYXJpYS1sYWJlbGxlZGJ5Iik7cmV0dXJuIGI/Yi5zcGxpdCgvXHMrLyk6W119KS5yZWR1Y2UoZnVuY3Rpb24oYSxiKXtyZXR1cm4gYS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuIGIuaW5kZXhPZihhKSE9PS0xfSl9KS5maWx0ZXIoZnVuY3Rpb24oYSl7dmFyIGI9ZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoYSk7cmV0dXJuIGImJmF4ZS5jb21tb25zLnRleHQuYWNjZXNzaWJsZVRleHQoYil9KS5sZW5ndGh9LGFmdGVyOmZ1bmN0aW9uKGEsYil7dmFyIGM9e307cmV0dXJuIGEuZmlsdGVyKGZ1bmN0aW9uKGEpe3ZhciBiPWEuZGF0YTtyZXR1cm4hKCFifHwoY1tiLnR5cGVdPWNbYi50eXBlXXx8e30sY1tiLnR5cGVdW2IubmFtZV0pKSYmKGNbYi50eXBlXVtiLm5hbWVdPSEwLCEwKX0pfX0se2lkOiJhY2Nlc3NrZXlzIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiBheGUuY29tbW9ucy5kb20uaXNWaXNpYmxlKGEsITEpJiYodGhpcy5kYXRhKGEuZ2V0QXR0cmlidXRlKCJhY2Nlc3NrZXkiKSksdGhpcy5yZWxhdGVkTm9kZXMoW2FdKSksITB9LGFmdGVyOmZ1bmN0aW9uKGEsYil7dmFyIGM9e307cmV0dXJuIGEuZmlsdGVyKGZ1bmN0aW9uKGEpe2lmKCFhLmRhdGEpcmV0dXJuITE7dmFyIGI9YS5kYXRhLnRvVXBwZXJDYXNlKCk7cmV0dXJuIGNbYl0/KGNbYl0ucmVsYXRlZE5vZGVzLnB1c2goYS5yZWxhdGVkTm9kZXNbMF0pLCExKTooY1tiXT1hLGEucmVsYXRlZE5vZGVzPVtdLCEwKX0pLm1hcChmdW5jdGlvbihhKXtyZXR1cm4gYS5yZXN1bHQ9ISFhLnJlbGF0ZWROb2Rlcy5sZW5ndGgsYX0pfX0se2lkOiJmb2N1c2FibGUtbm8tbmFtZSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1hLmdldEF0dHJpYnV0ZSgidGFiaW5kZXgiKSxkPWF4ZS5jb21tb25zLmRvbS5pc0ZvY3VzYWJsZShhKSYmYz4tMTtyZXR1cm4hIWQmJiFheGUuY29tbW9ucy50ZXh0LmFjY2Vzc2libGVUZXh0KGEpfX0se2lkOiJ0YWJpbmRleCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4gYS50YWJJbmRleDw9MH19LHtpZDoiZHVwbGljYXRlLWltZy1sYWJlbCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1hLnF1ZXJ5U2VsZWN0b3JBbGwoImltZyIpLGQ9YXhlLmNvbW1vbnMudGV4dC52aXNpYmxlKGEsITApLnRvTG93ZXJDYXNlKCk7aWYoIiI9PT1kKXJldHVybiExO2Zvcih2YXIgZT0wLGY9Yy5sZW5ndGg7ZTxmO2UrKyl7dmFyIGc9Y1tlXSxoPWF4ZS5jb21tb25zLnRleHQuYWNjZXNzaWJsZVRleHQoZykudG9Mb3dlckNhc2UoKTtpZihoPT09ZCYmInByZXNlbnRhdGlvbiIhPT1nLmdldEF0dHJpYnV0ZSgicm9sZSIpJiZheGUuY29tbW9ucy5kb20uaXNWaXNpYmxlKGcpKXJldHVybiEwfXJldHVybiExfX0se2lkOiJleHBsaWNpdC1sYWJlbCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtpZihhLmlkKXt2YXIgYz1kb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdsYWJlbFtmb3I9IicrYXhlLmNvbW1vbnMudXRpbHMuZXNjYXBlU2VsZWN0b3IoYS5pZCkrJyJdJyk7aWYoYylyZXR1cm4hIWF4ZS5jb21tb25zLnRleHQuYWNjZXNzaWJsZVRleHQoYyl9cmV0dXJuITF9fSx7aWQ6ImhlbHAtc2FtZS1hcy1sYWJlbCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1heGUuY29tbW9ucy50ZXh0LmxhYmVsKGEpLGQ9YS5nZXRBdHRyaWJ1dGUoInRpdGxlIik7aWYoIWMpcmV0dXJuITE7aWYoIWQmJihkPSIiLGEuZ2V0QXR0cmlidXRlKCJhcmlhLWRlc2NyaWJlZGJ5IikpKXt2YXIgZT1heGUuY29tbW9ucy5kb20uaWRyZWZzKGEsImFyaWEtZGVzY3JpYmVkYnkiKTtkPWUubWFwKGZ1bmN0aW9uKGEpe3JldHVybiBhP2F4ZS5jb21tb25zLnRleHQuYWNjZXNzaWJsZVRleHQoYSk6IiJ9KS5qb2luKCIiKX1yZXR1cm4gYXhlLmNvbW1vbnMudGV4dC5zYW5pdGl6ZShkKT09PWF4ZS5jb21tb25zLnRleHQuc2FuaXRpemUoYyl9LGVuYWJsZWQ6ITF9LHtpZDoiaW1wbGljaXQtbGFiZWwiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGM9YXhlLmNvbW1vbnMuZG9tLmZpbmRVcChhLCJsYWJlbCIpO3JldHVybiEhYyYmISFheGUuY29tbW9ucy50ZXh0LmFjY2Vzc2libGVUZXh0KGMpfX0se2lkOiJtdWx0aXBsZS1sYWJlbCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtmb3IodmFyIGM9W10uc2xpY2UuY2FsbChkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCdsYWJlbFtmb3I9IicrYXhlLmNvbW1vbnMudXRpbHMuZXNjYXBlU2VsZWN0b3IoYS5pZCkrJyJdJykpLGQ9YS5wYXJlbnROb2RlO2Q7KSJMQUJFTCI9PT1kLnRhZ05hbWUmJmMuaW5kZXhPZihkKT09PS0xJiZjLnB1c2goZCksZD1kLnBhcmVudE5vZGU7cmV0dXJuIHRoaXMucmVsYXRlZE5vZGVzKGMpLGMubGVuZ3RoPjF9fSx7aWQ6InRpdGxlLW9ubHkiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGM9YXhlLmNvbW1vbnMudGV4dC5sYWJlbChhKTtyZXR1cm4hKGN8fCFhLmdldEF0dHJpYnV0ZSgidGl0bGUiKSYmIWEuZ2V0QXR0cmlidXRlKCJhcmlhLWRlc2NyaWJlZGJ5IikpfX0se2lkOiJoYXMtbGFuZyIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4hIShhLmdldEF0dHJpYnV0ZSgibGFuZyIpfHxhLmdldEF0dHJpYnV0ZSgieG1sOmxhbmciKXx8IiIpLnRyaW0oKX19LHtpZDoidmFsaWQtbGFuZyIsb3B0aW9uczpbImFhIiwiYWIiLCJhZSIsImFmIiwiYWsiLCJhbSIsImFuIiwiYXIiLCJhcyIsImF2IiwiYXkiLCJheiIsImJhIiwiYmUiLCJiZyIsImJoIiwiYmkiLCJibSIsImJuIiwiYm8iLCJiciIsImJzIiwiY2EiLCJjZSIsImNoIiwiY28iLCJjciIsImNzIiwiY3UiLCJjdiIsImN5IiwiZGEiLCJkZSIsImR2IiwiZHoiLCJlZSIsImVsIiwiZW4iLCJlbyIsImVzIiwiZXQiLCJldSIsImZhIiwiZmYiLCJmaSIsImZqIiwiZm8iLCJmciIsImZ5IiwiZ2EiLCJnZCIsImdsIiwiZ24iLCJndSIsImd2IiwiaGEiLCJoZSIsImhpIiwiaG8iLCJociIsImh0IiwiaHUiLCJoeSIsImh6IiwiaWEiLCJpZCIsImllIiwiaWciLCJpaSIsImlrIiwiaW4iLCJpbyIsImlzIiwiaXQiLCJpdSIsIml3IiwiamEiLCJqaSIsImp2IiwianciLCJrYSIsImtnIiwia2kiLCJraiIsImtrIiwia2wiLCJrbSIsImtuIiwia28iLCJrciIsImtzIiwia3UiLCJrdiIsImt3Iiwia3kiLCJsYSIsImxiIiwibGciLCJsaSIsImxuIiwibG8iLCJsdCIsImx1IiwibHYiLCJtZyIsIm1oIiwibWkiLCJtayIsIm1sIiwibW4iLCJtbyIsIm1yIiwibXMiLCJtdCIsIm15IiwibmEiLCJuYiIsIm5kIiwibmUiLCJuZyIsIm5sIiwibm4iLCJubyIsIm5yIiwibnYiLCJueSIsIm9jIiwib2oiLCJvbSIsIm9yIiwib3MiLCJwYSIsInBpIiwicGwiLCJwcyIsInB0IiwicXUiLCJybSIsInJuIiwicm8iLCJydSIsInJ3Iiwic2EiLCJzYyIsInNkIiwic2UiLCJzZyIsInNoIiwic2kiLCJzayIsInNsIiwic20iLCJzbiIsInNvIiwic3EiLCJzciIsInNzIiwic3QiLCJzdSIsInN2Iiwic3ciLCJ0YSIsInRlIiwidGciLCJ0aCIsInRpIiwidGsiLCJ0bCIsInRuIiwidG8iLCJ0ciIsInRzIiwidHQiLCJ0dyIsInR5IiwidWciLCJ1ayIsInVyIiwidXoiLCJ2ZSIsInZpIiwidm8iLCJ3YSIsIndvIiwieGgiLCJ5aSIsInlvIiwiemEiLCJ6aCIsInp1Il0sZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtmdW5jdGlvbiBjKGEpe3JldHVybiBhLnRyaW0oKS5zcGxpdCgiLSIpWzBdLnRvTG93ZXJDYXNlKCl9dmFyIGQsZTtyZXR1cm4gZD0oYnx8W10pLm1hcChjKSxlPVsibGFuZyIsInhtbDpsYW5nIl0ucmVkdWNlKGZ1bmN0aW9uKGIsZSl7dmFyIGY9YS5nZXRBdHRyaWJ1dGUoZSk7aWYoInN0cmluZyIhPXR5cGVvZiBmKXJldHVybiBiO3ZhciBnPWMoZik7cmV0dXJuIiIhPT1nJiZkLmluZGV4T2YoZyk9PT0tMSYmYi5wdXNoKGUrJz0iJythLmdldEF0dHJpYnV0ZShlKSsnIicpLGJ9LFtdKSwhIWUubGVuZ3RoJiYodGhpcy5kYXRhKGUpLCEwKX19LHtpZDoiZGxpdGVtIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiJETCI9PT1hLnBhcmVudE5vZGUudGFnTmFtZX19LHtpZDoiaGFzLWxpc3RpdGVtIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuY2hpbGRyZW47aWYoMD09PWMubGVuZ3RoKXJldHVybiEwO2Zvcih2YXIgZD0wO2Q8Yy5sZW5ndGg7ZCsrKWlmKCJMSSI9PT1jW2RdLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCkpcmV0dXJuITE7cmV0dXJuITB9fSx7aWQ6Imxpc3RpdGVtIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVyblsiVUwiLCJPTCJdLmluZGV4T2YoYS5wYXJlbnROb2RlLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCkpIT09LTF8fCJsaXN0Ij09PWEucGFyZW50Tm9kZS5nZXRBdHRyaWJ1dGUoInJvbGUiKX19LHtpZDoib25seS1kbGl0ZW1zIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2Zvcih2YXIgYyxkLGU9W10sZj1hLmNoaWxkTm9kZXMsZz1bIlNUWUxFIiwiTUVUQSIsIkxJTksiLCJNQVAiLCJBUkVBIiwiU0NSSVBUIiwiREFUQUxJU1QiLCJURU1QTEFURSJdLGg9ITEsaT0wO2k8Zi5sZW5ndGg7aSsrKXtjPWZbaV07dmFyIGQ9Yy5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpOzE9PT1jLm5vZGVUeXBlJiYiRFQiIT09ZCYmIkREIiE9PWQmJmcuaW5kZXhPZihkKT09PS0xP2UucHVzaChjKTozPT09Yy5ub2RlVHlwZSYmIiIhPT1jLm5vZGVWYWx1ZS50cmltKCkmJihoPSEwKX1lLmxlbmd0aCYmdGhpcy5yZWxhdGVkTm9kZXMoZSk7dmFyIGo9ISFlLmxlbmd0aHx8aDtyZXR1cm4gan19LHtpZDoib25seS1saXN0aXRlbXMiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7Zm9yKHZhciBjLGQsZT1bXSxmPWEuY2hpbGROb2RlcyxnPVsiU1RZTEUiLCJNRVRBIiwiTElOSyIsIk1BUCIsIkFSRUEiLCJTQ1JJUFQiLCJEQVRBTElTVCIsIlRFTVBMQVRFIl0saD0hMSxpPTA7aTxmLmxlbmd0aDtpKyspYz1mW2ldLGQ9Yy5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpLDE9PT1jLm5vZGVUeXBlJiYiTEkiIT09ZCYmZy5pbmRleE9mKGQpPT09LTE/ZS5wdXNoKGMpOjM9PT1jLm5vZGVUeXBlJiYiIiE9PWMubm9kZVZhbHVlLnRyaW0oKSYmKGg9ITApO3JldHVybiBlLmxlbmd0aCYmdGhpcy5yZWxhdGVkTm9kZXMoZSksISFlLmxlbmd0aHx8aH19LHtpZDoic3RydWN0dXJlZC1kbGl0ZW1zIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuY2hpbGRyZW47aWYoIWN8fCFjLmxlbmd0aClyZXR1cm4hMTtmb3IodmFyIGQsZT0hMSxmPSExLGc9MDtnPGMubGVuZ3RoO2crKyl7aWYoZD1jW2ddLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCksIkRUIj09PWQmJihlPSEwKSxlJiYiREQiPT09ZClyZXR1cm4hMTsiREQiPT09ZCYmKGY9ITApfXJldHVybiBlfHxmfX0se2lkOiJjYXB0aW9uIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiFhLnF1ZXJ5U2VsZWN0b3IoInRyYWNrW2tpbmQ9Y2FwdGlvbnNdIil9fSx7aWQ6ImRlc2NyaXB0aW9uIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiFhLnF1ZXJ5U2VsZWN0b3IoInRyYWNrW2tpbmQ9ZGVzY3JpcHRpb25zXSIpfX0se2lkOiJtZXRhLXZpZXdwb3J0LWxhcmdlIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2I9Ynx8e307Zm9yKHZhciBjLGQ9YS5nZXRBdHRyaWJ1dGUoImNvbnRlbnQiKXx8IiIsZT1kLnNwbGl0KC9bOyxdLyksZj17fSxnPWIuc2NhbGVNaW5pbXVtfHwyLGg9Yi5sb3dlckJvdW5kfHwhMSxpPTAsaj1lLmxlbmd0aDtpPGo7aSsrKXtjPWVbaV0uc3BsaXQoIj0iKTt2YXIgaz1jLnNoaWZ0KCkudG9Mb3dlckNhc2UoKTtrJiZjLmxlbmd0aCYmKGZbay50cmltKCldPWMuc2hpZnQoKS50cmltKCkudG9Mb3dlckNhc2UoKSl9cmV0dXJuISEoaCYmZlsibWF4aW11bS1zY2FsZSJdJiZwYXJzZUZsb2F0KGZbIm1heGltdW0tc2NhbGUiXSk8aCl8fCEoIWgmJiJubyI9PT1mWyJ1c2VyLXNjYWxhYmxlIl0pJiYhKGZbIm1heGltdW0tc2NhbGUiXSYmcGFyc2VGbG9hdChmWyJtYXhpbXVtLXNjYWxlIl0pPGcpfSxvcHRpb25zOntzY2FsZU1pbmltdW06NSxsb3dlckJvdW5kOjJ9fSx7aWQ6Im1ldGEtdmlld3BvcnQiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7Yj1ifHx7fTtmb3IodmFyIGMsZD1hLmdldEF0dHJpYnV0ZSgiY29udGVudCIpfHwiIixlPWQuc3BsaXQoL1s7LF0vKSxmPXt9LGc9Yi5zY2FsZU1pbmltdW18fDIsaD1iLmxvd2VyQm91bmR8fCExLGk9MCxqPWUubGVuZ3RoO2k8ajtpKyspe2M9ZVtpXS5zcGxpdCgiPSIpO3ZhciBrPWMuc2hpZnQoKS50b0xvd2VyQ2FzZSgpO2smJmMubGVuZ3RoJiYoZltrLnRyaW0oKV09Yy5zaGlmdCgpLnRyaW0oKS50b0xvd2VyQ2FzZSgpKX1yZXR1cm4hIShoJiZmWyJtYXhpbXVtLXNjYWxlIl0mJnBhcnNlRmxvYXQoZlsibWF4aW11bS1zY2FsZSJdKTxoKXx8ISghaCYmIm5vIj09PWZbInVzZXItc2NhbGFibGUiXSkmJiEoZlsibWF4aW11bS1zY2FsZSJdJiZwYXJzZUZsb2F0KGZbIm1heGltdW0tc2NhbGUiXSk8Zyl9LG9wdGlvbnM6e3NjYWxlTWluaW11bToyfX0se2lkOiJoZWFkZXItcHJlc2VudCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4hIWEucXVlcnlTZWxlY3RvcignaDEsIGgyLCBoMywgaDQsIGg1LCBoNiwgW3JvbGU9ImhlYWRpbmciXScpfX0se2lkOiJoZWFkaW5nLW9yZGVyIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJhcmlhLWxldmVsIik7aWYobnVsbCE9PWMpcmV0dXJuIHRoaXMuZGF0YShwYXJzZUludChjLDEwKSksITA7dmFyIGQ9YS50YWdOYW1lLm1hdGNoKC9IKFxkKS8pO3JldHVybiFkfHwodGhpcy5kYXRhKHBhcnNlSW50KGRbMV0sMTApKSwhMCl9LGFmdGVyOmZ1bmN0aW9uKGEsYil7aWYoYS5sZW5ndGg8MilyZXR1cm4gYTtmb3IodmFyIGM9YVswXS5kYXRhLGQ9MTtkPGEubGVuZ3RoO2QrKylhW2RdLnJlc3VsdCYmYVtkXS5kYXRhPmMrMSYmKGFbZF0ucmVzdWx0PSExKSxjPWFbZF0uZGF0YTtyZXR1cm4gYX19LHtpZDoiaHJlZi1uby1oYXNoIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJocmVmIik7cmV0dXJuIiMiIT09Y319LHtpZDoiaW50ZXJuYWwtbGluay1wcmVzZW50IixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiEhYS5xdWVyeVNlbGVjdG9yKCdhW2hyZWZePSIjIl0nKX19LHtpZDoibGFuZG1hcmsiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuIGEuZ2V0RWxlbWVudHNCeVRhZ05hbWUoIm1haW4iKS5sZW5ndGg+MHx8ISFhLnF1ZXJ5U2VsZWN0b3IoJ1tyb2xlPSJtYWluIl0nKX19LHtpZDoibWV0YS1yZWZyZXNoIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJjb250ZW50Iil8fCIiLGQ9Yy5zcGxpdCgvWzssXS8pO3JldHVybiIiPT09Y3x8IjAiPT09ZFswXX19LHtpZDoicmVnaW9uIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2Z1bmN0aW9uIGMoYSl7cmV0dXJuIGgmJmF4ZS5jb21tb25zLmRvbS5pc0ZvY3VzYWJsZShheGUuY29tbW9ucy5kb20uZ2V0RWxlbWVudEJ5UmVmZXJlbmNlKGgsImhyZWYiKSkmJmg9PT1hfWZ1bmN0aW9uIGQoYSl7dmFyIGI9YS5nZXRBdHRyaWJ1dGUoInJvbGUiKTtyZXR1cm4gYiYmZy5pbmRleE9mKGIpIT09LTF9ZnVuY3Rpb24gZShhKXtyZXR1cm4gZChhKT9udWxsOmMoYSk/ZihhKTpheGUuY29tbW9ucy5kb20uaXNWaXNpYmxlKGEsITApJiYoYXhlLmNvbW1vbnMudGV4dC52aXNpYmxlKGEsITAsITApfHxheGUuY29tbW9ucy5kb20uaXNWaXN1YWxDb250ZW50KGEpKT9hOmYoYSl9ZnVuY3Rpb24gZihhKXt2YXIgYj1heGUuY29tbW9ucy51dGlscy50b0FycmF5KGEuY2hpbGRyZW4pO3JldHVybiAwPT09Yi5sZW5ndGg/W106Yi5tYXAoZSkuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiBudWxsIT09YX0pLnJlZHVjZShmdW5jdGlvbihhLGIpe3JldHVybiBhLmNvbmNhdChiKX0sW10pfXZhciBnPWF4ZS5jb21tb25zLmFyaWEuZ2V0Um9sZXNCeVR5cGUoImxhbmRtYXJrIiksaD1hLnF1ZXJ5U2VsZWN0b3IoImFbaHJlZl0iKSxpPWYoYSk7cmV0dXJuIHRoaXMucmVsYXRlZE5vZGVzKGkpLCFpLmxlbmd0aH0sYWZ0ZXI6ZnVuY3Rpb24oYSxiKXtyZXR1cm5bYVswXV19fSx7aWQ6InNraXAtbGluayIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4gYXhlLmNvbW1vbnMuZG9tLmlzRm9jdXNhYmxlKGF4ZS5jb21tb25zLmRvbS5nZXRFbGVtZW50QnlSZWZlcmVuY2UoYSwiaHJlZiIpKX0sYWZ0ZXI6ZnVuY3Rpb24oYSxiKXtyZXR1cm5bYVswXV19fSx7aWQ6InVuaXF1ZS1mcmFtZS10aXRsZSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1heGUuY29tbW9ucy50ZXh0LnNhbml0aXplKGEudGl0bGUpLnRyaW0oKS50b0xvd2VyQ2FzZSgpO3JldHVybiB0aGlzLmRhdGEoYyksITB9LGFmdGVyOmZ1bmN0aW9uKGEsYil7dmFyIGM9e307cmV0dXJuIGEuZm9yRWFjaChmdW5jdGlvbihhKXtjW2EuZGF0YV09dm9pZCAwIT09Y1thLmRhdGFdPysrY1thLmRhdGFdOjB9KSxhLmZvckVhY2goZnVuY3Rpb24oYSl7YS5yZXN1bHQ9ISFjW2EuZGF0YV19KSxhfX0se2lkOiJhcmlhLWxhYmVsIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJhcmlhLWxhYmVsIik7cmV0dXJuISEoYz9heGUuY29tbW9ucy50ZXh0LnNhbml0aXplKGMpLnRyaW0oKToiIil9fSx7aWQ6ImFyaWEtbGFiZWxsZWRieSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1heGUuY29tbW9ucy5kb20uaWRyZWZzO3JldHVybiBjKGEsImFyaWEtbGFiZWxsZWRieSIpLnNvbWUoZnVuY3Rpb24oYSl7cmV0dXJuIGEmJmF4ZS5jb21tb25zLnRleHQuYWNjZXNzaWJsZVRleHQoYSwhMCl9KX19LHtpZDoiYnV0dG9uLWhhcy12aXNpYmxlLXRleHQiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGM9YS5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpLGQ9YS5nZXRBdHRyaWJ1dGUoInJvbGUiKSxlPXZvaWQgMDtyZXR1cm4oIkJVVFRPTiI9PT1jfHwiYnV0dG9uIj09PWQmJiJJTlBVVCIhPT1jKSYmKGU9YXhlLmNvbW1vbnMudGV4dC5hY2Nlc3NpYmxlVGV4dChhKSx0aGlzLmRhdGEoZSksISFlKX19LHtpZDoiZG9jLWhhcy10aXRsZSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1kb2N1bWVudC50aXRsZTtyZXR1cm4hIShjP2F4ZS5jb21tb25zLnRleHQuc2FuaXRpemUoYykudHJpbSgpOiIiKX19LHtpZDoiZHVwbGljYXRlLWlkIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe2lmKCFhLmlkLnRyaW0oKSlyZXR1cm4hMDtmb3IodmFyIGM9ZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnW2lkPSInK2F4ZS5jb21tb25zLnV0aWxzLmVzY2FwZVNlbGVjdG9yKGEuaWQpKyciXScpLGQ9W10sZT0wO2U8Yy5sZW5ndGg7ZSsrKWNbZV0hPT1hJiZkLnB1c2goY1tlXSk7cmV0dXJuIGQubGVuZ3RoJiZ0aGlzLnJlbGF0ZWROb2RlcyhkKSx0aGlzLmRhdGEoYS5nZXRBdHRyaWJ1dGUoImlkIikpLGMubGVuZ3RoPD0xfSxhZnRlcjpmdW5jdGlvbihhLGIpe3ZhciBjPVtdO3JldHVybiBhLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4gYy5pbmRleE9mKGEuZGF0YSk9PT0tMSYmKGMucHVzaChhLmRhdGEpLCEwKX0pfX0se2lkOiJleGlzdHMiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuITB9fSx7aWQ6Imhhcy1hbHQiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuIGEuaGFzQXR0cmlidXRlKCJhbHQiKX19LHtpZDoiaGFzLXZpc2libGUtdGV4dCIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4gYXhlLmNvbW1vbnMudGV4dC5hY2Nlc3NpYmxlVGV4dChhKS5sZW5ndGg+MH19LHtpZDoiaXMtb24tc2NyZWVuIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiBheGUuY29tbW9ucy5kb20uaXNWaXNpYmxlKGEsITEpJiYhYXhlLmNvbW1vbnMuZG9tLmlzT2Zmc2NyZWVuKGEpfX0se2lkOiJub24tZW1wdHktYWx0IixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJhbHQiKTtyZXR1cm4hIShjP2F4ZS5jb21tb25zLnRleHQuc2FuaXRpemUoYykudHJpbSgpOiIiKX19LHtpZDoibm9uLWVtcHR5LWlmLXByZXNlbnQiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGM9YS5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpLGQ9KGEuZ2V0QXR0cmlidXRlKCJ0eXBlIil8fCIiKS50b0xvd2VyQ2FzZSgpLGU9YS5nZXRBdHRyaWJ1dGUoInZhbHVlIik7cmV0dXJuIHRoaXMuZGF0YShlKSwiSU5QVVQiPT09YyYmWyJzdWJtaXQiLCJyZXNldCJdLmluZGV4T2YoZCkhPT0tMSYmbnVsbD09PWV9fSx7aWQ6Im5vbi1lbXB0eS10aXRsZSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXt2YXIgYz1hLmdldEF0dHJpYnV0ZSgidGl0bGUiKTtyZXR1cm4hIShjP2F4ZS5jb21tb25zLnRleHQuc2FuaXRpemUoYykudHJpbSgpOiIiKX19LHtpZDoibm9uLWVtcHR5LXZhbHVlIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJ2YWx1ZSIpO3JldHVybiEhKGM/YXhlLmNvbW1vbnMudGV4dC5zYW5pdGl6ZShjKS50cmltKCk6IiIpfX0se2lkOiJyb2xlLW5vbmUiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuIm5vbmUiPT09YS5nZXRBdHRyaWJ1dGUoInJvbGUiKX19LHtpZDoicm9sZS1wcmVzZW50YXRpb24iLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuInByZXNlbnRhdGlvbiI9PT1hLmdldEF0dHJpYnV0ZSgicm9sZSIpfX0se2lkOiJjYXB0aW9uLWZha2VkIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWF4ZS5jb21tb25zLnRhYmxlLnRvR3JpZChhKSxkPWNbMF07cmV0dXJuIGMubGVuZ3RoPD0xfHxkLmxlbmd0aDw9MXx8YS5yb3dzLmxlbmd0aDw9MXx8ZC5yZWR1Y2UoZnVuY3Rpb24oYSxiLGMpe3JldHVybiBhfHxiIT09ZFtjKzFdJiZ2b2lkIDAhPT1kW2MrMV19LCExKX19LHtpZDoiaGFzLWNhcHRpb24iLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuISFhLmNhcHRpb259fSx7aWQ6Imhhcy1zdW1tYXJ5IixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3JldHVybiEhYS5zdW1tYXJ5fX0se2lkOiJoYXMtdGgiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7Zm9yKHZhciBjLGQsZT1bXSxmPTAsZz1hLnJvd3MubGVuZ3RoO2Y8ZztmKyspe2M9YS5yb3dzW2ZdO2Zvcih2YXIgaD0wLGk9Yy5jZWxscy5sZW5ndGg7aDxpO2grKylkPWMuY2VsbHNbaF0sIlRIIiE9PWQubm9kZU5hbWUudG9VcHBlckNhc2UoKSYmWyJyb3doZWFkZXIiLCJjb2x1bW5oZWFkZXIiXS5pbmRleE9mKGQuZ2V0QXR0cmlidXRlKCJyb2xlIikpPT09LTF8fGUucHVzaChkKX1yZXR1cm4hIWUubGVuZ3RoJiYodGhpcy5yZWxhdGVkTm9kZXMoZSksITApfX0se2lkOiJodG1sNS1zY29wZSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtyZXR1cm4hIWF4ZS5jb21tb25zLmRvbS5pc0hUTUw1KGRvY3VtZW50KSYmIlRIIj09PWEubm9kZU5hbWUudG9VcHBlckNhc2UoKX19LHtpZDoic2FtZS1jYXB0aW9uLXN1bW1hcnkiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7cmV0dXJuISghYS5zdW1tYXJ5fHwhYS5jYXB0aW9uKSYmYS5zdW1tYXJ5PT09YXhlLmNvbW1vbnMudGV4dC5hY2Nlc3NpYmxlVGV4dChhLmNhcHRpb24pfX0se2lkOiJzY29wZS12YWx1ZSIsZXZhbHVhdGU6ZnVuY3Rpb24oYSxiKXtiPWJ8fHt9O3ZhciBjPWEuZ2V0QXR0cmlidXRlKCJzY29wZSIpLnRvTG93ZXJDYXNlKCksZD1bInJvdyIsImNvbCIsInJvd2dyb3VwIiwiY29sZ3JvdXAiXXx8Yi52YWx1ZXM7cmV0dXJuIGQuaW5kZXhPZihjKSE9PS0xfX0se2lkOiJ0ZC1oYXMtaGVhZGVyIixldmFsdWF0ZTpmdW5jdGlvbihhLGIpe3ZhciBjPWF4ZS5jb21tb25zLnRhYmxlLGQ9W10sZT1jLmdldEFsbENlbGxzKGEpO3JldHVybiBlLmZvckVhY2goZnVuY3Rpb24oYSl7aWYoIiIhPT1hLnRleHRDb250ZW50LnRyaW0oKSYmYy5pc0RhdGFDZWxsKGEpJiYhYXhlLmNvbW1vbnMuYXJpYS5sYWJlbChhKSl7dmFyIGI9Yy5nZXRIZWFkZXJzKGEpO2I9Yi5yZWR1Y2UoZnVuY3Rpb24oYSxiKXtyZXR1cm4gYXx8bnVsbCE9PWImJiEhYi50ZXh0Q29udGVudC50cmltKCl9LCExKSxifHxkLnB1c2goYSl9fSksIWQubGVuZ3RofHwodGhpcy5yZWxhdGVkTm9kZXMoZCksITEpfX0se2lkOiJ0ZC1oZWFkZXJzLWF0dHIiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7Zm9yKHZhciBjPVtdLGQ9MCxlPWEucm93cy5sZW5ndGg7ZDxlO2QrKylmb3IodmFyIGY9YS5yb3dzW2RdLGc9MCxoPWYuY2VsbHMubGVuZ3RoO2c8aDtnKyspYy5wdXNoKGYuY2VsbHNbZ10pO3ZhciBpPWMucmVkdWNlKGZ1bmN0aW9uKGEsYil7cmV0dXJuIGIuaWQmJmEucHVzaChiLmlkKSxhfSxbXSksaj1jLnJlZHVjZShmdW5jdGlvbihhLGIpe3ZhciBjLGQsZT0oYi5nZXRBdHRyaWJ1dGUoImhlYWRlcnMiKXx8IiIpLnNwbGl0KC9ccy8pLnJlZHVjZShmdW5jdGlvbihhLGIpe3JldHVybiBiPWIudHJpbSgpLGImJmEucHVzaChiKSxhfSxbXSk7cmV0dXJuIDAhPT1lLmxlbmd0aCYmKGIuaWQmJihjPWUuaW5kZXhPZihiLmlkLnRyaW0oKSkhPT0tMSksZD1lLnJlZHVjZShmdW5jdGlvbihhLGIpe3JldHVybiBhfHxpLmluZGV4T2YoYik9PT0tMX0sITEpLChjfHxkKSYmYS5wdXNoKGIpKSxhfSxbXSk7cmV0dXJuIShqLmxlbmd0aD4wKXx8KHRoaXMucmVsYXRlZE5vZGVzKGopLCExKX19LHtpZDoidGgtaGFzLWRhdGEtY2VsbHMiLGV2YWx1YXRlOmZ1bmN0aW9uKGEsYil7dmFyIGM9YXhlLmNvbW1vbnMudGFibGUsZD1jLmdldEFsbENlbGxzKGEpLGU9dGhpcyxmPVtdO2QuZm9yRWFjaChmdW5jdGlvbihhKXt2YXIgYj1hLmdldEF0dHJpYnV0ZSgiaGVhZGVycyIpO2ImJihmPWYuY29uY2F0KGIuc3BsaXQoL1xzKy8pKSk7dmFyIGM9YS5nZXRBdHRyaWJ1dGUoImFyaWEtbGFiZWxsZWRieSIpO2MmJihmPWYuY29uY2F0KGMuc3BsaXQoL1xzKy8pKSl9KTt2YXIgZz1kLmZpbHRlcihmdW5jdGlvbihhKXtyZXR1cm4iIiE9PWF4ZS5jb21tb25zLnRleHQuc2FuaXRpemUoYS50ZXh0Q29udGVudCkmJigiVEgiPT09YS5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpfHxbInJvd2hlYWRlciIsImNvbHVtbmhlYWRlciJdLmluZGV4T2YoYS5nZXRBdHRyaWJ1dGUoInJvbGUiKSkhPT0tMSl9KSxoPWMudG9HcmlkKGEpO3JldHVybiBnLnJlZHVjZShmdW5jdGlvbihhLGIpe2lmKGIuaWQmJmYuaW5kZXhPZihiLmlkKSE9PS0xKXJldHVybiEhYXx8YTt2YXIgZD0hMSxnPWMuZ2V0Q2VsbFBvc2l0aW9uKGIsaCk7cmV0dXJuIGMuaXNDb2x1bW5IZWFkZXIoYikmJihkPWMudHJhdmVyc2UoImRvd24iLGcsaCkucmVkdWNlKGZ1bmN0aW9uKGEsYil7cmV0dXJuIGF8fCIiIT09Yi50ZXh0Q29udGVudC50cmltKCkmJiFjLmlzQ29sdW1uSGVhZGVyKGIpfSwhMSkpLCFkJiZjLmlzUm93SGVhZGVyKGIpJiYoZD1jLnRyYXZlcnNlKCJyaWdodCIsZyxoKS5yZWR1Y2UoZnVuY3Rpb24oYSxiKXsKcmV0dXJuIGF8fCIiIT09Yi50ZXh0Q29udGVudC50cmltKCkmJiFjLmlzUm93SGVhZGVyKGIpfSwhMSkpLGR8fGUucmVsYXRlZE5vZGVzKGIpLGEmJmR9LCEwKX19XSxjb21tb25zOmZ1bmN0aW9uKCl7ZnVuY3Rpb24gYShhKXtyZXR1cm4gYS5nZXRQcm9wZXJ0eVZhbHVlKCJmb250LWZhbWlseSIpLnNwbGl0KC9bLDtdL2cpLm1hcChmdW5jdGlvbihhKXtyZXR1cm4gYS50cmltKCkudG9Mb3dlckNhc2UoKX0pfWZ1bmN0aW9uIGIoYixjKXt2YXIgZD13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShiKTtpZigibm9uZSIhPT1kLmdldFByb3BlcnR5VmFsdWUoImJhY2tncm91bmQtaW1hZ2UiKSlyZXR1cm4hMDt2YXIgZT1bImJvcmRlci1ib3R0b20iLCJib3JkZXItdG9wIiwib3V0bGluZSJdLnJlZHVjZShmdW5jdGlvbihhLGIpe3ZhciBjPW5ldyB1LkNvbG9yO3JldHVybiBjLnBhcnNlUmdiU3RyaW5nKGQuZ2V0UHJvcGVydHlWYWx1ZShiKyItY29sb3IiKSksYXx8Im5vbmUiIT09ZC5nZXRQcm9wZXJ0eVZhbHVlKGIrIi1zdHlsZSIpJiZwYXJzZUZsb2F0KGQuZ2V0UHJvcGVydHlWYWx1ZShiKyItd2lkdGgiKSk+MCYmMCE9PWMuYWxwaGF9LCExKTtpZihlKXJldHVybiEwO3ZhciBmPXdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGMpO2lmKGEoZClbMF0hPT1hKGYpWzBdKXJldHVybiEwO3ZhciBnPVsidGV4dC1kZWNvcmF0aW9uLWxpbmUiLCJ0ZXh0LWRlY29yYXRpb24tc3R5bGUiLCJmb250LXdlaWdodCIsImZvbnQtc3R5bGUiLCJmb250LXNpemUiXS5yZWR1Y2UoZnVuY3Rpb24oYSxiKXtyZXR1cm4gYXx8ZC5nZXRQcm9wZXJ0eVZhbHVlKGIpIT09Zi5nZXRQcm9wZXJ0eVZhbHVlKGIpfSwhMSksaD1kLmdldFByb3BlcnR5VmFsdWUoInRleHQtZGVjb3JhdGlvbiIpO3JldHVybiBoLnNwbGl0KCIgIikubGVuZ3RoPDMmJihnPWd8fGghPT1mLmdldFByb3BlcnR5VmFsdWUoInRleHQtZGVjb3JhdGlvbiIpKSxnfWZ1bmN0aW9uIGMoYSxiKXt2YXIgYz1hLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCk7cmV0dXJuISF5LmluY2x1ZGVzKGMpfHwoYj1ifHx3aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShhKSwibm9uZSIhPT1iLmdldFByb3BlcnR5VmFsdWUoImJhY2tncm91bmQtaW1hZ2UiKSl9ZnVuY3Rpb24gZChhLGIpe2I9Ynx8d2luZG93LmdldENvbXB1dGVkU3R5bGUoYSk7dmFyIGM9bmV3IHUuQ29sb3I7aWYoYy5wYXJzZVJnYlN0cmluZyhiLmdldFByb3BlcnR5VmFsdWUoImJhY2tncm91bmQtY29sb3IiKSksMCE9PWMuYWxwaGEpe3ZhciBkPWIuZ2V0UHJvcGVydHlWYWx1ZSgib3BhY2l0eSIpO2MuYWxwaGE9Yy5hbHBoYSpkfXJldHVybiBjfWZ1bmN0aW9uIGUoYSxiKXt2YXIgYz0wO2lmKGE+MClmb3IodmFyIGU9YS0xO2U+PTA7ZS0tKXt2YXIgZj1iW2VdLGc9d2luZG93LmdldENvbXB1dGVkU3R5bGUoZiksaD1kKGYsZyk7aC5hbHBoYT9jKz1oLmFscGhhOmIuc3BsaWNlKGUsMSl9cmV0dXJuIGN9ZnVuY3Rpb24gZihhLGIpeyJ1c2Ugc3RyaWN0Ijt2YXIgYz1iKGEpO2ZvcihhPWEuZmlyc3RDaGlsZDthOyljIT09ITEmJmYoYSxiKSxhPWEubmV4dFNpYmxpbmd9ZnVuY3Rpb24gZyhhKXsidXNlIHN0cmljdCI7dmFyIGI9d2luZG93LmdldENvbXB1dGVkU3R5bGUoYSkuZ2V0UHJvcGVydHlWYWx1ZSgiZGlzcGxheSIpO3JldHVybiB6LmluZGV4T2YoYikhPT0tMXx8InRhYmxlLSI9PT1iLnN1YnN0cigwLDYpfWZ1bmN0aW9uIGgoYSl7InVzZSBzdHJpY3QiO3ZhciBiPWEubWF0Y2goL3JlY3RccypcKChbMC05XSspcHgsP1xzKihbMC05XSspcHgsP1xzKihbMC05XSspcHgsP1xzKihbMC05XSspcHhccypcKS8pO3JldHVybiEoIWJ8fDUhPT1iLmxlbmd0aCkmJihiWzNdLWJbMV08PTAmJmJbMl0tYls0XTw9MCl9ZnVuY3Rpb24gaShhKXt2YXIgYj1udWxsO3JldHVybiBhLmlkJiYoYj1kb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdsYWJlbFtmb3I9IicrYXhlLnV0aWxzLmVzY2FwZVNlbGVjdG9yKGEuaWQpKyciXScpKT9iOmI9di5maW5kVXAoYSwibGFiZWwiKX1mdW5jdGlvbiBqKGEpe3JldHVyblsiYnV0dG9uIiwicmVzZXQiLCJzdWJtaXQiXS5pbmRleE9mKGEudHlwZSkhPT0tMX1mdW5jdGlvbiBrKGEpe3ZhciBiPWEubm9kZU5hbWUudG9VcHBlckNhc2UoKTtyZXR1cm4iVEVYVEFSRUEiPT09Ynx8IlNFTEVDVCI9PT1ifHwiSU5QVVQiPT09YiYmImhpZGRlbiIhPT1hLnR5cGUudG9Mb3dlckNhc2UoKX1mdW5jdGlvbiBsKGEpe3JldHVyblsiQlVUVE9OIiwiU1VNTUFSWSIsIkEiXS5pbmRleE9mKGEubm9kZU5hbWUudG9VcHBlckNhc2UoKSkhPT0tMX1mdW5jdGlvbiBtKGEpe3JldHVyblsiVEFCTEUiLCJGSUdVUkUiXS5pbmRleE9mKGEubm9kZU5hbWUudG9VcHBlckNhc2UoKSkhPT0tMX1mdW5jdGlvbiBuKGEpe3ZhciBiPWEubm9kZU5hbWUudG9VcHBlckNhc2UoKTtpZigiSU5QVVQiPT09YilyZXR1cm4hYS5oYXNBdHRyaWJ1dGUoInR5cGUiKXx8Qi5pbmRleE9mKGEuZ2V0QXR0cmlidXRlKCJ0eXBlIikudG9Mb3dlckNhc2UoKSkhPT0tMSYmYS52YWx1ZT9hLnZhbHVlOiIiO2lmKCJTRUxFQ1QiPT09Yil7dmFyIGM9YS5vcHRpb25zO2lmKGMmJmMubGVuZ3RoKXtmb3IodmFyIGQ9IiIsZT0wO2U8Yy5sZW5ndGg7ZSsrKWNbZV0uc2VsZWN0ZWQmJihkKz0iICIrY1tlXS50ZXh0KTtyZXR1cm4geC5zYW5pdGl6ZShkKX1yZXR1cm4iIn1yZXR1cm4iVEVYVEFSRUEiPT09YiYmYS52YWx1ZT9hLnZhbHVlOiIifWZ1bmN0aW9uIG8oYSxiKXt2YXIgYz1hLnF1ZXJ5U2VsZWN0b3IoYi50b0xvd2VyQ2FzZSgpKTtyZXR1cm4gYz94LmFjY2Vzc2libGVUZXh0KGMpOiIifWZ1bmN0aW9uIHAoYSl7aWYoIWEpcmV0dXJuITE7c3dpdGNoKGEubm9kZU5hbWUudG9VcHBlckNhc2UoKSl7Y2FzZSJTRUxFQ1QiOmNhc2UiVEVYVEFSRUEiOnJldHVybiEwO2Nhc2UiSU5QVVQiOnJldHVybiFhLmhhc0F0dHJpYnV0ZSgidHlwZSIpfHxCLmluZGV4T2YoYS5nZXRBdHRyaWJ1dGUoInR5cGUiKS50b0xvd2VyQ2FzZSgpKSE9PS0xO2RlZmF1bHQ6cmV0dXJuITF9fWZ1bmN0aW9uIHEoYSl7dmFyIGI9YS5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpO3JldHVybiJJTlBVVCI9PT1iJiYiaW1hZ2UiPT09YS50eXBlLnRvTG93ZXJDYXNlKCl8fFsiSU1HIiwiQVBQTEVUIiwiQVJFQSJdLmluZGV4T2YoYikhPT0tMX1mdW5jdGlvbiByKGEpe3JldHVybiEheC5zYW5pdGl6ZShhKX12YXIgY29tbW9ucz17fSxzPWNvbW1vbnMuYXJpYT17fSx0PXMuX2x1dD17fTt0LmF0dHJpYnV0ZXM9eyJhcmlhLWFjdGl2ZWRlc2NlbmRhbnQiOnt0eXBlOiJpZHJlZiJ9LCJhcmlhLWF0b21pYyI6e3R5cGU6ImJvb2xlYW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSJdfSwiYXJpYS1hdXRvY29tcGxldGUiOnt0eXBlOiJubXRva2VuIix2YWx1ZXM6WyJpbmxpbmUiLCJsaXN0IiwiYm90aCIsIm5vbmUiXX0sImFyaWEtYnVzeSI6e3R5cGU6ImJvb2xlYW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSJdfSwiYXJpYS1jaGVja2VkIjp7dHlwZToibm10b2tlbiIsdmFsdWVzOlsidHJ1ZSIsImZhbHNlIiwibWl4ZWQiLCJ1bmRlZmluZWQiXX0sImFyaWEtY29sY291bnQiOnt0eXBlOiJpbnQifSwiYXJpYS1jb2xpbmRleCI6e3R5cGU6ImludCJ9LCJhcmlhLWNvbHNwYW4iOnt0eXBlOiJpbnQifSwiYXJpYS1jb250cm9scyI6e3R5cGU6ImlkcmVmcyJ9LCJhcmlhLWRlc2NyaWJlZGJ5Ijp7dHlwZToiaWRyZWZzIn0sImFyaWEtZGlzYWJsZWQiOnt0eXBlOiJib29sZWFuIix2YWx1ZXM6WyJ0cnVlIiwiZmFsc2UiXX0sImFyaWEtZHJvcGVmZmVjdCI6e3R5cGU6Im5tdG9rZW5zIix2YWx1ZXM6WyJjb3B5IiwibW92ZSIsInJlZmVyZW5jZSIsImV4ZWN1dGUiLCJwb3B1cCIsIm5vbmUiXX0sImFyaWEtZXhwYW5kZWQiOnt0eXBlOiJubXRva2VuIix2YWx1ZXM6WyJ0cnVlIiwiZmFsc2UiLCJ1bmRlZmluZWQiXX0sImFyaWEtZmxvd3RvIjp7dHlwZToiaWRyZWZzIn0sImFyaWEtZ3JhYmJlZCI6e3R5cGU6Im5tdG9rZW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSIsInVuZGVmaW5lZCJdfSwiYXJpYS1oYXNwb3B1cCI6e3R5cGU6ImJvb2xlYW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSJdfSwiYXJpYS1oaWRkZW4iOnt0eXBlOiJib29sZWFuIix2YWx1ZXM6WyJ0cnVlIiwiZmFsc2UiXX0sImFyaWEtaW52YWxpZCI6e3R5cGU6Im5tdG9rZW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSIsInNwZWxsaW5nIiwiZ3JhbW1hciJdfSwiYXJpYS1sYWJlbCI6e3R5cGU6InN0cmluZyJ9LCJhcmlhLWxhYmVsbGVkYnkiOnt0eXBlOiJpZHJlZnMifSwiYXJpYS1sZXZlbCI6e3R5cGU6ImludCJ9LCJhcmlhLWxpdmUiOnt0eXBlOiJubXRva2VuIix2YWx1ZXM6WyJvZmYiLCJwb2xpdGUiLCJhc3NlcnRpdmUiXX0sImFyaWEtbXVsdGlsaW5lIjp7dHlwZToiYm9vbGVhbiIsdmFsdWVzOlsidHJ1ZSIsImZhbHNlIl19LCJhcmlhLW11bHRpc2VsZWN0YWJsZSI6e3R5cGU6ImJvb2xlYW4iLHZhbHVlczpbInRydWUiLCJmYWxzZSJdfSwiYXJpYS1vcmllbnRhdGlvbiI6e3R5cGU6Im5tdG9rZW4iLHZhbHVlczpbImhvcml6b250YWwiLCJ2ZXJ0aWNhbCJdfSwiYXJpYS1vd25zIjp7dHlwZToiaWRyZWZzIn0sImFyaWEtcG9zaW5zZXQiOnt0eXBlOiJpbnQifSwiYXJpYS1wcmVzc2VkIjp7dHlwZToibm10b2tlbiIsdmFsdWVzOlsidHJ1ZSIsImZhbHNlIiwibWl4ZWQiLCJ1bmRlZmluZWQiXX0sImFyaWEtcmVhZG9ubHkiOnt0eXBlOiJib29sZWFuIix2YWx1ZXM6WyJ0cnVlIiwiZmFsc2UiXX0sImFyaWEtcmVsZXZhbnQiOnt0eXBlOiJubXRva2VucyIsdmFsdWVzOlsiYWRkaXRpb25zIiwicmVtb3ZhbHMiLCJ0ZXh0IiwiYWxsIl19LCJhcmlhLXJlcXVpcmVkIjp7dHlwZToiYm9vbGVhbiIsdmFsdWVzOlsidHJ1ZSIsImZhbHNlIl19LCJhcmlhLXJvd2NvdW50Ijp7dHlwZToiaW50In0sImFyaWEtcm93aW5kZXgiOnt0eXBlOiJpbnQifSwiYXJpYS1yb3dzcGFuIjp7dHlwZToiaW50In0sImFyaWEtc2VsZWN0ZWQiOnt0eXBlOiJubXRva2VuIix2YWx1ZXM6WyJ0cnVlIiwiZmFsc2UiLCJ1bmRlZmluZWQiXX0sImFyaWEtc2V0c2l6ZSI6e3R5cGU6ImludCJ9LCJhcmlhLXNvcnQiOnt0eXBlOiJubXRva2VuIix2YWx1ZXM6WyJhc2NlbmRpbmciLCJkZXNjZW5kaW5nIiwib3RoZXIiLCJub25lIl19LCJhcmlhLXZhbHVlbWF4Ijp7dHlwZToiZGVjaW1hbCJ9LCJhcmlhLXZhbHVlbWluIjp7dHlwZToiZGVjaW1hbCJ9LCJhcmlhLXZhbHVlbm93Ijp7dHlwZToiZGVjaW1hbCJ9LCJhcmlhLXZhbHVldGV4dCI6e3R5cGU6InN0cmluZyJ9fSx0Lmdsb2JhbEF0dHJpYnV0ZXM9WyJhcmlhLWF0b21pYyIsImFyaWEtYnVzeSIsImFyaWEtY29udHJvbHMiLCJhcmlhLWRlc2NyaWJlZGJ5IiwiYXJpYS1kaXNhYmxlZCIsImFyaWEtZHJvcGVmZmVjdCIsImFyaWEtZmxvd3RvIiwiYXJpYS1ncmFiYmVkIiwiYXJpYS1oYXNwb3B1cCIsImFyaWEtaGlkZGVuIiwiYXJpYS1pbnZhbGlkIiwiYXJpYS1sYWJlbCIsImFyaWEtbGFiZWxsZWRieSIsImFyaWEtbGl2ZSIsImFyaWEtb3ducyIsImFyaWEtcmVsZXZhbnQiXSx0LnJvbGU9e2FsZXJ0Ont0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LGFsZXJ0ZGlhbG9nOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LGFwcGxpY2F0aW9uOnt0eXBlOiJsYW5kbWFyayIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbH0sYXJ0aWNsZTp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsiYXJ0aWNsZSJdfSxiYW5uZXI6e3R5cGU6ImxhbmRtYXJrIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsiaGVhZGVyIl19LGJ1dHRvbjp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCIsImFyaWEtcHJlc3NlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIiwiY29udGVudHMiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WyJidXR0b24iLCdpbnB1dFt0eXBlPSJidXR0b24iXScsJ2lucHV0W3R5cGU9ImltYWdlIl0nLCdpbnB1dFt0eXBlPSJyZXNldCJdJywnaW5wdXRbdHlwZT0ic3VibWl0Il0nLCJzdW1tYXJ5Il19LGNlbGw6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtY29saW5kZXgiLCJhcmlhLWNvbHNwYW4iLCJhcmlhLXJvd2luZGV4IiwiYXJpYS1yb3dzcGFuIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJyb3ciXSxpbXBsaWNpdDpbInRkIiwidGgiXX0sY2hlY2tib3g6e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7cmVxdWlyZWQ6WyJhcmlhLWNoZWNrZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsnaW5wdXRbdHlwZT0iY2hlY2tib3giXSddfSxjb2x1bW5oZWFkZXI6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiLCJhcmlhLXNvcnQiLCJhcmlhLXJlYWRvbmx5IiwiYXJpYS1zZWxlY3RlZCIsImFyaWEtcmVxdWlyZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpbInJvdyJdLGltcGxpY2l0OlsidGgiXX0sY29tYm9ib3g6e3R5cGU6ImNvbXBvc2l0ZSIsYXR0cmlidXRlczp7cmVxdWlyZWQ6WyJhcmlhLWV4cGFuZGVkIl0sYWxsb3dlZDpbImFyaWEtYXV0b2NvbXBsZXRlIiwiYXJpYS1yZXF1aXJlZCIsImFyaWEtYWN0aXZlZGVzY2VuZGFudCJdfSxvd25lZDp7YWxsOlsibGlzdGJveCIsInRleHRib3giXX0sbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LGNvbW1hbmQ6e25hbWVGcm9tOlsiYXV0aG9yIl0sdHlwZToiYWJzdHJhY3QifSxjb21wbGVtZW50YXJ5Ont0eXBlOiJsYW5kbWFyayIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbImFzaWRlIl19LGNvbXBvc2l0ZTp7bmFtZUZyb206WyJhdXRob3IiXSx0eXBlOiJhYnN0cmFjdCJ9LGNvbnRlbnRpbmZvOnt0eXBlOiJsYW5kbWFyayIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbImZvb3RlciJdfSxkZWZpbml0aW9uOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WyJkZCJdfSxkaWFsb2c6e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbImRpYWxvZyJdfSxkaXJlY3Rvcnk6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpudWxsfSxkb2N1bWVudDp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsiYm9keSJdfSxmb3JtOnt0eXBlOiJsYW5kbWFyayIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbImZvcm0iXX0sZ3JpZDp7dHlwZToiY29tcG9zaXRlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1sZXZlbCIsImFyaWEtbXVsdGlzZWxlY3RhYmxlIiwiYXJpYS1yZWFkb25seSIsImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6e29uZTpbInJvd2dyb3VwIiwicm93Il19LG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsidGFibGUiXX0sZ3JpZGNlbGw6e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtc2VsZWN0ZWQiLCJhcmlhLXJlYWRvbmx5IiwiYXJpYS1leHBhbmRlZCIsImFyaWEtcmVxdWlyZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpbInJvdyJdLGltcGxpY2l0OlsidGQiLCJ0aCJdfSxncm91cDp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1hY3RpdmVkZXNjZW5kYW50IiwiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsiZGV0YWlscyIsIm9wdGdyb3VwIl19LGhlYWRpbmc6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtbGV2ZWwiLCJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbImgxIiwiaDIiLCJoMyIsImg0IiwiaDUiLCJoNiJdfSxpbWc6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbImltZyJdfSxpbnB1dDp7bmFtZUZyb206WyJhdXRob3IiXSx0eXBlOiJhYnN0cmFjdCJ9LGxhbmRtYXJrOntuYW1lRnJvbTpbImF1dGhvciJdLHR5cGU6ImFic3RyYWN0In0sbGluazp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIiwiY29udGVudHMiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WyJhW2hyZWZdIl19LGxpc3Q6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6e2FsbDpbImxpc3RpdGVtIl19LG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0Olsib2wiLCJ1bCIsImRsIl19LGxpc3Rib3g6e3R5cGU6ImNvbXBvc2l0ZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtbXVsdGlzZWxlY3RhYmxlIiwiYXJpYS1yZXF1aXJlZCIsImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6e2FsbDpbIm9wdGlvbiJdfSxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbInNlbGVjdCJdfSxsaXN0aXRlbTp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1sZXZlbCIsImFyaWEtcG9zaW5zZXQiLCJhcmlhLXNldHNpemUiLCJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJsaXN0Il0saW1wbGljaXQ6WyJsaSIsImR0Il19LGxvZzp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSxtYWluOnt0eXBlOiJsYW5kbWFyayIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbIm1haW4iXX0sbWFycXVlZTp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSxtYXRoOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WyJtYXRoIl19LG1lbnU6e3R5cGU6ImNvbXBvc2l0ZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6e29uZTpbIm1lbnVpdGVtIiwibWVudWl0ZW1yYWRpbyIsIm1lbnVpdGVtY2hlY2tib3giXX0sbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WydtZW51W3R5cGU9ImNvbnRleHQiXSddfSxtZW51YmFyOnt0eXBlOiJjb21wb3NpdGUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWFjdGl2ZWRlc2NlbmRhbnQiLCJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LG1lbnVpdGVtOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6bnVsbCxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIiwiY29udGVudHMiXSxjb250ZXh0OlsibWVudSIsIm1lbnViYXIiXSxpbXBsaWNpdDpbJ21lbnVpdGVtW3R5cGU9ImNvbW1hbmQiXSddfSxtZW51aXRlbWNoZWNrYm94Ont0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e3JlcXVpcmVkOlsiYXJpYS1jaGVja2VkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJtZW51IiwibWVudWJhciJdLGltcGxpY2l0OlsnbWVudWl0ZW1bdHlwZT0iY2hlY2tib3giXSddfSxtZW51aXRlbXJhZGlvOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXNlbGVjdGVkIiwiYXJpYS1wb3NpbnNldCIsImFyaWEtc2V0c2l6ZSJdLHJlcXVpcmVkOlsiYXJpYS1jaGVja2VkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJtZW51IiwibWVudWJhciJdLGltcGxpY2l0OlsnbWVudWl0ZW1bdHlwZT0icmFkaW8iXSddfSxuYXZpZ2F0aW9uOnt0eXBlOiJsYW5kbWFyayIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbIm5hdiJdfSxub25lOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6bnVsbCxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSxub3RlOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LG9wdGlvbjp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1zZWxlY3RlZCIsImFyaWEtcG9zaW5zZXQiLCJhcmlhLXNldHNpemUiLCJhcmlhLWNoZWNrZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpbImxpc3Rib3giXSxpbXBsaWNpdDpbIm9wdGlvbiJdfSxwcmVzZW50YXRpb246e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczpudWxsLG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHByb2dyZXNzYmFyOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXZhbHVldGV4dCIsImFyaWEtdmFsdWVub3ciLCJhcmlhLXZhbHVlbWF4IiwiYXJpYS12YWx1ZW1pbiJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsicHJvZ3Jlc3MiXX0scmFkaW86e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtc2VsZWN0ZWQiLCJhcmlhLXBvc2luc2V0IiwiYXJpYS1zZXRzaXplIl0scmVxdWlyZWQ6WyJhcmlhLWNoZWNrZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsnaW5wdXRbdHlwZT0icmFkaW8iXSddfSxyYWRpb2dyb3VwOnt0eXBlOiJjb21wb3NpdGUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWFjdGl2ZWRlc2NlbmRhbnQiLCJhcmlhLXJlcXVpcmVkIiwiYXJpYS1leHBhbmRlZCJdfSxvd25lZDp7YWxsOlsicmFkaW8iXX0sbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHJhbmdlOntuYW1lRnJvbTpbImF1dGhvciJdLHR5cGU6ImFic3RyYWN0In0scmVnaW9uOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WyJzZWN0aW9uIl19LHJvbGV0eXBlOnt0eXBlOiJhYnN0cmFjdCJ9LHJvdzp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1sZXZlbCIsImFyaWEtc2VsZWN0ZWQiLCJhcmlhLWFjdGl2ZWRlc2NlbmRhbnQiLCJhcmlhLWV4cGFuZGVkIl19LG93bmVkOntvbmU6WyJjZWxsIiwiY29sdW1uaGVhZGVyIiwicm93aGVhZGVyIiwiZ3JpZGNlbGwiXX0sbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJyb3dncm91cCIsImdyaWQiLCJ0cmVlZ3JpZCIsInRhYmxlIl0saW1wbGljaXQ6WyJ0ciJdfSxyb3dncm91cDp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1hY3RpdmVkZXNjZW5kYW50IiwiYXJpYS1leHBhbmRlZCJdfSxvd25lZDp7YWxsOlsicm93Il19LG5hbWVGcm9tOlsiYXV0aG9yIiwiY29udGVudHMiXSxjb250ZXh0OlsiZ3JpZCIsInRhYmxlIl0saW1wbGljaXQ6WyJ0Ym9keSIsInRoZWFkIiwidGZvb3QiXX0scm93aGVhZGVyOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXNvcnQiLCJhcmlhLXJlcXVpcmVkIiwiYXJpYS1yZWFkb25seSIsImFyaWEtZXhwYW5kZWQiLCJhcmlhLXNlbGVjdGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJyb3ciXSxpbXBsaWNpdDpbInRoIl19LHNjcm9sbGJhcjp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOntyZXF1aXJlZDpbImFyaWEtY29udHJvbHMiLCJhcmlhLW9yaWVudGF0aW9uIiwiYXJpYS12YWx1ZW5vdyIsImFyaWEtdmFsdWVtYXgiLCJhcmlhLXZhbHVlbWluIl0sYWxsb3dlZDpbImFyaWEtdmFsdWV0ZXh0Il19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHNlYXJjaDp7dHlwZToibGFuZG1hcmsiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHNlYXJjaGJveDp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1hY3RpdmVkZXNjZW5kYW50IiwiYXJpYS1hdXRvY29tcGxldGUiLCJhcmlhLW11bHRpbGluZSIsImFyaWEtcmVhZG9ubHkiLCJhcmlhLXJlcXVpcmVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WydpbnB1dFt0eXBlPSJzZWFyY2giXSddfSxzZWN0aW9uOntuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sdHlwZToiYWJzdHJhY3QifSxzZWN0aW9uaGVhZDp7bmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLHR5cGU6ImFic3RyYWN0In0sc2VsZWN0OntuYW1lRnJvbTpbImF1dGhvciJdLHR5cGU6ImFic3RyYWN0In0sc2VwYXJhdG9yOnt0eXBlOiJzdHJ1Y3R1cmUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIiwiYXJpYS1vcmllbnRhdGlvbiJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsiaHIiXX0sc2xpZGVyOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXZhbHVldGV4dCIsImFyaWEtb3JpZW50YXRpb24iXSxyZXF1aXJlZDpbImFyaWEtdmFsdWVub3ciLCJhcmlhLXZhbHVlbWF4IiwiYXJpYS12YWx1ZW1pbiJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsnaW5wdXRbdHlwZT0icmFuZ2UiXSddfSxzcGluYnV0dG9uOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXZhbHVldGV4dCIsImFyaWEtcmVxdWlyZWQiXSxyZXF1aXJlZDpbImFyaWEtdmFsdWVub3ciLCJhcmlhLXZhbHVlbWF4IiwiYXJpYS12YWx1ZW1pbiJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsnaW5wdXRbdHlwZT0ibnVtYmVyIl0nXX0sc3RhdHVzOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WyJvdXRwdXQiXX0sc3RydWN0dXJlOnt0eXBlOiJhYnN0cmFjdCJ9LCJzd2l0Y2giOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e3JlcXVpcmVkOlsiYXJpYS1jaGVja2VkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6bnVsbH0sdGFiOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLXNlbGVjdGVkIiwiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIiwiY29udGVudHMiXSxjb250ZXh0OlsidGFibGlzdCJdfSx0YWJsZTp7dHlwZToic3RydWN0dXJlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1jb2xjb3VudCIsImFyaWEtcm93Y291bnQiXX0sb3duZWQ6e29uZTpbInJvd2dyb3VwIiwicm93Il19LG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsLGltcGxpY2l0OlsidGFibGUiXX0sdGFibGlzdDp7dHlwZToiY29tcG9zaXRlIixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1hY3RpdmVkZXNjZW5kYW50IiwiYXJpYS1leHBhbmRlZCIsImFyaWEtbGV2ZWwiLCJhcmlhLW11bHRpc2VsZWN0YWJsZSJdfSxvd25lZDp7YWxsOlsidGFiIl19LG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSx0YWJwYW5lbDp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1leHBhbmRlZCJdfSxvd25lZDpudWxsLG5hbWVGcm9tOlsiYXV0aG9yIl0sY29udGV4dDpudWxsfSx0ZXh0Ont0eXBlOiJzdHJ1Y3R1cmUiLG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6bnVsbH0sdGV4dGJveDp7dHlwZToid2lkZ2V0IixhdHRyaWJ1dGVzOnthbGxvd2VkOlsiYXJpYS1hY3RpdmVkZXNjZW5kYW50IiwiYXJpYS1hdXRvY29tcGxldGUiLCJhcmlhLW11bHRpbGluZSIsImFyaWEtcmVhZG9ubHkiLCJhcmlhLXJlcXVpcmVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGwsaW1wbGljaXQ6WydpbnB1dFt0eXBlPSJ0ZXh0Il0nLCdpbnB1dFt0eXBlPSJlbWFpbCJdJywnaW5wdXRbdHlwZT0icGFzc3dvcmQiXScsJ2lucHV0W3R5cGU9InRlbCJdJywnaW5wdXRbdHlwZT0idXJsIl0nLCJpbnB1dDpub3QoW3R5cGVdKSIsInRleHRhcmVhIl19LHRpbWVyOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWV4cGFuZGVkIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHRvb2xiYXI6e3R5cGU6InN0cnVjdHVyZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbCxpbXBsaWNpdDpbJ21lbnVbdHlwZT0idG9vbGJhciJdJ119LHRvb2x0aXA6e3R5cGU6IndpZGdldCIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtZXhwYW5kZWQiXX0sb3duZWQ6bnVsbCxuYW1lRnJvbTpbImF1dGhvciIsImNvbnRlbnRzIl0sY29udGV4dDpudWxsfSx0cmVlOnt0eXBlOiJjb21wb3NpdGUiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWFjdGl2ZWRlc2NlbmRhbnQiLCJhcmlhLW11bHRpc2VsZWN0YWJsZSIsImFyaWEtcmVxdWlyZWQiLCJhcmlhLWV4cGFuZGVkIl19LG93bmVkOnthbGw6WyJ0cmVlaXRlbSJdfSxuYW1lRnJvbTpbImF1dGhvciJdLGNvbnRleHQ6bnVsbH0sdHJlZWdyaWQ6e3R5cGU6ImNvbXBvc2l0ZSIsYXR0cmlidXRlczp7YWxsb3dlZDpbImFyaWEtYWN0aXZlZGVzY2VuZGFudCIsImFyaWEtZXhwYW5kZWQiLCJhcmlhLWxldmVsIiwiYXJpYS1tdWx0aXNlbGVjdGFibGUiLCJhcmlhLXJlYWRvbmx5IiwiYXJpYS1yZXF1aXJlZCJdfSxvd25lZDp7YWxsOlsidHJlZWl0ZW0iXX0sbmFtZUZyb206WyJhdXRob3IiXSxjb250ZXh0Om51bGx9LHRyZWVpdGVtOnt0eXBlOiJ3aWRnZXQiLGF0dHJpYnV0ZXM6e2FsbG93ZWQ6WyJhcmlhLWNoZWNrZWQiLCJhcmlhLXNlbGVjdGVkIiwiYXJpYS1leHBhbmRlZCIsImFyaWEtbGV2ZWwiLCJhcmlhLXBvc2luc2V0IiwiYXJpYS1zZXRzaXplIl19LG93bmVkOm51bGwsbmFtZUZyb206WyJhdXRob3IiLCJjb250ZW50cyJdLGNvbnRleHQ6WyJ0cmVlZ3JpZCIsInRyZWUiXX0sd2lkZ2V0Ont0eXBlOiJhYnN0cmFjdCJ9LHdpbmRvdzp7bmFtZUZyb206WyJhdXRob3IiXSx0eXBlOiJhYnN0cmFjdCJ9fTt2YXIgdT17fTtjb21tb25zLmNvbG9yPXU7dmFyIHY9Y29tbW9ucy5kb209e30sdz1jb21tb25zLnRhYmxlPXt9LHg9Y29tbW9ucy50ZXh0PXt9O2NvbW1vbnMudXRpbHM9YXhlLnV0aWxzO3MucmVxdWlyZWRBdHRyPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYj10LnJvbGVbYV0sYz1iJiZiLmF0dHJpYnV0ZXMmJmIuYXR0cmlidXRlcy5yZXF1aXJlZDtyZXR1cm4gY3x8W119LHMuYWxsb3dlZEF0dHI9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPXQucm9sZVthXSxjPWImJmIuYXR0cmlidXRlcyYmYi5hdHRyaWJ1dGVzLmFsbG93ZWR8fFtdLGQ9YiYmYi5hdHRyaWJ1dGVzJiZiLmF0dHJpYnV0ZXMucmVxdWlyZWR8fFtdO3JldHVybiBjLmNvbmNhdCh0Lmdsb2JhbEF0dHJpYnV0ZXMpLmNvbmNhdChkKX0scy52YWxpZGF0ZUF0dHI9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3JldHVybiEhdC5hdHRyaWJ1dGVzW2FdfSxzLnZhbGlkYXRlQXR0clZhbHVlPWZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO3ZhciBjLGQsZT1kb2N1bWVudCxmPWEuZ2V0QXR0cmlidXRlKGIpLGc9dC5hdHRyaWJ1dGVzW2JdO2lmKCFnKXJldHVybiEwO3N3aXRjaChnLnR5cGUpe2Nhc2UiYm9vbGVhbiI6Y2FzZSJubXRva2VuIjpyZXR1cm4ic3RyaW5nIj09dHlwZW9mIGYmJmcudmFsdWVzLmluZGV4T2YoZi50b0xvd2VyQ2FzZSgpKSE9PS0xO2Nhc2Uibm10b2tlbnMiOnJldHVybiBkPWF4ZS51dGlscy50b2tlbkxpc3QoZiksZC5yZWR1Y2UoZnVuY3Rpb24oYSxiKXtyZXR1cm4gYSYmZy52YWx1ZXMuaW5kZXhPZihiKSE9PS0xfSwwIT09ZC5sZW5ndGgpO2Nhc2UiaWRyZWYiOnJldHVybiEoIWZ8fCFlLmdldEVsZW1lbnRCeUlkKGYpKTtjYXNlImlkcmVmcyI6cmV0dXJuIGQ9YXhlLnV0aWxzLnRva2VuTGlzdChmKSxkLnJlZHVjZShmdW5jdGlvbihhLGIpe3JldHVybiEoIWF8fCFlLmdldEVsZW1lbnRCeUlkKGIpKX0sMCE9PWQubGVuZ3RoKTtjYXNlInN0cmluZyI6cmV0dXJuITA7Y2FzZSJkZWNpbWFsIjpyZXR1cm4gYz1mLm1hdGNoKC9eWy0rXT8oWzAtOV0qKVwuPyhbMC05XSopJC8pLCEoIWN8fCFjWzFdJiYhY1syXSk7Y2FzZSJpbnQiOnJldHVybi9eWy0rXT9bMC05XSskLy50ZXN0KGYpfX0scy5sYWJlbD1mdW5jdGlvbihhKXt2YXIgYixjO3JldHVybiBhLmdldEF0dHJpYnV0ZSgiYXJpYS1sYWJlbGxlZGJ5IikmJihiPXYuaWRyZWZzKGEsImFyaWEtbGFiZWxsZWRieSIpLGM9Yi5tYXAoZnVuY3Rpb24oYSl7cmV0dXJuIGE/eC52aXNpYmxlKGEsITApOiIifSkuam9pbigiICIpLnRyaW0oKSk/YzooYz1hLmdldEF0dHJpYnV0ZSgiYXJpYS1sYWJlbCIpLGMmJihjPXguc2FuaXRpemUoYykudHJpbSgpKT9jOm51bGwpfSxzLmlzVmFsaWRSb2xlPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtyZXR1cm4hIXQucm9sZVthXX0scy5nZXRSb2xlc1dpdGhOYW1lRnJvbUNvbnRlbnRzPWZ1bmN0aW9uKCl7cmV0dXJuIE9iamVjdC5rZXlzKHQucm9sZSkuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiB0LnJvbGVbYV0ubmFtZUZyb20mJnQucm9sZVthXS5uYW1lRnJvbS5pbmRleE9mKCJjb250ZW50cyIpIT09LTF9KX0scy5nZXRSb2xlc0J5VHlwZT1mdW5jdGlvbihhKXtyZXR1cm4gT2JqZWN0LmtleXModC5yb2xlKS5maWx0ZXIoZnVuY3Rpb24oYil7cmV0dXJuIHQucm9sZVtiXS50eXBlPT09YX0pfSxzLmdldFJvbGVUeXBlPWZ1bmN0aW9uKGEpe3ZhciBiPXQucm9sZVthXTtyZXR1cm4gYiYmYi50eXBlfHxudWxsfSxzLnJlcXVpcmVkT3duZWQ9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPW51bGwsYz10LnJvbGVbYV07cmV0dXJuIGMmJihiPWF4ZS51dGlscy5jbG9uZShjLm93bmVkKSksYn0scy5yZXF1aXJlZENvbnRleHQ9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPW51bGwsYz10LnJvbGVbYV07cmV0dXJuIGMmJihiPWF4ZS51dGlscy5jbG9uZShjLmNvbnRleHQpKSxifSxzLmltcGxpY2l0Tm9kZXM9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPW51bGwsYz10LnJvbGVbYV07cmV0dXJuIGMmJmMuaW1wbGljaXQmJihiPWF4ZS51dGlscy5jbG9uZShjLmltcGxpY2l0KSksYn0scy5pbXBsaWNpdFJvbGU9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiLGMsZCxlPXQucm9sZTtmb3IoYiBpbiBlKWlmKGUuaGFzT3duUHJvcGVydHkoYikmJihjPWVbYl0sYy5pbXBsaWNpdCkpZm9yKHZhciBmPTAsZz1jLmltcGxpY2l0Lmxlbmd0aDtmPGc7ZisrKWlmKGQ9Yy5pbXBsaWNpdFtmXSxheGUudXRpbHMubWF0Y2hlc1NlbGVjdG9yKGEsZCkpcmV0dXJuIGI7cmV0dXJuIG51bGx9LHUuQ29sb3I9ZnVuY3Rpb24oYSxiLGMsZCl7dGhpcy5yZWQ9YSx0aGlzLmdyZWVuPWIsdGhpcy5ibHVlPWMsdGhpcy5hbHBoYT1kLHRoaXMudG9IZXhTdHJpbmc9ZnVuY3Rpb24oKXt2YXIgYT1NYXRoLnJvdW5kKHRoaXMucmVkKS50b1N0cmluZygxNiksYj1NYXRoLnJvdW5kKHRoaXMuZ3JlZW4pLnRvU3RyaW5nKDE2KSxjPU1hdGgucm91bmQodGhpcy5ibHVlKS50b1N0cmluZygxNik7cmV0dXJuIiMiKyh0aGlzLnJlZD4xNS41P2E6IjAiK2EpKyh0aGlzLmdyZWVuPjE1LjU/YjoiMCIrYikrKHRoaXMuYmx1ZT4xNS41P2M6IjAiK2MpfTt2YXIgZT0vXnJnYlwoKFxkKyksIChcZCspLCAoXGQrKVwpJC8sZj0vXnJnYmFcKChcZCspLCAoXGQrKSwgKFxkKyksIChcZCooXC5cZCspPylcKS87dGhpcy5wYXJzZVJnYlN0cmluZz1mdW5jdGlvbihhKXtpZigidHJhbnNwYXJlbnQiPT09YSlyZXR1cm4gdGhpcy5yZWQ9MCx0aGlzLmdyZWVuPTAsdGhpcy5ibHVlPTAsdm9pZCh0aGlzLmFscGhhPTApO3ZhciBiPWEubWF0Y2goZSk7cmV0dXJuIGI/KHRoaXMucmVkPXBhcnNlSW50KGJbMV0sMTApLHRoaXMuZ3JlZW49cGFyc2VJbnQoYlsyXSwxMCksdGhpcy5ibHVlPXBhcnNlSW50KGJbM10sMTApLHZvaWQodGhpcy5hbHBoYT0xKSk6KGI9YS5tYXRjaChmKSxiPyh0aGlzLnJlZD1wYXJzZUludChiWzFdLDEwKSx0aGlzLmdyZWVuPXBhcnNlSW50KGJbMl0sMTApLHRoaXMuYmx1ZT1wYXJzZUludChiWzNdLDEwKSx2b2lkKHRoaXMuYWxwaGE9cGFyc2VGbG9hdChiWzRdKSkpOnZvaWQgMCl9LHRoaXMuZ2V0UmVsYXRpdmVMdW1pbmFuY2U9ZnVuY3Rpb24oKXt2YXIgYT10aGlzLnJlZC8yNTUsYj10aGlzLmdyZWVuLzI1NSxjPXRoaXMuYmx1ZS8yNTUsZD1hPD0uMDM5Mjg/YS8xMi45MjpNYXRoLnBvdygoYSsuMDU1KS8xLjA1NSwyLjQpLGU9Yjw9LjAzOTI4P2IvMTIuOTI6TWF0aC5wb3coKGIrLjA1NSkvMS4wNTUsMi40KSxmPWM8PS4wMzkyOD9jLzEyLjkyOk1hdGgucG93KChjKy4wNTUpLzEuMDU1LDIuNCk7cmV0dXJuLjIxMjYqZCsuNzE1MiplKy4wNzIyKmZ9fSx1LmZsYXR0ZW5Db2xvcnM9ZnVuY3Rpb24oYSxiKXt2YXIgYz1hLmFscGhhLGQ9KDEtYykqYi5yZWQrYyphLnJlZCxlPSgxLWMpKmIuZ3JlZW4rYyphLmdyZWVuLGY9KDEtYykqYi5ibHVlK2MqYS5ibHVlLGc9YS5hbHBoYStiLmFscGhhKigxLWEuYWxwaGEpO3JldHVybiBuZXcgdS5Db2xvcihkLGUsZixnKX0sdS5nZXRDb250cmFzdD1mdW5jdGlvbihhLGIpe2lmKCFifHwhYSlyZXR1cm4gbnVsbDtiLmFscGhhPDEmJihiPXUuZmxhdHRlbkNvbG9ycyhiLGEpKTt2YXIgYz1hLmdldFJlbGF0aXZlTHVtaW5hbmNlKCksZD1iLmdldFJlbGF0aXZlTHVtaW5hbmNlKCk7cmV0dXJuKE1hdGgubWF4KGQsYykrLjA1KS8oTWF0aC5taW4oZCxjKSsuMDUpfSx1Lmhhc1ZhbGlkQ29udHJhc3RSYXRpbz1mdW5jdGlvbihhLGIsYyxkKXt2YXIgZT11LmdldENvbnRyYXN0KGEsYiksZj1kJiZNYXRoLmNlaWwoNzIqYykvOTY8MTR8fCFkJiZNYXRoLmNlaWwoNzIqYykvOTY8MTg7cmV0dXJue2lzVmFsaWQ6ZiYmZT49NC41fHwhZiYmZT49Myxjb250cmFzdFJhdGlvOmV9fSx1LmVsZW1lbnRJc0Rpc3RpbmN0PWI7dmFyIHk9WyJJTUciLCJDQU5WQVMiLCJPQkpFQ1QiLCJJRlJBTUUiLCJWSURFTyIsIlNWRyJdO3UuZ2V0QmFja2dyb3VuZFN0YWNrPWZ1bmN0aW9uKGEpe3ZhciBiPWEuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCksZj12b2lkIDAsZz12b2lkIDA7aWYoIShiLmxlZnQ+d2luZG93LmlubmVyV2lkdGh8fGIudG9wPndpbmRvdy5pbm5lcldpZHRoKSl7Zj1NYXRoLm1pbihNYXRoLmNlaWwoYi5sZWZ0K2Iud2lkdGgvMiksd2luZG93LmlubmVyV2lkdGgtMSksZz1NYXRoLm1pbihNYXRoLmNlaWwoYi50b3ArYi5oZWlnaHQvMiksd2luZG93LmlubmVySGVpZ2h0LTEpO3ZhciBoPWRvY3VtZW50LmVsZW1lbnRzRnJvbVBvaW50KGYsZyk7aD12LnJlZHVjZVRvRWxlbWVudHNCZWxvd0Zsb2F0aW5nKGgsYSk7dmFyIGk9aC5pbmRleE9mKGRvY3VtZW50LmJvZHkpO2k+MSYmIWMoZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50KSYmMD09PWQoZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50KS5hbHBoYSYmKGguc3BsaWNlKGksMSksaC5zcGxpY2UoaC5pbmRleE9mKGRvY3VtZW50LmRvY3VtZW50RWxlbWVudCksMSksaC5wdXNoKGRvY3VtZW50LmJvZHkpKTt2YXIgaj1oLmluZGV4T2YoYSk7cmV0dXJuIGUoaixoKT49Ljk5P251bGw6aiE9PS0xP2g6bnVsbH19LHUuZ2V0QmFja2dyb3VuZENvbG9yPWZ1bmN0aW9uKGEpe3ZhciBiPWFyZ3VtZW50cy5sZW5ndGg+MSYmdm9pZCAwIT09YXJndW1lbnRzWzFdP2FyZ3VtZW50c1sxXTpbXSxlPWFyZ3VtZW50cy5sZW5ndGg+MiYmdm9pZCAwIT09YXJndW1lbnRzWzJdJiZhcmd1bWVudHNbMl07ZSE9PSEwJiZhLnNjcm9sbEludG9WaWV3KCk7dmFyIGY9W10sZz11LmdldEJhY2tncm91bmRTdGFjayhhKTtyZXR1cm4oZ3x8W10pLnNvbWUoZnVuY3Rpb24oZSl7dmFyIGc9d2luZG93LmdldENvbXB1dGVkU3R5bGUoZSksaD1kKGUsZyk7cmV0dXJuIGEhPT1lJiYhdi52aXN1YWxseUNvbnRhaW5zKGEsZSkmJjAhPT1oLmFscGhhfHxjKGUsZyk/KGY9bnVsbCxiLnB1c2goZSksITApOjAhPT1oLmFscGhhJiYoYi5wdXNoKGUpLGYucHVzaChoKSwxPT09aC5hbHBoYSl9KSxudWxsIT09ZiYmbnVsbCE9PWc/KGYucHVzaChuZXcgdS5Db2xvcigyNTUsMjU1LDI1NSwxKSksZi5yZWR1Y2UodS5mbGF0dGVuQ29sb3JzKSk6bnVsbH0sdi5pc09wYXF1ZT1mdW5jdGlvbihhKXt2YXIgYj13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShhKTtyZXR1cm4gYyhhLGIpfHwxPT09ZChhLGIpLmFscGhhfSx1LmdldEZvcmVncm91bmRDb2xvcj1mdW5jdGlvbihhLGIpe3ZhciBjPXdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGEpLGQ9bmV3IHUuQ29sb3I7ZC5wYXJzZVJnYlN0cmluZyhjLmdldFByb3BlcnR5VmFsdWUoImNvbG9yIikpO3ZhciBlPWMuZ2V0UHJvcGVydHlWYWx1ZSgib3BhY2l0eSIpO2lmKGQuYWxwaGE9ZC5hbHBoYSplLDE9PT1kLmFscGhhKXJldHVybiBkO3ZhciBmPXUuZ2V0QmFja2dyb3VuZENvbG9yKGEsW10sYik7cmV0dXJuIG51bGw9PT1mP251bGw6dS5mbGF0dGVuQ29sb3JzKGQsZil9LHYucmVkdWNlVG9FbGVtZW50c0JlbG93RmxvYXRpbmc9ZnVuY3Rpb24oYSxiKXt2YXIgYyxkLGUsZj1bImZpeGVkIiwic3RpY2t5Il0sZz1bXSxoPSExO2ZvcihjPTA7YzxhLmxlbmd0aDsrK2MpZD1hW2NdLGQ9PT1iJiYoaD0hMCksZT13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShkKSxofHxmLmluZGV4T2YoZS5wb3NpdGlvbik9PT0tMT9nLnB1c2goZCk6Zz1bXTtyZXR1cm4gZ30sdi5maW5kVXA9ZnVuY3Rpb24oYSxiKXsidXNlIHN0cmljdCI7dmFyIGMsZD1kb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKGIpLGU9ZC5sZW5ndGg7aWYoIWUpcmV0dXJuIG51bGw7Zm9yKGQ9YXhlLnV0aWxzLnRvQXJyYXkoZCksYz1hLnBhcmVudE5vZGU7YyYmZC5pbmRleE9mKGMpPT09LTE7KWM9Yy5wYXJlbnROb2RlO3JldHVybiBjfSx2LmdldEVsZW1lbnRCeVJlZmVyZW5jZT1mdW5jdGlvbihhLGIpeyJ1c2Ugc3RyaWN0Ijt2YXIgYyxkPWEuZ2V0QXR0cmlidXRlKGIpLGU9ZG9jdW1lbnQ7aWYoZCYmIiMiPT09ZC5jaGFyQXQoMCkpe2lmKGQ9ZC5zdWJzdHJpbmcoMSksYz1lLmdldEVsZW1lbnRCeUlkKGQpKXJldHVybiBjO2lmKGM9ZS5nZXRFbGVtZW50c0J5TmFtZShkKSxjLmxlbmd0aClyZXR1cm4gY1swXX1yZXR1cm4gbnVsbH0sdi5nZXRFbGVtZW50Q29vcmRpbmF0ZXM9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3ZhciBiPXYuZ2V0U2Nyb2xsT2Zmc2V0KGRvY3VtZW50KSxjPWIubGVmdCxkPWIudG9wLGU9YS5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtyZXR1cm57dG9wOmUudG9wK2QscmlnaHQ6ZS5yaWdodCtjLGJvdHRvbTplLmJvdHRvbStkLGxlZnQ6ZS5sZWZ0K2Msd2lkdGg6ZS5yaWdodC1lLmxlZnQsaGVpZ2h0OmUuYm90dG9tLWUudG9wfX0sdi5nZXRTY3JvbGxPZmZzZXQ9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO2lmKCFhLm5vZGVUeXBlJiZhLmRvY3VtZW50JiYoYT1hLmRvY3VtZW50KSw5PT09YS5ub2RlVHlwZSl7dmFyIGI9YS5kb2N1bWVudEVsZW1lbnQsYz1hLmJvZHk7cmV0dXJue2xlZnQ6YiYmYi5zY3JvbGxMZWZ0fHxjJiZjLnNjcm9sbExlZnR8fDAsdG9wOmImJmIuc2Nyb2xsVG9wfHxjJiZjLnNjcm9sbFRvcHx8MH19cmV0dXJue2xlZnQ6YS5zY3JvbGxMZWZ0LHRvcDphLnNjcm9sbFRvcH19LHYuZ2V0Vmlld3BvcnRTaXplPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0Ijt2YXIgYixjPWEuZG9jdW1lbnQsZD1jLmRvY3VtZW50RWxlbWVudDtyZXR1cm4gYS5pbm5lcldpZHRoP3t3aWR0aDphLmlubmVyV2lkdGgsaGVpZ2h0OmEuaW5uZXJIZWlnaHR9OmQ/e3dpZHRoOmQuY2xpZW50V2lkdGgsaGVpZ2h0OmQuY2xpZW50SGVpZ2h0fTooYj1jLmJvZHkse3dpZHRoOmIuY2xpZW50V2lkdGgsaGVpZ2h0OmIuY2xpZW50SGVpZ2h0fSl9LHYuaWRyZWZzPWZ1bmN0aW9uKGEsYil7InVzZSBzdHJpY3QiO3ZhciBjLGQsZT1kb2N1bWVudCxmPVtdLGc9YS5nZXRBdHRyaWJ1dGUoYik7aWYoZylmb3IoZz1heGUudXRpbHMudG9rZW5MaXN0KGcpLGM9MCxkPWcubGVuZ3RoO2M8ZDtjKyspZi5wdXNoKGUuZ2V0RWxlbWVudEJ5SWQoZ1tjXSkpO3JldHVybiBmfSx2LmlzRm9jdXNhYmxlPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtpZighYXx8YS5kaXNhYmxlZHx8IXYuaXNWaXNpYmxlKGEpJiYiQVJFQSIhPT1hLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCkpcmV0dXJuITE7c3dpdGNoKGEubm9kZU5hbWUudG9VcHBlckNhc2UoKSl7Y2FzZSJBIjpjYXNlIkFSRUEiOmlmKGEuaHJlZilyZXR1cm4hMDticmVhaztjYXNlIklOUFVUIjpyZXR1cm4iaGlkZGVuIiE9PWEudHlwZTtjYXNlIlRFWFRBUkVBIjpjYXNlIlNFTEVDVCI6Y2FzZSJERVRBSUxTIjpjYXNlIkJVVFRPTiI6cmV0dXJuITB9dmFyIGI9YS5nZXRBdHRyaWJ1dGUoInRhYmluZGV4Iik7cmV0dXJuISghYnx8aXNOYU4ocGFyc2VJbnQoYiwxMCkpKX0sdi5pc0hUTUw1PWZ1bmN0aW9uKGEpe3ZhciBiPWEuZG9jdHlwZTtyZXR1cm4gbnVsbCE9PWImJigiaHRtbCI9PT1iLm5hbWUmJiFiLnB1YmxpY0lkJiYhYi5zeXN0ZW1JZCl9O3ZhciB6PVsiYmxvY2siLCJsaXN0LWl0ZW0iLCJ0YWJsZSIsImZsZXgiLCJncmlkIiwiaW5saW5lLWJsb2NrIl07di5pc0luVGV4dEJsb2NrPWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtpZihnKGEpKXJldHVybiExO2Zvcih2YXIgYj1hLnBhcmVudE5vZGU7MT09PWIubm9kZVR5cGUmJiFnKGIpOyliPWIucGFyZW50Tm9kZTt2YXIgYz0iIixkPSIiLGU9MDtyZXR1cm4gZihiLGZ1bmN0aW9uKGIpe2lmKDI9PT1lKXJldHVybiExO2lmKDM9PT1iLm5vZGVUeXBlJiYoYys9Yi5ub2RlVmFsdWUpLDE9PT1iLm5vZGVUeXBlKXt2YXIgZj0oYi5ub2RlTmFtZXx8IiIpLnRvVXBwZXJDYXNlKCk7aWYoWyJCUiIsIkhSIl0uaW5kZXhPZihmKSE9PS0xKTA9PT1lPyhjPSIiLGQ9IiIpOmU9MjtlbHNle2lmKCJub25lIj09PWIuc3R5bGUuZGlzcGxheXx8ImhpZGRlbiI9PT1iLnN0eWxlLm92ZXJmbG93fHxbIiIsbnVsbCwibm9uZSJdLmluZGV4T2YoYi5zdHlsZVsiZmxvYXQiXSk9PT0tMXx8WyIiLG51bGwsInJlbGF0aXZlIl0uaW5kZXhPZihiLnN0eWxlLnBvc2l0aW9uKT09PS0xKXJldHVybiExO2lmKCJBIj09PWYmJmIuaHJlZnx8ImxpbmsiPT09KGIuZ2V0QXR0cmlidXRlKCJyb2xlIil8fCIiKS50b0xvd2VyQ2FzZSgpKXJldHVybiBiPT09YSYmKGU9MSksZCs9Yi50ZXh0Q29udGVudCwhMX19fSksYz1heGUuY29tbW9ucy50ZXh0LnNhbml0aXplKGMpLGQ9YXhlLmNvbW1vbnMudGV4dC5zYW5pdGl6ZShkKSxjLmxlbmd0aD5kLmxlbmd0aH0sdi5pc05vZGU9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3JldHVybiBhIGluc3RhbmNlb2YgTm9kZX0sdi5pc09mZnNjcmVlbj1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7dmFyIGIsYz1kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQsZD13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShkb2N1bWVudC5ib2R5fHxjKS5nZXRQcm9wZXJ0eVZhbHVlKCJkaXJlY3Rpb24iKSxlPXYuZ2V0RWxlbWVudENvb3JkaW5hdGVzKGEpO2lmKGUuYm90dG9tPDApcmV0dXJuITA7aWYoImx0ciI9PT1kKXtpZihlLnJpZ2h0PDApcmV0dXJuITB9ZWxzZSBpZihiPU1hdGgubWF4KGMuc2Nyb2xsV2lkdGgsdi5nZXRWaWV3cG9ydFNpemUod2luZG93KS53aWR0aCksZS5sZWZ0PmIpcmV0dXJuITA7cmV0dXJuITF9LHYuaXNWaXNpYmxlPWZ1bmN0aW9uKGEsYixjKXsidXNlIHN0cmljdCI7dmFyIGQsZT1hLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCksZj1hLnBhcmVudE5vZGU7cmV0dXJuIDk9PT1hLm5vZGVUeXBlfHwoZD13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShhLG51bGwpLG51bGwhPT1kJiYoISgibm9uZSI9PT1kLmdldFByb3BlcnR5VmFsdWUoImRpc3BsYXkiKXx8IlNUWUxFIj09PWUudG9VcHBlckNhc2UoKXx8IlNDUklQVCI9PT1lLnRvVXBwZXJDYXNlKCl8fCFiJiZoKGQuZ2V0UHJvcGVydHlWYWx1ZSgiY2xpcCIpKXx8IWMmJigiaGlkZGVuIj09PWQuZ2V0UHJvcGVydHlWYWx1ZSgidmlzaWJpbGl0eSIpfHwhYiYmdi5pc09mZnNjcmVlbihhKSl8fGImJiJ0cnVlIj09PWEuZ2V0QXR0cmlidXRlKCJhcmlhLWhpZGRlbiIpKSYmKCEhZiYmdi5pc1Zpc2libGUoZixiLCEwKSkpKX0sdi5pc1Zpc3VhbENvbnRlbnQ9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3N3aXRjaChhLnRhZ05hbWUudG9VcHBlckNhc2UoKSl7Y2FzZSJJTUciOmNhc2UiSUZSQU1FIjpjYXNlIk9CSkVDVCI6Y2FzZSJWSURFTyI6Y2FzZSJBVURJTyI6Y2FzZSJDQU5WQVMiOmNhc2UiU1ZHIjpjYXNlIk1BVEgiOmNhc2UiQlVUVE9OIjpjYXNlIlNFTEVDVCI6Y2FzZSJURVhUQVJFQSI6Y2FzZSJLRVlHRU4iOmNhc2UiUFJPR1JFU1MiOmNhc2UiTUVURVIiOnJldHVybiEwO2Nhc2UiSU5QVVQiOnJldHVybiJoaWRkZW4iIT09YS50eXBlO2RlZmF1bHQ6cmV0dXJuITF9fSx2LnZpc3VhbGx5Q29udGFpbnM9ZnVuY3Rpb24oYSxiKXt2YXIgYz1hLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLGQ9LjAxLGU9e3RvcDpjLnRvcCtkLGJvdHRvbTpjLmJvdHRvbS1kLGxlZnQ6Yy5sZWZ0K2QscmlnaHQ6Yy5yaWdodC1kfSxmPWIuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCksZz1mLnRvcCxoPWYubGVmdCxpPXt0b3A6Zy1iLnNjcm9sbFRvcCxib3R0b206Zy1iLnNjcm9sbFRvcCtiLnNjcm9sbEhlaWdodCxsZWZ0OmgtYi5zY3JvbGxMZWZ0LHJpZ2h0OmgtYi5zY3JvbGxMZWZ0K2Iuc2Nyb2xsV2lkdGh9O2lmKGUubGVmdDxpLmxlZnQmJmUubGVmdDxmLmxlZnR8fGUudG9wPGkudG9wJiZlLnRvcDxmLnRvcHx8ZS5yaWdodD5pLnJpZ2h0JiZlLnJpZ2h0PmYucmlnaHR8fGUuYm90dG9tPmkuYm90dG9tJiZlLmJvdHRvbT5mLmJvdHRvbSlyZXR1cm4hMTt2YXIgaj13aW5kb3cuZ2V0Q29tcHV0ZWRTdHlsZShiKTtyZXR1cm4hKGUucmlnaHQ+Zi5yaWdodHx8ZS5ib3R0b20+Zi5ib3R0b20pfHwoInNjcm9sbCI9PT1qLm92ZXJmbG93fHwiYXV0byI9PT1qLm92ZXJmbG93fHwiaGlkZGVuIj09PWoub3ZlcmZsb3d8fGIgaW5zdGFuY2VvZiBIVE1MQm9keUVsZW1lbnR8fGIgaW5zdGFuY2VvZiBIVE1MSHRtbEVsZW1lbnQpfSx2LnZpc3VhbGx5T3ZlcmxhcHM9ZnVuY3Rpb24oYSxiKXt2YXIgYz1iLmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpLGQ9Yy50b3AsZT1jLmxlZnQsZj17dG9wOmQtYi5zY3JvbGxUb3AsYm90dG9tOmQtYi5zY3JvbGxUb3ArYi5zY3JvbGxIZWlnaHQsbGVmdDplLWIuc2Nyb2xsTGVmdCxyaWdodDplLWIuc2Nyb2xsTGVmdCtiLnNjcm9sbFdpZHRofTtpZihhLmxlZnQ+Zi5yaWdodCYmYS5sZWZ0PmMucmlnaHR8fGEudG9wPmYuYm90dG9tJiZhLnRvcD5jLmJvdHRvbXx8YS5yaWdodDxmLmxlZnQmJmEucmlnaHQ8Yy5sZWZ0fHxhLmJvdHRvbTxmLnRvcCYmYS5ib3R0b208Yy50b3ApcmV0dXJuITE7dmFyIGc9d2luZG93LmdldENvbXB1dGVkU3R5bGUoYik7cmV0dXJuIShhLmxlZnQ+Yy5yaWdodHx8YS50b3A+Yy5ib3R0b20pfHwoInNjcm9sbCI9PT1nLm92ZXJmbG93fHwiYXV0byI9PT1nLm92ZXJmbG93fHxiIGluc3RhbmNlb2YgSFRNTEJvZHlFbGVtZW50fHxiIGluc3RhbmNlb2YgSFRNTEh0bWxFbGVtZW50KX0sdy5nZXRBbGxDZWxscz1mdW5jdGlvbihhKXt2YXIgYixjLGQsZSxmPVtdO2ZvcihiPTAsZD1hLnJvd3MubGVuZ3RoO2I8ZDtiKyspZm9yKGM9MCxlPWEucm93c1tiXS5jZWxscy5sZW5ndGg7YzxlO2MrKylmLnB1c2goYS5yb3dzW2JdLmNlbGxzW2NdKTtyZXR1cm4gZn0sdy5nZXRDZWxsUG9zaXRpb249ZnVuY3Rpb24oYSxiKXt2YXIgYyxkO2ZvcihifHwoYj13LnRvR3JpZCh2LmZpbmRVcChhLCJ0YWJsZSIpKSksYz0wO2M8Yi5sZW5ndGg7YysrKWlmKGJbY10mJihkPWJbY10uaW5kZXhPZihhKSxkIT09LTEpKXJldHVybnt4OmQseTpjfX0sdy5nZXRIZWFkZXJzPWZ1bmN0aW9uKGEpe2lmKGEuaGFzQXR0cmlidXRlKCJoZWFkZXJzIikpcmV0dXJuIGNvbW1vbnMuZG9tLmlkcmVmcyhhLCJoZWFkZXJzIik7dmFyIGI9Y29tbW9ucy50YWJsZS50b0dyaWQoY29tbW9ucy5kb20uZmluZFVwKGEsInRhYmxlIikpLGM9Y29tbW9ucy50YWJsZS5nZXRDZWxsUG9zaXRpb24oYSxiKSxkPXcudHJhdmVyc2UoImxlZnQiLGMsYikuZmlsdGVyKGZ1bmN0aW9uKGEpe3JldHVybiB3LmlzUm93SGVhZGVyKGEpfSksZT13LnRyYXZlcnNlKCJ1cCIsYyxiKS5maWx0ZXIoZnVuY3Rpb24oYSl7cmV0dXJuIHcuaXNDb2x1bW5IZWFkZXIoYSl9KTtyZXR1cm5bXS5jb25jYXQoZCxlKS5yZXZlcnNlKCl9LHcuZ2V0U2NvcGU9ZnVuY3Rpb24oYSl7dmFyIGI9YS5nZXRBdHRyaWJ1dGUoInNjb3BlIiksYz1hLmdldEF0dHJpYnV0ZSgicm9sZSIpO2lmKGEgaW5zdGFuY2VvZiBFbGVtZW50PT0hMXx8WyJURCIsIlRIIl0uaW5kZXhPZihhLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCkpPT09LTEpdGhyb3cgbmV3IFR5cGVFcnJvcigiRXhwZWN0ZWQgVEQgb3IgVEggZWxlbWVudCIpO2lmKCJjb2x1bW5oZWFkZXIiPT09YylyZXR1cm4iY29sIjtpZigicm93aGVhZGVyIj09PWMpcmV0dXJuInJvdyI7aWYoImNvbCI9PT1ifHwicm93Ij09PWIpcmV0dXJuIGI7aWYoIlRIIiE9PWEubm9kZU5hbWUudG9VcHBlckNhc2UoKSlyZXR1cm4hMTt2YXIgZD13LnRvR3JpZCh2LmZpbmRVcChhLCJ0YWJsZSIpKSxlPXcuZ2V0Q2VsbFBvc2l0aW9uKGEpLGY9ZFtlLnldLnJlZHVjZShmdW5jdGlvbihhLGIpe3JldHVybiBhJiYiVEgiPT09Yi5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpfSwhMCk7aWYoZilyZXR1cm4iY29sIjt2YXIgZz1kLm1hcChmdW5jdGlvbihhKXtyZXR1cm4gYVtlLnhdfSkucmVkdWNlKGZ1bmN0aW9uKGEsYil7cmV0dXJuIGEmJiJUSCI9PT1iLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCl9LCEwKTtyZXR1cm4gZz8icm93IjoiYXV0byJ9LHcuaXNDb2x1bW5IZWFkZXI9ZnVuY3Rpb24oYSl7cmV0dXJuWyJjb2wiLCJhdXRvIl0uaW5kZXhPZih3LmdldFNjb3BlKGEpKSE9PS0xfSx3LmlzRGF0YUNlbGw9ZnVuY3Rpb24oYSl7cmV0dXJuISghYS5jaGlsZHJlbi5sZW5ndGgmJiFhLnRleHRDb250ZW50LnRyaW0oKSkmJiJURCI9PT1hLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCl9LHcuaXNEYXRhVGFibGU9ZnVuY3Rpb24oYSl7dmFyIGI9YS5nZXRBdHRyaWJ1dGUoInJvbGUiKTtpZigoInByZXNlbnRhdGlvbiI9PT1ifHwibm9uZSI9PT1iKSYmIXYuaXNGb2N1c2FibGUoYSkpcmV0dXJuITE7aWYoInRydWUiPT09YS5nZXRBdHRyaWJ1dGUoImNvbnRlbnRlZGl0YWJsZSIpfHx2LmZpbmRVcChhLCdbY29udGVudGVkaXRhYmxlPSJ0cnVlIl0nKSlyZXR1cm4hMDtpZigiZ3JpZCI9PT1ifHwidHJlZWdyaWQiPT09Ynx8InRhYmxlIj09PWIpcmV0dXJuITA7aWYoImxhbmRtYXJrIj09PWNvbW1vbnMuYXJpYS5nZXRSb2xlVHlwZShiKSlyZXR1cm4hMDtpZigiMCI9PT1hLmdldEF0dHJpYnV0ZSgiZGF0YXRhYmxlIikpcmV0dXJuITE7aWYoYS5nZXRBdHRyaWJ1dGUoInN1bW1hcnkiKSlyZXR1cm4hMDtpZihhLnRIZWFkfHxhLnRGb290fHxhLmNhcHRpb24pcmV0dXJuITA7Zm9yKHZhciBjPTAsZD1hLmNoaWxkcmVuLmxlbmd0aDtjPGQ7YysrKWlmKCJDT0xHUk9VUCI9PT1hLmNoaWxkcmVuW2NdLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCkpcmV0dXJuITA7Zm9yKHZhciBlLGYsZz0wLGg9YS5yb3dzLmxlbmd0aCxpPSExLGo9MDtqPGg7aisrKXtlPWEucm93c1tqXTtmb3IodmFyIGs9MCxsPWUuY2VsbHMubGVuZ3RoO2s8bDtrKyspe2lmKGY9ZS5jZWxsc1trXSwiVEgiPT09Zi5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpKXJldHVybiEwO2lmKGl8fGYub2Zmc2V0V2lkdGg9PT1mLmNsaWVudFdpZHRoJiZmLm9mZnNldEhlaWdodD09PWYuY2xpZW50SGVpZ2h0fHwoaT0hMCksZi5nZXRBdHRyaWJ1dGUoInNjb3BlIil8fGYuZ2V0QXR0cmlidXRlKCJoZWFkZXJzIil8fGYuZ2V0QXR0cmlidXRlKCJhYmJyIikpcmV0dXJuITA7aWYoWyJjb2x1bW5oZWFkZXIiLCJyb3doZWFkZXIiXS5pbmRleE9mKGYuZ2V0QXR0cmlidXRlKCJyb2xlIikpIT09LTEpcmV0dXJuITA7aWYoMT09PWYuY2hpbGRyZW4ubGVuZ3RoJiYiQUJCUiI9PT1mLmNoaWxkcmVuWzBdLm5vZGVOYW1lLnRvVXBwZXJDYXNlKCkpcmV0dXJuITA7ZysrfX1pZihhLmdldEVsZW1lbnRzQnlUYWdOYW1lKCJ0YWJsZSIpLmxlbmd0aClyZXR1cm4hMTtpZihoPDIpcmV0dXJuITE7dmFyIG09YS5yb3dzW01hdGguY2VpbChoLzIpXTtpZigxPT09bS5jZWxscy5sZW5ndGgmJjE9PT1tLmNlbGxzWzBdLmNvbFNwYW4pcmV0dXJuITE7aWYobS5jZWxscy5sZW5ndGg+PTUpcmV0dXJuITA7aWYoaSlyZXR1cm4hMDt2YXIgbixvO2ZvcihqPTA7ajxoO2orKyl7aWYoZT1hLnJvd3Nbal0sbiYmbiE9PXdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGUpLmdldFByb3BlcnR5VmFsdWUoImJhY2tncm91bmQtY29sb3IiKSlyZXR1cm4hMDtpZihuPXdpbmRvdy5nZXRDb21wdXRlZFN0eWxlKGUpLmdldFByb3BlcnR5VmFsdWUoImJhY2tncm91bmQtY29sb3IiKSxvJiZvIT09d2luZG93LmdldENvbXB1dGVkU3R5bGUoZSkuZ2V0UHJvcGVydHlWYWx1ZSgiYmFja2dyb3VuZC1pbWFnZSIpKXJldHVybiEwO289d2luZG93LmdldENvbXB1dGVkU3R5bGUoZSkuZ2V0UHJvcGVydHlWYWx1ZSgiYmFja2dyb3VuZC1pbWFnZSIpfXJldHVybiBoPj0yMHx8ISh2LmdldEVsZW1lbnRDb29yZGluYXRlcyhhKS53aWR0aD4uOTUqdi5nZXRWaWV3cG9ydFNpemUod2luZG93KS53aWR0aCkmJighKGc8MTApJiYhYS5xdWVyeVNlbGVjdG9yKCJvYmplY3QsIGVtYmVkLCBpZnJhbWUsIGFwcGxldCIpKX0sdy5pc0hlYWRlcj1mdW5jdGlvbihhKXtyZXR1cm4hKCF3LmlzQ29sdW1uSGVhZGVyKGEpJiYhdy5pc1Jvd0hlYWRlcihhKSl8fCEhYS5pZCYmISFkb2N1bWVudC5xdWVyeVNlbGVjdG9yKCdbaGVhZGVyc349IicrYXhlLnV0aWxzLmVzY2FwZVNlbGVjdG9yKGEuaWQpKyciXScpfSx3LmlzUm93SGVhZGVyPWZ1bmN0aW9uKGEpe3JldHVyblsicm93IiwiYXV0byJdLmluZGV4T2Yody5nZXRTY29wZShhKSkhPT0tMX0sdy50b0dyaWQ9ZnVuY3Rpb24oYSl7Zm9yKHZhciBiPVtdLGM9YS5yb3dzLGQ9MCxlPWMubGVuZ3RoO2Q8ZTtkKyspe3ZhciBmPWNbZF0uY2VsbHM7YltkXT1iW2RdfHxbXTtmb3IodmFyIGc9MCxoPTAsaT1mLmxlbmd0aDtoPGk7aCsrKWZvcih2YXIgaj0wO2o8ZltoXS5jb2xTcGFuO2orKyl7Zm9yKHZhciBrPTA7azxmW2hdLnJvd1NwYW47aysrKXtmb3IoYltkK2tdPWJbZCtrXXx8W107YltkK2tdW2ddOylnKys7YltkK2tdW2ddPWZbaF19ZysrfX1yZXR1cm4gYn0sdy50b0FycmF5PXcudG9HcmlkLGZ1bmN0aW9uKGEpe3ZhciBiPWZ1bmN0aW9uIGMoYSxiLGQsZSl7dmFyIGYsZz1kW2IueV0/ZFtiLnldW2IueF06dm9pZCAwO3JldHVybiBnPyJmdW5jdGlvbiI9PXR5cGVvZiBlJiYoZj1lKGcsYixkKSxmPT09ITApP1tnXTooZj1jKGEse3g6Yi54K2EueCx5OmIueSthLnl9LGQsZSksZi51bnNoaWZ0KGcpLGYpOltdfTthLnRyYXZlcnNlPWZ1bmN0aW9uKGEsYyxkLGUpe2lmKEFycmF5LmlzQXJyYXkoYykmJihlPWQsZD1jLGM9e3g6MCx5OjB9KSwic3RyaW5nIj09dHlwZW9mIGEpc3dpdGNoKGEpe2Nhc2UibGVmdCI6YT17eDotMSx5OjB9O2JyZWFrO2Nhc2UidXAiOmE9e3g6MCx5Oi0xfTticmVhaztjYXNlInJpZ2h0IjphPXt4OjEseTowfTticmVhaztjYXNlImRvd24iOmE9e3g6MCx5OjF9fXJldHVybiBiKGEse3g6Yy54K2EueCx5OmMueSthLnl9LGQsZSl9fSh3KTt2YXIgQT17c3VibWl0OiJTdWJtaXQiLHJlc2V0OiJSZXNldCJ9LEI9WyJ0ZXh0Iiwic2VhcmNoIiwidGVsIiwidXJsIiwiZW1haWwiLCJkYXRlIiwidGltZSIsIm51bWJlciIsInJhbmdlIiwiY29sb3IiXSxDPVsiQSIsIkVNIiwiU1RST05HIiwiU01BTEwiLCJNQVJLIiwiQUJCUiIsIkRGTiIsIkkiLCJCIiwiUyIsIlUiLCJDT0RFIiwiVkFSIiwiU0FNUCIsIktCRCIsIlNVUCIsIlNVQiIsIlEiLCJDSVRFIiwiU1BBTiIsIkJETyIsIkJESSIsIkJSIiwiV0JSIiwiSU5TIiwiREVMIiwiSU1HIiwiRU1CRUQiLCJPQkpFQ1QiLCJJRlJBTUUiLCJNQVAiLCJBUkVBIiwiU0NSSVBUIiwiTk9TQ1JJUFQiLCJSVUJZIiwiVklERU8iLCJBVURJTyIsIklOUFVUIiwiVEVYVEFSRUEiLCJTRUxFQ1QiLCJCVVRUT04iLCJMQUJFTCIsIk9VVFBVVCIsIkRBVEFMSVNUIiwiS0VZR0VOIiwiUFJPR1JFU1MiLCJDT01NQU5EIiwiQ0FOVkFTIiwiVElNRSIsIk1FVEVSIl07cmV0dXJuIHguYWNjZXNzaWJsZVRleHQ9ZnVuY3Rpb24oYSxiKXtmdW5jdGlvbiBjKGEsYixjKXsKZm9yKHZhciBkLGU9YS5jaGlsZE5vZGVzLGc9IiIsaD0wO2g8ZS5sZW5ndGg7aCsrKWQ9ZVtoXSwzPT09ZC5ub2RlVHlwZT9nKz1kLnRleHRDb250ZW50OjE9PT1kLm5vZGVUeXBlJiYoQy5pbmRleE9mKGQubm9kZU5hbWUudG9VcHBlckNhc2UoKSk9PT0tMSYmKGcrPSIgIiksZys9ZihlW2hdLGIsYykpO3JldHVybiBnfWZ1bmN0aW9uIGQoYSxiLGQpe3ZhciBlPSIiLGc9YS5ub2RlTmFtZS50b1VwcGVyQ2FzZSgpO2lmKGwoYSkmJihlPWMoYSwhMSwhMSl8fCIiLHIoZSkpKXJldHVybiBlO2lmKCJGSUdVUkUiPT09ZyYmKGU9byhhLCJmaWdjYXB0aW9uIikscihlKSkpcmV0dXJuIGU7aWYoIlRBQkxFIj09PWcpe2lmKGU9byhhLCJjYXB0aW9uIikscihlKSlyZXR1cm4gZTtpZihlPWEuZ2V0QXR0cmlidXRlKCJ0aXRsZSIpfHxhLmdldEF0dHJpYnV0ZSgic3VtbWFyeSIpfHwiIixyKGUpKXJldHVybiBlfWlmKHEoYSkpcmV0dXJuIGEuZ2V0QXR0cmlidXRlKCJhbHQiKXx8IiI7aWYoayhhKSYmIWQpe2lmKGooYSkpcmV0dXJuIGEudmFsdWV8fGEudGl0bGV8fEFbYS50eXBlXXx8IiI7dmFyIGg9aShhKTtpZihoKXJldHVybiBmKGgsYiwhMCl9cmV0dXJuIiJ9ZnVuY3Rpb24gZShhLGIsYyl7cmV0dXJuIWImJmEuaGFzQXR0cmlidXRlKCJhcmlhLWxhYmVsbGVkYnkiKT94LnNhbml0aXplKHYuaWRyZWZzKGEsImFyaWEtbGFiZWxsZWRieSIpLm1hcChmdW5jdGlvbihiKXtyZXR1cm4gYT09PWImJmcucG9wKCksZihiLCEwLGEhPT1iKX0pLmpvaW4oIiAiKSk6YyYmcChhKXx8IWEuaGFzQXR0cmlidXRlKCJhcmlhLWxhYmVsIik/IiI6eC5zYW5pdGl6ZShhLmdldEF0dHJpYnV0ZSgiYXJpYS1sYWJlbCIpKX12YXIgZixnPVtdO3JldHVybiBmPWZ1bmN0aW9uKGEsYixmKXsidXNlIHN0cmljdCI7dmFyIGg7aWYobnVsbD09PWF8fGcuaW5kZXhPZihhKSE9PS0xKXJldHVybiIiO2lmKCFiJiYhdi5pc1Zpc2libGUoYSwhMCkpcmV0dXJuIiI7Zy5wdXNoKGEpO3ZhciBpPWEuZ2V0QXR0cmlidXRlKCJyb2xlIik7cmV0dXJuIGg9ZShhLGIsZikscihoKT9oOihoPWQoYSxiLGYpLHIoaCk/aDpmJiYoaD1uKGEpLHIoaCkpP2g6bShhKXx8aSYmcy5nZXRSb2xlc1dpdGhOYW1lRnJvbUNvbnRlbnRzKCkuaW5kZXhPZihpKT09PS0xfHwoaD1jKGEsYixmKSwhcihoKSk/YS5oYXNBdHRyaWJ1dGUoInRpdGxlIik/YS5nZXRBdHRyaWJ1dGUoInRpdGxlIik6IiI6aCl9LHguc2FuaXRpemUoZihhLGIpKX0seC5sYWJlbD1mdW5jdGlvbihhKXt2YXIgYixjO3JldHVybihjPXMubGFiZWwoYSkpP2M6YS5pZCYmKGI9ZG9jdW1lbnQucXVlcnlTZWxlY3RvcignbGFiZWxbZm9yPSInK2F4ZS51dGlscy5lc2NhcGVTZWxlY3RvcihhLmlkKSsnIl0nKSxjPWImJngudmlzaWJsZShiLCEwKSk/YzooYj12LmZpbmRVcChhLCJsYWJlbCIpLGM9YiYmeC52aXNpYmxlKGIsITApLGM/YzpudWxsKX0seC5zYW5pdGl6ZT1mdW5jdGlvbihhKXsidXNlIHN0cmljdCI7cmV0dXJuIGEucmVwbGFjZSgvXHJcbi9nLCJcbiIpLnJlcGxhY2UoL1x1MDBBMC9nLCIgIikucmVwbGFjZSgvW1xzXXsyLH0vZywiICIpLnRyaW0oKX0seC52aXNpYmxlPWZ1bmN0aW9uKGEsYixjKXsidXNlIHN0cmljdCI7dmFyIGQsZSxmLGc9YS5jaGlsZE5vZGVzLGg9Zy5sZW5ndGgsaT0iIjtmb3IoZD0wO2Q8aDtkKyspZT1nW2RdLDM9PT1lLm5vZGVUeXBlPyhmPWUubm9kZVZhbHVlLGYmJnYuaXNWaXNpYmxlKGEsYikmJihpKz1lLm5vZGVWYWx1ZSkpOmN8fChpKz14LnZpc2libGUoZSxiKSk7cmV0dXJuIHguc2FuaXRpemUoaSl9LGF4ZS51dGlscy50b0FycmF5PWZ1bmN0aW9uKGEpeyJ1c2Ugc3RyaWN0IjtyZXR1cm4gQXJyYXkucHJvdG90eXBlLnNsaWNlLmNhbGwoYSl9LGF4ZS51dGlscy50b2tlbkxpc3Q9ZnVuY3Rpb24oYSl7InVzZSBzdHJpY3QiO3JldHVybiBhLnRyaW0oKS5yZXBsYWNlKC9cc3syLH0vZywiICIpLnNwbGl0KCIgIil9LGNvbW1vbnN9KCl9KX0oIm9iamVjdCI9PXR5cGVvZiB3aW5kb3c/d2luZG93OnRoaXMpOw==","base64"); +const axeLibSource = "!function a(window){function b(a){this.name=\"SupportError\",this.cause=a.cause,this.message=\"`\"+a.cause+\"` - feature unsupported in your environment.\",a.ruleId&&(this.ruleId=a.ruleId,this.message+=\" Skipping \"+this.ruleId+\" rule.\"),this.stack=(new Error).stack}function c(a){\"use strict\";var b;return a?(b=axe.utils.clone(a),b.commons=a.commons):b={},b.reporter=b.reporter||null,b.rules=b.rules||[],b.checks=b.checks||[],b.data=Object.assign({checks:{},rules:{}},b.data),b}function d(a,b,c){\"use strict\";var d,e;for(d=0,e=a.length;d<e;d++)b[c](a[d])}function e(a){this.brand=\"axe\",this.application=\"axeAPI\",this.tagExclude=[\"experimental\"],this.defaultConfig=a,this._init()}function f(a,b,c){var d=a.brand,e=a.application;return axe.constants.helpUrlBase+d+\"/\"+(c||axe.version.substring(0,axe.version.lastIndexOf(\".\")))+\"/\"+b+\"?application=\"+e}function g(a){\"use strict\";this.id=a.id,this.data=null,this.relatedNodes=[],this.result=null}function h(a){\"use strict\";return\"string\"==typeof a?new Function(\"return \"+a+\";\")():a}function i(a){a&&(this.id=a.id,this.configure(a))}function j(a,b){\"use strict\";if(!axe.utils.isHidden(b)){axe.utils.findBy(a,\"node\",b)||a.push({node:b,include:[],exclude:[]})}}function k(a,b,c){\"use strict\";a.frames=a.frames||[];var d,e,f=document.querySelectorAll(c.shift());a:for(var g=0,h=f.length;g<h;g++){e=f[g];for(var i=0,j=a.frames.length;i<j;i++)if(a.frames[i].node===e){a.frames[i][b].push(c);break a}d={node:e,include:[],exclude:[]},c&&d[b].push(c),a.frames.push(d)}}function l(a){\"use strict\";if(a&&\"object\"===(void 0===a?\"undefined\":Z(a))||a instanceof NodeList){if(a instanceof Node)return{include:[a],exclude:[]};if(a.hasOwnProperty(\"include\")||a.hasOwnProperty(\"exclude\"))return{include:a.include&&+a.include.length?a.include:[document],exclude:a.exclude||[]};if(a.length===+a.length)return{include:a,exclude:[]}}return\"string\"==typeof a?{include:[a],exclude:[]}:{include:[document],exclude:[]}}function m(a,b){\"use strict\";for(var c,d=[],e=0,f=a[b].length;e<f;e++){if(\"string\"==typeof(c=a[b][e])){d=d.concat(axe.utils.toArray(document.querySelectorAll(c)));break}!c||!c.length||c instanceof Node?d.push(c):c.length>1?k(a,b,c):d=d.concat(axe.utils.toArray(document.querySelectorAll(c[0])))}return d.filter(function(a){return a})}function n(a){\"use strict\";if(0===a.include.length){if(0===a.frames.length){var b=axe.utils.respondable.isInFrame()?\"frame\":\"page\";return new Error(\"No elements found for include in \"+b+\" Context\")}a.frames.forEach(function(a,b){if(0===a.include.length)return new Error(\"No elements found for include in Context of frame \"+b)})}}function o(a){\"use strict\";var b=this;this.frames=[],this.initiator=!a||\"boolean\"!=typeof a.initiator||a.initiator,this.page=!1,a=l(a),this.exclude=a.exclude,this.include=a.include,this.include=m(this,\"include\"),this.exclude=m(this,\"exclude\"),axe.utils.select(\"frame, iframe\",this).forEach(function(a){X(a,b)&&j(b.frames,a)}),1===this.include.length&&this.include[0]===document&&(this.page=!0);var c=n(this);if(c instanceof Error)throw c}function p(a){\"use strict\";this.id=a.id,this.result=axe.constants.NA,this.pageLevel=a.pageLevel,this.impact=null,this.nodes=[]}function q(a,b){\"use strict\";this._audit=b,this.id=a.id,this.selector=a.selector||\"*\",this.excludeHidden=\"boolean\"!=typeof a.excludeHidden||a.excludeHidden,this.enabled=\"boolean\"!=typeof a.enabled||a.enabled,this.pageLevel=\"boolean\"==typeof a.pageLevel&&a.pageLevel,this.any=a.any||[],this.all=a.all||[],this.none=a.none||[],this.tags=a.tags||[],a.matches&&(this.matches=h(a.matches))}function r(a){\"use strict\";return axe.utils.getAllChecks(a).map(function(b){var c=a._audit.checks[b.id||b];return c&&\"function\"==typeof c.after?c:null}).filter(Boolean)}function s(a,b){\"use strict\";var c=[];return a.forEach(function(a){axe.utils.getAllChecks(a).forEach(function(a){a.id===b&&c.push(a)})}),c}function t(a){\"use strict\";return a.filter(function(a){return!0!==a.filtered})}function u(a){\"use strict\";var b=[\"any\",\"all\",\"none\"],c=a.nodes.filter(function(a){var c=0;return b.forEach(function(b){a[b]=t(a[b]),c+=a[b].length}),c>0});return a.pageLevel&&c.length&&(c=[c.reduce(function(a,c){if(a)return b.forEach(function(b){a[b].push.apply(a[b],c[b])}),a})]),c}function v(a,b){\"use strict\";if(!axe._audit)throw new Error(\"No audit configured\");var c=axe.utils.queue(),d=[];Object.keys(axe.plugins).forEach(function(a){c.defer(function(b){var c=function(a){d.push(a),b()};try{axe.plugins[a].cleanup(b,c)}catch(a){c(a)}})}),axe.utils.toArray(document.querySelectorAll(\"frame, iframe\")).forEach(function(a){c.defer(function(b,c){return axe.utils.sendCommandToFrame(a,{command:\"cleanup-plugin\"},b,c)})}),c.then(function(c){0===d.length?a(c):b(d)}).catch(b)}function w(a){\"use strict\";var b;if(!(b=axe._audit))throw new Error(\"No audit configured\");a.reporter&&(\"function\"==typeof a.reporter||aa[a.reporter])&&(b.reporter=a.reporter),a.checks&&a.checks.forEach(function(a){b.addCheck(a)}),a.rules&&a.rules.forEach(function(a){b.addRule(a)}),void 0!==a.branding?b.setBranding(a.branding):b._constructHelpUrls(),a.tagExclude&&(b.tagExclude=a.tagExclude)}function x(a,b,c){\"use strict\";var d=c,e=function(a){a instanceof Error==!1&&(a=new Error(a)),c(a)},f=a&&a.context||{};f.include&&!f.include.length&&(f.include=[document]);var g=a&&a.options||{};switch(a.command){case\"rules\":return A(f,g,d,e);case\"cleanup-plugin\":return v(d,e);default:if(axe._audit&&axe._audit.commands&&axe._audit.commands[a.command])return axe._audit.commands[a.command](a,c)}}function y(a){\"use strict\";this._run=a.run,this._collect=a.collect,this._registry={},a.commands.forEach(function(a){axe._audit.registerCommand(a)})}function z(){\"use strict\";var a=axe._audit;if(!a)throw new Error(\"No audit configured\");a.resetRulesAndChecks()}function A(a,b,c,d){\"use strict\";try{a=new o(a)}catch(a){return d(a)}var e=axe.utils.queue(),f=axe._audit;b.performanceTimer&&axe.utils.performanceTimer.auditStart(),a.frames.length&&!1!==b.iframes&&e.defer(function(c,d){axe.utils.collectResultsFromFrames(a,b,\"rules\",null,c,d)}),e.defer(function(c,d){f.run(a,b,c,d)}),e.then(function(e){try{b.performanceTimer&&axe.utils.performanceTimer.auditEnd();var g=axe.utils.mergeResults(e.map(function(a){return{results:a}}));a.initiator&&(g=f.after(g,b),g.forEach(axe.utils.publishMetaData),g=g.map(axe.utils.finalizeRuleResult));try{c(g)}catch(a){axe.log(a)}}catch(a){d(a)}}).catch(d)}function B(a){\"use strict\";switch(!0){case\"string\"==typeof a:case Array.isArray(a):case Node&&a instanceof Node:case NodeList&&a instanceof NodeList:return!0;case\"object\"!==(void 0===a?\"undefined\":Z(a)):return!1;case void 0!==a.include:case void 0!==a.exclude:case\"number\"==typeof a.length:return!0;default:return!1}}function C(a,b,c){\"use strict\";var d=new TypeError(\"axe.run arguments are invalid\");if(!B(a)){if(void 0!==c)throw d;c=b,b=a,a=document}if(\"object\"!==(void 0===b?\"undefined\":Z(b))){if(void 0!==c)throw d;c=b,b={}}if(\"function\"!=typeof c&&void 0!==c)throw d;return{context:a,options:b,callback:c||ba}}function D(a,b){\"use strict\";[\"any\",\"all\",\"none\"].forEach(function(c){Array.isArray(a[c])&&a[c].filter(function(a){return Array.isArray(a.relatedNodes)}).forEach(function(a){a.relatedNodes=a.relatedNodes.map(function(a){var c={html:a.source};return b.elementRef&&!a.fromFrame&&(c.element=a.element),(!1!==b.selectors||a.fromFrame)&&(c.target=a.selector),b.xpath&&(c.xpath=a.xpath),c})})})}function E(a,b){return ea.reduce(function(c,d){return c[d]=(a[d]||[]).map(function(a){return b(a,d)}),c},{})}function F(a,b,c){var d=Object.assign({},b);d.nodes=(d[c]||[]).concat(),axe.constants.resultGroups.forEach(function(a){delete d[a]}),a[c].push(d)}function G(a,b,c){\"use strict\";var d=window.getComputedStyle(a,null),e=!1;return!!d&&(b.forEach(function(a){d.getPropertyValue(a.property)===a.value&&(e=!0)}),!!e||!(a.nodeName.toUpperCase()===c.toUpperCase()||!a.parentNode)&&G(a.parentNode,b,c))}function H(a,b){\"use strict\";return new Error(a+\": \"+axe.utils.getSelector(b))}function I(a,b,c,d,e,f){\"use strict\";var g=axe.utils.queue();a.frames.forEach(function(e){var f={options:b,command:c,parameter:d,context:{initiator:!1,page:a.page,include:e.include||[],exclude:e.exclude||[]}};g.defer(function(a,b){var c=e.node;axe.utils.sendCommandToFrame(c,f,function(b){if(b)return a({results:b,frameElement:c,frame:axe.utils.getSelector(c)});a(null)},b)})}),g.then(function(a){e(axe.utils.mergeResults(a))}).catch(f)}function J(a,b){\"use strict\";if(b=b||300,a.length>b){var c=a.indexOf(\">\");a=a.substring(0,c+1)}return a}function K(a){\"use strict\";var b=a.outerHTML;return b||\"function\"!=typeof XMLSerializer||(b=(new XMLSerializer).serializeToString(a)),J(b||\"\")}function L(a,b){\"use strict\";this._fromFrame=!!b,this.spec=b||{},this.source=void 0!==this.spec.source?this.spec.source:K(a),this._element=a}function M(a){\"use strict\";var b=1,c=a.nodeName.toUpperCase();for(a=a.previousElementSibling;a;)a.nodeName.toUpperCase()===c&&b++,a=a.previousElementSibling;return b}function N(a,b){\"use strict\";var c,d,e=a.parentNode.children;if(!e)return!1;var f=e.length;for(c=0;c<f;c++)if((d=e[c])!==a&&axe.utils.matchesSelector(d,b))return!0;return!1}function O(a,b){var c,d;if(!a)return[];if(!b&&9===a.nodeType)return b=[{str:\"html\"}];if(b=b||[],a.parentNode&&a.parentNode!==a&&(b=O(a.parentNode,b)),a.previousSibling){d=1,c=a.previousSibling;do{1===c.nodeType&&c.nodeName===a.nodeName&&d++,c=c.previousSibling}while(c);1===d&&(d=null)}else if(a.nextSibling){c=a.nextSibling;do{1===c.nodeType&&c.nodeName===a.nodeName?(d=1,c=null):(d=null,c=c.previousSibling)}while(c)}if(1===a.nodeType){var e={};e.str=a.nodeName.toLowerCase(),a.getAttribute&&a.getAttribute(\"id\")&&1===a.ownerDocument.querySelectorAll(\"#\"+axe.utils.escapeSelector(a.id)).length&&(e.id=a.getAttribute(\"id\")),d>1&&(e.count=d),b.push(e)}return b}function P(a){return a.reduce(function(a,b){return b.id?\"/\"+b.str+\"[@id='\"+b.id+\"']\":a+\"/\"+b.str+(b.count>0?\"[\"+b.count+\"]\":\"\")},\"\")}function Q(a){\"use strict\";if(fa&&fa.parentNode)return void 0===fa.styleSheet?fa.appendChild(document.createTextNode(a)):fa.styleSheet.cssText+=a,fa;if(a){var b=document.head||document.getElementsByTagName(\"head\")[0];return fa=document.createElement(\"style\"),fa.type=\"text/css\",void 0===fa.styleSheet?fa.appendChild(document.createTextNode(a)):fa.styleSheet.cssText=a,b.appendChild(fa),fa}}function R(a,b,c){\"use strict\";var d=axe.utils.getXpath(b),e={element:b,selector:c,xpath:d};a.forEach(function(a){a.node=axe.utils.DqElement.fromFrame(a.node,e);var b=axe.utils.getAllChecks(a);b.length&&b.forEach(function(a){a.relatedNodes=a.relatedNodes.map(function(a){return axe.utils.DqElement.fromFrame(a,e)})})})}function S(a,b){\"use strict\";for(var c,d,e=b[0].node,f=0,g=a.length;f<g;f++)if(d=a[f].node,(c=axe.utils.nodeSorter(d.element,e.element))>0||0===c&&e.selector.length<d.selector.length)return void a.splice.apply(a,[f,0].concat(b));a.push.apply(a,b)}function T(a){\"use strict\";return a&&a.results?Array.isArray(a.results)?a.results.length?a.results:null:[a.results]:null}function U(a,b){\"use strict\";return function(c){var d=a[c.id]||{},e=d.messages||{},f=Object.assign({},d);if(delete f.messages,void 0===c.result)if(\"object\"===Z(e.incomplete)){var g=c.data.missingData;g.forEach(function(a){var b;try{b=a.reason}catch(a){b=\"default\"}f.message=function(){return e.incomplete[b]}})}else f.message=e.incomplete;else f.message=c.result===b?e.pass:e.fail;axe.utils.extendMetaData(c,f)}}function V(a,b){\"use strict\";var c,d,e=axe._audit&&axe._audit.tagExclude?axe._audit.tagExclude:[];return b.include||b.exclude?(c=b.include||[],c=Array.isArray(c)?c:[c],d=b.exclude||[],d=Array.isArray(d)?d:[d],d=d.concat(e.filter(function(a){return-1===c.indexOf(a)}))):(c=Array.isArray(b)?b:[b],d=e.filter(function(a){return-1===c.indexOf(a)})),!!(c.some(function(b){return-1!==a.tags.indexOf(b)})||0===c.length&&!1!==a.enabled)&&d.every(function(b){return-1===a.tags.indexOf(b)})}function W(a){\"use strict\";return a.sort(function(a,b){return axe.utils.contains(a,b)?1:-1})[0]}function X(a,b){\"use strict\";var c=b.include&&W(b.include.filter(function(b){return axe.utils.contains(b,a)})),d=b.exclude&&W(b.exclude.filter(function(b){return axe.utils.contains(b,a)}));return!!(!d&&c||d&&axe.utils.contains(d,c))}function Y(a,b,c){\"use strict\";for(var d=0,e=b.length;d<e;d++)-1===a.indexOf(b[d])&&X(b[d],c)&&a.push(b[d])}var document=window.document,Z=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&\"function\"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?\"symbol\":typeof a},axe=axe||{};axe.version=\"2.2.0\",\"function\"==typeof define&&define.amd&&define([],function(){\"use strict\";return axe}),\"object\"===(\"undefined\"==typeof module?\"undefined\":Z(module))&&module.exports&&\"function\"==typeof a.toString&&(axe.source=\"(\"+a.toString()+\")(this, this.document);\",module.exports=axe),\"function\"==typeof window.getComputedStyle&&(window.axe=axe);var commons;b.prototype=Object.create(Error.prototype),b.prototype.constructor=b;var utils=axe.utils={},$={},Z=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&\"function\"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?\"symbol\":typeof a};e.prototype._init=function(){var a=c(this.defaultConfig);axe.commons=commons=a.commons,this.reporter=a.reporter,this.commands={},this.rules=[],this.checks={},d(a.rules,this,\"addRule\"),d(a.checks,this,\"addCheck\"),this.data={},this.data.checks=a.data&&a.data.checks||{},this.data.rules=a.data&&a.data.rules||{},this.data.failureSummaries=a.data&&a.data.failureSummaries||{},this._constructHelpUrls()},e.prototype.registerCommand=function(a){\"use strict\";this.commands[a.id]=a.callback},e.prototype.addRule=function(a){\"use strict\";a.metadata&&(this.data.rules[a.id]=a.metadata);var b=this.getRule(a.id);b?b.configure(a):this.rules.push(new q(a,this))},e.prototype.addCheck=function(a){\"use strict\";var b=a.metadata;\"object\"===(void 0===b?\"undefined\":Z(b))&&(this.data.checks[a.id]=b,\"object\"===Z(b.messages)&&Object.keys(b.messages).filter(function(a){return b.messages.hasOwnProperty(a)&&\"string\"==typeof b.messages[a]}).forEach(function(a){0===b.messages[a].indexOf(\"function\")&&(b.messages[a]=new Function(\"return \"+b.messages[a]+\";\")())})),this.checks[a.id]?this.checks[a.id].configure(a):this.checks[a.id]=new i(a)},e.prototype.run=function(a,b,c,d){\"use strict\";this.validateOptions(b);var e=axe.utils.queue();this.rules.forEach(function(c){if(axe.utils.ruleShouldRun(c,a,b)){if(b.performanceTimer){var d=\"mark_rule_end_\"+c.id,f=\"mark_rule_start_\"+c.id;axe.utils.performanceTimer.mark(f)}e.defer(function(e,g){c.run(a,b,function(a){b.performanceTimer&&(axe.utils.performanceTimer.mark(d),axe.utils.performanceTimer.measure(\"rule_\"+c.id,f,d)),e(a)},function(a){if(b.debug)g(a);else{var d=Object.assign(new p(c),{result:axe.constants.CANTTELL,description:\"An error occured while running this rule\",message:a.message,help:a.stack||a.message,error:a});e(d)}})})}}),e.then(function(a){c(a.filter(function(a){return!!a}))}).catch(d)},e.prototype.after=function(a,b){\"use strict\";var c=this.rules;return a.map(function(a){return axe.utils.findBy(c,\"id\",a.id).after(a,b)})},e.prototype.getRule=function(a){return this.rules.find(function(b){return b.id===a})},e.prototype.validateOptions=function(a){\"use strict\";var b=this;if(\"object\"===Z(a.runOnly)){var c=a.runOnly;if(\"rule\"===c.type&&Array.isArray(c.value))c.value.forEach(function(a){if(!b.getRule(a))throw new Error(\"unknown rule `\"+a+\"` in options.runOnly\")});else if(Array.isArray(c.value)&&c.value.length>0){var d=[].concat(c.value);if(b.rules.forEach(function(a){var b,c,e;if(d)for(c=0,e=a.tags.length;c<e;c++)-1!==(b=d.indexOf(a.tags[c]))&&d.splice(b,1)}),0!==d.length)throw new Error(\"could not find tags `\"+d.join(\"`, `\")+\"`\")}}return\"object\"===Z(a.rules)&&Object.keys(a.rules).forEach(function(a){if(!b.getRule(a))throw new Error(\"unknown rule `\"+a+\"` in options.rules\")}),a},e.prototype.setBranding=function(a){\"use strict\";var b={brand:this.brand,application:this.application};a&&a.hasOwnProperty(\"brand\")&&a.brand&&\"string\"==typeof a.brand&&(this.brand=a.brand),a&&a.hasOwnProperty(\"application\")&&a.application&&\"string\"==typeof a.application&&(this.application=a.application),this._constructHelpUrls(b)},e.prototype._constructHelpUrls=function(){var a=this,b=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null,c=axe.version.substring(0,axe.version.lastIndexOf(\".\"));this.rules.forEach(function(d){a.data.rules[d.id]||(a.data.rules[d.id]={});var e=a.data.rules[d.id];(\"string\"!=typeof e.helpUrl||b&&e.helpUrl===f(b,d.id,c))&&(e.helpUrl=f(a,d.id,c))})},e.prototype.resetRulesAndChecks=function(){\"use strict\";this._init()},i.prototype.enabled=!0,i.prototype.run=function(a,b,c,d){\"use strict\";b=b||{};var e=b.hasOwnProperty(\"enabled\")?b.enabled:this.enabled,f=b.options||this.options;if(e){var h,i=new g(this),j=axe.utils.checkHelper(i,c,d);try{h=this.evaluate.call(j,a,f)}catch(a){return void d(a)}j.isAsync||(i.result=h,setTimeout(function(){c(i)},0))}else c(null)},i.prototype.configure=function(a){var b=this;[\"options\",\"enabled\"].filter(function(b){return a.hasOwnProperty(b)}).forEach(function(c){return b[c]=a[c]}),[\"evaluate\",\"after\"].filter(function(b){return a.hasOwnProperty(b)}).forEach(function(c){return b[c]=h(a[c])})};var Z=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&\"function\"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?\"symbol\":typeof a};q.prototype.matches=function(){\"use strict\";return!0},q.prototype.gather=function(a){\"use strict\";var b=axe.utils.select(this.selector,a);return this.excludeHidden?b.filter(function(a){return!axe.utils.isHidden(a)}):b},q.prototype.runChecks=function(a,b,c,d,e){\"use strict\";var f=this,g=axe.utils.queue();this[a].forEach(function(a){var d=f._audit.checks[a.id||a],e=axe.utils.getCheckOption(d,f.id,c);g.defer(function(a,c){d.run(b,e,a,c)})}),g.then(function(b){b=b.filter(function(a){return a}),d({type:a,results:b})}).catch(e)},q.prototype.run=function(a,c,d,e){var f=this,g=axe.utils.queue(),h=new p(this),i=void 0;try{i=this.gather(a).filter(function(a){return f.matches(a)})}catch(a){return void e(new b({cause:a,ruleId:this.id}))}c.performanceTimer&&axe.log(\"gather (\",i.length,\"):\",axe.utils.performanceTimer.timeElapsed()+\"ms\"),i.forEach(function(a){g.defer(function(b,d){var e=axe.utils.queue();e.defer(function(b,d){f.runChecks(\"any\",a,c,b,d)}),e.defer(function(b,d){f.runChecks(\"all\",a,c,b,d)}),e.defer(function(b,d){f.runChecks(\"none\",a,c,b,d)}),e.then(function(c){if(c.length){var d=!1,e={};c.forEach(function(a){var b=a.results.filter(function(a){return a});e[a.type]=b,b.length&&(d=!0)}),d&&(e.node=new axe.utils.DqElement(a),h.nodes.push(e))}b()}).catch(function(a){return d(a)})})}),g.then(function(){return d(h)}).catch(function(a){return e(a)})},q.prototype.after=function(a,b){\"use strict\";var c=r(this),d=this.id;return c.forEach(function(c){var e=s(a.nodes,c.id),f=axe.utils.getCheckOption(c,d,b),g=c.after(e,f);e.forEach(function(a){-1===g.indexOf(a)&&(a.filtered=!0)})}),a.nodes=u(a),a},q.prototype.configure=function(a){\"use strict\";a.hasOwnProperty(\"selector\")&&(this.selector=a.selector),a.hasOwnProperty(\"excludeHidden\")&&(this.excludeHidden=\"boolean\"!=typeof a.excludeHidden||a.excludeHidden),a.hasOwnProperty(\"enabled\")&&(this.enabled=\"boolean\"!=typeof a.enabled||a.enabled),a.hasOwnProperty(\"pageLevel\")&&(this.pageLevel=\"boolean\"==typeof a.pageLevel&&a.pageLevel),a.hasOwnProperty(\"any\")&&(this.any=a.any),a.hasOwnProperty(\"all\")&&(this.all=a.all),a.hasOwnProperty(\"none\")&&(this.none=a.none),a.hasOwnProperty(\"tags\")&&(this.tags=a.tags),a.hasOwnProperty(\"matches\")&&(\"string\"==typeof a.matches?this.matches=new Function(\"return \"+a.matches+\";\")():this.matches=a.matches)},function(axe){var a=[{name:\"NA\",value:\"inapplicable\",priority:0,group:\"inapplicable\"},{name:\"PASS\",value:\"passed\",priority:1,group:\"passes\"},{name:\"CANTTELL\",value:\"cantTell\",priority:2,group:\"incomplete\"},{name:\"FAIL\",value:\"failed\",priority:3,group:\"violations\"}],b={helpUrlBase:\"https://dequeuniversity.com/rules/\",results:[],resultGroups:[],resultGroupMap:{},impact:Object.freeze([\"minor\",\"moderate\",\"serious\",\"critical\"])};a.forEach(function(a){var c=a.name,d=a.value,e=a.priority,f=a.group;b[c]=d,b[c+\"_PRIO\"]=e,b[c+\"_GROUP\"]=f,b.results[e]=d,b.resultGroups[e]=f,b.resultGroupMap[d]=f}),Object.freeze(b.results),Object.freeze(b.resultGroups),Object.freeze(b.resultGroupMap),Object.freeze(b),Object.defineProperty(axe,\"constants\",{value:b,enumerable:!0,configurable:!1,writable:!1})}(axe);var Z=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&\"function\"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?\"symbol\":typeof a};axe.log=function(){\"use strict\";\"object\"===(\"undefined\"==typeof console?\"undefined\":Z(console))&&console.log&&Function.prototype.apply.call(console.log,console,arguments)};var Z=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&\"function\"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?\"symbol\":typeof a};axe.a11yCheck=function(a,b,c){\"use strict\";\"function\"==typeof b&&(c=b,b={}),b&&\"object\"===(void 0===b?\"undefined\":Z(b))||(b={});var d=axe._audit;if(!d)throw new Error(\"No audit configured\");b.reporter=b.reporter||d.reporter||\"v2\",b.performanceTimer&&axe.utils.performanceTimer.start();var e=axe.getReporter(b.reporter);axe._runRules(a,b,function(a){var d=e(a,b,c);void 0!==d&&(b.performanceTimer&&axe.utils.performanceTimer.end(),c(d))},axe.log)},axe.cleanup=v,axe.configure=w,axe.getRules=function(a){\"use strict\";a=a||[];var b=a.length?axe._audit.rules.filter(function(b){return!!a.filter(function(a){return-1!==b.tags.indexOf(a)}).length}):axe._audit.rules,c=axe._audit.data.rules||{};return b.map(function(a){var b=c[a.id]||{};return{ruleId:a.id,description:b.description,help:b.help,helpUrl:b.helpUrl,tags:a.tags}})},axe._load=function(a){\"use strict\";axe.utils.respondable.subscribe(\"axe.ping\",function(a,b,c){c({axe:!0})}),axe.utils.respondable.subscribe(\"axe.start\",x),axe._audit=new e(a)};var axe=axe||{};axe.plugins={},y.prototype.run=function(){\"use strict\";return this._run.apply(this,arguments)},y.prototype.collect=function(){\"use strict\";return this._collect.apply(this,arguments)},y.prototype.cleanup=function(a){\"use strict\";var b=axe.utils.queue(),c=this;Object.keys(this._registry).forEach(function(a){b.defer(function(b){c._registry[a].cleanup(b)})}),b.then(function(){a()})},y.prototype.add=function(a){\"use strict\";this._registry[a.id]=a},axe.registerPlugin=function(a){\"use strict\";axe.plugins[a.id]=new y(a)};var _,aa={};axe.getReporter=function(a){\"use strict\";return\"string\"==typeof a&&aa[a]?aa[a]:\"function\"==typeof a?a:_},axe.addReporter=function(a,b,c){\"use strict\";aa[a]=b,c&&(_=b)},axe.reset=z,axe._runRules=A;var Z=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&\"function\"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?\"symbol\":typeof a},ba=function(){};axe.run=function(a,b,c){\"use strict\";if(!axe._audit)throw new Error(\"No audit configured\");var d=C(a,b,c);a=d.context,b=d.options,c=d.callback,b.reporter=b.reporter||axe._audit.reporter||\"v1\",b.performanceTimer&&axe.utils.performanceTimer.start();var e=void 0,f=ba,g=ba;return window.Promise&&c===ba&&(e=new Promise(function(a,b){f=b,g=a})),axe._runRules(a,b,function(a){var d=function(a){try{c(null,a)}catch(a){axe.log(a)}g(a)};b.performanceTimer&&axe.utils.performanceTimer.end();try{var e=axe.getReporter(b.reporter),h=e(a,b,d);void 0!==h&&d(h)}catch(a){c(a),f(a)}},function(a){c(a),f(a)}),e},$.failureSummary=function(a){\"use strict\";var b={};return b.none=a.none.concat(a.all),b.any=a.any,Object.keys(b).map(function(a){if(b[a].length){var c=axe._audit.data.failureSummaries[a];return c&&\"function\"==typeof c.failureMessage?c.failureMessage(b[a].map(function(a){return a.message||\"\"})):void 0}}).filter(function(a){return void 0!==a}).join(\"\\n\\n\")};var Z=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&\"function\"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?\"symbol\":typeof a},ca=axe.constants.resultGroups;$.processAggregate=function(a,b){var c=axe.utils.aggregateResult(a);return c.timestamp=(new Date).toISOString(),c.url=window.location.href,ca.forEach(function(a){c[a]=(c[a]||[]).map(function(a){return a=Object.assign({},a),Array.isArray(a.nodes)&&a.nodes.length>0&&(a.nodes=a.nodes.map(function(a){return\"object\"===Z(a.node)&&(a.html=a.node.source,b.elementRef&&!a.node.fromFrame&&(a.element=a.node.element),(!1!==b.selectors||a.node.fromFrame)&&(a.target=a.node.selector),b.xpath&&(a.xpath=a.node.xpath)),delete a.result,delete a.node,D(a,b),a})),ca.forEach(function(b){return delete a[b]}),delete a.pageLevel,delete a.result,a})}),c},axe.addReporter(\"na\",function(a,b,c){\"use strict\";\"function\"==typeof b&&(c=b,b={});var d=$.processAggregate(a,b);c({violations:d.violations,passes:d.passes,incomplete:d.incomplete,inapplicable:d.inapplicable,timestamp:d.timestamp,url:d.url})}),axe.addReporter(\"no-passes\",function(a,b,c){\"use strict\";\"function\"==typeof b&&(c=b,b={});var d=$.processAggregate(a,b);c({violations:d.violations,timestamp:d.timestamp,url:d.url})}),axe.addReporter(\"raw\",function(a,b,c){\"use strict\";\"function\"==typeof b&&(c=b,b={}),c(a)}),axe.addReporter(\"v1\",function(a,b,c){\"use strict\";\"function\"==typeof b&&(c=b,b={});var d=$.processAggregate(a,b);d.violations.forEach(function(a){return a.nodes.forEach(function(a){a.failureSummary=$.failureSummary(a)})}),c({violations:d.violations,passes:d.passes,incomplete:d.incomplete,inapplicable:d.inapplicable,timestamp:d.timestamp,url:d.url})}),axe.addReporter(\"v2\",function(a,b,c){\"use strict\";\"function\"==typeof b&&(c=b,b={});var d=$.processAggregate(a,b);c({violations:d.violations,passes:d.passes,incomplete:d.incomplete,inapplicable:d.inapplicable,timestamp:d.timestamp,url:d.url})},!0),axe.utils.aggregate=function(a,b,c){b=b.slice(),c&&b.push(c);var d=b.map(function(b){return a.indexOf(b)}).sort();return a[d.pop()]};var da=[];da[axe.constants.PASS_PRIO]=!0,da[axe.constants.CANTTELL_PRIO]=null,da[axe.constants.FAIL_PRIO]=!1;var ea=[\"any\",\"all\",\"none\"];axe.utils.aggregateChecks=function(a){var b=Object.assign({},a);E(b,function(a,b){var c=da.indexOf(a.result);a.priority=-1!==c?c:axe.constants.CANTTELL_PRIO,\"none\"===b&&(a.priority=4-a.priority)});var c=E(b,function(a){return a.priority});b.priority=Math.max(c.all.reduce(function(a,b){return Math.max(a,b)},0),c.none.reduce(function(a,b){return Math.max(a,b)},0),c.any.reduce(function(a,b){return Math.min(a,b)},4)%4);var d=[];return ea.forEach(function(a){b[a]=b[a].filter(function(a){return a.priority===b.priority}),b[a].forEach(function(a){return d.push(a.impact)})}),b.priority===axe.constants.FAIL_PRIO?b.impact=axe.utils.aggregate(axe.constants.impact,d):b.impact=null,E(b,function(a){delete a.result,delete a.priority}),b.result=axe.constants.results[b.priority],delete b.priority,b},axe.utils.aggregateResult=function(a){var b={};return axe.constants.resultGroups.forEach(function(a){return b[a]=[]}),a.forEach(function(a){a.error?F(b,a,axe.constants.CANTTELL_GROUP):a.result===axe.constants.NA?F(b,a,axe.constants.NA_GROUP):axe.constants.resultGroups.forEach(function(c){Array.isArray(a[c])&&a[c].length>0&&F(b,a,c)})}),b},function(){axe.utils.aggregateRule=function(a){var b={};a=a.map(function(a){if(a.any&&a.all&&a.none)return axe.utils.aggregateChecks(a);if(Array.isArray(a.node))return axe.utils.finalizeRuleResult(a);throw new TypeError(\"Invalid Result type\")});var c=a.map(function(a){return a.result});b.result=axe.utils.aggregate(axe.constants.results,c,b.result),axe.constants.resultGroups.forEach(function(a){return b[a]=[]}),a.forEach(function(a){var c=axe.constants.resultGroupMap[a.result];b[c].push(a)});var d=axe.constants.FAIL_GROUP;if(b[d].length>0){var e=b[d].map(function(a){return a.impact});b.impact=axe.utils.aggregate(axe.constants.impact,e)||null}else b.impact=null;return b}}(),axe.utils.areStylesSet=G,axe.utils.checkHelper=function(a,b,c){\"use strict\";return{isAsync:!1,async:function(){return this.isAsync=!0,function(d){d instanceof Error==!1?(a.value=d,b(a)):c(d)}},data:function(b){a.data=b},relatedNodes:function(b){b=b instanceof Node?[b]:axe.utils.toArray(b),a.relatedNodes=b.map(function(a){return new axe.utils.DqElement(a)})}}};var Z=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&\"function\"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?\"symbol\":typeof a};axe.utils.clone=function(a){\"use strict\";var b,c,d=a;if(null!==a&&\"object\"===(void 0===a?\"undefined\":Z(a)))if(Array.isArray(a))for(d=[],b=0,c=a.length;b<c;b++)d[b]=axe.utils.clone(a[b]);else{d={};for(b in a)d[b]=axe.utils.clone(a[b])}return d},axe.utils.sendCommandToFrame=function(a,b,c,d){\"use strict\";var e=a.contentWindow;if(!e)return axe.log(\"Frame does not have a content window\",a),void c(null);var f=setTimeout(function(){f=setTimeout(function(){var e=H(\"No response from frame\",a);b.debug?d(e):(axe.log(e),c(null))},0)},500);axe.utils.respondable(e,\"axe.ping\",null,void 0,function(){clearTimeout(f),f=setTimeout(function(){d(H(\"Axe in frame timed out\",a))},3e4),axe.utils.respondable(e,\"axe.start\",b,void 0,function(a){clearTimeout(f),a instanceof Error==!1?c(a):d(a)})})},axe.utils.collectResultsFromFrames=I,axe.utils.contains=function(a,b){\"use strict\";return\"function\"==typeof a.contains?a.contains(b):!!(16&a.compareDocumentPosition(b))},L.prototype={get selector(){return this.spec.selector||[axe.utils.getSelector(this.element)]},get xpath(){return this.spec.xpath||[axe.utils.getXpath(this.element)]},get element(){return this._element},get fromFrame(){return this._fromFrame},toJSON:function(){\"use strict\";return{selector:this.selector,source:this.source,xpath:this.xpath}}},L.fromFrame=function(a,b){return a.selector.unshift(b.selector),a.xpath.unshift(b.xpath),new axe.utils.DqElement(b.element,a)},axe.utils.DqElement=L,axe.utils.matchesSelector=function(){\"use strict\";function a(a){var b,c,d=a.Element.prototype,e=[\"matches\",\"matchesSelector\",\"mozMatchesSelector\",\"webkitMatchesSelector\",\"msMatchesSelector\"],f=e.length;for(b=0;b<f;b++)if(c=e[b],d[c])return c}var b;return function(c,d){return b&&c[b]||(b=a(c.ownerDocument.defaultView)),c[b](d)}}(),axe.utils.escapeSelector=function(a){\"use strict\";for(var b,c=String(a),d=c.length,e=-1,f=\"\",g=c.charCodeAt(0);++e<d;){if(0==(b=c.charCodeAt(e)))throw new Error(\"INVALID_CHARACTER_ERR\");b>=1&&b<=31||b>=127&&b<=159||0==e&&b>=48&&b<=57||1==e&&b>=48&&b<=57&&45==g?f+=\"\\\\\"+b.toString(16)+\" \":f+=(1!=e||45!=b||45!=g)&&(b>=128||45==b||95==b||b>=48&&b<=57||b>=65&&b<=90||b>=97&&b<=122)?c.charAt(e):\"\\\\\"+c.charAt(e)}return f},axe.utils.extendMetaData=function(a,b){Object.assign(a,b),Object.keys(b).filter(function(a){return\"function\"==typeof b[a]}).forEach(function(c){a[c]=null;try{a[c]=b[c](a)}catch(a){}})},axe.utils.finalizeRuleResult=function(a){return Object.assign(a,axe.utils.aggregateRule(a.nodes)),delete a.nodes,a};var Z=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&\"function\"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?\"symbol\":typeof a};axe.utils.findBy=function(a,b,c){if(Array.isArray(a))return a.find(function(a){return\"object\"===(void 0===a?\"undefined\":Z(a))&&a[b]===c})},axe.utils.getAllChecks=function(a){\"use strict\";return[].concat(a.any||[]).concat(a.all||[]).concat(a.none||[])},axe.utils.getCheckOption=function(a,b,c){\"use strict\";var d=((c.rules&&c.rules[b]||{}).checks||{})[a.id],e=(c.checks||{})[a.id],f=a.enabled,g=a.options;return e&&(e.hasOwnProperty(\"enabled\")&&(f=e.enabled),\ne.hasOwnProperty(\"options\")&&(g=e.options)),d&&(d.hasOwnProperty(\"enabled\")&&(f=d.enabled),d.hasOwnProperty(\"options\")&&(g=d.options)),{enabled:f,options:g}},axe.utils.getSelector=function(a){\"use strict\";function b(a){return axe.utils.escapeSelector(a)}for(var c,d=[];a.parentNode;){if(c=\"\",a.id&&1===document.querySelectorAll(\"#\"+axe.utils.escapeSelector(a.id)).length){d.unshift(\"#\"+axe.utils.escapeSelector(a.id));break}if(a.className&&\"string\"==typeof a.className&&(\".\"===(c=\".\"+a.className.trim().split(/\\s+/).map(b).join(\".\"))||N(a,c))&&(c=\"\"),!c){if(\"html\"===(c=axe.utils.escapeSelector(a.nodeName).toLowerCase())||\"body\"===c){d.unshift(c);break}N(a,c)&&(c+=\":nth-of-type(\"+M(a)+\")\")}d.unshift(c),a=a.parentNode}return d.join(\" > \")},axe.utils.getXpath=function(a){return P(O(a))};var fa;axe.utils.injectStyle=Q,axe.utils.isHidden=function(a,b){\"use strict\";if(9===a.nodeType)return!1;var c=window.getComputedStyle(a,null);return!c||!a.parentNode||\"none\"===c.getPropertyValue(\"display\")||!b&&\"hidden\"===c.getPropertyValue(\"visibility\")||\"true\"===a.getAttribute(\"aria-hidden\")||axe.utils.isHidden(a.parentNode,!0)},axe.utils.mergeResults=function(a){\"use strict\";var b=[];return a.forEach(function(a){var c=T(a);c&&c.length&&c.forEach(function(c){c.nodes&&a.frame&&R(c.nodes,a.frameElement,a.frame);var d=axe.utils.findBy(b,\"id\",c.id);d?c.nodes.length&&S(d.nodes,c.nodes):b.push(c)})}),b},axe.utils.nodeSorter=function(a,b){\"use strict\";return a===b?0:4&a.compareDocumentPosition(b)?-1:1},utils.performanceTimer=function(){\"use strict\";function a(){if(window.performance&&window.performance)return window.performance.now()}var b=null,c=a();return{start:function(){this.mark(\"mark_axe_start\")},end:function(){this.mark(\"mark_axe_end\"),this.measure(\"axe\",\"mark_axe_start\",\"mark_axe_end\"),this.logMeasures(\"axe\")},auditStart:function(){this.mark(\"mark_audit_start\")},auditEnd:function(){this.mark(\"mark_audit_end\"),this.measure(\"audit_start_to_end\",\"mark_audit_start\",\"mark_audit_end\"),this.logMeasures()},mark:function(a){window.performance&&void 0!==window.performance.mark&&window.performance.mark(a)},measure:function(a,b,c){window.performance&&void 0!==window.performance.measure&&window.performance.measure(a,b,c)},logMeasures:function(a){function b(a){axe.log(\"Measure \"+a.name+\" took \"+a.duration+\"ms\")}if(window.performance&&void 0!==window.performance.getEntriesByType)for(var c=window.performance.getEntriesByType(\"measure\"),d=0;d<c.length;++d){var e=c[d];if(e.name===a)return void b(e);b(e)}},timeElapsed:function(){return a()-c},reset:function(){b||(b=a()),c=a()}}}(),\"function\"!=typeof Object.assign&&function(){Object.assign=function(a){\"use strict\";if(void 0===a||null===a)throw new TypeError(\"Cannot convert undefined or null to object\");for(var b=Object(a),c=1;c<arguments.length;c++){var d=arguments[c];if(void 0!==d&&null!==d)for(var e in d)d.hasOwnProperty(e)&&(b[e]=d[e])}return b}}(),Array.prototype.find||(Array.prototype.find=function(a){if(null===this)throw new TypeError(\"Array.prototype.find called on null or undefined\");if(\"function\"!=typeof a)throw new TypeError(\"predicate must be a function\");for(var b,c=Object(this),d=c.length>>>0,e=arguments[1],f=0;f<d;f++)if(b=c[f],a.call(e,b,f,c))return b}),axe.utils.pollyfillElementsFromPoint=function(){if(document.elementsFromPoint)return document.elementsFromPoint;if(document.msElementsFromPoint)return document.msElementsFromPoint;var a=function(){var a=document.createElement(\"x\");return a.style.cssText=\"pointer-events:auto\",\"auto\"===a.style.pointerEvents}(),b=a?\"pointer-events\":\"visibility\",c=a?\"none\":\"hidden\",d=document.createElement(\"style\");return d.innerHTML=a?\"* { pointer-events: all }\":\"* { visibility: visible }\",function(a,e){var f,g,h,i=[],j=[];for(document.head.appendChild(d);(f=document.elementFromPoint(a,e))&&-1===i.indexOf(f);)i.push(f),j.push({value:f.style.getPropertyValue(b),priority:f.style.getPropertyPriority(b)}),f.style.setProperty(b,c,\"important\");for(g=j.length;h=j[--g];)i[g].style.setProperty(b,h.value?h.value:\"\",h.priority);return document.head.removeChild(d),i}},\"function\"==typeof window.addEventListener&&(document.elementsFromPoint=axe.utils.pollyfillElementsFromPoint()),Array.prototype.includes||(Array.prototype.includes=function(a){\"use strict\";var b=Object(this),c=parseInt(b.length,10)||0;if(0===c)return!1;var d,e=parseInt(arguments[1],10)||0;e>=0?d=e:(d=c+e)<0&&(d=0);for(var f;d<c;){if(f=b[d],a===f||a!==a&&f!==f)return!0;d++}return!1}),Array.prototype.some||(Array.prototype.some=function(a){\"use strict\";if(null==this)throw new TypeError(\"Array.prototype.some called on null or undefined\");if(\"function\"!=typeof a)throw new TypeError;for(var b=Object(this),c=b.length>>>0,d=arguments.length>=2?arguments[1]:void 0,e=0;e<c;e++)if(e in b&&a.call(d,b[e],e,b))return!0;return!1}),Array.from||(Array.from=function(){var a=Object.prototype.toString,b=function(b){return\"function\"==typeof b||\"[object Function]\"===a.call(b)},c=function(a){var b=Number(a);return isNaN(b)?0:0!==b&&isFinite(b)?(b>0?1:-1)*Math.floor(Math.abs(b)):b},d=Math.pow(2,53)-1,e=function(a){var b=c(a);return Math.min(Math.max(b,0),d)};return function(a){var c=this,d=Object(a);if(null==a)throw new TypeError(\"Array.from requires an array-like object - not null or undefined\");var f,g=arguments.length>1?arguments[1]:void 0;if(void 0!==g){if(!b(g))throw new TypeError(\"Array.from: when provided, the second argument must be a function\");arguments.length>2&&(f=arguments[2])}for(var h,i=e(d.length),j=b(c)?Object(new c(i)):new Array(i),k=0;k<i;)h=d[k],j[k]=g?void 0===f?g(h,k):g.call(f,h,k):h,k+=1;return j.length=i,j}}());var Z=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&\"function\"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?\"symbol\":typeof a};axe.utils.publishMetaData=function(a){\"use strict\";var b=axe._audit.data.checks||{},c=axe._audit.data.rules||{},d=axe.utils.findBy(axe._audit.rules,\"id\",a.id)||{};a.tags=axe.utils.clone(d.tags||[]);var e=U(b,!0),f=U(b,!1);a.nodes.forEach(function(a){a.any.forEach(e),a.all.forEach(e),a.none.forEach(f)}),axe.utils.extendMetaData(a,axe.utils.clone(c[a.id]||{}))};var Z=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&\"function\"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?\"symbol\":typeof a};!function(){\"use strict\";function a(){}function b(a){if(\"function\"!=typeof a)throw new TypeError(\"Queue methods require functions as arguments\")}function c(){function c(b){return function(c){g[b]=c,(i-=1)||j===a||(k=!0,j(g))}}function d(b){return j=a,m(b),g}function e(){for(var a=g.length;h<a;h++){var b=g[h];try{b.call(null,c(h),d)}catch(a){d(a)}}}var f,g=[],h=0,i=0,j=a,k=!1,l=function(a){f=a,setTimeout(function(){void 0!==f&&null!==f&&axe.log(\"Uncaught error (of queue)\",f)},1)},m=l,n={defer:function(a){if(\"object\"===(void 0===a?\"undefined\":Z(a))&&a.then&&a.catch){var c=a;a=function(a,b){c.then(a).catch(b)}}if(b(a),void 0===f){if(k)throw new Error(\"Queue already completed\");return g.push(a),++i,e(),n}},then:function(c){if(b(c),j!==a)throw new Error(\"queue `then` already set\");return f||(j=c,i||(k=!0,j(g))),n},catch:function(a){if(b(a),m!==l)throw new Error(\"queue `catch` already set\");return f?(a(f),f=null):m=a,n},abort:d};return n}axe.utils.queue=c}();var Z=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&\"function\"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?\"symbol\":typeof a};!function(a){\"use strict\";function b(){var a=\"axe\",b=\"\";return void 0!==axe&&axe._audit&&!axe._audit.application&&(a=axe._audit.application),void 0!==axe&&(b=axe.version),a+\".\"+b}function c(a){if(\"object\"===(void 0===a?\"undefined\":Z(a))&&\"string\"==typeof a.uuid&&!0===a._respondable){var c=b();return a._source===c||\"axe.x.y.z\"===a._source||\"axe.x.y.z\"===c}return!1}function d(a,c,d,e,f,g){var h;d instanceof Error&&(h={name:d.name,message:d.message,stack:d.stack},d=void 0);var i={uuid:e,topic:c,message:d,error:h,_respondable:!0,_source:b(),_keepalive:f};\"function\"==typeof g&&(j[e]=g),a.postMessage(JSON.stringify(i),\"*\")}function e(a,b,c,e,f){d(a,b,c,ga.v1(),e,f)}function f(a,b,c){return function(e,f,g){d(a,b,e,c,f,g)}}function g(a,b,c){var d=b.topic,e=k[d];if(e){var g=f(a,null,b.uuid);e(b.message,c,g)}}function h(a){var b=a.message||\"Unknown error occurred\",c=window[a.name]||Error;return a.stack&&(b+=\"\\n\"+a.stack.replace(a.message,\"\")),new c(b)}function i(a){var b;if(\"string\"==typeof a){try{b=JSON.parse(a)}catch(a){}if(c(b))return\"object\"===Z(b.error)?b.error=h(b.error):b.error=void 0,b}}var j={},k={};e.subscribe=function(a,b){k[a]=b},e.isInFrame=function(a){return a=a||window,!!a.frameElement},\"function\"==typeof window.addEventListener&&window.addEventListener(\"message\",function(a){var b=i(a.data);if(b){var c=b.uuid,e=b._keepalive,h=j[c];if(h){h(b.error||b.message,e,f(a.source,b.topic,c)),e||delete j[c]}if(!b.error)try{g(a.source,b,e)}catch(e){d(a.source,b.topic,e,c,!1)}}},!1),a.respondable=e}(utils),axe.utils.ruleShouldRun=function(a,b,c){\"use strict\";var d=c.runOnly||{},e=(c.rules||{})[a.id];return!(a.pageLevel&&!b.page)&&(\"rule\"===d.type?-1!==d.values.indexOf(a.id):e&&\"boolean\"==typeof e.enabled?e.enabled:\"tag\"===d.type&&d.values?V(a,d.values):V(a,[]))},axe.utils.select=function(a,b){\"use strict\";for(var c,d=[],e=0,f=b.include.length;e<f;e++)c=b.include[e],c.nodeType===c.ELEMENT_NODE&&axe.utils.matchesSelector(c,a)&&Y(d,[c],b),Y(d,c.querySelectorAll(a),b);return d.sort(axe.utils.nodeSorter)},axe.utils.toArray=function(a){\"use strict\";return Array.prototype.slice.call(a)};var ga;!function(a){function b(a,b,c){var d=b&&c||0,e=0;for(b=b||[],a.toLowerCase().replace(/[0-9a-f]{2}/g,function(a){e<16&&(b[d+e++]=l[a])});e<16;)b[d+e++]=0;return b}function c(a,b){var c=b||0,d=k;return d[a[c++]]+d[a[c++]]+d[a[c++]]+d[a[c++]]+\"-\"+d[a[c++]]+d[a[c++]]+\"-\"+d[a[c++]]+d[a[c++]]+\"-\"+d[a[c++]]+d[a[c++]]+\"-\"+d[a[c++]]+d[a[c++]]+d[a[c++]]+d[a[c++]]+d[a[c++]]+d[a[c++]]}function d(a,b,d){var e=b&&d||0,f=b||[];a=a||{};var g=null!=a.clockseq?a.clockseq:p,h=null!=a.msecs?a.msecs:(new Date).getTime(),i=null!=a.nsecs?a.nsecs:r+1,j=h-q+(i-r)/1e4;if(j<0&&null==a.clockseq&&(g=g+1&16383),(j<0||h>q)&&null==a.nsecs&&(i=0),i>=1e4)throw new Error(\"uuid.v1(): Can't create more than 10M uuids/sec\");q=h,r=i,p=g,h+=122192928e5;var k=(1e4*(268435455&h)+i)%4294967296;f[e++]=k>>>24&255,f[e++]=k>>>16&255,f[e++]=k>>>8&255,f[e++]=255&k;var l=h/4294967296*1e4&268435455;f[e++]=l>>>8&255,f[e++]=255&l,f[e++]=l>>>24&15|16,f[e++]=l>>>16&255,f[e++]=g>>>8|128,f[e++]=255&g;for(var m=a.node||o,n=0;n<6;n++)f[e+n]=m[n];return b||c(f)}function e(a,b,d){var e=b&&d||0;\"string\"==typeof a&&(b=\"binary\"==a?new j(16):null,a=null),a=a||{};var g=a.random||(a.rng||f)();if(g[6]=15&g[6]|64,g[8]=63&g[8]|128,b)for(var h=0;h<16;h++)b[e+h]=g[h];return b||c(g)}var f,g=a.crypto||a.msCrypto;if(!f&&g&&g.getRandomValues){var h=new Uint8Array(16);f=function(){return g.getRandomValues(h),h}}if(!f){var i=new Array(16);f=function(){for(var a,b=0;b<16;b++)0==(3&b)&&(a=4294967296*Math.random()),i[b]=a>>>((3&b)<<3)&255;return i}}for(var j=\"function\"==typeof a.Buffer?a.Buffer:Array,k=[],l={},m=0;m<256;m++)k[m]=(m+256).toString(16).substr(1),l[k[m]]=m;var n=f(),o=[1|n[0],n[1],n[2],n[3],n[4],n[5]],p=16383&(n[6]<<8|n[7]),q=0,r=0;ga=e,ga.v1=d,ga.v4=e,ga.parse=b,ga.unparse=c,ga.BufferClass=j}(window),axe._load({data:{rules:{accesskeys:{description:\"Ensures every accesskey attribute value is unique\",help:\"accesskey attribute value must be unique\"},\"area-alt\":{description:\"Ensures <area> elements of image maps have alternate text\",help:\"Active <area> elements must have alternate text\"},\"aria-allowed-attr\":{description:\"Ensures ARIA attributes are allowed for an element's role\",help:\"Elements must only use allowed ARIA attributes\"},\"aria-required-attr\":{description:\"Ensures elements with ARIA roles have all required ARIA attributes\",help:\"Required ARIA attributes must be provided\"},\"aria-required-children\":{description:\"Ensures elements with an ARIA role that require child roles contain them\",help:\"Certain ARIA roles must contain particular children\"},\"aria-required-parent\":{description:\"Ensures elements with an ARIA role that require parent roles are contained by them\",help:\"Certain ARIA roles must be contained by particular parents\"},\"aria-roles\":{description:\"Ensures all elements with a role attribute use a valid value\",help:\"ARIA roles used must conform to valid values\"},\"aria-valid-attr-value\":{description:\"Ensures all ARIA attributes have valid values\",help:\"ARIA attributes must conform to valid values\"},\"aria-valid-attr\":{description:\"Ensures attributes that begin with aria- are valid ARIA attributes\",help:\"ARIA attributes must conform to valid names\"},\"audio-caption\":{description:\"Ensures <audio> elements have captions\",help:\"<audio> elements must have a captions track\"},blink:{description:\"Ensures <blink> elements are not used\",help:\"<blink> elements are deprecated and must not be used\"},\"button-name\":{description:\"Ensures buttons have discernible text\",help:\"Buttons must have discernible text\"},bypass:{description:\"Ensures each page has at least one mechanism for a user to bypass navigation and jump straight to the content\",help:\"Page must have means to bypass repeated blocks\"},checkboxgroup:{description:'Ensures related <input type=\"checkbox\"> elements have a group and that that group designation is consistent',help:\"Checkbox inputs with the same name attribute value must be part of a group\"},\"color-contrast\":{description:\"Ensures the contrast between foreground and background colors meets WCAG 2 AA contrast ratio thresholds\",help:\"Elements must have sufficient color contrast\"},\"definition-list\":{description:\"Ensures <dl> elements are structured correctly\",help:\"<dl> elements must only directly contain properly-ordered <dt> and <dd> groups, <script> or <template> elements\"},dlitem:{description:\"Ensures <dt> and <dd> elements are contained by a <dl>\",help:\"<dt> and <dd> elements must be contained by a <dl>\"},\"document-title\":{description:\"Ensures each HTML document contains a non-empty <title> element\",help:\"Documents must have <title> element to aid in navigation\"},\"duplicate-id\":{description:\"Ensures every id attribute value is unique\",help:\"id attribute value must be unique\"},\"empty-heading\":{description:\"Ensures headings have discernible text\",help:\"Headings must not be empty\"},\"frame-title-unique\":{description:\"Ensures <iframe> and <frame> elements contain a unique title attribute\",help:\"Frames must have a unique title attribute\"},\"frame-title\":{description:\"Ensures <iframe> and <frame> elements contain a non-empty title attribute\",help:\"Frames must have title attribute\"},\"heading-order\":{description:\"Ensures the order of headings is semantically correct\",help:\"Heading levels should only increase by one\"},\"href-no-hash\":{description:\"Ensures that href values are valid link references to promote only using anchors as links\",help:\"Anchors must only be used as links and must therefore have an href value that is a valid reference. Otherwise you should probably usa a button\"},\"html-has-lang\":{description:\"Ensures every HTML document has a lang attribute\",help:\"<html> element must have a lang attribute\"},\"html-lang-valid\":{description:\"Ensures the lang attribute of the <html> element has a valid value\",help:\"<html> element must have a valid value for the lang attribute\"},\"image-alt\":{description:\"Ensures <img> elements have alternate text or a role of none or presentation\",help:\"Images must have alternate text\"},\"image-redundant-alt\":{description:\"Ensure button and link text is not repeated as image alternative\",help:\"Text of buttons and links should not be repeated in the image alternative\"},\"input-image-alt\":{description:'Ensures <input type=\"image\"> elements have alternate text',help:\"Image buttons must have alternate text\"},\"label-title-only\":{description:\"Ensures that every form element is not solely labeled using the title or aria-describedby attributes\",help:\"Form elements should have a visible label\"},label:{description:\"Ensures every form element has a label\",help:\"Form elements must have labels\"},\"layout-table\":{description:\"Ensures presentational <table> elements do not use <th>, <caption> elements or the summary attribute\",help:\"Layout tables must not use data table elements\"},\"link-in-text-block\":{description:\"Links can be distinguished without relying on color\",help:\"Links must be distinguished from surrounding text in a way that does not rely on color\"},\"link-name\":{description:\"Ensures links have discernible text\",help:\"Links must have discernible text\"},list:{description:\"Ensures that lists are structured correctly\",help:\"<ul> and <ol> must only directly contain <li>, <script> or <template> elements\"},listitem:{description:\"Ensures <li> elements are used semantically\",help:\"<li> elements must be contained in a <ul> or <ol>\"},marquee:{description:\"Ensures <marquee> elements are not used\",help:\"<marquee> elements are deprecated and must not be used\"},\"meta-refresh\":{description:'Ensures <meta http-equiv=\"refresh\"> is not used',help:\"Timed refresh must not exist\"},\"meta-viewport-large\":{description:'Ensures <meta name=\"viewport\"> can scale a significant amount',help:\"Users should be able to zoom and scale the text up to 500%\"},\"meta-viewport\":{description:'Ensures <meta name=\"viewport\"> does not disable text scaling and zooming',help:\"Zooming and scaling must not be disabled\"},\"object-alt\":{description:\"Ensures <object> elements have alternate text\",help:\"<object> elements must have alternate text\"},\"p-as-heading\":{description:\"Ensure p elements are not used to style headings\",help:\"Bold, italic text and font-size are not used to style p elements as a heading\"},radiogroup:{description:'Ensures related <input type=\"radio\"> elements have a group and that the group designation is consistent',help:\"Radio inputs with the same name attribute value must be part of a group\"},region:{description:\"Ensures all content is contained within a landmark region\",help:\"Content should be contained in a landmark region\"},\"scope-attr-valid\":{description:\"Ensures the scope attribute is used correctly on tables\",help:\"scope attribute should be used correctly\"},\"server-side-image-map\":{description:\"Ensures that server-side image maps are not used\",help:\"Server-side image maps must not be used\"},\"skip-link\":{description:\"Ensures the first link on the page is a skip link\",help:\"The page should have a skip link as its first link\"},tabindex:{description:\"Ensures tabindex attribute values are not greater than 0\",help:\"Elements should not have tabindex greater than zero\"},\"table-duplicate-name\":{description:\"Ensure that tables do not have the same summary and caption\",help:\"The <caption> element should not contain the same text as the summary attribute\"},\"table-fake-caption\":{description:\"Ensure that tables with a caption use the <caption> element.\",help:\"Data or header cells should not be used to give caption to a data table.\"},\"td-has-header\":{description:\"Ensure that each non-empty data cell in a large table has one or more table headers\",help:\"All non-empty td element in table larger than 3 by 3 must have an associated table header\"},\"td-headers-attr\":{description:\"Ensure that each cell in a table using the headers refers to another cell in that table\",help:\"All cells in a table element that use the headers attribute must only refer to other cells of that same table\"},\"th-has-data-cells\":{description:\"Ensure that each table header in a data table refers to data cells\",help:\"All th element and elements with role=columnheader/rowheader must data cells which it describes\"},\"valid-lang\":{description:\"Ensures lang attributes have valid values\",help:\"lang attribute must have a valid value\"},\"video-caption\":{description:\"Ensures <video> elements have captions\",help:\"<video> elements must have captions\"},\"video-description\":{description:\"Ensures <video> elements have audio descriptions\",help:\"<video> elements must have an audio description track\"}},checks:{accesskeys:{impact:\"critical\",messages:{pass:function(a){return\"Accesskey attribute value is unique\"},fail:function(a){return\"Document has multiple elements with the same accesskey\"}}},\"non-empty-alt\":{impact:\"critical\",messages:{pass:function(a){return\"Element has a non-empty alt attribute\"},fail:function(a){return\"Element has no alt attribute or the alt attribute is empty\"}}},\"non-empty-title\":{impact:\"critical\",messages:{pass:function(a){return\"Element has a title attribute\"},fail:function(a){return\"Element has no title attribute or the title attribute is empty\"}}},\"aria-label\":{impact:\"critical\",messages:{pass:function(a){return\"aria-label attribute exists and is not empty\"},fail:function(a){return\"aria-label attribute does not exist or is empty\"}}},\"aria-labelledby\":{impact:\"critical\",messages:{pass:function(a){return\"aria-labelledby attribute exists and references elements that are visible to screen readers\"},fail:function(a){return\"aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty or not visible\"}}},\"aria-allowed-attr\":{impact:\"critical\",messages:{pass:function(a){return\"ARIA attributes are used correctly for the defined role\"},fail:function(a){var b=\"ARIA attribute\"+(a.data&&a.data.length>1?\"s are\":\" is\")+\" not allowed:\",c=a.data;if(c)for(var d,e=-1,f=c.length-1;e<f;)d=c[e+=1],b+=\" \"+d;return b}}},\"aria-required-attr\":{impact:\"critical\",messages:{pass:function(a){return\"All required ARIA attributes are present\"},fail:function(a){var b=\"Required ARIA attribute\"+(a.data&&a.data.length>1?\"s\":\"\")+\" not present:\",c=a.data;if(c)for(var d,e=-1,f=c.length-1;e<f;)d=c[e+=1],b+=\" \"+d;return b}}},\"aria-required-children\":{impact:\"critical\",messages:{pass:function(a){return\"Required ARIA children are present\"},fail:function(a){var b=\"Required ARIA \"+(a.data&&a.data.length>1?\"children\":\"child\")+\" role not present:\",c=a.data;if(c)for(var d,e=-1,f=c.length-1;e<f;)d=c[e+=1],b+=\" \"+d;return b}}},\"aria-required-parent\":{impact:\"critical\",messages:{pass:function(a){return\"Required ARIA parent role present\"},fail:function(a){var b=\"Required ARIA parent\"+(a.data&&a.data.length>1?\"s\":\"\")+\" role not present:\",c=a.data;if(c)for(var d,e=-1,f=c.length-1;e<f;)d=c[e+=1],b+=\" \"+d;return b}}},invalidrole:{impact:\"critical\",messages:{pass:function(a){return\"ARIA role is valid\"},fail:function(a){return\"Role must be one of the valid ARIA roles\"}}},abstractrole:{impact:\"serious\",messages:{pass:function(a){return\"Abstract roles are not used\"},fail:function(a){return\"Abstract roles cannot be directly used\"}}},\"aria-valid-attr-value\":{impact:\"critical\",messages:{pass:function(a){return\"ARIA attribute values are valid\"},fail:function(a){var b=\"Invalid ARIA attribute value\"+(a.data&&a.data.length>1?\"s\":\"\")+\":\",c=a.data;if(c)for(var d,e=-1,f=c.length-1;e<f;)d=c[e+=1],b+=\" \"+d;return b}}},\"aria-valid-attr\":{impact:\"critical\",messages:{pass:function(a){return\"ARIA attribute name\"+(a.data&&a.data.length>1?\"s\":\"\")+\" are valid\"},fail:function(a){var b=\"Invalid ARIA attribute name\"+(a.data&&a.data.length>1?\"s\":\"\")+\":\",c=a.data;if(c)for(var d,e=-1,f=c.length-1;e<f;)d=c[e+=1],b+=\" \"+d;return b}}},caption:{impact:\"critical\",messages:{pass:function(a){return\"The multimedia element has a captions track\"},fail:function(a){return\"The multimedia element does not have a captions track\"},incomplete:function(a){return\"A captions track for this element could not be found\"}}},\"is-on-screen\":{impact:\"minor\",messages:{pass:function(a){return\"Element is not visible\"},fail:function(a){return\"Element is visible\"}}},\"non-empty-if-present\":{impact:\"critical\",messages:{pass:function(a){var b=\"Element \";return a.data?b+=\"has a non-empty value attribute\":b+=\"does not have a value attribute\",b},fail:function(a){return\"Element has a value attribute and the value attribute is empty\"}}},\"non-empty-value\":{impact:\"critical\",messages:{pass:function(a){return\"Element has a non-empty value attribute\"},fail:function(a){return\"Element has no value attribute or the value attribute is empty\"}}},\"button-has-visible-text\":{impact:\"critical\",messages:{pass:function(a){return\"Element has inner text that is visible to screen readers\"},fail:function(a){return\"Element does not have inner text that is visible to screen readers\"}}},\"role-presentation\":{impact:\"moderate\",messages:{pass:function(a){return'Element\\'s default semantics were overriden with role=\"presentation\"'},fail:function(a){return'Element\\'s default semantics were not overridden with role=\"presentation\"'}}},\"role-none\":{impact:\"moderate\",messages:{pass:function(a){return'Element\\'s default semantics were overriden with role=\"none\"'},fail:function(a){return'Element\\'s default semantics were not overridden with role=\"none\"'}}},\"focusable-no-name\":{impact:\"serious\",messages:{pass:function(a){return\"Element is not in tab order or has accessible text\"},fail:function(a){return\"Element is in tab order and does not have accessible text\"}}},\"internal-link-present\":{impact:\"critical\",messages:{pass:function(a){return\"Valid skip link found\"},fail:function(a){return\"No valid skip link found\"}}},\"header-present\":{impact:\"moderate\",messages:{pass:function(a){return\"Page has a header\"},fail:function(a){return\"Page does not have a header\"}}},landmark:{impact:\"serious\",messages:{pass:function(a){return\"Page has a landmark region\"},fail:function(a){return\"Page does not have a landmark region\"}}},\"group-labelledby\":{impact:\"critical\",messages:{pass:function(a){return'All elements with the name \"'+a.data.name+'\" reference the same element with aria-labelledby'},fail:function(a){return'All elements with the name \"'+a.data.name+'\" do not reference the same element with aria-labelledby'}}},fieldset:{impact:\"critical\",messages:{pass:function(a){return\"Element is contained in a fieldset\"},fail:function(a){var b=\"\",c=a.data&&a.data.failureCode;return b+=\"no-legend\"===c?\"Fieldset does not have a legend as its first child\":\"empty-legend\"===c?\"Legend does not have text that is visible to screen readers\":\"mixed-inputs\"===c?\"Fieldset contains unrelated inputs\":\"no-group-label\"===c?\"ARIA group does not have aria-label or aria-labelledby\":\"group-mixed-inputs\"===c?\"ARIA group contains unrelated inputs\":\"Element does not have a containing fieldset or ARIA group\"}}},\"color-contrast\":{impact:\"critical\",messages:{pass:function(a){return\"Element has sufficient color contrast of \"+a.data.contrastRatio},fail:function(a){return\"Element has insufficient color contrast of \"+a.data.contrastRatio+\" (foreground color: \"+a.data.fgColor+\", background color: \"+a.data.bgColor+\", font size: \"+a.data.fontSize+\", font weight: \"+a.data.fontWeight+\")\"},incomplete:{bgImage:\"Element's background color could not be determined due to a background image\",bgGradient:\"Element's background color could not be determined due to a background gradient\",imgNode:\"Element's background color could not be determined because element contains an image node\",bgOverlap:\"Element's background color could not be determined because it is overlapped by another element\",fgAlpha:\"Element's foreground color could not be determined because of alpha transparency\",default:\"Unable to determine contrast ratio\"}}},\"structured-dlitems\":{impact:\"serious\",messages:{pass:function(a){return\"When not empty, element has both <dt> and <dd> elements\"},fail:function(a){return\"When not empty, element does not have at least one <dt> element followed by at least one <dd> element\"}}},\"only-dlitems\":{impact:\"serious\",messages:{pass:function(a){return\"List element only has direct children that are allowed inside <dt> or <dd> elements\"},fail:function(a){return\"List element has direct children that are not allowed inside <dt> or <dd> elements\"}}},dlitem:{impact:\"serious\",messages:{pass:function(a){return\"Description list item has a <dl> parent element\"},fail:function(a){return\"Description list item does not have a <dl> parent element\"}}},\"doc-has-title\":{impact:\"moderate\",messages:{pass:function(a){return\"Document has a non-empty <title> element\"},fail:function(a){return\"Document does not have a non-empty <title> element\"}}},\"duplicate-id\":{impact:\"critical\",messages:{pass:function(a){return\"Document has no elements that share the same id attribute\"},fail:function(a){return\"Document has multiple elements with the same id attribute: \"+a.data}}},\"has-visible-text\":{impact:\"moderate\",messages:{pass:function(a){return\"Element has text that is visible to screen readers\"},fail:function(a){return\"Element does not have text that is visible to screen readers\"}}},\"unique-frame-title\":{impact:\"serious\",messages:{pass:function(a){return\"Element's title attribute is unique\"},fail:function(a){return\"Element's title attribute is not unique\"}}},\"heading-order\":{impact:\"minor\",messages:{pass:function(a){return\"Heading order valid\"},fail:function(a){return\"Heading order invalid\"}}},\"href-no-hash\":{impact:\"moderate\",messages:{pass:function(a){return\"Anchor does not have a href quals #\"},fail:function(a){return\"Anchor has a href quals #\"}}},\"has-lang\":{impact:\"serious\",messages:{pass:function(a){return\"The <html> element has a lang attribute\"},fail:function(a){return\"The <html> element does not have a lang attribute\"}}},\"valid-lang\":{impact:\"serious\",messages:{pass:function(a){return\"Value of lang attribute is included in the list of valid languages\"},fail:function(a){return\"Value of lang attribute not included in the list of valid languages\"}}},\"has-alt\":{impact:\"critical\",messages:{pass:function(a){return\"Element has an alt attribute\"},fail:function(a){return\"Element does not have an alt attribute\"}}},\"duplicate-img-label\":{impact:\"minor\",messages:{pass:function(a){return\"Element does not duplicate existing text in <img> alt text\"},fail:function(a){return\"Element contains <img> element with alt text that duplicates existing text\"}}},\"title-only\":{impact:\"serious\",messages:{pass:function(a){return\"Form element does not solely use title attribute for its label\"},fail:function(a){return\"Only title used to generate label for form element\"}}},\"implicit-label\":{impact:\"critical\",messages:{pass:function(a){return\"Form element has an implicit (wrapped) <label>\"},fail:function(a){return\"Form element does not have an implicit (wrapped) <label>\"}}},\"explicit-label\":{impact:\"critical\",messages:{pass:function(a){return\"Form element has an explicit <label>\"},fail:function(a){return\"Form element does not have an explicit <label>\"}}},\"help-same-as-label\":{impact:\"minor\",messages:{pass:function(a){return\"Help text (title or aria-describedby) does not duplicate label text\"},fail:function(a){return\"Help text (title or aria-describedby) text is the same as the label text\"}}},\"multiple-label\":{impact:\"serious\",messages:{pass:function(a){return\"Form element does not have multiple <label> elements\"},fail:function(a){return\"Form element has multiple <label> elements\"}}},\"has-th\":{impact:\"serious\",messages:{pass:function(a){return\"Layout table does not use <th> elements\"},fail:function(a){return\"Layout table uses <th> elements\"}}},\"has-caption\":{impact:\"serious\",messages:{pass:function(a){return\"Layout table does not use <caption> element\"},fail:function(a){return\"Layout table uses <caption> element\"}}},\"has-summary\":{impact:\"serious\",messages:{pass:function(a){return\"Layout table does not use summary attribute\"},fail:function(a){return\"Layout table uses summary attribute\"}}},\"link-in-text-block\":{impact:\"critical\",messages:{pass:function(a){return\"Links can be distinguished from surrounding text in a way that does not rely on color\"},fail:function(a){return\"Links can not be distinguished from surrounding text in a way that does not rely on color\"},incomplete:{bgContrast:\"Element's contrast ratio could not be determined. Check for a distinct hover/focus style\",\nbgImage:\"Element's contrast ratio could not be determined due to a background image\",bgGradient:\"Element's contrast ratio could not be determined due to a background gradient\",imgNode:\"Element's contrast ratio could not be determined because element contains an image node\",bgOverlap:\"Element's contrast ratio could not be determined because of element overlap\",default:\"Unable to determine contrast ratio\"}}},\"only-listitems\":{impact:\"serious\",messages:{pass:function(a){return\"List element only has direct children that are allowed inside <li> elements\"},fail:function(a){return\"List element has direct children that are not allowed inside <li> elements\"}}},listitem:{impact:\"critical\",messages:{pass:function(a){return'List item has a <ul>, <ol> or role=\"list\" parent element'},fail:function(a){return'List item does not have a <ul>, <ol> or role=\"list\" parent element'}}},\"meta-refresh\":{impact:\"critical\",messages:{pass:function(a){return\"<meta> tag does not immediately refresh the page\"},fail:function(a){return\"<meta> tag forces timed refresh of page\"}}},\"meta-viewport-large\":{impact:\"minor\",messages:{pass:function(a){return\"<meta> tag does not prevent significant zooming\"},fail:function(a){return\"<meta> tag limits zooming\"}}},\"meta-viewport\":{impact:\"critical\",messages:{pass:function(a){return\"<meta> tag does not disable zooming\"},fail:function(a){return\"<meta> tag disables zooming\"}}},\"p-as-heading\":{impact:\"critical\",messages:{pass:function(a){return\"<p> elements are not styled as headings\"},fail:function(a){return\"Heading elements should be used instead of styled p elements\"}}},region:{impact:\"moderate\",messages:{pass:function(a){return\"Content contained by ARIA landmark\"},fail:function(a){return\"Content not contained by an ARIA landmark\"}}},\"html5-scope\":{impact:\"serious\",messages:{pass:function(a){return\"Scope attribute is only used on table header elements (<th>)\"},fail:function(a){return\"In HTML 5, scope attributes may only be used on table header elements (<th>)\"}}},\"scope-value\":{impact:\"critical\",messages:{pass:function(a){return\"Scope attribute is used correctly\"},fail:function(a){return\"The value of the scope attribute may only be 'row' or 'col'\"}}},exists:{impact:\"minor\",messages:{pass:function(a){return\"Element does not exist\"},fail:function(a){return\"Element exists\"}}},\"skip-link\":{impact:\"critical\",messages:{pass:function(a){return\"Valid skip link found\"},fail:function(a){return\"No valid skip link found\"}}},tabindex:{impact:\"serious\",messages:{pass:function(a){return\"Element does not have a tabindex greater than 0\"},fail:function(a){return\"Element has a tabindex greater than 0\"}}},\"same-caption-summary\":{impact:\"moderate\",messages:{pass:function(a){return\"Content of summary attribute and <caption> are not duplicated\"},fail:function(a){return\"Content of summary attribute and <caption> element are identical\"}}},\"caption-faked\":{impact:\"critical\",messages:{pass:function(a){return\"The first row of a table is not used as a caption\"},fail:function(a){return\"The first row of the table should be a caption instead of a table cell\"}}},\"td-has-header\":{impact:\"critical\",messages:{pass:function(a){return\"All non-empty data cells have table headers\"},fail:function(a){return\"Some non-empty data cells do not have table headers\"}}},\"td-headers-attr\":{impact:\"serious\",messages:{pass:function(a){return\"The headers attribute is exclusively used to refer to other cells in the table\"},fail:function(a){return\"The headers attribute is not exclusively used to refer to other cells in the table\"}}},\"th-has-data-cells\":{impact:\"critical\",messages:{pass:function(a){return\"All table header cells refer to data cells\"},fail:function(a){return\"Not all table header cells refer to data cells\"},incomplete:function(a){return\"Table data cells are missing or empty\"}}},description:{impact:\"serious\",messages:{pass:function(a){return\"The multimedia element has an audio description track\"},fail:function(a){return\"The multimedia element does not have an audio description track\"},incomplete:function(a){return\"An audio description track for this element could not be found\"}}}},failureSummaries:{any:{failureMessage:function(a){var b=\"Fix any of the following:\",c=a;if(c)for(var d,e=-1,f=c.length-1;e<f;)d=c[e+=1],b+=\"\\n \"+d.split(\"\\n\").join(\"\\n \");return b}},none:{failureMessage:function(a){var b=\"Fix all of the following:\",c=a;if(c)for(var d,e=-1,f=c.length-1;e<f;)d=c[e+=1],b+=\"\\n \"+d.split(\"\\n\").join(\"\\n \");return b}}}},rules:[{id:\"accesskeys\",selector:\"[accesskey]\",excludeHidden:!1,tags:[\"wcag2a\",\"wcag211\",\"cat.keyboard\"],all:[],any:[],none:[\"accesskeys\"]},{id:\"area-alt\",selector:\"map area[href]\",excludeHidden:!1,tags:[\"cat.text-alternatives\",\"wcag2a\",\"wcag111\",\"section508\",\"section508.22.a\"],all:[],any:[\"non-empty-alt\",\"non-empty-title\",\"aria-label\",\"aria-labelledby\"],none:[]},{id:\"aria-allowed-attr\",matches:function(a){var b=a.getAttribute(\"role\");b||(b=axe.commons.aria.implicitRole(a));var c=axe.commons.aria.allowedAttr(b);if(b&&c){var d=/^aria-/;if(a.hasAttributes())for(var e=a.attributes,f=0,g=e.length;f<g;f++)if(d.test(e[f].name))return!0}return!1},tags:[\"cat.aria\",\"wcag2a\",\"wcag411\",\"wcag412\"],all:[],any:[\"aria-allowed-attr\"],none:[]},{id:\"aria-required-attr\",selector:\"[role]\",tags:[\"cat.aria\",\"wcag2a\",\"wcag411\",\"wcag412\"],all:[],any:[\"aria-required-attr\"],none:[]},{id:\"aria-required-children\",selector:\"[role]\",tags:[\"cat.aria\",\"wcag2a\",\"wcag131\"],all:[],any:[\"aria-required-children\"],none:[]},{id:\"aria-required-parent\",selector:\"[role]\",tags:[\"cat.aria\",\"wcag2a\",\"wcag131\"],all:[],any:[\"aria-required-parent\"],none:[]},{id:\"aria-roles\",selector:\"[role]\",tags:[\"cat.aria\",\"wcag2a\",\"wcag131\",\"wcag411\",\"wcag412\"],all:[],any:[],none:[\"invalidrole\",\"abstractrole\"]},{id:\"aria-valid-attr-value\",matches:function(a){var b=/^aria-/;if(a.hasAttributes())for(var c=a.attributes,d=0,e=c.length;d<e;d++)if(b.test(c[d].name))return!0;return!1},tags:[\"cat.aria\",\"wcag2a\",\"wcag131\",\"wcag411\",\"wcag412\"],all:[],any:[{options:[],id:\"aria-valid-attr-value\"}],none:[]},{id:\"aria-valid-attr\",matches:function(a){var b=/^aria-/;if(a.hasAttributes())for(var c=a.attributes,d=0,e=c.length;d<e;d++)if(b.test(c[d].name))return!0;return!1},tags:[\"cat.aria\",\"wcag2a\",\"wcag411\"],all:[],any:[{options:[],id:\"aria-valid-attr\"}],none:[]},{id:\"audio-caption\",selector:\"audio\",excludeHidden:!1,tags:[\"cat.time-and-media\",\"wcag2a\",\"wcag122\",\"section508\",\"section508.22.a\"],all:[],any:[],none:[\"caption\"]},{id:\"blink\",selector:\"blink\",excludeHidden:!1,tags:[\"cat.time-and-media\",\"wcag2a\",\"wcag222\",\"section508\",\"section508.22.j\"],all:[],any:[],none:[\"is-on-screen\"]},{id:\"button-name\",selector:'button, [role=\"button\"], input[type=\"button\"], input[type=\"submit\"], input[type=\"reset\"]',tags:[\"cat.name-role-value\",\"wcag2a\",\"wcag412\",\"section508\",\"section508.22.a\"],all:[],any:[\"non-empty-if-present\",\"non-empty-value\",\"button-has-visible-text\",\"aria-label\",\"aria-labelledby\",\"role-presentation\",\"role-none\"],none:[\"focusable-no-name\"]},{id:\"bypass\",selector:\"html\",pageLevel:!0,matches:function(a){return!!a.querySelector(\"a[href]\")},tags:[\"cat.keyboard\",\"wcag2a\",\"wcag241\",\"section508\",\"section508.22.o\"],all:[],any:[\"internal-link-present\",\"header-present\",\"landmark\"],none:[]},{id:\"checkboxgroup\",selector:\"input[type=checkbox][name]\",tags:[\"cat.forms\",\"best-practice\"],all:[],any:[\"group-labelledby\",\"fieldset\"],none:[]},{id:\"color-contrast\",matches:function(a){var b=a.nodeName.toUpperCase(),c=a.type,d=document;if(\"true\"===a.getAttribute(\"aria-disabled\")||axe.commons.dom.findUp(a,'[aria-disabled=\"true\"]'))return!1;if(\"INPUT\"===b)return-1===[\"hidden\",\"range\",\"color\",\"checkbox\",\"radio\",\"image\"].indexOf(c)&&!a.disabled;if(\"SELECT\"===b)return!!a.options.length&&!a.disabled;if(\"TEXTAREA\"===b)return!a.disabled;if(\"OPTION\"===b)return!1;if(\"BUTTON\"===b&&a.disabled||axe.commons.dom.findUp(a,\"button[disabled]\"))return!1;if(\"FIELDSET\"===b&&a.disabled||axe.commons.dom.findUp(a,\"fieldset[disabled]\"))return!1;var e=axe.commons.dom.findUp(a,\"label\");if(\"LABEL\"===b||e){var f=a;e&&(f=e);var g=f.htmlFor&&d.getElementById(f.htmlFor);if(g&&g.disabled)return!1;var g=a.querySelector('input:not([type=\"hidden\"]):not([type=\"image\"]):not([type=\"button\"]):not([type=\"submit\"]):not([type=\"reset\"]), select, textarea');if(g&&g.disabled)return!1}if(a.id){var g=d.querySelector(\"[aria-labelledby~=\"+axe.commons.utils.escapeSelector(a.id)+\"]\");if(g&&g.disabled)return!1}if(\"\"===axe.commons.text.visible(a,!1,!0))return!1;var h,i,j=document.createRange(),k=a.childNodes,l=k.length;for(i=0;i<l;i++)h=k[i],3===h.nodeType&&\"\"!==axe.commons.text.sanitize(h.nodeValue)&&j.selectNodeContents(h);var m=j.getClientRects();for(l=m.length,i=0;i<l;i++)if(axe.commons.dom.visuallyOverlaps(m[i],a))return!0;return!1},excludeHidden:!1,options:{noScroll:!1},tags:[\"cat.color\",\"wcag2aa\",\"wcag143\"],all:[],any:[\"color-contrast\"],none:[]},{id:\"definition-list\",selector:\"dl:not([role])\",tags:[\"cat.structure\",\"wcag2a\",\"wcag131\"],all:[],any:[],none:[\"structured-dlitems\",\"only-dlitems\"]},{id:\"dlitem\",selector:\"dd:not([role]), dt:not([role])\",tags:[\"cat.structure\",\"wcag2a\",\"wcag131\"],all:[],any:[\"dlitem\"],none:[]},{id:\"document-title\",selector:\"html\",matches:function(a){return a.ownerDocument.defaultView.self===a.ownerDocument.defaultView.top},tags:[\"cat.text-alternatives\",\"wcag2a\",\"wcag242\"],all:[],any:[\"doc-has-title\"],none:[]},{id:\"duplicate-id\",selector:\"[id]\",excludeHidden:!1,tags:[\"cat.parsing\",\"wcag2a\",\"wcag411\"],all:[],any:[\"duplicate-id\"],none:[]},{id:\"empty-heading\",selector:'h1, h2, h3, h4, h5, h6, [role=\"heading\"]',enabled:!0,tags:[\"cat.name-role-value\",\"best-practice\"],all:[],any:[\"has-visible-text\",\"role-presentation\",\"role-none\"],none:[]},{id:\"frame-title-unique\",selector:\"frame[title]:not([title='']), iframe[title]:not([title=''])\",matches:function(a){var b=a.getAttribute(\"title\");return!!(b?axe.commons.text.sanitize(b).trim():\"\")},tags:[\"cat.text-alternatives\",\"best-practice\"],all:[],any:[],none:[\"unique-frame-title\"]},{id:\"frame-title\",selector:\"frame, iframe\",tags:[\"cat.text-alternatives\",\"wcag2a\",\"wcag241\",\"section508\",\"section508.22.i\"],all:[],any:[\"aria-label\",\"aria-labelledby\",\"non-empty-title\",\"role-presentation\",\"role-none\"],none:[]},{id:\"heading-order\",selector:\"h1,h2,h3,h4,h5,h6,[role=heading]\",enabled:!1,tags:[\"cat.semantics\",\"best-practice\"],all:[],any:[\"heading-order\"],none:[]},{id:\"href-no-hash\",selector:\"a[href]\",enabled:!1,tags:[\"cat.semantics\",\"best-practice\"],all:[],any:[\"href-no-hash\"],none:[]},{id:\"html-has-lang\",selector:\"html\",tags:[\"cat.language\",\"wcag2a\",\"wcag311\"],all:[],any:[\"has-lang\"],none:[]},{id:\"html-lang-valid\",selector:\"html[lang]\",tags:[\"cat.language\",\"wcag2a\",\"wcag311\"],all:[],any:[],none:[{\noptions:[\"aa\",\"ab\",\"ae\",\"af\",\"ak\",\"am\",\"an\",\"ar\",\"as\",\"av\",\"ay\",\"az\",\"ba\",\"be\",\"bg\",\"bh\",\"bi\",\"bm\",\"bn\",\"bo\",\"br\",\"bs\",\"ca\",\"ce\",\"ch\",\"co\",\"cr\",\"cs\",\"cu\",\"cv\",\"cy\",\"da\",\"de\",\"dv\",\"dz\",\"ee\",\"el\",\"en\",\"eo\",\"es\",\"et\",\"eu\",\"fa\",\"ff\",\"fi\",\"fj\",\"fo\",\"fr\",\"fy\",\"ga\",\"gd\",\"gl\",\"gn\",\"gu\",\"gv\",\"ha\",\"he\",\"hi\",\"ho\",\"hr\",\"ht\",\"hu\",\"hy\",\"hz\",\"ia\",\"id\",\"ie\",\"ig\",\"ii\",\"ik\",\"in\",\"io\",\"is\",\"it\",\"iu\",\"iw\",\"ja\",\"ji\",\"jv\",\"jw\",\"ka\",\"kg\",\"ki\",\"kj\",\"kk\",\"kl\",\"km\",\"kn\",\"ko\",\"kr\",\"ks\",\"ku\",\"kv\",\"kw\",\"ky\",\"la\",\"lb\",\"lg\",\"li\",\"ln\",\"lo\",\"lt\",\"lu\",\"lv\",\"mg\",\"mh\",\"mi\",\"mk\",\"ml\",\"mn\",\"mo\",\"mr\",\"ms\",\"mt\",\"my\",\"na\",\"nb\",\"nd\",\"ne\",\"ng\",\"nl\",\"nn\",\"no\",\"nr\",\"nv\",\"ny\",\"oc\",\"oj\",\"om\",\"or\",\"os\",\"pa\",\"pi\",\"pl\",\"ps\",\"pt\",\"qu\",\"rm\",\"rn\",\"ro\",\"ru\",\"rw\",\"sa\",\"sc\",\"sd\",\"se\",\"sg\",\"sh\",\"si\",\"sk\",\"sl\",\"sm\",\"sn\",\"so\",\"sq\",\"sr\",\"ss\",\"st\",\"su\",\"sv\",\"sw\",\"ta\",\"te\",\"tg\",\"th\",\"ti\",\"tk\",\"tl\",\"tn\",\"to\",\"tr\",\"ts\",\"tt\",\"tw\",\"ty\",\"ug\",\"uk\",\"ur\",\"uz\",\"ve\",\"vi\",\"vo\",\"wa\",\"wo\",\"xh\",\"yi\",\"yo\",\"za\",\"zh\",\"zu\",\"aaa\",\"aab\",\"aac\",\"aad\",\"aae\",\"aaf\",\"aag\",\"aah\",\"aai\",\"aak\",\"aal\",\"aam\",\"aan\",\"aao\",\"aap\",\"aaq\",\"aas\",\"aat\",\"aau\",\"aav\",\"aaw\",\"aax\",\"aaz\",\"aba\",\"abb\",\"abc\",\"abd\",\"abe\",\"abf\",\"abg\",\"abh\",\"abi\",\"abj\",\"abl\",\"abm\",\"abn\",\"abo\",\"abp\",\"abq\",\"abr\",\"abs\",\"abt\",\"abu\",\"abv\",\"abw\",\"abx\",\"aby\",\"abz\",\"aca\",\"acb\",\"acd\",\"ace\",\"acf\",\"ach\",\"aci\",\"ack\",\"acl\",\"acm\",\"acn\",\"acp\",\"acq\",\"acr\",\"acs\",\"act\",\"acu\",\"acv\",\"acw\",\"acx\",\"acy\",\"acz\",\"ada\",\"adb\",\"add\",\"ade\",\"adf\",\"adg\",\"adh\",\"adi\",\"adj\",\"adl\",\"adn\",\"ado\",\"adp\",\"adq\",\"adr\",\"ads\",\"adt\",\"adu\",\"adw\",\"adx\",\"ady\",\"adz\",\"aea\",\"aeb\",\"aec\",\"aed\",\"aee\",\"aek\",\"ael\",\"aem\",\"aen\",\"aeq\",\"aer\",\"aes\",\"aeu\",\"aew\",\"aey\",\"aez\",\"afa\",\"afb\",\"afd\",\"afe\",\"afg\",\"afh\",\"afi\",\"afk\",\"afn\",\"afo\",\"afp\",\"afs\",\"aft\",\"afu\",\"afz\",\"aga\",\"agb\",\"agc\",\"agd\",\"age\",\"agf\",\"agg\",\"agh\",\"agi\",\"agj\",\"agk\",\"agl\",\"agm\",\"agn\",\"ago\",\"agp\",\"agq\",\"agr\",\"ags\",\"agt\",\"agu\",\"agv\",\"agw\",\"agx\",\"agy\",\"agz\",\"aha\",\"ahb\",\"ahg\",\"ahh\",\"ahi\",\"ahk\",\"ahl\",\"ahm\",\"ahn\",\"aho\",\"ahp\",\"ahr\",\"ahs\",\"aht\",\"aia\",\"aib\",\"aic\",\"aid\",\"aie\",\"aif\",\"aig\",\"aih\",\"aii\",\"aij\",\"aik\",\"ail\",\"aim\",\"ain\",\"aio\",\"aip\",\"aiq\",\"air\",\"ais\",\"ait\",\"aiw\",\"aix\",\"aiy\",\"aja\",\"ajg\",\"aji\",\"ajn\",\"ajp\",\"ajt\",\"aju\",\"ajw\",\"ajz\",\"akb\",\"akc\",\"akd\",\"ake\",\"akf\",\"akg\",\"akh\",\"aki\",\"akj\",\"akk\",\"akl\",\"akm\",\"ako\",\"akp\",\"akq\",\"akr\",\"aks\",\"akt\",\"aku\",\"akv\",\"akw\",\"akx\",\"aky\",\"akz\",\"ala\",\"alc\",\"ald\",\"ale\",\"alf\",\"alg\",\"alh\",\"ali\",\"alj\",\"alk\",\"all\",\"alm\",\"aln\",\"alo\",\"alp\",\"alq\",\"alr\",\"als\",\"alt\",\"alu\",\"alv\",\"alw\",\"alx\",\"aly\",\"alz\",\"ama\",\"amb\",\"amc\",\"ame\",\"amf\",\"amg\",\"ami\",\"amj\",\"amk\",\"aml\",\"amm\",\"amn\",\"amo\",\"amp\",\"amq\",\"amr\",\"ams\",\"amt\",\"amu\",\"amv\",\"amw\",\"amx\",\"amy\",\"amz\",\"ana\",\"anb\",\"anc\",\"and\",\"ane\",\"anf\",\"ang\",\"anh\",\"ani\",\"anj\",\"ank\",\"anl\",\"anm\",\"ann\",\"ano\",\"anp\",\"anq\",\"anr\",\"ans\",\"ant\",\"anu\",\"anv\",\"anw\",\"anx\",\"any\",\"anz\",\"aoa\",\"aob\",\"aoc\",\"aod\",\"aoe\",\"aof\",\"aog\",\"aoh\",\"aoi\",\"aoj\",\"aok\",\"aol\",\"aom\",\"aon\",\"aor\",\"aos\",\"aot\",\"aou\",\"aox\",\"aoz\",\"apa\",\"apb\",\"apc\",\"apd\",\"ape\",\"apf\",\"apg\",\"aph\",\"api\",\"apj\",\"apk\",\"apl\",\"apm\",\"apn\",\"apo\",\"app\",\"apq\",\"apr\",\"aps\",\"apt\",\"apu\",\"apv\",\"apw\",\"apx\",\"apy\",\"apz\",\"aqa\",\"aqc\",\"aqd\",\"aqg\",\"aql\",\"aqm\",\"aqn\",\"aqp\",\"aqr\",\"aqt\",\"aqz\",\"arb\",\"arc\",\"ard\",\"are\",\"arh\",\"ari\",\"arj\",\"ark\",\"arl\",\"arn\",\"aro\",\"arp\",\"arq\",\"arr\",\"ars\",\"art\",\"aru\",\"arv\",\"arw\",\"arx\",\"ary\",\"arz\",\"asa\",\"asb\",\"asc\",\"asd\",\"ase\",\"asf\",\"asg\",\"ash\",\"asi\",\"asj\",\"ask\",\"asl\",\"asn\",\"aso\",\"asp\",\"asq\",\"asr\",\"ass\",\"ast\",\"asu\",\"asv\",\"asw\",\"asx\",\"asy\",\"asz\",\"ata\",\"atb\",\"atc\",\"atd\",\"ate\",\"atg\",\"ath\",\"ati\",\"atj\",\"atk\",\"atl\",\"atm\",\"atn\",\"ato\",\"atp\",\"atq\",\"atr\",\"ats\",\"att\",\"atu\",\"atv\",\"atw\",\"atx\",\"aty\",\"atz\",\"aua\",\"aub\",\"auc\",\"aud\",\"aue\",\"auf\",\"aug\",\"auh\",\"aui\",\"auj\",\"auk\",\"aul\",\"aum\",\"aun\",\"auo\",\"aup\",\"auq\",\"aur\",\"aus\",\"aut\",\"auu\",\"auw\",\"aux\",\"auy\",\"auz\",\"avb\",\"avd\",\"avi\",\"avk\",\"avl\",\"avm\",\"avn\",\"avo\",\"avs\",\"avt\",\"avu\",\"avv\",\"awa\",\"awb\",\"awc\",\"awd\",\"awe\",\"awg\",\"awh\",\"awi\",\"awk\",\"awm\",\"awn\",\"awo\",\"awr\",\"aws\",\"awt\",\"awu\",\"awv\",\"aww\",\"awx\",\"awy\",\"axb\",\"axe\",\"axg\",\"axk\",\"axl\",\"axm\",\"axx\",\"aya\",\"ayb\",\"ayc\",\"ayd\",\"aye\",\"ayg\",\"ayh\",\"ayi\",\"ayk\",\"ayl\",\"ayn\",\"ayo\",\"ayp\",\"ayq\",\"ayr\",\"ays\",\"ayt\",\"ayu\",\"ayx\",\"ayy\",\"ayz\",\"aza\",\"azb\",\"azc\",\"azd\",\"azg\",\"azj\",\"azm\",\"azn\",\"azo\",\"azt\",\"azz\",\"baa\",\"bab\",\"bac\",\"bad\",\"bae\",\"baf\",\"bag\",\"bah\",\"bai\",\"baj\",\"bal\",\"ban\",\"bao\",\"bap\",\"bar\",\"bas\",\"bat\",\"bau\",\"bav\",\"baw\",\"bax\",\"bay\",\"baz\",\"bba\",\"bbb\",\"bbc\",\"bbd\",\"bbe\",\"bbf\",\"bbg\",\"bbh\",\"bbi\",\"bbj\",\"bbk\",\"bbl\",\"bbm\",\"bbn\",\"bbo\",\"bbp\",\"bbq\",\"bbr\",\"bbs\",\"bbt\",\"bbu\",\"bbv\",\"bbw\",\"bbx\",\"bby\",\"bbz\",\"bca\",\"bcb\",\"bcc\",\"bcd\",\"bce\",\"bcf\",\"bcg\",\"bch\",\"bci\",\"bcj\",\"bck\",\"bcl\",\"bcm\",\"bcn\",\"bco\",\"bcp\",\"bcq\",\"bcr\",\"bcs\",\"bct\",\"bcu\",\"bcv\",\"bcw\",\"bcy\",\"bcz\",\"bda\",\"bdb\",\"bdc\",\"bdd\",\"bde\",\"bdf\",\"bdg\",\"bdh\",\"bdi\",\"bdj\",\"bdk\",\"bdl\",\"bdm\",\"bdn\",\"bdo\",\"bdp\",\"bdq\",\"bdr\",\"bds\",\"bdt\",\"bdu\",\"bdv\",\"bdw\",\"bdx\",\"bdy\",\"bdz\",\"bea\",\"beb\",\"bec\",\"bed\",\"bee\",\"bef\",\"beg\",\"beh\",\"bei\",\"bej\",\"bek\",\"bem\",\"beo\",\"bep\",\"beq\",\"ber\",\"bes\",\"bet\",\"beu\",\"bev\",\"bew\",\"bex\",\"bey\",\"bez\",\"bfa\",\"bfb\",\"bfc\",\"bfd\",\"bfe\",\"bff\",\"bfg\",\"bfh\",\"bfi\",\"bfj\",\"bfk\",\"bfl\",\"bfm\",\"bfn\",\"bfo\",\"bfp\",\"bfq\",\"bfr\",\"bfs\",\"bft\",\"bfu\",\"bfw\",\"bfx\",\"bfy\",\"bfz\",\"bga\",\"bgb\",\"bgc\",\"bgd\",\"bge\",\"bgf\",\"bgg\",\"bgi\",\"bgj\",\"bgk\",\"bgl\",\"bgm\",\"bgn\",\"bgo\",\"bgp\",\"bgq\",\"bgr\",\"bgs\",\"bgt\",\"bgu\",\"bgv\",\"bgw\",\"bgx\",\"bgy\",\"bgz\",\"bha\",\"bhb\",\"bhc\",\"bhd\",\"bhe\",\"bhf\",\"bhg\",\"bhh\",\"bhi\",\"bhj\",\"bhk\",\"bhl\",\"bhm\",\"bhn\",\"bho\",\"bhp\",\"bhq\",\"bhr\",\"bhs\",\"bht\",\"bhu\",\"bhv\",\"bhw\",\"bhx\",\"bhy\",\"bhz\",\"bia\",\"bib\",\"bic\",\"bid\",\"bie\",\"bif\",\"big\",\"bij\",\"bik\",\"bil\",\"bim\",\"bin\",\"bio\",\"bip\",\"biq\",\"bir\",\"bit\",\"biu\",\"biv\",\"biw\",\"bix\",\"biy\",\"biz\",\"bja\",\"bjb\",\"bjc\",\"bjd\",\"bje\",\"bjf\",\"bjg\",\"bjh\",\"bji\",\"bjj\",\"bjk\",\"bjl\",\"bjm\",\"bjn\",\"bjo\",\"bjp\",\"bjq\",\"bjr\",\"bjs\",\"bjt\",\"bju\",\"bjv\",\"bjw\",\"bjx\",\"bjy\",\"bjz\",\"bka\",\"bkb\",\"bkc\",\"bkd\",\"bkf\",\"bkg\",\"bkh\",\"bki\",\"bkj\",\"bkk\",\"bkl\",\"bkm\",\"bkn\",\"bko\",\"bkp\",\"bkq\",\"bkr\",\"bks\",\"bkt\",\"bku\",\"bkv\",\"bkw\",\"bkx\",\"bky\",\"bkz\",\"bla\",\"blb\",\"blc\",\"bld\",\"ble\",\"blf\",\"blg\",\"blh\",\"bli\",\"blj\",\"blk\",\"bll\",\"blm\",\"bln\",\"blo\",\"blp\",\"blq\",\"blr\",\"bls\",\"blt\",\"blv\",\"blw\",\"blx\",\"bly\",\"blz\",\"bma\",\"bmb\",\"bmc\",\"bmd\",\"bme\",\"bmf\",\"bmg\",\"bmh\",\"bmi\",\"bmj\",\"bmk\",\"bml\",\"bmm\",\"bmn\",\"bmo\",\"bmp\",\"bmq\",\"bmr\",\"bms\",\"bmt\",\"bmu\",\"bmv\",\"bmw\",\"bmx\",\"bmy\",\"bmz\",\"bna\",\"bnb\",\"bnc\",\"bnd\",\"bne\",\"bnf\",\"bng\",\"bni\",\"bnj\",\"bnk\",\"bnl\",\"bnm\",\"bnn\",\"bno\",\"bnp\",\"bnq\",\"bnr\",\"bns\",\"bnt\",\"bnu\",\"bnv\",\"bnw\",\"bnx\",\"bny\",\"bnz\",\"boa\",\"bob\",\"boe\",\"bof\",\"bog\",\"boh\",\"boi\",\"boj\",\"bok\",\"bol\",\"bom\",\"bon\",\"boo\",\"bop\",\"boq\",\"bor\",\"bot\",\"bou\",\"bov\",\"bow\",\"box\",\"boy\",\"boz\",\"bpa\",\"bpb\",\"bpd\",\"bpg\",\"bph\",\"bpi\",\"bpj\",\"bpk\",\"bpl\",\"bpm\",\"bpn\",\"bpo\",\"bpp\",\"bpq\",\"bpr\",\"bps\",\"bpt\",\"bpu\",\"bpv\",\"bpw\",\"bpx\",\"bpy\",\"bpz\",\"bqa\",\"bqb\",\"bqc\",\"bqd\",\"bqf\",\"bqg\",\"bqh\",\"bqi\",\"bqj\",\"bqk\",\"bql\",\"bqm\",\"bqn\",\"bqo\",\"bqp\",\"bqq\",\"bqr\",\"bqs\",\"bqt\",\"bqu\",\"bqv\",\"bqw\",\"bqx\",\"bqy\",\"bqz\",\"bra\",\"brb\",\"brc\",\"brd\",\"brf\",\"brg\",\"brh\",\"bri\",\"brj\",\"brk\",\"brl\",\"brm\",\"brn\",\"bro\",\"brp\",\"brq\",\"brr\",\"brs\",\"brt\",\"bru\",\"brv\",\"brw\",\"brx\",\"bry\",\"brz\",\"bsa\",\"bsb\",\"bsc\",\"bse\",\"bsf\",\"bsg\",\"bsh\",\"bsi\",\"bsj\",\"bsk\",\"bsl\",\"bsm\",\"bsn\",\"bso\",\"bsp\",\"bsq\",\"bsr\",\"bss\",\"bst\",\"bsu\",\"bsv\",\"bsw\",\"bsx\",\"bsy\",\"bta\",\"btb\",\"btc\",\"btd\",\"bte\",\"btf\",\"btg\",\"bth\",\"bti\",\"btj\",\"btk\",\"btl\",\"btm\",\"btn\",\"bto\",\"btp\",\"btq\",\"btr\",\"bts\",\"btt\",\"btu\",\"btv\",\"btw\",\"btx\",\"bty\",\"btz\",\"bua\",\"bub\",\"buc\",\"bud\",\"bue\",\"buf\",\"bug\",\"buh\",\"bui\",\"buj\",\"buk\",\"bum\",\"bun\",\"buo\",\"bup\",\"buq\",\"bus\",\"but\",\"buu\",\"buv\",\"buw\",\"bux\",\"buy\",\"buz\",\"bva\",\"bvb\",\"bvc\",\"bvd\",\"bve\",\"bvf\",\"bvg\",\"bvh\",\"bvi\",\"bvj\",\"bvk\",\"bvl\",\"bvm\",\"bvn\",\"bvo\",\"bvp\",\"bvq\",\"bvr\",\"bvt\",\"bvu\",\"bvv\",\"bvw\",\"bvx\",\"bvy\",\"bvz\",\"bwa\",\"bwb\",\"bwc\",\"bwd\",\"bwe\",\"bwf\",\"bwg\",\"bwh\",\"bwi\",\"bwj\",\"bwk\",\"bwl\",\"bwm\",\"bwn\",\"bwo\",\"bwp\",\"bwq\",\"bwr\",\"bws\",\"bwt\",\"bwu\",\"bww\",\"bwx\",\"bwy\",\"bwz\",\"bxa\",\"bxb\",\"bxc\",\"bxd\",\"bxe\",\"bxf\",\"bxg\",\"bxh\",\"bxi\",\"bxj\",\"bxk\",\"bxl\",\"bxm\",\"bxn\",\"bxo\",\"bxp\",\"bxq\",\"bxr\",\"bxs\",\"bxu\",\"bxv\",\"bxw\",\"bxx\",\"bxz\",\"bya\",\"byb\",\"byc\",\"byd\",\"bye\",\"byf\",\"byg\",\"byh\",\"byi\",\"byj\",\"byk\",\"byl\",\"bym\",\"byn\",\"byo\",\"byp\",\"byq\",\"byr\",\"bys\",\"byt\",\"byv\",\"byw\",\"byx\",\"byy\",\"byz\",\"bza\",\"bzb\",\"bzc\",\"bzd\",\"bze\",\"bzf\",\"bzg\",\"bzh\",\"bzi\",\"bzj\",\"bzk\",\"bzl\",\"bzm\",\"bzn\",\"bzo\",\"bzp\",\"bzq\",\"bzr\",\"bzs\",\"bzt\",\"bzu\",\"bzv\",\"bzw\",\"bzx\",\"bzy\",\"bzz\",\"caa\",\"cab\",\"cac\",\"cad\",\"cae\",\"caf\",\"cag\",\"cah\",\"cai\",\"caj\",\"cak\",\"cal\",\"cam\",\"can\",\"cao\",\"cap\",\"caq\",\"car\",\"cas\",\"cau\",\"cav\",\"caw\",\"cax\",\"cay\",\"caz\",\"cba\",\"cbb\",\"cbc\",\"cbd\",\"cbe\",\"cbg\",\"cbh\",\"cbi\",\"cbj\",\"cbk\",\"cbl\",\"cbn\",\"cbo\",\"cbq\",\"cbr\",\"cbs\",\"cbt\",\"cbu\",\"cbv\",\"cbw\",\"cby\",\"cca\",\"ccc\",\"ccd\",\"cce\",\"ccg\",\"cch\",\"ccj\",\"ccl\",\"ccm\",\"ccn\",\"cco\",\"ccp\",\"ccq\",\"ccr\",\"ccs\",\"cda\",\"cdc\",\"cdd\",\"cde\",\"cdf\",\"cdg\",\"cdh\",\"cdi\",\"cdj\",\"cdm\",\"cdn\",\"cdo\",\"cdr\",\"cds\",\"cdy\",\"cdz\",\"cea\",\"ceb\",\"ceg\",\"cek\",\"cel\",\"cen\",\"cet\",\"cfa\",\"cfd\",\"cfg\",\"cfm\",\"cga\",\"cgc\",\"cgg\",\"cgk\",\"chb\",\"chc\",\"chd\",\"chf\",\"chg\",\"chh\",\"chj\",\"chk\",\"chl\",\"chm\",\"chn\",\"cho\",\"chp\",\"chq\",\"chr\",\"cht\",\"chw\",\"chx\",\"chy\",\"chz\",\"cia\",\"cib\",\"cic\",\"cid\",\"cie\",\"cih\",\"cik\",\"cim\",\"cin\",\"cip\",\"cir\",\"ciw\",\"ciy\",\"cja\",\"cje\",\"cjh\",\"cji\",\"cjk\",\"cjm\",\"cjn\",\"cjo\",\"cjp\",\"cjr\",\"cjs\",\"cjv\",\"cjy\",\"cka\",\"ckb\",\"ckh\",\"ckl\",\"ckn\",\"cko\",\"ckq\",\"ckr\",\"cks\",\"ckt\",\"cku\",\"ckv\",\"ckx\",\"cky\",\"ckz\",\"cla\",\"clc\",\"cld\",\"cle\",\"clh\",\"cli\",\"clj\",\"clk\",\"cll\",\"clm\",\"clo\",\"clt\",\"clu\",\"clw\",\"cly\",\"cma\",\"cmc\",\"cme\",\"cmg\",\"cmi\",\"cmk\",\"cml\",\"cmm\",\"cmn\",\"cmo\",\"cmr\",\"cms\",\"cmt\",\"cna\",\"cnb\",\"cnc\",\"cng\",\"cnh\",\"cni\",\"cnk\",\"cnl\",\"cno\",\"cns\",\"cnt\",\"cnu\",\"cnw\",\"cnx\",\"coa\",\"cob\",\"coc\",\"cod\",\"coe\",\"cof\",\"cog\",\"coh\",\"coj\",\"cok\",\"col\",\"com\",\"con\",\"coo\",\"cop\",\"coq\",\"cot\",\"cou\",\"cov\",\"cow\",\"cox\",\"coy\",\"coz\",\"cpa\",\"cpb\",\"cpc\",\"cpe\",\"cpf\",\"cpg\",\"cpi\",\"cpn\",\"cpo\",\"cpp\",\"cps\",\"cpu\",\"cpx\",\"cpy\",\"cqd\",\"cqu\",\"cra\",\"crb\",\"crc\",\"crd\",\"crf\",\"crg\",\"crh\",\"cri\",\"crj\",\"crk\",\"crl\",\"crm\",\"crn\",\"cro\",\"crp\",\"crq\",\"crr\",\"crs\",\"crt\",\"crv\",\"crw\",\"crx\",\"cry\",\"crz\",\"csa\",\"csb\",\"csc\",\"csd\",\"cse\",\"csf\",\"csg\",\"csh\",\"csi\",\"csj\",\"csk\",\"csl\",\"csm\",\"csn\",\"cso\",\"csq\",\"csr\",\"css\",\"cst\",\"csu\",\"csv\",\"csw\",\"csy\",\"csz\",\"cta\",\"ctc\",\"ctd\",\"cte\",\"ctg\",\"cth\",\"ctl\",\"ctm\",\"ctn\",\"cto\",\"ctp\",\"cts\",\"ctt\",\"ctu\",\"ctz\",\"cua\",\"cub\",\"cuc\",\"cug\",\"cuh\",\"cui\",\"cuj\",\"cuk\",\"cul\",\"cum\",\"cuo\",\"cup\",\"cuq\",\"cur\",\"cus\",\"cut\",\"cuu\",\"cuv\",\"cuw\",\"cux\",\"cvg\",\"cvn\",\"cwa\",\"cwb\",\"cwd\",\"cwe\",\"cwg\",\"cwt\",\"cya\",\"cyb\",\"cyo\",\"czh\",\"czk\",\"czn\",\"czo\",\"czt\",\"daa\",\"dac\",\"dad\",\"dae\",\"daf\",\"dag\",\"dah\",\"dai\",\"daj\",\"dak\",\"dal\",\"dam\",\"dao\",\"dap\",\"daq\",\"dar\",\"das\",\"dau\",\"dav\",\"daw\",\"dax\",\"day\",\"daz\",\"dba\",\"dbb\",\"dbd\",\"dbe\",\"dbf\",\"dbg\",\"dbi\",\"dbj\",\"dbl\",\"dbm\",\"dbn\",\"dbo\",\"dbp\",\"dbq\",\"dbr\",\"dbt\",\"dbu\",\"dbv\",\"dbw\",\"dby\",\"dcc\",\"dcr\",\"dda\",\"ddd\",\"dde\",\"ddg\",\"ddi\",\"ddj\",\"ddn\",\"ddo\",\"ddr\",\"dds\",\"ddw\",\"dec\",\"ded\",\"dee\",\"def\",\"deg\",\"deh\",\"dei\",\"dek\",\"del\",\"dem\",\"den\",\"dep\",\"deq\",\"der\",\"des\",\"dev\",\"dez\",\"dga\",\"dgb\",\"dgc\",\"dgd\",\"dge\",\"dgg\",\"dgh\",\"dgi\",\"dgk\",\"dgl\",\"dgn\",\"dgo\",\"dgr\",\"dgs\",\"dgt\",\"dgu\",\"dgw\",\"dgx\",\"dgz\",\"dha\",\"dhd\",\"dhg\",\"dhi\",\"dhl\",\"dhm\",\"dhn\",\"dho\",\"dhr\",\"dhs\",\"dhu\",\"dhv\",\"dhw\",\"dhx\",\"dia\",\"dib\",\"dic\",\"did\",\"dif\",\"dig\",\"dih\",\"dii\",\"dij\",\"dik\",\"dil\",\"dim\",\"din\",\"dio\",\"dip\",\"diq\",\"dir\",\"dis\",\"dit\",\"diu\",\"diw\",\"dix\",\"diy\",\"diz\",\"dja\",\"djb\",\"djc\",\"djd\",\"dje\",\"djf\",\"dji\",\"djj\",\"djk\",\"djl\",\"djm\",\"djn\",\"djo\",\"djr\",\"dju\",\"djw\",\"dka\",\"dkk\",\"dkl\",\"dkr\",\"dks\",\"dkx\",\"dlg\",\"dlk\",\"dlm\",\"dln\",\"dma\",\"dmb\",\"dmc\",\"dmd\",\"dme\",\"dmg\",\"dmk\",\"dml\",\"dmm\",\"dmn\",\"dmo\",\"dmr\",\"dms\",\"dmu\",\"dmv\",\"dmw\",\"dmx\",\"dmy\",\"dna\",\"dnd\",\"dne\",\"dng\",\"dni\",\"dnj\",\"dnk\",\"dnn\",\"dnr\",\"dnt\",\"dnu\",\"dnv\",\"dnw\",\"dny\",\"doa\",\"dob\",\"doc\",\"doe\",\"dof\",\"doh\",\"doi\",\"dok\",\"dol\",\"don\",\"doo\",\"dop\",\"doq\",\"dor\",\"dos\",\"dot\",\"dov\",\"dow\",\"dox\",\"doy\",\"doz\",\"dpp\",\"dra\",\"drb\",\"drc\",\"drd\",\"dre\",\"drg\",\"drh\",\"dri\",\"drl\",\"drn\",\"dro\",\"drq\",\"drr\",\"drs\",\"drt\",\"dru\",\"drw\",\"dry\",\"dsb\",\"dse\",\"dsh\",\"dsi\",\"dsl\",\"dsn\",\"dso\",\"dsq\",\"dta\",\"dtb\",\"dtd\",\"dth\",\"dti\",\"dtk\",\"dtm\",\"dtn\",\"dto\",\"dtp\",\"dtr\",\"dts\",\"dtt\",\"dtu\",\"dty\",\"dua\",\"dub\",\"duc\",\"dud\",\"due\",\"duf\",\"dug\",\"duh\",\"dui\",\"duj\",\"duk\",\"dul\",\"dum\",\"dun\",\"duo\",\"dup\",\"duq\",\"dur\",\"dus\",\"duu\",\"duv\",\"duw\",\"dux\",\"duy\",\"duz\",\"dva\",\"dwa\",\"dwl\",\"dwr\",\"dws\",\"dwu\",\"dww\",\"dwy\",\"dya\",\"dyb\",\"dyd\",\"dyg\",\"dyi\",\"dym\",\"dyn\",\"dyo\",\"dyu\",\"dyy\",\"dza\",\"dzd\",\"dze\",\"dzg\",\"dzl\",\"dzn\",\"eaa\",\"ebg\",\"ebk\",\"ebo\",\"ebr\",\"ebu\",\"ecr\",\"ecs\",\"ecy\",\"eee\",\"efa\",\"efe\",\"efi\",\"ega\",\"egl\",\"ego\",\"egx\",\"egy\",\"ehu\",\"eip\",\"eit\",\"eiv\",\"eja\",\"eka\",\"ekc\",\"eke\",\"ekg\",\"eki\",\"ekk\",\"ekl\",\"ekm\",\"eko\",\"ekp\",\"ekr\",\"eky\",\"ele\",\"elh\",\"eli\",\"elk\",\"elm\",\"elo\",\"elp\",\"elu\",\"elx\",\"ema\",\"emb\",\"eme\",\"emg\",\"emi\",\"emk\",\"emm\",\"emn\",\"emo\",\"emp\",\"ems\",\"emu\",\"emw\",\"emx\",\"emy\",\"ena\",\"enb\",\"enc\",\"end\",\"enf\",\"enh\",\"enl\",\"enm\",\"enn\",\"eno\",\"enq\",\"enr\",\"enu\",\"env\",\"enw\",\"enx\",\"eot\",\"epi\",\"era\",\"erg\",\"erh\",\"eri\",\"erk\",\"ero\",\"err\",\"ers\",\"ert\",\"erw\",\"ese\",\"esg\",\"esh\",\"esi\",\"esk\",\"esl\",\"esm\",\"esn\",\"eso\",\"esq\",\"ess\",\"esu\",\"esx\",\"esy\",\"etb\",\"etc\",\"eth\",\"etn\",\"eto\",\"etr\",\"ets\",\"ett\",\"etu\",\"etx\",\"etz\",\"euq\",\"eve\",\"evh\",\"evn\",\"ewo\",\"ext\",\"eya\",\"eyo\",\"eza\",\"eze\",\"faa\",\"fab\",\"fad\",\"faf\",\"fag\",\"fah\",\"fai\",\"faj\",\"fak\",\"fal\",\"fam\",\"fan\",\"fap\",\"far\",\"fat\",\"fau\",\"fax\",\"fay\",\"faz\",\"fbl\",\"fcs\",\"fer\",\"ffi\",\"ffm\",\"fgr\",\"fia\",\"fie\",\"fil\",\"fip\",\"fir\",\"fit\",\"fiu\",\"fiw\",\"fkk\",\"fkv\",\"fla\",\"flh\",\"fli\",\"fll\",\"fln\",\"flr\",\"fly\",\"fmp\",\"fmu\",\"fnb\",\"fng\",\"fni\",\"fod\",\"foi\",\"fom\",\"fon\",\"for\",\"fos\",\"fox\",\"fpe\",\"fqs\",\"frc\",\"frd\",\"frk\",\"frm\",\"fro\",\"frp\",\"frq\",\"frr\",\"frs\",\"frt\",\"fse\",\"fsl\",\"fss\",\"fub\",\"fuc\",\"fud\",\"fue\",\"fuf\",\"fuh\",\"fui\",\"fuj\",\"fum\",\"fun\",\"fuq\",\"fur\",\"fut\",\"fuu\",\"fuv\",\"fuy\",\"fvr\",\"fwa\",\"fwe\",\"gaa\",\"gab\",\"gac\",\"gad\",\"gae\",\"gaf\",\"gag\",\"gah\",\"gai\",\"gaj\",\"gak\",\"gal\",\"gam\",\"gan\",\"gao\",\"gap\",\"gaq\",\"gar\",\"gas\",\"gat\",\"gau\",\"gav\",\"gaw\",\"gax\",\"gay\",\"gaz\",\"gba\",\"gbb\",\"gbc\",\"gbd\",\"gbe\",\"gbf\",\"gbg\",\"gbh\",\"gbi\",\"gbj\",\"gbk\",\"gbl\",\"gbm\",\"gbn\",\"gbo\",\"gbp\",\"gbq\",\"gbr\",\"gbs\",\"gbu\",\"gbv\",\"gbw\",\"gbx\",\"gby\",\"gbz\",\"gcc\",\"gcd\",\"gce\",\"gcf\",\"gcl\",\"gcn\",\"gcr\",\"gct\",\"gda\",\"gdb\",\"gdc\",\"gdd\",\"gde\",\"gdf\",\"gdg\",\"gdh\",\"gdi\",\"gdj\",\"gdk\",\"gdl\",\"gdm\",\"gdn\",\"gdo\",\"gdq\",\"gdr\",\"gds\",\"gdt\",\"gdu\",\"gdx\",\"gea\",\"geb\",\"gec\",\"ged\",\"geg\",\"geh\",\"gei\",\"gej\",\"gek\",\"gel\",\"gem\",\"geq\",\"ges\",\"gev\",\"gew\",\"gex\",\"gey\",\"gez\",\"gfk\",\"gft\",\"gfx\",\"gga\",\"ggb\",\"ggd\",\"gge\",\"ggg\",\"ggk\",\"ggl\",\"ggn\",\"ggo\",\"ggr\",\"ggt\",\"ggu\",\"ggw\",\"gha\",\"ghc\",\"ghe\",\"ghh\",\"ghk\",\"ghl\",\"ghn\",\"gho\",\"ghr\",\"ghs\",\"ght\",\"gia\",\"gib\",\"gic\",\"gid\",\"gig\",\"gih\",\"gil\",\"gim\",\"gin\",\"gio\",\"gip\",\"giq\",\"gir\",\"gis\",\"git\",\"giu\",\"giw\",\"gix\",\"giy\",\"giz\",\"gji\",\"gjk\",\"gjm\",\"gjn\",\"gjr\",\"gju\",\"gka\",\"gke\",\"gkn\",\"gko\",\"gkp\",\"gku\",\"glc\",\"gld\",\"glh\",\"gli\",\"glj\",\"glk\",\"gll\",\"glo\",\"glr\",\"glu\",\"glw\",\"gly\",\"gma\",\"gmb\",\"gmd\",\"gme\",\"gmg\",\"gmh\",\"gml\",\"gmm\",\"gmn\",\"gmq\",\"gmu\",\"gmv\",\"gmw\",\"gmx\",\"gmy\",\"gmz\",\"gna\",\"gnb\",\"gnc\",\"gnd\",\"gne\",\"gng\",\"gnh\",\"gni\",\"gnk\",\"gnl\",\"gnm\",\"gnn\",\"gno\",\"gnq\",\"gnr\",\"gnt\",\"gnu\",\"gnw\",\"gnz\",\"goa\",\"gob\",\"goc\",\"god\",\"goe\",\"gof\",\"gog\",\"goh\",\"goi\",\"goj\",\"gok\",\"gol\",\"gom\",\"gon\",\"goo\",\"gop\",\"goq\",\"gor\",\"gos\",\"got\",\"gou\",\"gow\",\"gox\",\"goy\",\"goz\",\"gpa\",\"gpe\",\"gpn\",\"gqa\",\"gqi\",\"gqn\",\"gqr\",\"gqu\",\"gra\",\"grb\",\"grc\",\"grd\",\"grg\",\"grh\",\"gri\",\"grj\",\"grk\",\"grm\",\"gro\",\"grq\",\"grr\",\"grs\",\"grt\",\"gru\",\"grv\",\"grw\",\"grx\",\"gry\",\"grz\",\"gse\",\"gsg\",\"gsl\",\"gsm\",\"gsn\",\"gso\",\"gsp\",\"gss\",\"gsw\",\"gta\",\"gti\",\"gtu\",\"gua\",\"gub\",\"guc\",\"gud\",\"gue\",\"guf\",\"gug\",\"guh\",\"gui\",\"guk\",\"gul\",\"gum\",\"gun\",\"guo\",\"gup\",\"guq\",\"gur\",\"gus\",\"gut\",\"guu\",\"guv\",\"guw\",\"gux\",\"guz\",\"gva\",\"gvc\",\"gve\",\"gvf\",\"gvj\",\"gvl\",\"gvm\",\"gvn\",\"gvo\",\"gvp\",\"gvr\",\"gvs\",\"gvy\",\"gwa\",\"gwb\",\"gwc\",\"gwd\",\"gwe\",\"gwf\",\"gwg\",\"gwi\",\"gwj\",\"gwm\",\"gwn\",\"gwr\",\"gwt\",\"gwu\",\"gww\",\"gwx\",\"gxx\",\"gya\",\"gyb\",\"gyd\",\"gye\",\"gyf\",\"gyg\",\"gyi\",\"gyl\",\"gym\",\"gyn\",\"gyr\",\"gyy\",\"gza\",\"gzi\",\"gzn\",\"haa\",\"hab\",\"hac\",\"had\",\"hae\",\"haf\",\"hag\",\"hah\",\"hai\",\"haj\",\"hak\",\"hal\",\"ham\",\"han\",\"hao\",\"hap\",\"haq\",\"har\",\"has\",\"hav\",\"haw\",\"hax\",\"hay\",\"haz\",\"hba\",\"hbb\",\"hbn\",\"hbo\",\"hbu\",\"hca\",\"hch\",\"hdn\",\"hds\",\"hdy\",\"hea\",\"hed\",\"heg\",\"heh\",\"hei\",\"hem\",\"hgm\",\"hgw\",\"hhi\",\"hhr\",\"hhy\",\"hia\",\"hib\",\"hid\",\"hif\",\"hig\",\"hih\",\"hii\",\"hij\",\"hik\",\"hil\",\"him\",\"hio\",\"hir\",\"hit\",\"hiw\",\"hix\",\"hji\",\"hka\",\"hke\",\"hkk\",\"hks\",\"hla\",\"hlb\",\"hld\",\"hle\",\"hlt\",\"hlu\",\"hma\",\"hmb\",\"hmc\",\"hmd\",\"hme\",\"hmf\",\"hmg\",\"hmh\",\"hmi\",\"hmj\",\"hmk\",\"hml\",\"hmm\",\"hmn\",\"hmp\",\"hmq\",\"hmr\",\"hms\",\"hmt\",\"hmu\",\"hmv\",\"hmw\",\"hmx\",\"hmy\",\"hmz\",\"hna\",\"hnd\",\"hne\",\"hnh\",\"hni\",\"hnj\",\"hnn\",\"hno\",\"hns\",\"hnu\",\"hoa\",\"hob\",\"hoc\",\"hod\",\"hoe\",\"hoh\",\"hoi\",\"hoj\",\"hok\",\"hol\",\"hom\",\"hoo\",\"hop\",\"hor\",\"hos\",\"hot\",\"hov\",\"how\",\"hoy\",\"hoz\",\"hpo\",\"hps\",\"hra\",\"hrc\",\"hre\",\"hrk\",\"hrm\",\"hro\",\"hrp\",\"hrr\",\"hrt\",\"hru\",\"hrw\",\"hrx\",\"hrz\",\"hsb\",\"hsh\",\"hsl\",\"hsn\",\"hss\",\"hti\",\"hto\",\"hts\",\"htu\",\"htx\",\"hub\",\"huc\",\"hud\",\"hue\",\"huf\",\"hug\",\"huh\",\"hui\",\"huj\",\"huk\",\"hul\",\"hum\",\"huo\",\"hup\",\"huq\",\"hur\",\"hus\",\"hut\",\"huu\",\"huv\",\"huw\",\"hux\",\"huy\",\"huz\",\"hvc\",\"hve\",\"hvk\",\"hvn\",\"hvv\",\"hwa\",\"hwc\",\"hwo\",\"hya\",\"hyx\",\"iai\",\"ian\",\"iap\",\"iar\",\"iba\",\"ibb\",\"ibd\",\"ibe\",\"ibg\",\"ibi\",\"ibl\",\"ibm\",\"ibn\",\"ibr\",\"ibu\",\"iby\",\"ica\",\"ich\",\"icl\",\"icr\",\"ida\",\"idb\",\"idc\",\"idd\",\"ide\",\"idi\",\"idr\",\"ids\",\"idt\",\"idu\",\"ifa\",\"ifb\",\"ife\",\"iff\",\"ifk\",\"ifm\",\"ifu\",\"ify\",\"igb\",\"ige\",\"igg\",\"igl\",\"igm\",\"ign\",\"igo\",\"igs\",\"igw\",\"ihb\",\"ihi\",\"ihp\",\"ihw\",\"iin\",\"iir\",\"ijc\",\"ije\",\"ijj\",\"ijn\",\"ijo\",\"ijs\",\"ike\",\"iki\",\"ikk\",\"ikl\",\"iko\",\"ikp\",\"ikr\",\"iks\",\"ikt\",\"ikv\",\"ikw\",\"ikx\",\"ikz\",\"ila\",\"ilb\",\"ilg\",\"ili\",\"ilk\",\"ill\",\"ilm\",\"ilo\",\"ilp\",\"ils\",\"ilu\",\"ilv\",\"ilw\",\"ima\",\"ime\",\"imi\",\"iml\",\"imn\",\"imo\",\"imr\",\"ims\",\"imy\",\"inb\",\"inc\",\"ine\",\"ing\",\"inh\",\"inj\",\"inl\",\"inm\",\"inn\",\"ino\",\"inp\",\"ins\",\"int\",\"inz\",\"ior\",\"iou\",\"iow\",\"ipi\",\"ipo\",\"iqu\",\"iqw\",\"ira\",\"ire\",\"irh\",\"iri\",\"irk\",\"irn\",\"iro\",\"irr\",\"iru\",\"irx\",\"iry\",\"isa\",\"isc\",\"isd\",\"ise\",\"isg\",\"ish\",\"isi\",\"isk\",\"ism\",\"isn\",\"iso\",\"isr\",\"ist\",\"isu\",\"itb\",\"itc\",\"itd\",\"ite\",\"iti\",\"itk\",\"itl\",\"itm\",\"ito\",\"itr\",\"its\",\"itt\",\"itv\",\"itw\",\"itx\",\"ity\",\"itz\",\"ium\",\"ivb\",\"ivv\",\"iwk\",\"iwm\",\"iwo\",\"iws\",\"ixc\",\"ixl\",\"iya\",\"iyo\",\"iyx\",\"izh\",\"izi\",\"izr\",\"izz\",\"jaa\",\"jab\",\"jac\",\"jad\",\"jae\",\"jaf\",\"jah\",\"jaj\",\"jak\",\"jal\",\"jam\",\"jan\",\"jao\",\"jaq\",\"jar\",\"jas\",\"jat\",\"jau\",\"jax\",\"jay\",\"jaz\",\"jbe\",\"jbi\",\"jbj\",\"jbk\",\"jbn\",\"jbo\",\"jbr\",\"jbt\",\"jbu\",\"jbw\",\"jcs\",\"jct\",\"jda\",\"jdg\",\"jdt\",\"jeb\",\"jee\",\"jeg\",\"jeh\",\"jei\",\"jek\",\"jel\",\"jen\",\"jer\",\"jet\",\"jeu\",\"jgb\",\"jge\",\"jgk\",\"jgo\",\"jhi\",\"jhs\",\"jia\",\"jib\",\"jic\",\"jid\",\"jie\",\"jig\",\"jih\",\"jii\",\"jil\",\"jim\",\"jio\",\"jiq\",\"jit\",\"jiu\",\"jiv\",\"jiy\",\"jje\",\"jjr\",\"jka\",\"jkm\",\"jko\",\"jkp\",\"jkr\",\"jku\",\"jle\",\"jls\",\"jma\",\"jmb\",\"jmc\",\"jmd\",\"jmi\",\"jml\",\"jmn\",\"jmr\",\"jms\",\"jmw\",\"jmx\",\"jna\",\"jnd\",\"jng\",\"jni\",\"jnj\",\"jnl\",\"jns\",\"job\",\"jod\",\"jog\",\"jor\",\"jos\",\"jow\",\"jpa\",\"jpr\",\"jpx\",\"jqr\",\"jra\",\"jrb\",\"jrr\",\"jrt\",\"jru\",\"jsl\",\"jua\",\"jub\",\"juc\",\"jud\",\"juh\",\"jui\",\"juk\",\"jul\",\"jum\",\"jun\",\"juo\",\"jup\",\"jur\",\"jus\",\"jut\",\"juu\",\"juw\",\"juy\",\"jvd\",\"jvn\",\"jwi\",\"jya\",\"jye\",\"jyy\",\"kaa\",\"kab\",\"kac\",\"kad\",\"kae\",\"kaf\",\"kag\",\"kah\",\"kai\",\"kaj\",\"kak\",\"kam\",\"kao\",\"kap\",\"kaq\",\"kar\",\"kav\",\"kaw\",\"kax\",\"kay\",\"kba\",\"kbb\",\"kbc\",\"kbd\",\"kbe\",\"kbf\",\"kbg\",\"kbh\",\"kbi\",\"kbj\",\"kbk\",\"kbl\",\"kbm\",\"kbn\",\"kbo\",\"kbp\",\"kbq\",\"kbr\",\"kbs\",\"kbt\",\"kbu\",\"kbv\",\"kbw\",\"kbx\",\"kby\",\"kbz\",\"kca\",\"kcb\",\"kcc\",\"kcd\",\"kce\",\"kcf\",\"kcg\",\"kch\",\"kci\",\"kcj\",\"kck\",\"kcl\",\"kcm\",\"kcn\",\"kco\",\"kcp\",\"kcq\",\"kcr\",\"kcs\",\"kct\",\"kcu\",\"kcv\",\"kcw\",\"kcx\",\"kcy\",\"kcz\",\"kda\",\"kdc\",\"kdd\",\"kde\",\"kdf\",\"kdg\",\"kdh\",\"kdi\",\"kdj\",\"kdk\",\"kdl\",\"kdm\",\"kdn\",\"kdo\",\"kdp\",\"kdq\",\"kdr\",\"kdt\",\"kdu\",\"kdv\",\"kdw\",\"kdx\",\"kdy\",\"kdz\",\"kea\",\"keb\",\"kec\",\"ked\",\"kee\",\"kef\",\"keg\",\"keh\",\"kei\",\"kej\",\"kek\",\"kel\",\"kem\",\"ken\",\"keo\",\"kep\",\"keq\",\"ker\",\"kes\",\"ket\",\"keu\",\"kev\",\"kew\",\"kex\",\"key\",\"kez\",\"kfa\",\"kfb\",\"kfc\",\"kfd\",\"kfe\",\"kff\",\"kfg\",\"kfh\",\"kfi\",\"kfj\",\"kfk\",\"kfl\",\"kfm\",\"kfn\",\"kfo\",\"kfp\",\"kfq\",\"kfr\",\"kfs\",\"kft\",\"kfu\",\"kfv\",\"kfw\",\"kfx\",\"kfy\",\"kfz\",\"kga\",\"kgb\",\"kgc\",\"kgd\",\"kge\",\"kgf\",\"kgg\",\"kgh\",\"kgi\",\"kgj\",\"kgk\",\"kgl\",\"kgm\",\"kgn\",\"kgo\",\"kgp\",\"kgq\",\"kgr\",\"kgs\",\"kgt\",\"kgu\",\"kgv\",\"kgw\",\"kgx\",\"kgy\",\"kha\",\"khb\",\"khc\",\"khd\",\"khe\",\"khf\",\"khg\",\"khh\",\"khi\",\"khj\",\"khk\",\"khl\",\"khn\",\"kho\",\"khp\",\"khq\",\"khr\",\"khs\",\"kht\",\"khu\",\"khv\",\"khw\",\"khx\",\"khy\",\"khz\",\"kia\",\"kib\",\"kic\",\"kid\",\"kie\",\"kif\",\"kig\",\"kih\",\"kii\",\"kij\",\"kil\",\"kim\",\"kio\",\"kip\",\"kiq\",\"kis\",\"kit\",\"kiu\",\"kiv\",\"kiw\",\"kix\",\"kiy\",\"kiz\",\"kja\",\"kjb\",\"kjc\",\"kjd\",\"kje\",\"kjf\",\"kjg\",\"kjh\",\"kji\",\"kjj\",\"kjk\",\"kjl\",\"kjm\",\"kjn\",\"kjo\",\"kjp\",\"kjq\",\"kjr\",\"kjs\",\"kjt\",\"kju\",\"kjv\",\"kjx\",\"kjy\",\"kjz\",\"kka\",\"kkb\",\"kkc\",\"kkd\",\"kke\",\"kkf\",\"kkg\",\"kkh\",\"kki\",\"kkj\",\"kkk\",\"kkl\",\"kkm\",\"kkn\",\"kko\",\"kkp\",\"kkq\",\"kkr\",\"kks\",\"kkt\",\"kku\",\"kkv\",\"kkw\",\"kkx\",\"kky\",\"kkz\",\"kla\",\"klb\",\"klc\",\"kld\",\"kle\",\"klf\",\"klg\",\"klh\",\"kli\",\"klj\",\"klk\",\"kll\",\"klm\",\"kln\",\"klo\",\"klp\",\"klq\",\"klr\",\"kls\",\"klt\",\"klu\",\"klv\",\"klw\",\"klx\",\"kly\",\"klz\",\"kma\",\"kmb\",\"kmc\",\"kmd\",\"kme\",\"kmf\",\"kmg\",\"kmh\",\"kmi\",\"kmj\",\"kmk\",\"kml\",\"kmm\",\"kmn\",\"kmo\",\"kmp\",\"kmq\",\"kmr\",\"kms\",\"kmt\",\"kmu\",\"kmv\",\"kmw\",\"kmx\",\"kmy\",\"kmz\",\"kna\",\"knb\",\"knc\",\"knd\",\"kne\",\"knf\",\"kng\",\"kni\",\"knj\",\"knk\",\"knl\",\"knm\",\"knn\",\"kno\",\"knp\",\"knq\",\"knr\",\"kns\",\"knt\",\"knu\",\"knv\",\"knw\",\"knx\",\"kny\",\"knz\",\"koa\",\"koc\",\"kod\",\"koe\",\"kof\",\"kog\",\"koh\",\"koi\",\"koj\",\"kok\",\"kol\",\"koo\",\"kop\",\"koq\",\"kos\",\"kot\",\"kou\",\"kov\",\"kow\",\"kox\",\"koy\",\"koz\",\"kpa\",\"kpb\",\"kpc\",\"kpd\",\"kpe\",\"kpf\",\"kpg\",\"kph\",\"kpi\",\"kpj\",\"kpk\",\"kpl\",\"kpm\",\"kpn\",\"kpo\",\"kpp\",\"kpq\",\"kpr\",\"kps\",\"kpt\",\"kpu\",\"kpv\",\"kpw\",\"kpx\",\"kpy\",\"kpz\",\"kqa\",\"kqb\",\"kqc\",\"kqd\",\"kqe\",\"kqf\",\"kqg\",\"kqh\",\"kqi\",\"kqj\",\"kqk\",\"kql\",\"kqm\",\"kqn\",\"kqo\",\"kqp\",\"kqq\",\"kqr\",\"kqs\",\"kqt\",\"kqu\",\"kqv\",\"kqw\",\"kqx\",\"kqy\",\"kqz\",\"kra\",\"krb\",\"krc\",\"krd\",\"kre\",\"krf\",\"krh\",\"kri\",\"krj\",\"krk\",\"krl\",\"krm\",\"krn\",\"kro\",\"krp\",\"krr\",\"krs\",\"krt\",\"kru\",\"krv\",\"krw\",\"krx\",\"kry\",\"krz\",\"ksa\",\"ksb\",\"ksc\",\"ksd\",\"kse\",\"ksf\",\"ksg\",\"ksh\",\"ksi\",\"ksj\",\"ksk\",\"ksl\",\"ksm\",\"ksn\",\"kso\",\"ksp\",\"ksq\",\"ksr\",\"kss\",\"kst\",\"ksu\",\"ksv\",\"ksw\",\"ksx\",\"ksy\",\"ksz\",\"kta\",\"ktb\",\"ktc\",\"ktd\",\"kte\",\"ktf\",\"ktg\",\"kth\",\"kti\",\"ktj\",\"ktk\",\"ktl\",\"ktm\",\"ktn\",\"kto\",\"ktp\",\"ktq\",\"ktr\",\"kts\",\"ktt\",\"ktu\",\"ktv\",\"ktw\",\"ktx\",\"kty\",\"ktz\",\"kub\",\"kuc\",\"kud\",\"kue\",\"kuf\",\"kug\",\"kuh\",\"kui\",\"kuj\",\"kuk\",\"kul\",\"kum\",\"kun\",\"kuo\",\"kup\",\"kuq\",\"kus\",\"kut\",\"kuu\",\"kuv\",\"kuw\",\"kux\",\"kuy\",\"kuz\",\"kva\",\"kvb\",\"kvc\",\"kvd\",\"kve\",\"kvf\",\"kvg\",\"kvh\",\"kvi\",\"kvj\",\"kvk\",\"kvl\",\"kvm\",\"kvn\",\"kvo\",\"kvp\",\"kvq\",\"kvr\",\"kvs\",\"kvt\",\"kvu\",\"kvv\",\"kvw\",\"kvx\",\"kvy\",\"kvz\",\"kwa\",\"kwb\",\"kwc\",\"kwd\",\"kwe\",\"kwf\",\"kwg\",\"kwh\",\"kwi\",\"kwj\",\"kwk\",\"kwl\",\"kwm\",\"kwn\",\"kwo\",\"kwp\",\"kwq\",\"kwr\",\"kws\",\"kwt\",\"kwu\",\"kwv\",\"kww\",\"kwx\",\"kwy\",\"kwz\",\"kxa\",\"kxb\",\"kxc\",\"kxd\",\"kxe\",\"kxf\",\"kxh\",\"kxi\",\"kxj\",\"kxk\",\"kxl\",\"kxm\",\"kxn\",\"kxo\",\"kxp\",\"kxq\",\"kxr\",\"kxs\",\"kxt\",\"kxu\",\"kxv\",\"kxw\",\"kxx\",\"kxy\",\"kxz\",\"kya\",\"kyb\",\"kyc\",\"kyd\",\"kye\",\"kyf\",\"kyg\",\"kyh\",\"kyi\",\"kyj\",\"kyk\",\"kyl\",\"kym\",\"kyn\",\"kyo\",\"kyp\",\"kyq\",\"kyr\",\"kys\",\"kyt\",\"kyu\",\"kyv\",\"kyw\",\"kyx\",\"kyy\",\"kyz\",\"kza\",\"kzb\",\"kzc\",\"kzd\",\"kze\",\"kzf\",\"kzg\",\"kzh\",\"kzi\",\"kzj\",\"kzk\",\"kzl\",\"kzm\",\"kzn\",\"kzo\",\"kzp\",\"kzq\",\"kzr\",\"kzs\",\"kzt\",\"kzu\",\"kzv\",\"kzw\",\"kzx\",\"kzy\",\"kzz\",\"laa\",\"lab\",\"lac\",\"lad\",\"lae\",\"laf\",\"lag\",\"lah\",\"lai\",\"laj\",\"lak\",\"lal\",\"lam\",\"lan\",\"lap\",\"laq\",\"lar\",\"las\",\"lau\",\"law\",\"lax\",\"lay\",\"laz\",\"lba\",\"lbb\",\"lbc\",\"lbe\",\"lbf\",\"lbg\",\"lbi\",\"lbj\",\"lbk\",\"lbl\",\"lbm\",\"lbn\",\"lbo\",\"lbq\",\"lbr\",\"lbs\",\"lbt\",\"lbu\",\"lbv\",\"lbw\",\"lbx\",\"lby\",\"lbz\",\"lcc\",\"lcd\",\"lce\",\"lcf\",\"lch\",\"lcl\",\"lcm\",\"lcp\",\"lcq\",\"lcs\",\"lda\",\"ldb\",\"ldd\",\"ldg\",\"ldh\",\"ldi\",\"ldj\",\"ldk\",\"ldl\",\"ldm\",\"ldn\",\"ldo\",\"ldp\",\"ldq\",\"lea\",\"leb\",\"lec\",\"led\",\"lee\",\"lef\",\"leg\",\"leh\",\"lei\",\"lej\",\"lek\",\"lel\",\"lem\",\"len\",\"leo\",\"lep\",\"leq\",\"ler\",\"les\",\"let\",\"leu\",\"lev\",\"lew\",\"lex\",\"ley\",\"lez\",\"lfa\",\"lfn\",\"lga\",\"lgb\",\"lgg\",\"lgh\",\"lgi\",\"lgk\",\"lgl\",\"lgm\",\"lgn\",\"lgq\",\"lgr\",\"lgt\",\"lgu\",\"lgz\",\"lha\",\"lhh\",\"lhi\",\"lhl\",\"lhm\",\"lhn\",\"lhp\",\"lhs\",\"lht\",\"lhu\",\"lia\",\"lib\",\"lic\",\"lid\",\"lie\",\"lif\",\"lig\",\"lih\",\"lii\",\"lij\",\"lik\",\"lil\",\"lio\",\"lip\",\"liq\",\"lir\",\"lis\",\"liu\",\"liv\",\"liw\",\"lix\",\"liy\",\"liz\",\"lja\",\"lje\",\"lji\",\"ljl\",\"ljp\",\"ljw\",\"ljx\",\"lka\",\"lkb\",\"lkc\",\"lkd\",\"lke\",\"lkh\",\"lki\",\"lkj\",\"lkl\",\"lkm\",\"lkn\",\"lko\",\"lkr\",\"lks\",\"lkt\",\"lku\",\"lky\",\"lla\",\"llb\",\"llc\",\"lld\",\"lle\",\"llf\",\"llg\",\"llh\",\"lli\",\"llj\",\"llk\",\"lll\",\"llm\",\"lln\",\"llo\",\"llp\",\"llq\",\"lls\",\"llu\",\"llx\",\"lma\",\"lmb\",\"lmc\",\"lmd\",\"lme\",\"lmf\",\"lmg\",\"lmh\",\"lmi\",\"lmj\",\"lmk\",\"lml\",\"lmm\",\"lmn\",\"lmo\",\"lmp\",\"lmq\",\"lmr\",\"lmu\",\"lmv\",\"lmw\",\"lmx\",\"lmy\",\"lmz\",\"lna\",\"lnb\",\"lnd\",\"lng\",\"lnh\",\"lni\",\"lnj\",\"lnl\",\"lnm\",\"lnn\",\"lno\",\"lns\",\"lnu\",\"lnw\",\"lnz\",\"loa\",\"lob\",\"loc\",\"loe\",\"lof\",\"log\",\"loh\",\"loi\",\"loj\",\"lok\",\"lol\",\"lom\",\"lon\",\"loo\",\"lop\",\"loq\",\"lor\",\"los\",\"lot\",\"lou\",\"lov\",\"low\",\"lox\",\"loy\",\"loz\",\"lpa\",\"lpe\",\"lpn\",\"lpo\",\"lpx\",\"lra\",\"lrc\",\"lre\",\"lrg\",\"lri\",\"lrk\",\"lrl\",\"lrm\",\"lrn\",\"lro\",\"lrr\",\"lrt\",\"lrv\",\"lrz\",\"lsa\",\"lsd\",\"lse\",\"lsg\",\"lsh\",\"lsi\",\"lsl\",\"lsm\",\"lso\",\"lsp\",\"lsr\",\"lss\",\"lst\",\"lsy\",\"ltc\",\"ltg\",\"lti\",\"ltn\",\"lto\",\"lts\",\"ltu\",\"lua\",\"luc\",\"lud\",\"lue\",\"luf\",\"lui\",\"luj\",\"luk\",\"lul\",\"lum\",\"lun\",\"luo\",\"lup\",\"luq\",\"lur\",\"lus\",\"lut\",\"luu\",\"luv\",\"luw\",\"luy\",\"luz\",\"lva\",\"lvk\",\"lvs\",\"lvu\",\"lwa\",\"lwe\",\"lwg\",\"lwh\",\"lwl\",\"lwm\",\"lwo\",\"lwt\",\"lwu\",\"lww\",\"lya\",\"lyg\",\"lyn\",\"lzh\",\"lzl\",\"lzn\",\"lzz\",\"maa\",\"mab\",\"mad\",\"mae\",\"maf\",\"mag\",\"mai\",\"maj\",\"mak\",\"mam\",\"man\",\"map\",\"maq\",\"mas\",\"mat\",\"mau\",\"mav\",\"maw\",\"max\",\"maz\",\"mba\",\"mbb\",\"mbc\",\"mbd\",\"mbe\",\"mbf\",\"mbh\",\"mbi\",\"mbj\",\"mbk\",\"mbl\",\"mbm\",\"mbn\",\"mbo\",\"mbp\",\"mbq\",\"mbr\",\"mbs\",\"mbt\",\"mbu\",\"mbv\",\"mbw\",\"mbx\",\"mby\",\"mbz\",\"mca\",\"mcb\",\"mcc\",\"mcd\",\"mce\",\"mcf\",\"mcg\",\"mch\",\"mci\",\"mcj\",\"mck\",\"mcl\",\"mcm\",\"mcn\",\"mco\",\"mcp\",\"mcq\",\"mcr\",\"mcs\",\"mct\",\"mcu\",\"mcv\",\"mcw\",\"mcx\",\"mcy\",\"mcz\",\"mda\",\"mdb\",\"mdc\",\"mdd\",\"mde\",\"mdf\",\"mdg\",\"mdh\",\"mdi\",\"mdj\",\"mdk\",\"mdl\",\"mdm\",\"mdn\",\"mdp\",\"mdq\",\"mdr\",\"mds\",\"mdt\",\"mdu\",\"mdv\",\"mdw\",\"mdx\",\"mdy\",\"mdz\",\"mea\",\"meb\",\"mec\",\"med\",\"mee\",\"mef\",\"meg\",\"meh\",\"mei\",\"mej\",\"mek\",\"mel\",\"mem\",\"men\",\"meo\",\"mep\",\"meq\",\"mer\",\"mes\",\"met\",\"meu\",\"mev\",\"mew\",\"mey\",\"mez\",\"mfa\",\"mfb\",\"mfc\",\"mfd\",\"mfe\",\"mff\",\"mfg\",\"mfh\",\"mfi\",\"mfj\",\"mfk\",\"mfl\",\"mfm\",\"mfn\",\"mfo\",\"mfp\",\"mfq\",\"mfr\",\"mfs\",\"mft\",\"mfu\",\"mfv\",\"mfw\",\"mfx\",\"mfy\",\"mfz\",\"mga\",\"mgb\",\"mgc\",\"mgd\",\"mge\",\"mgf\",\"mgg\",\"mgh\",\"mgi\",\"mgj\",\"mgk\",\"mgl\",\"mgm\",\"mgn\",\"mgo\",\"mgp\",\"mgq\",\"mgr\",\"mgs\",\"mgt\",\"mgu\",\"mgv\",\"mgw\",\"mgx\",\"mgy\",\"mgz\",\"mha\",\"mhb\",\"mhc\",\"mhd\",\"mhe\",\"mhf\",\"mhg\",\"mhh\",\"mhi\",\"mhj\",\"mhk\",\"mhl\",\"mhm\",\"mhn\",\"mho\",\"mhp\",\"mhq\",\"mhr\",\"mhs\",\"mht\",\"mhu\",\"mhw\",\"mhx\",\"mhy\",\"mhz\",\"mia\",\"mib\",\"mic\",\"mid\",\"mie\",\"mif\",\"mig\",\"mih\",\"mii\",\"mij\",\"mik\",\"mil\",\"mim\",\"min\",\"mio\",\"mip\",\"miq\",\"mir\",\"mis\",\"mit\",\"miu\",\"miw\",\"mix\",\"miy\",\"miz\",\"mja\",\"mjb\",\"mjc\",\"mjd\",\"mje\",\"mjg\",\"mjh\",\"mji\",\"mjj\",\"mjk\",\"mjl\",\"mjm\",\"mjn\",\"mjo\",\"mjp\",\"mjq\",\"mjr\",\"mjs\",\"mjt\",\"mju\",\"mjv\",\"mjw\",\"mjx\",\"mjy\",\"mjz\",\"mka\",\"mkb\",\"mkc\",\"mke\",\"mkf\",\"mkg\",\"mkh\",\"mki\",\"mkj\",\"mkk\",\"mkl\",\"mkm\",\"mkn\",\"mko\",\"mkp\",\"mkq\",\"mkr\",\"mks\",\"mkt\",\"mku\",\"mkv\",\"mkw\",\"mkx\",\"mky\",\"mkz\",\"mla\",\"mlb\",\"mlc\",\"mld\",\"mle\",\"mlf\",\"mlh\",\"mli\",\"mlj\",\"mlk\",\"mll\",\"mlm\",\"mln\",\"mlo\",\"mlp\",\"mlq\",\"mlr\",\"mls\",\"mlu\",\"mlv\",\"mlw\",\"mlx\",\"mlz\",\"mma\",\"mmb\",\"mmc\",\"mmd\",\"mme\",\"mmf\",\"mmg\",\"mmh\",\"mmi\",\"mmj\",\"mmk\",\"mml\",\"mmm\",\"mmn\",\"mmo\",\"mmp\",\"mmq\",\"mmr\",\"mmt\",\"mmu\",\"mmv\",\"mmw\",\"mmx\",\"mmy\",\"mmz\",\"mna\",\"mnb\",\"mnc\",\"mnd\",\"mne\",\"mnf\",\"mng\",\"mnh\",\"mni\",\"mnj\",\"mnk\",\"mnl\",\"mnm\",\"mnn\",\"mno\",\"mnp\",\"mnq\",\"mnr\",\"mns\",\"mnt\",\"mnu\",\"mnv\",\"mnw\",\"mnx\",\"mny\",\"mnz\",\"moa\",\"moc\",\"mod\",\"moe\",\"mof\",\"mog\",\"moh\",\"moi\",\"moj\",\"mok\",\"mom\",\"moo\",\"mop\",\"moq\",\"mor\",\"mos\",\"mot\",\"mou\",\"mov\",\"mow\",\"mox\",\"moy\",\"moz\",\"mpa\",\"mpb\",\"mpc\",\"mpd\",\"mpe\",\"mpg\",\"mph\",\"mpi\",\"mpj\",\"mpk\",\"mpl\",\"mpm\",\"mpn\",\"mpo\",\"mpp\",\"mpq\",\"mpr\",\"mps\",\"mpt\",\"mpu\",\"mpv\",\"mpw\",\"mpx\",\"mpy\",\"mpz\",\"mqa\",\"mqb\",\"mqc\",\"mqe\",\"mqf\",\"mqg\",\"mqh\",\"mqi\",\"mqj\",\"mqk\",\"mql\",\"mqm\",\"mqn\",\"mqo\",\"mqp\",\"mqq\",\"mqr\",\"mqs\",\"mqt\",\"mqu\",\"mqv\",\"mqw\",\"mqx\",\"mqy\",\"mqz\",\"mra\",\"mrb\",\"mrc\",\"mrd\",\"mre\",\"mrf\",\"mrg\",\"mrh\",\"mrj\",\"mrk\",\"mrl\",\"mrm\",\"mrn\",\"mro\",\"mrp\",\"mrq\",\"mrr\",\"mrs\",\"mrt\",\"mru\",\"mrv\",\"mrw\",\"mrx\",\"mry\",\"mrz\",\"msb\",\"msc\",\"msd\",\"mse\",\"msf\",\"msg\",\"msh\",\"msi\",\"msj\",\"msk\",\"msl\",\"msm\",\"msn\",\"mso\",\"msp\",\"msq\",\"msr\",\"mss\",\"mst\",\"msu\",\"msv\",\"msw\",\"msx\",\"msy\",\"msz\",\"mta\",\"mtb\",\"mtc\",\"mtd\",\"mte\",\"mtf\",\"mtg\",\"mth\",\"mti\",\"mtj\",\"mtk\",\"mtl\",\"mtm\",\"mtn\",\"mto\",\"mtp\",\"mtq\",\"mtr\",\"mts\",\"mtt\",\"mtu\",\"mtv\",\"mtw\",\"mtx\",\"mty\",\"mua\",\"mub\",\"muc\",\"mud\",\"mue\",\"mug\",\"muh\",\"mui\",\"muj\",\"muk\",\"mul\",\"mum\",\"mun\",\"muo\",\"mup\",\"muq\",\"mur\",\"mus\",\"mut\",\"muu\",\"muv\",\"mux\",\"muy\",\"muz\",\"mva\",\"mvb\",\"mvd\",\"mve\",\"mvf\",\"mvg\",\"mvh\",\"mvi\",\"mvk\",\"mvl\",\"mvm\",\"mvn\",\"mvo\",\"mvp\",\"mvq\",\"mvr\",\"mvs\",\"mvt\",\"mvu\",\"mvv\",\"mvw\",\"mvx\",\"mvy\",\"mvz\",\"mwa\",\"mwb\",\"mwc\",\"mwd\",\"mwe\",\"mwf\",\"mwg\",\"mwh\",\"mwi\",\"mwj\",\"mwk\",\"mwl\",\"mwm\",\"mwn\",\"mwo\",\"mwp\",\"mwq\",\"mwr\",\"mws\",\"mwt\",\"mwu\",\"mwv\",\"mww\",\"mwx\",\"mwy\",\"mwz\",\"mxa\",\"mxb\",\"mxc\",\"mxd\",\"mxe\",\"mxf\",\"mxg\",\"mxh\",\"mxi\",\"mxj\",\"mxk\",\"mxl\",\"mxm\",\"mxn\",\"mxo\",\"mxp\",\"mxq\",\"mxr\",\"mxs\",\"mxt\",\"mxu\",\"mxv\",\"mxw\",\"mxx\",\"mxy\",\"mxz\",\"myb\",\"myc\",\"myd\",\"mye\",\"myf\",\"myg\",\"myh\",\"myi\",\"myj\",\"myk\",\"myl\",\"mym\",\"myn\",\"myo\",\"myp\",\"myq\",\"myr\",\"mys\",\"myt\",\"myu\",\"myv\",\"myw\",\"myx\",\"myy\",\"myz\",\"mza\",\"mzb\",\"mzc\",\"mzd\",\"mze\",\"mzg\",\"mzh\",\"mzi\",\"mzj\",\"mzk\",\"mzl\",\"mzm\",\"mzn\",\"mzo\",\"mzp\",\"mzq\",\"mzr\",\"mzs\",\"mzt\",\"mzu\",\"mzv\",\"mzw\",\"mzx\",\"mzy\",\"mzz\",\"naa\",\"nab\",\"nac\",\"nad\",\"nae\",\"naf\",\"nag\",\"nah\",\"nai\",\"naj\",\"nak\",\"nal\",\"nam\",\"nan\",\"nao\",\"nap\",\"naq\",\"nar\",\"nas\",\"nat\",\"naw\",\"nax\",\"nay\",\"naz\",\"nba\",\"nbb\",\"nbc\",\"nbd\",\"nbe\",\"nbf\",\"nbg\",\"nbh\",\"nbi\",\"nbj\",\"nbk\",\"nbm\",\"nbn\",\"nbo\",\"nbp\",\"nbq\",\"nbr\",\"nbs\",\"nbt\",\"nbu\",\"nbv\",\"nbw\",\"nbx\",\"nby\",\"nca\",\"ncb\",\"ncc\",\"ncd\",\"nce\",\"ncf\",\"ncg\",\"nch\",\"nci\",\"ncj\",\"nck\",\"ncl\",\"ncm\",\"ncn\",\"nco\",\"ncp\",\"ncr\",\"ncs\",\"nct\",\"ncu\",\"ncx\",\"ncz\",\"nda\",\"ndb\",\"ndc\",\"ndd\",\"ndf\",\"ndg\",\"ndh\",\"ndi\",\"ndj\",\"ndk\",\"ndl\",\"ndm\",\"ndn\",\"ndp\",\"ndq\",\"ndr\",\"nds\",\"ndt\",\"ndu\",\"ndv\",\"ndw\",\"ndx\",\"ndy\",\"ndz\",\"nea\",\"neb\",\"nec\",\"ned\",\"nee\",\"nef\",\"neg\",\"neh\",\"nei\",\"nej\",\"nek\",\"nem\",\"nen\",\"neo\",\"neq\",\"ner\",\"nes\",\"net\",\"neu\",\"nev\",\"new\",\"nex\",\"ney\",\"nez\",\"nfa\",\"nfd\",\"nfl\",\"nfr\",\"nfu\",\"nga\",\"ngb\",\"ngc\",\"ngd\",\"nge\",\"ngf\",\"ngg\",\"ngh\",\"ngi\",\"ngj\",\"ngk\",\"ngl\",\"ngm\",\"ngn\",\"ngo\",\"ngp\",\"ngq\",\"ngr\",\"ngs\",\"ngt\",\"ngu\",\"ngv\",\"ngw\",\"ngx\",\"ngy\",\"ngz\",\"nha\",\"nhb\",\"nhc\",\"nhd\",\"nhe\",\"nhf\",\"nhg\",\"nhh\",\"nhi\",\"nhk\",\"nhm\",\"nhn\",\"nho\",\"nhp\",\"nhq\",\"nhr\",\"nht\",\"nhu\",\"nhv\",\"nhw\",\"nhx\",\"nhy\",\"nhz\",\"nia\",\"nib\",\"nic\",\"nid\",\"nie\",\"nif\",\"nig\",\"nih\",\"nii\",\"nij\",\"nik\",\"nil\",\"nim\",\"nin\",\"nio\",\"niq\",\"nir\",\"nis\",\"nit\",\"niu\",\"niv\",\"niw\",\"nix\",\"niy\",\"niz\",\"nja\",\"njb\",\"njd\",\"njh\",\"nji\",\"njj\",\"njl\",\"njm\",\"njn\",\"njo\",\"njr\",\"njs\",\"njt\",\"nju\",\"njx\",\"njy\",\"njz\",\"nka\",\"nkb\",\"nkc\",\"nkd\",\"nke\",\"nkf\",\"nkg\",\"nkh\",\"nki\",\"nkj\",\"nkk\",\"nkm\",\"nkn\",\"nko\",\"nkp\",\"nkq\",\"nkr\",\"nks\",\"nkt\",\"nku\",\"nkv\",\"nkw\",\"nkx\",\"nkz\",\"nla\",\"nlc\",\"nle\",\"nlg\",\"nli\",\"nlj\",\"nlk\",\"nll\",\"nln\",\"nlo\",\"nlq\",\"nlr\",\"nlu\",\"nlv\",\"nlw\",\"nlx\",\"nly\",\"nlz\",\"nma\",\"nmb\",\"nmc\",\"nmd\",\"nme\",\"nmf\",\"nmg\",\"nmh\",\"nmi\",\"nmj\",\"nmk\",\"nml\",\"nmm\",\"nmn\",\"nmo\",\"nmp\",\"nmq\",\"nmr\",\"nms\",\"nmt\",\"nmu\",\"nmv\",\"nmw\",\"nmx\",\"nmy\",\"nmz\",\"nna\",\"nnb\",\"nnc\",\"nnd\",\"nne\",\"nnf\",\"nng\",\"nnh\",\"nni\",\"nnj\",\"nnk\",\"nnl\",\"nnm\",\"nnn\",\"nnp\",\"nnq\",\"nnr\",\"nns\",\"nnt\",\"nnu\",\"nnv\",\"nnw\",\"nnx\",\"nny\",\"nnz\",\"noa\",\"noc\",\"nod\",\"noe\",\"nof\",\"nog\",\"noh\",\"noi\",\"noj\",\"nok\",\"nol\",\"nom\",\"non\",\"noo\",\"nop\",\"noq\",\"nos\",\"not\",\"nou\",\"nov\",\"now\",\"noy\",\"noz\",\"npa\",\"npb\",\"npg\",\"nph\",\"npi\",\"npl\",\"npn\",\"npo\",\"nps\",\"npu\",\"npy\",\"nqg\",\"nqk\",\"nqm\",\"nqn\",\"nqo\",\"nqq\",\"nqy\",\"nra\",\"nrb\",\"nrc\",\"nre\",\"nrf\",\"nrg\",\"nri\",\"nrk\",\"nrl\",\"nrm\",\"nrn\",\"nrp\",\"nrr\",\"nrt\",\"nru\",\"nrx\",\"nrz\",\"nsa\",\"nsc\",\"nsd\",\"nse\",\"nsf\",\"nsg\",\"nsh\",\"nsi\",\"nsk\",\"nsl\",\"nsm\",\"nsn\",\"nso\",\"nsp\",\"nsq\",\"nsr\",\"nss\",\"nst\",\"nsu\",\"nsv\",\"nsw\",\"nsx\",\"nsy\",\"nsz\",\"ntd\",\"nte\",\"ntg\",\"nti\",\"ntj\",\"ntk\",\"ntm\",\"nto\",\"ntp\",\"ntr\",\"nts\",\"ntu\",\"ntw\",\"ntx\",\"nty\",\"ntz\",\"nua\",\"nub\",\"nuc\",\"nud\",\"nue\",\"nuf\",\"nug\",\"nuh\",\"nui\",\"nuj\",\"nuk\",\"nul\",\"num\",\"nun\",\"nuo\",\"nup\",\"nuq\",\"nur\",\"nus\",\"nut\",\"nuu\",\"nuv\",\"nuw\",\"nux\",\"nuy\",\"nuz\",\"nvh\",\"nvm\",\"nvo\",\"nwa\",\"nwb\",\"nwc\",\"nwe\",\"nwg\",\"nwi\",\"nwm\",\"nwo\",\"nwr\",\"nwx\",\"nwy\",\"nxa\",\"nxd\",\"nxe\",\"nxg\",\"nxi\",\"nxk\",\"nxl\",\"nxm\",\"nxn\",\"nxo\",\"nxq\",\"nxr\",\"nxu\",\"nxx\",\"nyb\",\"nyc\",\"nyd\",\"nye\",\"nyf\",\"nyg\",\"nyh\",\"nyi\",\"nyj\",\"nyk\",\"nyl\",\"nym\",\"nyn\",\"nyo\",\"nyp\",\"nyq\",\"nyr\",\"nys\",\"nyt\",\"nyu\",\"nyv\",\"nyw\",\"nyx\",\"nyy\",\"nza\",\"nzb\",\"nzi\",\"nzk\",\"nzm\",\"nzs\",\"nzu\",\"nzy\",\"nzz\",\"oaa\",\"oac\",\"oar\",\"oav\",\"obi\",\"obk\",\"obl\",\"obm\",\"obo\",\"obr\",\"obt\",\"obu\",\"oca\",\"och\",\"oco\",\"ocu\",\"oda\",\"odk\",\"odt\",\"odu\",\"ofo\",\"ofs\",\"ofu\",\"ogb\",\"ogc\",\"oge\",\"ogg\",\"ogo\",\"ogu\",\"oht\",\"ohu\",\"oia\",\"oin\",\"ojb\",\"ojc\",\"ojg\",\"ojp\",\"ojs\",\"ojv\",\"ojw\",\"oka\",\"okb\",\"okd\",\"oke\",\"okg\",\"okh\",\"oki\",\"okj\",\"okk\",\"okl\",\"okm\",\"okn\",\"oko\",\"okr\",\"oks\",\"oku\",\"okv\",\"okx\",\"ola\",\"old\",\"ole\",\"olk\",\"olm\",\"olo\",\"olr\",\"olt\",\"olu\",\"oma\",\"omb\",\"omc\",\"ome\",\"omg\",\"omi\",\"omk\",\"oml\",\"omn\",\"omo\",\"omp\",\"omq\",\"omr\",\"omt\",\"omu\",\"omv\",\"omw\",\"omx\",\"ona\",\"onb\",\"one\",\"ong\",\"oni\",\"onj\",\"onk\",\"onn\",\"ono\",\"onp\",\"onr\",\"ons\",\"ont\",\"onu\",\"onw\",\"onx\",\"ood\",\"oog\",\"oon\",\"oor\",\"oos\",\"opa\",\"opk\",\"opm\",\"opo\",\"opt\",\"opy\",\"ora\",\"orc\",\"ore\",\"org\",\"orh\",\"orn\",\"oro\",\"orr\",\"ors\",\"ort\",\"oru\",\"orv\",\"orw\",\"orx\",\"ory\",\"orz\",\"osa\",\"osc\",\"osi\",\"oso\",\"osp\",\"ost\",\"osu\",\"osx\",\"ota\",\"otb\",\"otd\",\"ote\",\"oti\",\"otk\",\"otl\",\"otm\",\"otn\",\"oto\",\"otq\",\"otr\",\"ots\",\"ott\",\"otu\",\"otw\",\"otx\",\"oty\",\"otz\",\"oua\",\"oub\",\"oue\",\"oui\",\"oum\",\"oun\",\"ovd\",\"owi\",\"owl\",\"oyb\",\"oyd\",\"oym\",\"oyy\",\"ozm\",\"paa\",\"pab\",\"pac\",\"pad\",\"pae\",\"paf\",\"pag\",\"pah\",\"pai\",\"pak\",\"pal\",\"pam\",\"pao\",\"pap\",\"paq\",\"par\",\"pas\",\"pat\",\"pau\",\"pav\",\"paw\",\"pax\",\"pay\",\"paz\",\"pbb\",\"pbc\",\"pbe\",\"pbf\",\"pbg\",\"pbh\",\"pbi\",\"pbl\",\"pbn\",\"pbo\",\"pbp\",\"pbr\",\"pbs\",\"pbt\",\"pbu\",\"pbv\",\"pby\",\"pbz\",\"pca\",\"pcb\",\"pcc\",\"pcd\",\"pce\",\"pcf\",\"pcg\",\"pch\",\"pci\",\"pcj\",\"pck\",\"pcl\",\"pcm\",\"pcn\",\"pcp\",\"pcr\",\"pcw\",\"pda\",\"pdc\",\"pdi\",\"pdn\",\"pdo\",\"pdt\",\"pdu\",\"pea\",\"peb\",\"ped\",\"pee\",\"pef\",\"peg\",\"peh\",\"pei\",\"pej\",\"pek\",\"pel\",\"pem\",\"peo\",\"pep\",\"peq\",\"pes\",\"pev\",\"pex\",\"pey\",\"pez\",\"pfa\",\"pfe\",\"pfl\",\"pga\",\"pgd\",\"pgg\",\"pgi\",\"pgk\",\"pgl\",\"pgn\",\"pgs\",\"pgu\",\"pgy\",\"pgz\",\"pha\",\"phd\",\"phg\",\"phh\",\"phi\",\"phk\",\"phl\",\"phm\",\"phn\",\"pho\",\"phq\",\"phr\",\"pht\",\"phu\",\"phv\",\"phw\",\"pia\",\"pib\",\"pic\",\"pid\",\"pie\",\"pif\",\"pig\",\"pih\",\"pii\",\"pij\",\"pil\",\"pim\",\"pin\",\"pio\",\"pip\",\"pir\",\"pis\",\"pit\",\"piu\",\"piv\",\"piw\",\"pix\",\"piy\",\"piz\",\"pjt\",\"pka\",\"pkb\",\"pkc\",\"pkg\",\"pkh\",\"pkn\",\"pko\",\"pkp\",\"pkr\",\"pks\",\"pkt\",\"pku\",\"pla\",\"plb\",\"plc\",\"pld\",\"ple\",\"plf\",\"plg\",\"plh\",\"plj\",\"plk\",\"pll\",\"pln\",\"plo\",\"plp\",\"plq\",\"plr\",\"pls\",\"plt\",\"plu\",\"plv\",\"plw\",\"ply\",\"plz\",\"pma\",\"pmb\",\"pmc\",\"pmd\",\"pme\",\"pmf\",\"pmh\",\"pmi\",\"pmj\",\"pmk\",\"pml\",\"pmm\",\"pmn\",\"pmo\",\"pmq\",\"pmr\",\"pms\",\"pmt\",\"pmu\",\"pmw\",\"pmx\",\"pmy\",\"pmz\",\"pna\",\"pnb\",\"pnc\",\"pne\",\"png\",\"pnh\",\"pni\",\"pnj\",\"pnk\",\"pnl\",\"pnm\",\"pnn\",\"pno\",\"pnp\",\"pnq\",\"pnr\",\"pns\",\"pnt\",\"pnu\",\"pnv\",\"pnw\",\"pnx\",\"pny\",\"pnz\",\"poc\",\"pod\",\"poe\",\"pof\",\"pog\",\"poh\",\"poi\",\"pok\",\"pom\",\"pon\",\"poo\",\"pop\",\"poq\",\"pos\",\"pot\",\"pov\",\"pow\",\"pox\",\"poy\",\"poz\",\"ppa\",\"ppe\",\"ppi\",\"ppk\",\"ppl\",\"ppm\",\"ppn\",\"ppo\",\"ppp\",\"ppq\",\"ppr\",\"pps\",\"ppt\",\"ppu\",\"pqa\",\"pqe\",\"pqm\",\"pqw\",\"pra\",\"prb\",\"prc\",\"prd\",\"pre\",\"prf\",\"prg\",\"prh\",\"pri\",\"prk\",\"prl\",\"prm\",\"prn\",\"pro\",\"prp\",\"prq\",\"prr\",\"prs\",\"prt\",\"pru\",\"prw\",\"prx\",\"pry\",\"prz\",\"psa\",\"psc\",\"psd\",\"pse\",\"psg\",\"psh\",\"psi\",\"psl\",\"psm\",\"psn\",\"pso\",\"psp\",\"psq\",\"psr\",\"pss\",\"pst\",\"psu\",\"psw\",\"psy\",\"pta\",\"pth\",\"pti\",\"ptn\",\"pto\",\"ptp\",\"ptq\",\"ptr\",\"ptt\",\"ptu\",\"ptv\",\"ptw\",\"pty\",\"pua\",\"pub\",\"puc\",\"pud\",\"pue\",\"puf\",\"pug\",\"pui\",\"puj\",\"puk\",\"pum\",\"puo\",\"pup\",\"puq\",\"pur\",\"put\",\"puu\",\"puw\",\"pux\",\"puy\",\"puz\",\"pwa\",\"pwb\",\"pwg\",\"pwi\",\"pwm\",\"pwn\",\"pwo\",\"pwr\",\"pww\",\"pxm\",\"pye\",\"pym\",\"pyn\",\"pys\",\"pyu\",\"pyx\",\"pyy\",\"pzn\",\"qaa..qtz\",\"qua\",\"qub\",\"quc\",\"qud\",\"quf\",\"qug\",\"quh\",\"qui\",\"quk\",\"qul\",\"qum\",\"qun\",\"qup\",\"quq\",\"qur\",\"qus\",\"quv\",\"quw\",\"qux\",\"quy\",\"quz\",\"qva\",\"qvc\",\"qve\",\"qvh\",\"qvi\",\"qvj\",\"qvl\",\"qvm\",\"qvn\",\"qvo\",\"qvp\",\"qvs\",\"qvw\",\"qvy\",\"qvz\",\"qwa\",\"qwc\",\"qwe\",\"qwh\",\"qwm\",\"qws\",\"qwt\",\"qxa\",\"qxc\",\"qxh\",\"qxl\",\"qxn\",\"qxo\",\"qxp\",\"qxq\",\"qxr\",\"qxs\",\"qxt\",\"qxu\",\"qxw\",\"qya\",\"qyp\",\"raa\",\"rab\",\"rac\",\"rad\",\"raf\",\"rag\",\"rah\",\"rai\",\"raj\",\"rak\",\"ral\",\"ram\",\"ran\",\"rao\",\"rap\",\"raq\",\"rar\",\"ras\",\"rat\",\"rau\",\"rav\",\"raw\",\"rax\",\"ray\",\"raz\",\"rbb\",\"rbk\",\"rbl\",\"rbp\",\"rcf\",\"rdb\",\"rea\",\"reb\",\"ree\",\"reg\",\"rei\",\"rej\",\"rel\",\"rem\",\"ren\",\"rer\",\"res\",\"ret\",\"rey\",\"rga\",\"rge\",\"rgk\",\"rgn\",\"rgr\",\"rgs\",\"rgu\",\"rhg\",\"rhp\",\"ria\",\"rie\",\"rif\",\"ril\",\"rim\",\"rin\",\"rir\",\"rit\",\"riu\",\"rjg\",\"rji\",\"rjs\",\"rka\",\"rkb\",\"rkh\",\"rki\",\"rkm\",\"rkt\",\"rkw\",\"rma\",\"rmb\",\"rmc\",\"rmd\",\"rme\",\"rmf\",\"rmg\",\"rmh\",\"rmi\",\"rmk\",\"rml\",\"rmm\",\"rmn\",\"rmo\",\"rmp\",\"rmq\",\"rmr\",\"rms\",\"rmt\",\"rmu\",\"rmv\",\"rmw\",\"rmx\",\"rmy\",\"rmz\",\"rna\",\"rnd\",\"rng\",\"rnl\",\"rnn\",\"rnp\",\"rnr\",\"rnw\",\"roa\",\"rob\",\"roc\",\"rod\",\"roe\",\"rof\",\"rog\",\"rol\",\"rom\",\"roo\",\"rop\",\"ror\",\"rou\",\"row\",\"rpn\",\"rpt\",\"rri\",\"rro\",\"rrt\",\"rsb\",\"rsi\",\"rsl\",\"rsm\",\"rtc\",\"rth\",\"rtm\",\"rts\",\"rtw\",\"rub\",\"ruc\",\"rue\",\"ruf\",\"rug\",\"ruh\",\"rui\",\"ruk\",\"ruo\",\"rup\",\"ruq\",\"rut\",\"ruu\",\"ruy\",\"ruz\",\"rwa\",\"rwk\",\"rwm\",\"rwo\",\"rwr\",\"rxd\",\"rxw\",\"ryn\",\"rys\",\"ryu\",\"rzh\",\"saa\",\"sab\",\"sac\",\"sad\",\"sae\",\"saf\",\"sah\",\"sai\",\"saj\",\"sak\",\"sal\",\"sam\",\"sao\",\"sap\",\"saq\",\"sar\",\"sas\",\"sat\",\"sau\",\"sav\",\"saw\",\"sax\",\"say\",\"saz\",\"sba\",\"sbb\",\"sbc\",\"sbd\",\"sbe\",\"sbf\",\"sbg\",\"sbh\",\"sbi\",\"sbj\",\"sbk\",\"sbl\",\"sbm\",\"sbn\",\"sbo\",\"sbp\",\"sbq\",\"sbr\",\"sbs\",\"sbt\",\"sbu\",\"sbv\",\"sbw\",\"sbx\",\"sby\",\"sbz\",\"sca\",\"scb\",\"sce\",\"scf\",\"scg\",\"sch\",\"sci\",\"sck\",\"scl\",\"scn\",\"sco\",\"scp\",\"scq\",\"scs\",\"scu\",\"scv\",\"scw\",\"scx\",\"sda\",\"sdb\",\"sdc\",\"sde\",\"sdf\",\"sdg\",\"sdh\",\"sdj\",\"sdk\",\"sdl\",\"sdm\",\"sdn\",\"sdo\",\"sdp\",\"sdr\",\"sds\",\"sdt\",\"sdu\",\"sdv\",\"sdx\",\"sdz\",\"sea\",\"seb\",\"sec\",\"sed\",\"see\",\"sef\",\"seg\",\"seh\",\"sei\",\"sej\",\"sek\",\"sel\",\"sem\",\"sen\",\"seo\",\"sep\",\"seq\",\"ser\",\"ses\",\"set\",\"seu\",\"sev\",\"sew\",\"sey\",\"sez\",\"sfb\",\"sfe\",\"sfm\",\"sfs\",\"sfw\",\"sga\",\"sgb\",\"sgc\",\"sgd\",\"sge\",\"sgg\",\"sgh\",\"sgi\",\"sgj\",\"sgk\",\"sgl\",\"sgm\",\"sgn\",\"sgo\",\"sgp\",\"sgr\",\"sgs\",\"sgt\",\"sgu\",\"sgw\",\"sgx\",\"sgy\",\"sgz\",\"sha\",\"shb\",\"shc\",\"shd\",\"she\",\"shg\",\"shh\",\"shi\",\"shj\",\"shk\",\"shl\",\"shm\",\"shn\",\"sho\",\"shp\",\"shq\",\"shr\",\"shs\",\"sht\",\"shu\",\"shv\",\"shw\",\"shx\",\"shy\",\"shz\",\"sia\",\"sib\",\"sid\",\"sie\",\"sif\",\"sig\",\"sih\",\"sii\",\"sij\",\"sik\",\"sil\",\"sim\",\"sio\",\"sip\",\"siq\",\"sir\",\"sis\",\"sit\",\"siu\",\"siv\",\"siw\",\"six\",\"siy\",\"siz\",\"sja\",\"sjb\",\"sjd\",\"sje\",\"sjg\",\"sjk\",\"sjl\",\"sjm\",\"sjn\",\"sjo\",\"sjp\",\"sjr\",\"sjs\",\"sjt\",\"sju\",\"sjw\",\"ska\",\"skb\",\"skc\",\"skd\",\"ske\",\"skf\",\"skg\",\"skh\",\"ski\",\"skj\",\"skk\",\"skm\",\"skn\",\"sko\",\"skp\",\"skq\",\"skr\",\"sks\",\"skt\",\"sku\",\"skv\",\"skw\",\"skx\",\"sky\",\"skz\",\"sla\",\"slc\",\"sld\",\"sle\",\"slf\",\"slg\",\"slh\",\"sli\",\"slj\",\"sll\",\"slm\",\"sln\",\"slp\",\"slq\",\"slr\",\"sls\",\"slt\",\"slu\",\"slw\",\"slx\",\"sly\",\"slz\",\"sma\",\"smb\",\"smc\",\"smd\",\"smf\",\"smg\",\"smh\",\"smi\",\"smj\",\"smk\",\"sml\",\"smm\",\"smn\",\"smp\",\"smq\",\"smr\",\"sms\",\"smt\",\"smu\",\"smv\",\"smw\",\"smx\",\"smy\",\"smz\",\"snb\",\"snc\",\"sne\",\"snf\",\"sng\",\"snh\",\"sni\",\"snj\",\"snk\",\"snl\",\"snm\",\"snn\",\"sno\",\"snp\",\"snq\",\"snr\",\"sns\",\"snu\",\"snv\",\"snw\",\"snx\",\"sny\",\"snz\",\"soa\",\"sob\",\"soc\",\"sod\",\"soe\",\"sog\",\"soh\",\"soi\",\"soj\",\"sok\",\"sol\",\"son\",\"soo\",\"sop\",\"soq\",\"sor\",\"sos\",\"sou\",\"sov\",\"sow\",\"sox\",\"soy\",\"soz\",\"spb\",\"spc\",\"spd\",\"spe\",\"spg\",\"spi\",\"spk\",\"spl\",\"spm\",\"spn\",\"spo\",\"spp\",\"spq\",\"spr\",\"sps\",\"spt\",\"spu\",\"spv\",\"spx\",\"spy\",\"sqa\",\"sqh\",\"sqj\",\"sqk\",\"sqm\",\"sqn\",\"sqo\",\"sqq\",\"sqr\",\"sqs\",\"sqt\",\"squ\",\"sra\",\"srb\",\"src\",\"sre\",\"srf\",\"srg\",\"srh\",\"sri\",\"srk\",\"srl\",\"srm\",\"srn\",\"sro\",\"srq\",\"srr\",\"srs\",\"srt\",\"sru\",\"srv\",\"srw\",\"srx\",\"sry\",\"srz\",\"ssa\",\"ssb\",\"ssc\",\"ssd\",\"sse\",\"ssf\",\"ssg\",\"ssh\",\"ssi\",\"ssj\",\"ssk\",\"ssl\",\"ssm\",\"ssn\",\"sso\",\"ssp\",\"ssq\",\"ssr\",\"sss\",\"sst\",\"ssu\",\"ssv\",\"ssx\",\"ssy\",\"ssz\",\"sta\",\"stb\",\"std\",\"ste\",\"stf\",\"stg\",\"sth\",\"sti\",\"stj\",\"stk\",\"stl\",\"stm\",\"stn\",\"sto\",\"stp\",\"stq\",\"str\",\"sts\",\"stt\",\"stu\",\"stv\",\"stw\",\"sty\",\"sua\",\"sub\",\"suc\",\"sue\",\"sug\",\"sui\",\"suj\",\"suk\",\"sul\",\"sum\",\"suq\",\"sur\",\"sus\",\"sut\",\"suv\",\"suw\",\"sux\",\"suy\",\"suz\",\"sva\",\"svb\",\"svc\",\"sve\",\"svk\",\"svm\",\"svr\",\"svs\",\"svx\",\"swb\",\"swc\",\"swf\",\"swg\",\"swh\",\"swi\",\"swj\",\"swk\",\"swl\",\"swm\",\"swn\",\"swo\",\"swp\",\"swq\",\"swr\",\"sws\",\"swt\",\"swu\",\"swv\",\"sww\",\"swx\",\"swy\",\"sxb\",\"sxc\",\"sxe\",\"sxg\",\"sxk\",\"sxl\",\"sxm\",\"sxn\",\"sxo\",\"sxr\",\"sxs\",\"sxu\",\"sxw\",\"sya\",\"syb\",\"syc\",\"syd\",\"syi\",\"syk\",\"syl\",\"sym\",\"syn\",\"syo\",\"syr\",\"sys\",\"syw\",\"syx\",\"syy\",\"sza\",\"szb\",\"szc\",\"szd\",\"sze\",\"szg\",\"szl\",\"szn\",\"szp\",\"szv\",\"szw\",\"taa\",\"tab\",\"tac\",\"tad\",\"tae\",\"taf\",\"tag\",\"tai\",\"taj\",\"tak\",\"tal\",\"tan\",\"tao\",\"tap\",\"taq\",\"tar\",\"tas\",\"tau\",\"tav\",\"taw\",\"tax\",\"tay\",\"taz\",\"tba\",\"tbb\",\"tbc\",\"tbd\",\"tbe\",\"tbf\",\"tbg\",\"tbh\",\"tbi\",\"tbj\",\"tbk\",\"tbl\",\"tbm\",\"tbn\",\"tbo\",\"tbp\",\"tbq\",\"tbr\",\"tbs\",\"tbt\",\"tbu\",\"tbv\",\"tbw\",\"tbx\",\"tby\",\"tbz\",\"tca\",\"tcb\",\"tcc\",\"tcd\",\"tce\",\"tcf\",\"tcg\",\"tch\",\"tci\",\"tck\",\"tcl\",\"tcm\",\"tcn\",\"tco\",\"tcp\",\"tcq\",\"tcs\",\"tct\",\"tcu\",\"tcw\",\"tcx\",\"tcy\",\"tcz\",\"tda\",\"tdb\",\"tdc\",\"tdd\",\"tde\",\"tdf\",\"tdg\",\"tdh\",\"tdi\",\"tdj\",\"tdk\",\"tdl\",\"tdm\",\"tdn\",\"tdo\",\"tdq\",\"tdr\",\"tds\",\"tdt\",\"tdu\",\"tdv\",\"tdx\",\"tdy\",\"tea\",\"teb\",\"tec\",\"ted\",\"tee\",\"tef\",\"teg\",\"teh\",\"tei\",\"tek\",\"tem\",\"ten\",\"teo\",\"tep\",\"teq\",\"ter\",\"tes\",\"tet\",\"teu\",\"tev\",\"tew\",\"tex\",\"tey\",\"tfi\",\"tfn\",\"tfo\",\"tfr\",\"tft\",\"tga\",\"tgb\",\"tgc\",\"tgd\",\"tge\",\"tgf\",\"tgg\",\"tgh\",\"tgi\",\"tgj\",\"tgn\",\"tgo\",\"tgp\",\"tgq\",\"tgr\",\"tgs\",\"tgt\",\"tgu\",\"tgv\",\"tgw\",\"tgx\",\"tgy\",\"tgz\",\"thc\",\"thd\",\"the\",\"thf\",\"thh\",\"thi\",\"thk\",\"thl\",\"thm\",\"thn\",\"thp\",\"thq\",\"thr\",\"ths\",\"tht\",\"thu\",\"thv\",\"thw\",\"thx\",\"thy\",\"thz\",\"tia\",\"tic\",\"tid\",\"tie\",\"tif\",\"tig\",\"tih\",\"tii\",\"tij\",\"tik\",\"til\",\"tim\",\"tin\",\"tio\",\"tip\",\"tiq\",\"tis\",\"tit\",\"tiu\",\"tiv\",\"tiw\",\"tix\",\"tiy\",\"tiz\",\"tja\",\"tjg\",\"tji\",\"tjl\",\"tjm\",\"tjn\",\"tjo\",\"tjs\",\"tju\",\"tjw\",\"tka\",\"tkb\",\"tkd\",\"tke\",\"tkf\",\"tkg\",\"tkk\",\"tkl\",\"tkm\",\"tkn\",\"tkp\",\"tkq\",\"tkr\",\"tks\",\"tkt\",\"tku\",\"tkv\",\"tkw\",\"tkx\",\"tkz\",\"tla\",\"tlb\",\"tlc\",\"tld\",\"tlf\",\"tlg\",\"tlh\",\"tli\",\"tlj\",\"tlk\",\"tll\",\"tlm\",\"tln\",\"tlo\",\"tlp\",\"tlq\",\"tlr\",\"tls\",\"tlt\",\"tlu\",\"tlv\",\"tlw\",\"tlx\",\"tly\",\"tma\",\"tmb\",\"tmc\",\"tmd\",\"tme\",\"tmf\",\"tmg\",\"tmh\",\"tmi\",\"tmj\",\"tmk\",\"tml\",\"tmm\",\"tmn\",\"tmo\",\"tmp\",\"tmq\",\"tmr\",\"tms\",\"tmt\",\"tmu\",\"tmv\",\"tmw\",\"tmy\",\"tmz\",\"tna\",\"tnb\",\"tnc\",\"tnd\",\"tne\",\"tnf\",\"tng\",\"tnh\",\"tni\",\"tnk\",\"tnl\",\"tnm\",\"tnn\",\"tno\",\"tnp\",\"tnq\",\"tnr\",\"tns\",\"tnt\",\"tnu\",\"tnv\",\"tnw\",\"tnx\",\"tny\",\"tnz\",\"tob\",\"toc\",\"tod\",\"toe\",\"tof\",\"tog\",\"toh\",\"toi\",\"toj\",\"tol\",\"tom\",\"too\",\"top\",\"toq\",\"tor\",\"tos\",\"tou\",\"tov\",\"tow\",\"tox\",\"toy\",\"toz\",\"tpa\",\"tpc\",\"tpe\",\"tpf\",\"tpg\",\"tpi\",\"tpj\",\"tpk\",\"tpl\",\"tpm\",\"tpn\",\"tpo\",\"tpp\",\"tpq\",\"tpr\",\"tpt\",\"tpu\",\"tpv\",\"tpw\",\"tpx\",\"tpy\",\"tpz\",\"tqb\",\"tql\",\"tqm\",\"tqn\",\"tqo\",\"tqp\",\"tqq\",\"tqr\",\"tqt\",\"tqu\",\"tqw\",\"tra\",\"trb\",\"trc\",\"trd\",\"tre\",\"trf\",\"trg\",\"trh\",\"tri\",\"trj\",\"trk\",\"trl\",\"trm\",\"trn\",\"tro\",\"trp\",\"trq\",\"trr\",\"trs\",\"trt\",\"tru\",\"trv\",\"trw\",\"trx\",\"try\",\"trz\",\"tsa\",\"tsb\",\"tsc\",\"tsd\",\"tse\",\"tsf\",\"tsg\",\"tsh\",\"tsi\",\"tsj\",\"tsk\",\"tsl\",\"tsm\",\"tsp\",\"tsq\",\"tsr\",\"tss\",\"tst\",\"tsu\",\"tsv\",\"tsw\",\"tsx\",\"tsy\",\"tsz\",\"tta\",\"ttb\",\"ttc\",\"ttd\",\"tte\",\"ttf\",\"ttg\",\"tth\",\"tti\",\"ttj\",\"ttk\",\"ttl\",\"ttm\",\"ttn\",\"tto\",\"ttp\",\"ttq\",\"ttr\",\"tts\",\"ttt\",\"ttu\",\"ttv\",\"ttw\",\"tty\",\"ttz\",\"tua\",\"tub\",\"tuc\",\"tud\",\"tue\",\"tuf\",\"tug\",\"tuh\",\"tui\",\"tuj\",\"tul\",\"tum\",\"tun\",\"tuo\",\"tup\",\"tuq\",\"tus\",\"tut\",\"tuu\",\"tuv\",\"tuw\",\"tux\",\"tuy\",\"tuz\",\"tva\",\"tvd\",\"tve\",\"tvk\",\"tvl\",\"tvm\",\"tvn\",\"tvo\",\"tvs\",\"tvt\",\"tvu\",\"tvw\",\"tvy\",\"twa\",\"twb\",\"twc\",\"twd\",\"twe\",\"twf\",\"twg\",\"twh\",\"twl\",\"twm\",\"twn\",\"two\",\"twp\",\"twq\",\"twr\",\"twt\",\"twu\",\"tww\",\"twx\",\"twy\",\"txa\",\"txb\",\"txc\",\"txe\",\"txg\",\"txh\",\"txi\",\"txj\",\"txm\",\"txn\",\"txo\",\"txq\",\"txr\",\"txs\",\"txt\",\"txu\",\"txx\",\"txy\",\"tya\",\"tye\",\"tyh\",\"tyi\",\"tyj\",\"tyl\",\"tyn\",\"typ\",\"tyr\",\"tys\",\"tyt\",\"tyu\",\"tyv\",\"tyx\",\"tyz\",\"tza\",\"tzh\",\"tzj\",\"tzl\",\"tzm\",\"tzn\",\"tzo\",\"tzx\",\"uam\",\"uan\",\"uar\",\"uba\",\"ubi\",\"ubl\",\"ubr\",\"ubu\",\"uby\",\"uda\",\"ude\",\"udg\",\"udi\",\"udj\",\"udl\",\"udm\",\"udu\",\"ues\",\"ufi\",\"uga\",\"ugb\",\"uge\",\"ugn\",\"ugo\",\"ugy\",\"uha\",\"uhn\",\"uis\",\"uiv\",\"uji\",\"uka\",\"ukg\",\"ukh\",\"ukl\",\"ukp\",\"ukq\",\"uks\",\"uku\",\"ukw\",\"uky\",\"ula\",\"ulb\",\"ulc\",\"ule\",\"ulf\",\"uli\",\"ulk\",\"ull\",\"ulm\",\"uln\",\"ulu\",\"ulw\",\"uma\",\"umb\",\"umc\",\"umd\",\"umg\",\"umi\",\"umm\",\"umn\",\"umo\",\"ump\",\"umr\",\"ums\",\"umu\",\"una\",\"und\",\"une\",\"ung\",\"unk\",\"unm\",\"unn\",\"unp\",\"unr\",\"unu\",\"unx\",\"unz\",\"uok\",\"upi\",\"upv\",\"ura\",\"urb\",\"urc\",\"ure\",\"urf\",\"urg\",\"urh\",\"uri\",\"urj\",\"urk\",\"url\",\"urm\",\"urn\",\"uro\",\"urp\",\"urr\",\"urt\",\"uru\",\"urv\",\"urw\",\"urx\",\"ury\",\"urz\",\"usa\",\"ush\",\"usi\",\"usk\",\"usp\",\"usu\",\"uta\",\"ute\",\"utp\",\"utr\",\"utu\",\"uum\",\"uun\",\"uur\",\"uuu\",\"uve\",\"uvh\",\"uvl\",\"uwa\",\"uya\",\"uzn\",\"uzs\",\"vaa\",\"vae\",\"vaf\",\"vag\",\"vah\",\"vai\",\"vaj\",\"val\",\"vam\",\"van\",\"vao\",\"vap\",\"var\",\"vas\",\"vau\",\"vav\",\"vay\",\"vbb\",\"vbk\",\"vec\",\"ved\",\"vel\",\"vem\",\"veo\",\"vep\",\"ver\",\"vgr\",\"vgt\",\"vic\",\"vid\",\"vif\",\"vig\",\"vil\",\"vin\",\"vis\",\"vit\",\"viv\",\"vka\",\"vki\",\"vkj\",\"vkk\",\"vkl\",\"vkm\",\"vko\",\"vkp\",\"vkt\",\"vku\",\"vlp\",\"vls\",\"vma\",\"vmb\",\"vmc\",\"vmd\",\"vme\",\"vmf\",\"vmg\",\"vmh\",\"vmi\",\"vmj\",\"vmk\",\"vml\",\"vmm\",\"vmp\",\"vmq\",\"vmr\",\"vms\",\"vmu\",\"vmv\",\"vmw\",\"vmx\",\"vmy\",\"vmz\",\"vnk\",\"vnm\",\"vnp\",\"vor\",\"vot\",\"vra\",\"vro\",\"vrs\",\"vrt\",\"vsi\",\"vsl\",\"vsv\",\"vto\",\"vum\",\"vun\",\"vut\",\"vwa\",\"waa\",\"wab\",\"wac\",\"wad\",\"wae\",\"waf\",\"wag\",\"wah\",\"wai\",\"waj\",\"wak\",\"wal\",\"wam\",\"wan\",\"wao\",\"wap\",\"waq\",\"war\",\"was\",\"wat\",\"wau\",\"wav\",\"waw\",\"wax\",\"way\",\"waz\",\"wba\",\"wbb\",\"wbe\",\"wbf\",\"wbh\",\"wbi\",\"wbj\",\"wbk\",\"wbl\",\"wbm\",\"wbp\",\"wbq\",\"wbr\",\"wbt\",\"wbv\",\"wbw\",\"wca\",\"wci\",\"wdd\",\"wdg\",\"wdj\",\"wdk\",\"wdu\",\"wdy\",\"wea\",\"wec\",\"wed\",\"weg\",\"weh\",\"wei\",\"wem\",\"wen\",\"weo\",\"wep\",\"wer\",\"wes\",\"wet\",\"weu\",\"wew\",\"wfg\",\"wga\",\"wgb\",\"wgg\",\"wgi\",\"wgo\",\"wgu\",\"wgw\",\"wgy\",\"wha\",\"whg\",\"whk\",\"whu\",\"wib\",\"wic\",\"wie\",\"wif\",\"wig\",\"wih\",\"wii\",\"wij\",\"wik\",\"wil\",\"wim\",\"win\",\"wir\",\"wit\",\"wiu\",\"wiv\",\"wiw\",\"wiy\",\"wja\",\"wji\",\"wka\",\"wkb\",\"wkd\",\"wkl\",\"wku\",\"wkw\",\"wky\",\"wla\",\"wlc\",\"wle\",\"wlg\",\"wli\",\"wlk\",\"wll\",\"wlm\",\"wlo\",\"wlr\",\"wls\",\"wlu\",\"wlv\",\"wlw\",\"wlx\",\"wly\",\"wma\",\"wmb\",\"wmc\",\"wmd\",\"wme\",\"wmh\",\"wmi\",\"wmm\",\"wmn\",\"wmo\",\"wms\",\"wmt\",\"wmw\",\"wmx\",\"wnb\",\"wnc\",\"wnd\",\"wne\",\"wng\",\"wni\",\"wnk\",\"wnm\",\"wnn\",\"wno\",\"wnp\",\"wnu\",\"wnw\",\"wny\",\"woa\",\"wob\",\"woc\",\"wod\",\"woe\",\"wof\",\"wog\",\"woi\",\"wok\",\"wom\",\"won\",\"woo\",\"wor\",\"wos\",\"wow\",\"woy\",\"wpc\",\"wra\",\"wrb\",\"wrd\",\"wrg\",\"wrh\",\"wri\",\"wrk\",\"wrl\",\"wrm\",\"wrn\",\"wro\",\"wrp\",\"wrr\",\"wrs\",\"wru\",\"wrv\",\"wrw\",\"wrx\",\"wry\",\"wrz\",\"wsa\",\"wsg\",\"wsi\",\"wsk\",\"wsr\",\"wss\",\"wsu\",\"wsv\",\"wtf\",\"wth\",\"wti\",\"wtk\",\"wtm\",\"wtw\",\"wua\",\"wub\",\"wud\",\"wuh\",\"wul\",\"wum\",\"wun\",\"wur\",\"wut\",\"wuu\",\"wuv\",\"wux\",\"wuy\",\"wwa\",\"wwb\",\"wwo\",\"wwr\",\"www\",\"wxa\",\"wxw\",\"wya\",\"wyb\",\"wyi\",\"wym\",\"wyr\",\"wyy\",\"xaa\",\"xab\",\"xac\",\"xad\",\"xae\",\"xag\",\"xai\",\"xaj\",\"xak\",\"xal\",\"xam\",\"xan\",\"xao\",\"xap\",\"xaq\",\"xar\",\"xas\",\"xat\",\"xau\",\"xav\",\"xaw\",\"xay\",\"xba\",\"xbb\",\"xbc\",\"xbd\",\"xbe\",\"xbg\",\"xbi\",\"xbj\",\"xbm\",\"xbn\",\"xbo\",\"xbp\",\"xbr\",\"xbw\",\"xbx\",\"xby\",\"xcb\",\"xcc\",\"xce\",\"xcg\",\"xch\",\"xcl\",\"xcm\",\"xcn\",\"xco\",\"xcr\",\"xct\",\"xcu\",\"xcv\",\"xcw\",\"xcy\",\"xda\",\"xdc\",\"xdk\",\"xdm\",\"xdy\",\"xeb\",\"xed\",\"xeg\",\"xel\",\"xem\",\"xep\",\"xer\",\"xes\",\"xet\",\"xeu\",\"xfa\",\"xga\",\"xgb\",\"xgd\",\"xgf\",\"xgg\",\"xgi\",\"xgl\",\"xgm\",\"xgn\",\"xgr\",\"xgu\",\"xgw\",\"xha\",\"xhc\",\"xhd\",\"xhe\",\"xhr\",\"xht\",\"xhu\",\"xhv\",\"xia\",\"xib\",\"xii\",\"xil\",\"xin\",\"xip\",\"xir\",\"xis\",\"xiv\",\"xiy\",\"xjb\",\"xjt\",\"xka\",\"xkb\",\"xkc\",\"xkd\",\"xke\",\"xkf\",\"xkg\",\"xkh\",\"xki\",\"xkj\",\"xkk\",\"xkl\",\"xkn\",\"xko\",\"xkp\",\"xkq\",\"xkr\",\"xks\",\"xkt\",\"xku\",\"xkv\",\"xkw\",\"xkx\",\"xky\",\"xkz\",\"xla\",\"xlb\",\"xlc\",\"xld\",\"xle\",\"xlg\",\"xli\",\"xln\",\"xlo\",\"xlp\",\"xls\",\"xlu\",\"xly\",\"xma\",\"xmb\",\"xmc\",\"xmd\",\"xme\",\"xmf\",\"xmg\",\"xmh\",\"xmj\",\"xmk\",\"xml\",\"xmm\",\"xmn\",\"xmo\",\"xmp\",\"xmq\",\"xmr\",\"xms\",\"xmt\",\"xmu\",\"xmv\",\"xmw\",\"xmx\",\"xmy\",\"xmz\",\"xna\",\"xnb\",\"xnd\",\"xng\",\"xnh\",\"xni\",\"xnk\",\"xnn\",\"xno\",\"xnr\",\"xns\",\"xnt\",\"xnu\",\"xny\",\"xnz\",\"xoc\",\"xod\",\"xog\",\"xoi\",\"xok\",\"xom\",\"xon\",\"xoo\",\"xop\",\"xor\",\"xow\",\"xpa\",\"xpc\",\"xpe\",\"xpg\",\"xpi\",\"xpj\",\"xpk\",\"xpm\",\"xpn\",\"xpo\",\"xpp\",\"xpq\",\"xpr\",\"xps\",\"xpt\",\"xpu\",\"xpy\",\"xqa\",\"xqt\",\"xra\",\"xrb\",\"xrd\",\"xre\",\"xrg\",\"xri\",\"xrm\",\"xrn\",\"xrq\",\"xrr\",\"xrt\",\"xru\",\"xrw\",\"xsa\",\"xsb\",\"xsc\",\"xsd\",\"xse\",\"xsh\",\"xsi\",\"xsj\",\"xsl\",\"xsm\",\"xsn\",\"xso\",\"xsp\",\"xsq\",\"xsr\",\"xss\",\"xsu\",\"xsv\",\"xsy\",\"xta\",\"xtb\",\"xtc\",\"xtd\",\"xte\",\"xtg\",\"xth\",\"xti\",\"xtj\",\"xtl\",\"xtm\",\"xtn\",\"xto\",\"xtp\",\"xtq\",\"xtr\",\"xts\",\"xtt\",\"xtu\",\"xtv\",\"xtw\",\"xty\",\"xtz\",\"xua\",\"xub\",\"xud\",\"xug\",\"xuj\",\"xul\",\"xum\",\"xun\",\"xuo\",\"xup\",\"xur\",\"xut\",\"xuu\",\"xve\",\"xvi\",\"xvn\",\"xvo\",\"xvs\",\"xwa\",\"xwc\",\"xwd\",\"xwe\",\"xwg\",\"xwj\",\"xwk\",\"xwl\",\"xwo\",\"xwr\",\"xwt\",\"xww\",\"xxb\",\"xxk\",\"xxm\",\"xxr\",\"xxt\",\"xya\",\"xyb\",\"xyj\",\"xyk\",\"xyl\",\"xyt\",\"xyy\",\"xzh\",\"xzm\",\"xzp\",\"yaa\",\"yab\",\"yac\",\"yad\",\"yae\",\"yaf\",\"yag\",\"yah\",\"yai\",\"yaj\",\"yak\",\"yal\",\"yam\",\"yan\",\"yao\",\"yap\",\"yaq\",\"yar\",\"yas\",\"yat\",\"yau\",\"yav\",\"yaw\",\"yax\",\"yay\",\"yaz\",\"yba\",\"ybb\",\"ybd\",\"ybe\",\"ybh\",\"ybi\",\"ybj\",\"ybk\",\"ybl\",\"ybm\",\"ybn\",\"ybo\",\"ybx\",\"yby\",\"ych\",\"ycl\",\"ycn\",\"ycp\",\"yda\",\"ydd\",\"yde\",\"ydg\",\"ydk\",\"yds\",\"yea\",\"yec\",\"yee\",\"yei\",\"yej\",\"yel\",\"yen\",\"yer\",\"yes\",\"yet\",\"yeu\",\"yev\",\"yey\",\"yga\",\"ygi\",\"ygl\",\"ygm\",\"ygp\",\"ygr\",\"ygs\",\"ygu\",\"ygw\",\"yha\",\"yhd\",\"yhl\",\"yhs\",\"yia\",\"yif\",\"yig\",\"yih\",\"yii\",\"yij\",\"yik\",\"yil\",\"yim\",\"yin\",\"yip\",\"yiq\",\"yir\",\"yis\",\"yit\",\"yiu\",\"yiv\",\"yix\",\"yiy\",\"yiz\",\"yka\",\"ykg\",\"yki\",\"ykk\",\"ykl\",\"ykm\",\"ykn\",\"yko\",\"ykr\",\"ykt\",\"yku\",\"yky\",\"yla\",\"ylb\",\"yle\",\"ylg\",\"yli\",\"yll\",\"ylm\",\"yln\",\"ylo\",\"ylr\",\"ylu\",\"yly\",\"yma\",\"ymb\",\"ymc\",\"ymd\",\"yme\",\"ymg\",\"ymh\",\"ymi\",\"ymk\",\"yml\",\"ymm\",\"ymn\",\"ymo\",\"ymp\",\"ymq\",\"ymr\",\"yms\",\"ymt\",\"ymx\",\"ymz\",\"yna\",\"ynd\",\"yne\",\"yng\",\"ynh\",\"ynk\",\"ynl\",\"ynn\",\"yno\",\"ynq\",\"yns\",\"ynu\",\"yob\",\"yog\",\"yoi\",\"yok\",\"yol\",\"yom\",\"yon\",\"yos\",\"yot\",\"yox\",\"yoy\",\"ypa\",\"ypb\",\"ypg\",\"yph\",\"ypk\",\"ypm\",\"ypn\",\"ypo\",\"ypp\",\"ypz\",\"yra\",\"yrb\",\"yre\",\"yri\",\"yrk\",\"yrl\",\"yrm\",\"yrn\",\"yro\",\"yrs\",\"yrw\",\"yry\",\"ysc\",\"ysd\",\"ysg\",\"ysl\",\"ysn\",\"yso\",\"ysp\",\"ysr\",\"yss\",\"ysy\",\"yta\",\"ytl\",\"ytp\",\"ytw\",\"yty\",\"yua\",\"yub\",\"yuc\",\"yud\",\"yue\",\"yuf\",\"yug\",\"yui\",\"yuj\",\"yuk\",\"yul\",\"yum\",\"yun\",\"yup\",\"yuq\",\"yur\",\"yut\",\"yuu\",\"yuw\",\"yux\",\"yuy\",\"yuz\",\"yva\",\"yvt\",\"ywa\",\"ywg\",\"ywl\",\"ywn\",\"ywq\",\"ywr\",\"ywt\",\"ywu\",\"yww\",\"yxa\",\"yxg\",\"yxl\",\"yxm\",\"yxu\",\"yxy\",\"yyr\",\"yyu\",\"yyz\",\"yzg\",\"yzk\",\"zaa\",\"zab\",\"zac\",\"zad\",\"zae\",\"zaf\",\"zag\",\"zah\",\"zai\",\"zaj\",\"zak\",\"zal\",\"zam\",\"zao\",\"zap\",\"zaq\",\"zar\",\"zas\",\"zat\",\"zau\",\"zav\",\"zaw\",\"zax\",\"zay\",\"zaz\",\"zbc\",\"zbe\",\"zbl\",\"zbt\",\"zbw\",\"zca\",\"zch\",\"zdj\",\"zea\",\"zeg\",\"zeh\",\"zen\",\"zga\",\"zgb\",\"zgh\",\"zgm\",\"zgn\",\"zgr\",\"zhb\",\"zhd\",\"zhi\",\"zhn\",\"zhw\",\"zhx\",\"zia\",\"zib\",\"zik\",\"zil\",\"zim\",\"zin\",\"zir\",\"ziw\",\"ziz\",\"zka\",\"zkb\",\"zkd\",\"zkg\",\"zkh\",\"zkk\",\"zkn\",\"zko\",\"zkp\",\"zkr\",\"zkt\",\"zku\",\"zkv\",\"zkz\",\"zle\",\"zlj\",\"zlm\",\"zln\",\"zlq\",\"zls\",\"zlw\",\"zma\",\"zmb\",\"zmc\",\"zmd\",\"zme\",\"zmf\",\"zmg\",\"zmh\",\"zmi\",\"zmj\",\"zmk\",\"zml\",\"zmm\",\"zmn\",\"zmo\",\"zmp\",\"zmq\",\"zmr\",\"zms\",\"zmt\",\"zmu\",\"zmv\",\"zmw\",\"zmx\",\"zmy\",\"zmz\",\"zna\",\"znd\",\"zne\",\"zng\",\"znk\",\"zns\",\"zoc\",\"zoh\",\"zom\",\"zoo\",\"zoq\",\"zor\",\"zos\",\"zpa\",\"zpb\",\"zpc\",\"zpd\",\"zpe\",\"zpf\",\"zpg\",\"zph\",\"zpi\",\"zpj\",\"zpk\",\"zpl\",\"zpm\",\"zpn\",\"zpo\",\"zpp\",\"zpq\",\"zpr\",\"zps\",\"zpt\",\"zpu\",\"zpv\",\"zpw\",\"zpx\",\"zpy\",\"zpz\",\"zqe\",\"zra\",\"zrg\",\"zrn\",\"zro\",\"zrp\",\"zrs\",\"zsa\",\"zsk\",\"zsl\",\"zsm\",\"zsr\",\"zsu\",\"zte\",\"ztg\",\"ztl\",\"ztm\",\"ztn\",\"ztp\",\"ztq\",\"zts\",\"ztt\",\"ztu\",\"ztx\",\"zty\",\"zua\",\"zuh\",\"zum\",\"zun\",\"zuy\",\"zwa\",\"zxx\",\"zyb\",\"zyg\",\"zyj\",\"zyn\",\"zyp\",\"zza\",\"zzj\"],\nid:\"valid-lang\"}]},{id:\"image-alt\",selector:\"img, [role='img']\",tags:[\"cat.text-alternatives\",\"wcag2a\",\"wcag111\",\"section508\",\"section508.22.a\"],all:[],any:[\"has-alt\",\"aria-label\",\"aria-labelledby\",\"non-empty-title\",\"role-presentation\",\"role-none\"],none:[]},{id:\"image-redundant-alt\",selector:'button, [role=\"button\"], a[href], p, li, td, th',tags:[\"cat.text-alternatives\",\"best-practice\"],all:[],any:[],none:[\"duplicate-img-label\"]},{id:\"input-image-alt\",selector:'input[type=\"image\"]',tags:[\"cat.text-alternatives\",\"wcag2a\",\"wcag111\",\"section508\",\"section508.22.a\"],all:[],any:[\"non-empty-alt\",\"aria-label\",\"aria-labelledby\",\"non-empty-title\"],none:[]},{id:\"label-title-only\",selector:\"input:not([type='hidden']):not([type='image']):not([type='button']):not([type='submit']):not([type='reset']), select, textarea\",enabled:!1,tags:[\"cat.forms\",\"best-practice\"],all:[],any:[],none:[\"title-only\"]},{id:\"label\",selector:\"input:not([type='hidden']):not([type='image']):not([type='button']):not([type='submit']):not([type='reset']), select, textarea\",tags:[\"cat.forms\",\"wcag2a\",\"wcag332\",\"wcag131\",\"section508\",\"section508.22.n\"],all:[],any:[\"aria-label\",\"aria-labelledby\",\"implicit-label\",\"explicit-label\",\"non-empty-title\"],none:[\"help-same-as-label\",\"multiple-label\"]},{id:\"layout-table\",selector:\"table\",matches:function(a){return!axe.commons.table.isDataTable(a)},tags:[\"cat.semantics\",\"wcag2a\",\"wcag131\"],all:[],any:[],none:[\"has-th\",\"has-caption\",\"has-summary\"]},{id:\"link-in-text-block\",selector:\"a[href]:not([role]), *[role=link]\",matches:function(a){return!!axe.commons.text.sanitize(a.textContent)&&!!axe.commons.dom.isVisible(a,!1)&&axe.commons.dom.isInTextBlock(a)},excludeHidden:!1,enabled:!1,tags:[\"cat.color\",\"experimental\",\"wcag2a\",\"wcag141\"],all:[\"link-in-text-block\"],any:[],none:[]},{id:\"link-name\",selector:'a[href]:not([role=\"button\"]), [role=link][href]',tags:[\"cat.name-role-value\",\"wcag2a\",\"wcag111\",\"wcag412\",\"section508\",\"section508.22.a\"],all:[],any:[\"has-visible-text\",\"aria-label\",\"aria-labelledby\",\"role-presentation\",\"role-none\"],none:[\"focusable-no-name\"]},{id:\"list\",selector:\"ul:not([role]), ol:not([role])\",tags:[\"cat.structure\",\"wcag2a\",\"wcag131\"],all:[],any:[],none:[\"only-listitems\"]},{id:\"listitem\",selector:\"li:not([role])\",tags:[\"cat.structure\",\"wcag2a\",\"wcag131\"],all:[],any:[\"listitem\"],none:[]},{id:\"marquee\",selector:\"marquee\",excludeHidden:!1,tags:[\"cat.parsing\",\"wcag2a\",\"wcag222\"],all:[],any:[],none:[\"is-on-screen\"]},{id:\"meta-refresh\",selector:'meta[http-equiv=\"refresh\"]',excludeHidden:!1,tags:[\"cat.time\",\"wcag2a\",\"wcag2aaa\",\"wcag221\",\"wcag224\",\"wcag325\"],all:[],any:[\"meta-refresh\"],none:[]},{id:\"meta-viewport-large\",selector:'meta[name=\"viewport\"]',excludeHidden:!1,tags:[\"cat.sensory-and-visual-cues\",\"best-practice\"],all:[],any:[{options:{scaleMinimum:5,lowerBound:2},id:\"meta-viewport-large\"}],none:[]},{id:\"meta-viewport\",selector:'meta[name=\"viewport\"]',excludeHidden:!1,tags:[\"cat.sensory-and-visual-cues\",\"wcag2aa\",\"wcag144\"],all:[],any:[{options:{scaleMinimum:2},id:\"meta-viewport\"}],none:[]},{id:\"object-alt\",selector:\"object\",tags:[\"cat.text-alternatives\",\"wcag2a\",\"wcag111\",\"section508\",\"section508.22.a\"],all:[],any:[\"has-visible-text\",\"aria-label\",\"aria-labelledby\",\"non-empty-title\"],none:[]},{id:\"p-as-heading\",selector:\"p\",matches:function(a){var b=Array.from(a.parentNode.childNodes),c=a.textContent.trim();return!(0===c.length||(c.match(/[.!?:;](?![.!?:;])/g)||[]).length>=2)&&0!==b.slice(b.indexOf(a)+1).filter(function(a){return\"P\"===a.nodeName.toUpperCase()&&\"\"!==a.textContent.trim()}).length},tags:[\"cat.semantics\",\"wcag2a\",\"wcag131\",\"experimental\"],all:[{options:{margins:[{weight:150,italic:!0},{weight:150,size:1.15},{italic:!0,size:1.15},{size:1.4}]},id:\"p-as-heading\"}],any:[],none:[]},{id:\"radiogroup\",selector:\"input[type=radio][name]\",tags:[\"cat.forms\",\"best-practice\"],all:[],any:[\"group-labelledby\",\"fieldset\"],none:[]},{id:\"region\",selector:\"html\",pageLevel:!0,enabled:!1,tags:[\"cat.keyboard\",\"best-practice\"],all:[],any:[\"region\"],none:[]},{id:\"scope-attr-valid\",selector:\"td[scope], th[scope]\",enabled:!0,tags:[\"cat.tables\",\"best-practice\"],all:[\"html5-scope\",\"scope-value\"],any:[],none:[]},{id:\"server-side-image-map\",selector:\"img[ismap]\",tags:[\"cat.text-alternatives\",\"wcag2a\",\"wcag211\",\"section508\",\"section508.22.f\"],all:[],any:[],none:[\"exists\"]},{id:\"skip-link\",selector:\"a[href]\",pageLevel:!0,enabled:!1,tags:[\"cat.keyboard\",\"best-practice\"],all:[],any:[\"skip-link\"],none:[]},{id:\"tabindex\",selector:\"[tabindex]\",tags:[\"cat.keyboard\",\"best-practice\"],all:[],any:[\"tabindex\"],none:[]},{id:\"table-duplicate-name\",selector:\"table\",tags:[\"cat.tables\",\"best-practice\"],all:[],any:[],none:[\"same-caption-summary\"]},{id:\"table-fake-caption\",selector:\"table\",matches:function(a){return axe.commons.table.isDataTable(a)},tags:[\"cat.tables\",\"experimental\",\"wcag2a\",\"wcag131\",\"section508\",\"section508.22.g\"],all:[\"caption-faked\"],any:[],none:[]},{id:\"td-has-header\",selector:\"table\",matches:function(a){if(axe.commons.table.isDataTable(a)){var b=axe.commons.table.toArray(a);return b.length>=3&&b[0].length>=3&&b[1].length>=3&&b[2].length>=3}return!1},tags:[\"cat.tables\",\"experimental\",\"wcag2a\",\"wcag131\",\"section508\",\"section508.22.g\"],all:[\"td-has-header\"],any:[],none:[]},{id:\"td-headers-attr\",selector:\"table\",tags:[\"cat.tables\",\"wcag2a\",\"wcag131\",\"section508\",\"section508.22.g\"],all:[\"td-headers-attr\"],any:[],none:[]},{id:\"th-has-data-cells\",selector:\"table\",matches:function(a){return axe.commons.table.isDataTable(a)},tags:[\"cat.tables\",\"wcag2a\",\"wcag131\",\"section508\",\"section508.22.g\"],all:[\"th-has-data-cells\"],any:[],none:[]},{id:\"valid-lang\",selector:\"[lang]:not(html), [xml\\\\:lang]:not(html)\",tags:[\"cat.language\",\"wcag2aa\",\"wcag312\"],all:[],any:[],none:[{\noptions:[\"aa\",\"ab\",\"ae\",\"af\",\"ak\",\"am\",\"an\",\"ar\",\"as\",\"av\",\"ay\",\"az\",\"ba\",\"be\",\"bg\",\"bh\",\"bi\",\"bm\",\"bn\",\"bo\",\"br\",\"bs\",\"ca\",\"ce\",\"ch\",\"co\",\"cr\",\"cs\",\"cu\",\"cv\",\"cy\",\"da\",\"de\",\"dv\",\"dz\",\"ee\",\"el\",\"en\",\"eo\",\"es\",\"et\",\"eu\",\"fa\",\"ff\",\"fi\",\"fj\",\"fo\",\"fr\",\"fy\",\"ga\",\"gd\",\"gl\",\"gn\",\"gu\",\"gv\",\"ha\",\"he\",\"hi\",\"ho\",\"hr\",\"ht\",\"hu\",\"hy\",\"hz\",\"ia\",\"id\",\"ie\",\"ig\",\"ii\",\"ik\",\"in\",\"io\",\"is\",\"it\",\"iu\",\"iw\",\"ja\",\"ji\",\"jv\",\"jw\",\"ka\",\"kg\",\"ki\",\"kj\",\"kk\",\"kl\",\"km\",\"kn\",\"ko\",\"kr\",\"ks\",\"ku\",\"kv\",\"kw\",\"ky\",\"la\",\"lb\",\"lg\",\"li\",\"ln\",\"lo\",\"lt\",\"lu\",\"lv\",\"mg\",\"mh\",\"mi\",\"mk\",\"ml\",\"mn\",\"mo\",\"mr\",\"ms\",\"mt\",\"my\",\"na\",\"nb\",\"nd\",\"ne\",\"ng\",\"nl\",\"nn\",\"no\",\"nr\",\"nv\",\"ny\",\"oc\",\"oj\",\"om\",\"or\",\"os\",\"pa\",\"pi\",\"pl\",\"ps\",\"pt\",\"qu\",\"rm\",\"rn\",\"ro\",\"ru\",\"rw\",\"sa\",\"sc\",\"sd\",\"se\",\"sg\",\"sh\",\"si\",\"sk\",\"sl\",\"sm\",\"sn\",\"so\",\"sq\",\"sr\",\"ss\",\"st\",\"su\",\"sv\",\"sw\",\"ta\",\"te\",\"tg\",\"th\",\"ti\",\"tk\",\"tl\",\"tn\",\"to\",\"tr\",\"ts\",\"tt\",\"tw\",\"ty\",\"ug\",\"uk\",\"ur\",\"uz\",\"ve\",\"vi\",\"vo\",\"wa\",\"wo\",\"xh\",\"yi\",\"yo\",\"za\",\"zh\",\"zu\",\"aaa\",\"aab\",\"aac\",\"aad\",\"aae\",\"aaf\",\"aag\",\"aah\",\"aai\",\"aak\",\"aal\",\"aam\",\"aan\",\"aao\",\"aap\",\"aaq\",\"aas\",\"aat\",\"aau\",\"aav\",\"aaw\",\"aax\",\"aaz\",\"aba\",\"abb\",\"abc\",\"abd\",\"abe\",\"abf\",\"abg\",\"abh\",\"abi\",\"abj\",\"abl\",\"abm\",\"abn\",\"abo\",\"abp\",\"abq\",\"abr\",\"abs\",\"abt\",\"abu\",\"abv\",\"abw\",\"abx\",\"aby\",\"abz\",\"aca\",\"acb\",\"acd\",\"ace\",\"acf\",\"ach\",\"aci\",\"ack\",\"acl\",\"acm\",\"acn\",\"acp\",\"acq\",\"acr\",\"acs\",\"act\",\"acu\",\"acv\",\"acw\",\"acx\",\"acy\",\"acz\",\"ada\",\"adb\",\"add\",\"ade\",\"adf\",\"adg\",\"adh\",\"adi\",\"adj\",\"adl\",\"adn\",\"ado\",\"adp\",\"adq\",\"adr\",\"ads\",\"adt\",\"adu\",\"adw\",\"adx\",\"ady\",\"adz\",\"aea\",\"aeb\",\"aec\",\"aed\",\"aee\",\"aek\",\"ael\",\"aem\",\"aen\",\"aeq\",\"aer\",\"aes\",\"aeu\",\"aew\",\"aey\",\"aez\",\"afa\",\"afb\",\"afd\",\"afe\",\"afg\",\"afh\",\"afi\",\"afk\",\"afn\",\"afo\",\"afp\",\"afs\",\"aft\",\"afu\",\"afz\",\"aga\",\"agb\",\"agc\",\"agd\",\"age\",\"agf\",\"agg\",\"agh\",\"agi\",\"agj\",\"agk\",\"agl\",\"agm\",\"agn\",\"ago\",\"agp\",\"agq\",\"agr\",\"ags\",\"agt\",\"agu\",\"agv\",\"agw\",\"agx\",\"agy\",\"agz\",\"aha\",\"ahb\",\"ahg\",\"ahh\",\"ahi\",\"ahk\",\"ahl\",\"ahm\",\"ahn\",\"aho\",\"ahp\",\"ahr\",\"ahs\",\"aht\",\"aia\",\"aib\",\"aic\",\"aid\",\"aie\",\"aif\",\"aig\",\"aih\",\"aii\",\"aij\",\"aik\",\"ail\",\"aim\",\"ain\",\"aio\",\"aip\",\"aiq\",\"air\",\"ais\",\"ait\",\"aiw\",\"aix\",\"aiy\",\"aja\",\"ajg\",\"aji\",\"ajn\",\"ajp\",\"ajt\",\"aju\",\"ajw\",\"ajz\",\"akb\",\"akc\",\"akd\",\"ake\",\"akf\",\"akg\",\"akh\",\"aki\",\"akj\",\"akk\",\"akl\",\"akm\",\"ako\",\"akp\",\"akq\",\"akr\",\"aks\",\"akt\",\"aku\",\"akv\",\"akw\",\"akx\",\"aky\",\"akz\",\"ala\",\"alc\",\"ald\",\"ale\",\"alf\",\"alg\",\"alh\",\"ali\",\"alj\",\"alk\",\"all\",\"alm\",\"aln\",\"alo\",\"alp\",\"alq\",\"alr\",\"als\",\"alt\",\"alu\",\"alv\",\"alw\",\"alx\",\"aly\",\"alz\",\"ama\",\"amb\",\"amc\",\"ame\",\"amf\",\"amg\",\"ami\",\"amj\",\"amk\",\"aml\",\"amm\",\"amn\",\"amo\",\"amp\",\"amq\",\"amr\",\"ams\",\"amt\",\"amu\",\"amv\",\"amw\",\"amx\",\"amy\",\"amz\",\"ana\",\"anb\",\"anc\",\"and\",\"ane\",\"anf\",\"ang\",\"anh\",\"ani\",\"anj\",\"ank\",\"anl\",\"anm\",\"ann\",\"ano\",\"anp\",\"anq\",\"anr\",\"ans\",\"ant\",\"anu\",\"anv\",\"anw\",\"anx\",\"any\",\"anz\",\"aoa\",\"aob\",\"aoc\",\"aod\",\"aoe\",\"aof\",\"aog\",\"aoh\",\"aoi\",\"aoj\",\"aok\",\"aol\",\"aom\",\"aon\",\"aor\",\"aos\",\"aot\",\"aou\",\"aox\",\"aoz\",\"apa\",\"apb\",\"apc\",\"apd\",\"ape\",\"apf\",\"apg\",\"aph\",\"api\",\"apj\",\"apk\",\"apl\",\"apm\",\"apn\",\"apo\",\"app\",\"apq\",\"apr\",\"aps\",\"apt\",\"apu\",\"apv\",\"apw\",\"apx\",\"apy\",\"apz\",\"aqa\",\"aqc\",\"aqd\",\"aqg\",\"aql\",\"aqm\",\"aqn\",\"aqp\",\"aqr\",\"aqt\",\"aqz\",\"arb\",\"arc\",\"ard\",\"are\",\"arh\",\"ari\",\"arj\",\"ark\",\"arl\",\"arn\",\"aro\",\"arp\",\"arq\",\"arr\",\"ars\",\"art\",\"aru\",\"arv\",\"arw\",\"arx\",\"ary\",\"arz\",\"asa\",\"asb\",\"asc\",\"asd\",\"ase\",\"asf\",\"asg\",\"ash\",\"asi\",\"asj\",\"ask\",\"asl\",\"asn\",\"aso\",\"asp\",\"asq\",\"asr\",\"ass\",\"ast\",\"asu\",\"asv\",\"asw\",\"asx\",\"asy\",\"asz\",\"ata\",\"atb\",\"atc\",\"atd\",\"ate\",\"atg\",\"ath\",\"ati\",\"atj\",\"atk\",\"atl\",\"atm\",\"atn\",\"ato\",\"atp\",\"atq\",\"atr\",\"ats\",\"att\",\"atu\",\"atv\",\"atw\",\"atx\",\"aty\",\"atz\",\"aua\",\"aub\",\"auc\",\"aud\",\"aue\",\"auf\",\"aug\",\"auh\",\"aui\",\"auj\",\"auk\",\"aul\",\"aum\",\"aun\",\"auo\",\"aup\",\"auq\",\"aur\",\"aus\",\"aut\",\"auu\",\"auw\",\"aux\",\"auy\",\"auz\",\"avb\",\"avd\",\"avi\",\"avk\",\"avl\",\"avm\",\"avn\",\"avo\",\"avs\",\"avt\",\"avu\",\"avv\",\"awa\",\"awb\",\"awc\",\"awd\",\"awe\",\"awg\",\"awh\",\"awi\",\"awk\",\"awm\",\"awn\",\"awo\",\"awr\",\"aws\",\"awt\",\"awu\",\"awv\",\"aww\",\"awx\",\"awy\",\"axb\",\"axe\",\"axg\",\"axk\",\"axl\",\"axm\",\"axx\",\"aya\",\"ayb\",\"ayc\",\"ayd\",\"aye\",\"ayg\",\"ayh\",\"ayi\",\"ayk\",\"ayl\",\"ayn\",\"ayo\",\"ayp\",\"ayq\",\"ayr\",\"ays\",\"ayt\",\"ayu\",\"ayx\",\"ayy\",\"ayz\",\"aza\",\"azb\",\"azc\",\"azd\",\"azg\",\"azj\",\"azm\",\"azn\",\"azo\",\"azt\",\"azz\",\"baa\",\"bab\",\"bac\",\"bad\",\"bae\",\"baf\",\"bag\",\"bah\",\"bai\",\"baj\",\"bal\",\"ban\",\"bao\",\"bap\",\"bar\",\"bas\",\"bat\",\"bau\",\"bav\",\"baw\",\"bax\",\"bay\",\"baz\",\"bba\",\"bbb\",\"bbc\",\"bbd\",\"bbe\",\"bbf\",\"bbg\",\"bbh\",\"bbi\",\"bbj\",\"bbk\",\"bbl\",\"bbm\",\"bbn\",\"bbo\",\"bbp\",\"bbq\",\"bbr\",\"bbs\",\"bbt\",\"bbu\",\"bbv\",\"bbw\",\"bbx\",\"bby\",\"bbz\",\"bca\",\"bcb\",\"bcc\",\"bcd\",\"bce\",\"bcf\",\"bcg\",\"bch\",\"bci\",\"bcj\",\"bck\",\"bcl\",\"bcm\",\"bcn\",\"bco\",\"bcp\",\"bcq\",\"bcr\",\"bcs\",\"bct\",\"bcu\",\"bcv\",\"bcw\",\"bcy\",\"bcz\",\"bda\",\"bdb\",\"bdc\",\"bdd\",\"bde\",\"bdf\",\"bdg\",\"bdh\",\"bdi\",\"bdj\",\"bdk\",\"bdl\",\"bdm\",\"bdn\",\"bdo\",\"bdp\",\"bdq\",\"bdr\",\"bds\",\"bdt\",\"bdu\",\"bdv\",\"bdw\",\"bdx\",\"bdy\",\"bdz\",\"bea\",\"beb\",\"bec\",\"bed\",\"bee\",\"bef\",\"beg\",\"beh\",\"bei\",\"bej\",\"bek\",\"bem\",\"beo\",\"bep\",\"beq\",\"ber\",\"bes\",\"bet\",\"beu\",\"bev\",\"bew\",\"bex\",\"bey\",\"bez\",\"bfa\",\"bfb\",\"bfc\",\"bfd\",\"bfe\",\"bff\",\"bfg\",\"bfh\",\"bfi\",\"bfj\",\"bfk\",\"bfl\",\"bfm\",\"bfn\",\"bfo\",\"bfp\",\"bfq\",\"bfr\",\"bfs\",\"bft\",\"bfu\",\"bfw\",\"bfx\",\"bfy\",\"bfz\",\"bga\",\"bgb\",\"bgc\",\"bgd\",\"bge\",\"bgf\",\"bgg\",\"bgi\",\"bgj\",\"bgk\",\"bgl\",\"bgm\",\"bgn\",\"bgo\",\"bgp\",\"bgq\",\"bgr\",\"bgs\",\"bgt\",\"bgu\",\"bgv\",\"bgw\",\"bgx\",\"bgy\",\"bgz\",\"bha\",\"bhb\",\"bhc\",\"bhd\",\"bhe\",\"bhf\",\"bhg\",\"bhh\",\"bhi\",\"bhj\",\"bhk\",\"bhl\",\"bhm\",\"bhn\",\"bho\",\"bhp\",\"bhq\",\"bhr\",\"bhs\",\"bht\",\"bhu\",\"bhv\",\"bhw\",\"bhx\",\"bhy\",\"bhz\",\"bia\",\"bib\",\"bic\",\"bid\",\"bie\",\"bif\",\"big\",\"bij\",\"bik\",\"bil\",\"bim\",\"bin\",\"bio\",\"bip\",\"biq\",\"bir\",\"bit\",\"biu\",\"biv\",\"biw\",\"bix\",\"biy\",\"biz\",\"bja\",\"bjb\",\"bjc\",\"bjd\",\"bje\",\"bjf\",\"bjg\",\"bjh\",\"bji\",\"bjj\",\"bjk\",\"bjl\",\"bjm\",\"bjn\",\"bjo\",\"bjp\",\"bjq\",\"bjr\",\"bjs\",\"bjt\",\"bju\",\"bjv\",\"bjw\",\"bjx\",\"bjy\",\"bjz\",\"bka\",\"bkb\",\"bkc\",\"bkd\",\"bkf\",\"bkg\",\"bkh\",\"bki\",\"bkj\",\"bkk\",\"bkl\",\"bkm\",\"bkn\",\"bko\",\"bkp\",\"bkq\",\"bkr\",\"bks\",\"bkt\",\"bku\",\"bkv\",\"bkw\",\"bkx\",\"bky\",\"bkz\",\"bla\",\"blb\",\"blc\",\"bld\",\"ble\",\"blf\",\"blg\",\"blh\",\"bli\",\"blj\",\"blk\",\"bll\",\"blm\",\"bln\",\"blo\",\"blp\",\"blq\",\"blr\",\"bls\",\"blt\",\"blv\",\"blw\",\"blx\",\"bly\",\"blz\",\"bma\",\"bmb\",\"bmc\",\"bmd\",\"bme\",\"bmf\",\"bmg\",\"bmh\",\"bmi\",\"bmj\",\"bmk\",\"bml\",\"bmm\",\"bmn\",\"bmo\",\"bmp\",\"bmq\",\"bmr\",\"bms\",\"bmt\",\"bmu\",\"bmv\",\"bmw\",\"bmx\",\"bmy\",\"bmz\",\"bna\",\"bnb\",\"bnc\",\"bnd\",\"bne\",\"bnf\",\"bng\",\"bni\",\"bnj\",\"bnk\",\"bnl\",\"bnm\",\"bnn\",\"bno\",\"bnp\",\"bnq\",\"bnr\",\"bns\",\"bnt\",\"bnu\",\"bnv\",\"bnw\",\"bnx\",\"bny\",\"bnz\",\"boa\",\"bob\",\"boe\",\"bof\",\"bog\",\"boh\",\"boi\",\"boj\",\"bok\",\"bol\",\"bom\",\"bon\",\"boo\",\"bop\",\"boq\",\"bor\",\"bot\",\"bou\",\"bov\",\"bow\",\"box\",\"boy\",\"boz\",\"bpa\",\"bpb\",\"bpd\",\"bpg\",\"bph\",\"bpi\",\"bpj\",\"bpk\",\"bpl\",\"bpm\",\"bpn\",\"bpo\",\"bpp\",\"bpq\",\"bpr\",\"bps\",\"bpt\",\"bpu\",\"bpv\",\"bpw\",\"bpx\",\"bpy\",\"bpz\",\"bqa\",\"bqb\",\"bqc\",\"bqd\",\"bqf\",\"bqg\",\"bqh\",\"bqi\",\"bqj\",\"bqk\",\"bql\",\"bqm\",\"bqn\",\"bqo\",\"bqp\",\"bqq\",\"bqr\",\"bqs\",\"bqt\",\"bqu\",\"bqv\",\"bqw\",\"bqx\",\"bqy\",\"bqz\",\"bra\",\"brb\",\"brc\",\"brd\",\"brf\",\"brg\",\"brh\",\"bri\",\"brj\",\"brk\",\"brl\",\"brm\",\"brn\",\"bro\",\"brp\",\"brq\",\"brr\",\"brs\",\"brt\",\"bru\",\"brv\",\"brw\",\"brx\",\"bry\",\"brz\",\"bsa\",\"bsb\",\"bsc\",\"bse\",\"bsf\",\"bsg\",\"bsh\",\"bsi\",\"bsj\",\"bsk\",\"bsl\",\"bsm\",\"bsn\",\"bso\",\"bsp\",\"bsq\",\"bsr\",\"bss\",\"bst\",\"bsu\",\"bsv\",\"bsw\",\"bsx\",\"bsy\",\"bta\",\"btb\",\"btc\",\"btd\",\"bte\",\"btf\",\"btg\",\"bth\",\"bti\",\"btj\",\"btk\",\"btl\",\"btm\",\"btn\",\"bto\",\"btp\",\"btq\",\"btr\",\"bts\",\"btt\",\"btu\",\"btv\",\"btw\",\"btx\",\"bty\",\"btz\",\"bua\",\"bub\",\"buc\",\"bud\",\"bue\",\"buf\",\"bug\",\"buh\",\"bui\",\"buj\",\"buk\",\"bum\",\"bun\",\"buo\",\"bup\",\"buq\",\"bus\",\"but\",\"buu\",\"buv\",\"buw\",\"bux\",\"buy\",\"buz\",\"bva\",\"bvb\",\"bvc\",\"bvd\",\"bve\",\"bvf\",\"bvg\",\"bvh\",\"bvi\",\"bvj\",\"bvk\",\"bvl\",\"bvm\",\"bvn\",\"bvo\",\"bvp\",\"bvq\",\"bvr\",\"bvt\",\"bvu\",\"bvv\",\"bvw\",\"bvx\",\"bvy\",\"bvz\",\"bwa\",\"bwb\",\"bwc\",\"bwd\",\"bwe\",\"bwf\",\"bwg\",\"bwh\",\"bwi\",\"bwj\",\"bwk\",\"bwl\",\"bwm\",\"bwn\",\"bwo\",\"bwp\",\"bwq\",\"bwr\",\"bws\",\"bwt\",\"bwu\",\"bww\",\"bwx\",\"bwy\",\"bwz\",\"bxa\",\"bxb\",\"bxc\",\"bxd\",\"bxe\",\"bxf\",\"bxg\",\"bxh\",\"bxi\",\"bxj\",\"bxk\",\"bxl\",\"bxm\",\"bxn\",\"bxo\",\"bxp\",\"bxq\",\"bxr\",\"bxs\",\"bxu\",\"bxv\",\"bxw\",\"bxx\",\"bxz\",\"bya\",\"byb\",\"byc\",\"byd\",\"bye\",\"byf\",\"byg\",\"byh\",\"byi\",\"byj\",\"byk\",\"byl\",\"bym\",\"byn\",\"byo\",\"byp\",\"byq\",\"byr\",\"bys\",\"byt\",\"byv\",\"byw\",\"byx\",\"byy\",\"byz\",\"bza\",\"bzb\",\"bzc\",\"bzd\",\"bze\",\"bzf\",\"bzg\",\"bzh\",\"bzi\",\"bzj\",\"bzk\",\"bzl\",\"bzm\",\"bzn\",\"bzo\",\"bzp\",\"bzq\",\"bzr\",\"bzs\",\"bzt\",\"bzu\",\"bzv\",\"bzw\",\"bzx\",\"bzy\",\"bzz\",\"caa\",\"cab\",\"cac\",\"cad\",\"cae\",\"caf\",\"cag\",\"cah\",\"cai\",\"caj\",\"cak\",\"cal\",\"cam\",\"can\",\"cao\",\"cap\",\"caq\",\"car\",\"cas\",\"cau\",\"cav\",\"caw\",\"cax\",\"cay\",\"caz\",\"cba\",\"cbb\",\"cbc\",\"cbd\",\"cbe\",\"cbg\",\"cbh\",\"cbi\",\"cbj\",\"cbk\",\"cbl\",\"cbn\",\"cbo\",\"cbq\",\"cbr\",\"cbs\",\"cbt\",\"cbu\",\"cbv\",\"cbw\",\"cby\",\"cca\",\"ccc\",\"ccd\",\"cce\",\"ccg\",\"cch\",\"ccj\",\"ccl\",\"ccm\",\"ccn\",\"cco\",\"ccp\",\"ccq\",\"ccr\",\"ccs\",\"cda\",\"cdc\",\"cdd\",\"cde\",\"cdf\",\"cdg\",\"cdh\",\"cdi\",\"cdj\",\"cdm\",\"cdn\",\"cdo\",\"cdr\",\"cds\",\"cdy\",\"cdz\",\"cea\",\"ceb\",\"ceg\",\"cek\",\"cel\",\"cen\",\"cet\",\"cfa\",\"cfd\",\"cfg\",\"cfm\",\"cga\",\"cgc\",\"cgg\",\"cgk\",\"chb\",\"chc\",\"chd\",\"chf\",\"chg\",\"chh\",\"chj\",\"chk\",\"chl\",\"chm\",\"chn\",\"cho\",\"chp\",\"chq\",\"chr\",\"cht\",\"chw\",\"chx\",\"chy\",\"chz\",\"cia\",\"cib\",\"cic\",\"cid\",\"cie\",\"cih\",\"cik\",\"cim\",\"cin\",\"cip\",\"cir\",\"ciw\",\"ciy\",\"cja\",\"cje\",\"cjh\",\"cji\",\"cjk\",\"cjm\",\"cjn\",\"cjo\",\"cjp\",\"cjr\",\"cjs\",\"cjv\",\"cjy\",\"cka\",\"ckb\",\"ckh\",\"ckl\",\"ckn\",\"cko\",\"ckq\",\"ckr\",\"cks\",\"ckt\",\"cku\",\"ckv\",\"ckx\",\"cky\",\"ckz\",\"cla\",\"clc\",\"cld\",\"cle\",\"clh\",\"cli\",\"clj\",\"clk\",\"cll\",\"clm\",\"clo\",\"clt\",\"clu\",\"clw\",\"cly\",\"cma\",\"cmc\",\"cme\",\"cmg\",\"cmi\",\"cmk\",\"cml\",\"cmm\",\"cmn\",\"cmo\",\"cmr\",\"cms\",\"cmt\",\"cna\",\"cnb\",\"cnc\",\"cng\",\"cnh\",\"cni\",\"cnk\",\"cnl\",\"cno\",\"cns\",\"cnt\",\"cnu\",\"cnw\",\"cnx\",\"coa\",\"cob\",\"coc\",\"cod\",\"coe\",\"cof\",\"cog\",\"coh\",\"coj\",\"cok\",\"col\",\"com\",\"con\",\"coo\",\"cop\",\"coq\",\"cot\",\"cou\",\"cov\",\"cow\",\"cox\",\"coy\",\"coz\",\"cpa\",\"cpb\",\"cpc\",\"cpe\",\"cpf\",\"cpg\",\"cpi\",\"cpn\",\"cpo\",\"cpp\",\"cps\",\"cpu\",\"cpx\",\"cpy\",\"cqd\",\"cqu\",\"cra\",\"crb\",\"crc\",\"crd\",\"crf\",\"crg\",\"crh\",\"cri\",\"crj\",\"crk\",\"crl\",\"crm\",\"crn\",\"cro\",\"crp\",\"crq\",\"crr\",\"crs\",\"crt\",\"crv\",\"crw\",\"crx\",\"cry\",\"crz\",\"csa\",\"csb\",\"csc\",\"csd\",\"cse\",\"csf\",\"csg\",\"csh\",\"csi\",\"csj\",\"csk\",\"csl\",\"csm\",\"csn\",\"cso\",\"csq\",\"csr\",\"css\",\"cst\",\"csu\",\"csv\",\"csw\",\"csy\",\"csz\",\"cta\",\"ctc\",\"ctd\",\"cte\",\"ctg\",\"cth\",\"ctl\",\"ctm\",\"ctn\",\"cto\",\"ctp\",\"cts\",\"ctt\",\"ctu\",\"ctz\",\"cua\",\"cub\",\"cuc\",\"cug\",\"cuh\",\"cui\",\"cuj\",\"cuk\",\"cul\",\"cum\",\"cuo\",\"cup\",\"cuq\",\"cur\",\"cus\",\"cut\",\"cuu\",\"cuv\",\"cuw\",\"cux\",\"cvg\",\"cvn\",\"cwa\",\"cwb\",\"cwd\",\"cwe\",\"cwg\",\"cwt\",\"cya\",\"cyb\",\"cyo\",\"czh\",\"czk\",\"czn\",\"czo\",\"czt\",\"daa\",\"dac\",\"dad\",\"dae\",\"daf\",\"dag\",\"dah\",\"dai\",\"daj\",\"dak\",\"dal\",\"dam\",\"dao\",\"dap\",\"daq\",\"dar\",\"das\",\"dau\",\"dav\",\"daw\",\"dax\",\"day\",\"daz\",\"dba\",\"dbb\",\"dbd\",\"dbe\",\"dbf\",\"dbg\",\"dbi\",\"dbj\",\"dbl\",\"dbm\",\"dbn\",\"dbo\",\"dbp\",\"dbq\",\"dbr\",\"dbt\",\"dbu\",\"dbv\",\"dbw\",\"dby\",\"dcc\",\"dcr\",\"dda\",\"ddd\",\"dde\",\"ddg\",\"ddi\",\"ddj\",\"ddn\",\"ddo\",\"ddr\",\"dds\",\"ddw\",\"dec\",\"ded\",\"dee\",\"def\",\"deg\",\"deh\",\"dei\",\"dek\",\"del\",\"dem\",\"den\",\"dep\",\"deq\",\"der\",\"des\",\"dev\",\"dez\",\"dga\",\"dgb\",\"dgc\",\"dgd\",\"dge\",\"dgg\",\"dgh\",\"dgi\",\"dgk\",\"dgl\",\"dgn\",\"dgo\",\"dgr\",\"dgs\",\"dgt\",\"dgu\",\"dgw\",\"dgx\",\"dgz\",\"dha\",\"dhd\",\"dhg\",\"dhi\",\"dhl\",\"dhm\",\"dhn\",\"dho\",\"dhr\",\"dhs\",\"dhu\",\"dhv\",\"dhw\",\"dhx\",\"dia\",\"dib\",\"dic\",\"did\",\"dif\",\"dig\",\"dih\",\"dii\",\"dij\",\"dik\",\"dil\",\"dim\",\"din\",\"dio\",\"dip\",\"diq\",\"dir\",\"dis\",\"dit\",\"diu\",\"diw\",\"dix\",\"diy\",\"diz\",\"dja\",\"djb\",\"djc\",\"djd\",\"dje\",\"djf\",\"dji\",\"djj\",\"djk\",\"djl\",\"djm\",\"djn\",\"djo\",\"djr\",\"dju\",\"djw\",\"dka\",\"dkk\",\"dkl\",\"dkr\",\"dks\",\"dkx\",\"dlg\",\"dlk\",\"dlm\",\"dln\",\"dma\",\"dmb\",\"dmc\",\"dmd\",\"dme\",\"dmg\",\"dmk\",\"dml\",\"dmm\",\"dmn\",\"dmo\",\"dmr\",\"dms\",\"dmu\",\"dmv\",\"dmw\",\"dmx\",\"dmy\",\"dna\",\"dnd\",\"dne\",\"dng\",\"dni\",\"dnj\",\"dnk\",\"dnn\",\"dnr\",\"dnt\",\"dnu\",\"dnv\",\"dnw\",\"dny\",\"doa\",\"dob\",\"doc\",\"doe\",\"dof\",\"doh\",\"doi\",\"dok\",\"dol\",\"don\",\"doo\",\"dop\",\"doq\",\"dor\",\"dos\",\"dot\",\"dov\",\"dow\",\"dox\",\"doy\",\"doz\",\"dpp\",\"dra\",\"drb\",\"drc\",\"drd\",\"dre\",\"drg\",\"drh\",\"dri\",\"drl\",\"drn\",\"dro\",\"drq\",\"drr\",\"drs\",\"drt\",\"dru\",\"drw\",\"dry\",\"dsb\",\"dse\",\"dsh\",\"dsi\",\"dsl\",\"dsn\",\"dso\",\"dsq\",\"dta\",\"dtb\",\"dtd\",\"dth\",\"dti\",\"dtk\",\"dtm\",\"dtn\",\"dto\",\"dtp\",\"dtr\",\"dts\",\"dtt\",\"dtu\",\"dty\",\"dua\",\"dub\",\"duc\",\"dud\",\"due\",\"duf\",\"dug\",\"duh\",\"dui\",\"duj\",\"duk\",\"dul\",\"dum\",\"dun\",\"duo\",\"dup\",\"duq\",\"dur\",\"dus\",\"duu\",\"duv\",\"duw\",\"dux\",\"duy\",\"duz\",\"dva\",\"dwa\",\"dwl\",\"dwr\",\"dws\",\"dwu\",\"dww\",\"dwy\",\"dya\",\"dyb\",\"dyd\",\"dyg\",\"dyi\",\"dym\",\"dyn\",\"dyo\",\"dyu\",\"dyy\",\"dza\",\"dzd\",\"dze\",\"dzg\",\"dzl\",\"dzn\",\"eaa\",\"ebg\",\"ebk\",\"ebo\",\"ebr\",\"ebu\",\"ecr\",\"ecs\",\"ecy\",\"eee\",\"efa\",\"efe\",\"efi\",\"ega\",\"egl\",\"ego\",\"egx\",\"egy\",\"ehu\",\"eip\",\"eit\",\"eiv\",\"eja\",\"eka\",\"ekc\",\"eke\",\"ekg\",\"eki\",\"ekk\",\"ekl\",\"ekm\",\"eko\",\"ekp\",\"ekr\",\"eky\",\"ele\",\"elh\",\"eli\",\"elk\",\"elm\",\"elo\",\"elp\",\"elu\",\"elx\",\"ema\",\"emb\",\"eme\",\"emg\",\"emi\",\"emk\",\"emm\",\"emn\",\"emo\",\"emp\",\"ems\",\"emu\",\"emw\",\"emx\",\"emy\",\"ena\",\"enb\",\"enc\",\"end\",\"enf\",\"enh\",\"enl\",\"enm\",\"enn\",\"eno\",\"enq\",\"enr\",\"enu\",\"env\",\"enw\",\"enx\",\"eot\",\"epi\",\"era\",\"erg\",\"erh\",\"eri\",\"erk\",\"ero\",\"err\",\"ers\",\"ert\",\"erw\",\"ese\",\"esg\",\"esh\",\"esi\",\"esk\",\"esl\",\"esm\",\"esn\",\"eso\",\"esq\",\"ess\",\"esu\",\"esx\",\"esy\",\"etb\",\"etc\",\"eth\",\"etn\",\"eto\",\"etr\",\"ets\",\"ett\",\"etu\",\"etx\",\"etz\",\"euq\",\"eve\",\"evh\",\"evn\",\"ewo\",\"ext\",\"eya\",\"eyo\",\"eza\",\"eze\",\"faa\",\"fab\",\"fad\",\"faf\",\"fag\",\"fah\",\"fai\",\"faj\",\"fak\",\"fal\",\"fam\",\"fan\",\"fap\",\"far\",\"fat\",\"fau\",\"fax\",\"fay\",\"faz\",\"fbl\",\"fcs\",\"fer\",\"ffi\",\"ffm\",\"fgr\",\"fia\",\"fie\",\"fil\",\"fip\",\"fir\",\"fit\",\"fiu\",\"fiw\",\"fkk\",\"fkv\",\"fla\",\"flh\",\"fli\",\"fll\",\"fln\",\"flr\",\"fly\",\"fmp\",\"fmu\",\"fnb\",\"fng\",\"fni\",\"fod\",\"foi\",\"fom\",\"fon\",\"for\",\"fos\",\"fox\",\"fpe\",\"fqs\",\"frc\",\"frd\",\"frk\",\"frm\",\"fro\",\"frp\",\"frq\",\"frr\",\"frs\",\"frt\",\"fse\",\"fsl\",\"fss\",\"fub\",\"fuc\",\"fud\",\"fue\",\"fuf\",\"fuh\",\"fui\",\"fuj\",\"fum\",\"fun\",\"fuq\",\"fur\",\"fut\",\"fuu\",\"fuv\",\"fuy\",\"fvr\",\"fwa\",\"fwe\",\"gaa\",\"gab\",\"gac\",\"gad\",\"gae\",\"gaf\",\"gag\",\"gah\",\"gai\",\"gaj\",\"gak\",\"gal\",\"gam\",\"gan\",\"gao\",\"gap\",\"gaq\",\"gar\",\"gas\",\"gat\",\"gau\",\"gav\",\"gaw\",\"gax\",\"gay\",\"gaz\",\"gba\",\"gbb\",\"gbc\",\"gbd\",\"gbe\",\"gbf\",\"gbg\",\"gbh\",\"gbi\",\"gbj\",\"gbk\",\"gbl\",\"gbm\",\"gbn\",\"gbo\",\"gbp\",\"gbq\",\"gbr\",\"gbs\",\"gbu\",\"gbv\",\"gbw\",\"gbx\",\"gby\",\"gbz\",\"gcc\",\"gcd\",\"gce\",\"gcf\",\"gcl\",\"gcn\",\"gcr\",\"gct\",\"gda\",\"gdb\",\"gdc\",\"gdd\",\"gde\",\"gdf\",\"gdg\",\"gdh\",\"gdi\",\"gdj\",\"gdk\",\"gdl\",\"gdm\",\"gdn\",\"gdo\",\"gdq\",\"gdr\",\"gds\",\"gdt\",\"gdu\",\"gdx\",\"gea\",\"geb\",\"gec\",\"ged\",\"geg\",\"geh\",\"gei\",\"gej\",\"gek\",\"gel\",\"gem\",\"geq\",\"ges\",\"gev\",\"gew\",\"gex\",\"gey\",\"gez\",\"gfk\",\"gft\",\"gfx\",\"gga\",\"ggb\",\"ggd\",\"gge\",\"ggg\",\"ggk\",\"ggl\",\"ggn\",\"ggo\",\"ggr\",\"ggt\",\"ggu\",\"ggw\",\"gha\",\"ghc\",\"ghe\",\"ghh\",\"ghk\",\"ghl\",\"ghn\",\"gho\",\"ghr\",\"ghs\",\"ght\",\"gia\",\"gib\",\"gic\",\"gid\",\"gig\",\"gih\",\"gil\",\"gim\",\"gin\",\"gio\",\"gip\",\"giq\",\"gir\",\"gis\",\"git\",\"giu\",\"giw\",\"gix\",\"giy\",\"giz\",\"gji\",\"gjk\",\"gjm\",\"gjn\",\"gjr\",\"gju\",\"gka\",\"gke\",\"gkn\",\"gko\",\"gkp\",\"gku\",\"glc\",\"gld\",\"glh\",\"gli\",\"glj\",\"glk\",\"gll\",\"glo\",\"glr\",\"glu\",\"glw\",\"gly\",\"gma\",\"gmb\",\"gmd\",\"gme\",\"gmg\",\"gmh\",\"gml\",\"gmm\",\"gmn\",\"gmq\",\"gmu\",\"gmv\",\"gmw\",\"gmx\",\"gmy\",\"gmz\",\"gna\",\"gnb\",\"gnc\",\"gnd\",\"gne\",\"gng\",\"gnh\",\"gni\",\"gnk\",\"gnl\",\"gnm\",\"gnn\",\"gno\",\"gnq\",\"gnr\",\"gnt\",\"gnu\",\"gnw\",\"gnz\",\"goa\",\"gob\",\"goc\",\"god\",\"goe\",\"gof\",\"gog\",\"goh\",\"goi\",\"goj\",\"gok\",\"gol\",\"gom\",\"gon\",\"goo\",\"gop\",\"goq\",\"gor\",\"gos\",\"got\",\"gou\",\"gow\",\"gox\",\"goy\",\"goz\",\"gpa\",\"gpe\",\"gpn\",\"gqa\",\"gqi\",\"gqn\",\"gqr\",\"gqu\",\"gra\",\"grb\",\"grc\",\"grd\",\"grg\",\"grh\",\"gri\",\"grj\",\"grk\",\"grm\",\"gro\",\"grq\",\"grr\",\"grs\",\"grt\",\"gru\",\"grv\",\"grw\",\"grx\",\"gry\",\"grz\",\"gse\",\"gsg\",\"gsl\",\"gsm\",\"gsn\",\"gso\",\"gsp\",\"gss\",\"gsw\",\"gta\",\"gti\",\"gtu\",\"gua\",\"gub\",\"guc\",\"gud\",\"gue\",\"guf\",\"gug\",\"guh\",\"gui\",\"guk\",\"gul\",\"gum\",\"gun\",\"guo\",\"gup\",\"guq\",\"gur\",\"gus\",\"gut\",\"guu\",\"guv\",\"guw\",\"gux\",\"guz\",\"gva\",\"gvc\",\"gve\",\"gvf\",\"gvj\",\"gvl\",\"gvm\",\"gvn\",\"gvo\",\"gvp\",\"gvr\",\"gvs\",\"gvy\",\"gwa\",\"gwb\",\"gwc\",\"gwd\",\"gwe\",\"gwf\",\"gwg\",\"gwi\",\"gwj\",\"gwm\",\"gwn\",\"gwr\",\"gwt\",\"gwu\",\"gww\",\"gwx\",\"gxx\",\"gya\",\"gyb\",\"gyd\",\"gye\",\"gyf\",\"gyg\",\"gyi\",\"gyl\",\"gym\",\"gyn\",\"gyr\",\"gyy\",\"gza\",\"gzi\",\"gzn\",\"haa\",\"hab\",\"hac\",\"had\",\"hae\",\"haf\",\"hag\",\"hah\",\"hai\",\"haj\",\"hak\",\"hal\",\"ham\",\"han\",\"hao\",\"hap\",\"haq\",\"har\",\"has\",\"hav\",\"haw\",\"hax\",\"hay\",\"haz\",\"hba\",\"hbb\",\"hbn\",\"hbo\",\"hbu\",\"hca\",\"hch\",\"hdn\",\"hds\",\"hdy\",\"hea\",\"hed\",\"heg\",\"heh\",\"hei\",\"hem\",\"hgm\",\"hgw\",\"hhi\",\"hhr\",\"hhy\",\"hia\",\"hib\",\"hid\",\"hif\",\"hig\",\"hih\",\"hii\",\"hij\",\"hik\",\"hil\",\"him\",\"hio\",\"hir\",\"hit\",\"hiw\",\"hix\",\"hji\",\"hka\",\"hke\",\"hkk\",\"hks\",\"hla\",\"hlb\",\"hld\",\"hle\",\"hlt\",\"hlu\",\"hma\",\"hmb\",\"hmc\",\"hmd\",\"hme\",\"hmf\",\"hmg\",\"hmh\",\"hmi\",\"hmj\",\"hmk\",\"hml\",\"hmm\",\"hmn\",\"hmp\",\"hmq\",\"hmr\",\"hms\",\"hmt\",\"hmu\",\"hmv\",\"hmw\",\"hmx\",\"hmy\",\"hmz\",\"hna\",\"hnd\",\"hne\",\"hnh\",\"hni\",\"hnj\",\"hnn\",\"hno\",\"hns\",\"hnu\",\"hoa\",\"hob\",\"hoc\",\"hod\",\"hoe\",\"hoh\",\"hoi\",\"hoj\",\"hok\",\"hol\",\"hom\",\"hoo\",\"hop\",\"hor\",\"hos\",\"hot\",\"hov\",\"how\",\"hoy\",\"hoz\",\"hpo\",\"hps\",\"hra\",\"hrc\",\"hre\",\"hrk\",\"hrm\",\"hro\",\"hrp\",\"hrr\",\"hrt\",\"hru\",\"hrw\",\"hrx\",\"hrz\",\"hsb\",\"hsh\",\"hsl\",\"hsn\",\"hss\",\"hti\",\"hto\",\"hts\",\"htu\",\"htx\",\"hub\",\"huc\",\"hud\",\"hue\",\"huf\",\"hug\",\"huh\",\"hui\",\"huj\",\"huk\",\"hul\",\"hum\",\"huo\",\"hup\",\"huq\",\"hur\",\"hus\",\"hut\",\"huu\",\"huv\",\"huw\",\"hux\",\"huy\",\"huz\",\"hvc\",\"hve\",\"hvk\",\"hvn\",\"hvv\",\"hwa\",\"hwc\",\"hwo\",\"hya\",\"hyx\",\"iai\",\"ian\",\"iap\",\"iar\",\"iba\",\"ibb\",\"ibd\",\"ibe\",\"ibg\",\"ibi\",\"ibl\",\"ibm\",\"ibn\",\"ibr\",\"ibu\",\"iby\",\"ica\",\"ich\",\"icl\",\"icr\",\"ida\",\"idb\",\"idc\",\"idd\",\"ide\",\"idi\",\"idr\",\"ids\",\"idt\",\"idu\",\"ifa\",\"ifb\",\"ife\",\"iff\",\"ifk\",\"ifm\",\"ifu\",\"ify\",\"igb\",\"ige\",\"igg\",\"igl\",\"igm\",\"ign\",\"igo\",\"igs\",\"igw\",\"ihb\",\"ihi\",\"ihp\",\"ihw\",\"iin\",\"iir\",\"ijc\",\"ije\",\"ijj\",\"ijn\",\"ijo\",\"ijs\",\"ike\",\"iki\",\"ikk\",\"ikl\",\"iko\",\"ikp\",\"ikr\",\"iks\",\"ikt\",\"ikv\",\"ikw\",\"ikx\",\"ikz\",\"ila\",\"ilb\",\"ilg\",\"ili\",\"ilk\",\"ill\",\"ilm\",\"ilo\",\"ilp\",\"ils\",\"ilu\",\"ilv\",\"ilw\",\"ima\",\"ime\",\"imi\",\"iml\",\"imn\",\"imo\",\"imr\",\"ims\",\"imy\",\"inb\",\"inc\",\"ine\",\"ing\",\"inh\",\"inj\",\"inl\",\"inm\",\"inn\",\"ino\",\"inp\",\"ins\",\"int\",\"inz\",\"ior\",\"iou\",\"iow\",\"ipi\",\"ipo\",\"iqu\",\"iqw\",\"ira\",\"ire\",\"irh\",\"iri\",\"irk\",\"irn\",\"iro\",\"irr\",\"iru\",\"irx\",\"iry\",\"isa\",\"isc\",\"isd\",\"ise\",\"isg\",\"ish\",\"isi\",\"isk\",\"ism\",\"isn\",\"iso\",\"isr\",\"ist\",\"isu\",\"itb\",\"itc\",\"itd\",\"ite\",\"iti\",\"itk\",\"itl\",\"itm\",\"ito\",\"itr\",\"its\",\"itt\",\"itv\",\"itw\",\"itx\",\"ity\",\"itz\",\"ium\",\"ivb\",\"ivv\",\"iwk\",\"iwm\",\"iwo\",\"iws\",\"ixc\",\"ixl\",\"iya\",\"iyo\",\"iyx\",\"izh\",\"izi\",\"izr\",\"izz\",\"jaa\",\"jab\",\"jac\",\"jad\",\"jae\",\"jaf\",\"jah\",\"jaj\",\"jak\",\"jal\",\"jam\",\"jan\",\"jao\",\"jaq\",\"jar\",\"jas\",\"jat\",\"jau\",\"jax\",\"jay\",\"jaz\",\"jbe\",\"jbi\",\"jbj\",\"jbk\",\"jbn\",\"jbo\",\"jbr\",\"jbt\",\"jbu\",\"jbw\",\"jcs\",\"jct\",\"jda\",\"jdg\",\"jdt\",\"jeb\",\"jee\",\"jeg\",\"jeh\",\"jei\",\"jek\",\"jel\",\"jen\",\"jer\",\"jet\",\"jeu\",\"jgb\",\"jge\",\"jgk\",\"jgo\",\"jhi\",\"jhs\",\"jia\",\"jib\",\"jic\",\"jid\",\"jie\",\"jig\",\"jih\",\"jii\",\"jil\",\"jim\",\"jio\",\"jiq\",\"jit\",\"jiu\",\"jiv\",\"jiy\",\"jje\",\"jjr\",\"jka\",\"jkm\",\"jko\",\"jkp\",\"jkr\",\"jku\",\"jle\",\"jls\",\"jma\",\"jmb\",\"jmc\",\"jmd\",\"jmi\",\"jml\",\"jmn\",\"jmr\",\"jms\",\"jmw\",\"jmx\",\"jna\",\"jnd\",\"jng\",\"jni\",\"jnj\",\"jnl\",\"jns\",\"job\",\"jod\",\"jog\",\"jor\",\"jos\",\"jow\",\"jpa\",\"jpr\",\"jpx\",\"jqr\",\"jra\",\"jrb\",\"jrr\",\"jrt\",\"jru\",\"jsl\",\"jua\",\"jub\",\"juc\",\"jud\",\"juh\",\"jui\",\"juk\",\"jul\",\"jum\",\"jun\",\"juo\",\"jup\",\"jur\",\"jus\",\"jut\",\"juu\",\"juw\",\"juy\",\"jvd\",\"jvn\",\"jwi\",\"jya\",\"jye\",\"jyy\",\"kaa\",\"kab\",\"kac\",\"kad\",\"kae\",\"kaf\",\"kag\",\"kah\",\"kai\",\"kaj\",\"kak\",\"kam\",\"kao\",\"kap\",\"kaq\",\"kar\",\"kav\",\"kaw\",\"kax\",\"kay\",\"kba\",\"kbb\",\"kbc\",\"kbd\",\"kbe\",\"kbf\",\"kbg\",\"kbh\",\"kbi\",\"kbj\",\"kbk\",\"kbl\",\"kbm\",\"kbn\",\"kbo\",\"kbp\",\"kbq\",\"kbr\",\"kbs\",\"kbt\",\"kbu\",\"kbv\",\"kbw\",\"kbx\",\"kby\",\"kbz\",\"kca\",\"kcb\",\"kcc\",\"kcd\",\"kce\",\"kcf\",\"kcg\",\"kch\",\"kci\",\"kcj\",\"kck\",\"kcl\",\"kcm\",\"kcn\",\"kco\",\"kcp\",\"kcq\",\"kcr\",\"kcs\",\"kct\",\"kcu\",\"kcv\",\"kcw\",\"kcx\",\"kcy\",\"kcz\",\"kda\",\"kdc\",\"kdd\",\"kde\",\"kdf\",\"kdg\",\"kdh\",\"kdi\",\"kdj\",\"kdk\",\"kdl\",\"kdm\",\"kdn\",\"kdo\",\"kdp\",\"kdq\",\"kdr\",\"kdt\",\"kdu\",\"kdv\",\"kdw\",\"kdx\",\"kdy\",\"kdz\",\"kea\",\"keb\",\"kec\",\"ked\",\"kee\",\"kef\",\"keg\",\"keh\",\"kei\",\"kej\",\"kek\",\"kel\",\"kem\",\"ken\",\"keo\",\"kep\",\"keq\",\"ker\",\"kes\",\"ket\",\"keu\",\"kev\",\"kew\",\"kex\",\"key\",\"kez\",\"kfa\",\"kfb\",\"kfc\",\"kfd\",\"kfe\",\"kff\",\"kfg\",\"kfh\",\"kfi\",\"kfj\",\"kfk\",\"kfl\",\"kfm\",\"kfn\",\"kfo\",\"kfp\",\"kfq\",\"kfr\",\"kfs\",\"kft\",\"kfu\",\"kfv\",\"kfw\",\"kfx\",\"kfy\",\"kfz\",\"kga\",\"kgb\",\"kgc\",\"kgd\",\"kge\",\"kgf\",\"kgg\",\"kgh\",\"kgi\",\"kgj\",\"kgk\",\"kgl\",\"kgm\",\"kgn\",\"kgo\",\"kgp\",\"kgq\",\"kgr\",\"kgs\",\"kgt\",\"kgu\",\"kgv\",\"kgw\",\"kgx\",\"kgy\",\"kha\",\"khb\",\"khc\",\"khd\",\"khe\",\"khf\",\"khg\",\"khh\",\"khi\",\"khj\",\"khk\",\"khl\",\"khn\",\"kho\",\"khp\",\"khq\",\"khr\",\"khs\",\"kht\",\"khu\",\"khv\",\"khw\",\"khx\",\"khy\",\"khz\",\"kia\",\"kib\",\"kic\",\"kid\",\"kie\",\"kif\",\"kig\",\"kih\",\"kii\",\"kij\",\"kil\",\"kim\",\"kio\",\"kip\",\"kiq\",\"kis\",\"kit\",\"kiu\",\"kiv\",\"kiw\",\"kix\",\"kiy\",\"kiz\",\"kja\",\"kjb\",\"kjc\",\"kjd\",\"kje\",\"kjf\",\"kjg\",\"kjh\",\"kji\",\"kjj\",\"kjk\",\"kjl\",\"kjm\",\"kjn\",\"kjo\",\"kjp\",\"kjq\",\"kjr\",\"kjs\",\"kjt\",\"kju\",\"kjv\",\"kjx\",\"kjy\",\"kjz\",\"kka\",\"kkb\",\"kkc\",\"kkd\",\"kke\",\"kkf\",\"kkg\",\"kkh\",\"kki\",\"kkj\",\"kkk\",\"kkl\",\"kkm\",\"kkn\",\"kko\",\"kkp\",\"kkq\",\"kkr\",\"kks\",\"kkt\",\"kku\",\"kkv\",\"kkw\",\"kkx\",\"kky\",\"kkz\",\"kla\",\"klb\",\"klc\",\"kld\",\"kle\",\"klf\",\"klg\",\"klh\",\"kli\",\"klj\",\"klk\",\"kll\",\"klm\",\"kln\",\"klo\",\"klp\",\"klq\",\"klr\",\"kls\",\"klt\",\"klu\",\"klv\",\"klw\",\"klx\",\"kly\",\"klz\",\"kma\",\"kmb\",\"kmc\",\"kmd\",\"kme\",\"kmf\",\"kmg\",\"kmh\",\"kmi\",\"kmj\",\"kmk\",\"kml\",\"kmm\",\"kmn\",\"kmo\",\"kmp\",\"kmq\",\"kmr\",\"kms\",\"kmt\",\"kmu\",\"kmv\",\"kmw\",\"kmx\",\"kmy\",\"kmz\",\"kna\",\"knb\",\"knc\",\"knd\",\"kne\",\"knf\",\"kng\",\"kni\",\"knj\",\"knk\",\"knl\",\"knm\",\"knn\",\"kno\",\"knp\",\"knq\",\"knr\",\"kns\",\"knt\",\"knu\",\"knv\",\"knw\",\"knx\",\"kny\",\"knz\",\"koa\",\"koc\",\"kod\",\"koe\",\"kof\",\"kog\",\"koh\",\"koi\",\"koj\",\"kok\",\"kol\",\"koo\",\"kop\",\"koq\",\"kos\",\"kot\",\"kou\",\"kov\",\"kow\",\"kox\",\"koy\",\"koz\",\"kpa\",\"kpb\",\"kpc\",\"kpd\",\"kpe\",\"kpf\",\"kpg\",\"kph\",\"kpi\",\"kpj\",\"kpk\",\"kpl\",\"kpm\",\"kpn\",\"kpo\",\"kpp\",\"kpq\",\"kpr\",\"kps\",\"kpt\",\"kpu\",\"kpv\",\"kpw\",\"kpx\",\"kpy\",\"kpz\",\"kqa\",\"kqb\",\"kqc\",\"kqd\",\"kqe\",\"kqf\",\"kqg\",\"kqh\",\"kqi\",\"kqj\",\"kqk\",\"kql\",\"kqm\",\"kqn\",\"kqo\",\"kqp\",\"kqq\",\"kqr\",\"kqs\",\"kqt\",\"kqu\",\"kqv\",\"kqw\",\"kqx\",\"kqy\",\"kqz\",\"kra\",\"krb\",\"krc\",\"krd\",\"kre\",\"krf\",\"krh\",\"kri\",\"krj\",\"krk\",\"krl\",\"krm\",\"krn\",\"kro\",\"krp\",\"krr\",\"krs\",\"krt\",\"kru\",\"krv\",\"krw\",\"krx\",\"kry\",\"krz\",\"ksa\",\"ksb\",\"ksc\",\"ksd\",\"kse\",\"ksf\",\"ksg\",\"ksh\",\"ksi\",\"ksj\",\"ksk\",\"ksl\",\"ksm\",\"ksn\",\"kso\",\"ksp\",\"ksq\",\"ksr\",\"kss\",\"kst\",\"ksu\",\"ksv\",\"ksw\",\"ksx\",\"ksy\",\"ksz\",\"kta\",\"ktb\",\"ktc\",\"ktd\",\"kte\",\"ktf\",\"ktg\",\"kth\",\"kti\",\"ktj\",\"ktk\",\"ktl\",\"ktm\",\"ktn\",\"kto\",\"ktp\",\"ktq\",\"ktr\",\"kts\",\"ktt\",\"ktu\",\"ktv\",\"ktw\",\"ktx\",\"kty\",\"ktz\",\"kub\",\"kuc\",\"kud\",\"kue\",\"kuf\",\"kug\",\"kuh\",\"kui\",\"kuj\",\"kuk\",\"kul\",\"kum\",\"kun\",\"kuo\",\"kup\",\"kuq\",\"kus\",\"kut\",\"kuu\",\"kuv\",\"kuw\",\"kux\",\"kuy\",\"kuz\",\"kva\",\"kvb\",\"kvc\",\"kvd\",\"kve\",\"kvf\",\"kvg\",\"kvh\",\"kvi\",\"kvj\",\"kvk\",\"kvl\",\"kvm\",\"kvn\",\"kvo\",\"kvp\",\"kvq\",\"kvr\",\"kvs\",\"kvt\",\"kvu\",\"kvv\",\"kvw\",\"kvx\",\"kvy\",\"kvz\",\"kwa\",\"kwb\",\"kwc\",\"kwd\",\"kwe\",\"kwf\",\"kwg\",\"kwh\",\"kwi\",\"kwj\",\"kwk\",\"kwl\",\"kwm\",\"kwn\",\"kwo\",\"kwp\",\"kwq\",\"kwr\",\"kws\",\"kwt\",\"kwu\",\"kwv\",\"kww\",\"kwx\",\"kwy\",\"kwz\",\"kxa\",\"kxb\",\"kxc\",\"kxd\",\"kxe\",\"kxf\",\"kxh\",\"kxi\",\"kxj\",\"kxk\",\"kxl\",\"kxm\",\"kxn\",\"kxo\",\"kxp\",\"kxq\",\"kxr\",\"kxs\",\"kxt\",\"kxu\",\"kxv\",\"kxw\",\"kxx\",\"kxy\",\"kxz\",\"kya\",\"kyb\",\"kyc\",\"kyd\",\"kye\",\"kyf\",\"kyg\",\"kyh\",\"kyi\",\"kyj\",\"kyk\",\"kyl\",\"kym\",\"kyn\",\"kyo\",\"kyp\",\"kyq\",\"kyr\",\"kys\",\"kyt\",\"kyu\",\"kyv\",\"kyw\",\"kyx\",\"kyy\",\"kyz\",\"kza\",\"kzb\",\"kzc\",\"kzd\",\"kze\",\"kzf\",\"kzg\",\"kzh\",\"kzi\",\"kzj\",\"kzk\",\"kzl\",\"kzm\",\"kzn\",\"kzo\",\"kzp\",\"kzq\",\"kzr\",\"kzs\",\"kzt\",\"kzu\",\"kzv\",\"kzw\",\"kzx\",\"kzy\",\"kzz\",\"laa\",\"lab\",\"lac\",\"lad\",\"lae\",\"laf\",\"lag\",\"lah\",\"lai\",\"laj\",\"lak\",\"lal\",\"lam\",\"lan\",\"lap\",\"laq\",\"lar\",\"las\",\"lau\",\"law\",\"lax\",\"lay\",\"laz\",\"lba\",\"lbb\",\"lbc\",\"lbe\",\"lbf\",\"lbg\",\"lbi\",\"lbj\",\"lbk\",\"lbl\",\"lbm\",\"lbn\",\"lbo\",\"lbq\",\"lbr\",\"lbs\",\"lbt\",\"lbu\",\"lbv\",\"lbw\",\"lbx\",\"lby\",\"lbz\",\"lcc\",\"lcd\",\"lce\",\"lcf\",\"lch\",\"lcl\",\"lcm\",\"lcp\",\"lcq\",\"lcs\",\"lda\",\"ldb\",\"ldd\",\"ldg\",\"ldh\",\"ldi\",\"ldj\",\"ldk\",\"ldl\",\"ldm\",\"ldn\",\"ldo\",\"ldp\",\"ldq\",\"lea\",\"leb\",\"lec\",\"led\",\"lee\",\"lef\",\"leg\",\"leh\",\"lei\",\"lej\",\"lek\",\"lel\",\"lem\",\"len\",\"leo\",\"lep\",\"leq\",\"ler\",\"les\",\"let\",\"leu\",\"lev\",\"lew\",\"lex\",\"ley\",\"lez\",\"lfa\",\"lfn\",\"lga\",\"lgb\",\"lgg\",\"lgh\",\"lgi\",\"lgk\",\"lgl\",\"lgm\",\"lgn\",\"lgq\",\"lgr\",\"lgt\",\"lgu\",\"lgz\",\"lha\",\"lhh\",\"lhi\",\"lhl\",\"lhm\",\"lhn\",\"lhp\",\"lhs\",\"lht\",\"lhu\",\"lia\",\"lib\",\"lic\",\"lid\",\"lie\",\"lif\",\"lig\",\"lih\",\"lii\",\"lij\",\"lik\",\"lil\",\"lio\",\"lip\",\"liq\",\"lir\",\"lis\",\"liu\",\"liv\",\"liw\",\"lix\",\"liy\",\"liz\",\"lja\",\"lje\",\"lji\",\"ljl\",\"ljp\",\"ljw\",\"ljx\",\"lka\",\"lkb\",\"lkc\",\"lkd\",\"lke\",\"lkh\",\"lki\",\"lkj\",\"lkl\",\"lkm\",\"lkn\",\"lko\",\"lkr\",\"lks\",\"lkt\",\"lku\",\"lky\",\"lla\",\"llb\",\"llc\",\"lld\",\"lle\",\"llf\",\"llg\",\"llh\",\"lli\",\"llj\",\"llk\",\"lll\",\"llm\",\"lln\",\"llo\",\"llp\",\"llq\",\"lls\",\"llu\",\"llx\",\"lma\",\"lmb\",\"lmc\",\"lmd\",\"lme\",\"lmf\",\"lmg\",\"lmh\",\"lmi\",\"lmj\",\"lmk\",\"lml\",\"lmm\",\"lmn\",\"lmo\",\"lmp\",\"lmq\",\"lmr\",\"lmu\",\"lmv\",\"lmw\",\"lmx\",\"lmy\",\"lmz\",\"lna\",\"lnb\",\"lnd\",\"lng\",\"lnh\",\"lni\",\"lnj\",\"lnl\",\"lnm\",\"lnn\",\"lno\",\"lns\",\"lnu\",\"lnw\",\"lnz\",\"loa\",\"lob\",\"loc\",\"loe\",\"lof\",\"log\",\"loh\",\"loi\",\"loj\",\"lok\",\"lol\",\"lom\",\"lon\",\"loo\",\"lop\",\"loq\",\"lor\",\"los\",\"lot\",\"lou\",\"lov\",\"low\",\"lox\",\"loy\",\"loz\",\"lpa\",\"lpe\",\"lpn\",\"lpo\",\"lpx\",\"lra\",\"lrc\",\"lre\",\"lrg\",\"lri\",\"lrk\",\"lrl\",\"lrm\",\"lrn\",\"lro\",\"lrr\",\"lrt\",\"lrv\",\"lrz\",\"lsa\",\"lsd\",\"lse\",\"lsg\",\"lsh\",\"lsi\",\"lsl\",\"lsm\",\"lso\",\"lsp\",\"lsr\",\"lss\",\"lst\",\"lsy\",\"ltc\",\"ltg\",\"lti\",\"ltn\",\"lto\",\"lts\",\"ltu\",\"lua\",\"luc\",\"lud\",\"lue\",\"luf\",\"lui\",\"luj\",\"luk\",\"lul\",\"lum\",\"lun\",\"luo\",\"lup\",\"luq\",\"lur\",\"lus\",\"lut\",\"luu\",\"luv\",\"luw\",\"luy\",\"luz\",\"lva\",\"lvk\",\"lvs\",\"lvu\",\"lwa\",\"lwe\",\"lwg\",\"lwh\",\"lwl\",\"lwm\",\"lwo\",\"lwt\",\"lwu\",\"lww\",\"lya\",\"lyg\",\"lyn\",\"lzh\",\"lzl\",\"lzn\",\"lzz\",\"maa\",\"mab\",\"mad\",\"mae\",\"maf\",\"mag\",\"mai\",\"maj\",\"mak\",\"mam\",\"man\",\"map\",\"maq\",\"mas\",\"mat\",\"mau\",\"mav\",\"maw\",\"max\",\"maz\",\"mba\",\"mbb\",\"mbc\",\"mbd\",\"mbe\",\"mbf\",\"mbh\",\"mbi\",\"mbj\",\"mbk\",\"mbl\",\"mbm\",\"mbn\",\"mbo\",\"mbp\",\"mbq\",\"mbr\",\"mbs\",\"mbt\",\"mbu\",\"mbv\",\"mbw\",\"mbx\",\"mby\",\"mbz\",\"mca\",\"mcb\",\"mcc\",\"mcd\",\"mce\",\"mcf\",\"mcg\",\"mch\",\"mci\",\"mcj\",\"mck\",\"mcl\",\"mcm\",\"mcn\",\"mco\",\"mcp\",\"mcq\",\"mcr\",\"mcs\",\"mct\",\"mcu\",\"mcv\",\"mcw\",\"mcx\",\"mcy\",\"mcz\",\"mda\",\"mdb\",\"mdc\",\"mdd\",\"mde\",\"mdf\",\"mdg\",\"mdh\",\"mdi\",\"mdj\",\"mdk\",\"mdl\",\"mdm\",\"mdn\",\"mdp\",\"mdq\",\"mdr\",\"mds\",\"mdt\",\"mdu\",\"mdv\",\"mdw\",\"mdx\",\"mdy\",\"mdz\",\"mea\",\"meb\",\"mec\",\"med\",\"mee\",\"mef\",\"meg\",\"meh\",\"mei\",\"mej\",\"mek\",\"mel\",\"mem\",\"men\",\"meo\",\"mep\",\"meq\",\"mer\",\"mes\",\"met\",\"meu\",\"mev\",\"mew\",\"mey\",\"mez\",\"mfa\",\"mfb\",\"mfc\",\"mfd\",\"mfe\",\"mff\",\"mfg\",\"mfh\",\"mfi\",\"mfj\",\"mfk\",\"mfl\",\"mfm\",\"mfn\",\"mfo\",\"mfp\",\"mfq\",\"mfr\",\"mfs\",\"mft\",\"mfu\",\"mfv\",\"mfw\",\"mfx\",\"mfy\",\"mfz\",\"mga\",\"mgb\",\"mgc\",\"mgd\",\"mge\",\"mgf\",\"mgg\",\"mgh\",\"mgi\",\"mgj\",\"mgk\",\"mgl\",\"mgm\",\"mgn\",\"mgo\",\"mgp\",\"mgq\",\"mgr\",\"mgs\",\"mgt\",\"mgu\",\"mgv\",\"mgw\",\"mgx\",\"mgy\",\"mgz\",\"mha\",\"mhb\",\"mhc\",\"mhd\",\"mhe\",\"mhf\",\"mhg\",\"mhh\",\"mhi\",\"mhj\",\"mhk\",\"mhl\",\"mhm\",\"mhn\",\"mho\",\"mhp\",\"mhq\",\"mhr\",\"mhs\",\"mht\",\"mhu\",\"mhw\",\"mhx\",\"mhy\",\"mhz\",\"mia\",\"mib\",\"mic\",\"mid\",\"mie\",\"mif\",\"mig\",\"mih\",\"mii\",\"mij\",\"mik\",\"mil\",\"mim\",\"min\",\"mio\",\"mip\",\"miq\",\"mir\",\"mis\",\"mit\",\"miu\",\"miw\",\"mix\",\"miy\",\"miz\",\"mja\",\"mjb\",\"mjc\",\"mjd\",\"mje\",\"mjg\",\"mjh\",\"mji\",\"mjj\",\"mjk\",\"mjl\",\"mjm\",\"mjn\",\"mjo\",\"mjp\",\"mjq\",\"mjr\",\"mjs\",\"mjt\",\"mju\",\"mjv\",\"mjw\",\"mjx\",\"mjy\",\"mjz\",\"mka\",\"mkb\",\"mkc\",\"mke\",\"mkf\",\"mkg\",\"mkh\",\"mki\",\"mkj\",\"mkk\",\"mkl\",\"mkm\",\"mkn\",\"mko\",\"mkp\",\"mkq\",\"mkr\",\"mks\",\"mkt\",\"mku\",\"mkv\",\"mkw\",\"mkx\",\"mky\",\"mkz\",\"mla\",\"mlb\",\"mlc\",\"mld\",\"mle\",\"mlf\",\"mlh\",\"mli\",\"mlj\",\"mlk\",\"mll\",\"mlm\",\"mln\",\"mlo\",\"mlp\",\"mlq\",\"mlr\",\"mls\",\"mlu\",\"mlv\",\"mlw\",\"mlx\",\"mlz\",\"mma\",\"mmb\",\"mmc\",\"mmd\",\"mme\",\"mmf\",\"mmg\",\"mmh\",\"mmi\",\"mmj\",\"mmk\",\"mml\",\"mmm\",\"mmn\",\"mmo\",\"mmp\",\"mmq\",\"mmr\",\"mmt\",\"mmu\",\"mmv\",\"mmw\",\"mmx\",\"mmy\",\"mmz\",\"mna\",\"mnb\",\"mnc\",\"mnd\",\"mne\",\"mnf\",\"mng\",\"mnh\",\"mni\",\"mnj\",\"mnk\",\"mnl\",\"mnm\",\"mnn\",\"mno\",\"mnp\",\"mnq\",\"mnr\",\"mns\",\"mnt\",\"mnu\",\"mnv\",\"mnw\",\"mnx\",\"mny\",\"mnz\",\"moa\",\"moc\",\"mod\",\"moe\",\"mof\",\"mog\",\"moh\",\"moi\",\"moj\",\"mok\",\"mom\",\"moo\",\"mop\",\"moq\",\"mor\",\"mos\",\"mot\",\"mou\",\"mov\",\"mow\",\"mox\",\"moy\",\"moz\",\"mpa\",\"mpb\",\"mpc\",\"mpd\",\"mpe\",\"mpg\",\"mph\",\"mpi\",\"mpj\",\"mpk\",\"mpl\",\"mpm\",\"mpn\",\"mpo\",\"mpp\",\"mpq\",\"mpr\",\"mps\",\"mpt\",\"mpu\",\"mpv\",\"mpw\",\"mpx\",\"mpy\",\"mpz\",\"mqa\",\"mqb\",\"mqc\",\"mqe\",\"mqf\",\"mqg\",\"mqh\",\"mqi\",\"mqj\",\"mqk\",\"mql\",\"mqm\",\"mqn\",\"mqo\",\"mqp\",\"mqq\",\"mqr\",\"mqs\",\"mqt\",\"mqu\",\"mqv\",\"mqw\",\"mqx\",\"mqy\",\"mqz\",\"mra\",\"mrb\",\"mrc\",\"mrd\",\"mre\",\"mrf\",\"mrg\",\"mrh\",\"mrj\",\"mrk\",\"mrl\",\"mrm\",\"mrn\",\"mro\",\"mrp\",\"mrq\",\"mrr\",\"mrs\",\"mrt\",\"mru\",\"mrv\",\"mrw\",\"mrx\",\"mry\",\"mrz\",\"msb\",\"msc\",\"msd\",\"mse\",\"msf\",\"msg\",\"msh\",\"msi\",\"msj\",\"msk\",\"msl\",\"msm\",\"msn\",\"mso\",\"msp\",\"msq\",\"msr\",\"mss\",\"mst\",\"msu\",\"msv\",\"msw\",\"msx\",\"msy\",\"msz\",\"mta\",\"mtb\",\"mtc\",\"mtd\",\"mte\",\"mtf\",\"mtg\",\"mth\",\"mti\",\"mtj\",\"mtk\",\"mtl\",\"mtm\",\"mtn\",\"mto\",\"mtp\",\"mtq\",\"mtr\",\"mts\",\"mtt\",\"mtu\",\"mtv\",\"mtw\",\"mtx\",\"mty\",\"mua\",\"mub\",\"muc\",\"mud\",\"mue\",\"mug\",\"muh\",\"mui\",\"muj\",\"muk\",\"mul\",\"mum\",\"mun\",\"muo\",\"mup\",\"muq\",\"mur\",\"mus\",\"mut\",\"muu\",\"muv\",\"mux\",\"muy\",\"muz\",\"mva\",\"mvb\",\"mvd\",\"mve\",\"mvf\",\"mvg\",\"mvh\",\"mvi\",\"mvk\",\"mvl\",\"mvm\",\"mvn\",\"mvo\",\"mvp\",\"mvq\",\"mvr\",\"mvs\",\"mvt\",\"mvu\",\"mvv\",\"mvw\",\"mvx\",\"mvy\",\"mvz\",\"mwa\",\"mwb\",\"mwc\",\"mwd\",\"mwe\",\"mwf\",\"mwg\",\"mwh\",\"mwi\",\"mwj\",\"mwk\",\"mwl\",\"mwm\",\"mwn\",\"mwo\",\"mwp\",\"mwq\",\"mwr\",\"mws\",\"mwt\",\"mwu\",\"mwv\",\"mww\",\"mwx\",\"mwy\",\"mwz\",\"mxa\",\"mxb\",\"mxc\",\"mxd\",\"mxe\",\"mxf\",\"mxg\",\"mxh\",\"mxi\",\"mxj\",\"mxk\",\"mxl\",\"mxm\",\"mxn\",\"mxo\",\"mxp\",\"mxq\",\"mxr\",\"mxs\",\"mxt\",\"mxu\",\"mxv\",\"mxw\",\"mxx\",\"mxy\",\"mxz\",\"myb\",\"myc\",\"myd\",\"mye\",\"myf\",\"myg\",\"myh\",\"myi\",\"myj\",\"myk\",\"myl\",\"mym\",\"myn\",\"myo\",\"myp\",\"myq\",\"myr\",\"mys\",\"myt\",\"myu\",\"myv\",\"myw\",\"myx\",\"myy\",\"myz\",\"mza\",\"mzb\",\"mzc\",\"mzd\",\"mze\",\"mzg\",\"mzh\",\"mzi\",\"mzj\",\"mzk\",\"mzl\",\"mzm\",\"mzn\",\"mzo\",\"mzp\",\"mzq\",\"mzr\",\"mzs\",\"mzt\",\"mzu\",\"mzv\",\"mzw\",\"mzx\",\"mzy\",\"mzz\",\"naa\",\"nab\",\"nac\",\"nad\",\"nae\",\"naf\",\"nag\",\"nah\",\"nai\",\"naj\",\"nak\",\"nal\",\"nam\",\"nan\",\"nao\",\"nap\",\"naq\",\"nar\",\"nas\",\"nat\",\"naw\",\"nax\",\"nay\",\"naz\",\"nba\",\"nbb\",\"nbc\",\"nbd\",\"nbe\",\"nbf\",\"nbg\",\"nbh\",\"nbi\",\"nbj\",\"nbk\",\"nbm\",\"nbn\",\"nbo\",\"nbp\",\"nbq\",\"nbr\",\"nbs\",\"nbt\",\"nbu\",\"nbv\",\"nbw\",\"nbx\",\"nby\",\"nca\",\"ncb\",\"ncc\",\"ncd\",\"nce\",\"ncf\",\"ncg\",\"nch\",\"nci\",\"ncj\",\"nck\",\"ncl\",\"ncm\",\"ncn\",\"nco\",\"ncp\",\"ncr\",\"ncs\",\"nct\",\"ncu\",\"ncx\",\"ncz\",\"nda\",\"ndb\",\"ndc\",\"ndd\",\"ndf\",\"ndg\",\"ndh\",\"ndi\",\"ndj\",\"ndk\",\"ndl\",\"ndm\",\"ndn\",\"ndp\",\"ndq\",\"ndr\",\"nds\",\"ndt\",\"ndu\",\"ndv\",\"ndw\",\"ndx\",\"ndy\",\"ndz\",\"nea\",\"neb\",\"nec\",\"ned\",\"nee\",\"nef\",\"neg\",\"neh\",\"nei\",\"nej\",\"nek\",\"nem\",\"nen\",\"neo\",\"neq\",\"ner\",\"nes\",\"net\",\"neu\",\"nev\",\"new\",\"nex\",\"ney\",\"nez\",\"nfa\",\"nfd\",\"nfl\",\"nfr\",\"nfu\",\"nga\",\"ngb\",\"ngc\",\"ngd\",\"nge\",\"ngf\",\"ngg\",\"ngh\",\"ngi\",\"ngj\",\"ngk\",\"ngl\",\"ngm\",\"ngn\",\"ngo\",\"ngp\",\"ngq\",\"ngr\",\"ngs\",\"ngt\",\"ngu\",\"ngv\",\"ngw\",\"ngx\",\"ngy\",\"ngz\",\"nha\",\"nhb\",\"nhc\",\"nhd\",\"nhe\",\"nhf\",\"nhg\",\"nhh\",\"nhi\",\"nhk\",\"nhm\",\"nhn\",\"nho\",\"nhp\",\"nhq\",\"nhr\",\"nht\",\"nhu\",\"nhv\",\"nhw\",\"nhx\",\"nhy\",\"nhz\",\"nia\",\"nib\",\"nic\",\"nid\",\"nie\",\"nif\",\"nig\",\"nih\",\"nii\",\"nij\",\"nik\",\"nil\",\"nim\",\"nin\",\"nio\",\"niq\",\"nir\",\"nis\",\"nit\",\"niu\",\"niv\",\"niw\",\"nix\",\"niy\",\"niz\",\"nja\",\"njb\",\"njd\",\"njh\",\"nji\",\"njj\",\"njl\",\"njm\",\"njn\",\"njo\",\"njr\",\"njs\",\"njt\",\"nju\",\"njx\",\"njy\",\"njz\",\"nka\",\"nkb\",\"nkc\",\"nkd\",\"nke\",\"nkf\",\"nkg\",\"nkh\",\"nki\",\"nkj\",\"nkk\",\"nkm\",\"nkn\",\"nko\",\"nkp\",\"nkq\",\"nkr\",\"nks\",\"nkt\",\"nku\",\"nkv\",\"nkw\",\"nkx\",\"nkz\",\"nla\",\"nlc\",\"nle\",\"nlg\",\"nli\",\"nlj\",\"nlk\",\"nll\",\"nln\",\"nlo\",\"nlq\",\"nlr\",\"nlu\",\"nlv\",\"nlw\",\"nlx\",\"nly\",\"nlz\",\"nma\",\"nmb\",\"nmc\",\"nmd\",\"nme\",\"nmf\",\"nmg\",\"nmh\",\"nmi\",\"nmj\",\"nmk\",\"nml\",\"nmm\",\"nmn\",\"nmo\",\"nmp\",\"nmq\",\"nmr\",\"nms\",\"nmt\",\"nmu\",\"nmv\",\"nmw\",\"nmx\",\"nmy\",\"nmz\",\"nna\",\"nnb\",\"nnc\",\"nnd\",\"nne\",\"nnf\",\"nng\",\"nnh\",\"nni\",\"nnj\",\"nnk\",\"nnl\",\"nnm\",\"nnn\",\"nnp\",\"nnq\",\"nnr\",\"nns\",\"nnt\",\"nnu\",\"nnv\",\"nnw\",\"nnx\",\"nny\",\"nnz\",\"noa\",\"noc\",\"nod\",\"noe\",\"nof\",\"nog\",\"noh\",\"noi\",\"noj\",\"nok\",\"nol\",\"nom\",\"non\",\"noo\",\"nop\",\"noq\",\"nos\",\"not\",\"nou\",\"nov\",\"now\",\"noy\",\"noz\",\"npa\",\"npb\",\"npg\",\"nph\",\"npi\",\"npl\",\"npn\",\"npo\",\"nps\",\"npu\",\"npy\",\"nqg\",\"nqk\",\"nqm\",\"nqn\",\"nqo\",\"nqq\",\"nqy\",\"nra\",\"nrb\",\"nrc\",\"nre\",\"nrf\",\"nrg\",\"nri\",\"nrk\",\"nrl\",\"nrm\",\"nrn\",\"nrp\",\"nrr\",\"nrt\",\"nru\",\"nrx\",\"nrz\",\"nsa\",\"nsc\",\"nsd\",\"nse\",\"nsf\",\"nsg\",\"nsh\",\"nsi\",\"nsk\",\"nsl\",\"nsm\",\"nsn\",\"nso\",\"nsp\",\"nsq\",\"nsr\",\"nss\",\"nst\",\"nsu\",\"nsv\",\"nsw\",\"nsx\",\"nsy\",\"nsz\",\"ntd\",\"nte\",\"ntg\",\"nti\",\"ntj\",\"ntk\",\"ntm\",\"nto\",\"ntp\",\"ntr\",\"nts\",\"ntu\",\"ntw\",\"ntx\",\"nty\",\"ntz\",\"nua\",\"nub\",\"nuc\",\"nud\",\"nue\",\"nuf\",\"nug\",\"nuh\",\"nui\",\"nuj\",\"nuk\",\"nul\",\"num\",\"nun\",\"nuo\",\"nup\",\"nuq\",\"nur\",\"nus\",\"nut\",\"nuu\",\"nuv\",\"nuw\",\"nux\",\"nuy\",\"nuz\",\"nvh\",\"nvm\",\"nvo\",\"nwa\",\"nwb\",\"nwc\",\"nwe\",\"nwg\",\"nwi\",\"nwm\",\"nwo\",\"nwr\",\"nwx\",\"nwy\",\"nxa\",\"nxd\",\"nxe\",\"nxg\",\"nxi\",\"nxk\",\"nxl\",\"nxm\",\"nxn\",\"nxo\",\"nxq\",\"nxr\",\"nxu\",\"nxx\",\"nyb\",\"nyc\",\"nyd\",\"nye\",\"nyf\",\"nyg\",\"nyh\",\"nyi\",\"nyj\",\"nyk\",\"nyl\",\"nym\",\"nyn\",\"nyo\",\"nyp\",\"nyq\",\"nyr\",\"nys\",\"nyt\",\"nyu\",\"nyv\",\"nyw\",\"nyx\",\"nyy\",\"nza\",\"nzb\",\"nzi\",\"nzk\",\"nzm\",\"nzs\",\"nzu\",\"nzy\",\"nzz\",\"oaa\",\"oac\",\"oar\",\"oav\",\"obi\",\"obk\",\"obl\",\"obm\",\"obo\",\"obr\",\"obt\",\"obu\",\"oca\",\"och\",\"oco\",\"ocu\",\"oda\",\"odk\",\"odt\",\"odu\",\"ofo\",\"ofs\",\"ofu\",\"ogb\",\"ogc\",\"oge\",\"ogg\",\"ogo\",\"ogu\",\"oht\",\"ohu\",\"oia\",\"oin\",\"ojb\",\"ojc\",\"ojg\",\"ojp\",\"ojs\",\"ojv\",\"ojw\",\"oka\",\"okb\",\"okd\",\"oke\",\"okg\",\"okh\",\"oki\",\"okj\",\"okk\",\"okl\",\"okm\",\"okn\",\"oko\",\"okr\",\"oks\",\"oku\",\"okv\",\"okx\",\"ola\",\"old\",\"ole\",\"olk\",\"olm\",\"olo\",\"olr\",\"olt\",\"olu\",\"oma\",\"omb\",\"omc\",\"ome\",\"omg\",\"omi\",\"omk\",\"oml\",\"omn\",\"omo\",\"omp\",\"omq\",\"omr\",\"omt\",\"omu\",\"omv\",\"omw\",\"omx\",\"ona\",\"onb\",\"one\",\"ong\",\"oni\",\"onj\",\"onk\",\"onn\",\"ono\",\"onp\",\"onr\",\"ons\",\"ont\",\"onu\",\"onw\",\"onx\",\"ood\",\"oog\",\"oon\",\"oor\",\"oos\",\"opa\",\"opk\",\"opm\",\"opo\",\"opt\",\"opy\",\"ora\",\"orc\",\"ore\",\"org\",\"orh\",\"orn\",\"oro\",\"orr\",\"ors\",\"ort\",\"oru\",\"orv\",\"orw\",\"orx\",\"ory\",\"orz\",\"osa\",\"osc\",\"osi\",\"oso\",\"osp\",\"ost\",\"osu\",\"osx\",\"ota\",\"otb\",\"otd\",\"ote\",\"oti\",\"otk\",\"otl\",\"otm\",\"otn\",\"oto\",\"otq\",\"otr\",\"ots\",\"ott\",\"otu\",\"otw\",\"otx\",\"oty\",\"otz\",\"oua\",\"oub\",\"oue\",\"oui\",\"oum\",\"oun\",\"ovd\",\"owi\",\"owl\",\"oyb\",\"oyd\",\"oym\",\"oyy\",\"ozm\",\"paa\",\"pab\",\"pac\",\"pad\",\"pae\",\"paf\",\"pag\",\"pah\",\"pai\",\"pak\",\"pal\",\"pam\",\"pao\",\"pap\",\"paq\",\"par\",\"pas\",\"pat\",\"pau\",\"pav\",\"paw\",\"pax\",\"pay\",\"paz\",\"pbb\",\"pbc\",\"pbe\",\"pbf\",\"pbg\",\"pbh\",\"pbi\",\"pbl\",\"pbn\",\"pbo\",\"pbp\",\"pbr\",\"pbs\",\"pbt\",\"pbu\",\"pbv\",\"pby\",\"pbz\",\"pca\",\"pcb\",\"pcc\",\"pcd\",\"pce\",\"pcf\",\"pcg\",\"pch\",\"pci\",\"pcj\",\"pck\",\"pcl\",\"pcm\",\"pcn\",\"pcp\",\"pcr\",\"pcw\",\"pda\",\"pdc\",\"pdi\",\"pdn\",\"pdo\",\"pdt\",\"pdu\",\"pea\",\"peb\",\"ped\",\"pee\",\"pef\",\"peg\",\"peh\",\"pei\",\"pej\",\"pek\",\"pel\",\"pem\",\"peo\",\"pep\",\"peq\",\"pes\",\"pev\",\"pex\",\"pey\",\"pez\",\"pfa\",\"pfe\",\"pfl\",\"pga\",\"pgd\",\"pgg\",\"pgi\",\"pgk\",\"pgl\",\"pgn\",\"pgs\",\"pgu\",\"pgy\",\"pgz\",\"pha\",\"phd\",\"phg\",\"phh\",\"phi\",\"phk\",\"phl\",\"phm\",\"phn\",\"pho\",\"phq\",\"phr\",\"pht\",\"phu\",\"phv\",\"phw\",\"pia\",\"pib\",\"pic\",\"pid\",\"pie\",\"pif\",\"pig\",\"pih\",\"pii\",\"pij\",\"pil\",\"pim\",\"pin\",\"pio\",\"pip\",\"pir\",\"pis\",\"pit\",\"piu\",\"piv\",\"piw\",\"pix\",\"piy\",\"piz\",\"pjt\",\"pka\",\"pkb\",\"pkc\",\"pkg\",\"pkh\",\"pkn\",\"pko\",\"pkp\",\"pkr\",\"pks\",\"pkt\",\"pku\",\"pla\",\"plb\",\"plc\",\"pld\",\"ple\",\"plf\",\"plg\",\"plh\",\"plj\",\"plk\",\"pll\",\"pln\",\"plo\",\"plp\",\"plq\",\"plr\",\"pls\",\"plt\",\"plu\",\"plv\",\"plw\",\"ply\",\"plz\",\"pma\",\"pmb\",\"pmc\",\"pmd\",\"pme\",\"pmf\",\"pmh\",\"pmi\",\"pmj\",\"pmk\",\"pml\",\"pmm\",\"pmn\",\"pmo\",\"pmq\",\"pmr\",\"pms\",\"pmt\",\"pmu\",\"pmw\",\"pmx\",\"pmy\",\"pmz\",\"pna\",\"pnb\",\"pnc\",\"pne\",\"png\",\"pnh\",\"pni\",\"pnj\",\"pnk\",\"pnl\",\"pnm\",\"pnn\",\"pno\",\"pnp\",\"pnq\",\"pnr\",\"pns\",\"pnt\",\"pnu\",\"pnv\",\"pnw\",\"pnx\",\"pny\",\"pnz\",\"poc\",\"pod\",\"poe\",\"pof\",\"pog\",\"poh\",\"poi\",\"pok\",\"pom\",\"pon\",\"poo\",\"pop\",\"poq\",\"pos\",\"pot\",\"pov\",\"pow\",\"pox\",\"poy\",\"poz\",\"ppa\",\"ppe\",\"ppi\",\"ppk\",\"ppl\",\"ppm\",\"ppn\",\"ppo\",\"ppp\",\"ppq\",\"ppr\",\"pps\",\"ppt\",\"ppu\",\"pqa\",\"pqe\",\"pqm\",\"pqw\",\"pra\",\"prb\",\"prc\",\"prd\",\"pre\",\"prf\",\"prg\",\"prh\",\"pri\",\"prk\",\"prl\",\"prm\",\"prn\",\"pro\",\"prp\",\"prq\",\"prr\",\"prs\",\"prt\",\"pru\",\"prw\",\"prx\",\"pry\",\"prz\",\"psa\",\"psc\",\"psd\",\"pse\",\"psg\",\"psh\",\"psi\",\"psl\",\"psm\",\"psn\",\"pso\",\"psp\",\"psq\",\"psr\",\"pss\",\"pst\",\"psu\",\"psw\",\"psy\",\"pta\",\"pth\",\"pti\",\"ptn\",\"pto\",\"ptp\",\"ptq\",\"ptr\",\"ptt\",\"ptu\",\"ptv\",\"ptw\",\"pty\",\"pua\",\"pub\",\"puc\",\"pud\",\"pue\",\"puf\",\"pug\",\"pui\",\"puj\",\"puk\",\"pum\",\"puo\",\"pup\",\"puq\",\"pur\",\"put\",\"puu\",\"puw\",\"pux\",\"puy\",\"puz\",\"pwa\",\"pwb\",\"pwg\",\"pwi\",\"pwm\",\"pwn\",\"pwo\",\"pwr\",\"pww\",\"pxm\",\"pye\",\"pym\",\"pyn\",\"pys\",\"pyu\",\"pyx\",\"pyy\",\"pzn\",\"qaa..qtz\",\"qua\",\"qub\",\"quc\",\"qud\",\"quf\",\"qug\",\"quh\",\"qui\",\"quk\",\"qul\",\"qum\",\"qun\",\"qup\",\"quq\",\"qur\",\"qus\",\"quv\",\"quw\",\"qux\",\"quy\",\"quz\",\"qva\",\"qvc\",\"qve\",\"qvh\",\"qvi\",\"qvj\",\"qvl\",\"qvm\",\"qvn\",\"qvo\",\"qvp\",\"qvs\",\"qvw\",\"qvy\",\"qvz\",\"qwa\",\"qwc\",\"qwe\",\"qwh\",\"qwm\",\"qws\",\"qwt\",\"qxa\",\"qxc\",\"qxh\",\"qxl\",\"qxn\",\"qxo\",\"qxp\",\"qxq\",\"qxr\",\"qxs\",\"qxt\",\"qxu\",\"qxw\",\"qya\",\"qyp\",\"raa\",\"rab\",\"rac\",\"rad\",\"raf\",\"rag\",\"rah\",\"rai\",\"raj\",\"rak\",\"ral\",\"ram\",\"ran\",\"rao\",\"rap\",\"raq\",\"rar\",\"ras\",\"rat\",\"rau\",\"rav\",\"raw\",\"rax\",\"ray\",\"raz\",\"rbb\",\"rbk\",\"rbl\",\"rbp\",\"rcf\",\"rdb\",\"rea\",\"reb\",\"ree\",\"reg\",\"rei\",\"rej\",\"rel\",\"rem\",\"ren\",\"rer\",\"res\",\"ret\",\"rey\",\"rga\",\"rge\",\"rgk\",\"rgn\",\"rgr\",\"rgs\",\"rgu\",\"rhg\",\"rhp\",\"ria\",\"rie\",\"rif\",\"ril\",\"rim\",\"rin\",\"rir\",\"rit\",\"riu\",\"rjg\",\"rji\",\"rjs\",\"rka\",\"rkb\",\"rkh\",\"rki\",\"rkm\",\"rkt\",\"rkw\",\"rma\",\"rmb\",\"rmc\",\"rmd\",\"rme\",\"rmf\",\"rmg\",\"rmh\",\"rmi\",\"rmk\",\"rml\",\"rmm\",\"rmn\",\"rmo\",\"rmp\",\"rmq\",\"rmr\",\"rms\",\"rmt\",\"rmu\",\"rmv\",\"rmw\",\"rmx\",\"rmy\",\"rmz\",\"rna\",\"rnd\",\"rng\",\"rnl\",\"rnn\",\"rnp\",\"rnr\",\"rnw\",\"roa\",\"rob\",\"roc\",\"rod\",\"roe\",\"rof\",\"rog\",\"rol\",\"rom\",\"roo\",\"rop\",\"ror\",\"rou\",\"row\",\"rpn\",\"rpt\",\"rri\",\"rro\",\"rrt\",\"rsb\",\"rsi\",\"rsl\",\"rsm\",\"rtc\",\"rth\",\"rtm\",\"rts\",\"rtw\",\"rub\",\"ruc\",\"rue\",\"ruf\",\"rug\",\"ruh\",\"rui\",\"ruk\",\"ruo\",\"rup\",\"ruq\",\"rut\",\"ruu\",\"ruy\",\"ruz\",\"rwa\",\"rwk\",\"rwm\",\"rwo\",\"rwr\",\"rxd\",\"rxw\",\"ryn\",\"rys\",\"ryu\",\"rzh\",\"saa\",\"sab\",\"sac\",\"sad\",\"sae\",\"saf\",\"sah\",\"sai\",\"saj\",\"sak\",\"sal\",\"sam\",\"sao\",\"sap\",\"saq\",\"sar\",\"sas\",\"sat\",\"sau\",\"sav\",\"saw\",\"sax\",\"say\",\"saz\",\"sba\",\"sbb\",\"sbc\",\"sbd\",\"sbe\",\"sbf\",\"sbg\",\"sbh\",\"sbi\",\"sbj\",\"sbk\",\"sbl\",\"sbm\",\"sbn\",\"sbo\",\"sbp\",\"sbq\",\"sbr\",\"sbs\",\"sbt\",\"sbu\",\"sbv\",\"sbw\",\"sbx\",\"sby\",\"sbz\",\"sca\",\"scb\",\"sce\",\"scf\",\"scg\",\"sch\",\"sci\",\"sck\",\"scl\",\"scn\",\"sco\",\"scp\",\"scq\",\"scs\",\"scu\",\"scv\",\"scw\",\"scx\",\"sda\",\"sdb\",\"sdc\",\"sde\",\"sdf\",\"sdg\",\"sdh\",\"sdj\",\"sdk\",\"sdl\",\"sdm\",\"sdn\",\"sdo\",\"sdp\",\"sdr\",\"sds\",\"sdt\",\"sdu\",\"sdv\",\"sdx\",\"sdz\",\"sea\",\"seb\",\"sec\",\"sed\",\"see\",\"sef\",\"seg\",\"seh\",\"sei\",\"sej\",\"sek\",\"sel\",\"sem\",\"sen\",\"seo\",\"sep\",\"seq\",\"ser\",\"ses\",\"set\",\"seu\",\"sev\",\"sew\",\"sey\",\"sez\",\"sfb\",\"sfe\",\"sfm\",\"sfs\",\"sfw\",\"sga\",\"sgb\",\"sgc\",\"sgd\",\"sge\",\"sgg\",\"sgh\",\"sgi\",\"sgj\",\"sgk\",\"sgl\",\"sgm\",\"sgn\",\"sgo\",\"sgp\",\"sgr\",\"sgs\",\"sgt\",\"sgu\",\"sgw\",\"sgx\",\"sgy\",\"sgz\",\"sha\",\"shb\",\"shc\",\"shd\",\"she\",\"shg\",\"shh\",\"shi\",\"shj\",\"shk\",\"shl\",\"shm\",\"shn\",\"sho\",\"shp\",\"shq\",\"shr\",\"shs\",\"sht\",\"shu\",\"shv\",\"shw\",\"shx\",\"shy\",\"shz\",\"sia\",\"sib\",\"sid\",\"sie\",\"sif\",\"sig\",\"sih\",\"sii\",\"sij\",\"sik\",\"sil\",\"sim\",\"sio\",\"sip\",\"siq\",\"sir\",\"sis\",\"sit\",\"siu\",\"siv\",\"siw\",\"six\",\"siy\",\"siz\",\"sja\",\"sjb\",\"sjd\",\"sje\",\"sjg\",\"sjk\",\"sjl\",\"sjm\",\"sjn\",\"sjo\",\"sjp\",\"sjr\",\"sjs\",\"sjt\",\"sju\",\"sjw\",\"ska\",\"skb\",\"skc\",\"skd\",\"ske\",\"skf\",\"skg\",\"skh\",\"ski\",\"skj\",\"skk\",\"skm\",\"skn\",\"sko\",\"skp\",\"skq\",\"skr\",\"sks\",\"skt\",\"sku\",\"skv\",\"skw\",\"skx\",\"sky\",\"skz\",\"sla\",\"slc\",\"sld\",\"sle\",\"slf\",\"slg\",\"slh\",\"sli\",\"slj\",\"sll\",\"slm\",\"sln\",\"slp\",\"slq\",\"slr\",\"sls\",\"slt\",\"slu\",\"slw\",\"slx\",\"sly\",\"slz\",\"sma\",\"smb\",\"smc\",\"smd\",\"smf\",\"smg\",\"smh\",\"smi\",\"smj\",\"smk\",\"sml\",\"smm\",\"smn\",\"smp\",\"smq\",\"smr\",\"sms\",\"smt\",\"smu\",\"smv\",\"smw\",\"smx\",\"smy\",\"smz\",\"snb\",\"snc\",\"sne\",\"snf\",\"sng\",\"snh\",\"sni\",\"snj\",\"snk\",\"snl\",\"snm\",\"snn\",\"sno\",\"snp\",\"snq\",\"snr\",\"sns\",\"snu\",\"snv\",\"snw\",\"snx\",\"sny\",\"snz\",\"soa\",\"sob\",\"soc\",\"sod\",\"soe\",\"sog\",\"soh\",\"soi\",\"soj\",\"sok\",\"sol\",\"son\",\"soo\",\"sop\",\"soq\",\"sor\",\"sos\",\"sou\",\"sov\",\"sow\",\"sox\",\"soy\",\"soz\",\"spb\",\"spc\",\"spd\",\"spe\",\"spg\",\"spi\",\"spk\",\"spl\",\"spm\",\"spn\",\"spo\",\"spp\",\"spq\",\"spr\",\"sps\",\"spt\",\"spu\",\"spv\",\"spx\",\"spy\",\"sqa\",\"sqh\",\"sqj\",\"sqk\",\"sqm\",\"sqn\",\"sqo\",\"sqq\",\"sqr\",\"sqs\",\"sqt\",\"squ\",\"sra\",\"srb\",\"src\",\"sre\",\"srf\",\"srg\",\"srh\",\"sri\",\"srk\",\"srl\",\"srm\",\"srn\",\"sro\",\"srq\",\"srr\",\"srs\",\"srt\",\"sru\",\"srv\",\"srw\",\"srx\",\"sry\",\"srz\",\"ssa\",\"ssb\",\"ssc\",\"ssd\",\"sse\",\"ssf\",\"ssg\",\"ssh\",\"ssi\",\"ssj\",\"ssk\",\"ssl\",\"ssm\",\"ssn\",\"sso\",\"ssp\",\"ssq\",\"ssr\",\"sss\",\"sst\",\"ssu\",\"ssv\",\"ssx\",\"ssy\",\"ssz\",\"sta\",\"stb\",\"std\",\"ste\",\"stf\",\"stg\",\"sth\",\"sti\",\"stj\",\"stk\",\"stl\",\"stm\",\"stn\",\"sto\",\"stp\",\"stq\",\"str\",\"sts\",\"stt\",\"stu\",\"stv\",\"stw\",\"sty\",\"sua\",\"sub\",\"suc\",\"sue\",\"sug\",\"sui\",\"suj\",\"suk\",\"sul\",\"sum\",\"suq\",\"sur\",\"sus\",\"sut\",\"suv\",\"suw\",\"sux\",\"suy\",\"suz\",\"sva\",\"svb\",\"svc\",\"sve\",\"svk\",\"svm\",\"svr\",\"svs\",\"svx\",\"swb\",\"swc\",\"swf\",\"swg\",\"swh\",\"swi\",\"swj\",\"swk\",\"swl\",\"swm\",\"swn\",\"swo\",\"swp\",\"swq\",\"swr\",\"sws\",\"swt\",\"swu\",\"swv\",\"sww\",\"swx\",\"swy\",\"sxb\",\"sxc\",\"sxe\",\"sxg\",\"sxk\",\"sxl\",\"sxm\",\"sxn\",\"sxo\",\"sxr\",\"sxs\",\"sxu\",\"sxw\",\"sya\",\"syb\",\"syc\",\"syd\",\"syi\",\"syk\",\"syl\",\"sym\",\"syn\",\"syo\",\"syr\",\"sys\",\"syw\",\"syx\",\"syy\",\"sza\",\"szb\",\"szc\",\"szd\",\"sze\",\"szg\",\"szl\",\"szn\",\"szp\",\"szv\",\"szw\",\"taa\",\"tab\",\"tac\",\"tad\",\"tae\",\"taf\",\"tag\",\"tai\",\"taj\",\"tak\",\"tal\",\"tan\",\"tao\",\"tap\",\"taq\",\"tar\",\"tas\",\"tau\",\"tav\",\"taw\",\"tax\",\"tay\",\"taz\",\"tba\",\"tbb\",\"tbc\",\"tbd\",\"tbe\",\"tbf\",\"tbg\",\"tbh\",\"tbi\",\"tbj\",\"tbk\",\"tbl\",\"tbm\",\"tbn\",\"tbo\",\"tbp\",\"tbq\",\"tbr\",\"tbs\",\"tbt\",\"tbu\",\"tbv\",\"tbw\",\"tbx\",\"tby\",\"tbz\",\"tca\",\"tcb\",\"tcc\",\"tcd\",\"tce\",\"tcf\",\"tcg\",\"tch\",\"tci\",\"tck\",\"tcl\",\"tcm\",\"tcn\",\"tco\",\"tcp\",\"tcq\",\"tcs\",\"tct\",\"tcu\",\"tcw\",\"tcx\",\"tcy\",\"tcz\",\"tda\",\"tdb\",\"tdc\",\"tdd\",\"tde\",\"tdf\",\"tdg\",\"tdh\",\"tdi\",\"tdj\",\"tdk\",\"tdl\",\"tdm\",\"tdn\",\"tdo\",\"tdq\",\"tdr\",\"tds\",\"tdt\",\"tdu\",\"tdv\",\"tdx\",\"tdy\",\"tea\",\"teb\",\"tec\",\"ted\",\"tee\",\"tef\",\"teg\",\"teh\",\"tei\",\"tek\",\"tem\",\"ten\",\"teo\",\"tep\",\"teq\",\"ter\",\"tes\",\"tet\",\"teu\",\"tev\",\"tew\",\"tex\",\"tey\",\"tfi\",\"tfn\",\"tfo\",\"tfr\",\"tft\",\"tga\",\"tgb\",\"tgc\",\"tgd\",\"tge\",\"tgf\",\"tgg\",\"tgh\",\"tgi\",\"tgj\",\"tgn\",\"tgo\",\"tgp\",\"tgq\",\"tgr\",\"tgs\",\"tgt\",\"tgu\",\"tgv\",\"tgw\",\"tgx\",\"tgy\",\"tgz\",\"thc\",\"thd\",\"the\",\"thf\",\"thh\",\"thi\",\"thk\",\"thl\",\"thm\",\"thn\",\"thp\",\"thq\",\"thr\",\"ths\",\"tht\",\"thu\",\"thv\",\"thw\",\"thx\",\"thy\",\"thz\",\"tia\",\"tic\",\"tid\",\"tie\",\"tif\",\"tig\",\"tih\",\"tii\",\"tij\",\"tik\",\"til\",\"tim\",\"tin\",\"tio\",\"tip\",\"tiq\",\"tis\",\"tit\",\"tiu\",\"tiv\",\"tiw\",\"tix\",\"tiy\",\"tiz\",\"tja\",\"tjg\",\"tji\",\"tjl\",\"tjm\",\"tjn\",\"tjo\",\"tjs\",\"tju\",\"tjw\",\"tka\",\"tkb\",\"tkd\",\"tke\",\"tkf\",\"tkg\",\"tkk\",\"tkl\",\"tkm\",\"tkn\",\"tkp\",\"tkq\",\"tkr\",\"tks\",\"tkt\",\"tku\",\"tkv\",\"tkw\",\"tkx\",\"tkz\",\"tla\",\"tlb\",\"tlc\",\"tld\",\"tlf\",\"tlg\",\"tlh\",\"tli\",\"tlj\",\"tlk\",\"tll\",\"tlm\",\"tln\",\"tlo\",\"tlp\",\"tlq\",\"tlr\",\"tls\",\"tlt\",\"tlu\",\"tlv\",\"tlw\",\"tlx\",\"tly\",\"tma\",\"tmb\",\"tmc\",\"tmd\",\"tme\",\"tmf\",\"tmg\",\"tmh\",\"tmi\",\"tmj\",\"tmk\",\"tml\",\"tmm\",\"tmn\",\"tmo\",\"tmp\",\"tmq\",\"tmr\",\"tms\",\"tmt\",\"tmu\",\"tmv\",\"tmw\",\"tmy\",\"tmz\",\"tna\",\"tnb\",\"tnc\",\"tnd\",\"tne\",\"tnf\",\"tng\",\"tnh\",\"tni\",\"tnk\",\"tnl\",\"tnm\",\"tnn\",\"tno\",\"tnp\",\"tnq\",\"tnr\",\"tns\",\"tnt\",\"tnu\",\"tnv\",\"tnw\",\"tnx\",\"tny\",\"tnz\",\"tob\",\"toc\",\"tod\",\"toe\",\"tof\",\"tog\",\"toh\",\"toi\",\"toj\",\"tol\",\"tom\",\"too\",\"top\",\"toq\",\"tor\",\"tos\",\"tou\",\"tov\",\"tow\",\"tox\",\"toy\",\"toz\",\"tpa\",\"tpc\",\"tpe\",\"tpf\",\"tpg\",\"tpi\",\"tpj\",\"tpk\",\"tpl\",\"tpm\",\"tpn\",\"tpo\",\"tpp\",\"tpq\",\"tpr\",\"tpt\",\"tpu\",\"tpv\",\"tpw\",\"tpx\",\"tpy\",\"tpz\",\"tqb\",\"tql\",\"tqm\",\"tqn\",\"tqo\",\"tqp\",\"tqq\",\"tqr\",\"tqt\",\"tqu\",\"tqw\",\"tra\",\"trb\",\"trc\",\"trd\",\"tre\",\"trf\",\"trg\",\"trh\",\"tri\",\"trj\",\"trk\",\"trl\",\"trm\",\"trn\",\"tro\",\"trp\",\"trq\",\"trr\",\"trs\",\"trt\",\"tru\",\"trv\",\"trw\",\"trx\",\"try\",\"trz\",\"tsa\",\"tsb\",\"tsc\",\"tsd\",\"tse\",\"tsf\",\"tsg\",\"tsh\",\"tsi\",\"tsj\",\"tsk\",\"tsl\",\"tsm\",\"tsp\",\"tsq\",\"tsr\",\"tss\",\"tst\",\"tsu\",\"tsv\",\"tsw\",\"tsx\",\"tsy\",\"tsz\",\"tta\",\"ttb\",\"ttc\",\"ttd\",\"tte\",\"ttf\",\"ttg\",\"tth\",\"tti\",\"ttj\",\"ttk\",\"ttl\",\"ttm\",\"ttn\",\"tto\",\"ttp\",\"ttq\",\"ttr\",\"tts\",\"ttt\",\"ttu\",\"ttv\",\"ttw\",\"tty\",\"ttz\",\"tua\",\"tub\",\"tuc\",\"tud\",\"tue\",\"tuf\",\"tug\",\"tuh\",\"tui\",\"tuj\",\"tul\",\"tum\",\"tun\",\"tuo\",\"tup\",\"tuq\",\"tus\",\"tut\",\"tuu\",\"tuv\",\"tuw\",\"tux\",\"tuy\",\"tuz\",\"tva\",\"tvd\",\"tve\",\"tvk\",\"tvl\",\"tvm\",\"tvn\",\"tvo\",\"tvs\",\"tvt\",\"tvu\",\"tvw\",\"tvy\",\"twa\",\"twb\",\"twc\",\"twd\",\"twe\",\"twf\",\"twg\",\"twh\",\"twl\",\"twm\",\"twn\",\"two\",\"twp\",\"twq\",\"twr\",\"twt\",\"twu\",\"tww\",\"twx\",\"twy\",\"txa\",\"txb\",\"txc\",\"txe\",\"txg\",\"txh\",\"txi\",\"txj\",\"txm\",\"txn\",\"txo\",\"txq\",\"txr\",\"txs\",\"txt\",\"txu\",\"txx\",\"txy\",\"tya\",\"tye\",\"tyh\",\"tyi\",\"tyj\",\"tyl\",\"tyn\",\"typ\",\"tyr\",\"tys\",\"tyt\",\"tyu\",\"tyv\",\"tyx\",\"tyz\",\"tza\",\"tzh\",\"tzj\",\"tzl\",\"tzm\",\"tzn\",\"tzo\",\"tzx\",\"uam\",\"uan\",\"uar\",\"uba\",\"ubi\",\"ubl\",\"ubr\",\"ubu\",\"uby\",\"uda\",\"ude\",\"udg\",\"udi\",\"udj\",\"udl\",\"udm\",\"udu\",\"ues\",\"ufi\",\"uga\",\"ugb\",\"uge\",\"ugn\",\"ugo\",\"ugy\",\"uha\",\"uhn\",\"uis\",\"uiv\",\"uji\",\"uka\",\"ukg\",\"ukh\",\"ukl\",\"ukp\",\"ukq\",\"uks\",\"uku\",\"ukw\",\"uky\",\"ula\",\"ulb\",\"ulc\",\"ule\",\"ulf\",\"uli\",\"ulk\",\"ull\",\"ulm\",\"uln\",\"ulu\",\"ulw\",\"uma\",\"umb\",\"umc\",\"umd\",\"umg\",\"umi\",\"umm\",\"umn\",\"umo\",\"ump\",\"umr\",\"ums\",\"umu\",\"una\",\"und\",\"une\",\"ung\",\"unk\",\"unm\",\"unn\",\"unp\",\"unr\",\"unu\",\"unx\",\"unz\",\"uok\",\"upi\",\"upv\",\"ura\",\"urb\",\"urc\",\"ure\",\"urf\",\"urg\",\"urh\",\"uri\",\"urj\",\"urk\",\"url\",\"urm\",\"urn\",\"uro\",\"urp\",\"urr\",\"urt\",\"uru\",\"urv\",\"urw\",\"urx\",\"ury\",\"urz\",\"usa\",\"ush\",\"usi\",\"usk\",\"usp\",\"usu\",\"uta\",\"ute\",\"utp\",\"utr\",\"utu\",\"uum\",\"uun\",\"uur\",\"uuu\",\"uve\",\"uvh\",\"uvl\",\"uwa\",\"uya\",\"uzn\",\"uzs\",\"vaa\",\"vae\",\"vaf\",\"vag\",\"vah\",\"vai\",\"vaj\",\"val\",\"vam\",\"van\",\"vao\",\"vap\",\"var\",\"vas\",\"vau\",\"vav\",\"vay\",\"vbb\",\"vbk\",\"vec\",\"ved\",\"vel\",\"vem\",\"veo\",\"vep\",\"ver\",\"vgr\",\"vgt\",\"vic\",\"vid\",\"vif\",\"vig\",\"vil\",\"vin\",\"vis\",\"vit\",\"viv\",\"vka\",\"vki\",\"vkj\",\"vkk\",\"vkl\",\"vkm\",\"vko\",\"vkp\",\"vkt\",\"vku\",\"vlp\",\"vls\",\"vma\",\"vmb\",\"vmc\",\"vmd\",\"vme\",\"vmf\",\"vmg\",\"vmh\",\"vmi\",\"vmj\",\"vmk\",\"vml\",\"vmm\",\"vmp\",\"vmq\",\"vmr\",\"vms\",\"vmu\",\"vmv\",\"vmw\",\"vmx\",\"vmy\",\"vmz\",\"vnk\",\"vnm\",\"vnp\",\"vor\",\"vot\",\"vra\",\"vro\",\"vrs\",\"vrt\",\"vsi\",\"vsl\",\"vsv\",\"vto\",\"vum\",\"vun\",\"vut\",\"vwa\",\"waa\",\"wab\",\"wac\",\"wad\",\"wae\",\"waf\",\"wag\",\"wah\",\"wai\",\"waj\",\"wak\",\"wal\",\"wam\",\"wan\",\"wao\",\"wap\",\"waq\",\"war\",\"was\",\"wat\",\"wau\",\"wav\",\"waw\",\"wax\",\"way\",\"waz\",\"wba\",\"wbb\",\"wbe\",\"wbf\",\"wbh\",\"wbi\",\"wbj\",\"wbk\",\"wbl\",\"wbm\",\"wbp\",\"wbq\",\"wbr\",\"wbt\",\"wbv\",\"wbw\",\"wca\",\"wci\",\"wdd\",\"wdg\",\"wdj\",\"wdk\",\"wdu\",\"wdy\",\"wea\",\"wec\",\"wed\",\"weg\",\"weh\",\"wei\",\"wem\",\"wen\",\"weo\",\"wep\",\"wer\",\"wes\",\"wet\",\"weu\",\"wew\",\"wfg\",\"wga\",\"wgb\",\"wgg\",\"wgi\",\"wgo\",\"wgu\",\"wgw\",\"wgy\",\"wha\",\"whg\",\"whk\",\"whu\",\"wib\",\"wic\",\"wie\",\"wif\",\"wig\",\"wih\",\"wii\",\"wij\",\"wik\",\"wil\",\"wim\",\"win\",\"wir\",\"wit\",\"wiu\",\"wiv\",\"wiw\",\"wiy\",\"wja\",\"wji\",\"wka\",\"wkb\",\"wkd\",\"wkl\",\"wku\",\"wkw\",\"wky\",\"wla\",\"wlc\",\"wle\",\"wlg\",\"wli\",\"wlk\",\"wll\",\"wlm\",\"wlo\",\"wlr\",\"wls\",\"wlu\",\"wlv\",\"wlw\",\"wlx\",\"wly\",\"wma\",\"wmb\",\"wmc\",\"wmd\",\"wme\",\"wmh\",\"wmi\",\"wmm\",\"wmn\",\"wmo\",\"wms\",\"wmt\",\"wmw\",\"wmx\",\"wnb\",\"wnc\",\"wnd\",\"wne\",\"wng\",\"wni\",\"wnk\",\"wnm\",\"wnn\",\"wno\",\"wnp\",\"wnu\",\"wnw\",\"wny\",\"woa\",\"wob\",\"woc\",\"wod\",\"woe\",\"wof\",\"wog\",\"woi\",\"wok\",\"wom\",\"won\",\"woo\",\"wor\",\"wos\",\"wow\",\"woy\",\"wpc\",\"wra\",\"wrb\",\"wrd\",\"wrg\",\"wrh\",\"wri\",\"wrk\",\"wrl\",\"wrm\",\"wrn\",\"wro\",\"wrp\",\"wrr\",\"wrs\",\"wru\",\"wrv\",\"wrw\",\"wrx\",\"wry\",\"wrz\",\"wsa\",\"wsg\",\"wsi\",\"wsk\",\"wsr\",\"wss\",\"wsu\",\"wsv\",\"wtf\",\"wth\",\"wti\",\"wtk\",\"wtm\",\"wtw\",\"wua\",\"wub\",\"wud\",\"wuh\",\"wul\",\"wum\",\"wun\",\"wur\",\"wut\",\"wuu\",\"wuv\",\"wux\",\"wuy\",\"wwa\",\"wwb\",\"wwo\",\"wwr\",\"www\",\"wxa\",\"wxw\",\"wya\",\"wyb\",\"wyi\",\"wym\",\"wyr\",\"wyy\",\"xaa\",\"xab\",\"xac\",\"xad\",\"xae\",\"xag\",\"xai\",\"xaj\",\"xak\",\"xal\",\"xam\",\"xan\",\"xao\",\"xap\",\"xaq\",\"xar\",\"xas\",\"xat\",\"xau\",\"xav\",\"xaw\",\"xay\",\"xba\",\"xbb\",\"xbc\",\"xbd\",\"xbe\",\"xbg\",\"xbi\",\"xbj\",\"xbm\",\"xbn\",\"xbo\",\"xbp\",\"xbr\",\"xbw\",\"xbx\",\"xby\",\"xcb\",\"xcc\",\"xce\",\"xcg\",\"xch\",\"xcl\",\"xcm\",\"xcn\",\"xco\",\"xcr\",\"xct\",\"xcu\",\"xcv\",\"xcw\",\"xcy\",\"xda\",\"xdc\",\"xdk\",\"xdm\",\"xdy\",\"xeb\",\"xed\",\"xeg\",\"xel\",\"xem\",\"xep\",\"xer\",\"xes\",\"xet\",\"xeu\",\"xfa\",\"xga\",\"xgb\",\"xgd\",\"xgf\",\"xgg\",\"xgi\",\"xgl\",\"xgm\",\"xgn\",\"xgr\",\"xgu\",\"xgw\",\"xha\",\"xhc\",\"xhd\",\"xhe\",\"xhr\",\"xht\",\"xhu\",\"xhv\",\"xia\",\"xib\",\"xii\",\"xil\",\"xin\",\"xip\",\"xir\",\"xis\",\"xiv\",\"xiy\",\"xjb\",\"xjt\",\"xka\",\"xkb\",\"xkc\",\"xkd\",\"xke\",\"xkf\",\"xkg\",\"xkh\",\"xki\",\"xkj\",\"xkk\",\"xkl\",\"xkn\",\"xko\",\"xkp\",\"xkq\",\"xkr\",\"xks\",\"xkt\",\"xku\",\"xkv\",\"xkw\",\"xkx\",\"xky\",\"xkz\",\"xla\",\"xlb\",\"xlc\",\"xld\",\"xle\",\"xlg\",\"xli\",\"xln\",\"xlo\",\"xlp\",\"xls\",\"xlu\",\"xly\",\"xma\",\"xmb\",\"xmc\",\"xmd\",\"xme\",\"xmf\",\"xmg\",\"xmh\",\"xmj\",\"xmk\",\"xml\",\"xmm\",\"xmn\",\"xmo\",\"xmp\",\"xmq\",\"xmr\",\"xms\",\"xmt\",\"xmu\",\"xmv\",\"xmw\",\"xmx\",\"xmy\",\"xmz\",\"xna\",\"xnb\",\"xnd\",\"xng\",\"xnh\",\"xni\",\"xnk\",\"xnn\",\"xno\",\"xnr\",\"xns\",\"xnt\",\"xnu\",\"xny\",\"xnz\",\"xoc\",\"xod\",\"xog\",\"xoi\",\"xok\",\"xom\",\"xon\",\"xoo\",\"xop\",\"xor\",\"xow\",\"xpa\",\"xpc\",\"xpe\",\"xpg\",\"xpi\",\"xpj\",\"xpk\",\"xpm\",\"xpn\",\"xpo\",\"xpp\",\"xpq\",\"xpr\",\"xps\",\"xpt\",\"xpu\",\"xpy\",\"xqa\",\"xqt\",\"xra\",\"xrb\",\"xrd\",\"xre\",\"xrg\",\"xri\",\"xrm\",\"xrn\",\"xrq\",\"xrr\",\"xrt\",\"xru\",\"xrw\",\"xsa\",\"xsb\",\"xsc\",\"xsd\",\"xse\",\"xsh\",\"xsi\",\"xsj\",\"xsl\",\"xsm\",\"xsn\",\"xso\",\"xsp\",\"xsq\",\"xsr\",\"xss\",\"xsu\",\"xsv\",\"xsy\",\"xta\",\"xtb\",\"xtc\",\"xtd\",\"xte\",\"xtg\",\"xth\",\"xti\",\"xtj\",\"xtl\",\"xtm\",\"xtn\",\"xto\",\"xtp\",\"xtq\",\"xtr\",\"xts\",\"xtt\",\"xtu\",\"xtv\",\"xtw\",\"xty\",\"xtz\",\"xua\",\"xub\",\"xud\",\"xug\",\"xuj\",\"xul\",\"xum\",\"xun\",\"xuo\",\"xup\",\"xur\",\"xut\",\"xuu\",\"xve\",\"xvi\",\"xvn\",\"xvo\",\"xvs\",\"xwa\",\"xwc\",\"xwd\",\"xwe\",\"xwg\",\"xwj\",\"xwk\",\"xwl\",\"xwo\",\"xwr\",\"xwt\",\"xww\",\"xxb\",\"xxk\",\"xxm\",\"xxr\",\"xxt\",\"xya\",\"xyb\",\"xyj\",\"xyk\",\"xyl\",\"xyt\",\"xyy\",\"xzh\",\"xzm\",\"xzp\",\"yaa\",\"yab\",\"yac\",\"yad\",\"yae\",\"yaf\",\"yag\",\"yah\",\"yai\",\"yaj\",\"yak\",\"yal\",\"yam\",\"yan\",\"yao\",\"yap\",\"yaq\",\"yar\",\"yas\",\"yat\",\"yau\",\"yav\",\"yaw\",\"yax\",\"yay\",\"yaz\",\"yba\",\"ybb\",\"ybd\",\"ybe\",\"ybh\",\"ybi\",\"ybj\",\"ybk\",\"ybl\",\"ybm\",\"ybn\",\"ybo\",\"ybx\",\"yby\",\"ych\",\"ycl\",\"ycn\",\"ycp\",\"yda\",\"ydd\",\"yde\",\"ydg\",\"ydk\",\"yds\",\"yea\",\"yec\",\"yee\",\"yei\",\"yej\",\"yel\",\"yen\",\"yer\",\"yes\",\"yet\",\"yeu\",\"yev\",\"yey\",\"yga\",\"ygi\",\"ygl\",\"ygm\",\"ygp\",\"ygr\",\"ygs\",\"ygu\",\"ygw\",\"yha\",\"yhd\",\"yhl\",\"yhs\",\"yia\",\"yif\",\"yig\",\"yih\",\"yii\",\"yij\",\"yik\",\"yil\",\"yim\",\"yin\",\"yip\",\"yiq\",\"yir\",\"yis\",\"yit\",\"yiu\",\"yiv\",\"yix\",\"yiy\",\"yiz\",\"yka\",\"ykg\",\"yki\",\"ykk\",\"ykl\",\"ykm\",\"ykn\",\"yko\",\"ykr\",\"ykt\",\"yku\",\"yky\",\"yla\",\"ylb\",\"yle\",\"ylg\",\"yli\",\"yll\",\"ylm\",\"yln\",\"ylo\",\"ylr\",\"ylu\",\"yly\",\"yma\",\"ymb\",\"ymc\",\"ymd\",\"yme\",\"ymg\",\"ymh\",\"ymi\",\"ymk\",\"yml\",\"ymm\",\"ymn\",\"ymo\",\"ymp\",\"ymq\",\"ymr\",\"yms\",\"ymt\",\"ymx\",\"ymz\",\"yna\",\"ynd\",\"yne\",\"yng\",\"ynh\",\"ynk\",\"ynl\",\"ynn\",\"yno\",\"ynq\",\"yns\",\"ynu\",\"yob\",\"yog\",\"yoi\",\"yok\",\"yol\",\"yom\",\"yon\",\"yos\",\"yot\",\"yox\",\"yoy\",\"ypa\",\"ypb\",\"ypg\",\"yph\",\"ypk\",\"ypm\",\"ypn\",\"ypo\",\"ypp\",\"ypz\",\"yra\",\"yrb\",\"yre\",\"yri\",\"yrk\",\"yrl\",\"yrm\",\"yrn\",\"yro\",\"yrs\",\"yrw\",\"yry\",\"ysc\",\"ysd\",\"ysg\",\"ysl\",\"ysn\",\"yso\",\"ysp\",\"ysr\",\"yss\",\"ysy\",\"yta\",\"ytl\",\"ytp\",\"ytw\",\"yty\",\"yua\",\"yub\",\"yuc\",\"yud\",\"yue\",\"yuf\",\"yug\",\"yui\",\"yuj\",\"yuk\",\"yul\",\"yum\",\"yun\",\"yup\",\"yuq\",\"yur\",\"yut\",\"yuu\",\"yuw\",\"yux\",\"yuy\",\"yuz\",\"yva\",\"yvt\",\"ywa\",\"ywg\",\"ywl\",\"ywn\",\"ywq\",\"ywr\",\"ywt\",\"ywu\",\"yww\",\"yxa\",\"yxg\",\"yxl\",\"yxm\",\"yxu\",\"yxy\",\"yyr\",\"yyu\",\"yyz\",\"yzg\",\"yzk\",\"zaa\",\"zab\",\"zac\",\"zad\",\"zae\",\"zaf\",\"zag\",\"zah\",\"zai\",\"zaj\",\"zak\",\"zal\",\"zam\",\"zao\",\"zap\",\"zaq\",\"zar\",\"zas\",\"zat\",\"zau\",\"zav\",\"zaw\",\"zax\",\"zay\",\"zaz\",\"zbc\",\"zbe\",\"zbl\",\"zbt\",\"zbw\",\"zca\",\"zch\",\"zdj\",\"zea\",\"zeg\",\"zeh\",\"zen\",\"zga\",\"zgb\",\"zgh\",\"zgm\",\"zgn\",\"zgr\",\"zhb\",\"zhd\",\"zhi\",\"zhn\",\"zhw\",\"zhx\",\"zia\",\"zib\",\"zik\",\"zil\",\"zim\",\"zin\",\"zir\",\"ziw\",\"ziz\",\"zka\",\"zkb\",\"zkd\",\"zkg\",\"zkh\",\"zkk\",\"zkn\",\"zko\",\"zkp\",\"zkr\",\"zkt\",\"zku\",\"zkv\",\"zkz\",\"zle\",\"zlj\",\"zlm\",\"zln\",\"zlq\",\"zls\",\"zlw\",\"zma\",\"zmb\",\"zmc\",\"zmd\",\"zme\",\"zmf\",\"zmg\",\"zmh\",\"zmi\",\"zmj\",\"zmk\",\"zml\",\"zmm\",\"zmn\",\"zmo\",\"zmp\",\"zmq\",\"zmr\",\"zms\",\"zmt\",\"zmu\",\"zmv\",\"zmw\",\"zmx\",\"zmy\",\"zmz\",\"zna\",\"znd\",\"zne\",\"zng\",\"znk\",\"zns\",\"zoc\",\"zoh\",\"zom\",\"zoo\",\"zoq\",\"zor\",\"zos\",\"zpa\",\"zpb\",\"zpc\",\"zpd\",\"zpe\",\"zpf\",\"zpg\",\"zph\",\"zpi\",\"zpj\",\"zpk\",\"zpl\",\"zpm\",\"zpn\",\"zpo\",\"zpp\",\"zpq\",\"zpr\",\"zps\",\"zpt\",\"zpu\",\"zpv\",\"zpw\",\"zpx\",\"zpy\",\"zpz\",\"zqe\",\"zra\",\"zrg\",\"zrn\",\"zro\",\"zrp\",\"zrs\",\"zsa\",\"zsk\",\"zsl\",\"zsm\",\"zsr\",\"zsu\",\"zte\",\"ztg\",\"ztl\",\"ztm\",\"ztn\",\"ztp\",\"ztq\",\"zts\",\"ztt\",\"ztu\",\"ztx\",\"zty\",\"zua\",\"zuh\",\"zum\",\"zun\",\"zuy\",\"zwa\",\"zxx\",\"zyb\",\"zyg\",\"zyj\",\"zyn\",\"zyp\",\"zza\",\"zzj\"],\nid:\"valid-lang\"}]},{id:\"video-caption\",selector:\"video\",excludeHidden:!1,tags:[\"cat.text-alternatives\",\"wcag2a\",\"wcag122\",\"wcag123\",\"section508\",\"section508.22.a\"],all:[],any:[],none:[\"caption\"]},{id:\"video-description\",selector:\"video\",excludeHidden:!1,tags:[\"cat.text-alternatives\",\"wcag2aa\",\"wcag125\",\"section508\",\"section508.22.b\"],all:[],any:[],none:[\"description\"]}],checks:[{id:\"abstractrole\",evaluate:function(a,b){return\"abstract\"===axe.commons.aria.getRoleType(a.getAttribute(\"role\"))}},{id:\"aria-allowed-attr\",evaluate:function(a,b){var c,d,e,f=[],g=a.getAttribute(\"role\"),h=a.attributes;if(g||(g=axe.commons.aria.implicitRole(a)),e=axe.commons.aria.allowedAttr(g),g&&e)for(var i=0,j=h.length;i<j;i++)c=h[i],d=c.name,axe.commons.aria.validateAttr(d)&&-1===e.indexOf(d)&&f.push(d+'=\"'+c.nodeValue+'\"');return!f.length||(this.data(f),!1)}},{id:\"invalidrole\",evaluate:function(a,b){return!axe.commons.aria.isValidRole(a.getAttribute(\"role\"))}},{id:\"aria-required-attr\",evaluate:function(a,b){var c=[];if(a.hasAttributes()){var d,e=a.getAttribute(\"role\"),f=axe.commons.aria.requiredAttr(e);if(e&&f)for(var g=0,h=f.length;g<h;g++)d=f[g],a.getAttribute(d)||c.push(d)}return!c.length||(this.data(c),!1)}},{id:\"aria-required-children\",evaluate:function(a,b){function c(a,b,c){if(null===a)return!1;var d=f(b),e=['[role=\"'+b+'\"]'];return d&&(e=e.concat(d)),e=e.join(\",\"),c?g(a,e)||!!a.querySelector(e):!!a.querySelector(e)}function d(a,b){var d,e;for(d=0,e=a.length;d<e;d++)if(null!==a[d]&&c(a[d],b,!0))return!0;return!1}var e=axe.commons.aria.requiredOwned,f=axe.commons.aria.implicitNodes,g=axe.commons.utils.matchesSelector,h=axe.commons.dom.idrefs,i=a.getAttribute(\"role\"),j=e(i);if(!j)return!0;var k=!1,l=j.one;if(!l){var k=!0;l=j.all}var m=function(a,b,e){var f,g=b.length,i=[],j=h(a,\"aria-owns\");for(f=0;f<g;f++){var k=b[f];if(c(a,k)||d(j,k)){if(!e)return null}else e&&i.push(k)}return i.length?i:!e&&b.length?b:null}(a,l,k);return!m||(this.data(m),!1)}},{id:\"aria-required-parent\",evaluate:function(a,b){function c(a){return(axe.commons.aria.implicitNodes(a)||[]).concat('[role=\"'+a+'\"]').join(\",\")}function d(a,b,d){var e,f,g=a.getAttribute(\"role\"),h=[];if(b||(b=axe.commons.aria.requiredContext(g)),!b)return null;for(e=0,f=b.length;e<f;e++){if(d&&axe.utils.matchesSelector(a,c(b[e])))return null;if(axe.commons.dom.findUp(a,c(b[e])))return null;h.push(b[e])}return h}var e=d(a);if(!e)return!0;var f=function(a){for(var b=[],c=null;a;)a.id&&(c=document.querySelector(\"[aria-owns~=\"+axe.commons.utils.escapeSelector(a.id)+\"]\"))&&b.push(c),a=a.parentNode;return b.length?b:null}(a);if(f)for(var g=0,h=f.length;g<h;g++)if(!(e=d(f[g],e,!0)))return!0;return this.data(e),!1}},{id:\"aria-valid-attr-value\",evaluate:function(a,b){b=Array.isArray(b)?b:[];for(var c,d,e=[],f=/^aria-/,g=a.attributes,h=0,i=g.length;h<i;h++)c=g[h],d=c.name,-1===b.indexOf(d)&&f.test(d)&&!axe.commons.aria.validateAttrValue(a,d)&&e.push(d+'=\"'+c.nodeValue+'\"');return!e.length||(this.data(e),!1)},options:[]},{id:\"aria-valid-attr\",evaluate:function(a,b){b=Array.isArray(b)?b:[];for(var c,d=[],e=/^aria-/,f=a.attributes,g=0,h=f.length;g<h;g++)c=f[g].name,-1===b.indexOf(c)&&e.test(c)&&!axe.commons.aria.validateAttr(c)&&d.push(c);return!d.length||(this.data(d),!1)},options:[]},{id:\"color-contrast\",evaluate:function(a,b){if(!axe.commons.dom.isVisible(a,!1))return!0;var c=!!(b||{}).noScroll,d=[],e=axe.commons.color.getBackgroundColor(a,d,c),f=axe.commons.color.getForegroundColor(a,c),g=window.getComputedStyle(a),h=parseFloat(g.getPropertyValue(\"font-size\")),i=g.getPropertyValue(\"font-weight\"),j=-1!==[\"bold\",\"bolder\",\"600\",\"700\",\"800\",\"900\"].indexOf(i),k=axe.commons.color.hasValidContrastRatio(e,f,h,j),l=Math.floor(100*k.contrastRatio)/100,m=[];null===f&&m.push(axe.commons.color.incompleteData.get(\"fgColor\")),null===e&&m.push(axe.commons.color.incompleteData.get(\"bgColor\"));var n={fgColor:f?f.toHexString():void 0,bgColor:e?e.toHexString():void 0,contrastRatio:k?l:void 0,fontSize:(72*h/96).toFixed(1)+\"pt\",fontWeight:j?\"bold\":\"normal\",missingData:m};return this.data(n),k.isValid||this.relatedNodes(d),null===f||null===e?(m=[],void axe.commons.color.incompleteData.clear()):k.isValid}},{id:\"link-in-text-block\",evaluate:function(a,b){function c(a,b){var c=a.getRelativeLuminance(),d=b.getRelativeLuminance();return(Math.max(c,d)+.05)/(Math.min(c,d)+.05)}function d(a){var b=window.getComputedStyle(a).getPropertyValue(\"display\");return-1!==f.indexOf(b)||\"table-\"===b.substr(0,6)}var e=axe.commons.color,f=[\"block\",\"list-item\",\"table\",\"flex\",\"grid\",\"inline-block\"];if(d(a))return!1;for(var g=a.parentNode;1===g.nodeType&&!d(g);)g=g.parentNode;if(e.elementIsDistinct(a,g))return!0;var h,i;if(h=e.getForegroundColor(a),i=e.getForegroundColor(g),h&&i){var j=c(h,i);if(1===j)return!0;if(j>=3)return axe.commons.color.incompleteData.set(\"fgColor\",{node:h?g:a,reason:\"bgContrast\"}),this.data({missingData:[axe.commons.color.incompleteData.get(\"fgColor\")]}),void axe.commons.color.incompleteData.clear();if(h=e.getBackgroundColor(a),i=e.getBackgroundColor(g),!h||!i||c(h,i)>=3){var k=void 0;return k=h&&i?\"bgContrast\":axe.commons.color.incompleteData.get(\"bgColor\").reason,axe.commons.color.incompleteData.set(\"fgColor\",{node:h?g:a,reason:k}),this.data({missingData:[axe.commons.color.incompleteData.get(\"fgColor\")]}),void axe.commons.color.incompleteData.clear()}return!1}}},{id:\"fieldset\",evaluate:function(a,b){function c(a,b){return axe.commons.utils.toArray(a.querySelectorAll('select,textarea,button,input:not([name=\"'+b+'\"]):not([type=\"hidden\"])'))}function d(a,b){var d=a.firstElementChild;if(!d||\"LEGEND\"!==d.nodeName.toUpperCase())return h.relatedNodes([a]),g=\"no-legend\",!1;if(!axe.commons.text.accessibleText(d))return h.relatedNodes([d]),g=\"empty-legend\",!1;var e=c(a,b);return!e.length||(h.relatedNodes(e),g=\"mixed-inputs\",!1)}function e(a,b){var d=axe.commons.dom.idrefs(a,\"aria-labelledby\").some(function(a){return a&&axe.commons.text.accessibleText(a)}),e=a.getAttribute(\"aria-label\");if(!(d||e&&axe.commons.text.sanitize(e)))return h.relatedNodes(a),g=\"no-group-label\",!1;var f=c(a,b);return!f.length||(h.relatedNodes(f),g=\"group-mixed-inputs\",!1)}function f(a,b){return axe.commons.utils.toArray(a).filter(function(a){return a!==b})}var g,h=this,i={name:a.getAttribute(\"name\"),type:a.getAttribute(\"type\")},j=function(b){var c=axe.commons.utils.escapeSelector(a.name),i=document.querySelectorAll('input[type=\"'+axe.commons.utils.escapeSelector(a.type)+'\"][name=\"'+c+'\"]');if(i.length<2)return!0;var j=axe.commons.dom.findUp(b,\"fieldset\"),k=axe.commons.dom.findUp(b,'[role=\"group\"]'+(\"radio\"===a.type?',[role=\"radiogroup\"]':\"\"));return k||j?j?d(j,c):e(k,c):(g=\"no-group\",h.relatedNodes(f(i,b)),!1)}(a);return j||(i.failureCode=g),this.data(i),j},after:function(a,b){var c={};return a.filter(function(a){if(a.result)return!0;var b=a.data;if(b){if(c[b.type]=c[b.type]||{},!c[b.type][b.name])return c[b.type][b.name]=[b],!0;var d=c[b.type][b.name].some(function(a){return a.failureCode===b.failureCode});return d||c[b.type][b.name].push(b),!d}return!1})}},{id:\"group-labelledby\",evaluate:function(a,b){this.data({name:a.getAttribute(\"name\"),type:a.getAttribute(\"type\")});var c=document.querySelectorAll('input[type=\"'+axe.commons.utils.escapeSelector(a.type)+'\"][name=\"'+axe.commons.utils.escapeSelector(a.name)+'\"]');return c.length<=1||0!==[].map.call(c,function(a){var b=a.getAttribute(\"aria-labelledby\");return b?b.split(/\\s+/):[]}).reduce(function(a,b){return a.filter(function(a){return-1!==b.indexOf(a)})}).filter(function(a){var b=document.getElementById(a);return b&&axe.commons.text.accessibleText(b)}).length},after:function(a,b){var c={};return a.filter(function(a){var b=a.data;return!(!b||(c[b.type]=c[b.type]||{},c[b.type][b.name]))&&(c[b.type][b.name]=!0,!0)})}},{id:\"accesskeys\",evaluate:function(a,b){return axe.commons.dom.isVisible(a,!1)&&(this.data(a.getAttribute(\"accesskey\")),this.relatedNodes([a])),!0},after:function(a,b){var c={};return a.filter(function(a){if(!a.data)return!1;var b=a.data.toUpperCase();return c[b]?(c[b].relatedNodes.push(a.relatedNodes[0]),!1):(c[b]=a,a.relatedNodes=[],!0)}).map(function(a){return a.result=!!a.relatedNodes.length,a})}},{id:\"focusable-no-name\",evaluate:function(a,b){var c=a.getAttribute(\"tabindex\");return!!(axe.commons.dom.isFocusable(a)&&c>-1)&&!axe.commons.text.accessibleText(a)}},{id:\"tabindex\",evaluate:function(a,b){return a.tabIndex<=0}},{id:\"duplicate-img-label\",evaluate:function(a,b){var c=a.querySelectorAll(\"img\"),d=axe.commons.text.visible(a,!0).toLowerCase();if(\"\"===d)return!1;for(var e=0,f=c.length;e<f;e++){var g=c[e];if(axe.commons.text.accessibleText(g).toLowerCase()===d&&\"presentation\"!==g.getAttribute(\"role\")&&axe.commons.dom.isVisible(g))return!0}return!1}},{id:\"explicit-label\",evaluate:function(a,b){if(a.id){var c=document.querySelector('label[for=\"'+axe.commons.utils.escapeSelector(a.id)+'\"]');if(c)return!!axe.commons.text.accessibleText(c)}return!1}},{id:\"help-same-as-label\",evaluate:function(a,b){var c=axe.commons.text.label(a),d=a.getAttribute(\"title\");if(!c)return!1;if(!d&&(d=\"\",a.getAttribute(\"aria-describedby\"))){d=axe.commons.dom.idrefs(a,\"aria-describedby\").map(function(a){return a?axe.commons.text.accessibleText(a):\"\"}).join(\"\")}return axe.commons.text.sanitize(d)===axe.commons.text.sanitize(c)},enabled:!1},{id:\"implicit-label\",evaluate:function(a,b){var c=axe.commons.dom.findUp(a,\"label\");return!!c&&!!axe.commons.text.accessibleText(c)}},{id:\"multiple-label\",evaluate:function(a,b){var c=[].slice.call(document.querySelectorAll('label[for=\"'+axe.commons.utils.escapeSelector(a.id)+'\"]')),d=a.parentNode;for(c.length&&(c=c.filter(function(a,b){if(0===b&&!axe.commons.dom.isVisible(a,!0)||axe.commons.dom.isVisible(a,!0))return a}));d;)\"LABEL\"===d.tagName&&-1===c.indexOf(d)&&c.push(d),d=d.parentNode;return this.relatedNodes(c),c.length>1}},{id:\"title-only\",evaluate:function(a,b){return!(axe.commons.text.label(a)||!a.getAttribute(\"title\")&&!a.getAttribute(\"aria-describedby\"))}},{id:\"has-lang\",evaluate:function(a,b){return!!(a.getAttribute(\"lang\")||a.getAttribute(\"xml:lang\")||\"\").trim()}},{id:\"valid-lang\",\noptions:[\"aa\",\"ab\",\"ae\",\"af\",\"ak\",\"am\",\"an\",\"ar\",\"as\",\"av\",\"ay\",\"az\",\"ba\",\"be\",\"bg\",\"bh\",\"bi\",\"bm\",\"bn\",\"bo\",\"br\",\"bs\",\"ca\",\"ce\",\"ch\",\"co\",\"cr\",\"cs\",\"cu\",\"cv\",\"cy\",\"da\",\"de\",\"dv\",\"dz\",\"ee\",\"el\",\"en\",\"eo\",\"es\",\"et\",\"eu\",\"fa\",\"ff\",\"fi\",\"fj\",\"fo\",\"fr\",\"fy\",\"ga\",\"gd\",\"gl\",\"gn\",\"gu\",\"gv\",\"ha\",\"he\",\"hi\",\"ho\",\"hr\",\"ht\",\"hu\",\"hy\",\"hz\",\"ia\",\"id\",\"ie\",\"ig\",\"ii\",\"ik\",\"in\",\"io\",\"is\",\"it\",\"iu\",\"iw\",\"ja\",\"ji\",\"jv\",\"jw\",\"ka\",\"kg\",\"ki\",\"kj\",\"kk\",\"kl\",\"km\",\"kn\",\"ko\",\"kr\",\"ks\",\"ku\",\"kv\",\"kw\",\"ky\",\"la\",\"lb\",\"lg\",\"li\",\"ln\",\"lo\",\"lt\",\"lu\",\"lv\",\"mg\",\"mh\",\"mi\",\"mk\",\"ml\",\"mn\",\"mo\",\"mr\",\"ms\",\"mt\",\"my\",\"na\",\"nb\",\"nd\",\"ne\",\"ng\",\"nl\",\"nn\",\"no\",\"nr\",\"nv\",\"ny\",\"oc\",\"oj\",\"om\",\"or\",\"os\",\"pa\",\"pi\",\"pl\",\"ps\",\"pt\",\"qu\",\"rm\",\"rn\",\"ro\",\"ru\",\"rw\",\"sa\",\"sc\",\"sd\",\"se\",\"sg\",\"sh\",\"si\",\"sk\",\"sl\",\"sm\",\"sn\",\"so\",\"sq\",\"sr\",\"ss\",\"st\",\"su\",\"sv\",\"sw\",\"ta\",\"te\",\"tg\",\"th\",\"ti\",\"tk\",\"tl\",\"tn\",\"to\",\"tr\",\"ts\",\"tt\",\"tw\",\"ty\",\"ug\",\"uk\",\"ur\",\"uz\",\"ve\",\"vi\",\"vo\",\"wa\",\"wo\",\"xh\",\"yi\",\"yo\",\"za\",\"zh\",\"zu\",\"aaa\",\"aab\",\"aac\",\"aad\",\"aae\",\"aaf\",\"aag\",\"aah\",\"aai\",\"aak\",\"aal\",\"aam\",\"aan\",\"aao\",\"aap\",\"aaq\",\"aas\",\"aat\",\"aau\",\"aav\",\"aaw\",\"aax\",\"aaz\",\"aba\",\"abb\",\"abc\",\"abd\",\"abe\",\"abf\",\"abg\",\"abh\",\"abi\",\"abj\",\"abl\",\"abm\",\"abn\",\"abo\",\"abp\",\"abq\",\"abr\",\"abs\",\"abt\",\"abu\",\"abv\",\"abw\",\"abx\",\"aby\",\"abz\",\"aca\",\"acb\",\"acd\",\"ace\",\"acf\",\"ach\",\"aci\",\"ack\",\"acl\",\"acm\",\"acn\",\"acp\",\"acq\",\"acr\",\"acs\",\"act\",\"acu\",\"acv\",\"acw\",\"acx\",\"acy\",\"acz\",\"ada\",\"adb\",\"add\",\"ade\",\"adf\",\"adg\",\"adh\",\"adi\",\"adj\",\"adl\",\"adn\",\"ado\",\"adp\",\"adq\",\"adr\",\"ads\",\"adt\",\"adu\",\"adw\",\"adx\",\"ady\",\"adz\",\"aea\",\"aeb\",\"aec\",\"aed\",\"aee\",\"aek\",\"ael\",\"aem\",\"aen\",\"aeq\",\"aer\",\"aes\",\"aeu\",\"aew\",\"aey\",\"aez\",\"afa\",\"afb\",\"afd\",\"afe\",\"afg\",\"afh\",\"afi\",\"afk\",\"afn\",\"afo\",\"afp\",\"afs\",\"aft\",\"afu\",\"afz\",\"aga\",\"agb\",\"agc\",\"agd\",\"age\",\"agf\",\"agg\",\"agh\",\"agi\",\"agj\",\"agk\",\"agl\",\"agm\",\"agn\",\"ago\",\"agp\",\"agq\",\"agr\",\"ags\",\"agt\",\"agu\",\"agv\",\"agw\",\"agx\",\"agy\",\"agz\",\"aha\",\"ahb\",\"ahg\",\"ahh\",\"ahi\",\"ahk\",\"ahl\",\"ahm\",\"ahn\",\"aho\",\"ahp\",\"ahr\",\"ahs\",\"aht\",\"aia\",\"aib\",\"aic\",\"aid\",\"aie\",\"aif\",\"aig\",\"aih\",\"aii\",\"aij\",\"aik\",\"ail\",\"aim\",\"ain\",\"aio\",\"aip\",\"aiq\",\"air\",\"ais\",\"ait\",\"aiw\",\"aix\",\"aiy\",\"aja\",\"ajg\",\"aji\",\"ajn\",\"ajp\",\"ajt\",\"aju\",\"ajw\",\"ajz\",\"akb\",\"akc\",\"akd\",\"ake\",\"akf\",\"akg\",\"akh\",\"aki\",\"akj\",\"akk\",\"akl\",\"akm\",\"ako\",\"akp\",\"akq\",\"akr\",\"aks\",\"akt\",\"aku\",\"akv\",\"akw\",\"akx\",\"aky\",\"akz\",\"ala\",\"alc\",\"ald\",\"ale\",\"alf\",\"alg\",\"alh\",\"ali\",\"alj\",\"alk\",\"all\",\"alm\",\"aln\",\"alo\",\"alp\",\"alq\",\"alr\",\"als\",\"alt\",\"alu\",\"alv\",\"alw\",\"alx\",\"aly\",\"alz\",\"ama\",\"amb\",\"amc\",\"ame\",\"amf\",\"amg\",\"ami\",\"amj\",\"amk\",\"aml\",\"amm\",\"amn\",\"amo\",\"amp\",\"amq\",\"amr\",\"ams\",\"amt\",\"amu\",\"amv\",\"amw\",\"amx\",\"amy\",\"amz\",\"ana\",\"anb\",\"anc\",\"and\",\"ane\",\"anf\",\"ang\",\"anh\",\"ani\",\"anj\",\"ank\",\"anl\",\"anm\",\"ann\",\"ano\",\"anp\",\"anq\",\"anr\",\"ans\",\"ant\",\"anu\",\"anv\",\"anw\",\"anx\",\"any\",\"anz\",\"aoa\",\"aob\",\"aoc\",\"aod\",\"aoe\",\"aof\",\"aog\",\"aoh\",\"aoi\",\"aoj\",\"aok\",\"aol\",\"aom\",\"aon\",\"aor\",\"aos\",\"aot\",\"aou\",\"aox\",\"aoz\",\"apa\",\"apb\",\"apc\",\"apd\",\"ape\",\"apf\",\"apg\",\"aph\",\"api\",\"apj\",\"apk\",\"apl\",\"apm\",\"apn\",\"apo\",\"app\",\"apq\",\"apr\",\"aps\",\"apt\",\"apu\",\"apv\",\"apw\",\"apx\",\"apy\",\"apz\",\"aqa\",\"aqc\",\"aqd\",\"aqg\",\"aql\",\"aqm\",\"aqn\",\"aqp\",\"aqr\",\"aqt\",\"aqz\",\"arb\",\"arc\",\"ard\",\"are\",\"arh\",\"ari\",\"arj\",\"ark\",\"arl\",\"arn\",\"aro\",\"arp\",\"arq\",\"arr\",\"ars\",\"art\",\"aru\",\"arv\",\"arw\",\"arx\",\"ary\",\"arz\",\"asa\",\"asb\",\"asc\",\"asd\",\"ase\",\"asf\",\"asg\",\"ash\",\"asi\",\"asj\",\"ask\",\"asl\",\"asn\",\"aso\",\"asp\",\"asq\",\"asr\",\"ass\",\"ast\",\"asu\",\"asv\",\"asw\",\"asx\",\"asy\",\"asz\",\"ata\",\"atb\",\"atc\",\"atd\",\"ate\",\"atg\",\"ath\",\"ati\",\"atj\",\"atk\",\"atl\",\"atm\",\"atn\",\"ato\",\"atp\",\"atq\",\"atr\",\"ats\",\"att\",\"atu\",\"atv\",\"atw\",\"atx\",\"aty\",\"atz\",\"aua\",\"aub\",\"auc\",\"aud\",\"aue\",\"auf\",\"aug\",\"auh\",\"aui\",\"auj\",\"auk\",\"aul\",\"aum\",\"aun\",\"auo\",\"aup\",\"auq\",\"aur\",\"aus\",\"aut\",\"auu\",\"auw\",\"aux\",\"auy\",\"auz\",\"avb\",\"avd\",\"avi\",\"avk\",\"avl\",\"avm\",\"avn\",\"avo\",\"avs\",\"avt\",\"avu\",\"avv\",\"awa\",\"awb\",\"awc\",\"awd\",\"awe\",\"awg\",\"awh\",\"awi\",\"awk\",\"awm\",\"awn\",\"awo\",\"awr\",\"aws\",\"awt\",\"awu\",\"awv\",\"aww\",\"awx\",\"awy\",\"axb\",\"axe\",\"axg\",\"axk\",\"axl\",\"axm\",\"axx\",\"aya\",\"ayb\",\"ayc\",\"ayd\",\"aye\",\"ayg\",\"ayh\",\"ayi\",\"ayk\",\"ayl\",\"ayn\",\"ayo\",\"ayp\",\"ayq\",\"ayr\",\"ays\",\"ayt\",\"ayu\",\"ayx\",\"ayy\",\"ayz\",\"aza\",\"azb\",\"azc\",\"azd\",\"azg\",\"azj\",\"azm\",\"azn\",\"azo\",\"azt\",\"azz\",\"baa\",\"bab\",\"bac\",\"bad\",\"bae\",\"baf\",\"bag\",\"bah\",\"bai\",\"baj\",\"bal\",\"ban\",\"bao\",\"bap\",\"bar\",\"bas\",\"bat\",\"bau\",\"bav\",\"baw\",\"bax\",\"bay\",\"baz\",\"bba\",\"bbb\",\"bbc\",\"bbd\",\"bbe\",\"bbf\",\"bbg\",\"bbh\",\"bbi\",\"bbj\",\"bbk\",\"bbl\",\"bbm\",\"bbn\",\"bbo\",\"bbp\",\"bbq\",\"bbr\",\"bbs\",\"bbt\",\"bbu\",\"bbv\",\"bbw\",\"bbx\",\"bby\",\"bbz\",\"bca\",\"bcb\",\"bcc\",\"bcd\",\"bce\",\"bcf\",\"bcg\",\"bch\",\"bci\",\"bcj\",\"bck\",\"bcl\",\"bcm\",\"bcn\",\"bco\",\"bcp\",\"bcq\",\"bcr\",\"bcs\",\"bct\",\"bcu\",\"bcv\",\"bcw\",\"bcy\",\"bcz\",\"bda\",\"bdb\",\"bdc\",\"bdd\",\"bde\",\"bdf\",\"bdg\",\"bdh\",\"bdi\",\"bdj\",\"bdk\",\"bdl\",\"bdm\",\"bdn\",\"bdo\",\"bdp\",\"bdq\",\"bdr\",\"bds\",\"bdt\",\"bdu\",\"bdv\",\"bdw\",\"bdx\",\"bdy\",\"bdz\",\"bea\",\"beb\",\"bec\",\"bed\",\"bee\",\"bef\",\"beg\",\"beh\",\"bei\",\"bej\",\"bek\",\"bem\",\"beo\",\"bep\",\"beq\",\"ber\",\"bes\",\"bet\",\"beu\",\"bev\",\"bew\",\"bex\",\"bey\",\"bez\",\"bfa\",\"bfb\",\"bfc\",\"bfd\",\"bfe\",\"bff\",\"bfg\",\"bfh\",\"bfi\",\"bfj\",\"bfk\",\"bfl\",\"bfm\",\"bfn\",\"bfo\",\"bfp\",\"bfq\",\"bfr\",\"bfs\",\"bft\",\"bfu\",\"bfw\",\"bfx\",\"bfy\",\"bfz\",\"bga\",\"bgb\",\"bgc\",\"bgd\",\"bge\",\"bgf\",\"bgg\",\"bgi\",\"bgj\",\"bgk\",\"bgl\",\"bgm\",\"bgn\",\"bgo\",\"bgp\",\"bgq\",\"bgr\",\"bgs\",\"bgt\",\"bgu\",\"bgv\",\"bgw\",\"bgx\",\"bgy\",\"bgz\",\"bha\",\"bhb\",\"bhc\",\"bhd\",\"bhe\",\"bhf\",\"bhg\",\"bhh\",\"bhi\",\"bhj\",\"bhk\",\"bhl\",\"bhm\",\"bhn\",\"bho\",\"bhp\",\"bhq\",\"bhr\",\"bhs\",\"bht\",\"bhu\",\"bhv\",\"bhw\",\"bhx\",\"bhy\",\"bhz\",\"bia\",\"bib\",\"bic\",\"bid\",\"bie\",\"bif\",\"big\",\"bij\",\"bik\",\"bil\",\"bim\",\"bin\",\"bio\",\"bip\",\"biq\",\"bir\",\"bit\",\"biu\",\"biv\",\"biw\",\"bix\",\"biy\",\"biz\",\"bja\",\"bjb\",\"bjc\",\"bjd\",\"bje\",\"bjf\",\"bjg\",\"bjh\",\"bji\",\"bjj\",\"bjk\",\"bjl\",\"bjm\",\"bjn\",\"bjo\",\"bjp\",\"bjq\",\"bjr\",\"bjs\",\"bjt\",\"bju\",\"bjv\",\"bjw\",\"bjx\",\"bjy\",\"bjz\",\"bka\",\"bkb\",\"bkc\",\"bkd\",\"bkf\",\"bkg\",\"bkh\",\"bki\",\"bkj\",\"bkk\",\"bkl\",\"bkm\",\"bkn\",\"bko\",\"bkp\",\"bkq\",\"bkr\",\"bks\",\"bkt\",\"bku\",\"bkv\",\"bkw\",\"bkx\",\"bky\",\"bkz\",\"bla\",\"blb\",\"blc\",\"bld\",\"ble\",\"blf\",\"blg\",\"blh\",\"bli\",\"blj\",\"blk\",\"bll\",\"blm\",\"bln\",\"blo\",\"blp\",\"blq\",\"blr\",\"bls\",\"blt\",\"blv\",\"blw\",\"blx\",\"bly\",\"blz\",\"bma\",\"bmb\",\"bmc\",\"bmd\",\"bme\",\"bmf\",\"bmg\",\"bmh\",\"bmi\",\"bmj\",\"bmk\",\"bml\",\"bmm\",\"bmn\",\"bmo\",\"bmp\",\"bmq\",\"bmr\",\"bms\",\"bmt\",\"bmu\",\"bmv\",\"bmw\",\"bmx\",\"bmy\",\"bmz\",\"bna\",\"bnb\",\"bnc\",\"bnd\",\"bne\",\"bnf\",\"bng\",\"bni\",\"bnj\",\"bnk\",\"bnl\",\"bnm\",\"bnn\",\"bno\",\"bnp\",\"bnq\",\"bnr\",\"bns\",\"bnt\",\"bnu\",\"bnv\",\"bnw\",\"bnx\",\"bny\",\"bnz\",\"boa\",\"bob\",\"boe\",\"bof\",\"bog\",\"boh\",\"boi\",\"boj\",\"bok\",\"bol\",\"bom\",\"bon\",\"boo\",\"bop\",\"boq\",\"bor\",\"bot\",\"bou\",\"bov\",\"bow\",\"box\",\"boy\",\"boz\",\"bpa\",\"bpb\",\"bpd\",\"bpg\",\"bph\",\"bpi\",\"bpj\",\"bpk\",\"bpl\",\"bpm\",\"bpn\",\"bpo\",\"bpp\",\"bpq\",\"bpr\",\"bps\",\"bpt\",\"bpu\",\"bpv\",\"bpw\",\"bpx\",\"bpy\",\"bpz\",\"bqa\",\"bqb\",\"bqc\",\"bqd\",\"bqf\",\"bqg\",\"bqh\",\"bqi\",\"bqj\",\"bqk\",\"bql\",\"bqm\",\"bqn\",\"bqo\",\"bqp\",\"bqq\",\"bqr\",\"bqs\",\"bqt\",\"bqu\",\"bqv\",\"bqw\",\"bqx\",\"bqy\",\"bqz\",\"bra\",\"brb\",\"brc\",\"brd\",\"brf\",\"brg\",\"brh\",\"bri\",\"brj\",\"brk\",\"brl\",\"brm\",\"brn\",\"bro\",\"brp\",\"brq\",\"brr\",\"brs\",\"brt\",\"bru\",\"brv\",\"brw\",\"brx\",\"bry\",\"brz\",\"bsa\",\"bsb\",\"bsc\",\"bse\",\"bsf\",\"bsg\",\"bsh\",\"bsi\",\"bsj\",\"bsk\",\"bsl\",\"bsm\",\"bsn\",\"bso\",\"bsp\",\"bsq\",\"bsr\",\"bss\",\"bst\",\"bsu\",\"bsv\",\"bsw\",\"bsx\",\"bsy\",\"bta\",\"btb\",\"btc\",\"btd\",\"bte\",\"btf\",\"btg\",\"bth\",\"bti\",\"btj\",\"btk\",\"btl\",\"btm\",\"btn\",\"bto\",\"btp\",\"btq\",\"btr\",\"bts\",\"btt\",\"btu\",\"btv\",\"btw\",\"btx\",\"bty\",\"btz\",\"bua\",\"bub\",\"buc\",\"bud\",\"bue\",\"buf\",\"bug\",\"buh\",\"bui\",\"buj\",\"buk\",\"bum\",\"bun\",\"buo\",\"bup\",\"buq\",\"bus\",\"but\",\"buu\",\"buv\",\"buw\",\"bux\",\"buy\",\"buz\",\"bva\",\"bvb\",\"bvc\",\"bvd\",\"bve\",\"bvf\",\"bvg\",\"bvh\",\"bvi\",\"bvj\",\"bvk\",\"bvl\",\"bvm\",\"bvn\",\"bvo\",\"bvp\",\"bvq\",\"bvr\",\"bvt\",\"bvu\",\"bvv\",\"bvw\",\"bvx\",\"bvy\",\"bvz\",\"bwa\",\"bwb\",\"bwc\",\"bwd\",\"bwe\",\"bwf\",\"bwg\",\"bwh\",\"bwi\",\"bwj\",\"bwk\",\"bwl\",\"bwm\",\"bwn\",\"bwo\",\"bwp\",\"bwq\",\"bwr\",\"bws\",\"bwt\",\"bwu\",\"bww\",\"bwx\",\"bwy\",\"bwz\",\"bxa\",\"bxb\",\"bxc\",\"bxd\",\"bxe\",\"bxf\",\"bxg\",\"bxh\",\"bxi\",\"bxj\",\"bxk\",\"bxl\",\"bxm\",\"bxn\",\"bxo\",\"bxp\",\"bxq\",\"bxr\",\"bxs\",\"bxu\",\"bxv\",\"bxw\",\"bxx\",\"bxz\",\"bya\",\"byb\",\"byc\",\"byd\",\"bye\",\"byf\",\"byg\",\"byh\",\"byi\",\"byj\",\"byk\",\"byl\",\"bym\",\"byn\",\"byo\",\"byp\",\"byq\",\"byr\",\"bys\",\"byt\",\"byv\",\"byw\",\"byx\",\"byy\",\"byz\",\"bza\",\"bzb\",\"bzc\",\"bzd\",\"bze\",\"bzf\",\"bzg\",\"bzh\",\"bzi\",\"bzj\",\"bzk\",\"bzl\",\"bzm\",\"bzn\",\"bzo\",\"bzp\",\"bzq\",\"bzr\",\"bzs\",\"bzt\",\"bzu\",\"bzv\",\"bzw\",\"bzx\",\"bzy\",\"bzz\",\"caa\",\"cab\",\"cac\",\"cad\",\"cae\",\"caf\",\"cag\",\"cah\",\"cai\",\"caj\",\"cak\",\"cal\",\"cam\",\"can\",\"cao\",\"cap\",\"caq\",\"car\",\"cas\",\"cau\",\"cav\",\"caw\",\"cax\",\"cay\",\"caz\",\"cba\",\"cbb\",\"cbc\",\"cbd\",\"cbe\",\"cbg\",\"cbh\",\"cbi\",\"cbj\",\"cbk\",\"cbl\",\"cbn\",\"cbo\",\"cbq\",\"cbr\",\"cbs\",\"cbt\",\"cbu\",\"cbv\",\"cbw\",\"cby\",\"cca\",\"ccc\",\"ccd\",\"cce\",\"ccg\",\"cch\",\"ccj\",\"ccl\",\"ccm\",\"ccn\",\"cco\",\"ccp\",\"ccq\",\"ccr\",\"ccs\",\"cda\",\"cdc\",\"cdd\",\"cde\",\"cdf\",\"cdg\",\"cdh\",\"cdi\",\"cdj\",\"cdm\",\"cdn\",\"cdo\",\"cdr\",\"cds\",\"cdy\",\"cdz\",\"cea\",\"ceb\",\"ceg\",\"cek\",\"cel\",\"cen\",\"cet\",\"cfa\",\"cfd\",\"cfg\",\"cfm\",\"cga\",\"cgc\",\"cgg\",\"cgk\",\"chb\",\"chc\",\"chd\",\"chf\",\"chg\",\"chh\",\"chj\",\"chk\",\"chl\",\"chm\",\"chn\",\"cho\",\"chp\",\"chq\",\"chr\",\"cht\",\"chw\",\"chx\",\"chy\",\"chz\",\"cia\",\"cib\",\"cic\",\"cid\",\"cie\",\"cih\",\"cik\",\"cim\",\"cin\",\"cip\",\"cir\",\"ciw\",\"ciy\",\"cja\",\"cje\",\"cjh\",\"cji\",\"cjk\",\"cjm\",\"cjn\",\"cjo\",\"cjp\",\"cjr\",\"cjs\",\"cjv\",\"cjy\",\"cka\",\"ckb\",\"ckh\",\"ckl\",\"ckn\",\"cko\",\"ckq\",\"ckr\",\"cks\",\"ckt\",\"cku\",\"ckv\",\"ckx\",\"cky\",\"ckz\",\"cla\",\"clc\",\"cld\",\"cle\",\"clh\",\"cli\",\"clj\",\"clk\",\"cll\",\"clm\",\"clo\",\"clt\",\"clu\",\"clw\",\"cly\",\"cma\",\"cmc\",\"cme\",\"cmg\",\"cmi\",\"cmk\",\"cml\",\"cmm\",\"cmn\",\"cmo\",\"cmr\",\"cms\",\"cmt\",\"cna\",\"cnb\",\"cnc\",\"cng\",\"cnh\",\"cni\",\"cnk\",\"cnl\",\"cno\",\"cns\",\"cnt\",\"cnu\",\"cnw\",\"cnx\",\"coa\",\"cob\",\"coc\",\"cod\",\"coe\",\"cof\",\"cog\",\"coh\",\"coj\",\"cok\",\"col\",\"com\",\"con\",\"coo\",\"cop\",\"coq\",\"cot\",\"cou\",\"cov\",\"cow\",\"cox\",\"coy\",\"coz\",\"cpa\",\"cpb\",\"cpc\",\"cpe\",\"cpf\",\"cpg\",\"cpi\",\"cpn\",\"cpo\",\"cpp\",\"cps\",\"cpu\",\"cpx\",\"cpy\",\"cqd\",\"cqu\",\"cra\",\"crb\",\"crc\",\"crd\",\"crf\",\"crg\",\"crh\",\"cri\",\"crj\",\"crk\",\"crl\",\"crm\",\"crn\",\"cro\",\"crp\",\"crq\",\"crr\",\"crs\",\"crt\",\"crv\",\"crw\",\"crx\",\"cry\",\"crz\",\"csa\",\"csb\",\"csc\",\"csd\",\"cse\",\"csf\",\"csg\",\"csh\",\"csi\",\"csj\",\"csk\",\"csl\",\"csm\",\"csn\",\"cso\",\"csq\",\"csr\",\"css\",\"cst\",\"csu\",\"csv\",\"csw\",\"csy\",\"csz\",\"cta\",\"ctc\",\"ctd\",\"cte\",\"ctg\",\"cth\",\"ctl\",\"ctm\",\"ctn\",\"cto\",\"ctp\",\"cts\",\"ctt\",\"ctu\",\"ctz\",\"cua\",\"cub\",\"cuc\",\"cug\",\"cuh\",\"cui\",\"cuj\",\"cuk\",\"cul\",\"cum\",\"cuo\",\"cup\",\"cuq\",\"cur\",\"cus\",\"cut\",\"cuu\",\"cuv\",\"cuw\",\"cux\",\"cvg\",\"cvn\",\"cwa\",\"cwb\",\"cwd\",\"cwe\",\"cwg\",\"cwt\",\"cya\",\"cyb\",\"cyo\",\"czh\",\"czk\",\"czn\",\"czo\",\"czt\",\"daa\",\"dac\",\"dad\",\"dae\",\"daf\",\"dag\",\"dah\",\"dai\",\"daj\",\"dak\",\"dal\",\"dam\",\"dao\",\"dap\",\"daq\",\"dar\",\"das\",\"dau\",\"dav\",\"daw\",\"dax\",\"day\",\"daz\",\"dba\",\"dbb\",\"dbd\",\"dbe\",\"dbf\",\"dbg\",\"dbi\",\"dbj\",\"dbl\",\"dbm\",\"dbn\",\"dbo\",\"dbp\",\"dbq\",\"dbr\",\"dbt\",\"dbu\",\"dbv\",\"dbw\",\"dby\",\"dcc\",\"dcr\",\"dda\",\"ddd\",\"dde\",\"ddg\",\"ddi\",\"ddj\",\"ddn\",\"ddo\",\"ddr\",\"dds\",\"ddw\",\"dec\",\"ded\",\"dee\",\"def\",\"deg\",\"deh\",\"dei\",\"dek\",\"del\",\"dem\",\"den\",\"dep\",\"deq\",\"der\",\"des\",\"dev\",\"dez\",\"dga\",\"dgb\",\"dgc\",\"dgd\",\"dge\",\"dgg\",\"dgh\",\"dgi\",\"dgk\",\"dgl\",\"dgn\",\"dgo\",\"dgr\",\"dgs\",\"dgt\",\"dgu\",\"dgw\",\"dgx\",\"dgz\",\"dha\",\"dhd\",\"dhg\",\"dhi\",\"dhl\",\"dhm\",\"dhn\",\"dho\",\"dhr\",\"dhs\",\"dhu\",\"dhv\",\"dhw\",\"dhx\",\"dia\",\"dib\",\"dic\",\"did\",\"dif\",\"dig\",\"dih\",\"dii\",\"dij\",\"dik\",\"dil\",\"dim\",\"din\",\"dio\",\"dip\",\"diq\",\"dir\",\"dis\",\"dit\",\"diu\",\"diw\",\"dix\",\"diy\",\"diz\",\"dja\",\"djb\",\"djc\",\"djd\",\"dje\",\"djf\",\"dji\",\"djj\",\"djk\",\"djl\",\"djm\",\"djn\",\"djo\",\"djr\",\"dju\",\"djw\",\"dka\",\"dkk\",\"dkl\",\"dkr\",\"dks\",\"dkx\",\"dlg\",\"dlk\",\"dlm\",\"dln\",\"dma\",\"dmb\",\"dmc\",\"dmd\",\"dme\",\"dmg\",\"dmk\",\"dml\",\"dmm\",\"dmn\",\"dmo\",\"dmr\",\"dms\",\"dmu\",\"dmv\",\"dmw\",\"dmx\",\"dmy\",\"dna\",\"dnd\",\"dne\",\"dng\",\"dni\",\"dnj\",\"dnk\",\"dnn\",\"dnr\",\"dnt\",\"dnu\",\"dnv\",\"dnw\",\"dny\",\"doa\",\"dob\",\"doc\",\"doe\",\"dof\",\"doh\",\"doi\",\"dok\",\"dol\",\"don\",\"doo\",\"dop\",\"doq\",\"dor\",\"dos\",\"dot\",\"dov\",\"dow\",\"dox\",\"doy\",\"doz\",\"dpp\",\"dra\",\"drb\",\"drc\",\"drd\",\"dre\",\"drg\",\"drh\",\"dri\",\"drl\",\"drn\",\"dro\",\"drq\",\"drr\",\"drs\",\"drt\",\"dru\",\"drw\",\"dry\",\"dsb\",\"dse\",\"dsh\",\"dsi\",\"dsl\",\"dsn\",\"dso\",\"dsq\",\"dta\",\"dtb\",\"dtd\",\"dth\",\"dti\",\"dtk\",\"dtm\",\"dtn\",\"dto\",\"dtp\",\"dtr\",\"dts\",\"dtt\",\"dtu\",\"dty\",\"dua\",\"dub\",\"duc\",\"dud\",\"due\",\"duf\",\"dug\",\"duh\",\"dui\",\"duj\",\"duk\",\"dul\",\"dum\",\"dun\",\"duo\",\"dup\",\"duq\",\"dur\",\"dus\",\"duu\",\"duv\",\"duw\",\"dux\",\"duy\",\"duz\",\"dva\",\"dwa\",\"dwl\",\"dwr\",\"dws\",\"dwu\",\"dww\",\"dwy\",\"dya\",\"dyb\",\"dyd\",\"dyg\",\"dyi\",\"dym\",\"dyn\",\"dyo\",\"dyu\",\"dyy\",\"dza\",\"dzd\",\"dze\",\"dzg\",\"dzl\",\"dzn\",\"eaa\",\"ebg\",\"ebk\",\"ebo\",\"ebr\",\"ebu\",\"ecr\",\"ecs\",\"ecy\",\"eee\",\"efa\",\"efe\",\"efi\",\"ega\",\"egl\",\"ego\",\"egx\",\"egy\",\"ehu\",\"eip\",\"eit\",\"eiv\",\"eja\",\"eka\",\"ekc\",\"eke\",\"ekg\",\"eki\",\"ekk\",\"ekl\",\"ekm\",\"eko\",\"ekp\",\"ekr\",\"eky\",\"ele\",\"elh\",\"eli\",\"elk\",\"elm\",\"elo\",\"elp\",\"elu\",\"elx\",\"ema\",\"emb\",\"eme\",\"emg\",\"emi\",\"emk\",\"emm\",\"emn\",\"emo\",\"emp\",\"ems\",\"emu\",\"emw\",\"emx\",\"emy\",\"ena\",\"enb\",\"enc\",\"end\",\"enf\",\"enh\",\"enl\",\"enm\",\"enn\",\"eno\",\"enq\",\"enr\",\"enu\",\"env\",\"enw\",\"enx\",\"eot\",\"epi\",\"era\",\"erg\",\"erh\",\"eri\",\"erk\",\"ero\",\"err\",\"ers\",\"ert\",\"erw\",\"ese\",\"esg\",\"esh\",\"esi\",\"esk\",\"esl\",\"esm\",\"esn\",\"eso\",\"esq\",\"ess\",\"esu\",\"esx\",\"esy\",\"etb\",\"etc\",\"eth\",\"etn\",\"eto\",\"etr\",\"ets\",\"ett\",\"etu\",\"etx\",\"etz\",\"euq\",\"eve\",\"evh\",\"evn\",\"ewo\",\"ext\",\"eya\",\"eyo\",\"eza\",\"eze\",\"faa\",\"fab\",\"fad\",\"faf\",\"fag\",\"fah\",\"fai\",\"faj\",\"fak\",\"fal\",\"fam\",\"fan\",\"fap\",\"far\",\"fat\",\"fau\",\"fax\",\"fay\",\"faz\",\"fbl\",\"fcs\",\"fer\",\"ffi\",\"ffm\",\"fgr\",\"fia\",\"fie\",\"fil\",\"fip\",\"fir\",\"fit\",\"fiu\",\"fiw\",\"fkk\",\"fkv\",\"fla\",\"flh\",\"fli\",\"fll\",\"fln\",\"flr\",\"fly\",\"fmp\",\"fmu\",\"fnb\",\"fng\",\"fni\",\"fod\",\"foi\",\"fom\",\"fon\",\"for\",\"fos\",\"fox\",\"fpe\",\"fqs\",\"frc\",\"frd\",\"frk\",\"frm\",\"fro\",\"frp\",\"frq\",\"frr\",\"frs\",\"frt\",\"fse\",\"fsl\",\"fss\",\"fub\",\"fuc\",\"fud\",\"fue\",\"fuf\",\"fuh\",\"fui\",\"fuj\",\"fum\",\"fun\",\"fuq\",\"fur\",\"fut\",\"fuu\",\"fuv\",\"fuy\",\"fvr\",\"fwa\",\"fwe\",\"gaa\",\"gab\",\"gac\",\"gad\",\"gae\",\"gaf\",\"gag\",\"gah\",\"gai\",\"gaj\",\"gak\",\"gal\",\"gam\",\"gan\",\"gao\",\"gap\",\"gaq\",\"gar\",\"gas\",\"gat\",\"gau\",\"gav\",\"gaw\",\"gax\",\"gay\",\"gaz\",\"gba\",\"gbb\",\"gbc\",\"gbd\",\"gbe\",\"gbf\",\"gbg\",\"gbh\",\"gbi\",\"gbj\",\"gbk\",\"gbl\",\"gbm\",\"gbn\",\"gbo\",\"gbp\",\"gbq\",\"gbr\",\"gbs\",\"gbu\",\"gbv\",\"gbw\",\"gbx\",\"gby\",\"gbz\",\"gcc\",\"gcd\",\"gce\",\"gcf\",\"gcl\",\"gcn\",\"gcr\",\"gct\",\"gda\",\"gdb\",\"gdc\",\"gdd\",\"gde\",\"gdf\",\"gdg\",\"gdh\",\"gdi\",\"gdj\",\"gdk\",\"gdl\",\"gdm\",\"gdn\",\"gdo\",\"gdq\",\"gdr\",\"gds\",\"gdt\",\"gdu\",\"gdx\",\"gea\",\"geb\",\"gec\",\"ged\",\"geg\",\"geh\",\"gei\",\"gej\",\"gek\",\"gel\",\"gem\",\"geq\",\"ges\",\"gev\",\"gew\",\"gex\",\"gey\",\"gez\",\"gfk\",\"gft\",\"gfx\",\"gga\",\"ggb\",\"ggd\",\"gge\",\"ggg\",\"ggk\",\"ggl\",\"ggn\",\"ggo\",\"ggr\",\"ggt\",\"ggu\",\"ggw\",\"gha\",\"ghc\",\"ghe\",\"ghh\",\"ghk\",\"ghl\",\"ghn\",\"gho\",\"ghr\",\"ghs\",\"ght\",\"gia\",\"gib\",\"gic\",\"gid\",\"gig\",\"gih\",\"gil\",\"gim\",\"gin\",\"gio\",\"gip\",\"giq\",\"gir\",\"gis\",\"git\",\"giu\",\"giw\",\"gix\",\"giy\",\"giz\",\"gji\",\"gjk\",\"gjm\",\"gjn\",\"gjr\",\"gju\",\"gka\",\"gke\",\"gkn\",\"gko\",\"gkp\",\"gku\",\"glc\",\"gld\",\"glh\",\"gli\",\"glj\",\"glk\",\"gll\",\"glo\",\"glr\",\"glu\",\"glw\",\"gly\",\"gma\",\"gmb\",\"gmd\",\"gme\",\"gmg\",\"gmh\",\"gml\",\"gmm\",\"gmn\",\"gmq\",\"gmu\",\"gmv\",\"gmw\",\"gmx\",\"gmy\",\"gmz\",\"gna\",\"gnb\",\"gnc\",\"gnd\",\"gne\",\"gng\",\"gnh\",\"gni\",\"gnk\",\"gnl\",\"gnm\",\"gnn\",\"gno\",\"gnq\",\"gnr\",\"gnt\",\"gnu\",\"gnw\",\"gnz\",\"goa\",\"gob\",\"goc\",\"god\",\"goe\",\"gof\",\"gog\",\"goh\",\"goi\",\"goj\",\"gok\",\"gol\",\"gom\",\"gon\",\"goo\",\"gop\",\"goq\",\"gor\",\"gos\",\"got\",\"gou\",\"gow\",\"gox\",\"goy\",\"goz\",\"gpa\",\"gpe\",\"gpn\",\"gqa\",\"gqi\",\"gqn\",\"gqr\",\"gqu\",\"gra\",\"grb\",\"grc\",\"grd\",\"grg\",\"grh\",\"gri\",\"grj\",\"grk\",\"grm\",\"gro\",\"grq\",\"grr\",\"grs\",\"grt\",\"gru\",\"grv\",\"grw\",\"grx\",\"gry\",\"grz\",\"gse\",\"gsg\",\"gsl\",\"gsm\",\"gsn\",\"gso\",\"gsp\",\"gss\",\"gsw\",\"gta\",\"gti\",\"gtu\",\"gua\",\"gub\",\"guc\",\"gud\",\"gue\",\"guf\",\"gug\",\"guh\",\"gui\",\"guk\",\"gul\",\"gum\",\"gun\",\"guo\",\"gup\",\"guq\",\"gur\",\"gus\",\"gut\",\"guu\",\"guv\",\"guw\",\"gux\",\"guz\",\"gva\",\"gvc\",\"gve\",\"gvf\",\"gvj\",\"gvl\",\"gvm\",\"gvn\",\"gvo\",\"gvp\",\"gvr\",\"gvs\",\"gvy\",\"gwa\",\"gwb\",\"gwc\",\"gwd\",\"gwe\",\"gwf\",\"gwg\",\"gwi\",\"gwj\",\"gwm\",\"gwn\",\"gwr\",\"gwt\",\"gwu\",\"gww\",\"gwx\",\"gxx\",\"gya\",\"gyb\",\"gyd\",\"gye\",\"gyf\",\"gyg\",\"gyi\",\"gyl\",\"gym\",\"gyn\",\"gyr\",\"gyy\",\"gza\",\"gzi\",\"gzn\",\"haa\",\"hab\",\"hac\",\"had\",\"hae\",\"haf\",\"hag\",\"hah\",\"hai\",\"haj\",\"hak\",\"hal\",\"ham\",\"han\",\"hao\",\"hap\",\"haq\",\"har\",\"has\",\"hav\",\"haw\",\"hax\",\"hay\",\"haz\",\"hba\",\"hbb\",\"hbn\",\"hbo\",\"hbu\",\"hca\",\"hch\",\"hdn\",\"hds\",\"hdy\",\"hea\",\"hed\",\"heg\",\"heh\",\"hei\",\"hem\",\"hgm\",\"hgw\",\"hhi\",\"hhr\",\"hhy\",\"hia\",\"hib\",\"hid\",\"hif\",\"hig\",\"hih\",\"hii\",\"hij\",\"hik\",\"hil\",\"him\",\"hio\",\"hir\",\"hit\",\"hiw\",\"hix\",\"hji\",\"hka\",\"hke\",\"hkk\",\"hks\",\"hla\",\"hlb\",\"hld\",\"hle\",\"hlt\",\"hlu\",\"hma\",\"hmb\",\"hmc\",\"hmd\",\"hme\",\"hmf\",\"hmg\",\"hmh\",\"hmi\",\"hmj\",\"hmk\",\"hml\",\"hmm\",\"hmn\",\"hmp\",\"hmq\",\"hmr\",\"hms\",\"hmt\",\"hmu\",\"hmv\",\"hmw\",\"hmx\",\"hmy\",\"hmz\",\"hna\",\"hnd\",\"hne\",\"hnh\",\"hni\",\"hnj\",\"hnn\",\"hno\",\"hns\",\"hnu\",\"hoa\",\"hob\",\"hoc\",\"hod\",\"hoe\",\"hoh\",\"hoi\",\"hoj\",\"hok\",\"hol\",\"hom\",\"hoo\",\"hop\",\"hor\",\"hos\",\"hot\",\"hov\",\"how\",\"hoy\",\"hoz\",\"hpo\",\"hps\",\"hra\",\"hrc\",\"hre\",\"hrk\",\"hrm\",\"hro\",\"hrp\",\"hrr\",\"hrt\",\"hru\",\"hrw\",\"hrx\",\"hrz\",\"hsb\",\"hsh\",\"hsl\",\"hsn\",\"hss\",\"hti\",\"hto\",\"hts\",\"htu\",\"htx\",\"hub\",\"huc\",\"hud\",\"hue\",\"huf\",\"hug\",\"huh\",\"hui\",\"huj\",\"huk\",\"hul\",\"hum\",\"huo\",\"hup\",\"huq\",\"hur\",\"hus\",\"hut\",\"huu\",\"huv\",\"huw\",\"hux\",\"huy\",\"huz\",\"hvc\",\"hve\",\"hvk\",\"hvn\",\"hvv\",\"hwa\",\"hwc\",\"hwo\",\"hya\",\"hyx\",\"iai\",\"ian\",\"iap\",\"iar\",\"iba\",\"ibb\",\"ibd\",\"ibe\",\"ibg\",\"ibi\",\"ibl\",\"ibm\",\"ibn\",\"ibr\",\"ibu\",\"iby\",\"ica\",\"ich\",\"icl\",\"icr\",\"ida\",\"idb\",\"idc\",\"idd\",\"ide\",\"idi\",\"idr\",\"ids\",\"idt\",\"idu\",\"ifa\",\"ifb\",\"ife\",\"iff\",\"ifk\",\"ifm\",\"ifu\",\"ify\",\"igb\",\"ige\",\"igg\",\"igl\",\"igm\",\"ign\",\"igo\",\"igs\",\"igw\",\"ihb\",\"ihi\",\"ihp\",\"ihw\",\"iin\",\"iir\",\"ijc\",\"ije\",\"ijj\",\"ijn\",\"ijo\",\"ijs\",\"ike\",\"iki\",\"ikk\",\"ikl\",\"iko\",\"ikp\",\"ikr\",\"iks\",\"ikt\",\"ikv\",\"ikw\",\"ikx\",\"ikz\",\"ila\",\"ilb\",\"ilg\",\"ili\",\"ilk\",\"ill\",\"ilm\",\"ilo\",\"ilp\",\"ils\",\"ilu\",\"ilv\",\"ilw\",\"ima\",\"ime\",\"imi\",\"iml\",\"imn\",\"imo\",\"imr\",\"ims\",\"imy\",\"inb\",\"inc\",\"ine\",\"ing\",\"inh\",\"inj\",\"inl\",\"inm\",\"inn\",\"ino\",\"inp\",\"ins\",\"int\",\"inz\",\"ior\",\"iou\",\"iow\",\"ipi\",\"ipo\",\"iqu\",\"iqw\",\"ira\",\"ire\",\"irh\",\"iri\",\"irk\",\"irn\",\"iro\",\"irr\",\"iru\",\"irx\",\"iry\",\"isa\",\"isc\",\"isd\",\"ise\",\"isg\",\"ish\",\"isi\",\"isk\",\"ism\",\"isn\",\"iso\",\"isr\",\"ist\",\"isu\",\"itb\",\"itc\",\"itd\",\"ite\",\"iti\",\"itk\",\"itl\",\"itm\",\"ito\",\"itr\",\"its\",\"itt\",\"itv\",\"itw\",\"itx\",\"ity\",\"itz\",\"ium\",\"ivb\",\"ivv\",\"iwk\",\"iwm\",\"iwo\",\"iws\",\"ixc\",\"ixl\",\"iya\",\"iyo\",\"iyx\",\"izh\",\"izi\",\"izr\",\"izz\",\"jaa\",\"jab\",\"jac\",\"jad\",\"jae\",\"jaf\",\"jah\",\"jaj\",\"jak\",\"jal\",\"jam\",\"jan\",\"jao\",\"jaq\",\"jar\",\"jas\",\"jat\",\"jau\",\"jax\",\"jay\",\"jaz\",\"jbe\",\"jbi\",\"jbj\",\"jbk\",\"jbn\",\"jbo\",\"jbr\",\"jbt\",\"jbu\",\"jbw\",\"jcs\",\"jct\",\"jda\",\"jdg\",\"jdt\",\"jeb\",\"jee\",\"jeg\",\"jeh\",\"jei\",\"jek\",\"jel\",\"jen\",\"jer\",\"jet\",\"jeu\",\"jgb\",\"jge\",\"jgk\",\"jgo\",\"jhi\",\"jhs\",\"jia\",\"jib\",\"jic\",\"jid\",\"jie\",\"jig\",\"jih\",\"jii\",\"jil\",\"jim\",\"jio\",\"jiq\",\"jit\",\"jiu\",\"jiv\",\"jiy\",\"jje\",\"jjr\",\"jka\",\"jkm\",\"jko\",\"jkp\",\"jkr\",\"jku\",\"jle\",\"jls\",\"jma\",\"jmb\",\"jmc\",\"jmd\",\"jmi\",\"jml\",\"jmn\",\"jmr\",\"jms\",\"jmw\",\"jmx\",\"jna\",\"jnd\",\"jng\",\"jni\",\"jnj\",\"jnl\",\"jns\",\"job\",\"jod\",\"jog\",\"jor\",\"jos\",\"jow\",\"jpa\",\"jpr\",\"jpx\",\"jqr\",\"jra\",\"jrb\",\"jrr\",\"jrt\",\"jru\",\"jsl\",\"jua\",\"jub\",\"juc\",\"jud\",\"juh\",\"jui\",\"juk\",\"jul\",\"jum\",\"jun\",\"juo\",\"jup\",\"jur\",\"jus\",\"jut\",\"juu\",\"juw\",\"juy\",\"jvd\",\"jvn\",\"jwi\",\"jya\",\"jye\",\"jyy\",\"kaa\",\"kab\",\"kac\",\"kad\",\"kae\",\"kaf\",\"kag\",\"kah\",\"kai\",\"kaj\",\"kak\",\"kam\",\"kao\",\"kap\",\"kaq\",\"kar\",\"kav\",\"kaw\",\"kax\",\"kay\",\"kba\",\"kbb\",\"kbc\",\"kbd\",\"kbe\",\"kbf\",\"kbg\",\"kbh\",\"kbi\",\"kbj\",\"kbk\",\"kbl\",\"kbm\",\"kbn\",\"kbo\",\"kbp\",\"kbq\",\"kbr\",\"kbs\",\"kbt\",\"kbu\",\"kbv\",\"kbw\",\"kbx\",\"kby\",\"kbz\",\"kca\",\"kcb\",\"kcc\",\"kcd\",\"kce\",\"kcf\",\"kcg\",\"kch\",\"kci\",\"kcj\",\"kck\",\"kcl\",\"kcm\",\"kcn\",\"kco\",\"kcp\",\"kcq\",\"kcr\",\"kcs\",\"kct\",\"kcu\",\"kcv\",\"kcw\",\"kcx\",\"kcy\",\"kcz\",\"kda\",\"kdc\",\"kdd\",\"kde\",\"kdf\",\"kdg\",\"kdh\",\"kdi\",\"kdj\",\"kdk\",\"kdl\",\"kdm\",\"kdn\",\"kdo\",\"kdp\",\"kdq\",\"kdr\",\"kdt\",\"kdu\",\"kdv\",\"kdw\",\"kdx\",\"kdy\",\"kdz\",\"kea\",\"keb\",\"kec\",\"ked\",\"kee\",\"kef\",\"keg\",\"keh\",\"kei\",\"kej\",\"kek\",\"kel\",\"kem\",\"ken\",\"keo\",\"kep\",\"keq\",\"ker\",\"kes\",\"ket\",\"keu\",\"kev\",\"kew\",\"kex\",\"key\",\"kez\",\"kfa\",\"kfb\",\"kfc\",\"kfd\",\"kfe\",\"kff\",\"kfg\",\"kfh\",\"kfi\",\"kfj\",\"kfk\",\"kfl\",\"kfm\",\"kfn\",\"kfo\",\"kfp\",\"kfq\",\"kfr\",\"kfs\",\"kft\",\"kfu\",\"kfv\",\"kfw\",\"kfx\",\"kfy\",\"kfz\",\"kga\",\"kgb\",\"kgc\",\"kgd\",\"kge\",\"kgf\",\"kgg\",\"kgh\",\"kgi\",\"kgj\",\"kgk\",\"kgl\",\"kgm\",\"kgn\",\"kgo\",\"kgp\",\"kgq\",\"kgr\",\"kgs\",\"kgt\",\"kgu\",\"kgv\",\"kgw\",\"kgx\",\"kgy\",\"kha\",\"khb\",\"khc\",\"khd\",\"khe\",\"khf\",\"khg\",\"khh\",\"khi\",\"khj\",\"khk\",\"khl\",\"khn\",\"kho\",\"khp\",\"khq\",\"khr\",\"khs\",\"kht\",\"khu\",\"khv\",\"khw\",\"khx\",\"khy\",\"khz\",\"kia\",\"kib\",\"kic\",\"kid\",\"kie\",\"kif\",\"kig\",\"kih\",\"kii\",\"kij\",\"kil\",\"kim\",\"kio\",\"kip\",\"kiq\",\"kis\",\"kit\",\"kiu\",\"kiv\",\"kiw\",\"kix\",\"kiy\",\"kiz\",\"kja\",\"kjb\",\"kjc\",\"kjd\",\"kje\",\"kjf\",\"kjg\",\"kjh\",\"kji\",\"kjj\",\"kjk\",\"kjl\",\"kjm\",\"kjn\",\"kjo\",\"kjp\",\"kjq\",\"kjr\",\"kjs\",\"kjt\",\"kju\",\"kjv\",\"kjx\",\"kjy\",\"kjz\",\"kka\",\"kkb\",\"kkc\",\"kkd\",\"kke\",\"kkf\",\"kkg\",\"kkh\",\"kki\",\"kkj\",\"kkk\",\"kkl\",\"kkm\",\"kkn\",\"kko\",\"kkp\",\"kkq\",\"kkr\",\"kks\",\"kkt\",\"kku\",\"kkv\",\"kkw\",\"kkx\",\"kky\",\"kkz\",\"kla\",\"klb\",\"klc\",\"kld\",\"kle\",\"klf\",\"klg\",\"klh\",\"kli\",\"klj\",\"klk\",\"kll\",\"klm\",\"kln\",\"klo\",\"klp\",\"klq\",\"klr\",\"kls\",\"klt\",\"klu\",\"klv\",\"klw\",\"klx\",\"kly\",\"klz\",\"kma\",\"kmb\",\"kmc\",\"kmd\",\"kme\",\"kmf\",\"kmg\",\"kmh\",\"kmi\",\"kmj\",\"kmk\",\"kml\",\"kmm\",\"kmn\",\"kmo\",\"kmp\",\"kmq\",\"kmr\",\"kms\",\"kmt\",\"kmu\",\"kmv\",\"kmw\",\"kmx\",\"kmy\",\"kmz\",\"kna\",\"knb\",\"knc\",\"knd\",\"kne\",\"knf\",\"kng\",\"kni\",\"knj\",\"knk\",\"knl\",\"knm\",\"knn\",\"kno\",\"knp\",\"knq\",\"knr\",\"kns\",\"knt\",\"knu\",\"knv\",\"knw\",\"knx\",\"kny\",\"knz\",\"koa\",\"koc\",\"kod\",\"koe\",\"kof\",\"kog\",\"koh\",\"koi\",\"koj\",\"kok\",\"kol\",\"koo\",\"kop\",\"koq\",\"kos\",\"kot\",\"kou\",\"kov\",\"kow\",\"kox\",\"koy\",\"koz\",\"kpa\",\"kpb\",\"kpc\",\"kpd\",\"kpe\",\"kpf\",\"kpg\",\"kph\",\"kpi\",\"kpj\",\"kpk\",\"kpl\",\"kpm\",\"kpn\",\"kpo\",\"kpp\",\"kpq\",\"kpr\",\"kps\",\"kpt\",\"kpu\",\"kpv\",\"kpw\",\"kpx\",\"kpy\",\"kpz\",\"kqa\",\"kqb\",\"kqc\",\"kqd\",\"kqe\",\"kqf\",\"kqg\",\"kqh\",\"kqi\",\"kqj\",\"kqk\",\"kql\",\"kqm\",\"kqn\",\"kqo\",\"kqp\",\"kqq\",\"kqr\",\"kqs\",\"kqt\",\"kqu\",\"kqv\",\"kqw\",\"kqx\",\"kqy\",\"kqz\",\"kra\",\"krb\",\"krc\",\"krd\",\"kre\",\"krf\",\"krh\",\"kri\",\"krj\",\"krk\",\"krl\",\"krm\",\"krn\",\"kro\",\"krp\",\"krr\",\"krs\",\"krt\",\"kru\",\"krv\",\"krw\",\"krx\",\"kry\",\"krz\",\"ksa\",\"ksb\",\"ksc\",\"ksd\",\"kse\",\"ksf\",\"ksg\",\"ksh\",\"ksi\",\"ksj\",\"ksk\",\"ksl\",\"ksm\",\"ksn\",\"kso\",\"ksp\",\"ksq\",\"ksr\",\"kss\",\"kst\",\"ksu\",\"ksv\",\"ksw\",\"ksx\",\"ksy\",\"ksz\",\"kta\",\"ktb\",\"ktc\",\"ktd\",\"kte\",\"ktf\",\"ktg\",\"kth\",\"kti\",\"ktj\",\"ktk\",\"ktl\",\"ktm\",\"ktn\",\"kto\",\"ktp\",\"ktq\",\"ktr\",\"kts\",\"ktt\",\"ktu\",\"ktv\",\"ktw\",\"ktx\",\"kty\",\"ktz\",\"kub\",\"kuc\",\"kud\",\"kue\",\"kuf\",\"kug\",\"kuh\",\"kui\",\"kuj\",\"kuk\",\"kul\",\"kum\",\"kun\",\"kuo\",\"kup\",\"kuq\",\"kus\",\"kut\",\"kuu\",\"kuv\",\"kuw\",\"kux\",\"kuy\",\"kuz\",\"kva\",\"kvb\",\"kvc\",\"kvd\",\"kve\",\"kvf\",\"kvg\",\"kvh\",\"kvi\",\"kvj\",\"kvk\",\"kvl\",\"kvm\",\"kvn\",\"kvo\",\"kvp\",\"kvq\",\"kvr\",\"kvs\",\"kvt\",\"kvu\",\"kvv\",\"kvw\",\"kvx\",\"kvy\",\"kvz\",\"kwa\",\"kwb\",\"kwc\",\"kwd\",\"kwe\",\"kwf\",\"kwg\",\"kwh\",\"kwi\",\"kwj\",\"kwk\",\"kwl\",\"kwm\",\"kwn\",\"kwo\",\"kwp\",\"kwq\",\"kwr\",\"kws\",\"kwt\",\"kwu\",\"kwv\",\"kww\",\"kwx\",\"kwy\",\"kwz\",\"kxa\",\"kxb\",\"kxc\",\"kxd\",\"kxe\",\"kxf\",\"kxh\",\"kxi\",\"kxj\",\"kxk\",\"kxl\",\"kxm\",\"kxn\",\"kxo\",\"kxp\",\"kxq\",\"kxr\",\"kxs\",\"kxt\",\"kxu\",\"kxv\",\"kxw\",\"kxx\",\"kxy\",\"kxz\",\"kya\",\"kyb\",\"kyc\",\"kyd\",\"kye\",\"kyf\",\"kyg\",\"kyh\",\"kyi\",\"kyj\",\"kyk\",\"kyl\",\"kym\",\"kyn\",\"kyo\",\"kyp\",\"kyq\",\"kyr\",\"kys\",\"kyt\",\"kyu\",\"kyv\",\"kyw\",\"kyx\",\"kyy\",\"kyz\",\"kza\",\"kzb\",\"kzc\",\"kzd\",\"kze\",\"kzf\",\"kzg\",\"kzh\",\"kzi\",\"kzj\",\"kzk\",\"kzl\",\"kzm\",\"kzn\",\"kzo\",\"kzp\",\"kzq\",\"kzr\",\"kzs\",\"kzt\",\"kzu\",\"kzv\",\"kzw\",\"kzx\",\"kzy\",\"kzz\",\"laa\",\"lab\",\"lac\",\"lad\",\"lae\",\"laf\",\"lag\",\"lah\",\"lai\",\"laj\",\"lak\",\"lal\",\"lam\",\"lan\",\"lap\",\"laq\",\"lar\",\"las\",\"lau\",\"law\",\"lax\",\"lay\",\"laz\",\"lba\",\"lbb\",\"lbc\",\"lbe\",\"lbf\",\"lbg\",\"lbi\",\"lbj\",\"lbk\",\"lbl\",\"lbm\",\"lbn\",\"lbo\",\"lbq\",\"lbr\",\"lbs\",\"lbt\",\"lbu\",\"lbv\",\"lbw\",\"lbx\",\"lby\",\"lbz\",\"lcc\",\"lcd\",\"lce\",\"lcf\",\"lch\",\"lcl\",\"lcm\",\"lcp\",\"lcq\",\"lcs\",\"lda\",\"ldb\",\"ldd\",\"ldg\",\"ldh\",\"ldi\",\"ldj\",\"ldk\",\"ldl\",\"ldm\",\"ldn\",\"ldo\",\"ldp\",\"ldq\",\"lea\",\"leb\",\"lec\",\"led\",\"lee\",\"lef\",\"leg\",\"leh\",\"lei\",\"lej\",\"lek\",\"lel\",\"lem\",\"len\",\"leo\",\"lep\",\"leq\",\"ler\",\"les\",\"let\",\"leu\",\"lev\",\"lew\",\"lex\",\"ley\",\"lez\",\"lfa\",\"lfn\",\"lga\",\"lgb\",\"lgg\",\"lgh\",\"lgi\",\"lgk\",\"lgl\",\"lgm\",\"lgn\",\"lgq\",\"lgr\",\"lgt\",\"lgu\",\"lgz\",\"lha\",\"lhh\",\"lhi\",\"lhl\",\"lhm\",\"lhn\",\"lhp\",\"lhs\",\"lht\",\"lhu\",\"lia\",\"lib\",\"lic\",\"lid\",\"lie\",\"lif\",\"lig\",\"lih\",\"lii\",\"lij\",\"lik\",\"lil\",\"lio\",\"lip\",\"liq\",\"lir\",\"lis\",\"liu\",\"liv\",\"liw\",\"lix\",\"liy\",\"liz\",\"lja\",\"lje\",\"lji\",\"ljl\",\"ljp\",\"ljw\",\"ljx\",\"lka\",\"lkb\",\"lkc\",\"lkd\",\"lke\",\"lkh\",\"lki\",\"lkj\",\"lkl\",\"lkm\",\"lkn\",\"lko\",\"lkr\",\"lks\",\"lkt\",\"lku\",\"lky\",\"lla\",\"llb\",\"llc\",\"lld\",\"lle\",\"llf\",\"llg\",\"llh\",\"lli\",\"llj\",\"llk\",\"lll\",\"llm\",\"lln\",\"llo\",\"llp\",\"llq\",\"lls\",\"llu\",\"llx\",\"lma\",\"lmb\",\"lmc\",\"lmd\",\"lme\",\"lmf\",\"lmg\",\"lmh\",\"lmi\",\"lmj\",\"lmk\",\"lml\",\"lmm\",\"lmn\",\"lmo\",\"lmp\",\"lmq\",\"lmr\",\"lmu\",\"lmv\",\"lmw\",\"lmx\",\"lmy\",\"lmz\",\"lna\",\"lnb\",\"lnd\",\"lng\",\"lnh\",\"lni\",\"lnj\",\"lnl\",\"lnm\",\"lnn\",\"lno\",\"lns\",\"lnu\",\"lnw\",\"lnz\",\"loa\",\"lob\",\"loc\",\"loe\",\"lof\",\"log\",\"loh\",\"loi\",\"loj\",\"lok\",\"lol\",\"lom\",\"lon\",\"loo\",\"lop\",\"loq\",\"lor\",\"los\",\"lot\",\"lou\",\"lov\",\"low\",\"lox\",\"loy\",\"loz\",\"lpa\",\"lpe\",\"lpn\",\"lpo\",\"lpx\",\"lra\",\"lrc\",\"lre\",\"lrg\",\"lri\",\"lrk\",\"lrl\",\"lrm\",\"lrn\",\"lro\",\"lrr\",\"lrt\",\"lrv\",\"lrz\",\"lsa\",\"lsd\",\"lse\",\"lsg\",\"lsh\",\"lsi\",\"lsl\",\"lsm\",\"lso\",\"lsp\",\"lsr\",\"lss\",\"lst\",\"lsy\",\"ltc\",\"ltg\",\"lti\",\"ltn\",\"lto\",\"lts\",\"ltu\",\"lua\",\"luc\",\"lud\",\"lue\",\"luf\",\"lui\",\"luj\",\"luk\",\"lul\",\"lum\",\"lun\",\"luo\",\"lup\",\"luq\",\"lur\",\"lus\",\"lut\",\"luu\",\"luv\",\"luw\",\"luy\",\"luz\",\"lva\",\"lvk\",\"lvs\",\"lvu\",\"lwa\",\"lwe\",\"lwg\",\"lwh\",\"lwl\",\"lwm\",\"lwo\",\"lwt\",\"lwu\",\"lww\",\"lya\",\"lyg\",\"lyn\",\"lzh\",\"lzl\",\"lzn\",\"lzz\",\"maa\",\"mab\",\"mad\",\"mae\",\"maf\",\"mag\",\"mai\",\"maj\",\"mak\",\"mam\",\"man\",\"map\",\"maq\",\"mas\",\"mat\",\"mau\",\"mav\",\"maw\",\"max\",\"maz\",\"mba\",\"mbb\",\"mbc\",\"mbd\",\"mbe\",\"mbf\",\"mbh\",\"mbi\",\"mbj\",\"mbk\",\"mbl\",\"mbm\",\"mbn\",\"mbo\",\"mbp\",\"mbq\",\"mbr\",\"mbs\",\"mbt\",\"mbu\",\"mbv\",\"mbw\",\"mbx\",\"mby\",\"mbz\",\"mca\",\"mcb\",\"mcc\",\"mcd\",\"mce\",\"mcf\",\"mcg\",\"mch\",\"mci\",\"mcj\",\"mck\",\"mcl\",\"mcm\",\"mcn\",\"mco\",\"mcp\",\"mcq\",\"mcr\",\"mcs\",\"mct\",\"mcu\",\"mcv\",\"mcw\",\"mcx\",\"mcy\",\"mcz\",\"mda\",\"mdb\",\"mdc\",\"mdd\",\"mde\",\"mdf\",\"mdg\",\"mdh\",\"mdi\",\"mdj\",\"mdk\",\"mdl\",\"mdm\",\"mdn\",\"mdp\",\"mdq\",\"mdr\",\"mds\",\"mdt\",\"mdu\",\"mdv\",\"mdw\",\"mdx\",\"mdy\",\"mdz\",\"mea\",\"meb\",\"mec\",\"med\",\"mee\",\"mef\",\"meg\",\"meh\",\"mei\",\"mej\",\"mek\",\"mel\",\"mem\",\"men\",\"meo\",\"mep\",\"meq\",\"mer\",\"mes\",\"met\",\"meu\",\"mev\",\"mew\",\"mey\",\"mez\",\"mfa\",\"mfb\",\"mfc\",\"mfd\",\"mfe\",\"mff\",\"mfg\",\"mfh\",\"mfi\",\"mfj\",\"mfk\",\"mfl\",\"mfm\",\"mfn\",\"mfo\",\"mfp\",\"mfq\",\"mfr\",\"mfs\",\"mft\",\"mfu\",\"mfv\",\"mfw\",\"mfx\",\"mfy\",\"mfz\",\"mga\",\"mgb\",\"mgc\",\"mgd\",\"mge\",\"mgf\",\"mgg\",\"mgh\",\"mgi\",\"mgj\",\"mgk\",\"mgl\",\"mgm\",\"mgn\",\"mgo\",\"mgp\",\"mgq\",\"mgr\",\"mgs\",\"mgt\",\"mgu\",\"mgv\",\"mgw\",\"mgx\",\"mgy\",\"mgz\",\"mha\",\"mhb\",\"mhc\",\"mhd\",\"mhe\",\"mhf\",\"mhg\",\"mhh\",\"mhi\",\"mhj\",\"mhk\",\"mhl\",\"mhm\",\"mhn\",\"mho\",\"mhp\",\"mhq\",\"mhr\",\"mhs\",\"mht\",\"mhu\",\"mhw\",\"mhx\",\"mhy\",\"mhz\",\"mia\",\"mib\",\"mic\",\"mid\",\"mie\",\"mif\",\"mig\",\"mih\",\"mii\",\"mij\",\"mik\",\"mil\",\"mim\",\"min\",\"mio\",\"mip\",\"miq\",\"mir\",\"mis\",\"mit\",\"miu\",\"miw\",\"mix\",\"miy\",\"miz\",\"mja\",\"mjb\",\"mjc\",\"mjd\",\"mje\",\"mjg\",\"mjh\",\"mji\",\"mjj\",\"mjk\",\"mjl\",\"mjm\",\"mjn\",\"mjo\",\"mjp\",\"mjq\",\"mjr\",\"mjs\",\"mjt\",\"mju\",\"mjv\",\"mjw\",\"mjx\",\"mjy\",\"mjz\",\"mka\",\"mkb\",\"mkc\",\"mke\",\"mkf\",\"mkg\",\"mkh\",\"mki\",\"mkj\",\"mkk\",\"mkl\",\"mkm\",\"mkn\",\"mko\",\"mkp\",\"mkq\",\"mkr\",\"mks\",\"mkt\",\"mku\",\"mkv\",\"mkw\",\"mkx\",\"mky\",\"mkz\",\"mla\",\"mlb\",\"mlc\",\"mld\",\"mle\",\"mlf\",\"mlh\",\"mli\",\"mlj\",\"mlk\",\"mll\",\"mlm\",\"mln\",\"mlo\",\"mlp\",\"mlq\",\"mlr\",\"mls\",\"mlu\",\"mlv\",\"mlw\",\"mlx\",\"mlz\",\"mma\",\"mmb\",\"mmc\",\"mmd\",\"mme\",\"mmf\",\"mmg\",\"mmh\",\"mmi\",\"mmj\",\"mmk\",\"mml\",\"mmm\",\"mmn\",\"mmo\",\"mmp\",\"mmq\",\"mmr\",\"mmt\",\"mmu\",\"mmv\",\"mmw\",\"mmx\",\"mmy\",\"mmz\",\"mna\",\"mnb\",\"mnc\",\"mnd\",\"mne\",\"mnf\",\"mng\",\"mnh\",\"mni\",\"mnj\",\"mnk\",\"mnl\",\"mnm\",\"mnn\",\"mno\",\"mnp\",\"mnq\",\"mnr\",\"mns\",\"mnt\",\"mnu\",\"mnv\",\"mnw\",\"mnx\",\"mny\",\"mnz\",\"moa\",\"moc\",\"mod\",\"moe\",\"mof\",\"mog\",\"moh\",\"moi\",\"moj\",\"mok\",\"mom\",\"moo\",\"mop\",\"moq\",\"mor\",\"mos\",\"mot\",\"mou\",\"mov\",\"mow\",\"mox\",\"moy\",\"moz\",\"mpa\",\"mpb\",\"mpc\",\"mpd\",\"mpe\",\"mpg\",\"mph\",\"mpi\",\"mpj\",\"mpk\",\"mpl\",\"mpm\",\"mpn\",\"mpo\",\"mpp\",\"mpq\",\"mpr\",\"mps\",\"mpt\",\"mpu\",\"mpv\",\"mpw\",\"mpx\",\"mpy\",\"mpz\",\"mqa\",\"mqb\",\"mqc\",\"mqe\",\"mqf\",\"mqg\",\"mqh\",\"mqi\",\"mqj\",\"mqk\",\"mql\",\"mqm\",\"mqn\",\"mqo\",\"mqp\",\"mqq\",\"mqr\",\"mqs\",\"mqt\",\"mqu\",\"mqv\",\"mqw\",\"mqx\",\"mqy\",\"mqz\",\"mra\",\"mrb\",\"mrc\",\"mrd\",\"mre\",\"mrf\",\"mrg\",\"mrh\",\"mrj\",\"mrk\",\"mrl\",\"mrm\",\"mrn\",\"mro\",\"mrp\",\"mrq\",\"mrr\",\"mrs\",\"mrt\",\"mru\",\"mrv\",\"mrw\",\"mrx\",\"mry\",\"mrz\",\"msb\",\"msc\",\"msd\",\"mse\",\"msf\",\"msg\",\"msh\",\"msi\",\"msj\",\"msk\",\"msl\",\"msm\",\"msn\",\"mso\",\"msp\",\"msq\",\"msr\",\"mss\",\"mst\",\"msu\",\"msv\",\"msw\",\"msx\",\"msy\",\"msz\",\"mta\",\"mtb\",\"mtc\",\"mtd\",\"mte\",\"mtf\",\"mtg\",\"mth\",\"mti\",\"mtj\",\"mtk\",\"mtl\",\"mtm\",\"mtn\",\"mto\",\"mtp\",\"mtq\",\"mtr\",\"mts\",\"mtt\",\"mtu\",\"mtv\",\"mtw\",\"mtx\",\"mty\",\"mua\",\"mub\",\"muc\",\"mud\",\"mue\",\"mug\",\"muh\",\"mui\",\"muj\",\"muk\",\"mul\",\"mum\",\"mun\",\"muo\",\"mup\",\"muq\",\"mur\",\"mus\",\"mut\",\"muu\",\"muv\",\"mux\",\"muy\",\"muz\",\"mva\",\"mvb\",\"mvd\",\"mve\",\"mvf\",\"mvg\",\"mvh\",\"mvi\",\"mvk\",\"mvl\",\"mvm\",\"mvn\",\"mvo\",\"mvp\",\"mvq\",\"mvr\",\"mvs\",\"mvt\",\"mvu\",\"mvv\",\"mvw\",\"mvx\",\"mvy\",\"mvz\",\"mwa\",\"mwb\",\"mwc\",\"mwd\",\"mwe\",\"mwf\",\"mwg\",\"mwh\",\"mwi\",\"mwj\",\"mwk\",\"mwl\",\"mwm\",\"mwn\",\"mwo\",\"mwp\",\"mwq\",\"mwr\",\"mws\",\"mwt\",\"mwu\",\"mwv\",\"mww\",\"mwx\",\"mwy\",\"mwz\",\"mxa\",\"mxb\",\"mxc\",\"mxd\",\"mxe\",\"mxf\",\"mxg\",\"mxh\",\"mxi\",\"mxj\",\"mxk\",\"mxl\",\"mxm\",\"mxn\",\"mxo\",\"mxp\",\"mxq\",\"mxr\",\"mxs\",\"mxt\",\"mxu\",\"mxv\",\"mxw\",\"mxx\",\"mxy\",\"mxz\",\"myb\",\"myc\",\"myd\",\"mye\",\"myf\",\"myg\",\"myh\",\"myi\",\"myj\",\"myk\",\"myl\",\"mym\",\"myn\",\"myo\",\"myp\",\"myq\",\"myr\",\"mys\",\"myt\",\"myu\",\"myv\",\"myw\",\"myx\",\"myy\",\"myz\",\"mza\",\"mzb\",\"mzc\",\"mzd\",\"mze\",\"mzg\",\"mzh\",\"mzi\",\"mzj\",\"mzk\",\"mzl\",\"mzm\",\"mzn\",\"mzo\",\"mzp\",\"mzq\",\"mzr\",\"mzs\",\"mzt\",\"mzu\",\"mzv\",\"mzw\",\"mzx\",\"mzy\",\"mzz\",\"naa\",\"nab\",\"nac\",\"nad\",\"nae\",\"naf\",\"nag\",\"nah\",\"nai\",\"naj\",\"nak\",\"nal\",\"nam\",\"nan\",\"nao\",\"nap\",\"naq\",\"nar\",\"nas\",\"nat\",\"naw\",\"nax\",\"nay\",\"naz\",\"nba\",\"nbb\",\"nbc\",\"nbd\",\"nbe\",\"nbf\",\"nbg\",\"nbh\",\"nbi\",\"nbj\",\"nbk\",\"nbm\",\"nbn\",\"nbo\",\"nbp\",\"nbq\",\"nbr\",\"nbs\",\"nbt\",\"nbu\",\"nbv\",\"nbw\",\"nbx\",\"nby\",\"nca\",\"ncb\",\"ncc\",\"ncd\",\"nce\",\"ncf\",\"ncg\",\"nch\",\"nci\",\"ncj\",\"nck\",\"ncl\",\"ncm\",\"ncn\",\"nco\",\"ncp\",\"ncr\",\"ncs\",\"nct\",\"ncu\",\"ncx\",\"ncz\",\"nda\",\"ndb\",\"ndc\",\"ndd\",\"ndf\",\"ndg\",\"ndh\",\"ndi\",\"ndj\",\"ndk\",\"ndl\",\"ndm\",\"ndn\",\"ndp\",\"ndq\",\"ndr\",\"nds\",\"ndt\",\"ndu\",\"ndv\",\"ndw\",\"ndx\",\"ndy\",\"ndz\",\"nea\",\"neb\",\"nec\",\"ned\",\"nee\",\"nef\",\"neg\",\"neh\",\"nei\",\"nej\",\"nek\",\"nem\",\"nen\",\"neo\",\"neq\",\"ner\",\"nes\",\"net\",\"neu\",\"nev\",\"new\",\"nex\",\"ney\",\"nez\",\"nfa\",\"nfd\",\"nfl\",\"nfr\",\"nfu\",\"nga\",\"ngb\",\"ngc\",\"ngd\",\"nge\",\"ngf\",\"ngg\",\"ngh\",\"ngi\",\"ngj\",\"ngk\",\"ngl\",\"ngm\",\"ngn\",\"ngo\",\"ngp\",\"ngq\",\"ngr\",\"ngs\",\"ngt\",\"ngu\",\"ngv\",\"ngw\",\"ngx\",\"ngy\",\"ngz\",\"nha\",\"nhb\",\"nhc\",\"nhd\",\"nhe\",\"nhf\",\"nhg\",\"nhh\",\"nhi\",\"nhk\",\"nhm\",\"nhn\",\"nho\",\"nhp\",\"nhq\",\"nhr\",\"nht\",\"nhu\",\"nhv\",\"nhw\",\"nhx\",\"nhy\",\"nhz\",\"nia\",\"nib\",\"nic\",\"nid\",\"nie\",\"nif\",\"nig\",\"nih\",\"nii\",\"nij\",\"nik\",\"nil\",\"nim\",\"nin\",\"nio\",\"niq\",\"nir\",\"nis\",\"nit\",\"niu\",\"niv\",\"niw\",\"nix\",\"niy\",\"niz\",\"nja\",\"njb\",\"njd\",\"njh\",\"nji\",\"njj\",\"njl\",\"njm\",\"njn\",\"njo\",\"njr\",\"njs\",\"njt\",\"nju\",\"njx\",\"njy\",\"njz\",\"nka\",\"nkb\",\"nkc\",\"nkd\",\"nke\",\"nkf\",\"nkg\",\"nkh\",\"nki\",\"nkj\",\"nkk\",\"nkm\",\"nkn\",\"nko\",\"nkp\",\"nkq\",\"nkr\",\"nks\",\"nkt\",\"nku\",\"nkv\",\"nkw\",\"nkx\",\"nkz\",\"nla\",\"nlc\",\"nle\",\"nlg\",\"nli\",\"nlj\",\"nlk\",\"nll\",\"nln\",\"nlo\",\"nlq\",\"nlr\",\"nlu\",\"nlv\",\"nlw\",\"nlx\",\"nly\",\"nlz\",\"nma\",\"nmb\",\"nmc\",\"nmd\",\"nme\",\"nmf\",\"nmg\",\"nmh\",\"nmi\",\"nmj\",\"nmk\",\"nml\",\"nmm\",\"nmn\",\"nmo\",\"nmp\",\"nmq\",\"nmr\",\"nms\",\"nmt\",\"nmu\",\"nmv\",\"nmw\",\"nmx\",\"nmy\",\"nmz\",\"nna\",\"nnb\",\"nnc\",\"nnd\",\"nne\",\"nnf\",\"nng\",\"nnh\",\"nni\",\"nnj\",\"nnk\",\"nnl\",\"nnm\",\"nnn\",\"nnp\",\"nnq\",\"nnr\",\"nns\",\"nnt\",\"nnu\",\"nnv\",\"nnw\",\"nnx\",\"nny\",\"nnz\",\"noa\",\"noc\",\"nod\",\"noe\",\"nof\",\"nog\",\"noh\",\"noi\",\"noj\",\"nok\",\"nol\",\"nom\",\"non\",\"noo\",\"nop\",\"noq\",\"nos\",\"not\",\"nou\",\"nov\",\"now\",\"noy\",\"noz\",\"npa\",\"npb\",\"npg\",\"nph\",\"npi\",\"npl\",\"npn\",\"npo\",\"nps\",\"npu\",\"npy\",\"nqg\",\"nqk\",\"nqm\",\"nqn\",\"nqo\",\"nqq\",\"nqy\",\"nra\",\"nrb\",\"nrc\",\"nre\",\"nrf\",\"nrg\",\"nri\",\"nrk\",\"nrl\",\"nrm\",\"nrn\",\"nrp\",\"nrr\",\"nrt\",\"nru\",\"nrx\",\"nrz\",\"nsa\",\"nsc\",\"nsd\",\"nse\",\"nsf\",\"nsg\",\"nsh\",\"nsi\",\"nsk\",\"nsl\",\"nsm\",\"nsn\",\"nso\",\"nsp\",\"nsq\",\"nsr\",\"nss\",\"nst\",\"nsu\",\"nsv\",\"nsw\",\"nsx\",\"nsy\",\"nsz\",\"ntd\",\"nte\",\"ntg\",\"nti\",\"ntj\",\"ntk\",\"ntm\",\"nto\",\"ntp\",\"ntr\",\"nts\",\"ntu\",\"ntw\",\"ntx\",\"nty\",\"ntz\",\"nua\",\"nub\",\"nuc\",\"nud\",\"nue\",\"nuf\",\"nug\",\"nuh\",\"nui\",\"nuj\",\"nuk\",\"nul\",\"num\",\"nun\",\"nuo\",\"nup\",\"nuq\",\"nur\",\"nus\",\"nut\",\"nuu\",\"nuv\",\"nuw\",\"nux\",\"nuy\",\"nuz\",\"nvh\",\"nvm\",\"nvo\",\"nwa\",\"nwb\",\"nwc\",\"nwe\",\"nwg\",\"nwi\",\"nwm\",\"nwo\",\"nwr\",\"nwx\",\"nwy\",\"nxa\",\"nxd\",\"nxe\",\"nxg\",\"nxi\",\"nxk\",\"nxl\",\"nxm\",\"nxn\",\"nxo\",\"nxq\",\"nxr\",\"nxu\",\"nxx\",\"nyb\",\"nyc\",\"nyd\",\"nye\",\"nyf\",\"nyg\",\"nyh\",\"nyi\",\"nyj\",\"nyk\",\"nyl\",\"nym\",\"nyn\",\"nyo\",\"nyp\",\"nyq\",\"nyr\",\"nys\",\"nyt\",\"nyu\",\"nyv\",\"nyw\",\"nyx\",\"nyy\",\"nza\",\"nzb\",\"nzi\",\"nzk\",\"nzm\",\"nzs\",\"nzu\",\"nzy\",\"nzz\",\"oaa\",\"oac\",\"oar\",\"oav\",\"obi\",\"obk\",\"obl\",\"obm\",\"obo\",\"obr\",\"obt\",\"obu\",\"oca\",\"och\",\"oco\",\"ocu\",\"oda\",\"odk\",\"odt\",\"odu\",\"ofo\",\"ofs\",\"ofu\",\"ogb\",\"ogc\",\"oge\",\"ogg\",\"ogo\",\"ogu\",\"oht\",\"ohu\",\"oia\",\"oin\",\"ojb\",\"ojc\",\"ojg\",\"ojp\",\"ojs\",\"ojv\",\"ojw\",\"oka\",\"okb\",\"okd\",\"oke\",\"okg\",\"okh\",\"oki\",\"okj\",\"okk\",\"okl\",\"okm\",\"okn\",\"oko\",\"okr\",\"oks\",\"oku\",\"okv\",\"okx\",\"ola\",\"old\",\"ole\",\"olk\",\"olm\",\"olo\",\"olr\",\"olt\",\"olu\",\"oma\",\"omb\",\"omc\",\"ome\",\"omg\",\"omi\",\"omk\",\"oml\",\"omn\",\"omo\",\"omp\",\"omq\",\"omr\",\"omt\",\"omu\",\"omv\",\"omw\",\"omx\",\"ona\",\"onb\",\"one\",\"ong\",\"oni\",\"onj\",\"onk\",\"onn\",\"ono\",\"onp\",\"onr\",\"ons\",\"ont\",\"onu\",\"onw\",\"onx\",\"ood\",\"oog\",\"oon\",\"oor\",\"oos\",\"opa\",\"opk\",\"opm\",\"opo\",\"opt\",\"opy\",\"ora\",\"orc\",\"ore\",\"org\",\"orh\",\"orn\",\"oro\",\"orr\",\"ors\",\"ort\",\"oru\",\"orv\",\"orw\",\"orx\",\"ory\",\"orz\",\"osa\",\"osc\",\"osi\",\"oso\",\"osp\",\"ost\",\"osu\",\"osx\",\"ota\",\"otb\",\"otd\",\"ote\",\"oti\",\"otk\",\"otl\",\"otm\",\"otn\",\"oto\",\"otq\",\"otr\",\"ots\",\"ott\",\"otu\",\"otw\",\"otx\",\"oty\",\"otz\",\"oua\",\"oub\",\"oue\",\"oui\",\"oum\",\"oun\",\"ovd\",\"owi\",\"owl\",\"oyb\",\"oyd\",\"oym\",\"oyy\",\"ozm\",\"paa\",\"pab\",\"pac\",\"pad\",\"pae\",\"paf\",\"pag\",\"pah\",\"pai\",\"pak\",\"pal\",\"pam\",\"pao\",\"pap\",\"paq\",\"par\",\"pas\",\"pat\",\"pau\",\"pav\",\"paw\",\"pax\",\"pay\",\"paz\",\"pbb\",\"pbc\",\"pbe\",\"pbf\",\"pbg\",\"pbh\",\"pbi\",\"pbl\",\"pbn\",\"pbo\",\"pbp\",\"pbr\",\"pbs\",\"pbt\",\"pbu\",\"pbv\",\"pby\",\"pbz\",\"pca\",\"pcb\",\"pcc\",\"pcd\",\"pce\",\"pcf\",\"pcg\",\"pch\",\"pci\",\"pcj\",\"pck\",\"pcl\",\"pcm\",\"pcn\",\"pcp\",\"pcr\",\"pcw\",\"pda\",\"pdc\",\"pdi\",\"pdn\",\"pdo\",\"pdt\",\"pdu\",\"pea\",\"peb\",\"ped\",\"pee\",\"pef\",\"peg\",\"peh\",\"pei\",\"pej\",\"pek\",\"pel\",\"pem\",\"peo\",\"pep\",\"peq\",\"pes\",\"pev\",\"pex\",\"pey\",\"pez\",\"pfa\",\"pfe\",\"pfl\",\"pga\",\"pgd\",\"pgg\",\"pgi\",\"pgk\",\"pgl\",\"pgn\",\"pgs\",\"pgu\",\"pgy\",\"pgz\",\"pha\",\"phd\",\"phg\",\"phh\",\"phi\",\"phk\",\"phl\",\"phm\",\"phn\",\"pho\",\"phq\",\"phr\",\"pht\",\"phu\",\"phv\",\"phw\",\"pia\",\"pib\",\"pic\",\"pid\",\"pie\",\"pif\",\"pig\",\"pih\",\"pii\",\"pij\",\"pil\",\"pim\",\"pin\",\"pio\",\"pip\",\"pir\",\"pis\",\"pit\",\"piu\",\"piv\",\"piw\",\"pix\",\"piy\",\"piz\",\"pjt\",\"pka\",\"pkb\",\"pkc\",\"pkg\",\"pkh\",\"pkn\",\"pko\",\"pkp\",\"pkr\",\"pks\",\"pkt\",\"pku\",\"pla\",\"plb\",\"plc\",\"pld\",\"ple\",\"plf\",\"plg\",\"plh\",\"plj\",\"plk\",\"pll\",\"pln\",\"plo\",\"plp\",\"plq\",\"plr\",\"pls\",\"plt\",\"plu\",\"plv\",\"plw\",\"ply\",\"plz\",\"pma\",\"pmb\",\"pmc\",\"pmd\",\"pme\",\"pmf\",\"pmh\",\"pmi\",\"pmj\",\"pmk\",\"pml\",\"pmm\",\"pmn\",\"pmo\",\"pmq\",\"pmr\",\"pms\",\"pmt\",\"pmu\",\"pmw\",\"pmx\",\"pmy\",\"pmz\",\"pna\",\"pnb\",\"pnc\",\"pne\",\"png\",\"pnh\",\"pni\",\"pnj\",\"pnk\",\"pnl\",\"pnm\",\"pnn\",\"pno\",\"pnp\",\"pnq\",\"pnr\",\"pns\",\"pnt\",\"pnu\",\"pnv\",\"pnw\",\"pnx\",\"pny\",\"pnz\",\"poc\",\"pod\",\"poe\",\"pof\",\"pog\",\"poh\",\"poi\",\"pok\",\"pom\",\"pon\",\"poo\",\"pop\",\"poq\",\"pos\",\"pot\",\"pov\",\"pow\",\"pox\",\"poy\",\"poz\",\"ppa\",\"ppe\",\"ppi\",\"ppk\",\"ppl\",\"ppm\",\"ppn\",\"ppo\",\"ppp\",\"ppq\",\"ppr\",\"pps\",\"ppt\",\"ppu\",\"pqa\",\"pqe\",\"pqm\",\"pqw\",\"pra\",\"prb\",\"prc\",\"prd\",\"pre\",\"prf\",\"prg\",\"prh\",\"pri\",\"prk\",\"prl\",\"prm\",\"prn\",\"pro\",\"prp\",\"prq\",\"prr\",\"prs\",\"prt\",\"pru\",\"prw\",\"prx\",\"pry\",\"prz\",\"psa\",\"psc\",\"psd\",\"pse\",\"psg\",\"psh\",\"psi\",\"psl\",\"psm\",\"psn\",\"pso\",\"psp\",\"psq\",\"psr\",\"pss\",\"pst\",\"psu\",\"psw\",\"psy\",\"pta\",\"pth\",\"pti\",\"ptn\",\"pto\",\"ptp\",\"ptq\",\"ptr\",\"ptt\",\"ptu\",\"ptv\",\"ptw\",\"pty\",\"pua\",\"pub\",\"puc\",\"pud\",\"pue\",\"puf\",\"pug\",\"pui\",\"puj\",\"puk\",\"pum\",\"puo\",\"pup\",\"puq\",\"pur\",\"put\",\"puu\",\"puw\",\"pux\",\"puy\",\"puz\",\"pwa\",\"pwb\",\"pwg\",\"pwi\",\"pwm\",\"pwn\",\"pwo\",\"pwr\",\"pww\",\"pxm\",\"pye\",\"pym\",\"pyn\",\"pys\",\"pyu\",\"pyx\",\"pyy\",\"pzn\",\"qaa..qtz\",\"qua\",\"qub\",\"quc\",\"qud\",\"quf\",\"qug\",\"quh\",\"qui\",\"quk\",\"qul\",\"qum\",\"qun\",\"qup\",\"quq\",\"qur\",\"qus\",\"quv\",\"quw\",\"qux\",\"quy\",\"quz\",\"qva\",\"qvc\",\"qve\",\"qvh\",\"qvi\",\"qvj\",\"qvl\",\"qvm\",\"qvn\",\"qvo\",\"qvp\",\"qvs\",\"qvw\",\"qvy\",\"qvz\",\"qwa\",\"qwc\",\"qwe\",\"qwh\",\"qwm\",\"qws\",\"qwt\",\"qxa\",\"qxc\",\"qxh\",\"qxl\",\"qxn\",\"qxo\",\"qxp\",\"qxq\",\"qxr\",\"qxs\",\"qxt\",\"qxu\",\"qxw\",\"qya\",\"qyp\",\"raa\",\"rab\",\"rac\",\"rad\",\"raf\",\"rag\",\"rah\",\"rai\",\"raj\",\"rak\",\"ral\",\"ram\",\"ran\",\"rao\",\"rap\",\"raq\",\"rar\",\"ras\",\"rat\",\"rau\",\"rav\",\"raw\",\"rax\",\"ray\",\"raz\",\"rbb\",\"rbk\",\"rbl\",\"rbp\",\"rcf\",\"rdb\",\"rea\",\"reb\",\"ree\",\"reg\",\"rei\",\"rej\",\"rel\",\"rem\",\"ren\",\"rer\",\"res\",\"ret\",\"rey\",\"rga\",\"rge\",\"rgk\",\"rgn\",\"rgr\",\"rgs\",\"rgu\",\"rhg\",\"rhp\",\"ria\",\"rie\",\"rif\",\"ril\",\"rim\",\"rin\",\"rir\",\"rit\",\"riu\",\"rjg\",\"rji\",\"rjs\",\"rka\",\"rkb\",\"rkh\",\"rki\",\"rkm\",\"rkt\",\"rkw\",\"rma\",\"rmb\",\"rmc\",\"rmd\",\"rme\",\"rmf\",\"rmg\",\"rmh\",\"rmi\",\"rmk\",\"rml\",\"rmm\",\"rmn\",\"rmo\",\"rmp\",\"rmq\",\"rmr\",\"rms\",\"rmt\",\"rmu\",\"rmv\",\"rmw\",\"rmx\",\"rmy\",\"rmz\",\"rna\",\"rnd\",\"rng\",\"rnl\",\"rnn\",\"rnp\",\"rnr\",\"rnw\",\"roa\",\"rob\",\"roc\",\"rod\",\"roe\",\"rof\",\"rog\",\"rol\",\"rom\",\"roo\",\"rop\",\"ror\",\"rou\",\"row\",\"rpn\",\"rpt\",\"rri\",\"rro\",\"rrt\",\"rsb\",\"rsi\",\"rsl\",\"rsm\",\"rtc\",\"rth\",\"rtm\",\"rts\",\"rtw\",\"rub\",\"ruc\",\"rue\",\"ruf\",\"rug\",\"ruh\",\"rui\",\"ruk\",\"ruo\",\"rup\",\"ruq\",\"rut\",\"ruu\",\"ruy\",\"ruz\",\"rwa\",\"rwk\",\"rwm\",\"rwo\",\"rwr\",\"rxd\",\"rxw\",\"ryn\",\"rys\",\"ryu\",\"rzh\",\"saa\",\"sab\",\"sac\",\"sad\",\"sae\",\"saf\",\"sah\",\"sai\",\"saj\",\"sak\",\"sal\",\"sam\",\"sao\",\"sap\",\"saq\",\"sar\",\"sas\",\"sat\",\"sau\",\"sav\",\"saw\",\"sax\",\"say\",\"saz\",\"sba\",\"sbb\",\"sbc\",\"sbd\",\"sbe\",\"sbf\",\"sbg\",\"sbh\",\"sbi\",\"sbj\",\"sbk\",\"sbl\",\"sbm\",\"sbn\",\"sbo\",\"sbp\",\"sbq\",\"sbr\",\"sbs\",\"sbt\",\"sbu\",\"sbv\",\"sbw\",\"sbx\",\"sby\",\"sbz\",\"sca\",\"scb\",\"sce\",\"scf\",\"scg\",\"sch\",\"sci\",\"sck\",\"scl\",\"scn\",\"sco\",\"scp\",\"scq\",\"scs\",\"scu\",\"scv\",\"scw\",\"scx\",\"sda\",\"sdb\",\"sdc\",\"sde\",\"sdf\",\"sdg\",\"sdh\",\"sdj\",\"sdk\",\"sdl\",\"sdm\",\"sdn\",\"sdo\",\"sdp\",\"sdr\",\"sds\",\"sdt\",\"sdu\",\"sdv\",\"sdx\",\"sdz\",\"sea\",\"seb\",\"sec\",\"sed\",\"see\",\"sef\",\"seg\",\"seh\",\"sei\",\"sej\",\"sek\",\"sel\",\"sem\",\"sen\",\"seo\",\"sep\",\"seq\",\"ser\",\"ses\",\"set\",\"seu\",\"sev\",\"sew\",\"sey\",\"sez\",\"sfb\",\"sfe\",\"sfm\",\"sfs\",\"sfw\",\"sga\",\"sgb\",\"sgc\",\"sgd\",\"sge\",\"sgg\",\"sgh\",\"sgi\",\"sgj\",\"sgk\",\"sgl\",\"sgm\",\"sgn\",\"sgo\",\"sgp\",\"sgr\",\"sgs\",\"sgt\",\"sgu\",\"sgw\",\"sgx\",\"sgy\",\"sgz\",\"sha\",\"shb\",\"shc\",\"shd\",\"she\",\"shg\",\"shh\",\"shi\",\"shj\",\"shk\",\"shl\",\"shm\",\"shn\",\"sho\",\"shp\",\"shq\",\"shr\",\"shs\",\"sht\",\"shu\",\"shv\",\"shw\",\"shx\",\"shy\",\"shz\",\"sia\",\"sib\",\"sid\",\"sie\",\"sif\",\"sig\",\"sih\",\"sii\",\"sij\",\"sik\",\"sil\",\"sim\",\"sio\",\"sip\",\"siq\",\"sir\",\"sis\",\"sit\",\"siu\",\"siv\",\"siw\",\"six\",\"siy\",\"siz\",\"sja\",\"sjb\",\"sjd\",\"sje\",\"sjg\",\"sjk\",\"sjl\",\"sjm\",\"sjn\",\"sjo\",\"sjp\",\"sjr\",\"sjs\",\"sjt\",\"sju\",\"sjw\",\"ska\",\"skb\",\"skc\",\"skd\",\"ske\",\"skf\",\"skg\",\"skh\",\"ski\",\"skj\",\"skk\",\"skm\",\"skn\",\"sko\",\"skp\",\"skq\",\"skr\",\"sks\",\"skt\",\"sku\",\"skv\",\"skw\",\"skx\",\"sky\",\"skz\",\"sla\",\"slc\",\"sld\",\"sle\",\"slf\",\"slg\",\"slh\",\"sli\",\"slj\",\"sll\",\"slm\",\"sln\",\"slp\",\"slq\",\"slr\",\"sls\",\"slt\",\"slu\",\"slw\",\"slx\",\"sly\",\"slz\",\"sma\",\"smb\",\"smc\",\"smd\",\"smf\",\"smg\",\"smh\",\"smi\",\"smj\",\"smk\",\"sml\",\"smm\",\"smn\",\"smp\",\"smq\",\"smr\",\"sms\",\"smt\",\"smu\",\"smv\",\"smw\",\"smx\",\"smy\",\"smz\",\"snb\",\"snc\",\"sne\",\"snf\",\"sng\",\"snh\",\"sni\",\"snj\",\"snk\",\"snl\",\"snm\",\"snn\",\"sno\",\"snp\",\"snq\",\"snr\",\"sns\",\"snu\",\"snv\",\"snw\",\"snx\",\"sny\",\"snz\",\"soa\",\"sob\",\"soc\",\"sod\",\"soe\",\"sog\",\"soh\",\"soi\",\"soj\",\"sok\",\"sol\",\"son\",\"soo\",\"sop\",\"soq\",\"sor\",\"sos\",\"sou\",\"sov\",\"sow\",\"sox\",\"soy\",\"soz\",\"spb\",\"spc\",\"spd\",\"spe\",\"spg\",\"spi\",\"spk\",\"spl\",\"spm\",\"spn\",\"spo\",\"spp\",\"spq\",\"spr\",\"sps\",\"spt\",\"spu\",\"spv\",\"spx\",\"spy\",\"sqa\",\"sqh\",\"sqj\",\"sqk\",\"sqm\",\"sqn\",\"sqo\",\"sqq\",\"sqr\",\"sqs\",\"sqt\",\"squ\",\"sra\",\"srb\",\"src\",\"sre\",\"srf\",\"srg\",\"srh\",\"sri\",\"srk\",\"srl\",\"srm\",\"srn\",\"sro\",\"srq\",\"srr\",\"srs\",\"srt\",\"sru\",\"srv\",\"srw\",\"srx\",\"sry\",\"srz\",\"ssa\",\"ssb\",\"ssc\",\"ssd\",\"sse\",\"ssf\",\"ssg\",\"ssh\",\"ssi\",\"ssj\",\"ssk\",\"ssl\",\"ssm\",\"ssn\",\"sso\",\"ssp\",\"ssq\",\"ssr\",\"sss\",\"sst\",\"ssu\",\"ssv\",\"ssx\",\"ssy\",\"ssz\",\"sta\",\"stb\",\"std\",\"ste\",\"stf\",\"stg\",\"sth\",\"sti\",\"stj\",\"stk\",\"stl\",\"stm\",\"stn\",\"sto\",\"stp\",\"stq\",\"str\",\"sts\",\"stt\",\"stu\",\"stv\",\"stw\",\"sty\",\"sua\",\"sub\",\"suc\",\"sue\",\"sug\",\"sui\",\"suj\",\"suk\",\"sul\",\"sum\",\"suq\",\"sur\",\"sus\",\"sut\",\"suv\",\"suw\",\"sux\",\"suy\",\"suz\",\"sva\",\"svb\",\"svc\",\"sve\",\"svk\",\"svm\",\"svr\",\"svs\",\"svx\",\"swb\",\"swc\",\"swf\",\"swg\",\"swh\",\"swi\",\"swj\",\"swk\",\"swl\",\"swm\",\"swn\",\"swo\",\"swp\",\"swq\",\"swr\",\"sws\",\"swt\",\"swu\",\"swv\",\"sww\",\"swx\",\"swy\",\"sxb\",\"sxc\",\"sxe\",\"sxg\",\"sxk\",\"sxl\",\"sxm\",\"sxn\",\"sxo\",\"sxr\",\"sxs\",\"sxu\",\"sxw\",\"sya\",\"syb\",\"syc\",\"syd\",\"syi\",\"syk\",\"syl\",\"sym\",\"syn\",\"syo\",\"syr\",\"sys\",\"syw\",\"syx\",\"syy\",\"sza\",\"szb\",\"szc\",\"szd\",\"sze\",\"szg\",\"szl\",\"szn\",\"szp\",\"szv\",\"szw\",\"taa\",\"tab\",\"tac\",\"tad\",\"tae\",\"taf\",\"tag\",\"tai\",\"taj\",\"tak\",\"tal\",\"tan\",\"tao\",\"tap\",\"taq\",\"tar\",\"tas\",\"tau\",\"tav\",\"taw\",\"tax\",\"tay\",\"taz\",\"tba\",\"tbb\",\"tbc\",\"tbd\",\"tbe\",\"tbf\",\"tbg\",\"tbh\",\"tbi\",\"tbj\",\"tbk\",\"tbl\",\"tbm\",\"tbn\",\"tbo\",\"tbp\",\"tbq\",\"tbr\",\"tbs\",\"tbt\",\"tbu\",\"tbv\",\"tbw\",\"tbx\",\"tby\",\"tbz\",\"tca\",\"tcb\",\"tcc\",\"tcd\",\"tce\",\"tcf\",\"tcg\",\"tch\",\"tci\",\"tck\",\"tcl\",\"tcm\",\"tcn\",\"tco\",\"tcp\",\"tcq\",\"tcs\",\"tct\",\"tcu\",\"tcw\",\"tcx\",\"tcy\",\"tcz\",\"tda\",\"tdb\",\"tdc\",\"tdd\",\"tde\",\"tdf\",\"tdg\",\"tdh\",\"tdi\",\"tdj\",\"tdk\",\"tdl\",\"tdm\",\"tdn\",\"tdo\",\"tdq\",\"tdr\",\"tds\",\"tdt\",\"tdu\",\"tdv\",\"tdx\",\"tdy\",\"tea\",\"teb\",\"tec\",\"ted\",\"tee\",\"tef\",\"teg\",\"teh\",\"tei\",\"tek\",\"tem\",\"ten\",\"teo\",\"tep\",\"teq\",\"ter\",\"tes\",\"tet\",\"teu\",\"tev\",\"tew\",\"tex\",\"tey\",\"tfi\",\"tfn\",\"tfo\",\"tfr\",\"tft\",\"tga\",\"tgb\",\"tgc\",\"tgd\",\"tge\",\"tgf\",\"tgg\",\"tgh\",\"tgi\",\"tgj\",\"tgn\",\"tgo\",\"tgp\",\"tgq\",\"tgr\",\"tgs\",\"tgt\",\"tgu\",\"tgv\",\"tgw\",\"tgx\",\"tgy\",\"tgz\",\"thc\",\"thd\",\"the\",\"thf\",\"thh\",\"thi\",\"thk\",\"thl\",\"thm\",\"thn\",\"thp\",\"thq\",\"thr\",\"ths\",\"tht\",\"thu\",\"thv\",\"thw\",\"thx\",\"thy\",\"thz\",\"tia\",\"tic\",\"tid\",\"tie\",\"tif\",\"tig\",\"tih\",\"tii\",\"tij\",\"tik\",\"til\",\"tim\",\"tin\",\"tio\",\"tip\",\"tiq\",\"tis\",\"tit\",\"tiu\",\"tiv\",\"tiw\",\"tix\",\"tiy\",\"tiz\",\"tja\",\"tjg\",\"tji\",\"tjl\",\"tjm\",\"tjn\",\"tjo\",\"tjs\",\"tju\",\"tjw\",\"tka\",\"tkb\",\"tkd\",\"tke\",\"tkf\",\"tkg\",\"tkk\",\"tkl\",\"tkm\",\"tkn\",\"tkp\",\"tkq\",\"tkr\",\"tks\",\"tkt\",\"tku\",\"tkv\",\"tkw\",\"tkx\",\"tkz\",\"tla\",\"tlb\",\"tlc\",\"tld\",\"tlf\",\"tlg\",\"tlh\",\"tli\",\"tlj\",\"tlk\",\"tll\",\"tlm\",\"tln\",\"tlo\",\"tlp\",\"tlq\",\"tlr\",\"tls\",\"tlt\",\"tlu\",\"tlv\",\"tlw\",\"tlx\",\"tly\",\"tma\",\"tmb\",\"tmc\",\"tmd\",\"tme\",\"tmf\",\"tmg\",\"tmh\",\"tmi\",\"tmj\",\"tmk\",\"tml\",\"tmm\",\"tmn\",\"tmo\",\"tmp\",\"tmq\",\"tmr\",\"tms\",\"tmt\",\"tmu\",\"tmv\",\"tmw\",\"tmy\",\"tmz\",\"tna\",\"tnb\",\"tnc\",\"tnd\",\"tne\",\"tnf\",\"tng\",\"tnh\",\"tni\",\"tnk\",\"tnl\",\"tnm\",\"tnn\",\"tno\",\"tnp\",\"tnq\",\"tnr\",\"tns\",\"tnt\",\"tnu\",\"tnv\",\"tnw\",\"tnx\",\"tny\",\"tnz\",\"tob\",\"toc\",\"tod\",\"toe\",\"tof\",\"tog\",\"toh\",\"toi\",\"toj\",\"tol\",\"tom\",\"too\",\"top\",\"toq\",\"tor\",\"tos\",\"tou\",\"tov\",\"tow\",\"tox\",\"toy\",\"toz\",\"tpa\",\"tpc\",\"tpe\",\"tpf\",\"tpg\",\"tpi\",\"tpj\",\"tpk\",\"tpl\",\"tpm\",\"tpn\",\"tpo\",\"tpp\",\"tpq\",\"tpr\",\"tpt\",\"tpu\",\"tpv\",\"tpw\",\"tpx\",\"tpy\",\"tpz\",\"tqb\",\"tql\",\"tqm\",\"tqn\",\"tqo\",\"tqp\",\"tqq\",\"tqr\",\"tqt\",\"tqu\",\"tqw\",\"tra\",\"trb\",\"trc\",\"trd\",\"tre\",\"trf\",\"trg\",\"trh\",\"tri\",\"trj\",\"trk\",\"trl\",\"trm\",\"trn\",\"tro\",\"trp\",\"trq\",\"trr\",\"trs\",\"trt\",\"tru\",\"trv\",\"trw\",\"trx\",\"try\",\"trz\",\"tsa\",\"tsb\",\"tsc\",\"tsd\",\"tse\",\"tsf\",\"tsg\",\"tsh\",\"tsi\",\"tsj\",\"tsk\",\"tsl\",\"tsm\",\"tsp\",\"tsq\",\"tsr\",\"tss\",\"tst\",\"tsu\",\"tsv\",\"tsw\",\"tsx\",\"tsy\",\"tsz\",\"tta\",\"ttb\",\"ttc\",\"ttd\",\"tte\",\"ttf\",\"ttg\",\"tth\",\"tti\",\"ttj\",\"ttk\",\"ttl\",\"ttm\",\"ttn\",\"tto\",\"ttp\",\"ttq\",\"ttr\",\"tts\",\"ttt\",\"ttu\",\"ttv\",\"ttw\",\"tty\",\"ttz\",\"tua\",\"tub\",\"tuc\",\"tud\",\"tue\",\"tuf\",\"tug\",\"tuh\",\"tui\",\"tuj\",\"tul\",\"tum\",\"tun\",\"tuo\",\"tup\",\"tuq\",\"tus\",\"tut\",\"tuu\",\"tuv\",\"tuw\",\"tux\",\"tuy\",\"tuz\",\"tva\",\"tvd\",\"tve\",\"tvk\",\"tvl\",\"tvm\",\"tvn\",\"tvo\",\"tvs\",\"tvt\",\"tvu\",\"tvw\",\"tvy\",\"twa\",\"twb\",\"twc\",\"twd\",\"twe\",\"twf\",\"twg\",\"twh\",\"twl\",\"twm\",\"twn\",\"two\",\"twp\",\"twq\",\"twr\",\"twt\",\"twu\",\"tww\",\"twx\",\"twy\",\"txa\",\"txb\",\"txc\",\"txe\",\"txg\",\"txh\",\"txi\",\"txj\",\"txm\",\"txn\",\"txo\",\"txq\",\"txr\",\"txs\",\"txt\",\"txu\",\"txx\",\"txy\",\"tya\",\"tye\",\"tyh\",\"tyi\",\"tyj\",\"tyl\",\"tyn\",\"typ\",\"tyr\",\"tys\",\"tyt\",\"tyu\",\"tyv\",\"tyx\",\"tyz\",\"tza\",\"tzh\",\"tzj\",\"tzl\",\"tzm\",\"tzn\",\"tzo\",\"tzx\",\"uam\",\"uan\",\"uar\",\"uba\",\"ubi\",\"ubl\",\"ubr\",\"ubu\",\"uby\",\"uda\",\"ude\",\"udg\",\"udi\",\"udj\",\"udl\",\"udm\",\"udu\",\"ues\",\"ufi\",\"uga\",\"ugb\",\"uge\",\"ugn\",\"ugo\",\"ugy\",\"uha\",\"uhn\",\"uis\",\"uiv\",\"uji\",\"uka\",\"ukg\",\"ukh\",\"ukl\",\"ukp\",\"ukq\",\"uks\",\"uku\",\"ukw\",\"uky\",\"ula\",\"ulb\",\"ulc\",\"ule\",\"ulf\",\"uli\",\"ulk\",\"ull\",\"ulm\",\"uln\",\"ulu\",\"ulw\",\"uma\",\"umb\",\"umc\",\"umd\",\"umg\",\"umi\",\"umm\",\"umn\",\"umo\",\"ump\",\"umr\",\"ums\",\"umu\",\"una\",\"und\",\"une\",\"ung\",\"unk\",\"unm\",\"unn\",\"unp\",\"unr\",\"unu\",\"unx\",\"unz\",\"uok\",\"upi\",\"upv\",\"ura\",\"urb\",\"urc\",\"ure\",\"urf\",\"urg\",\"urh\",\"uri\",\"urj\",\"urk\",\"url\",\"urm\",\"urn\",\"uro\",\"urp\",\"urr\",\"urt\",\"uru\",\"urv\",\"urw\",\"urx\",\"ury\",\"urz\",\"usa\",\"ush\",\"usi\",\"usk\",\"usp\",\"usu\",\"uta\",\"ute\",\"utp\",\"utr\",\"utu\",\"uum\",\"uun\",\"uur\",\"uuu\",\"uve\",\"uvh\",\"uvl\",\"uwa\",\"uya\",\"uzn\",\"uzs\",\"vaa\",\"vae\",\"vaf\",\"vag\",\"vah\",\"vai\",\"vaj\",\"val\",\"vam\",\"van\",\"vao\",\"vap\",\"var\",\"vas\",\"vau\",\"vav\",\"vay\",\"vbb\",\"vbk\",\"vec\",\"ved\",\"vel\",\"vem\",\"veo\",\"vep\",\"ver\",\"vgr\",\"vgt\",\"vic\",\"vid\",\"vif\",\"vig\",\"vil\",\"vin\",\"vis\",\"vit\",\"viv\",\"vka\",\"vki\",\"vkj\",\"vkk\",\"vkl\",\"vkm\",\"vko\",\"vkp\",\"vkt\",\"vku\",\"vlp\",\"vls\",\"vma\",\"vmb\",\"vmc\",\"vmd\",\"vme\",\"vmf\",\"vmg\",\"vmh\",\"vmi\",\"vmj\",\"vmk\",\"vml\",\"vmm\",\"vmp\",\"vmq\",\"vmr\",\"vms\",\"vmu\",\"vmv\",\"vmw\",\"vmx\",\"vmy\",\"vmz\",\"vnk\",\"vnm\",\"vnp\",\"vor\",\"vot\",\"vra\",\"vro\",\"vrs\",\"vrt\",\"vsi\",\"vsl\",\"vsv\",\"vto\",\"vum\",\"vun\",\"vut\",\"vwa\",\"waa\",\"wab\",\"wac\",\"wad\",\"wae\",\"waf\",\"wag\",\"wah\",\"wai\",\"waj\",\"wak\",\"wal\",\"wam\",\"wan\",\"wao\",\"wap\",\"waq\",\"war\",\"was\",\"wat\",\"wau\",\"wav\",\"waw\",\"wax\",\"way\",\"waz\",\"wba\",\"wbb\",\"wbe\",\"wbf\",\"wbh\",\"wbi\",\"wbj\",\"wbk\",\"wbl\",\"wbm\",\"wbp\",\"wbq\",\"wbr\",\"wbt\",\"wbv\",\"wbw\",\"wca\",\"wci\",\"wdd\",\"wdg\",\"wdj\",\"wdk\",\"wdu\",\"wdy\",\"wea\",\"wec\",\"wed\",\"weg\",\"weh\",\"wei\",\"wem\",\"wen\",\"weo\",\"wep\",\"wer\",\"wes\",\"wet\",\"weu\",\"wew\",\"wfg\",\"wga\",\"wgb\",\"wgg\",\"wgi\",\"wgo\",\"wgu\",\"wgw\",\"wgy\",\"wha\",\"whg\",\"whk\",\"whu\",\"wib\",\"wic\",\"wie\",\"wif\",\"wig\",\"wih\",\"wii\",\"wij\",\"wik\",\"wil\",\"wim\",\"win\",\"wir\",\"wit\",\"wiu\",\"wiv\",\"wiw\",\"wiy\",\"wja\",\"wji\",\"wka\",\"wkb\",\"wkd\",\"wkl\",\"wku\",\"wkw\",\"wky\",\"wla\",\"wlc\",\"wle\",\"wlg\",\"wli\",\"wlk\",\"wll\",\"wlm\",\"wlo\",\"wlr\",\"wls\",\"wlu\",\"wlv\",\"wlw\",\"wlx\",\"wly\",\"wma\",\"wmb\",\"wmc\",\"wmd\",\"wme\",\"wmh\",\"wmi\",\"wmm\",\"wmn\",\"wmo\",\"wms\",\"wmt\",\"wmw\",\"wmx\",\"wnb\",\"wnc\",\"wnd\",\"wne\",\"wng\",\"wni\",\"wnk\",\"wnm\",\"wnn\",\"wno\",\"wnp\",\"wnu\",\"wnw\",\"wny\",\"woa\",\"wob\",\"woc\",\"wod\",\"woe\",\"wof\",\"wog\",\"woi\",\"wok\",\"wom\",\"won\",\"woo\",\"wor\",\"wos\",\"wow\",\"woy\",\"wpc\",\"wra\",\"wrb\",\"wrd\",\"wrg\",\"wrh\",\"wri\",\"wrk\",\"wrl\",\"wrm\",\"wrn\",\"wro\",\"wrp\",\"wrr\",\"wrs\",\"wru\",\"wrv\",\"wrw\",\"wrx\",\"wry\",\"wrz\",\"wsa\",\"wsg\",\"wsi\",\"wsk\",\"wsr\",\"wss\",\"wsu\",\"wsv\",\"wtf\",\"wth\",\"wti\",\"wtk\",\"wtm\",\"wtw\",\"wua\",\"wub\",\"wud\",\"wuh\",\"wul\",\"wum\",\"wun\",\"wur\",\"wut\",\"wuu\",\"wuv\",\"wux\",\"wuy\",\"wwa\",\"wwb\",\"wwo\",\"wwr\",\"www\",\"wxa\",\"wxw\",\"wya\",\"wyb\",\"wyi\",\"wym\",\"wyr\",\"wyy\",\"xaa\",\"xab\",\"xac\",\"xad\",\"xae\",\"xag\",\"xai\",\"xaj\",\"xak\",\"xal\",\"xam\",\"xan\",\"xao\",\"xap\",\"xaq\",\"xar\",\"xas\",\"xat\",\"xau\",\"xav\",\"xaw\",\"xay\",\"xba\",\"xbb\",\"xbc\",\"xbd\",\"xbe\",\"xbg\",\"xbi\",\"xbj\",\"xbm\",\"xbn\",\"xbo\",\"xbp\",\"xbr\",\"xbw\",\"xbx\",\"xby\",\"xcb\",\"xcc\",\"xce\",\"xcg\",\"xch\",\"xcl\",\"xcm\",\"xcn\",\"xco\",\"xcr\",\"xct\",\"xcu\",\"xcv\",\"xcw\",\"xcy\",\"xda\",\"xdc\",\"xdk\",\"xdm\",\"xdy\",\"xeb\",\"xed\",\"xeg\",\"xel\",\"xem\",\"xep\",\"xer\",\"xes\",\"xet\",\"xeu\",\"xfa\",\"xga\",\"xgb\",\"xgd\",\"xgf\",\"xgg\",\"xgi\",\"xgl\",\"xgm\",\"xgn\",\"xgr\",\"xgu\",\"xgw\",\"xha\",\"xhc\",\"xhd\",\"xhe\",\"xhr\",\"xht\",\"xhu\",\"xhv\",\"xia\",\"xib\",\"xii\",\"xil\",\"xin\",\"xip\",\"xir\",\"xis\",\"xiv\",\"xiy\",\"xjb\",\"xjt\",\"xka\",\"xkb\",\"xkc\",\"xkd\",\"xke\",\"xkf\",\"xkg\",\"xkh\",\"xki\",\"xkj\",\"xkk\",\"xkl\",\"xkn\",\"xko\",\"xkp\",\"xkq\",\"xkr\",\"xks\",\"xkt\",\"xku\",\"xkv\",\"xkw\",\"xkx\",\"xky\",\"xkz\",\"xla\",\"xlb\",\"xlc\",\"xld\",\"xle\",\"xlg\",\"xli\",\"xln\",\"xlo\",\"xlp\",\"xls\",\"xlu\",\"xly\",\"xma\",\"xmb\",\"xmc\",\"xmd\",\"xme\",\"xmf\",\"xmg\",\"xmh\",\"xmj\",\"xmk\",\"xml\",\"xmm\",\"xmn\",\"xmo\",\"xmp\",\"xmq\",\"xmr\",\"xms\",\"xmt\",\"xmu\",\"xmv\",\"xmw\",\"xmx\",\"xmy\",\"xmz\",\"xna\",\"xnb\",\"xnd\",\"xng\",\"xnh\",\"xni\",\"xnk\",\"xnn\",\"xno\",\"xnr\",\"xns\",\"xnt\",\"xnu\",\"xny\",\"xnz\",\"xoc\",\"xod\",\"xog\",\"xoi\",\"xok\",\"xom\",\"xon\",\"xoo\",\"xop\",\"xor\",\"xow\",\"xpa\",\"xpc\",\"xpe\",\"xpg\",\"xpi\",\"xpj\",\"xpk\",\"xpm\",\"xpn\",\"xpo\",\"xpp\",\"xpq\",\"xpr\",\"xps\",\"xpt\",\"xpu\",\"xpy\",\"xqa\",\"xqt\",\"xra\",\"xrb\",\"xrd\",\"xre\",\"xrg\",\"xri\",\"xrm\",\"xrn\",\"xrq\",\"xrr\",\"xrt\",\"xru\",\"xrw\",\"xsa\",\"xsb\",\"xsc\",\"xsd\",\"xse\",\"xsh\",\"xsi\",\"xsj\",\"xsl\",\"xsm\",\"xsn\",\"xso\",\"xsp\",\"xsq\",\"xsr\",\"xss\",\"xsu\",\"xsv\",\"xsy\",\"xta\",\"xtb\",\"xtc\",\"xtd\",\"xte\",\"xtg\",\"xth\",\"xti\",\"xtj\",\"xtl\",\"xtm\",\"xtn\",\"xto\",\"xtp\",\"xtq\",\"xtr\",\"xts\",\"xtt\",\"xtu\",\"xtv\",\"xtw\",\"xty\",\"xtz\",\"xua\",\"xub\",\"xud\",\"xug\",\"xuj\",\"xul\",\"xum\",\"xun\",\"xuo\",\"xup\",\"xur\",\"xut\",\"xuu\",\"xve\",\"xvi\",\"xvn\",\"xvo\",\"xvs\",\"xwa\",\"xwc\",\"xwd\",\"xwe\",\"xwg\",\"xwj\",\"xwk\",\"xwl\",\"xwo\",\"xwr\",\"xwt\",\"xww\",\"xxb\",\"xxk\",\"xxm\",\"xxr\",\"xxt\",\"xya\",\"xyb\",\"xyj\",\"xyk\",\"xyl\",\"xyt\",\"xyy\",\"xzh\",\"xzm\",\"xzp\",\"yaa\",\"yab\",\"yac\",\"yad\",\"yae\",\"yaf\",\"yag\",\"yah\",\"yai\",\"yaj\",\"yak\",\"yal\",\"yam\",\"yan\",\"yao\",\"yap\",\"yaq\",\"yar\",\"yas\",\"yat\",\"yau\",\"yav\",\"yaw\",\"yax\",\"yay\",\"yaz\",\"yba\",\"ybb\",\"ybd\",\"ybe\",\"ybh\",\"ybi\",\"ybj\",\"ybk\",\"ybl\",\"ybm\",\"ybn\",\"ybo\",\"ybx\",\"yby\",\"ych\",\"ycl\",\"ycn\",\"ycp\",\"yda\",\"ydd\",\"yde\",\"ydg\",\"ydk\",\"yds\",\"yea\",\"yec\",\"yee\",\"yei\",\"yej\",\"yel\",\"yen\",\"yer\",\"yes\",\"yet\",\"yeu\",\"yev\",\"yey\",\"yga\",\"ygi\",\"ygl\",\"ygm\",\"ygp\",\"ygr\",\"ygs\",\"ygu\",\"ygw\",\"yha\",\"yhd\",\"yhl\",\"yhs\",\"yia\",\"yif\",\"yig\",\"yih\",\"yii\",\"yij\",\"yik\",\"yil\",\"yim\",\"yin\",\"yip\",\"yiq\",\"yir\",\"yis\",\"yit\",\"yiu\",\"yiv\",\"yix\",\"yiy\",\"yiz\",\"yka\",\"ykg\",\"yki\",\"ykk\",\"ykl\",\"ykm\",\"ykn\",\"yko\",\"ykr\",\"ykt\",\"yku\",\"yky\",\"yla\",\"ylb\",\"yle\",\"ylg\",\"yli\",\"yll\",\"ylm\",\"yln\",\"ylo\",\"ylr\",\"ylu\",\"yly\",\"yma\",\"ymb\",\"ymc\",\"ymd\",\"yme\",\"ymg\",\"ymh\",\"ymi\",\"ymk\",\"yml\",\"ymm\",\"ymn\",\"ymo\",\"ymp\",\"ymq\",\"ymr\",\"yms\",\"ymt\",\"ymx\",\"ymz\",\"yna\",\"ynd\",\"yne\",\"yng\",\"ynh\",\"ynk\",\"ynl\",\"ynn\",\"yno\",\"ynq\",\"yns\",\"ynu\",\"yob\",\"yog\",\"yoi\",\"yok\",\"yol\",\"yom\",\"yon\",\"yos\",\"yot\",\"yox\",\"yoy\",\"ypa\",\"ypb\",\"ypg\",\"yph\",\"ypk\",\"ypm\",\"ypn\",\"ypo\",\"ypp\",\"ypz\",\"yra\",\"yrb\",\"yre\",\"yri\",\"yrk\",\"yrl\",\"yrm\",\"yrn\",\"yro\",\"yrs\",\"yrw\",\"yry\",\"ysc\",\"ysd\",\"ysg\",\"ysl\",\"ysn\",\"yso\",\"ysp\",\"ysr\",\"yss\",\"ysy\",\"yta\",\"ytl\",\"ytp\",\"ytw\",\"yty\",\"yua\",\"yub\",\"yuc\",\"yud\",\"yue\",\"yuf\",\"yug\",\"yui\",\"yuj\",\"yuk\",\"yul\",\"yum\",\"yun\",\"yup\",\"yuq\",\"yur\",\"yut\",\"yuu\",\"yuw\",\"yux\",\"yuy\",\"yuz\",\"yva\",\"yvt\",\"ywa\",\"ywg\",\"ywl\",\"ywn\",\"ywq\",\"ywr\",\"ywt\",\"ywu\",\"yww\",\"yxa\",\"yxg\",\"yxl\",\"yxm\",\"yxu\",\"yxy\",\"yyr\",\"yyu\",\"yyz\",\"yzg\",\"yzk\",\"zaa\",\"zab\",\"zac\",\"zad\",\"zae\",\"zaf\",\"zag\",\"zah\",\"zai\",\"zaj\",\"zak\",\"zal\",\"zam\",\"zao\",\"zap\",\"zaq\",\"zar\",\"zas\",\"zat\",\"zau\",\"zav\",\"zaw\",\"zax\",\"zay\",\"zaz\",\"zbc\",\"zbe\",\"zbl\",\"zbt\",\"zbw\",\"zca\",\"zch\",\"zdj\",\"zea\",\"zeg\",\"zeh\",\"zen\",\"zga\",\"zgb\",\"zgh\",\"zgm\",\"zgn\",\"zgr\",\"zhb\",\"zhd\",\"zhi\",\"zhn\",\"zhw\",\"zhx\",\"zia\",\"zib\",\"zik\",\"zil\",\"zim\",\"zin\",\"zir\",\"ziw\",\"ziz\",\"zka\",\"zkb\",\"zkd\",\"zkg\",\"zkh\",\"zkk\",\"zkn\",\"zko\",\"zkp\",\"zkr\",\"zkt\",\"zku\",\"zkv\",\"zkz\",\"zle\",\"zlj\",\"zlm\",\"zln\",\"zlq\",\"zls\",\"zlw\",\"zma\",\"zmb\",\"zmc\",\"zmd\",\"zme\",\"zmf\",\"zmg\",\"zmh\",\"zmi\",\"zmj\",\"zmk\",\"zml\",\"zmm\",\"zmn\",\"zmo\",\"zmp\",\"zmq\",\"zmr\",\"zms\",\"zmt\",\"zmu\",\"zmv\",\"zmw\",\"zmx\",\"zmy\",\"zmz\",\"zna\",\"znd\",\"zne\",\"zng\",\"znk\",\"zns\",\"zoc\",\"zoh\",\"zom\",\"zoo\",\"zoq\",\"zor\",\"zos\",\"zpa\",\"zpb\",\"zpc\",\"zpd\",\"zpe\",\"zpf\",\"zpg\",\"zph\",\"zpi\",\"zpj\",\"zpk\",\"zpl\",\"zpm\",\"zpn\",\"zpo\",\"zpp\",\"zpq\",\"zpr\",\"zps\",\"zpt\",\"zpu\",\"zpv\",\"zpw\",\"zpx\",\"zpy\",\"zpz\",\"zqe\",\"zra\",\"zrg\",\"zrn\",\"zro\",\"zrp\",\"zrs\",\"zsa\",\"zsk\",\"zsl\",\"zsm\",\"zsr\",\"zsu\",\"zte\",\"ztg\",\"ztl\",\"ztm\",\"ztn\",\"ztp\",\"ztq\",\"zts\",\"ztt\",\"ztu\",\"ztx\",\"zty\",\"zua\",\"zuh\",\"zum\",\"zun\",\"zuy\",\"zwa\",\"zxx\",\"zyb\",\"zyg\",\"zyj\",\"zyn\",\"zyp\",\"zza\",\"zzj\"],\nevaluate:function(a,b){function c(a){return a.trim().split(\"-\")[0].toLowerCase()}var d,e;return d=(b||[]).map(c),e=[\"lang\",\"xml:lang\"].reduce(function(b,e){var f=a.getAttribute(e);if(\"string\"!=typeof f)return b;var g=c(f);return\"\"!==g&&-1===d.indexOf(g)&&b.push(e+'=\"'+a.getAttribute(e)+'\"'),b},[]),!!e.length&&(this.data(e),!0)}},{id:\"dlitem\",evaluate:function(a,b){return\"DL\"===a.parentNode.tagName}},{id:\"has-listitem\",evaluate:function(a,b){var c=a.children;if(0===c.length)return!0;for(var d=0;d<c.length;d++)if(\"LI\"===c[d].nodeName.toUpperCase())return!1;return!0}},{id:\"listitem\",evaluate:function(a,b){return-1!==[\"UL\",\"OL\"].indexOf(a.parentNode.nodeName.toUpperCase())||\"list\"===a.parentNode.getAttribute(\"role\")}},{id:\"only-dlitems\",evaluate:function(a,b){for(var c,d,e=[],f=a.childNodes,g=[\"STYLE\",\"META\",\"LINK\",\"MAP\",\"AREA\",\"SCRIPT\",\"DATALIST\",\"TEMPLATE\"],h=!1,i=0;i<f.length;i++){c=f[i];var d=c.nodeName.toUpperCase();1===c.nodeType&&\"DT\"!==d&&\"DD\"!==d&&-1===g.indexOf(d)?e.push(c):3===c.nodeType&&\"\"!==c.nodeValue.trim()&&(h=!0)}return e.length&&this.relatedNodes(e),!!e.length||h}},{id:\"only-listitems\",evaluate:function(a,b){for(var c,d,e=[],f=a.childNodes,g=[\"STYLE\",\"META\",\"LINK\",\"MAP\",\"AREA\",\"SCRIPT\",\"DATALIST\",\"TEMPLATE\"],h=!1,i=0;i<f.length;i++)c=f[i],d=c.nodeName.toUpperCase(),1===c.nodeType&&\"LI\"!==d&&-1===g.indexOf(d)?e.push(c):3===c.nodeType&&\"\"!==c.nodeValue.trim()&&(h=!0);return e.length&&this.relatedNodes(e),!!e.length||h}},{id:\"structured-dlitems\",evaluate:function(a,b){var c=a.children;if(!c||!c.length)return!1;for(var d,e=!1,f=!1,g=0;g<c.length;g++){if(d=c[g].nodeName.toUpperCase(),\"DT\"===d&&(e=!0),e&&\"DD\"===d)return!1;\"DD\"===d&&(f=!0)}return e||f}},{id:\"caption\",evaluate:function(a,b){var c=a.querySelectorAll(\"track\");if(c.length){for(var d=0;d<c.length;d++){var e=c[d].getAttribute(\"kind\");if(e&&\"captions\"===e)return!1}return!0}}},{id:\"description\",evaluate:function(a,b){var c=a.querySelectorAll(\"track\");if(c.length){for(var d=0;d<c.length;d++){var e=c[d].getAttribute(\"kind\");if(e&&\"descriptions\"===e)return!1}return!0}}},{id:\"meta-viewport-large\",evaluate:function(a,b){b=b||{};for(var c,d=a.getAttribute(\"content\")||\"\",e=d.split(/[;,]/),f={},g=b.scaleMinimum||2,h=b.lowerBound||!1,i=0,j=e.length;i<j;i++){c=e[i].split(\"=\");var k=c.shift().toLowerCase();k&&c.length&&(f[k.trim()]=c.shift().trim().toLowerCase())}return!!(h&&f[\"maximum-scale\"]&&parseFloat(f[\"maximum-scale\"])<h)||!(!h&&\"no\"===f[\"user-scalable\"])&&!(f[\"maximum-scale\"]&&parseFloat(f[\"maximum-scale\"])<g)},options:{scaleMinimum:5,lowerBound:2}},{id:\"meta-viewport\",evaluate:function(a,b){b=b||{};for(var c,d=a.getAttribute(\"content\")||\"\",e=d.split(/[;,]/),f={},g=b.scaleMinimum||2,h=b.lowerBound||!1,i=0,j=e.length;i<j;i++){c=e[i].split(\"=\");var k=c.shift().toLowerCase();k&&c.length&&(f[k.trim()]=c.shift().trim().toLowerCase())}return!!(h&&f[\"maximum-scale\"]&&parseFloat(f[\"maximum-scale\"])<h)||!(!h&&\"no\"===f[\"user-scalable\"])&&!(f[\"maximum-scale\"]&&parseFloat(f[\"maximum-scale\"])<g)},options:{scaleMinimum:2}},{id:\"header-present\",evaluate:function(a,b){return!!a.querySelector('h1, h2, h3, h4, h5, h6, [role=\"heading\"]')}},{id:\"heading-order\",evaluate:function(a,b){var c=a.getAttribute(\"aria-level\");if(null!==c)return this.data(parseInt(c,10)),!0;var d=a.tagName.match(/H(\\d)/);return!d||(this.data(parseInt(d[1],10)),!0)},after:function(a,b){if(a.length<2)return a;for(var c=a[0].data,d=1;d<a.length;d++)a[d].result&&a[d].data>c+1&&(a[d].result=!1),c=a[d].data;return a}},{id:\"href-no-hash\",evaluate:function(a,b){return\"#\"!==a.getAttribute(\"href\")}},{id:\"internal-link-present\",evaluate:function(a,b){return!!a.querySelector('a[href^=\"#\"]')}},{id:\"landmark\",evaluate:function(a,b){return a.getElementsByTagName(\"main\").length>0||!!a.querySelector('[role=\"main\"]')}},{id:\"meta-refresh\",evaluate:function(a,b){var c=a.getAttribute(\"content\")||\"\",d=c.split(/[;,]/);return\"\"===c||\"0\"===d[0]}},{id:\"p-as-heading\",evaluate:function(a,b){function c(a){for(var b=a,c=a.textContent.trim(),d=c;d===c&&void 0!==b;){var e=-1;if(a=b,0===a.children.length)return a;do{e++,d=a.children[e].textContent.trim()}while(\"\"===d&&e+1<a.children.length);b=a.children[e]}return a}function d(a){switch(a){case\"lighter\":return 100;case\"normal\":return 400;case\"bold\":return 700;case\"bolder\":return 900}return a=parseInt(a),isNaN(a)?400:a}function e(a){var b=window.getComputedStyle(c(a));return{fontWeight:d(b.getPropertyValue(\"font-weight\")),fontSize:parseInt(b.getPropertyValue(\"font-size\")),isItalic:\"italic\"===b.getPropertyValue(\"font-style\")}}function f(a,b,c){return c.reduce(function(c,d){return c||(!d.size||a.fontSize/d.size>b.fontSize)&&(!d.weight||a.fontWeight-d.weight>b.fontWeight)&&(!d.italic||a.isItalic&&!b.isItalic)},!1)}var g=Array.from(a.parentNode.children),h=g.indexOf(a);b=b||{};var i=b.margins||[],j=g.slice(h+1).find(function(a){return\"P\"===a.nodeName.toUpperCase()}),k=g.slice(0,h).reverse().find(function(a){return\"P\"===a.nodeName.toUpperCase()}),l=e(a),m=j?e(j):null,n=k?e(k):null;if(!m||!f(l,m,i))return!0;var o=axe.commons.dom.findUp(a,\"blockquote\");return!!(o&&\"BLOCKQUOTE\"===o.nodeName.toUpperCase()||n&&!f(l,n,i))&&void 0},options:{margins:[{weight:150,italic:!0},{weight:150,size:1.15},{italic:!0,size:1.15},{size:1.4}]}},{id:\"region\",evaluate:function(a,b){function c(a){return h&&axe.commons.dom.isFocusable(axe.commons.dom.getElementByReference(h,\"href\"))&&h===a}function d(a){var b=a.getAttribute(\"role\");return b&&-1!==g.indexOf(b)}function e(a){return d(a)?null:c(a)?f(a):axe.commons.dom.isVisible(a,!0)&&(axe.commons.text.visible(a,!0,!0)||axe.commons.dom.isVisualContent(a))?a:f(a)}function f(a){var b=axe.commons.utils.toArray(a.children);return 0===b.length?[]:b.map(e).filter(function(a){return null!==a}).reduce(function(a,b){return a.concat(b)},[])}var g=axe.commons.aria.getRolesByType(\"landmark\"),h=a.querySelector(\"a[href]\"),i=f(a);return this.relatedNodes(i),!i.length},after:function(a,b){return[a[0]]}},{id:\"skip-link\",evaluate:function(a,b){return axe.commons.dom.isFocusable(axe.commons.dom.getElementByReference(a,\"href\"))},after:function(a,b){return[a[0]]}},{id:\"unique-frame-title\",evaluate:function(a,b){var c=axe.commons.text.sanitize(a.title).trim().toLowerCase();return this.data(c),!0},after:function(a,b){var c={};return a.forEach(function(a){c[a.data]=void 0!==c[a.data]?++c[a.data]:0}),a.forEach(function(a){a.result=!!c[a.data]}),a}},{id:\"aria-label\",evaluate:function(a,b){var c=a.getAttribute(\"aria-label\");return!!(c?axe.commons.text.sanitize(c).trim():\"\")}},{id:\"aria-labelledby\",evaluate:function(a,b){return(0,axe.commons.dom.idrefs)(a,\"aria-labelledby\").some(function(a){return a&&axe.commons.text.accessibleText(a,!0)})}},{id:\"button-has-visible-text\",evaluate:function(a,b){var c=a.nodeName.toUpperCase(),d=a.getAttribute(\"role\"),e=void 0;return(\"BUTTON\"===c||\"button\"===d&&\"INPUT\"!==c)&&(e=axe.commons.text.accessibleText(a),this.data(e),!!e)}},{id:\"doc-has-title\",evaluate:function(a,b){var c=document.title;return!!(c?axe.commons.text.sanitize(c).trim():\"\")}},{id:\"duplicate-id\",evaluate:function(a,b){if(!a.id.trim())return!0;for(var c=document.querySelectorAll('[id=\"'+axe.commons.utils.escapeSelector(a.id)+'\"]'),d=[],e=0;e<c.length;e++)c[e]!==a&&d.push(c[e]);return d.length&&this.relatedNodes(d),this.data(a.getAttribute(\"id\")),c.length<=1},after:function(a,b){var c=[];return a.filter(function(a){return-1===c.indexOf(a.data)&&(c.push(a.data),!0)})}},{id:\"exists\",evaluate:function(a,b){return!0}},{id:\"has-alt\",evaluate:function(a,b){var c=a.nodeName.toLowerCase();return a.hasAttribute(\"alt\")&&(\"img\"===c||\"input\"===c||\"area\"===c)}},{id:\"has-visible-text\",evaluate:function(a,b){return axe.commons.text.accessibleText(a).length>0}},{id:\"is-on-screen\",evaluate:function(a,b){return axe.commons.dom.isVisible(a,!1)&&!axe.commons.dom.isOffscreen(a)}},{id:\"non-empty-alt\",evaluate:function(a,b){var c=a.getAttribute(\"alt\");return!!(c?axe.commons.text.sanitize(c).trim():\"\")}},{id:\"non-empty-if-present\",evaluate:function(a,b){var c=a.nodeName.toUpperCase(),d=(a.getAttribute(\"type\")||\"\").toLowerCase(),e=a.getAttribute(\"value\");return this.data(e),\"INPUT\"===c&&-1!==[\"submit\",\"reset\"].indexOf(d)&&null===e}},{id:\"non-empty-title\",evaluate:function(a,b){var c=a.getAttribute(\"title\");return!!(c?axe.commons.text.sanitize(c).trim():\"\")}},{id:\"non-empty-value\",evaluate:function(a,b){var c=a.getAttribute(\"value\");return!!(c?axe.commons.text.sanitize(c).trim():\"\")}},{id:\"role-none\",evaluate:function(a,b){return\"none\"===a.getAttribute(\"role\")}},{id:\"role-presentation\",evaluate:function(a,b){return\"presentation\"===a.getAttribute(\"role\")}},{id:\"caption-faked\",evaluate:function(a,b){var c=axe.commons.table.toGrid(a),d=c[0];return c.length<=1||d.length<=1||a.rows.length<=1||d.reduce(function(a,b,c){return a||b!==d[c+1]&&void 0!==d[c+1]},!1)}},{id:\"has-caption\",evaluate:function(a,b){return!!a.caption}},{id:\"has-summary\",evaluate:function(a,b){return!!a.summary}},{id:\"has-th\",evaluate:function(a,b){for(var c,d,e=[],f=0,g=a.rows.length;f<g;f++){c=a.rows[f];for(var h=0,i=c.cells.length;h<i;h++)d=c.cells[h],\"TH\"!==d.nodeName.toUpperCase()&&-1===[\"rowheader\",\"columnheader\"].indexOf(d.getAttribute(\"role\"))||e.push(d)}return!!e.length&&(this.relatedNodes(e),!0)}},{id:\"html5-scope\",evaluate:function(a,b){return!axe.commons.dom.isHTML5(document)||\"TH\"===a.nodeName.toUpperCase()}},{id:\"same-caption-summary\",evaluate:function(a,b){return!(!a.summary||!a.caption)&&a.summary===axe.commons.text.accessibleText(a.caption)}},{id:\"scope-value\",evaluate:function(a,b){b=b||{};var c=a.getAttribute(\"scope\").toLowerCase();return-1!==([\"row\",\"col\",\"rowgroup\",\"colgroup\"]||b.values).indexOf(c)}},{id:\"td-has-header\",evaluate:function(a,b){var c=axe.commons.table,d=[];return c.getAllCells(a).forEach(function(a){if(axe.commons.dom.hasContent(a)&&c.isDataCell(a)&&!axe.commons.aria.label(a)){var b=c.getHeaders(a);(b=b.reduce(function(a,b){return a||null!==b&&!!axe.commons.dom.hasContent(b)},!1))||d.push(a)}}),!d.length||(this.relatedNodes(d),!1)}},{id:\"td-headers-attr\",evaluate:function(a,b){for(var c=[],d=0,e=a.rows.length;d<e;d++)for(var f=a.rows[d],g=0,h=f.cells.length;g<h;g++)c.push(f.cells[g]);var i=c.reduce(function(a,b){return b.id&&a.push(b.id),a},[]),j=c.reduce(function(a,b){var c,d,e=(b.getAttribute(\"headers\")||\"\").split(/\\s/).reduce(function(a,b){return b=b.trim(),b&&a.push(b),a},[]);return 0!==e.length&&(b.id&&(c=-1!==e.indexOf(b.id.trim())),d=e.reduce(function(a,b){return a||-1===i.indexOf(b)},!1),(c||d)&&a.push(b)),a},[]);return!(j.length>0)||(this.relatedNodes(j),!1)}},{id:\"th-has-data-cells\",evaluate:function(a,b){var c=axe.commons.table,d=c.getAllCells(a),e=this,f=[];d.forEach(function(a){var b=a.getAttribute(\"headers\");b&&(f=f.concat(b.split(/\\s+/)));var c=a.getAttribute(\"aria-labelledby\");c&&(f=f.concat(c.split(/\\s+/)))});var g=d.filter(function(a){return\"\"!==axe.commons.text.sanitize(a.textContent)&&(\"TH\"===a.nodeName.toUpperCase()||-1!==[\"rowheader\",\"columnheader\"].indexOf(a.getAttribute(\"role\")))}),h=c.toGrid(a);return!!g.reduce(function(a,b){if(b.id&&-1!==f.indexOf(b.id))return!!a||a;var d=!1,g=c.getCellPosition(b,h);return c.isColumnHeader(b)&&(d=c.traverse(\"down\",g,h).reduce(function(a,b){return a||axe.commons.dom.hasContent(b)&&!c.isColumnHeader(b)},!1)),!d&&c.isRowHeader(b)&&(d=c.traverse(\"right\",g,h).reduce(function(a,b){return a||axe.commons.dom.hasContent(b)&&!c.isRowHeader(b)},!1)),d||e.relatedNodes(b),a&&d},!0)||void 0}}],commons:function(){function a(a){return a.getPropertyValue(\"font-family\").split(/[,;]/g).map(function(a){return a.trim().toLowerCase()})}function b(b,c){var d=window.getComputedStyle(b);if(\"none\"!==d.getPropertyValue(\"background-image\"))return!0;if([\"border-bottom\",\"border-top\",\"outline\"].reduce(function(a,b){var c=new v.Color;return c.parseRgbString(d.getPropertyValue(b+\"-color\")),a||\"none\"!==d.getPropertyValue(b+\"-style\")&&parseFloat(d.getPropertyValue(b+\"-width\"))>0&&0!==c.alpha},!1))return!0;var e=window.getComputedStyle(c);if(a(d)[0]!==a(e)[0])return!0;var f=[\"text-decoration-line\",\"text-decoration-style\",\"font-weight\",\"font-style\",\"font-size\"].reduce(function(a,b){return a||d.getPropertyValue(b)!==e.getPropertyValue(b)},!1),g=d.getPropertyValue(\"text-decoration\");return g.split(\" \").length<3&&(f=f||g!==e.getPropertyValue(\"text-decoration\")),f}function c(a,b){var c=a.nodeName.toUpperCase();if(z.includes(c))return axe.commons.color.incompleteData.set(\"bgColor\",{node:a,reason:\"imgNode\"}),!0;b=b||window.getComputedStyle(a);var d=b.getPropertyValue(\"background-image\"),e=\"none\"!==d;if(e){var f=/gradient/.test(d);axe.commons.color.incompleteData.set(\"bgColor\",{node:a,reason:f?\"bgGradient\":\"bgImage\"})}return e}function d(a,b){b=b||window.getComputedStyle(a);var c=new v.Color;if(c.parseRgbString(b.getPropertyValue(\"background-color\")),0!==c.alpha){var d=b.getPropertyValue(\"opacity\");c.alpha=c.alpha*d}return c}function e(a,b){var c=0;if(a>0)for(var e=a-1;e>=0;e--){var f=b[e],g=window.getComputedStyle(f),h=d(f,g);h.alpha?c+=h.alpha:b.splice(e,1)}return c}function f(a,b,c){return a!==b&&!w.visuallyContains(a,b)&&0!==c.alpha}function g(a,b){\"use strict\";var c=b(a);for(a=a.firstChild;a;)!1!==c&&g(a,b),a=a.nextSibling}function h(a){\"use strict\";var b=window.getComputedStyle(a).getPropertyValue(\"display\");return-1!==A.indexOf(b)||\"table-\"===b.substr(0,6)}function i(a){\"use strict\";var b=a.match(/rect\\s*\\(([0-9]+)px,?\\s*([0-9]+)px,?\\s*([0-9]+)px,?\\s*([0-9]+)px\\s*\\)/);return!(!b||5!==b.length)&&(b[3]-b[1]<=0&&b[2]-b[4]<=0)}function j(a){var b=null;return a.id&&(b=document.querySelector('label[for=\"'+axe.utils.escapeSelector(a.id)+'\"]'))?b:b=w.findUp(a,\"label\")}function k(a){return-1!==[\"button\",\"reset\",\"submit\"].indexOf(a.type)}function l(a){var b=a.nodeName.toUpperCase();return\"TEXTAREA\"===b||\"SELECT\"===b||\"INPUT\"===b&&\"hidden\"!==a.type.toLowerCase()}function m(a){return-1!==[\"BUTTON\",\"SUMMARY\",\"A\"].indexOf(a.nodeName.toUpperCase())}function n(a){return-1!==[\"TABLE\",\"FIGURE\"].indexOf(a.nodeName.toUpperCase())}function o(a){var b=a.nodeName.toUpperCase();if(\"INPUT\"===b)return!a.hasAttribute(\"type\")||-1!==D.indexOf(a.getAttribute(\"type\").toLowerCase())&&a.value?a.value:\"\";if(\"SELECT\"===b){var c=a.options;if(c&&c.length){for(var d=\"\",e=0;e<c.length;e++)c[e].selected&&(d+=\" \"+c[e].text);return y.sanitize(d)}return\"\"}return\"TEXTAREA\"===b&&a.value?a.value:\"\"}function p(a,b){var c=a.querySelector(b.toLowerCase());return c?y.accessibleText(c):\"\"}function q(a){if(!a)return!1;switch(a.nodeName.toUpperCase()){case\"SELECT\":case\"TEXTAREA\":return!0;case\"INPUT\":return!a.hasAttribute(\"type\")||-1!==D.indexOf(a.getAttribute(\"type\").toLowerCase());default:return!1}}function r(a){var b=a.nodeName.toUpperCase();return\"INPUT\"===b&&\"image\"===a.type.toLowerCase()||-1!==[\"IMG\",\"APPLET\",\"AREA\"].indexOf(b)}function s(a){return!!y.sanitize(a)}var commons={},t=commons.aria={},u=t._lut={};u.attributes={\"aria-activedescendant\":{type:\"idref\"},\"aria-atomic\":{type:\"boolean\",values:[\"true\",\"false\"]},\"aria-autocomplete\":{type:\"nmtoken\",values:[\"inline\",\"list\",\"both\",\"none\"]},\"aria-busy\":{type:\"boolean\",values:[\"true\",\"false\"]},\"aria-checked\":{type:\"nmtoken\",values:[\"true\",\"false\",\"mixed\",\"undefined\"]},\"aria-colcount\":{type:\"int\"},\"aria-colindex\":{type:\"int\"},\"aria-colspan\":{type:\"int\"},\"aria-controls\":{type:\"idrefs\"},\"aria-current\":{type:\"nmtoken\",values:[\"page\",\"step\",\"location\",\"date\",\"time\",\"true\",\"false\"]},\"aria-describedby\":{type:\"idrefs\"},\"aria-disabled\":{type:\"boolean\",values:[\"true\",\"false\"]},\"aria-dropeffect\":{type:\"nmtokens\",values:[\"copy\",\"move\",\"reference\",\"execute\",\"popup\",\"none\"]},\"aria-expanded\":{type:\"nmtoken\",values:[\"true\",\"false\",\"undefined\"]},\"aria-flowto\":{type:\"idrefs\"},\"aria-grabbed\":{type:\"nmtoken\",values:[\"true\",\"false\",\"undefined\"]},\"aria-haspopup\":{type:\"boolean\",values:[\"true\",\"false\"]},\"aria-hidden\":{type:\"boolean\",values:[\"true\",\"false\"]},\"aria-invalid\":{type:\"nmtoken\",values:[\"true\",\"false\",\"spelling\",\"grammar\"]},\"aria-label\":{type:\"string\"},\"aria-labelledby\":{type:\"idrefs\"},\"aria-level\":{type:\"int\"},\"aria-live\":{type:\"nmtoken\",values:[\"off\",\"polite\",\"assertive\"]},\"aria-multiline\":{type:\"boolean\",values:[\"true\",\"false\"]},\"aria-multiselectable\":{type:\"boolean\",values:[\"true\",\"false\"]},\"aria-orientation\":{type:\"nmtoken\",values:[\"horizontal\",\"vertical\"]},\"aria-owns\":{type:\"idrefs\"},\"aria-posinset\":{type:\"int\"},\"aria-pressed\":{type:\"nmtoken\",values:[\"true\",\"false\",\"mixed\",\"undefined\"]},\"aria-readonly\":{type:\"boolean\",values:[\"true\",\"false\"]},\"aria-relevant\":{type:\"nmtokens\",values:[\"additions\",\"removals\",\"text\",\"all\"]},\"aria-required\":{type:\"boolean\",values:[\"true\",\"false\"]},\"aria-rowcount\":{type:\"int\"},\"aria-rowindex\":{type:\"int\"},\"aria-rowspan\":{type:\"int\"},\"aria-selected\":{type:\"nmtoken\",values:[\"true\",\"false\",\"undefined\"]},\"aria-setsize\":{type:\"int\"},\"aria-sort\":{type:\"nmtoken\",values:[\"ascending\",\"descending\",\"other\",\"none\"]},\"aria-valuemax\":{type:\"decimal\"},\"aria-valuemin\":{type:\"decimal\"},\"aria-valuenow\":{type:\"decimal\"},\"aria-valuetext\":{type:\"string\"}},u.globalAttributes=[\"aria-atomic\",\"aria-busy\",\"aria-controls\",\"aria-current\",\"aria-describedby\",\"aria-disabled\",\"aria-dropeffect\",\"aria-flowto\",\"aria-grabbed\",\"aria-haspopup\",\"aria-hidden\",\"aria-invalid\",\"aria-label\",\"aria-labelledby\",\"aria-live\",\"aria-owns\",\"aria-relevant\"],u.role={alert:{type:\"widget\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null},alertdialog:{type:\"widget\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null},application:{type:\"landmark\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null},article:{type:\"structure\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null,implicit:[\"article\"]},banner:{type:\"landmark\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null,implicit:[\"header\"]},button:{type:\"widget\",attributes:{allowed:[\"aria-expanded\",\"aria-pressed\"]},owned:null,nameFrom:[\"author\",\"contents\"],context:null,implicit:[\"button\",'input[type=\"button\"]','input[type=\"image\"]','input[type=\"reset\"]','input[type=\"submit\"]',\"summary\"]},cell:{type:\"structure\",attributes:{allowed:[\"aria-colindex\",\"aria-colspan\",\"aria-rowindex\",\"aria-rowspan\"]},owned:null,nameFrom:[\"author\",\"contents\"],context:[\"row\"],implicit:[\"td\",\"th\"]},checkbox:{type:\"widget\",attributes:{required:[\"aria-checked\"]},owned:null,nameFrom:[\"author\",\"contents\"],context:null,implicit:['input[type=\"checkbox\"]']},columnheader:{type:\"structure\",attributes:{allowed:[\"aria-expanded\",\"aria-sort\",\"aria-readonly\",\"aria-selected\",\"aria-required\"]},owned:null,nameFrom:[\"author\",\"contents\"],context:[\"row\"],implicit:[\"th\"]},combobox:{type:\"composite\",attributes:{required:[\"aria-expanded\"],allowed:[\"aria-autocomplete\",\"aria-required\",\"aria-activedescendant\"]},owned:{all:[\"listbox\",\"textbox\"]},nameFrom:[\"author\"],context:null},command:{nameFrom:[\"author\"],type:\"abstract\"},complementary:{type:\"landmark\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null,implicit:[\"aside\"]},composite:{nameFrom:[\"author\"],type:\"abstract\"},contentinfo:{type:\"landmark\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null,implicit:[\"footer\"]},definition:{type:\"structure\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null,implicit:[\"dd\"]},dialog:{type:\"widget\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null,implicit:[\"dialog\"]},directory:{type:\"structure\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\",\"contents\"],context:null},document:{type:\"structure\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null,implicit:[\"body\"]},form:{type:\"landmark\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null,implicit:[\"form\"]},grid:{type:\"composite\",attributes:{allowed:[\"aria-level\",\"aria-multiselectable\",\"aria-readonly\",\"aria-activedescendant\",\"aria-expanded\"]},owned:{one:[\"rowgroup\",\"row\"]},nameFrom:[\"author\"],context:null,implicit:[\"table\"]},gridcell:{type:\"widget\",attributes:{allowed:[\"aria-selected\",\"aria-readonly\",\"aria-expanded\",\"aria-required\"]},owned:null,nameFrom:[\"author\",\"contents\"],context:[\"row\"],implicit:[\"td\",\"th\"]},group:{type:\"structure\",attributes:{allowed:[\"aria-activedescendant\",\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null,implicit:[\"details\",\"optgroup\"]},heading:{type:\"structure\",attributes:{allowed:[\"aria-level\",\"aria-expanded\"]},owned:null,nameFrom:[\"author\",\"contents\"],context:null,implicit:[\"h1\",\"h2\",\"h3\",\"h4\",\"h5\",\"h6\"]},img:{type:\"structure\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null,implicit:[\"img\"]},input:{nameFrom:[\"author\"],type:\"abstract\"},landmark:{nameFrom:[\"author\"],type:\"abstract\"},link:{type:\"widget\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\",\"contents\"],context:null,implicit:[\"a[href]\"]},list:{type:\"structure\",attributes:{allowed:[\"aria-expanded\"]},owned:{all:[\"listitem\"]},nameFrom:[\"author\"],context:null,implicit:[\"ol\",\"ul\",\"dl\"]},listbox:{type:\"composite\",attributes:{allowed:[\"aria-activedescendant\",\"aria-multiselectable\",\"aria-required\",\"aria-expanded\"]},owned:{all:[\"option\"]},nameFrom:[\"author\"],context:null,implicit:[\"select\"]},listitem:{type:\"structure\",attributes:{allowed:[\"aria-level\",\"aria-posinset\",\"aria-setsize\",\"aria-expanded\"]},owned:null,nameFrom:[\"author\",\"contents\"],context:[\"list\"],implicit:[\"li\",\"dt\"]},log:{type:\"widget\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null},main:{type:\"landmark\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null,implicit:[\"main\"]},marquee:{type:\"widget\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null},math:{type:\"structure\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null,implicit:[\"math\"]},menu:{type:\"composite\",attributes:{allowed:[\"aria-activedescendant\",\"aria-expanded\"]},owned:{one:[\"menuitem\",\"menuitemradio\",\"menuitemcheckbox\"]},nameFrom:[\"author\"],context:null,implicit:['menu[type=\"context\"]']},menubar:{type:\"composite\",attributes:{allowed:[\"aria-activedescendant\",\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null},menuitem:{type:\"widget\",attributes:null,owned:null,nameFrom:[\"author\",\"contents\"],context:[\"menu\",\"menubar\"],implicit:['menuitem[type=\"command\"]']},menuitemcheckbox:{type:\"widget\",attributes:{required:[\"aria-checked\"]},owned:null,nameFrom:[\"author\",\"contents\"],context:[\"menu\",\"menubar\"],implicit:['menuitem[type=\"checkbox\"]']},menuitemradio:{type:\"widget\",attributes:{allowed:[\"aria-selected\",\"aria-posinset\",\"aria-setsize\"],required:[\"aria-checked\"]},owned:null,nameFrom:[\"author\",\"contents\"],context:[\"menu\",\"menubar\"],implicit:['menuitem[type=\"radio\"]']},navigation:{type:\"landmark\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null,implicit:[\"nav\"]},none:{type:\"structure\",attributes:null,owned:null,nameFrom:[\"author\"],context:null},note:{type:\"structure\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null},option:{type:\"widget\",attributes:{allowed:[\"aria-selected\",\"aria-posinset\",\"aria-setsize\",\"aria-checked\"]},owned:null,nameFrom:[\"author\",\"contents\"],context:[\"listbox\"],implicit:[\"option\"]},presentation:{type:\"structure\",attributes:null,owned:null,nameFrom:[\"author\"],context:null},progressbar:{type:\"widget\",attributes:{allowed:[\"aria-valuetext\",\"aria-valuenow\",\"aria-valuemax\",\"aria-valuemin\"]},owned:null,nameFrom:[\"author\"],context:null,implicit:[\"progress\"]},radio:{type:\"widget\",attributes:{allowed:[\"aria-selected\",\"aria-posinset\",\"aria-setsize\"],required:[\"aria-checked\"]},owned:null,nameFrom:[\"author\",\"contents\"],context:null,implicit:['input[type=\"radio\"]']},radiogroup:{type:\"composite\",attributes:{allowed:[\"aria-activedescendant\",\"aria-required\",\"aria-expanded\"]},owned:{all:[\"radio\"]},nameFrom:[\"author\"],context:null},range:{nameFrom:[\"author\"],type:\"abstract\"},region:{type:\"structure\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null,implicit:[\"section\"]},roletype:{type:\"abstract\"},row:{type:\"structure\",attributes:{allowed:[\"aria-level\",\"aria-selected\",\"aria-activedescendant\",\"aria-expanded\"]},owned:{one:[\"cell\",\"columnheader\",\"rowheader\",\"gridcell\"]},nameFrom:[\"author\",\"contents\"],context:[\"rowgroup\",\"grid\",\"treegrid\",\"table\"],implicit:[\"tr\"]},rowgroup:{type:\"structure\",attributes:{allowed:[\"aria-activedescendant\",\"aria-expanded\"]},owned:{all:[\"row\"]},nameFrom:[\"author\",\"contents\"],context:[\"grid\",\"table\"],implicit:[\"tbody\",\"thead\",\"tfoot\"]},rowheader:{type:\"structure\",attributes:{allowed:[\"aria-sort\",\"aria-required\",\"aria-readonly\",\"aria-expanded\",\"aria-selected\"]},owned:null,nameFrom:[\"author\",\"contents\"],context:[\"row\"],implicit:[\"th\"]},scrollbar:{type:\"widget\",attributes:{required:[\"aria-controls\",\"aria-orientation\",\"aria-valuenow\",\"aria-valuemax\",\"aria-valuemin\"],allowed:[\"aria-valuetext\"]},owned:null,nameFrom:[\"author\"],context:null},search:{type:\"landmark\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null},searchbox:{type:\"widget\",attributes:{allowed:[\"aria-activedescendant\",\"aria-autocomplete\",\"aria-multiline\",\"aria-readonly\",\"aria-required\"]},owned:null,nameFrom:[\"author\"],context:null,implicit:['input[type=\"search\"]']},section:{nameFrom:[\"author\",\"contents\"],type:\"abstract\"},sectionhead:{nameFrom:[\"author\",\"contents\"],type:\"abstract\"},select:{nameFrom:[\"author\"],type:\"abstract\"},separator:{type:\"structure\",attributes:{allowed:[\"aria-expanded\",\"aria-orientation\"]},owned:null,nameFrom:[\"author\"],context:null,implicit:[\"hr\"]},slider:{type:\"widget\",attributes:{allowed:[\"aria-valuetext\",\"aria-orientation\"],required:[\"aria-valuenow\",\"aria-valuemax\",\"aria-valuemin\"]},owned:null,nameFrom:[\"author\"],context:null,implicit:['input[type=\"range\"]']},spinbutton:{type:\"widget\",attributes:{allowed:[\"aria-valuetext\",\"aria-required\"],required:[\"aria-valuenow\",\"aria-valuemax\",\"aria-valuemin\"]},owned:null,nameFrom:[\"author\"],context:null,implicit:['input[type=\"number\"]']},status:{type:\"widget\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null,implicit:[\"output\"]},structure:{type:\"abstract\"},switch:{type:\"widget\",attributes:{required:[\"aria-checked\"]},owned:null,nameFrom:[\"author\",\"contents\"],context:null},tab:{type:\"widget\",attributes:{allowed:[\"aria-selected\",\"aria-expanded\"]},owned:null,nameFrom:[\"author\",\"contents\"],context:[\"tablist\"]},table:{type:\"structure\",attributes:{allowed:[\"aria-colcount\",\"aria-rowcount\"]},owned:{one:[\"rowgroup\",\"row\"]},nameFrom:[\"author\"],context:null,implicit:[\"table\"]},tablist:{type:\"composite\",attributes:{allowed:[\"aria-activedescendant\",\"aria-expanded\",\"aria-level\",\"aria-multiselectable\"]},owned:{all:[\"tab\"]},nameFrom:[\"author\"],context:null},tabpanel:{type:\"widget\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null},text:{type:\"structure\",owned:null,nameFrom:[\"author\",\"contents\"],context:null},textbox:{type:\"widget\",attributes:{allowed:[\"aria-activedescendant\",\"aria-autocomplete\",\"aria-multiline\",\"aria-readonly\",\"aria-required\"]},owned:null,nameFrom:[\"author\"],context:null,implicit:['input[type=\"text\"]','input[type=\"email\"]','input[type=\"password\"]','input[type=\"tel\"]','input[type=\"url\"]',\"input:not([type])\",\"textarea\"]},timer:{type:\"widget\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null},toolbar:{type:\"structure\",attributes:{allowed:[\"aria-activedescendant\",\"aria-expanded\"]},owned:null,nameFrom:[\"author\"],context:null,implicit:['menu[type=\"toolbar\"]']},tooltip:{type:\"widget\",attributes:{allowed:[\"aria-expanded\"]},owned:null,nameFrom:[\"author\",\"contents\"],context:null},tree:{type:\"composite\",attributes:{allowed:[\"aria-activedescendant\",\"aria-multiselectable\",\"aria-required\",\"aria-expanded\"]},owned:{all:[\"treeitem\"]},nameFrom:[\"author\"],context:null},treegrid:{type:\"composite\",attributes:{allowed:[\"aria-activedescendant\",\"aria-expanded\",\"aria-level\",\"aria-multiselectable\",\"aria-readonly\",\"aria-required\"]},owned:{all:[\"treeitem\"]},nameFrom:[\"author\"],context:null},treeitem:{type:\"widget\",attributes:{allowed:[\"aria-checked\",\"aria-selected\",\"aria-expanded\",\"aria-level\",\"aria-posinset\",\"aria-setsize\"]},owned:null,nameFrom:[\"author\",\"contents\"],context:[\"treegrid\",\"tree\"]},widget:{type:\"abstract\"},window:{nameFrom:[\"author\"],type:\"abstract\"}};var v={};commons.color=v;var w=commons.dom={},x=commons.table={},y=commons.text={};commons.utils=axe.utils;t.requiredAttr=function(a){\"use strict\";var b=u.role[a];return b&&b.attributes&&b.attributes.required||[]},t.allowedAttr=function(a){\"use strict\";var b=u.role[a],c=b&&b.attributes&&b.attributes.allowed||[],d=b&&b.attributes&&b.attributes.required||[];return c.concat(u.globalAttributes).concat(d)},t.validateAttr=function(a){\"use strict\";return!!u.attributes[a]},t.validateAttrValue=function(a,b){\"use strict\";var c,d,e=document,f=a.getAttribute(b),g=u.attributes[b];if(!g)return!0;switch(g.type){case\"boolean\":case\"nmtoken\":return\"string\"==typeof f&&-1!==g.values.indexOf(f.toLowerCase());case\"nmtokens\":return d=axe.utils.tokenList(f),d.reduce(function(a,b){return a&&-1!==g.values.indexOf(b)},0!==d.length);case\"idref\":return!(!f||!e.getElementById(f));case\"idrefs\":return d=axe.utils.tokenList(f),d.reduce(function(a,b){return!(!a||!e.getElementById(b))},0!==d.length);case\"string\":return!0;case\"decimal\":return!(!(c=f.match(/^[-+]?([0-9]*)\\.?([0-9]*)$/))||!c[1]&&!c[2]);case\"int\":return/^[-+]?[0-9]+$/.test(f)}},t.label=function(a){var b,c;return a.getAttribute(\"aria-labelledby\")&&(b=w.idrefs(a,\"aria-labelledby\"),c=b.map(function(a){return a?y.visible(a,!0):\"\"}).join(\" \").trim())?c:(c=a.getAttribute(\"aria-label\"),c&&(c=y.sanitize(c).trim())?c:null)},t.isValidRole=function(a){\"use strict\";return!!u.role[a]},t.getRolesWithNameFromContents=function(){return Object.keys(u.role).filter(function(a){return u.role[a].nameFrom&&-1!==u.role[a].nameFrom.indexOf(\"contents\")})},t.getRolesByType=function(a){return Object.keys(u.role).filter(function(b){return u.role[b].type===a})},t.getRoleType=function(a){var b=u.role[a];return b&&b.type||null},t.requiredOwned=function(a){\"use strict\";var b=null,c=u.role[a];return c&&(b=axe.utils.clone(c.owned)),b},t.requiredContext=function(a){\"use strict\";var b=null,c=u.role[a];return c&&(b=axe.utils.clone(c.context)),b},t.implicitNodes=function(a){\"use strict\";var b=null,c=u.role[a];return c&&c.implicit&&(b=axe.utils.clone(c.implicit)),b},t.implicitRole=function(a){\"use strict\";var b=function(b,c){var d=function(b){return axe.utils.matchesSelector(a,b)};return c.implicit&&c.implicit.some(d)&&b.push(c.name),b},c=Object.keys(u.role).map(function(a){var b=u.role[a];return{name:a,implicit:b&&b.implicit}}),d=c.reduce(b,[]);if(!d.length)return null;for(var e=a.attributes,f=[],g=0,h=e.length;g<h;g++){var i=e[g];i.name.match(/^aria-/)&&f.push(i.name)}return function(a,b){var c=function(a){return t.allowedAttr(a).reduce(function(a,c){return a+(b.indexOf(c)>-1?1:0)},0)};return a.map(function(a){return{score:c(a),name:a}}).sort(function(a,b){return b.score-a.score}).map(function(a){return a.name})}(d,f).shift()},v.Color=function(a,b,c,d){this.red=a,this.green=b,this.blue=c,this.alpha=d,this.toHexString=function(){var a=Math.round(this.red).toString(16),b=Math.round(this.green).toString(16),c=Math.round(this.blue).toString(16);return\"#\"+(this.red>15.5?a:\"0\"+a)+(this.green>15.5?b:\"0\"+b)+(this.blue>15.5?c:\"0\"+c)};this.parseRgbString=function(a){if(\"transparent\"===a)return this.red=0,this.green=0,this.blue=0,void(this.alpha=0);var b=a.match(/^rgb\\((\\d+), (\\d+), (\\d+)\\)$/);return b?(this.red=parseInt(b[1],10),this.green=parseInt(b[2],10),this.blue=parseInt(b[3],10),void(this.alpha=1)):(b=a.match(/^rgba\\((\\d+), (\\d+), (\\d+), (\\d*(\\.\\d+)?)\\)/),b?(this.red=parseInt(b[1],10),this.green=parseInt(b[2],10),\nthis.blue=parseInt(b[3],10),void(this.alpha=parseFloat(b[4]))):void 0)},this.getRelativeLuminance=function(){var a=this.red/255,b=this.green/255,c=this.blue/255;return.2126*(a<=.03928?a/12.92:Math.pow((a+.055)/1.055,2.4))+.7152*(b<=.03928?b/12.92:Math.pow((b+.055)/1.055,2.4))+.0722*(c<=.03928?c/12.92:Math.pow((c+.055)/1.055,2.4))}},v.flattenColors=function(a,b){var c=a.alpha,d=(1-c)*b.red+c*a.red,e=(1-c)*b.green+c*a.green,f=(1-c)*b.blue+c*a.blue,g=a.alpha+b.alpha*(1-a.alpha);return new v.Color(d,e,f,g)},v.getContrast=function(a,b){if(!b||!a)return null;b.alpha<1&&(b=v.flattenColors(b,a));var c=a.getRelativeLuminance(),d=b.getRelativeLuminance();return(Math.max(d,c)+.05)/(Math.min(d,c)+.05)},v.hasValidContrastRatio=function(a,b,c,d){var e=v.getContrast(a,b),f=d&&Math.ceil(72*c)/96<14||!d&&Math.ceil(72*c)/96<18;return{isValid:f&&e>=4.5||!f&&e>=3,contrastRatio:e}},v.elementIsDistinct=b;var z=[\"IMG\",\"CANVAS\",\"OBJECT\",\"IFRAME\",\"VIDEO\",\"SVG\"];v.getBackgroundStack=function(a){var b=a.getBoundingClientRect(),f=void 0,g=void 0;if(!(b.left>window.innerWidth||b.top>window.innerHeight)){f=Math.min(Math.ceil(b.left+b.width/2),window.innerWidth-1),g=Math.min(Math.ceil(b.top+b.height/2),window.innerHeight-1);var h=document.elementsFromPoint(f,g);h=w.reduceToElementsBelowFloating(h,a);var i=h.indexOf(document.body);i>1&&!c(document.documentElement)&&0===d(document.documentElement).alpha&&(h.splice(i,1),h.splice(h.indexOf(document.documentElement),1),h.push(document.body));var j=h.indexOf(a);return e(j,h)>=.99?(axe.commons.color.incompleteData.set(\"bgColor\",{node:a,reason:\"bgOverlap\"}),null):-1!==j?h:null}},v.getBackgroundColor=function(a){var b=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[];!0!==(arguments.length>2&&void 0!==arguments[2]&&arguments[2])&&a.scrollIntoView();var e=[],g=v.getBackgroundStack(a);if((g||[]).some(function(g){var h=window.getComputedStyle(g),i=d(g,h);return f(a,g,i)||c(g,h)?(e=null,b.push(g),!0):0!==i.alpha&&(b.push(g),e.push(i),1===i.alpha)}),null!==e&&null!==g){e.push(new v.Color(255,255,255,1));return e.reduce(v.flattenColors)}return null},w.isOpaque=function(a){var b=window.getComputedStyle(a);return c(a,b)||1===d(a,b).alpha},v.getForegroundColor=function(a,b){var c=window.getComputedStyle(a),d=new v.Color;d.parseRgbString(c.getPropertyValue(\"color\"));var e=c.getPropertyValue(\"opacity\");if(d.alpha=d.alpha*e,1===d.alpha)return d;var f=v.getBackgroundColor(a,[],b);if(null===f){var g=axe.commons.color.incompleteData.get(\"bgColor\").reason;return axe.commons.color.incompleteData.set(\"fgColor\",{node:a,reason:g}),null}return v.flattenColors(d,f)},v.incompleteData=function(){var a={};return{set:function(b,c){if(\"string\"!=typeof b)throw new Error(\"Incomplete data: key must be a string\");c&&(a[b]=c)},get:function(b){return a[b]},clear:function(){a={}}}}(),w.reduceToElementsBelowFloating=function(a,b){var c,d,e,f=[\"fixed\",\"sticky\"],g=[],h=!1;for(c=0;c<a.length;++c)d=a[c],d===b&&(h=!0),e=window.getComputedStyle(d),h||-1===f.indexOf(e.position)?g.push(d):g=[];return g},w.findUp=function(a,b){\"use strict\";var c,d=document.querySelectorAll(b);if(!d.length)return null;for(d=axe.utils.toArray(d),c=a.parentNode;c&&-1===d.indexOf(c);)c=c.parentNode;return c},w.getElementByReference=function(a,b){\"use strict\";var c,d=a.getAttribute(b),e=document;if(d&&\"#\"===d.charAt(0)){if(d=d.substring(1),c=e.getElementById(d))return c;if(c=e.getElementsByName(d),c.length)return c[0]}return null},w.getElementCoordinates=function(a){\"use strict\";var b=w.getScrollOffset(document),c=b.left,d=b.top,e=a.getBoundingClientRect();return{top:e.top+d,right:e.right+c,bottom:e.bottom+d,left:e.left+c,width:e.right-e.left,height:e.bottom-e.top}},w.getScrollOffset=function(a){\"use strict\";if(!a.nodeType&&a.document&&(a=a.document),9===a.nodeType){var b=a.documentElement,c=a.body;return{left:b&&b.scrollLeft||c&&c.scrollLeft||0,top:b&&b.scrollTop||c&&c.scrollTop||0}}return{left:a.scrollLeft,top:a.scrollTop}},w.getViewportSize=function(a){\"use strict\";var b,c=a.document,d=c.documentElement;return a.innerWidth?{width:a.innerWidth,height:a.innerHeight}:d?{width:d.clientWidth,height:d.clientHeight}:(b=c.body,{width:b.clientWidth,height:b.clientHeight})},w.hasContent=function(a){var b=arguments.length>1&&void 0!==arguments[1]?arguments[1]:[];if(a.textContent.trim()||t.label(a))return!0;for(var c=a.querySelectorAll(\"*\"),d=0;d<c.length;d++)if(-1===b.indexOf(c[d])&&t.label(c[d])||w.isVisualContent(c[d]))return!0;return!1},w.idrefs=function(a,b){\"use strict\";var c,d,e=document,f=[],g=a.getAttribute(b);if(g)for(g=axe.utils.tokenList(g),c=0,d=g.length;c<d;c++)f.push(e.getElementById(g[c]));return f},w.isFocusable=function(a){\"use strict\";if(!a||a.disabled||!w.isVisible(a)&&\"AREA\"!==a.nodeName.toUpperCase())return!1;switch(a.nodeName.toUpperCase()){case\"A\":case\"AREA\":if(a.href)return!0;break;case\"INPUT\":return\"hidden\"!==a.type;case\"TEXTAREA\":case\"SELECT\":case\"DETAILS\":case\"BUTTON\":return!0}var b=a.getAttribute(\"tabindex\");return!(!b||isNaN(parseInt(b,10)))},w.isHTML5=function(a){var b=a.doctype;return null!==b&&(\"html\"===b.name&&!b.publicId&&!b.systemId)};var A=[\"block\",\"list-item\",\"table\",\"flex\",\"grid\",\"inline-block\"];w.isInTextBlock=function(a){\"use strict\";if(h(a))return!1;for(var b=a.parentNode;1===b.nodeType&&!h(b);)b=b.parentNode;var c=\"\",d=\"\",e=0;return g(b,function(b){if(2===e)return!1;if(3===b.nodeType&&(c+=b.nodeValue),1===b.nodeType){var f=(b.nodeName||\"\").toUpperCase();if(-1!==[\"BR\",\"HR\"].indexOf(f))0===e?(c=\"\",d=\"\"):e=2;else{if(\"none\"===b.style.display||\"hidden\"===b.style.overflow||-1===[\"\",null,\"none\"].indexOf(b.style.float)||-1===[\"\",null,\"relative\"].indexOf(b.style.position))return!1;if(\"A\"===f&&b.href||\"link\"===(b.getAttribute(\"role\")||\"\").toLowerCase())return b===a&&(e=1),d+=b.textContent,!1}}}),c=axe.commons.text.sanitize(c),d=axe.commons.text.sanitize(d),c.length>d.length},w.isNode=function(a){\"use strict\";return a instanceof Node},w.isOffscreen=function(a){\"use strict\";var b,c=document.documentElement,d=window.getComputedStyle(document.body||c).getPropertyValue(\"direction\"),e=w.getElementCoordinates(a);if(e.bottom<0&&function(a,b){for(a=a.parentNode;\"html\"!==a.nodeName.toLowerCase();){if(a.scrollTop&&(b+=a.scrollTop)>=0)return!1;a=a.parentNode}return!0}(a,e.bottom))return!0;if(0===e.left&&0===e.right)return!1;if(\"ltr\"===d){if(e.right<=0)return!0}else if(b=Math.max(c.scrollWidth,w.getViewportSize(window).width),e.left>=b)return!0;return!1},w.isVisible=function(a,b,c){\"use strict\";var d,e=a.nodeName.toUpperCase(),f=a.parentNode;return 9===a.nodeType||null!==(d=window.getComputedStyle(a,null))&&(!(\"none\"===d.getPropertyValue(\"display\")||\"STYLE\"===e.toUpperCase()||\"SCRIPT\"===e.toUpperCase()||!b&&i(d.getPropertyValue(\"clip\"))||!c&&(\"hidden\"===d.getPropertyValue(\"visibility\")||!b&&w.isOffscreen(a))||b&&\"true\"===a.getAttribute(\"aria-hidden\"))&&(!!f&&w.isVisible(f,b,!0)))};var B=[\"checkbox\",\"img\",\"radio\",\"range\",\"slider\",\"spinbutton\",\"textbox\"];w.isVisualContent=function(a){var b=a.getAttribute(\"role\");if(b)return-1!==B.indexOf(b);switch(a.tagName.toUpperCase()){case\"IMG\":case\"IFRAME\":case\"OBJECT\":case\"VIDEO\":case\"AUDIO\":case\"CANVAS\":case\"SVG\":case\"MATH\":case\"BUTTON\":case\"SELECT\":case\"TEXTAREA\":case\"KEYGEN\":case\"PROGRESS\":case\"METER\":return!0;case\"INPUT\":return\"hidden\"!==a.type;default:return!1}},w.visuallyContains=function(a,b){var c=a.getBoundingClientRect(),d={top:c.top+.01,bottom:c.bottom-.01,left:c.left+.01,right:c.right-.01},e=b.getBoundingClientRect(),f=e.top,g=e.left,h={top:f-b.scrollTop,bottom:f-b.scrollTop+b.scrollHeight,left:g-b.scrollLeft,right:g-b.scrollLeft+b.scrollWidth};if(d.left<h.left&&d.left<e.left||d.top<h.top&&d.top<e.top||d.right>h.right&&d.right>e.right||d.bottom>h.bottom&&d.bottom>e.bottom)return!1;var i=window.getComputedStyle(b);return!(d.right>e.right||d.bottom>e.bottom)||(\"scroll\"===i.overflow||\"auto\"===i.overflow||\"hidden\"===i.overflow||b instanceof HTMLBodyElement||b instanceof HTMLHtmlElement)},w.visuallyOverlaps=function(a,b){var c=b.getBoundingClientRect(),d=c.top,e=c.left,f={top:d-b.scrollTop,bottom:d-b.scrollTop+b.scrollHeight,left:e-b.scrollLeft,right:e-b.scrollLeft+b.scrollWidth};if(a.left>f.right&&a.left>c.right||a.top>f.bottom&&a.top>c.bottom||a.right<f.left&&a.right<c.left||a.bottom<f.top&&a.bottom<c.top)return!1;var g=window.getComputedStyle(b);return!(a.left>c.right||a.top>c.bottom)||(\"scroll\"===g.overflow||\"auto\"===g.overflow||b instanceof HTMLBodyElement||b instanceof HTMLHtmlElement)},x.getAllCells=function(a){var b,c,d,e,f=[];for(b=0,d=a.rows.length;b<d;b++)for(c=0,e=a.rows[b].cells.length;c<e;c++)f.push(a.rows[b].cells[c]);return f},x.getCellPosition=function(a,b){var c,d;for(b||(b=x.toGrid(w.findUp(a,\"table\"))),c=0;c<b.length;c++)if(b[c]&&-1!==(d=b[c].indexOf(a)))return{x:d,y:c}},x.getHeaders=function(a){if(a.hasAttribute(\"headers\"))return commons.dom.idrefs(a,\"headers\");var b=commons.table.toGrid(commons.dom.findUp(a,\"table\")),c=commons.table.getCellPosition(a,b);return[].concat(x.traverse(\"left\",c,b).filter(function(a){return x.isRowHeader(a)}),x.traverse(\"up\",c,b).filter(function(a){return x.isColumnHeader(a)})).reverse()},x.getScope=function(a){var b=a.getAttribute(\"scope\"),c=a.getAttribute(\"role\");if(a instanceof Element==!1||-1===[\"TD\",\"TH\"].indexOf(a.nodeName.toUpperCase()))throw new TypeError(\"Expected TD or TH element\");if(\"columnheader\"===c)return\"col\";if(\"rowheader\"===c)return\"row\";if(\"col\"===b||\"row\"===b)return b;if(\"TH\"!==a.nodeName.toUpperCase())return!1;var d=x.toGrid(w.findUp(a,\"table\")),e=x.getCellPosition(a);return d[e.y].reduce(function(a,b){return a&&\"TH\"===b.nodeName.toUpperCase()},!0)?\"col\":d.map(function(a){return a[e.x]}).reduce(function(a,b){return a&&\"TH\"===b.nodeName.toUpperCase()},!0)?\"row\":\"auto\"},x.isColumnHeader=function(a){return-1!==[\"col\",\"auto\"].indexOf(x.getScope(a))},x.isDataCell=function(a){return!(!a.children.length&&!a.textContent.trim())&&\"TD\"===a.nodeName.toUpperCase()},x.isDataTable=function(a){var b=a.getAttribute(\"role\");if((\"presentation\"===b||\"none\"===b)&&!w.isFocusable(a))return!1;if(\"true\"===a.getAttribute(\"contenteditable\")||w.findUp(a,'[contenteditable=\"true\"]'))return!0;if(\"grid\"===b||\"treegrid\"===b||\"table\"===b)return!0;if(\"landmark\"===commons.aria.getRoleType(b))return!0;if(\"0\"===a.getAttribute(\"datatable\"))return!1;if(a.getAttribute(\"summary\"))return!0;if(a.tHead||a.tFoot||a.caption)return!0;for(var c=0,d=a.children.length;c<d;c++)if(\"COLGROUP\"===a.children[c].nodeName.toUpperCase())return!0;for(var e,f,g=0,h=a.rows.length,i=!1,j=0;j<h;j++){e=a.rows[j];for(var k=0,l=e.cells.length;k<l;k++){if(f=e.cells[k],\"TH\"===f.nodeName.toUpperCase())return!0;if(i||f.offsetWidth===f.clientWidth&&f.offsetHeight===f.clientHeight||(i=!0),f.getAttribute(\"scope\")||f.getAttribute(\"headers\")||f.getAttribute(\"abbr\"))return!0;if(-1!==[\"columnheader\",\"rowheader\"].indexOf(f.getAttribute(\"role\")))return!0;if(1===f.children.length&&\"ABBR\"===f.children[0].nodeName.toUpperCase())return!0;g++}}if(a.getElementsByTagName(\"table\").length)return!1;if(h<2)return!1;var m=a.rows[Math.ceil(h/2)];if(1===m.cells.length&&1===m.cells[0].colSpan)return!1;if(m.cells.length>=5)return!0;if(i)return!0;var n,o;for(j=0;j<h;j++){if(e=a.rows[j],n&&n!==window.getComputedStyle(e).getPropertyValue(\"background-color\"))return!0;if(n=window.getComputedStyle(e).getPropertyValue(\"background-color\"),o&&o!==window.getComputedStyle(e).getPropertyValue(\"background-image\"))return!0;o=window.getComputedStyle(e).getPropertyValue(\"background-image\")}return h>=20||!(w.getElementCoordinates(a).width>.95*w.getViewportSize(window).width)&&(!(g<10)&&!a.querySelector(\"object, embed, iframe, applet\"))},x.isHeader=function(a){return!(!x.isColumnHeader(a)&&!x.isRowHeader(a))||!!a.id&&!!document.querySelector('[headers~=\"'+axe.utils.escapeSelector(a.id)+'\"]')},x.isRowHeader=function(a){return-1!==[\"row\",\"auto\"].indexOf(x.getScope(a))},x.toGrid=function(a){for(var b=[],c=a.rows,d=0,e=c.length;d<e;d++){var f=c[d].cells;b[d]=b[d]||[];for(var g=0,h=0,i=f.length;h<i;h++)for(var j=0;j<f[h].colSpan;j++){for(var k=0;k<f[h].rowSpan;k++){for(b[d+k]=b[d+k]||[];b[d+k][g];)g++;b[d+k][g]=f[h]}g++}}return b},x.toArray=x.toGrid,function(a){var b=function a(b,c,d,e){var f,g=d[c.y]?d[c.y][c.x]:void 0;return g?\"function\"==typeof e&&!0===(f=e(g,c,d))?[g]:(f=a(b,{x:c.x+b.x,y:c.y+b.y},d,e),f.unshift(g),f):[]};a.traverse=function(a,c,d,e){if(Array.isArray(c)&&(e=d,d=c,c={x:0,y:0}),\"string\"==typeof a)switch(a){case\"left\":a={x:-1,y:0};break;case\"up\":a={x:0,y:-1};break;case\"right\":a={x:1,y:0};break;case\"down\":a={x:0,y:1}}return b(a,{x:c.x+a.x,y:c.y+a.y},d,e)}}(x);var C={submit:\"Submit\",reset:\"Reset\"},D=[\"text\",\"search\",\"tel\",\"url\",\"email\",\"date\",\"time\",\"number\",\"range\",\"color\"],E=[\"A\",\"EM\",\"STRONG\",\"SMALL\",\"MARK\",\"ABBR\",\"DFN\",\"I\",\"B\",\"S\",\"U\",\"CODE\",\"VAR\",\"SAMP\",\"KBD\",\"SUP\",\"SUB\",\"Q\",\"CITE\",\"SPAN\",\"BDO\",\"BDI\",\"BR\",\"WBR\",\"INS\",\"DEL\",\"IMG\",\"EMBED\",\"OBJECT\",\"IFRAME\",\"MAP\",\"AREA\",\"SCRIPT\",\"NOSCRIPT\",\"RUBY\",\"VIDEO\",\"AUDIO\",\"INPUT\",\"TEXTAREA\",\"SELECT\",\"BUTTON\",\"LABEL\",\"OUTPUT\",\"DATALIST\",\"KEYGEN\",\"PROGRESS\",\"COMMAND\",\"CANVAS\",\"TIME\",\"METER\"];return y.accessibleText=function(a,b){function c(a,b,c){for(var d,e=a.childNodes,g=\"\",h=0;h<e.length;h++)d=e[h],3===d.nodeType?g+=d.textContent:1===d.nodeType&&(-1===E.indexOf(d.nodeName.toUpperCase())&&(g+=\" \"),g+=f(e[h],b,c));return g}function d(a,b,d){var e=\"\",g=a.nodeName.toUpperCase();if(m(a)&&(e=c(a,!1,!1)||\"\",s(e)))return e;if(\"FIGURE\"===g&&(e=p(a,\"figcaption\"),s(e)))return e;if(\"TABLE\"===g){if(e=p(a,\"caption\"),s(e))return e;if(e=a.getAttribute(\"title\")||a.getAttribute(\"summary\")||\"\",s(e))return e}if(r(a))return a.getAttribute(\"alt\")||\"\";if(l(a)&&!d){if(k(a))return a.value||a.title||C[a.type]||\"\";var h=j(a);if(h)return f(h,b,!0)}return\"\"}function e(a,b,c){return!b&&a.hasAttribute(\"aria-labelledby\")?y.sanitize(w.idrefs(a,\"aria-labelledby\").map(function(b){return a===b&&g.pop(),f(b,!0,a!==b)}).join(\" \")):c&&q(a)||!a.hasAttribute(\"aria-label\")?\"\":y.sanitize(a.getAttribute(\"aria-label\"))}var f,g=[];return f=function(a,b,f){\"use strict\";var h;if(null===a||-1!==g.indexOf(a))return\"\";if(!b&&!w.isVisible(a,!0))return\"\";g.push(a);var i=a.getAttribute(\"role\");return h=e(a,b,f),s(h)?h:(h=d(a,b,f),s(h)?h:f&&(h=o(a),s(h))?h:n(a)||i&&-1===t.getRolesWithNameFromContents().indexOf(i)||(h=c(a,b,f),!s(h))?a.hasAttribute(\"title\")?a.getAttribute(\"title\"):\"\":h)},y.sanitize(f(a,b))},y.label=function(a){var b,c;return(c=t.label(a))?c:a.id&&(b=document.querySelector('label[for=\"'+axe.utils.escapeSelector(a.id)+'\"]'),c=b&&y.visible(b,!0))?c:(b=w.findUp(a,\"label\"),(c=b&&y.visible(b,!0))||null)},y.sanitize=function(a){\"use strict\";return a.replace(/\\r\\n/g,\"\\n\").replace(/\\u00A0/g,\" \").replace(/[\\s]{2,}/g,\" \").trim()},y.visible=function(a,b,c){\"use strict\";var d,e,f=a.childNodes,g=f.length,h=\"\";for(d=0;d<g;d++)e=f[d],3===e.nodeType?e.nodeValue&&w.isVisible(a,b)&&(h+=e.nodeValue):c||(h+=y.visible(e,b));return y.sanitize(h)},axe.utils.toArray=function(a){\"use strict\";return Array.prototype.slice.call(a)},axe.utils.tokenList=function(a){\"use strict\";return a.trim().replace(/\\s{2,}/g,\" \").split(\" \")},commons}()})}(\"object\"==typeof window?window:this);"; // This is run in the page, not Lighthouse itself. // axe.run returns a promise which fulfills with a results object // containing any violations. /* istanbul ignore next */ function runA11yChecks() { - return axe.run(document, { + return window.axe.run(document, { runOnly: { type: 'tag', values: [ @@ -6405,7 +6717,7 @@ afterPass(options) { const driver = options.driver; const expression = `(function () { - ${axe}; + ${axeLibSource}; return (${runA11yChecks.toString()}()); })()`; @@ -6423,8 +6735,7 @@ module.exports = Accessibility; -}).call(this,require("buffer").Buffer) -},{"./gatherer":13,"buffer":205}],"./gatherers/cache-contents":[function(require,module,exports){ +},{"./gatherer":13}],"./gatherers/cache-contents":[function(require,module,exports){ /** * @license * Copyright 2016 Google Inc. All rights reserved. @@ -8257,7 +8568,8 @@ const parsedContent = parser.parse(styleHeader.content); if (parsedContent.error) { - log.warn('Styles Gatherer', `Could not parse content: ${parsedContent.error}`); + const error = parsedContent.error.toString().slice(0, 100); + log.warn('Styles Gatherer', `Could not parse content: ${error}…`); styleHeader.parsedContent = []; } else { styleHeader.parsedContent = getCSSPropsInStyleSheet(parsedContent); @@ -8960,6 +9272,27 @@ }); } +function validateCategories(categories, audits, auditResults) { + if (!categories) { + return; + } + + const auditIds = audits ? + audits.map(audit => audit.meta.name) : + auditResults.map(audit => audit.name); + Object.keys(categories).forEach(categoryId => { + categories[categoryId].audits.forEach((audit, index) => { + if (!audit.id) { + throw new Error(`missing an audit id at ${categoryId}[${index}]`); + } + + if (!auditIds.includes(audit.id)) { + throw new Error(`could not find ${audit.id} audit for category ${categoryId}`); + } + }); + }); +} + function assertValidAudit(auditDefinition, auditPath) { const auditName = auditPath || auditDefinition.meta.name; @@ -9113,6 +9446,7 @@ // validatePasses must follow after audits are required validatePasses(configJSON.passes, this._audits, this._configDir); + validateCategories(configJSON.categories, this._audits, this._auditResults); } /** @@ -9437,6 +9771,7 @@ "load-fast-enough-for-pwa", "speed-index-metric", "estimated-input-latency", + "first-interactive", "time-to-interactive", "user-timings", "critical-request-chains", @@ -9699,7 +10034,7 @@ }, { "name": "Accessibility", "id": "accessibility", - "description": "These checks highlight opportunities to improve the accessibility of your app.", + "description": "These checks highlight opportunities to [improve the accessibility of your app](https://developers.google.com/web/fundamentals/accessibility).", "scored": false, "categorizable": true, "items": [{ @@ -9985,6 +10320,7 @@ {"id": "speed-index-metric", "weight": 1}, {"id": "estimated-input-latency", "weight": 1}, {"id": "time-to-interactive", "weight": 5}, + {"id": "first-interactive", "weight": 5}, {"id": "link-blocking-first-paint", "weight": 0}, {"id": "script-blocking-first-paint", "weight": 0}, // {"id": "unused-css-rules", "weight": 0}, @@ -9999,7 +10335,7 @@ }, "accessibility": { "name": "Accessibility", - "description": "These audits validate that your app [works for all users](https://developers.google.com/web/fundamentals/accessibility/).", + "description": "These checks highlight opportunities to [improve the accessibility of your app](https://developers.google.com/web/fundamentals/accessibility).", "audits": [ {"id": "accesskeys", "weight": 1}, {"id": "aria-allowed-attr", "weight": 1}, @@ -10788,10 +11124,24 @@ getServiceWorkerVersions() { return new Promise((resolve, reject) => { - this.once('ServiceWorker.workerVersionUpdated', data => { - this.sendCommand('ServiceWorker.disable') - .then(_ => resolve(data), reject); - }); + const versionUpdatedListener = data => { + // find a service worker with runningStatus that looks like active + // on slow connections the serviceworker might still be installing + const activateCandidates = data.versions.filter(sw => { + return sw.status !== 'redundant'; + }); + const hasActiveServiceWorker = activateCandidates.find(sw => { + return sw.status === 'activated'; + }); + + if (!activateCandidates.length || hasActiveServiceWorker) { + this.off('ServiceWorker.workerVersionUpdated', versionUpdatedListener); + this.sendCommand('ServiceWorker.disable') + .then(_ => resolve(data), reject); + } + }; + + this.on('ServiceWorker.workerVersionUpdated', versionUpdatedListener); this.sendCommand('ServiceWorker.enable').catch(reject); }); @@ -11443,7 +11793,6 @@ const log = require('../lib/log.js'); const Audit = require('../audits/audit'); -const path = require('path'); const URL = require('../lib/url-shim'); /** @@ -11798,10 +12147,8 @@ .then(_ => GatherRunner.disposeDriver(driver)) .then(_ => GatherRunner.collectArtifacts(gathererResults)) .then(artifacts => { - // Add tracing data and computed artifacts to artifacts object. - const computedArtifacts = this.instantiateComputedArtifacts(); - Object.assign(artifacts, computedArtifacts, tracingData); - return artifacts; + // Add tracing data to the artifacts object. + return Object.assign(artifacts, tracingData); }) // cleanup on error .catch(err => { @@ -11855,19 +12202,6 @@ } } - static instantiateComputedArtifacts() { - const computedArtifacts = {}; - ["computed-artifact.js","critical-request-chains.js","manifest-values.js","network-throughput.js","pushed-requests.js","screenshots.js","speedline.js","trace-of-tab.js","tracing-model.js"].forEach(function(file) { - // Drop `.js` suffix to keep browserify import happy. - file = file.replace(/\.js$/, ''); - const ArtifactClass = require('./computed/' + file); - const artifact = new ArtifactClass(); - // define the request* function that will be exposed on `artifacts` - computedArtifacts['request' + artifact.name] = artifact.request.bind(artifact); - }); - return computedArtifacts; - } - static instantiateGatherers(passes, rootPath) { return passes.map(pass => { pass.gatherers = pass.gatherers.map(gatherer => { @@ -11887,7 +12221,7 @@ module.exports = GatherRunner; -},{"../audits/audit":2,"../lib/log.js":19,"../lib/url-shim":25,"../runner":33,"path":220}],13:[function(require,module,exports){ +},{"../audits/audit":2,"../lib/log.js":19,"../lib/url-shim":25,"../runner":33}],13:[function(require,module,exports){ /** * @license * Copyright 2016 Google Inc. All rights reserved. @@ -12582,6 +12916,10 @@ return '\x1b[1m'; } + static get dim() { + return '\x1b[2m'; + } + static get tick() { return isWindows ? '\u221A' : '✓'; } @@ -13497,6 +13835,7 @@ // The ideal input response latency, the time between the input task and the // first frame of the response. const BASE_RESPONSE_LATENCY = 16; +const SCHEDULABLE_TASK_TITLE = 'TaskQueueManager::ProcessTaskFromWorkQueue'; // we need gl-matrix and jszip for traceviewer // since it has internal forks for isNode and they get mixed up during @@ -13678,23 +14017,12 @@ * @return {{durations: !Array<number>, clippedLength: number}} */ static getMainThreadTopLevelEventDurations(model, trace, startTime, endTime) { - // Find the main thread via the first TracingStartedInPage event in the trace - const startEvent = trace.traceEvents.find(event => { - return event.name === 'TracingStartedInPage'; - }); - const mainThread = TraceProcessor._findMainThreadFromIds(model, startEvent.pid, startEvent.tid); + const slices = TraceProcessor.getMainThreadTopLevelEvents(model, trace, startTime, endTime); // Find durations of all slices in range of interest. - // TODO(bckenny): filter for top level slices ourselves? const durations = []; let clippedLength = 0; - mainThread.sliceGroup.topLevelSlices.forEach(slice => { - // Discard slices outside range. - - if (slice.end <= startTime || slice.start >= endTime) { - return; - } - + slices.forEach(slice => { // Clip any at edges of range. let duration = slice.duration; let sliceStart = slice.start; @@ -13719,6 +14047,26 @@ } /** + * Provides the top level events on the main thread. + * @param {!traceviewer.Model} model + * @param {{traceEvents: !Array<!Object>}} trace + * @param {number=} startTime Optional start time (in ms) of range of interest. Defaults to trace start. + * @param {number=} endTime Optional end time (in ms) of range of interest. Defaults to trace end. + * @return {!Array<{start: number, end: number, duration: number}>} + */ + static getMainThreadTopLevelEvents(model, trace, startTime = -Infinity, endTime = Infinity) { + // Find the main thread via the first TracingStartedInPage event in the trace + const startEvent = trace.traceEvents.find(event => event.name === 'TracingStartedInPage'); + const mainThread = TraceProcessor._findMainThreadFromIds(model, startEvent.pid, startEvent.tid); + + return mainThread.sliceGroup.slices.filter(slice => { + return slice.title === SCHEDULABLE_TASK_TITLE && + slice.end > startTime && + slice.start < endTime; + }); + } + + /** * Uses traceviewer's statistics package to create a log-normal distribution. * Specified by providing the median value, at which the score will be 0.5, * and the falloff, the initial point of diminishing returns where any @@ -15062,8 +15410,8 @@ return []; } else { return [ - "/**\n * @license\n * Copyright 2016 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use strict';\n\n/* eslint-disable no-console */\n\n/**\n * Logs messages via a UI butter.\n * @class\n */\nclass Logger {\n constructor(selector) {\n this.el = document.querySelector(selector);\n }\n\n /**\n * Shows a butter bar.\n * @param {!string} msg The message to show.\n * @param {boolean=} optAutoHide True to hide the message after a duration.\n * Default is true.\n */\n log(msg, optAutoHide) {\n const autoHide = typeof optAutoHide === 'undefined' ? true : optAutoHide;\n\n clearTimeout(this._id);\n\n this.el.textContent = msg;\n this.el.classList.add('show');\n if (autoHide) {\n this._id = setTimeout(_ => {\n this.el.classList.remove('show');\n }, 7000);\n }\n }\n\n warn(msg) {\n this.log('Warning: ' + msg);\n console.warn(msg);\n }\n\n error(msg) {\n this.log(msg);\n console.error(msg);\n }\n\n /**\n * Explicitly hides the butter bar.\n */\n hide() {\n clearTimeout(this._id);\n this.el.classList.remove('show');\n }\n}\n\nif (typeof module !== 'undefined' && module.exports) {\n module.exports = Logger;\n}\n", - "/**\n * @license\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use strict';\n\n/**\n * Generate a filenamePrefix of hostname_YYYY-MM-DD_HH-MM-SS\n * Date/time uses the local timezone, however Node has unreliable ICU\n * support, so we must construct a YYYY-MM-DD date format manually. :/\n * @param {!Object} results\n * @returns string\n */\nfunction getFilenamePrefix(results) {\n // eslint-disable-next-line no-undef\n const hostname = new (URLConstructor || URL)(results.url).hostname;\n const date = (results.generatedTime && new Date(results.generatedTime)) || new Date();\n\n const timeStr = date.toLocaleTimeString('en-US', {hour12: false});\n const dateParts = date.toLocaleDateString('en-US', {\n year: 'numeric', month: '2-digit', day: '2-digit'\n }).split('/');\n dateParts.unshift(dateParts.pop());\n const dateStr = dateParts.join('-');\n\n const filenamePrefix = `${hostname}_${dateStr}_${timeStr}`;\n // replace characters that are unfriendly to filenames\n return filenamePrefix.replace(/[\\/\\?<>\\\\:\\*\\|\":]/g, '-');\n}\n\nlet URLConstructor;\nif (typeof module !== 'undefined' && module.exports) {\n URLConstructor = require('./url-shim');\n\n module.exports = {\n getFilenamePrefix\n };\n}\n", + "/**\n * @license\n * Copyright 2016 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use strict';\n\n/* eslint-disable no-console */\n\n/**\n * Logs messages via a UI butter.\n */\nclass Logger {\n constructor(selector) {\n /** @type {!Element} */\n this.el = document.querySelector(selector);\n }\n\n /**\n * Shows a butter bar.\n * @param {!string} msg The message to show.\n * @param {boolean=} autoHide True to hide the message after a duration.\n * Default is true.\n */\n log(msg, autoHide = true) {\n clearTimeout(this._id);\n\n this.el.textContent = msg;\n this.el.classList.add('show');\n if (autoHide) {\n this._id = setTimeout(_ => {\n this.el.classList.remove('show');\n }, 7000);\n }\n }\n\n warn(msg) {\n this.log('Warning: ' + msg);\n console.warn(msg);\n }\n\n error(msg) {\n this.log(msg);\n console.error(msg);\n }\n\n /**\n * Explicitly hides the butter bar.\n */\n hide() {\n clearTimeout(this._id);\n this.el.classList.remove('show');\n }\n}\n\nif (typeof module !== 'undefined' && module.exports) {\n module.exports = Logger;\n}\n", + "/**\n * @license\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use strict';\n\n/* global URL */\n\n/**\n * Generate a filenamePrefix of hostname_YYYY-MM-DD_HH-MM-SS\n * Date/time uses the local timezone, however Node has unreliable ICU\n * support, so we must construct a YYYY-MM-DD date format manually. :/\n * @param {{url: string, generatedTime: string}} results\n * @return {string}\n */\nfunction getFilenamePrefix(results) {\n const hostname = new (URLConstructor || URL)(results.url).hostname;\n const date = (results.generatedTime && new Date(results.generatedTime)) || new Date();\n\n const timeStr = date.toLocaleTimeString('en-US', {hour12: false});\n const dateParts = date.toLocaleDateString('en-US', {\n year: 'numeric', month: '2-digit', day: '2-digit'\n }).split('/');\n dateParts.unshift(dateParts.pop());\n const dateStr = dateParts.join('-');\n\n const filenamePrefix = `${hostname}_${dateStr}_${timeStr}`;\n // replace characters that are unfriendly to filenames\n return filenamePrefix.replace(/[\\/\\?<>\\\\:\\*\\|\":]/g, '-');\n}\n\nlet URLConstructor;\nif (typeof module !== 'undefined' && module.exports) {\n URLConstructor = require('./url-shim');\n\n module.exports = {getFilenamePrefix};\n}\n", "/**\n * @license\n * Copyright 2016 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* global ga, logger */\n\n'use strict';\n\nclass LighthouseReport {\n\n /**\n * @param {Object=} lhresults Lighthouse JSON results.\n */\n constructor(lhresults) {\n this.json = lhresults || null;\n this._copyAttempt = false;\n\n this.onCopy = this.onCopy.bind(this);\n this.onExportButtonClick = this.onExportButtonClick.bind(this);\n this.onExport = this.onExport.bind(this);\n this.onKeyDown = this.onKeyDown.bind(this);\n this.printShortCutDetect = this.printShortCutDetect.bind(this);\n\n this._addEventListeners();\n }\n\n _addEventListeners() {\n this._setUpCollaspeDetailsAfterPrinting();\n\n const headerContainer = document.querySelector('.js-header-container');\n if (headerContainer) {\n const toggleButton = headerContainer.querySelector('.js-header-toggle');\n toggleButton.addEventListener('click', () => headerContainer.classList.toggle('expanded'));\n }\n\n this.exportButton = document.querySelector('.js-export');\n if (this.exportButton) {\n this.exportButton.addEventListener('click', this.onExportButtonClick);\n const dropdown = document.querySelector('.export-dropdown');\n dropdown.addEventListener('click', this.onExport);\n\n document.addEventListener('copy', this.onCopy);\n }\n document.addEventListener('keydown', this.printShortCutDetect);\n }\n\n /**\n * Handler copy events.\n */\n onCopy(e) {\n // Only handle copy button presses (e.g. ignore the user copying page text).\n if (this._copyAttempt) {\n // We want to write our own data to the clipboard, not the user's text selection.\n e.preventDefault();\n e.clipboardData.setData('text/plain', JSON.stringify(this.json, null, 2));\n logger.log('Report JSON copied to clipboard');\n }\n\n this._copyAttempt = false;\n }\n\n /**\n * Copies the report JSON to the clipboard (if supported by the browser).\n */\n onCopyButtonClick() {\n if (window.ga) {\n ga('send', 'event', 'report', 'copy');\n }\n\n try {\n if (document.queryCommandSupported('copy')) {\n this._copyAttempt = true;\n\n // Note: In Safari 10.0.1, execCommand('copy') returns true if there's\n // a valid text selection on the page. See http://caniuse.com/#feat=clipboard.\n const successful = document.execCommand('copy');\n if (!successful) {\n this._copyAttempt = false; // Prevent event handler from seeing this as a copy attempt.\n logger.warn('Your browser does not support copy to clipboard.');\n }\n }\n } catch (err) {\n this._copyAttempt = false;\n logger.log(err.message);\n }\n }\n\n closeExportDropdown() {\n this.exportButton.classList.remove('active');\n }\n\n /**\n * Click handler for export button.\n */\n onExportButtonClick(e) {\n e.preventDefault();\n e.target.classList.toggle('active');\n document.addEventListener('keydown', this.onKeyDown);\n }\n\n /**\n * Handler for \"export as\" button.\n */\n onExport(e) {\n e.preventDefault();\n\n if (!e.target.dataset.action) {\n return;\n }\n\n switch (e.target.dataset.action) {\n case 'copy':\n this.onCopyButtonClick();\n break;\n case 'open-viewer':\n this.sendJSONReport();\n break;\n case 'print':\n this.expandDetailsWhenPrinting();\n window.print();\n break;\n case 'save-json': {\n const jsonStr = JSON.stringify(this.json, null, 2);\n this._saveFile(new Blob([jsonStr], {type: 'application/json'}));\n break;\n }\n case 'save-html': {\n let htmlStr = '';\n\n // Since Viewer generates its page HTML dynamically from report JSON,\n // run the ReportGenerator. For everything else, the page's HTML is\n // already the final product.\n if (e.target.dataset.context !== 'viewer') {\n htmlStr = document.documentElement.outerHTML;\n } else {\n const reportGenerator = new ReportGenerator();\n htmlStr = reportGenerator.generateHTML(this.json, 'cli');\n }\n\n try {\n this._saveFile(new Blob([htmlStr], {type: 'text/html'}));\n } catch (err) {\n logger.error('Could not export as HTML. ' + err.message);\n }\n break;\n }\n }\n\n this.closeExportDropdown();\n document.removeEventListener('keydown', this.onKeyDown);\n }\n\n /**\n * Keydown handler for the document.\n */\n onKeyDown(e) {\n if (e.keyCode === 27) { // ESC\n this.closeExportDropdown();\n }\n }\n\n /**\n * Opens a new tab to the online viewer and sends the local page's JSON results\n * to the online viewer using postMessage.\n */\n sendJSONReport() {\n const VIEWER_ORIGIN = 'https://googlechrome.github.io';\n const VIEWER_URL = `${VIEWER_ORIGIN}/lighthouse/viewer/`;\n\n // Chrome doesn't allow us to immediately postMessage to a popup right\n // after it's created. Normally, we could also listen for the popup window's\n // load event, however it is cross-domain and won't fire. Instead, listen\n // for a message from the target app saying \"I'm open\".\n window.addEventListener('message', function msgHandler(e) {\n if (e.origin !== VIEWER_ORIGIN) {\n return;\n }\n\n if (e.data.opened) {\n popup.postMessage({lhresults: this.json}, VIEWER_ORIGIN);\n window.removeEventListener('message', msgHandler);\n }\n }.bind(this));\n\n const popup = window.open(VIEWER_URL, '_blank');\n }\n\n /**\n * Expands details while user using short cut to print report\n */\n printShortCutDetect(e) {\n if ((e.ctrlKey || e.metaKey) && e.keyCode === 80) { // Ctrl+P\n this.expandDetailsWhenPrinting();\n }\n }\n\n /**\n * Expands audit `<details>` when the user prints the page.\n * Ideally, a print stylesheet could take care of this, but CSS has no way to\n * open a `<details>` element.\n */\n expandDetailsWhenPrinting() {\n const details = Array.from(document.querySelectorAll('details'));\n details.map(detail => detail.open = true);\n }\n\n /**\n * Sets up listeners to collapse audit `<details>` when the user closes the\n * print dialog, all `<details>` are collapsed.\n */\n _setUpCollaspeDetailsAfterPrinting() {\n const details = Array.from(document.querySelectorAll('details'));\n\n // FF and IE implement these old events.\n if ('onbeforeprint' in window) {\n window.addEventListener('afterprint', _ => {\n details.map(detail => detail.open = false);\n });\n } else {\n // Note: while FF has media listeners, it doesn't fire when matching 'print'.\n window.matchMedia('print').addListener(mql => {\n if (!mql.matches) {\n details.map(detail => detail.open = mql.matches);\n }\n });\n }\n }\n /**\n * Downloads a file (blob) using a[download].\n * @param {Blob|File} blob The file to save.\n */\n _saveFile(blob) {\n const filename = window.getFilenamePrefix({\n url: this.json.url,\n generatedTime: this.json.generatedTime\n });\n\n const ext = blob.type.match('json') ? '.json' : '.html';\n\n const a = document.createElement('a');\n a.download = `${filename}${ext}`;\n a.href = URL.createObjectURL(blob);\n document.body.appendChild(a); // Firefox requires anchor to be in the DOM.\n a.click();\n\n // cleanup.\n document.body.removeChild(a);\n setTimeout(_ => URL.revokeObjectURL(a.href), 500);\n }\n}\n\n// Exports for Node usage (Viewer browserifies).\nlet ReportGenerator;\nif (typeof module !== 'undefined' && module.exports) {\n module.exports = LighthouseReport;\n ReportGenerator = require('../../../lighthouse-core/report/report-generator');\n window.getFilenamePrefix = require('../../../lighthouse-core/lib/file-namer').getFilenamePrefix;\n}\n" ]; } @@ -15505,14 +15853,19 @@ -const REPORT_TEMPLATE = "<!--\n\nCopyright 2017 Google Inc. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n-->\n<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, minimum-scale=1\">\n <link rel=\"icon\" href=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAADjklEQVR4AWI08P/HQEvAQrxSQKvlECfLFYXx75xCY2qmh89GbNvOMjb3v9jOOlxnFWxj206ebQ3b7q6q+z1rNagu8/zvPSZACAABpeUAA0miMgU7SA7JjCraFGwZwECOwvL75dWjsKgWBKtx0jvWo+vkBAFbACCkByMP6nMn48+AVgXB2fzSCwsv22/lMGlUhmJ0AE7BH8dyUUDbUEgN6RzJRSeaPxhdRYR0Inel+7Hd5lBiFpkMAxACc0394//9C4voFHDiAAGLpuOXebdfdHfctgwJKaZRLRKy6ItrSis6RBnVBgGtbHyKTEmJHQoEXoBCE5BCrDeA2ogMUIGDAKEBDEhUqwgMqBYDjW4DQzmuffVdqff42/ZQYYqVcMXGZsMPyCsH3lyJSetxvEaxAQXdjR1HjfwCdIS7lo2DZke26Qe+MXO12OWkGT0O6oE7vMGkMnkYw4aN1KQgMKExhXqswfiov4+a7MQ11XPnbr/5qpKlgACAAQj94Lu271bN9DUecQasIZlNzG72llRAAKJiAi+/BSHrSFjRvQhg3DEKEqJh08tsmLTx597+f6enr4cc2Zpk57pihfX24dW7RHcOLLUbJYhJSl0ErQCI9BVXH/XrO97QasuvQQSiECa0BrQCIIJp6X9T/r8QG6L71WYSqCoIIGo2BZDUBnS/D9EA9Nun1iYvbM0MFExIDQRoKFatc1Z6zrm5uWeObJotq0BGV9FuQBWq5a4Fw3PPz848rZHstZSuA5FWAFSMP2nOppOOGpl6qh9PCSg0IFyHKjSQyDNQHTru2t75NOEe0fsf246oAmFkI6vCdnWvbQFQFCKx8vCswV8TrDLiDLgH4Nr7RAtNsrC9d8sfk7b8ls4igdNy8CQKAISlsB0FjZfd3Lfp155tf8fKI4BxZZIj/oTdVEAIAcJFOCmzauHG71I7/rdreUAgAqpDP05fDARCAQQARwEIBQSVxq0FyaLvZZtevpHa8WHw8cft6cpxlq8eAJtIhnSbWDf951yx3y13OqUuu5qyGgkxCgGFh9cDihDGbTa6BqvT1lWmrav3bmt2ZMJ4mU6TGgIC4DBzcv/JqAau1WhzSt3x9Ixk/4Jk/8J4ZrrViFMA4W6A7+WK8xcVjvyrOmVD0FbAXokcT48r+xVqLKvuJYbmpNadnlp3mpufJHOe/GXktM+r09bT8kEdq9BRYAbGSgzP7ll82U71Mc+ZFooXgwAAAABJRU5ErkJggg==\">\n <title>Lighthouse Report</title>\n <style>/*%%LIGHTHOUSE_CSS%%*/</style>\n</head>\n<body>\n <noscript>Lighthouse report requires JavaScript. Please enable.</noscript>\n <div hidden>%%LIGHTHOUSE_TEMPLATES%%</div>\n <script>%%LIGHTHOUSE_JAVASCRIPT%%</script>\n <script>window.__LIGHTHOUSE_JSON__ = %%LIGHTHOUSE_JSON%%;</script>\n <script>\n const dom = new DOM(document);\n const detailsRenderer = new DetailsRenderer(dom);\n const renderer = new ReportRenderer(dom, detailsRenderer);\n const reportElem = renderer.renderReport(window.__LIGHTHOUSE_JSON__);\n document.body.appendChild(reportElem);\n </script>\n</body>\n</html>\n"; +const REPORT_TEMPLATE = "<!--\n\nCopyright 2017 Google Inc. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n-->\n<!doctype html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, minimum-scale=1\">\n <link rel=\"icon\" href=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAADjklEQVR4AWI08P/HQEvAQrxSQKvlECfLFYXx75xCY2qmh89GbNvOMjb3v9jOOlxnFWxj206ebQ3b7q6q+z1rNagu8/zvPSZACAABpeUAA0miMgU7SA7JjCraFGwZwECOwvL75dWjsKgWBKtx0jvWo+vkBAFbACCkByMP6nMn48+AVgXB2fzSCwsv22/lMGlUhmJ0AE7BH8dyUUDbUEgN6RzJRSeaPxhdRYR0Inel+7Hd5lBiFpkMAxACc0394//9C4voFHDiAAGLpuOXebdfdHfctgwJKaZRLRKy6ItrSis6RBnVBgGtbHyKTEmJHQoEXoBCE5BCrDeA2ogMUIGDAKEBDEhUqwgMqBYDjW4DQzmuffVdqff42/ZQYYqVcMXGZsMPyCsH3lyJSetxvEaxAQXdjR1HjfwCdIS7lo2DZke26Qe+MXO12OWkGT0O6oE7vMGkMnkYw4aN1KQgMKExhXqswfiov4+a7MQ11XPnbr/5qpKlgACAAQj94Lu271bN9DUecQasIZlNzG72llRAAKJiAi+/BSHrSFjRvQhg3DEKEqJh08tsmLTx597+f6enr4cc2Zpk57pihfX24dW7RHcOLLUbJYhJSl0ErQCI9BVXH/XrO97QasuvQQSiECa0BrQCIIJp6X9T/r8QG6L71WYSqCoIIGo2BZDUBnS/D9EA9Nun1iYvbM0MFExIDQRoKFatc1Z6zrm5uWeObJotq0BGV9FuQBWq5a4Fw3PPz848rZHstZSuA5FWAFSMP2nOppOOGpl6qh9PCSg0IFyHKjSQyDNQHTru2t75NOEe0fsf246oAmFkI6vCdnWvbQFQFCKx8vCswV8TrDLiDLgH4Nr7RAtNsrC9d8sfk7b8ls4igdNy8CQKAISlsB0FjZfd3Lfp155tf8fKI4BxZZIj/oTdVEAIAcJFOCmzauHG71I7/rdreUAgAqpDP05fDARCAQQARwEIBQSVxq0FyaLvZZtevpHa8WHw8cft6cpxlq8eAJtIhnSbWDf951yx3y13OqUuu5qyGgkxCgGFh9cDihDGbTa6BqvT1lWmrav3bmt2ZMJ4mU6TGgIC4DBzcv/JqAau1WhzSt3x9Ixk/4Jk/8J4ZrrViFMA4W6A7+WK8xcVjvyrOmVD0FbAXokcT48r+xVqLKvuJYbmpNadnlp3mpufJHOe/GXktM+r09bT8kEdq9BRYAbGSgzP7ll82U71Mc+ZFooXgwAAAABJRU5ErkJggg==\">\n <title>Lighthouse Report</title>\n <style>/*%%LIGHTHOUSE_CSS%%*/</style>\n</head>\n<body class=\"lh-root\">\n <noscript>Lighthouse report requires JavaScript. Please enable.</noscript>\n <div hidden>%%LIGHTHOUSE_TEMPLATES%%</div>\n\n <main><!-- report populated here --></main>\n\n <div id=\"lh-log\"></div>\n\n <script>%%LIGHTHOUSE_JAVASCRIPT%%</script>\n <script>window.__LIGHTHOUSE_JSON__ = %%LIGHTHOUSE_JSON%%;</script>\n <script>\n window.addEventListener('DOMContentLoaded', _ => {\n const dom = new DOM(document);\n const detailsRenderer = new DetailsRenderer(dom);\n const categoryRenderer = new CategoryRenderer(dom, detailsRenderer);\n const features = new ReportUIFeatures(dom);\n const renderer = new ReportRenderer(dom, categoryRenderer, features);\n\n const container = document.querySelector('main');\n renderer.renderReport(window.__LIGHTHOUSE_JSON__, container);\n });\n\n document.addEventListener('lh-analytics', e => {\n if (window.ga) {\n ga(e.detail.cmd, e.detail.fields);\n }\n });\n\n document.addEventListener('lh-log', e => {\n const logger = new Logger(document.querySelector('#lh-log'));\n\n switch (e.detail.cmd) {\n case 'log':\n logger.log(e.detail.msg);\n break;\n case 'warn':\n logger.warn(e.detail.msg);\n break;\n case 'error':\n logger.error(e.detail.msg);\n break;\n case 'hide':\n logger.hide();\n break;\n }\n });\n </script>\n</body>\n</html>\n"; const REPORT_JAVASCRIPT = [ - "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use strict';\n\n/* globals URL */\n\nclass DOM {\n /**\n * @param {!Document} document\n */\n constructor(document) {\n this._document = document;\n }\n\n /**\n * @param {string} name\n * @param {string=} className\n * @param {!Object<string, (string|undefined)>=} attrs Attribute key/val pairs.\n * Note: if an attribute key has an undefined value, this method does not\n * set the attribute on the node.\n * @return {!Element}\n */\n createElement(name, className, attrs) {\n // TODO(all): adopt `attrs` default arg when https://codereview.chromium.org/2821773002/ lands\n attrs = attrs || {};\n const element = this._document.createElement(name);\n if (className) {\n element.className = className;\n }\n Object.keys(attrs).forEach(key => {\n const value = attrs[key];\n if (typeof value !== 'undefined') {\n element.setAttribute(key, value);\n }\n });\n return element;\n }\n\n /**\n * @param {string} selector\n * @param {!Document|!Element} context\n * @return {!DocumentFragment} A clone of the template content.\n * @throws {Error}\n */\n cloneTemplate(selector, context) {\n const template = context.querySelector(selector);\n if (!template) {\n throw new Error(`Template not found: template${selector}`);\n }\n return /** @type {!DocumentFragment} */ (this._document.importNode(template.content, true));\n }\n\n /**\n * @param {string} text\n * @return {!Element}\n */\n createSpanFromMarkdown(text) {\n const element = this.createElement('span');\n\n // Split on markdown links (e.g. [some link](https://...)).\n const parts = text.split(/\\[(.*?)\\]\\((https?:\\/\\/.*?)\\)/g);\n\n while (parts.length) {\n // Pop off the same number of elements as there are capture groups.\n const [preambleText, linkText, linkHref] = parts.splice(0, 3);\n element.appendChild(this._document.createTextNode(preambleText));\n\n // Append link if there are any.\n if (linkText && linkHref) {\n const a = this.createElement('a');\n a.rel = 'noopener';\n a.target = '_blank';\n a.textContent = linkText;\n a.href = (new URL(linkHref)).href;\n element.appendChild(a);\n }\n }\n\n return element;\n }\n\n /**\n * @return {!Document}\n */\n document() {\n return this._document;\n }\n}\n\nif (typeof module !== 'undefined' && module.exports) {\n module.exports = DOM;\n}\n", - "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use strict';\n\nclass DetailsRenderer {\n /**\n * @param {!DOM} dom\n */\n constructor(dom) {\n this._dom = dom;\n }\n\n /**\n * @param {(!DetailsRenderer.DetailsJSON|!DetailsRenderer.CardsDetailsJSON)} details\n * @return {!Element}\n */\n render(details) {\n switch (details.type) {\n case 'text':\n return this._renderText(details);\n case 'block':\n return this._renderBlock(details);\n case 'cards':\n return this._renderCards(/** @type {!DetailsRenderer.CardsDetailsJSON} */ (details));\n case 'list':\n return this._renderList(details);\n default:\n throw new Error(`Unknown type: ${details.type}`);\n }\n }\n\n /**\n * @param {!DetailsRenderer.DetailsJSON} text\n * @return {!Element}\n */\n _renderText(text) {\n const element = this._dom.createElement('div', 'lh-text');\n element.textContent = text.text;\n return element;\n }\n\n /**\n * @param {!DetailsRenderer.DetailsJSON} block\n * @return {!Element}\n */\n _renderBlock(block) {\n const element = this._dom.createElement('div', 'lh-block');\n const items = block.items || [];\n for (const item of items) {\n element.appendChild(this.render(item));\n }\n return element;\n }\n\n /**\n * @param {!DetailsRenderer.DetailsJSON} list\n * @return {!Element}\n */\n _renderList(list) {\n const element = this._dom.createElement('details', 'lh-details');\n if (list.header) {\n const summary = this._dom.createElement('summary', 'lh-list__header');\n summary.textContent = list.header.text;\n element.appendChild(summary);\n }\n\n const itemsElem = this._dom.createElement('div', 'lh-list__items');\n const items = list.items || [];\n for (const item of items) {\n itemsElem.appendChild(this.render(item));\n }\n element.appendChild(itemsElem);\n return element;\n }\n\n /**\n * @param {!DetailsRenderer.CardsDetailsJSON} details\n * @return {!Element}\n */\n _renderCards(details) {\n const element = this._dom.createElement('details', 'lh-details');\n if (details.header) {\n element.appendChild(this._dom.createElement('summary')).textContent = details.header.text;\n }\n\n const cardsParent = this._dom.createElement('div', 'lh-scorecards');\n for (const item of details.items) {\n const card = cardsParent.appendChild(\n this._dom.createElement('div', 'lh-scorecard', {title: item.snippet}));\n const titleEl = this._dom.createElement('div', 'lh-scorecard__title');\n const valueEl = this._dom.createElement('div', 'lh-scorecard__value');\n const targetEl = this._dom.createElement('div', 'lh-scorecard__target');\n\n card.appendChild(titleEl).textContent = item.title;\n card.appendChild(valueEl).textContent = item.value;\n\n if (item.target) {\n card.appendChild(targetEl).textContent = `target: ${item.target}`;\n }\n }\n\n element.appendChild(cardsParent);\n return element;\n }\n}\n\nif (typeof module !== 'undefined' && module.exports) {\n module.exports = DetailsRenderer;\n}\n\n/**\n * @typedef {{\n * type: string,\n * text: (string|undefined),\n * header: (!DetailsRenderer.DetailsJSON|undefined),\n * items: (!Array<!DetailsRenderer.DetailsJSON>|undefined)\n * }}\n */\nDetailsRenderer.DetailsJSON; // eslint-disable-line no-unused-expressions\n\n/** @typedef {{\n * type: string,\n * text: string,\n * header: !DetailsRenderer.DetailsJSON,\n * items: !Array<{title: string, value: string, snippet: (string|undefined), target: string}>\n * }}\n */\nDetailsRenderer.CardsDetailsJSON; // eslint-disable-line no-unused-expressions\n", - "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use strict';\n\n/**\n * @fileoverview The entry point for rendering the Lighthouse report based on the JSON output.\n * This file is injected into the report HTML along with the JSON report.\n *\n * Dummy text for ensuring report robustness: </script> pre$`post %%LIGHTHOUSE_JSON%%\n */\n\nconst RATINGS = {\n PASS: {label: 'pass', minScore: 75},\n AVERAGE: {label: 'average', minScore: 45},\n FAIL: {label: 'fail'}\n};\n\n/**\n * Convert a score to a rating label.\n * @param {number} score\n * @return {string}\n */\nfunction calculateRating(score) {\n let rating = RATINGS.FAIL.label;\n if (score >= RATINGS.PASS.minScore) {\n rating = RATINGS.PASS.label;\n } else if (score >= RATINGS.AVERAGE.minScore) {\n rating = RATINGS.AVERAGE.label;\n }\n return rating;\n}\n\n/**\n * Format number.\n * @param {number} number\n * @return {string}\n */\nfunction formatNumber(number) {\n return number.toLocaleString(undefined, {maximumFractionDigits: 1});\n}\n\nclass ReportRenderer {\n /**\n * @param {!DOM} dom\n * @param {!DetailsRenderer} detailsRenderer\n */\n constructor(dom, detailsRenderer) {\n this._dom = dom;\n this._detailsRenderer = detailsRenderer;\n\n this._templateContext = this._dom.document();\n }\n\n /**\n * @param {!ReportRenderer.ReportJSON} report\n * @return {!Element}\n */\n renderReport(report) {\n try {\n return this._renderReport(report);\n } catch (e) {\n return this._renderException(e);\n }\n }\n\n /**\n * @param {!DocumentFragment|!Element} element DOM node to populate with values.\n * @param {number} score\n * @param {string} scoringMode\n * @param {string} title\n * @param {string} description\n * @return {!Element}\n */\n _populateScore(element, score, scoringMode, title, description) {\n // Fill in the blanks.\n const valueEl = element.querySelector('.lh-score__value');\n valueEl.textContent = formatNumber(score);\n valueEl.classList.add(`lh-score__value--${calculateRating(score)}`,\n `lh-score__value--${scoringMode}`);\n\n element.querySelector('.lh-score__title').textContent = title;\n element.querySelector('.lh-score__description')\n .appendChild(this._dom.createSpanFromMarkdown(description));\n\n return /** @type {!Element} **/ (element);\n }\n\n /**\n * Define a custom element for <templates> to be extracted from. For example:\n * this.setTemplateContext(new DOMParser().parseFromString(htmlStr, 'text/html'))\n * @param {!Document|!Element} context\n */\n setTemplateContext(context) {\n this._templateContext = context;\n }\n\n /**\n * @param {!ReportRenderer.AuditJSON} audit\n * @return {!Element}\n */\n _renderAuditScore(audit) {\n const tmpl = this._dom.cloneTemplate('#tmpl-lh-audit-score', this._templateContext);\n\n const scoringMode = audit.result.scoringMode;\n const description = audit.result.helpText;\n let title = audit.result.description;\n\n if (audit.result.displayValue) {\n title += `: ${audit.result.displayValue}`;\n }\n if (audit.result.optimalValue) {\n title += ` (target: ${audit.result.optimalValue})`;\n }\n\n // Append audit details to header section so the entire audit is within a <details>.\n const header = tmpl.querySelector('.lh-score__header');\n header.open = audit.score < 100; // expand failed audits\n if (audit.result.details) {\n header.appendChild(this._detailsRenderer.render(audit.result.details));\n }\n\n return this._populateScore(tmpl, audit.score, scoringMode, title, description);\n }\n\n /**\n * @param {!ReportRenderer.CategoryJSON} category\n * @return {!Element}\n */\n _renderCategoryScore(category) {\n const tmpl = this._dom.cloneTemplate('#tmpl-lh-category-score', this._templateContext);\n const score = Math.round(category.score);\n return this._populateScore(tmpl, score, 'numeric', category.name, category.description);\n }\n\n /**\n * @param {!Error} e\n * @return {!Element}\n */\n _renderException(e) {\n const element = this._dom.createElement('div', 'lh-exception');\n element.textContent = String(e.stack);\n return element;\n }\n\n /**\n * @param {!ReportRenderer.ReportJSON} report\n * @return {!Element}\n */\n _renderReport(report) {\n const element = this._dom.createElement('div', 'lh-report');\n for (const category of report.reportCategories) {\n element.appendChild(this._renderCategory(category));\n }\n return element;\n }\n\n /**\n * @param {!ReportRenderer.CategoryJSON} category\n * @return {!Element}\n */\n _renderCategory(category) {\n const element = this._dom.createElement('div', 'lh-category');\n element.appendChild(this._renderCategoryScore(category));\n\n const passedAudits = category.audits.filter(audit => audit.score === 100);\n const nonPassedAudits = category.audits.filter(audit => !passedAudits.includes(audit));\n\n for (const audit of nonPassedAudits) {\n element.appendChild(this._renderAudit(audit));\n }\n\n // don't create a passed section if there are no passed\n if (!passedAudits.length) return element;\n\n const passedElem = this._dom.createElement('details', 'lh-passed-audits');\n const passedSummary = this._dom.createElement('summary', 'lh-passed-audits-summary');\n passedSummary.textContent = `View ${passedAudits.length} passed items`;\n passedElem.appendChild(passedSummary);\n\n for (const audit of passedAudits) {\n passedElem.appendChild(this._renderAudit(audit));\n }\n element.appendChild(passedElem);\n return element;\n }\n\n /**\n * @param {!ReportRenderer.AuditJSON} audit\n * @return {!Element}\n */\n _renderAudit(audit) {\n const element = this._dom.createElement('div', 'lh-audit');\n element.appendChild(this._renderAuditScore(audit));\n return element;\n }\n}\n\nif (typeof module !== 'undefined' && module.exports) {\n module.exports = ReportRenderer;\n}\n\n/**\n * @typedef {{\n * id: string, weight:\n * number, score: number,\n * result: {\n * description: string,\n * displayValue: string,\n * helpText: string,\n * score: (number|boolean),\n * scoringMode: string,\n * details: (!DetailsRenderer.DetailsJSON|!DetailsRenderer.CardsDetailsJSON|undefined)\n * }\n * }}\n */\nReportRenderer.AuditJSON; // eslint-disable-line no-unused-expressions\n\n/**\n * @typedef {{\n * name: string,\n * weight: number,\n * score: number,\n * description: string,\n * audits: !Array<!ReportRenderer.AuditJSON>\n * }}\n */\nReportRenderer.CategoryJSON; // eslint-disable-line no-unused-expressions\n\n/**\n * @typedef {{\n * lighthouseVersion: !string,\n * generatedTime: !string,\n * initialUrl: !string,\n * url: !string,\n * audits: ?Object,\n * reportCategories: !Array<!ReportRenderer.CategoryJSON>\n * }}\n */\nReportRenderer.ReportJSON; // eslint-disable-line no-unused-expressions\n", + "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use strict';\n\n/* globals self */\n\nconst RATINGS = {\n PASS: {label: 'pass', minScore: 75},\n AVERAGE: {label: 'average', minScore: 45},\n FAIL: {label: 'fail'}\n};\n\nclass Util {\n /**\n * Convert a score to a rating label.\n * @param {number} score\n * @return {string}\n */\n static calculateRating(score) {\n let rating = RATINGS.FAIL.label;\n if (score >= RATINGS.PASS.minScore) {\n rating = RATINGS.PASS.label;\n } else if (score >= RATINGS.AVERAGE.minScore) {\n rating = RATINGS.AVERAGE.label;\n }\n return rating;\n }\n\n /**\n * Format number.\n * @param {number} number\n * @return {string}\n */\n static formatNumber(number) {\n return number.toLocaleString(undefined, {maximumFractionDigits: 1});\n }\n\n /**\n * Format time.\n * @param {string} date\n * @return {string}\n */\n static formatDateTime(date) {\n const options = {\n month: 'short', day: 'numeric', year: 'numeric',\n hour: 'numeric', minute: 'numeric', timeZoneName: 'short'\n };\n let formatter = new Intl.DateTimeFormat('en-US', options);\n\n // Force UTC if runtime timezone could not be detected.\n // See https://github.com/GoogleChrome/lighthouse/issues/1056\n const tz = formatter.resolvedOptions().timeZone;\n if (!tz || tz.toLowerCase() === 'etc/unknown') {\n options.timeZone = 'UTC';\n formatter = new Intl.DateTimeFormat('en-US', options);\n }\n return formatter.format(new Date(date));\n }\n}\n\nif (typeof module !== 'undefined' && module.exports) {\n module.exports = Util;\n} else {\n self.Util = Util;\n}\n", + "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use strict';\n\n/* globals URL self */\n\nclass DOM {\n /**\n * @param {!Document} document\n */\n constructor(document) {\n /** @private {!Document} */\n this._document = document;\n }\n\n /**\n * @param {string} name\n * @param {string=} className\n * @param {!Object<string, (string|undefined)>=} attrs Attribute key/val pairs.\n * Note: if an attribute key has an undefined value, this method does not\n * set the attribute on the node.\n * @return {!Element}\n */\n createElement(name, className, attrs = {}) {\n const element = this._document.createElement(name);\n if (className) {\n element.className = className;\n }\n Object.keys(attrs).forEach(key => {\n const value = attrs[key];\n if (typeof value !== 'undefined') {\n element.setAttribute(key, value);\n }\n });\n return element;\n }\n\n /**\n * @param {string} selector\n * @param {!Node} context\n * @return {!DocumentFragment} A clone of the template content.\n * @throws {Error}\n */\n cloneTemplate(selector, context) {\n const template = /** @type {?HTMLTemplateElement} */ (context.querySelector(selector));\n if (!template) {\n throw new Error(`Template not found: template${selector}`);\n }\n\n const clone = /** @type {!DocumentFragment} */ (\n this._document.importNode(template.content, true));\n\n // Prevent duplicate styles in the DOM. After a template has been stamped\n // for the first time, remove the clone's styles so they're not re-added.\n if (template.hasAttribute('data-stamped')) {\n this.findAll('style', clone).forEach(style => style.remove());\n }\n template.setAttribute('data-stamped', true);\n\n return clone;\n }\n\n /**\n * Resets the \"stamped\" state of the templates.\n */\n resetTemplates() {\n this.findAll('template[data-stamped]', this._document).forEach(t => {\n t.removeAttribute('data-stamped');\n });\n }\n\n /**\n * @param {string} text\n * @return {!Element}\n */\n createSpanFromMarkdown(text) {\n const element = this.createElement('span');\n\n // Split on markdown links (e.g. [some link](https://...)).\n const parts = text.split(/\\[(.*?)\\]\\((https?:\\/\\/.*?)\\)/g);\n\n while (parts.length) {\n // Pop off the same number of elements as there are capture groups.\n const [preambleText, linkText, linkHref] = parts.splice(0, 3);\n element.appendChild(this._document.createTextNode(preambleText));\n\n // Append link if there are any.\n if (linkText && linkHref) {\n const a = /** @type {!HTMLAnchorElement} */ (this.createElement('a'));\n a.rel = 'noopener';\n a.target = '_blank';\n a.textContent = linkText;\n a.href = (new URL(linkHref)).href;\n element.appendChild(a);\n }\n }\n\n return element;\n }\n\n /**\n * @return {!Document}\n */\n document() {\n return this._document;\n }\n\n /**\n * Guaranteed context.querySelector. Always returns an element or throws if\n * nothing matches query.\n * @param {string} query\n * @param {!Node} context\n * @return {!Element}\n */\n find(query, context) {\n const result = context.querySelector(query);\n if (result === null) {\n throw new Error(`query ${query} not found`);\n }\n return result;\n }\n\n /**\n * Helper for context.querySelectorAll. Returns an Array instead of a NodeList.\n * @param {string} query\n * @param {!Node} context\n * @return {!Array<!Element>}\n */\n findAll(query, context) {\n return Array.from(context.querySelectorAll(query));\n }\n}\n\nif (typeof module !== 'undefined' && module.exports) {\n module.exports = DOM;\n} else {\n self.DOM = DOM;\n}\n", + "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use strict';\n\n/* globals self */\n\nclass DetailsRenderer {\n /**\n * @param {!DOM} dom\n */\n constructor(dom) {\n /** @private {!DOM} */\n this._dom = dom;\n }\n\n /**\n * @param {!DetailsRenderer.DetailsJSON} details\n * @return {!Element}\n */\n render(details) {\n switch (details.type) {\n case 'text':\n return this._renderText(details);\n case 'cards':\n return this._renderCards(/** @type {!DetailsRenderer.CardsDetailsJSON} */ (details));\n case 'list':\n return this._renderList(/** @type {!DetailsRenderer.ListDetailsJSON} */ (details));\n default:\n throw new Error(`Unknown type: ${details.type}`);\n }\n }\n\n /**\n * @param {!DetailsRenderer.DetailsJSON} text\n * @return {!Element}\n */\n _renderText(text) {\n const element = this._dom.createElement('div', 'lh-text');\n element.textContent = text.text;\n return element;\n }\n\n /**\n * @param {!DetailsRenderer.ListDetailsJSON} list\n * @return {!Element}\n */\n _renderList(list) {\n const element = this._dom.createElement('details', 'lh-details');\n if (list.header) {\n const summary = this._dom.createElement('summary', 'lh-list__header');\n summary.textContent = list.header.text;\n element.appendChild(summary);\n }\n\n const itemsElem = this._dom.createElement('div', 'lh-list__items');\n for (const item of list.items) {\n itemsElem.appendChild(this.render(item));\n }\n element.appendChild(itemsElem);\n return element;\n }\n\n /**\n * @param {!DetailsRenderer.CardsDetailsJSON} details\n * @return {!Element}\n */\n _renderCards(details) {\n const element = this._dom.createElement('details', 'lh-details');\n if (details.header) {\n element.appendChild(this._dom.createElement('summary')).textContent = details.header.text;\n }\n\n const cardsParent = this._dom.createElement('div', 'lh-scorecards');\n for (const item of details.items) {\n const card = cardsParent.appendChild(\n this._dom.createElement('div', 'lh-scorecard', {title: item.snippet}));\n const titleEl = this._dom.createElement('div', 'lh-scorecard__title');\n const valueEl = this._dom.createElement('div', 'lh-scorecard__value');\n const targetEl = this._dom.createElement('div', 'lh-scorecard__target');\n\n card.appendChild(titleEl).textContent = item.title;\n card.appendChild(valueEl).textContent = item.value;\n\n if (item.target) {\n card.appendChild(targetEl).textContent = `target: ${item.target}`;\n }\n }\n\n element.appendChild(cardsParent);\n return element;\n }\n}\n\nif (typeof module !== 'undefined' && module.exports) {\n module.exports = DetailsRenderer;\n} else {\n self.DetailsRenderer = DetailsRenderer;\n}\n\n/**\n * @typedef {{\n * type: string,\n * text: (string|undefined)\n * }}\n */\nDetailsRenderer.DetailsJSON; // eslint-disable-line no-unused-expressions\n\n/**\n * @typedef {{\n * type: string,\n * header: ({text: string}|undefined),\n * items: !Array<{type: string, text: (string|undefined)}>\n * }}\n */\nDetailsRenderer.ListDetailsJSON; // eslint-disable-line no-unused-expressions\n\n/** @typedef {{\n * type: string,\n * header: ({text: string}|undefined),\n * items: !Array<{title: string, value: string, snippet: (string|undefined), target: string}>\n * }}\n */\nDetailsRenderer.CardsDetailsJSON; // eslint-disable-line no-unused-expressions\n", + "/**\n * @license\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use strict';\n\n/* global URL */\n\n/**\n * Generate a filenamePrefix of hostname_YYYY-MM-DD_HH-MM-SS\n * Date/time uses the local timezone, however Node has unreliable ICU\n * support, so we must construct a YYYY-MM-DD date format manually. :/\n * @param {{url: string, generatedTime: string}} results\n * @return {string}\n */\nfunction getFilenamePrefix(results) {\n const hostname = new (URLConstructor || URL)(results.url).hostname;\n const date = (results.generatedTime && new Date(results.generatedTime)) || new Date();\n\n const timeStr = date.toLocaleTimeString('en-US', {hour12: false});\n const dateParts = date.toLocaleDateString('en-US', {\n year: 'numeric', month: '2-digit', day: '2-digit'\n }).split('/');\n dateParts.unshift(dateParts.pop());\n const dateStr = dateParts.join('-');\n\n const filenamePrefix = `${hostname}_${dateStr}_${timeStr}`;\n // replace characters that are unfriendly to filenames\n return filenamePrefix.replace(/[\\/\\?<>\\\\:\\*\\|\":]/g, '-');\n}\n\nlet URLConstructor;\nif (typeof module !== 'undefined' && module.exports) {\n URLConstructor = require('./url-shim');\n\n module.exports = {getFilenamePrefix};\n}\n", + "/**\n * @license\n * Copyright 2017 Google Inc. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use strict';\n\n/**\n * Logs messages via a UI butter.\n */\nclass Logger {\n /**\n * @param {!Element} element\n */\n constructor(element) {\n /** @type {!Element} */\n this.el = element;\n /** @private {?number} */\n this._id = null;\n }\n\n /**\n * Shows a butter bar.\n * @param {!string} msg The message to show.\n * @param {boolean=} autoHide True to hide the message after a duration.\n * Default is true.\n */\n log(msg, autoHide = true) {\n clearTimeout(this._id);\n\n this.el.textContent = msg;\n this.el.classList.add('show');\n if (autoHide) {\n this._id = setTimeout(_ => {\n this.el.classList.remove('show');\n }, 7000);\n }\n }\n\n /**\n * @param {string} msg\n */\n warn(msg) {\n this.log('Warning: ' + msg);\n }\n\n /**\n * @param {string} msg\n */\n error(msg) {\n this.log(msg);\n }\n\n /**\n * Explicitly hides the butter bar.\n */\n hide() {\n clearTimeout(this._id);\n this.el.classList.remove('show');\n }\n}\n\nif (typeof module !== 'undefined' && module.exports) {\n module.exports = Logger;\n}\n", + "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use strict';\n\n/**\n * @fileoverview Adds export button, print, and other dynamic functionality to\n * the report.\n */\n\n/* globals self URL Blob CustomEvent getFilenamePrefix */\n\nclass ReportUIFeatures {\n\n /**\n * @param {!DOM} dom\n */\n constructor(dom) {\n /** @type {!ReportRenderer.ReportJSON} */\n this.json; // eslint-disable-line no-unused-expressions\n /** @private {!DOM} */\n this._dom = dom;\n /** @private {!Document} */\n this._document = this._dom.document();\n /** @private {boolean} */\n this._copyAttempt = false;\n /** @type {!Element} **/\n this.exportButton; // eslint-disable-line no-unused-expressions\n\n this.onCopy = this.onCopy.bind(this);\n this.onExportButtonClick = this.onExportButtonClick.bind(this);\n this.onExport = this.onExport.bind(this);\n this.onKeyDown = this.onKeyDown.bind(this);\n this.printShortCutDetect = this.printShortCutDetect.bind(this);\n }\n\n /**\n * Adds export button, print, and other functionality to the report. The method\n * should be called whenever the report needs to be re-rendered.\n * @param {!ReportRenderer.ReportJSON} report\n */\n initFeatures(report) {\n this.json = report;\n this._setupExportButton();\n this._setUpCollapseDetailsAfterPrinting();\n this._resetUIState();\n this._document.addEventListener('keydown', this.printShortCutDetect);\n this._document.addEventListener('copy', this.onCopy);\n }\n\n /**\n * Fires a custom DOM event on target.\n * @param {string} name Name of the event.\n * @param {!Node=} target DOM node to fire the event on.\n * @param {*=} detail Custom data to include.\n */\n _fireEventOn(name, target = this._document, detail) {\n const event = new CustomEvent(name, detail ? {detail} : null);\n this._document.dispatchEvent(event);\n }\n\n _setupExportButton() {\n this.exportButton = this._dom.find('.lh-export__button', this._document);\n this.exportButton.addEventListener('click', this.onExportButtonClick);\n\n const dropdown = this._dom.find('.lh-export__dropdown', this._document);\n dropdown.addEventListener('click', this.onExport);\n }\n\n /**\n * Handler copy events.\n * @param {!Event} e\n */\n onCopy(e) {\n // Only handle copy button presses (e.g. ignore the user copying page text).\n if (this._copyAttempt) {\n // We want to write our own data to the clipboard, not the user's text selection.\n e.preventDefault();\n e.clipboardData.setData('text/plain', JSON.stringify(this.json, null, 2));\n\n this._fireEventOn('lh-log', this._document, {\n cmd: 'log', msg: 'Report JSON copied to clipboard'\n });\n }\n\n this._copyAttempt = false;\n }\n\n /**\n * Copies the report JSON to the clipboard (if supported by the browser).\n * @suppress {reportUnknownTypes}\n */\n onCopyButtonClick() {\n this._fireEventOn('lh-analytics', this._document, {\n cmd: 'send',\n fields: {hitType: 'event', eventCategory: 'report', eventAction: 'copy'}\n });\n\n try {\n if (this._document.queryCommandSupported('copy')) {\n this._copyAttempt = true;\n\n // Note: In Safari 10.0.1, execCommand('copy') returns true if there's\n // a valid text selection on the page. See http://caniuse.com/#feat=clipboard.\n if (!this._document.execCommand('copy')) {\n this._copyAttempt = false; // Prevent event handler from seeing this as a copy attempt.\n\n this._fireEventOn('lh-log', this._document, {\n cmd: 'warn', msg: 'Your browser does not support copy to clipboard.'\n });\n }\n }\n } catch (/** @type {!Error} */ e) {\n this._copyAttempt = false;\n this._fireEventOn('lh-log', this._document, {cmd: 'log', msg: e.message});\n }\n }\n\n closeExportDropdown() {\n this.exportButton.classList.remove('active');\n }\n\n /**\n * Click handler for export button.\n * @param {!Event} e\n */\n onExportButtonClick(e) {\n e.preventDefault();\n const el = /** @type {!Element} */ (e.target);\n el.classList.toggle('active');\n this._document.addEventListener('keydown', this.onKeyDown);\n }\n\n /**\n * Resets the state of page before capturing the page for export.\n * When the user opens the exported HTML page, certain UI elements should\n * be in their closed state (not opened) and the templates should be unstamped.\n */\n _resetUIState() {\n this.closeExportDropdown();\n this._dom.resetTemplates();\n }\n\n /**\n * Handler for \"export as\" button.\n * @param {!Event} e\n */\n onExport(e) {\n e.preventDefault();\n\n const el = /** @type {!Element} */ (e.target);\n\n if (!el.hasAttribute('data-action')) {\n return;\n }\n\n switch (el.getAttribute('data-action')) {\n case 'copy':\n this.onCopyButtonClick();\n break;\n case 'print':\n this.expandAllDetails();\n self.print();\n break;\n case 'save-json': {\n const jsonStr = JSON.stringify(this.json, null, 2);\n this._saveFile(new Blob([jsonStr], {type: 'application/json'}));\n break;\n }\n case 'save-html': {\n this._resetUIState();\n\n const htmlStr = this._document.documentElement.outerHTML;\n try {\n this._saveFile(new Blob([htmlStr], {type: 'text/html'}));\n } catch (/** @type {!Error} */ e) {\n this._fireEventOn('lh-log', this._document, {\n cmd: 'error', msg: 'Could not export as HTML. ' + e.message\n });\n }\n break;\n }\n }\n\n this.closeExportDropdown();\n this._document.removeEventListener('keydown', this.onKeyDown);\n }\n\n /**\n * Keydown handler for the document.\n * @param {!Event} e\n */\n onKeyDown(e) {\n if (e.keyCode === 27) { // ESC\n this.closeExportDropdown();\n }\n }\n\n /**\n * Expands audit details when user prints via keyboard shortcut.\n * @param {!Event} e\n */\n printShortCutDetect(e) {\n if ((e.ctrlKey || e.metaKey) && e.keyCode === 80) { // Ctrl+P\n this.expandAllDetails();\n }\n }\n\n /**\n * Expands all audit `<details>`.\n * Ideally, a print stylesheet could take care of this, but CSS has no way to\n * open a `<details>` element.\n */\n expandAllDetails() {\n const details = this._dom.findAll('.lh-categories details', this._document);\n details.map(detail => detail.open = true);\n }\n\n /**\n * Collapses all audit `<details>`.\n * open a `<details>` element.\n */\n collapseAllDetails() {\n const details = this._dom.findAll('.lh-categories details', this._document);\n details.map(detail => detail.open = false);\n }\n\n /**\n * Sets up listeners to collapse audit `<details>` when the user closes the\n * print dialog, all `<details>` are collapsed.\n */\n _setUpCollapseDetailsAfterPrinting() {\n // FF and IE implement these old events.\n if ('onbeforeprint' in self) {\n self.addEventListener('afterprint', this.collapseAllDetails);\n } else {\n // Note: FF implements both window.onbeforeprint and media listeners. However,\n // it doesn't matchMedia doesn't fire when matching 'print'.\n self.matchMedia('print').addListener(mql => {\n if (mql.matches) {\n this.expandAllDetails();\n } else {\n this.collapseAllDetails();\n }\n });\n }\n }\n /**\n * Downloads a file (blob) using a[download].\n * @param {!Blob|!File} blob The file to save.\n */\n _saveFile(blob) {\n const filename = getFilenamePrefix({\n url: this.json.url,\n generatedTime: this.json.generatedTime\n });\n\n const ext = blob.type.match('json') ? '.json' : '.html';\n const href = URL.createObjectURL(blob);\n\n const a = /** @type {!HTMLAnchorElement} */ (this._dom.createElement('a'));\n a.download = `${filename}${ext}`;\n a.href = href;\n this._document.body.appendChild(a); // Firefox requires anchor to be in the DOM.\n a.click();\n\n // cleanup.\n this._document.body.removeChild(a);\n setTimeout(_ => URL.revokeObjectURL(href), 500);\n }\n}\n\nif (typeof module !== 'undefined' && module.exports) {\n module.exports = ReportUIFeatures;\n} else {\n self.ReportUIFeatures = ReportUIFeatures;\n}\n", + "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use strict';\n\n/* globals self, Util */\n\nclass CategoryRenderer {\n /**\n * @param {!DOM} dom\n * @param {!DetailsRenderer} detailsRenderer\n */\n constructor(dom, detailsRenderer) {\n /** @private {!DOM} */\n this._dom = dom;\n /** @private {!DetailsRenderer} */\n this._detailsRenderer = detailsRenderer;\n /** @private {!Document|!Element} */\n this._templateContext = this._dom.document();\n }\n\n /**\n * @param {!ReportRenderer.AuditJSON} audit\n * @return {!Element}\n */\n _renderAuditScore(audit) {\n const tmpl = this._dom.cloneTemplate('#tmpl-lh-audit-score', this._templateContext);\n\n const scoringMode = audit.result.scoringMode;\n const description = audit.result.helpText;\n let title = audit.result.description;\n\n if (audit.result.displayValue) {\n title += `: ${audit.result.displayValue}`;\n }\n if (audit.result.optimalValue) {\n title += ` (target: ${audit.result.optimalValue})`;\n }\n\n if (audit.result.debugString) {\n const debugStrEl = tmpl.appendChild(this._dom.createElement('div', 'lh-debug'));\n debugStrEl.textContent = audit.result.debugString;\n }\n\n // Append audit details to header section so the entire audit is within a <details>.\n const header = /** @type {!HTMLDetailsElement} */ (this._dom.find('.lh-score__header', tmpl));\n header.open = audit.score < 100; // expand failed audits\n if (audit.result.details) {\n header.appendChild(this._detailsRenderer.render(audit.result.details));\n }\n\n return this._populateScore(tmpl, audit.score, scoringMode, title, description);\n }\n\n /**\n * @param {!DocumentFragment|!Element} element DOM node to populate with values.\n * @param {number} score\n * @param {string} scoringMode\n * @param {string} title\n * @param {string} description\n * @return {!Element}\n */\n _populateScore(element, score, scoringMode, title, description) {\n // Fill in the blanks.\n const valueEl = this._dom.find('.lh-score__value', element);\n valueEl.textContent = Util.formatNumber(score);\n valueEl.classList.add(`lh-score__value--${Util.calculateRating(score)}`,\n `lh-score__value--${scoringMode}`);\n\n this._dom.find('.lh-score__title', element).textContent = title;\n this._dom.find('.lh-score__description', element)\n .appendChild(this._dom.createSpanFromMarkdown(description));\n\n return /** @type {!Element} **/ (element);\n }\n\n /**\n * @param {!ReportRenderer.CategoryJSON} category\n * @return {!Element}\n */\n _renderCategoryScore(category) {\n const tmpl = this._dom.cloneTemplate('#tmpl-lh-category-score', this._templateContext);\n const score = Math.round(category.score);\n return this._populateScore(tmpl, score, 'numeric', category.name, category.description);\n }\n\n /**\n * @param {!ReportRenderer.AuditJSON} audit\n * @return {!Element}\n */\n _renderAudit(audit) {\n const element = this._dom.createElement('div', 'lh-audit');\n element.appendChild(this._renderAuditScore(audit));\n return element;\n }\n\n /**\n * @param {!Document|!Element} context\n */\n setTemplateContext(context) {\n this._templateContext = context;\n }\n\n /**\n * @param {!ReportRenderer.CategoryJSON} category\n * @return {!DocumentFragment}\n */\n renderScoreGauge(category) {\n const tmpl = this._dom.cloneTemplate('#tmpl-lh-gauge', this._templateContext);\n this._dom.find('.lh-gauge__wrapper', tmpl).href = `#${category.id}`;\n this._dom.find('.lh-gauge__label', tmpl).textContent = category.name;\n\n const score = Math.round(category.score);\n const fillRotation = Math.floor((score / 100) * 180);\n\n const gauge = this._dom.find('.lh-gauge', tmpl);\n gauge.setAttribute('data-progress', score); // .dataset not supported in jsdom.\n gauge.classList.add(`lh-gauge--${Util.calculateRating(score)}`);\n\n this._dom.findAll('.lh-gauge__fill', gauge).forEach(el => {\n el.style.transform = `rotate(${fillRotation}deg)`;\n });\n\n this._dom.find('.lh-gauge__mask--full', gauge).style.transform =\n `rotate(${fillRotation}deg)`;\n this._dom.find('.lh-gauge__fill--fix', gauge).style.transform =\n `rotate(${fillRotation * 2}deg)`;\n this._dom.find('.lh-gauge__percentage', gauge).textContent = score;\n\n return tmpl;\n }\n\n /**\n * @param {!ReportRenderer.CategoryJSON} category\n * @return {!Element}\n */\n render(category) {\n const element = this._dom.createElement('div', 'lh-category');\n element.id = category.id;\n element.appendChild(this._renderCategoryScore(category));\n\n const passedAudits = category.audits.filter(audit => audit.score === 100);\n const nonPassedAudits = category.audits.filter(audit => !passedAudits.includes(audit));\n\n for (const audit of nonPassedAudits) {\n element.appendChild(this._renderAudit(audit));\n }\n\n // Don't create a passed section if there are no passed.\n if (!passedAudits.length) {\n return element;\n }\n\n const passedElem = this._dom.createElement('details', 'lh-passed-audits');\n const passedSummary = this._dom.createElement('summary', 'lh-passed-audits-summary');\n passedSummary.textContent = `View ${passedAudits.length} passed items`;\n passedElem.appendChild(passedSummary);\n\n for (const audit of passedAudits) {\n passedElem.appendChild(this._renderAudit(audit));\n }\n element.appendChild(passedElem);\n return element;\n }\n}\n\nif (typeof module !== 'undefined' && module.exports) {\n module.exports = CategoryRenderer;\n} else {\n self.CategoryRenderer = CategoryRenderer;\n}\n", + "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use strict';\n\n/**\n * @fileoverview The entry point for rendering the Lighthouse report based on the JSON output.\n * This file is injected into the report HTML along with the JSON report.\n *\n * Dummy text for ensuring report robustness: </script> pre$`post %%LIGHTHOUSE_JSON%%\n */\n\n/* globals self, Util */\n\nclass ReportRenderer {\n /**\n * @param {!DOM} dom\n * @param {!CategoryRenderer} categoryRenderer\n * @param {?ReportUIFeatures=} uiFeatures\n */\n constructor(dom, categoryRenderer, uiFeatures = null) {\n /** @private {!DOM} */\n this._dom = dom;\n /** @private {!CategoryRenderer} */\n this._categoryRenderer = categoryRenderer;\n /** @private {!Document|!Element} */\n this._templateContext = this._dom.document();\n /** @private {ReportUIFeatures} */\n this._uiFeatures = uiFeatures;\n }\n\n /**\n * @param {!ReportRenderer.ReportJSON} report\n * @param {!Element} container Parent element to render the report into.\n */\n renderReport(report, container) {\n container.textContent = ''; // Remove previous report.\n\n let element;\n try {\n element = container.appendChild(this._renderReport(report));\n\n // Hook in JS features and page-level event listeners after the report\n // is in the document.\n if (this._uiFeatures) {\n this._uiFeatures.initFeatures(report);\n }\n } catch (/** @type {!Error} */ e) {\n container.textContent = '';\n element = container.appendChild(this._renderException(e));\n }\n\n return /** @type {!Element} **/ (element);\n }\n\n /**\n * Define a custom element for <templates> to be extracted from. For example:\n * this.setTemplateContext(new DOMParser().parseFromString(htmlStr, 'text/html'))\n * @param {!Document|!Element} context\n */\n setTemplateContext(context) {\n this._templateContext = context;\n this._categoryRenderer.setTemplateContext(context);\n }\n\n /**\n * @param {!Error} e\n * @return {!Element}\n */\n _renderException(e) {\n const element = this._dom.createElement('div', 'lh-exception');\n element.textContent = String(e.stack);\n return element;\n }\n\n /**\n * @param {!ReportRenderer.ReportJSON} report\n * @return {!DocumentFragment}\n */\n _renderReportHeader(report) {\n const header = this._dom.cloneTemplate('#tmpl-lh-heading', this._templateContext);\n this._dom.find('.lh-config__timestamp', header).textContent =\n Util.formatDateTime(report.generatedTime);\n const url = this._dom.find('.lh-metadata__url', header);\n url.href = report.url;\n url.textContent = report.url;\n\n const env = this._dom.find('.lh-env__items', header);\n report.runtimeConfig.environment.forEach(runtime => {\n const item = this._dom.cloneTemplate('#tmpl-lh-env__items', env);\n this._dom.find('.lh-env__name', item).textContent = runtime.name;\n this._dom.find('.lh-env__description', item).textContent = runtime.description;\n this._dom.find('.lh-env__enabled', item).textContent =\n runtime.enabled ? 'Enabled' : 'Disabled';\n env.appendChild(item);\n });\n\n return header;\n }\n\n /**\n * @param {!ReportRenderer.ReportJSON} report\n * @return {!DocumentFragment}\n */\n _renderReportFooter(report) {\n const footer = this._dom.cloneTemplate('#tmpl-lh-footer', this._templateContext);\n this._dom.find('.lh-footer__version', footer).textContent = report.lighthouseVersion;\n this._dom.find('.lh-footer__timestamp', footer).textContent =\n Util.formatDateTime(report.generatedTime);\n return footer;\n }\n\n /**\n * @param {!ReportRenderer.ReportJSON} report\n * @return {!DocumentFragment}\n */\n _renderReportNav(report) {\n const leftNav = this._dom.cloneTemplate('#tmpl-lh-leftnav', this._templateContext);\n\n this._dom.find('.leftnav__header__version', leftNav).textContent =\n `Version: ${report.lighthouseVersion}`;\n\n const nav = this._dom.find('.lh-leftnav', leftNav);\n for (const category of report.reportCategories) {\n const itemsTmpl = this._dom.cloneTemplate('#tmpl-lh-leftnav__items', leftNav);\n\n const navItem = this._dom.find('.lh-leftnav__item', itemsTmpl);\n navItem.href = `#${category.id}`;\n\n this._dom.find('.leftnav-item__category', navItem).textContent = category.name;\n const score = this._dom.find('.leftnav-item__score', navItem);\n score.classList.add(`lh-score__value--${Util.calculateRating(category.score)}`);\n score.textContent = Math.round(Util.formatNumber(category.score));\n nav.appendChild(navItem);\n }\n return leftNav;\n }\n\n /**\n * @param {!ReportRenderer.ReportJSON} report\n * @return {!Element}\n */\n _renderReport(report) {\n const container = this._dom.createElement('div', 'lh-container');\n\n container.appendChild(this._renderReportHeader(report)); // sticky header goes at the top.\n container.appendChild(this._renderReportNav(report));\n\n const reportSection = container.appendChild(this._dom.createElement('div', 'lh-report'));\n\n const scoreHeader = reportSection.appendChild(\n this._dom.createElement('div', 'lh-scores-header'));\n\n const categories = reportSection.appendChild(this._dom.createElement('div', 'lh-categories'));\n for (const category of report.reportCategories) {\n scoreHeader.appendChild(this._categoryRenderer.renderScoreGauge(category));\n categories.appendChild(this._categoryRenderer.render(category));\n }\n\n reportSection.appendChild(this._renderReportFooter(report));\n\n return container;\n }\n}\n\nif (typeof module !== 'undefined' && module.exports) {\n module.exports = ReportRenderer;\n} else {\n self.ReportRenderer = ReportRenderer;\n}\n\n/**\n * @typedef {{\n * id: string,\n * weight: number,\n * score: number,\n * result: {\n * description: string,\n * debugString: string,\n * displayValue: string,\n * helpText: string,\n * score: (number|boolean),\n * scoringMode: string,\n * optimalValue: number,\n * details: (!DetailsRenderer.DetailsJSON|undefined)\n * }\n * }}\n */\nReportRenderer.AuditJSON; // eslint-disable-line no-unused-expressions\n\n/**\n * @typedef {{\n * name: string,\n * id: string,\n * weight: number,\n * score: number,\n * description: string,\n * audits: !Array<!ReportRenderer.AuditJSON>\n * }}\n */\nReportRenderer.CategoryJSON; // eslint-disable-line no-unused-expressions\n\n/**\n * @typedef {{\n * lighthouseVersion: string,\n * generatedTime: string,\n * initialUrl: string,\n * url: string,\n * reportCategories: !Array<!ReportRenderer.CategoryJSON>,\n * runtimeConfig: {\n * blockedUrlPatterns: !Array<string>,\n * environment: !Array<{description: string, enabled: boolean, name: string}>\n * }\n * }}\n */\nReportRenderer.ReportJSON; // eslint-disable-line no-unused-expressions\n", ].join(';\n'); -const REPORT_CSS = "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n :root {\n --text-font-family: '.SFNSDisplay-Regular', 'Helvetica Neue', 'Lucida Grande', sans-serif;\n --body-font-size: 13px;\n --default-padding: 16px;\n\n --secondary-text-color: #565656;\n /*--accent-color: #3879d9;*/\n --fail-color: #df332f;\n --pass-color: #2b882f;\n --average-color: #ef6c00; /* md orange 800 */\n --warning-color: #757575; /* md grey 600 */\n\n --report-border-color: #ebebeb;\n\n --lh-score-highlight-bg: #fafafa;\n --lh-score-icon-background-size: 17px;\n --lh-score-margin: var(--default-padding);\n --lh-audit-score-width: 35px;\n --lh-category-score-width: 50px;\n}\n\n* {\n box-sizing: border-box;\n}\n\nbody {\n font-family: var(--text-font-family);\n font-size: var(--body-font-size);\n margin: 0;\n line-height: var(--body-line-height);\n}\n\n[hidden] {\n display: none !important;\n}\n\n.lh-details {\n font-size: smaller;\n margin-top: var(--default-padding);\n}\n\n.lh-details summary {\n cursor: pointer;\n}\n\n.lh-details[open] summary {\n margin-bottom: var(--default-padding);\n}\n\n/* List */\n.lh-list__items {\n padding-left: var(--default-padding);\n}\n\n.lh-list__items > * {\n border-bottom: 1px solid gray;\n margin-bottom: 2px;\n}\n\n/* Card */\n.lh-scorecards {\n display: flex;\n flex-wrap: wrap;\n}\n.lh-scorecard {\n display: flex;\n align-items: center;\n justify-content: center;\n flex: 0 0 180px;\n flex-direction: column;\n padding: var(--default-padding);\n padding-top: calc(32px + var(--default-padding));\n border-radius: 3px;\n margin-right: var(--default-padding);\n position: relative;\n line-height: inherit;\n border: 1px solid #ebebeb;\n}\n.lh-scorecard__title {\n background-color: #eee;\n position: absolute;\n top: 0;\n right: 0;\n left: 0;\n display: flex;\n justify-content: center;\n align-items: center;\n padding: calc(var(--default-padding) / 2);\n}\n.lh-scorecard__value {\n font-size: 28px;\n}\n.lh-scorecard__target {\n margin-top: calc(var(--default-padding) / 2);\n}\n\n/* Score */\n\n.lh-score {\n display: flex;\n align-items: flex-start;\n}\n\n.lh-score__value {\n flex: none;\n padding: 5px;\n margin-right: var(--lh-score-margin);\n width: var(--lh-audit-score-width);\n display: flex;\n justify-content: center;\n align-items: center;\n background: var(--warning-color);\n color: #fff;\n border-radius: 2px;\n position: relative;\n}\n\n.lh-score__value::after {\n content: '';\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n bottom: 0;\n background-color: #000;\n border-radius: inherit;\n}\n\n.lh-score__value--binary {\n text-indent: -500px;\n}\n\n/* No icon for audits with number scores. */\n.lh-score__value:not(.lh-score__value--binary)::after {\n content: none;\n}\n\n.lh-score__value--pass {\n background: var(--pass-color);\n}\n\n.lh-score__value--pass::after {\n background: url('data:image/svg+xml;utf8,<svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" xmlns=\"http://www.w3.org/2000/svg\"><title>pass</title><path d=\"M9.17 2.33L4.5 7 2.83 5.33 1.5 6.66l3 3 6-6z\" fill=\"white\" fill-rule=\"evenodd\"/></svg>') no-repeat 50% 50%;\n background-size: var(--lh-score-icon-background-size);\n}\n\n.lh-score__value--average {\n background: var(--average-color);\n}\n\n.lh-score__value--average::after {\n background: none;\n content: '!';\n background-color: var(--average-color);\n color: #fff;\n display: flex;\n justify-content: center;\n align-items: center;\n font-weight: 500;\n font-size: 15px;\n}\n\n.lh-score__value--fail {\n background: var(--fail-color);\n}\n\n.lh-score__value--fail::after {\n background: url('data:image/svg+xml;utf8,<svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" xmlns=\"http://www.w3.org/2000/svg\"><title>fail</title><path d=\"M8.33 2.33l1.33 1.33-2.335 2.335L9.66 8.33 8.33 9.66 5.995 7.325 3.66 9.66 2.33 8.33l2.335-2.335L2.33 3.66l1.33-1.33 2.335 2.335z\" fill=\"white\"/></svg>') no-repeat 50% 50%;\n background-size: var(--lh-score-icon-background-size);\n}\n\n.lh-score__title {\n margin-bottom: calc(var(--default-padding) / 2);\n}\n\n.lh-score__description {\n font-size: smaller;\n color: var(--secondary-text-color);\n margin-top: calc(var(--default-padding) / 2);\n}\n\n.lh-score__header {\n flex: 1;\n margin-top: 2px;\n}\n\n.lh-score__header[open] .lh-score__arrow {\n transform: rotateZ(90deg);\n}\n\n.lh-score__arrow {\n background: url('data:image/svg+xml;utf8,<svg fill=\"black\" height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z\"/><path d=\"M0-.25h24v24H0z\" fill=\"none\"/></svg>') no-repeat 50% 50%;\n background-size: contain;\n background-color: transparent;\n width: 24px;\n height: 24px;\n flex: none;\n margin: 0 8px 0 8px;\n transition: transform 150ms ease-in-out;\n}\n\n.lh-score__snippet {\n display: flex;\n align-items: center;\n justify-content: space-between;\n cursor: pointer;\n /*outline: none;*/\n}\n\n.lh-score__snippet::-moz-list-bullet {\n display: none;\n}\n\n.lh-score__snippet::-webkit-details-marker {\n display: none;\n}\n\n/*.lh-score__snippet:focus .lh-score__title {\n outline: rgb(59, 153, 252) auto 5px;\n}*/\n\n/* Audit */\n\n.lh-audit {\n margin-top: var(--default-padding);\n}\n\n.lh-audit > .lh-score {\n font-size: 16px;\n}\n\n/* Report */\n\n.lh-exception {\n font-size: large;\n}\n\n.lh-report {\n padding: var(--default-padding);\n}\n\n.lh-category {\n padding: 24px 0;\n border-top: 1px solid var(--report-border-color);\n}\n\n.lh-category:first-of-type {\n border: none;\n padding-top: 0;\n}\n\n.lh-category > .lh-audit,\n.lh-category > .lh-passed-audits > .lh-audit {\n margin-left: calc(var(--lh-category-score-width) + var(--lh-score-margin));\n}\n\n.lh-category > .lh-score {\n font-size: 20px;\n}\n\n.lh-category > .lh-score .lh-score__value {\n width: var(--lh-category-score-width);\n}\n\n/* Category snippet shouldnt have pointer cursor. */\n.lh-category > .lh-score .lh-score__snippet {\n cursor: initial;\n}\n\n.lh-category > .lh-score .lh-score__title {\n font-size: 24px;\n font-weight: 400;\n}\n\nsummary.lh-passed-audits-summary {\n margin: 10px 5px;\n font-size: 15px;\n}\n\nsummary.lh-passed-audits-summary::-webkit-details-marker {\n background: rgb(66, 175, 69);\n color: white;\n position:relative;\n content: '';\n padding: 3px;\n}\n\n/*# sourceURL=report.styles.css */\n"; -const REPORT_TEMPLATES = "<!-- Lighthouse category score -->\n<template id=\"tmpl-lh-category-score\">\n <div class=\"lh-score\">\n <div class=\"lh-score__value\"><!-- fill me --></div>\n <div class=\"lh-score__header\">\n <div class=\"lh-score__snippet\">\n <span class=\"lh-score__title\"><!-- fill me --></span>\n </div>\n <div class=\"lh-score__description\"><!-- fill me --></div>\n </div>\n </div>\n</template>\n\n<!-- Lighthouse audit score -->\n<template id=\"tmpl-lh-audit-score\">\n <div class=\"lh-score\">\n <div class=\"lh-score__value\"><!-- fill me --></div>\n <details class=\"lh-score__header\">\n <summary class=\"lh-score__snippet\">\n <span class=\"lh-score__title\"><!-- fill me --></span>\n <div class=\"lh-score__arrow\" title=\"See audits\"></div>\n </summary>\n <div class=\"lh-score__description\"><!-- fill me --></div>\n </details>\n </div>\n</template>\n"; +const REPORT_CSS = "/**\n * Copyright 2017 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n.lh-root {\n --text-font-family: '.SFNSDisplay-Regular', 'Helvetica Neue', 'Lucida Grande', sans-serif;\n --body-font-size: 13px;\n --header-font-size: 16px;\n --body-line-height: 1.5;\n --default-padding: 16px;\n\n --secondary-text-color: #565656;\n /*--accent-color: #3879d9;*/\n --fail-color: #df332f;\n --pass-color: #2b882f;\n --average-color: #ef6c00; /* md orange 800 */\n --warning-color: #757575; /* md grey 600 */\n\n --report-border-color: #ccc;\n --report-secondary-border-color: #ebebeb;\n --report-width: 850px;\n --report-menu-width: 280px;\n --report-content-width: calc(var(--report-width) + var(--report-menu-width));\n\n --lh-score-highlight-bg: #fafafa;\n --lh-score-icon-background-size: 17px;\n --lh-score-margin: var(--default-padding);\n --lh-audit-score-width: 35px;\n --lh-category-score-width: 50px;\n}\n\n.lh-root * {\n box-sizing: border-box;\n}\n\n.lh-root {\n font-family: var(--text-font-family);\n font-size: var(--body-font-size);\n margin: 0;\n line-height: var(--body-line-height);\n background: #f5f5f5;\n scroll-behavior: smooth;\n}\n\n.lh-root [hidden] {\n display: none !important;\n}\n\na {\n color: #0c50c7;\n}\n\nsummary {\n cursor: pointer;\n}\n\n.lh-details {\n font-size: smaller;\n margin-top: var(--default-padding);\n}\n\n.lh-details[open] summary {\n margin-bottom: var(--default-padding);\n}\n\n/* Report header */\n\n.report-icon {\n opacity: 0.7;\n}\n.report-icon:hover {\n opacity: 1;\n}\n.report-icon[disabled] {\n opacity: 0.3;\n pointer-events: none;\n}\n\n.report-icon--share {\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path fill=\"none\" d=\"M0 0h24v24H0z\"/><path d=\"M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z\"/></svg>');\n}\n.report-icon--print {\n background-image: url('data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\"><path d=\"M19 8H5c-1.66 0-3 1.34-3 3v6h4v4h12v-4h4v-6c0-1.66-1.34-3-3-3zm-3 11H8v-5h8v5zm3-7c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm-1-9H6v4h12V3z\"/><path fill=\"none\" d=\"M0 0h24v24H0z\"/></svg>');\n}\n.report-icon--copy {\n background-image: url('data:image/svg+xml;utf8,<svg height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z\"/></svg>');\n}\n.report-icon--open {\n background-image: url('data:image/svg+xml;utf8,<svg height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M0 0h24v24H0z\" fill=\"none\"/><path d=\"M19 4H5c-1.11 0-2 .9-2 2v12c0 1.1.89 2 2 2h4v-2H5V8h14v10h-4v2h4c1.1 0 2-.9 2-2V6c0-1.1-.89-2-2-2zm-7 6l-4 4h3v6h2v-6h3l-4-4z\"/></svg>');\n}\n.report-icon--download {\n background-image: url('data:image/svg+xml;utf8,<svg height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z\"/><path d=\"M0 0h24v24H0z\" fill=\"none\"/></svg>');\n}\n\n/* List */\n.lh-list {\n font-size: smaller;\n margin-top: var(--default-padding);\n}\n\n.lh-list__items {\n padding-left: var(--default-padding);\n}\n\n.lh-list__items > * {\n border-bottom: 1px solid gray;\n margin-bottom: 2px;\n}\n\n/* Card */\n.lh-scorecards {\n display: flex;\n flex-wrap: wrap;\n}\n.lh-scorecard {\n display: flex;\n align-items: center;\n justify-content: center;\n flex: 0 0 180px;\n flex-direction: column;\n padding: var(--default-padding);\n padding-top: calc(32px + var(--default-padding));\n border-radius: 3px;\n margin-right: var(--default-padding);\n position: relative;\n line-height: inherit;\n border: 1px solid #ebebeb;\n}\n.lh-scorecard__title {\n background-color: #eee;\n position: absolute;\n top: 0;\n right: 0;\n left: 0;\n display: flex;\n justify-content: center;\n align-items: center;\n padding: calc(var(--default-padding) / 2);\n}\n.lh-scorecard__value {\n font-size: 28px;\n}\n.lh-scorecard__target {\n margin-top: calc(var(--default-padding) / 2);\n}\n\n/* Score */\n\n.lh-score {\n display: flex;\n align-items: flex-start;\n}\n\n.lh-score__value {\n flex: none;\n padding: 5px;\n margin-right: var(--lh-score-margin);\n width: var(--lh-audit-score-width);\n display: flex;\n justify-content: center;\n align-items: center;\n background: var(--warning-color);\n color: #fff;\n border-radius: 2px;\n position: relative;\n}\n\n.lh-score__value::after {\n content: '';\n position: absolute;\n left: 0;\n right: 0;\n top: 0;\n bottom: 0;\n background-color: #000;\n border-radius: inherit;\n}\n\n.lh-score__value--binary {\n text-indent: -5000px;\n}\n\n/* No icon for audits with number scores. */\n.lh-score__value:not(.lh-score__value--binary)::after {\n content: none;\n}\n\n.lh-score__value--pass {\n background: var(--pass-color);\n}\n\n.lh-score__value--pass::after {\n background: url('data:image/svg+xml;utf8,<svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" xmlns=\"http://www.w3.org/2000/svg\"><title>pass</title><path d=\"M9.17 2.33L4.5 7 2.83 5.33 1.5 6.66l3 3 6-6z\" fill=\"white\" fill-rule=\"evenodd\"/></svg>') no-repeat 50% 50%;\n background-size: var(--lh-score-icon-background-size);\n}\n\n.lh-score__value--average {\n background: var(--average-color);\n}\n\n.lh-score__value--average::after {\n background: none;\n content: '!';\n background-color: var(--average-color);\n color: #fff;\n display: flex;\n justify-content: center;\n align-items: center;\n font-weight: 500;\n font-size: 15px;\n}\n\n.lh-score__value--fail {\n background: var(--fail-color);\n}\n\n.lh-score__value--fail::after {\n background: url('data:image/svg+xml;utf8,<svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" xmlns=\"http://www.w3.org/2000/svg\"><title>fail</title><path d=\"M8.33 2.33l1.33 1.33-2.335 2.335L9.66 8.33 8.33 9.66 5.995 7.325 3.66 9.66 2.33 8.33l2.335-2.335L2.33 3.66l1.33-1.33 2.335 2.335z\" fill=\"white\"/></svg>') no-repeat 50% 50%;\n background-size: var(--lh-score-icon-background-size);\n}\n\n.lh-score__title {\n margin-bottom: calc(var(--default-padding) / 2);\n}\n\n.lh-score__description {\n font-size: smaller;\n color: var(--secondary-text-color);\n margin-top: calc(var(--default-padding) / 2);\n}\n\n.lh-score__header {\n flex: 1;\n margin-top: 2px;\n}\n\n.lh-score__header[open] .lh-toggle-arrow {\n transform: rotateZ(90deg);\n}\n\n.lh-toggle-arrow {\n background: url('data:image/svg+xml;utf8,<svg fill=\"black\" height=\"24\" viewBox=\"0 0 24 24\" width=\"24\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z\"/><path d=\"M0-.25h24v24H0z\" fill=\"none\"/></svg>') no-repeat 50% 50%;\n background-size: contain;\n background-color: transparent;\n width: 24px;\n height: 24px;\n flex: none;\n margin-left: calc(var(--default-padding) / 2);\n transition: transform 150ms ease-in-out;\n cursor: pointer;\n border: none;\n}\n\n.lh-score__snippet {\n display: flex;\n align-items: center;\n justify-content: space-between;\n /*outline: none;*/\n}\n\n.lh-score__snippet::-moz-list-bullet {\n display: none;\n}\n\n.lh-score__snippet::-webkit-details-marker {\n display: none;\n}\n\n/*.lh-score__snippet:focus .lh-score__title {\n outline: rgb(59, 153, 252) auto 5px;\n}*/\n\n/* Audit */\n\n.lh-audit {\n margin-top: var(--default-padding);\n}\n\n.lh-audit > .lh-score {\n font-size: 16px;\n font-size: var(--header-font-size);\n}\n\n.lh-debug {\n margin-top: calc(var(--default-padding) / 2);\n margin-left: calc(var(--lh-audit-score-width) + var(--lh-score-margin));\n color: var(--fail-color);\n}\n\n/* Report */\n\n.lh-container {\n display: flex;\n max-width: var(--report-content-width);\n margin: 0 auto;\n /*border-right: 1px solid var(--report-border-color);*/\n /*border-left: 1px solid var(--report-border-color);*/\n}\n\n.lh-report {\n margin-left: var(--report-menu-width);\n background-color: #fff;\n}\n\n.lh-exception {\n font-size: large;\n}\n\n.lh-scores-header {\n display: flex;\n margin: var(--report-header-height) 0 0 0;\n padding: calc(var(--default-padding) * 2) var(--default-padding);\n border-bottom: 1px solid var(--report-border-color);\n}\n\n.lh-category {\n padding: 24px calc(var(--default-padding) * 2);\n border-top: 1px solid var(--report-border-color);\n}\n\n.lh-category:first-of-type {\n border: none;\n}\n\n.lh-category > .lh-audit,\n.lh-category > .lh-passed-audits > .lh-audit {\n margin-left: calc(var(--lh-category-score-width) + var(--lh-score-margin));\n}\n\n.lh-category > .lh-score {\n font-size: 20px;\n}\n\n.lh-category > .lh-score .lh-score__value {\n width: var(--lh-category-score-width);\n}\n\n/* Category snippet shouldnt have pointer cursor. */\n.lh-category > .lh-score .lh-score__snippet {\n cursor: initial;\n}\n\n.lh-category > .lh-score .lh-score__title {\n font-size: 24px;\n font-weight: 400;\n}\n\nsummary.lh-passed-audits-summary {\n margin: var(--default-padding);\n font-size: 15px;\n display: flex;\n align-items: center;\n}\n\nsummary.lh-passed-audits-summary::-webkit-details-marker {\n background: var(--pass-color);\n color: white;\n position: relative;\n content: '';\n padding: 3px 3px 3px 6px;\n border-radius: 2px;\n}\n\n.lh-passed-audits[open] summary.lh-passed-audits-summary::-webkit-details-marker {\n padding: 3px 5px 3px 4px;\n}\n\n#lh-log {\n position: fixed;\n background-color: #323232;\n color: #fff;\n min-height: 48px;\n min-width: 288px;\n padding: 16px 24px;\n box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);\n border-radius: 2px;\n margin: 12px;\n font-size: 14px;\n cursor: default;\n transition: transform 0.3s, opacity 0.3s;\n transform: translateY(100px);\n opacity: 0;\n -webkit-font-smoothing: antialiased;\n bottom: 0;\n left: 0;\n z-index: 3;\n}\n\n#lh-log.show {\n opacity: 1;\n transform: translateY(0);\n}\n\n@media screen and (max-width: 767px) {\n .lh-report {\n margin-left: 0;\n }\n .lh-category {\n padding: 24px var(--default-padding);\n }\n}\n\n@media print {\n body {\n -webkit-print-color-adjust: exact; /* print background colors */\n }\n .lh-report {\n margin-left: 0;\n }\n .lh-categories {\n margin-top: 0;\n }\n}\n\n/*# sourceURL=report.styles.css */\n"; +const REPORT_TEMPLATES = "<!-- Lighthouse category score -->\n<template id=\"tmpl-lh-category-score\">\n <div class=\"lh-score\">\n <div class=\"lh-score__value\"><!-- fill me --></div>\n <div class=\"lh-score__header\">\n <div class=\"lh-score__snippet\">\n <span class=\"lh-score__title\"><!-- fill me --></span>\n </div>\n <div class=\"lh-score__description\"><!-- fill me --></div>\n </div>\n </div>\n</template>\n\n<!-- Lighthouse audit score -->\n<template id=\"tmpl-lh-audit-score\">\n <div class=\"lh-score\">\n <div class=\"lh-score__value\"><!-- fill me --></div>\n <details class=\"lh-score__header\">\n <summary class=\"lh-score__snippet\">\n <span class=\"lh-score__title\"><!-- fill me --></span>\n <div class=\"lh-toggle-arrow\" title=\"See audits\"></div>\n </summary>\n <div class=\"lh-score__description\"><!-- fill me --></div>\n </details>\n </div>\n</template>\n\n<!-- Lighthouse left nav -->\n<template id=\"tmpl-lh-leftnav\">\n <style>\n .lh-leftnav {\n width: var(--report-menu-width);\n border-right: 1px solid var(--report-secondary-border-color);\n position: fixed;\n height: 100%;\n background: #fff;\n will-change: transform; /* prevent excessive paints */\n z-index: 2;\n }\n .lh-leftnav__item {\n padding: var(--default-padding);\n color: var(--secondary-text-color);\n font-size: 16px;\n display: flex;\n justify-content: space-between;\n text-decoration: none;\n color: inherit;\n }\n .leftnav-item__score {\n background: transparent;\n }\n .leftnav-item__score.lh-score__value--pass {\n color: var(--pass-color);\n }\n .leftnav-item__score.lh-score__value--average {\n color: var(--average-color);\n }\n .leftnav-item__score.lh-score__value--fail {\n color: var(--fail-color);\n }\n .leftnav__header {\n padding: 0 20px;\n height: 115px;\n font-size: 18px;\n display: flex;\n flex-direction: column;\n justify-content: center;\n background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZoAAADeCAYAAAAEuMatAABPH0lEQVR4Ae3dBZyk1ZX38d+59z5PSfv0uMPg7iQB4oRkIbrZbGSz7Js368mbrLtE1903nuzGVuIGgQgxCBbIAAPjru1d8sg9b091fxhBMsN0T1V13+/ncz5VNRaFP+ee89zi8QRBEARBEARBEARBEARBIARB0BTZuru7Be0HOoFYlcirAqgIiaoMGKtbAaWNBYFkD9/JzAuC4H3/57KeZKB0Wb/jea9YVrowFbNC0KVAj4Cr50olzTACCKCy11j/QLG/tlmEb5bXp18GdtFmgkDSy3lS7qxzLKsWzSfVcWCMIAiOzxfvuIZV3S+lp/hSUk5DYSzzCIcIkORKJcsQDqOg3gBgUj8qyP8YlU8DnyEI2oTsfc4ynkjvm65eaJ9z8cuMdQvwmoCpgAwiOlH2AERDCONABZUxICUIgob6R7/ynOz7D/+yjlRuEE8BryAccixBczQBUXCj+Xc73difhcAJ2oFUn1PmcV19dnfxZ1/yM2CXkucJIEeWGsCD1IAKSBUvIyCDGD0AphFEUz9fb9QcEATZvetPSd/7uT/2ef56CpFgDIecYNBMUSOAYES+YJ37XeA+WlQQyN6Xr+bxzP+XX3upJEPPIPdVnpwB5LBXEBQa5UHGgFGQUXIzBDIETFakg6B1IGMWCILq337+NfnajX8lxcISjDBl2oPmcCIMl7fXfh/4R1pQEMjwhX0cLfq5a1eUXn3lGxgZtSCeE2MeLcEAOZABOaIJyDAwAAxh4iFERqaCaQzrhwGlxQWBfv3+qP4/t/xZVo9+RZxhykkJGgCNDEZ4rxV5C1ChtQSho1nO0fr/6NWvMPN6Lif3dWaGABzVCZmpSoAaUMdSRRgGBkEGcbof0RGgAlIBMoKgSe5ev6KcRoVC18Z1C0758Hve7p39SZwBOMlBc4hFPmeM3AgM0iKCwEmqHK7nRef1miXzz6JST5k5etir50gCFIAiOX3AUkBAIUUQqaIyBhMlOlGMAIMoQzg9gGUMJQFSQDlOQbB7sLsDm5f/4+tXdW7b1rM0sqxQdInAotTbRV+9/8z+pEp3LnQV8aU/tz+39PSC70kkotly9MWo/9SY6XtNWIVuFYFsePHZHG78rTc+/fxz6jdQI6U1CWAOKwV8o+RgSRUYRhhBGUAZQnQIzBCqw+BrIYACgA994+m9Y2OF00yUnXLHnlWrvn3XmhVdsEpFVwBLKnWzMMuwIkxSEIFyQfEiGFL+2Pw6L+E/qVFGEYCmdjQABWrcaZ73wG8W/uPm3fPjnRbdirLJCI+IMAgoJ1EQyIpfyJnCaZ25fOqtf/36nrR6FpiE9iOPE0QeSBBJUFJEx0GGEBkE9qN+GBgDxhEZAZRgVvnSHWeVXSlddd8jq9d89vbzzi0V9FxVOXvTvu4VWUqXCOWCgUIBlENElMejQBXhzfLX/LK8m+qhkGmJoBGUAhU+ZH6Pf+U36aChKjAK7BR4QIW1ueUhYKsxbAH2MUOCQC55hTKFC/vW9r3/Tz76C4wXSoBn9pAnKIOQg1SBg1UBHQUZRBgAOYD3gwg1aFRK0NLW7Z7fsXt/39L33HTVeXGUP21guPviezb2L7OWJc7SFztAaYic8lRUEJ4nX+Ov5KcxeDIcQMsEDYAhx+L5Q/cRviYvpIjyBKoi7B4fZHNW415V+a6HtcBuYIBpEASy6gplCl/9t387/7SF219LEtWZO44MHhAmKQ2SIkxtwukoMAIyBAwjcgCfDwFZo0664LaHTu/64t1nnke9+MzP337uBanlTIFzvacIYA0UI0WZHhlCHwO817yMNTxEjRJAywUNQEyNHXIab7Zf4YD0EaE8EWMBYQqKss5Y1lq4R+EbKfJDYATwBMFxcvNW8ah0xK5iIcrcolMFkPMYKih9QD9gaNAcyFFyRGogI8AwRgbI/QDCKDCKyBAwzrQJ/ufWS1099pd86juXPX3z7r6nZXn89OFxWWYNrqOoOB5LmT4Z8Fp5L2ewlgqdtLKEIqv0QV6tf8/fyR8R8cR8zuEEOMvnnJXCjwO5ovtR7rQi30G402V8BxgjCI6BXH6DAvCMNevkz3//Yz8fj8kKkJTgyQjA46xoWwBEEqAO1EArIMPAIEYOkPv9wDhQmXr1BE/qextWnvuhW64+f/POeS9Yv2PeVRksLxcoOwMIGFFOhgThNDbwPvNiuhgmwwG0bEcDYMmoSwe/aG9ho5xKAWUaeOPZauCutCZf3rOOO1S4H1CC4HHIolUKwOtf84P+v/iNj7+RkVInkBNM53Hc4T+mIBXQcZAxiMeBySCSxkzoAFADkkbNQVv29/R84JZrTt+8e97131q79PkYLoosncZA7JRmUIQa8LvyB/y0/DPjdAK0fNCA0MEwn7Rv4S/MuyijTBcVEEAMVRV+oMLtqvIZ4F5gkCCYIhf8uALwyivvOeMPbvyv11MpekCZSYE5VCqAAh5BQXJgMnxERlAdBIanPg+hfgDVFPDMIg9tX1z87D1nPfPOh8684f7Ni5/rvZwLUC4orSBBWM5OPmReSB8HyHBtEjQQkTDAYt4U3cxOlhChzLBHVLnVeG4e7JObgRGCOc2NlGm48fnfWUg9ioAaMy3wUwUIj1IABOgCelA1gAFyIJ0skyCMggwh/gCqEyUjj86FoEKb+M7Dqwp/d8fVz9r24MIbSAo/tmsgXl0uYouRAkoryYBnyK0sZCdVOmgnGRGL2cIl+nU2y2uImHGni3A6lp/vqOpWb/nynsXyhTp8ExgimHPcvnk0dEWVhSCeoNl0qjyHSKNUC0AR6AFdgWJABEhAqqBVvKsiZnJF2+T7yQr70KgC1BBt+rU9P9i+oPjNe8666LaHlr30we2LXpqmnB1FIAK9nUorUoQIz/Pkc4DQbhQhw/Ec/2m+Yn8SxSAoM02BOGEl8HOnbNSf8ykbxg9wi4p8TOD7wDhzQuCWfRdWnlkR63QB4AlakR56fVyCahnoQFIBBAAPmBRE6iBjiB/BuxGIRoAhxA+QR/ungigDUmbIr9/03FOHHu77iVsfWPby2HIlQCGCUlFpdSmwWrazhnVkRLSjjIg1ej+djDBML5aTQ4WGXIAiazpXsgb05wTuxvNFKcgnpcBawDNrBXL5jcrPveBrpTdee/P/Y7wYFgFmJzlsJmSYlCPkqOSIVEGGER3EuwF8NAwMoYxQYBio8xR8/BsXlbfumn/DR799/ktG6/bFkdJdLihKe6kgvEBu5m/lddQoAgLQNjMaAINHEd7t3sPNcgNFlJYg1FX4qop8PEr57Oyc5wTOD0BhWHuBCFBmo0CBfLKERykCWFS7QHtQVkNisIkH6igJSp2IKsgAogOo3U9eGAYqCOPAKEf59HfPP+O9X73iVQ9unPdTKGd2laA7VgCU9mOg8dyMJQOEduQxdDDKGl3LFxtB0yKUgijXC3q9wmbj+S9V+ShwL7NG4Mgh7ZBefAiaNjQDt2jLoQCCMjllMuYBy0EEvEGq+VTQVIARjB1Mk6jy/YdPWfr7n7/2RYPbup9jnfbP68ypp5YkAxHBCIiAESaJIrQ2RXDASjbgsbQzwVPWYQytyRtW55bfENU3E3MzVt6rwteAUYK25rIesOQ9gANSggB0qiYJRxOMdwhd1Wph2cM7F527bseiC/YNdS99zXn34s8Xxusx47UCo9UiY7WDFTNaKTJUKTFWLZB7g5+ozINXEMAYpoJIaSUGWC5byLG0s5SYFWygm4wEh0VpNaIAFKnzYtCJ4h6FD8WpfBTYR9CWXDQEL7/qni6qBfukQRMEAhifgeZDo539D25dcvEjuxZfPBEgfaA46/EIAnQV6/SUapj+IUDJvWlU5i311DbCZqRSYvhg8NQKVA4G00SNVYtU6hGqNCiThEPdkIgy01KEjEkCdDLMyaAzfHzWowcQlCpgESLAoTwV3gtJDtZAZJVpJTQIXHywfKS/Jrl8RHM+DKwjaCtyyQuV7/7du14ex/kVQJ2jBYGgGJ+j6IGhrkVrty29aP2uhReN1wpdzipGPD+KAIgCIAKCYmSyEEgyO1GOWhJRT10jfIbGywxO1ESQUU0d9SSi2vh5g+pU6BxeAJx4ECUI8xnkeXyOpbINUF4qH6OL4RntanJVMq/MFEPOKPO41byCGmU2s4Zv6I8xQgcRyvHIvFBwGacu2sS+4QXsHemd+VsbBIADAh8zKv8KrCVoC/K6X97p3vuWD766GKVnAylBcDibZ6joroG+ZfdvXnbl5j3zz03SqGBNjjHK9DgUPiL66PsGARTqWcR4bbLrqUzUwSAaHi9PVImRarERTlluSHNLltMgAuY4u6AEYTWb+DPzc5zH9/E4QKhRQhFmmjBzFMHgialiUIScb3Mtv+3/hSH6j7mz8SqU4grv+Mm38dwLb2HrvlX84cf/mLs2nk/BKSfJuCqfVZW/B75H0NLkNW/a2fH+t3zotUVXXwmSEwQA1mcounugZ8n9m5dfuWnPgguT1EXOekSUk01EMVMloiiC9wfL4FWoJlFjHjRSKTJaK07OhyZq4nPjOC7NLKrgG3XUUZxRBFCEFOWP5Dd5lfwb4/Qx23UwxN/r2/lnfQsllGNRqQsvvuwW/vINb4U8gtIoX/r2T/LWD7yTcgFAOYkyVfkE8A/A7QQtyWW5iVHKIEoAhIBBNN83ETD3bFz19C17+8+vp67gjCdyOc2iKuQHiyMJYIzSWazTXaqxfP5kGOVeGt1NmrmJspMdUKV0MHgar9WDnVESUZnqkNIccqBb6pxu7ieVInAokWYrT8xF8j1U38KxEgOj1S7II3AJoAxXulGawono64BXCHxCkb8FfkDQUuR3fmv9oj964yd/puDSMqDMTYFRj/Hp0Ejn/B9sXHHlwzsXX1JLXCma6mDakYgicMRxXONVlCw31NKoETjVg4GTxAxVyoyOxfxK9Te5OPkmtawAaQZpDl7BCFjDbNLBKJ/hdfym/wfKKMdCAVXPG5/3YW648lNs2H42f/6ZX2fX4AKcVZpsTOAjxjQC52GCliB619+eyryR14I65p5AVLF5MjZe7vnBppVXrtu2+NLxetzlrMeIMlsdCqBDCwkopMScyoM8N/8spl4nq+ZQT9BaSj5Sw+8agiwHYQYICDPKyKH/XWPqjNHFm/3HuIOnUUCPq8OsZ1CMlCQTjNAImRYyCLzHO/kbYDdNFYhu/aOLyM0NgGOOCQHj06QeFddtW3reDzYvv3pwrGOBszlGlLlL8Ahnyf2ca+6lJFUQQUUoZGP4u9eh1QREmHbqwecgwkwQYLxeppoUEFF2sZz/0F/kq/pCIkBQjpeqtHTHK45NQzvkr8cH+AAwTlMEolvecTXePwewzClhk2z9jkVn3b1h9bP2DXetEFGs8QQAQoajSIUCdRTwavixp9/N/Pm7IQcQpp/C4B6ojoIYppUo2Ix//tKNfPS7z6cjVoboY5QCRQCU2cpYEOFuQd4BfJrjpB30qNIjUGRSAowCBwiOiejmt12HckUImjkyhxGf7RvsWXz3+tXXbNi94CJVcNYTPJbHoAgAuTe86nnfY9GqPaAWVJl2YiCrw96tkCUgMu1B8+7/eTP/dut1dBXAABZlLokyPm28vAu4kyfgu+jP+rjCC9dV53GONSxVZZ4IJSbVUYaBnSiPqHKLV24DdhI8LgdSBhWC2UsAmyfVaqF8z4aVz3pg67Kn1ZKo7GyOGJ5AYPAcouA9ZB48M8SDRFDug4HdIExv0KhifUYMRChzURLxMtDnCfKP3vCXwABTagu4uL6AnykrL1FhNYA9+iraQxYBZwDPFvhZa9iP50uqfBC4leBxg4bZLTwPs2H74jPvePjU5+8f6Vwa2ZzI5bSiQKHcA2NDkNRAhGB68xboAv0d43mJyeV3h5awTubzK7HwUx05HQoIx0mZj/B6EV4twmeBdwL3EjQ4lCIgBLNx2J8MT6wr377u1Gdt2LXgYo+Y2GW0sEAVrJ0Mm6QKCDMgUMBzbjqPTxfnMaDQjwflhEUKP64511W28Bfe86dAEoJGNEYJZhPjM/XG3Ld+9ZV3r1/17PFa3OOsx4knaJOwKXXAqIN82rfQAgUMZIuFvB9B6UeZPgpi6Ow6g7chXK7KzwK753jQMMuCJnQxBwa7F333oTXXbtk7/xwjvh2PyULQRAVwMeRVgukPmXQZ+D4gZ8b4FARu8I7Pbt3OK4GtzFEOJAYlaP9ZjM+NTHQxT7vnkVXPHU/izsjmBG0sLkK9AgjBNBHIlgq+F8hPUq55Ll9R5n+yUW4A9jAHORQHCG0tPHg5ONzV/50HTnvBxj0LznPG0+4hEwi4AiggBNPBQ7ZEyOcBOSeNerC9XObm8T6ElwEZc4wDNQTtyfgcFX1w87KLvrduzbXj1UJvGPbPEgJYByjBNPDgeyCfD3hOOvWNul7ht4B3Mcc4wBC0H5cnlWqh4ztrz3jBup2LLjNomMXMNiKNOkGBAhFkiwQE8DSNgd/0yufn2g3TDhFBlaBNiHpsnm7dvXDNtx847cf2j3QueZIbloMgUMjmgZaAnKZS6Eb4A+CVzCFhPtNOrM99buWuB0951l0bVj0nz20cu5xgFlLA+xOd0QQKWgDfJ+BpCQIvF8MzgW8yRzi8KiC0tsBlyeh4ufub9591/cbd88+LrMfZnGAWy1NOTCAKebegMa0UNCaHX8wM95cMw4CfC0dnHlVDSwvPxmzbveDU2x444/qBkY4lc6GLCRTSOgjBU6WgFnwXLUUB4/mJ2PNMEWoC6wVuQ7kF+C6zkAP1tKbAaK6ov/fh1U+fuKfsusybeI4M/AP1kNQAIXjqNAZfAjytxgJLMwWFU4EXAG+zwje85W+AzzGLOIQcpQWFBzBr9Tj+9trTrntw29IrrPU445kDAjFQG4P8hL4qINCpkLGAp2UJjzJeeY5mPAflw1h+HdjHLOBAUtBwsWYribL64GD3gq/df9ZLdg70rpljXUwgQHUMcg/G8AQCzyHCIXroVUtCuxEA4adRzspyXgVsoc050DrQRcsI85ituxas+fp9Z71spFrqD0/4zzEikNahMgJGCB6HBwxoGXwn+JKgEQ2iQB1MVbEjoBHtS7kisnwmHeF6YAdtzKEkBK0RMqLZ2k0rLv3W2tNfnHsTz8WQCQRGDkCeghiCo3jwXZD3C74TMBxBATrAi5AnoAJ42pfhQp/wntoOXgYk7dzRJDRXYHyuKnz3gdOfc+/Glc8VVKzxzDGBMVAZhfFhQDhMoIBAtkjIFwAG8I16QuoApa1pDoWFvKi0hJ8F/ok25RBTQb0CQnDy2TxLkjj6+n1n3vDwzsWXOBOe8p+TxEBah6E9oAoiBEeFzFIhnwco4PnRlFlBc8hzfgvhk8C+dn1gswIoQTNCJh2vljq/es85r9i6r/+MyOUISvMEXs0T/7gIGAGEaSUCSQ0GdkKegDVMOwGMoGLIgRzhaAYQlJajU7cu9wM5c9UK8bwC+DfakEO0gtIE4VLM/YM9C2/9wdmv3DvUtaz5ty4HRjwFk/B4cjFInkEtA2+YPgK1MRjci9brIMcwVJCncDONKNiMOB2nm1E6UQ5RFKFGGY9tzVuX+wHPnKbCq9s3aKwbJ8sITiKX1Xbt61/51XvPeeVIpbSg+evLQeojTu3cwrVLv0nuHQACIAKAihDtSknv2AtpBiJMH0VrCdkdD0Oag/Ckqt6TAsLxe3n9d3mReRuS8ShLTpUyb7cf4CE5nwilZRjI5wtICBrgfIFlwI42DJp0hEyU4OSIstq2ifXlm+8991XVetzd3PvKAhFFAGs8TnIilxIhAKgCuQfvIVd8LYPUQy4gTB9jIFG0kkGW/8gQ0zTDe0U4fmWp0SEKemTQVOjAkaAt2M34MuAJoFuFs9oyaP7s6y8e+ZUrv5LHJo0AZeYELq9v3Lb43FvuO+fHk8yWQsic1AfgAMXIZIkoAGnuqGeWSlZmODHQMUYuRUgydKLIPZr7Q4EjAobpZQSt1EEVjAHhyRnhqfJYHkvIcShCSxHIOwED5AQQAYtpQ+62HSuSt6jUgRKgBDMWMg9tWX7xN+4/4yW5N4Vwncz0E0DMkUHiveD1YBnSzDFSLTI6UePVmNFakZFKkbFagX3VLq5KDvATV27Gz18GWU6DMEVAhBkhgg6NQZaDs0wJDGhJwDMlUKVIG3IlmyagM3p7X3jaP68/sGnFZd/44ZkvQ8WGZ2ROIEhEpwoE5VECWW4Yqxao1GOqScxItcBwpcToeInhaolqPSLNLVluJkoeveHFCFQF8B72DcHC5WCEk0IEkhS/fxiMcJhAAMdhAoE6bcgZpA4yDgjB9BIU45O1G1de/s0fnvFSBGueNGQCEQA9LFR4tEPJvVBPo4ly1DPXCJShgyEyXn60M6klEbXUkUxUmoMRkEPVIICzekTzYBAwAjsH0NU1pFQA75lx1uB3DaDDVTCGKYGCGlDhkCATwx7akJNIEzSvAIZgehmf3rd+1dO//eDpP4aoNaIcEkwFCOZgGcWrkOV2srw5GBqNIBmplBrHXaO1AuMTVanFjNdjkswggB7R8RyqQqTHP5Sv1sk37sadtwoEUGaOEbSekm/YxWMF4kEAJUBAc4aTAzxAG3J33r9UudZVcLkhmM7LMev3r1/59G89ePr1ImrmYMgc6kamCkBVUAWvQiWJGKsWGas3AmSi4smjrkZ3UqSWOLwKuRe8BwWMHOpSIqtMO2PwW/fh+7swy/ohzZkxIuTrd6JDFXCGwwQCKJADjkBAPWtdDztoQ871wKc3Xjj66gtvU5ICJygQYGom860HTn+xiIoRneUzE4DJV+HQ5zS3jNViKvVCIzTGapMzk6GxcmMoX6nHjV+TZpYkE1TBmENBIgCiWHOwTmI6eiX/4VYoxpj+rukPGxFwQr5xD37THp7gP1zgQaqgRUDDkXJ9Fx+nTbn6Ltj5YHGIyzUlOeFONbB5/cGpwT+CGNFZtBoMcth6cJabRkjUs4gks42OZGi8fLAaQVKtxxMVUUliaqnF50fNSmSqgNgpLcPI5AOUd2/AXXQKZn4P5B5UOWFTKZpv2E3+4DYahODxKJhRxfdJyFzYVjX8D23KVQ2YNX6ElAxwIWhO7AvL1m1efuFEyLwUsEa0PWcmRhEUr2bq2MqQedvoQEarxcbcZOJ1alZSaAziD75muQCgOlkcdszljIKhfdjJeU1253rsmcuxKxeAtZDnPCUiYA1aT/HrtpNv2QcCiPAEAgN2DPIaaBHwzFmJ5y86lrOXNuU6lkOn+CGYCpqnLDzxv2nH4rO+PhEyCq4FQ+bILS4UAAUEIVeoJVEjMMarxcaR13C12JiXTA3jSb0hb6wGG3IPHD4zMYo1yqxiDGQ5+Q83o/uGsWuWIH2dYAAPqAfliclhSZvl+O37G52MDo+BtRyDIAV3QEmXC3ORCkSGr3QY3kMbc/McfHn9paOvPueu8c64WsIbguPksmT77vmn3nLv2a/Mc1O0xrfMarCgGFEUoZ5Z6knUCJTqRI1MBcnkcVeJetZYC278fJo/9u+VwiTTCBXmBpFG+d2D+P0jmAU9mGXzkJ4OpBhDZEAB1aN+D5Bk6HiC7h/B7ziAHxoD5XhCJrBgBsB2QN4H5MwdBiTlIfH8dAI12phLKvBIpS9PE7ePAguBnOMSrvrfvb9/2c33nPeqehp1nKxrZUSOnJmAkuWWqQcSG6ExVi0wWikx2JiZFKgmcWNuMvm8iUUfM9g/9HrUzCSwBrzH7xpolJQLSFcJKRehFCOxo0EVradotY6OTdRoBerpVGIbMARPgdulqBN8F5Az+1mQcYh2aJlElgF7aWNO1tNQNPk+wAIpwTGHzPBoZ9/BTqZSj3uczWfkKXgjCnJoNVhVSHLbWAkeO1jVIsOVIqPVwkQVD1YjVLyXRuUKqiACZqqcVYKnMmcRALSaoON1GgwgQoMCKPjD/gt3lhMQCJBBtFVJlwq+F9BGzT4GUDAD4HYrkrIS0f8GuRbY2L5B42lYu/b0PZc9424lNxyDwPqsXi8Uv3rv2T8+0TEsjI4zZARAjlwJFhQEBMi8aWxs1dJoagg/GSgjjWF8ufF5qoMhzQzZYU/BGwMCMDXYNwTTbSqxn3hNzxBMJwFyiLYr+biQ94MWOURpX8IkDzIG9gDYEaXBAHCqop/0yHXAAdqQy4WG/3rw9AOXPev2cepRDHieWGDU+8zYiZB5xc6B3tWxy580TI5+eNFMXaeSZo4ks40aqxcad3KN1A52JxOv40VqU9etVBtPwYPIVHHke2OUgiEIZjehwR5Q7DD4TvBdgi8CBlRAaDMeJAVTBRnVxiseMBxB4FKDfsyovAyo0GacUxpKqe73uR01sOBJgiYQnYD/+v1n3rBx98JzYpcBIKJHBIki5I3trIMlVJP40WtURiuNMGmsB1eSyZlJNXGgNCgcCieOuE4lCAID5GCGwAwrCKgFDO1FQXLAA8ok06jHJXCtyfWfXcYbAE8bcXFdAfjy7efVX3vdyr1nrN6+iCTiMQJB1OCy2n0Pn3rV+p2LrywXEqZQSx3j1UIjOMZrcePZkpFqibHGVfSFxtPxuZep4AFVMHLYMb5RjlEQBDJVUySjPcmhOhaZ48aJ2gC8gzbiakUeFTu/Gbhgzv9fVxFQAcyhH9c6pfruu3549iWfvf3C68brMaNTYTI8PvnAYpJaktyRpIbcHxkkIjSIKJGlBQUGD9SJKfB4shxUAWFGGQEB9InHgxOlyFN+KJcjWHIyHIKnbQlzgtDwNlFZD3yMNuGMClP4yNev2PIHr/9Uwuy+NFWYZB4twQAZUAcSIptgzSjKAdBBusoH6JAD7/i7Z57xpW+f87bhasEAiDz+avDUFfRBGxGgoh3s4HQqdPAYCj09OZEDVWaMAEkqZDlPqFY2pIBwfERgvOao1CKMKFMw5NQoUaMDIWgDoqLvSXLZCnybNiDn3KBM4fzVO6OP/+E/vpGx4nKElPZ2eJAI4B8tLznoGCoDGB0hZQjPCDCI6DAdpWEg5TB37Ty9+6ff+fJbNOOyMC+ZvQTlaKqQ5cJvvWGQs9ck1BNhpkRWueehIht2uCdZQReeCmeVBzYWeHBDTOR4HErQVjYoPBvYTotzh1+1/tC2JemB8d4t/XZ8Jd7S4g4dbSkCCJMEUESqwDDoOCmjwADIAIYBhnSIROuIJkDO0QarHO7OTSvNL3zs+vdIzmVxCJlZzWM4mgLWgnUCGBBhxhgFDNooZVoJGAMKqADa5qdPwRoR3q/Ii4E6LcwhwuE+e9OlP/yZF996pdStAErzCMBRQWIapQCSAOOI1ihQRWQYZB/4ARIGQSvAOGiVCE+D0tDPcfm/7/6pt2d59Kq5+7R86GjiWImsoqqgzBxVQJGpmk6C4owigKgSzALKtaL6V8CbaGFOvHK4j9125fb/88LbtiF6CirpSR7EW8CACkKGkjZeY6kBQygDCIP06jCiYyhDGB0FasyQt7//xT8+Xo1+p7usKHNRoAjOeVwjaGhbChijiBDMJsIvA3cD76dFOYQj7Bvt8A9tX37XWadsOYV6NF1dzZEdCQqgh90AmYKMgQ6BDONLI8DBGsDoAGUdATLAA5BwUrzjo88/7QP/e+k/9HaqUdpLkgkCRE45QYFC5MA52p41IKJMJ++FegJewVmIoxBmTfCXGLkXuJsW5DDC0f7iw1ev/dff3XxlZHQlXpLj3whXATFTr4KQA1NHWTKOxiMIA6gOYO0A3Z3DoAlQAzJawL++75LyP3/y0g8sXswSlLahCEkGK/r34L1lx8D8E3zYM1DAWW2UqtC2VLAWjIACwolLUqFYUC48c4zOzpRde4us21zGWbBGOTkCEfryTD+wa508Bxigxbgda3mMHWvPSn7r515w8xnLv/jTjJcdQgYIU47qTgyCAlNBIXXUjTVCRMwBvB8kYxiRCqpjwNgRWZLkUBmg1Xzq3vP+on8+V6NKO0kz5bVX/y8/+4J/w6vwL196E//zvRefwG3MgSrEUx2N0r4UsFYRAVVATjxkli+q80e/uJ5nXDIINmdsLOaTX1jKP3xsJV4FI8rMC1TBGC5Yfq7+A/A6Woxbfq7yeF79zms2/O/v3PXF1Wt2v4jUxkA+WeJBKogMgg7hoyHUjQIjiA6jdgCo8yhLu/n5v3rda9bt7PqleZ2KajuFjLC49wC/cO2/M3/eThDPm174L9x83/Op1EoYowTHTxUiB1Gbz2hQsAaMQK6cEK+Cs57ffeNGnnH5fsgsJI7Ocs4bfnIzewciPvDppZRLnFzBa9Nxvg38My3EpeM8oRf//ltv/9Nf+d+9jNpTERlD9AA+HkRdBUiBjFlmy76+0773yLK/6i0rqrQXgdw7kiwGkwNKksZ4b0A4AYFzOlG0PWMUkenoZuC8NVWedsEQeAteaMgEYuGFV+/nEzctIc/lZB+hhc4m5h1JVb4F3EeLcFkuPJlf/8sf3wRsYg5QT4zjX4pllghKu4mssm+kh7/5/Fv55Rf9UyNg/v6Lb2asVghHZyfc0SjOKr7dt86sRYQTHtKoh45yTqHgwXMUoaOUUyp4RsctJ1GgYCzzSt3670bluUCFFuA6ugim5J7fRvT5tLHYKV+4+3nc9tBVqAqj1UJTlwFUBQARbf+gcUo9EdqRGosKdGYDRMwjoYCgJ9DhwZZdRXbvK7B4aRUS4RDPI1vLDAxbirFy8gXqudKj7wbeSgtwXhUIRLka4bdpf41gqdSLyNT7Zq5YR/bQ+3buqiIH1tBmBDUWUOLxAboGNtAxvJWSXs8YBU5E5JSde2Pe8z/L+f1fegSJcyYpe3aXed//rsAaECFoEoU3Gy83AV+kyZx4Ya5TodOL/iNQYpawRpu+mPDMs2/nLTf8Lbka/v7zb+FbD13RnmEjUIg9SpsQwYtF1FMc20PngY2Uhndg8xqIwZkMPCesVFA+/uXFHBiOecXz9tDVnbJpa5kPf3YpD28pU4iVoKmMN/q3InInsJcmcmqZ88aL+q6OCheqMA0Cr0JXqcrvv/JdLFvyCAj89sv+gtf/wwcYrnTirNJOBCjGCtoOAeMwPqU8sp2u/esbQWN8hheLNxEGTyzptKw3i0DklC9/ex5fmag4gmodIkcImRahcHqk+hfAjU1eplHmMoe8IK/ySypMk0CByGV0FschiwDoKIwT2RSl/YhAIVaU1qQiYCwmS+gc3krnwEaKY3tBPdoIGAdTRJRIUqaLyGRnowqqQkdJCVqHACm83iBfAD5Jk7gcYa6q7qdHO/UvSiWmMW8DIzAw1sW/3/xGfunH/hFV4X23/h/2jvYRGdpSK/4TuopBxWKzKh0HNtF5YD1xdRDUo8aBGB5PbFJUpj+MRZSg9SgI6J/1WfkGsIcmcH2WOcsu5I9S5QJVgmm/JRg+/I3X8I0HngkKW/YvI7KKtHNHo7QEFYuKIUrG6BjcQsfAJqLa0KHhvxieiAAFk4ICAhB4FbwHY8CIMtsI4GH1/lz/FPg/NIHbnytz0f7N8rwFp+ibUGZAIALWwMa9yxAgsiAA7Ro0BW2JFWUQotownQObKQ9uJqqPosagxnFslILJmBTkXjAC/V1DDFV6yf3sfcBU4MahnfIZ4NOcZG54pzDXdPTTOREyfw5EzJhARCm42RGahQhQmmIqRIgrA435S3loKy6tomLwNuJ4CBwxowlLK2P81kv+mqed9W1uf/jp/PlnfoXhSs9sDRuZt1L/DOQbwCAnkZu3kjnH57wJ5RKC4JgoxYKiJ3uDzFhEtTHYn1xR3o7N6/ipAf9TFZGiBJU6/NTVn+cl13wU0iIvfuZ/sm7Hmfz7La+ho8Cs5DPOUOG3gd/iJHJ5ztxiOcegv6UEwY+mj3Y0Hj1J8xdvY0RzyiO76dr3CMWx3YetKDtOlCNlUhBFCQigFlAil6DKrCaqb/bCfwPf5yRxijJXjOzDdHbxp1KglyA4RgKNjgZlxqiZDJhiMkDf7g1UNhygo7bniBXl6aE4MiAoxfCZ79/AFWu+z0Vn3M4P7n0hn73reooRs5ooJYG/3LNIrgUSTgK3d6EwVxQjXhlV9cVKEBwjBZGZu8rH2wg1jrhygAWbv8Oy9V+gsm0F++rnoNaAGKaTAFYzILBGOTA6j//3gb+lv3sfB0bmU88cziqzmQoIPHPeADcC7zk5M5oB5oRinT4VfZtaguC4OAvOgSrTRPDWocZRHNnFwo3fYNGGWykObQeTomY1ahyQMt0UsJJjhGAqbNLcsvPAYowBZ5S5opDo74nwOWA3M8wVU2VOsLxVlbMIguOgCnGsWKOATEPARCDSeP5l4fpbWbDpNuLR3WAcuBhEmFmCJcOhKIKghAeMFWOZeyyr6qP8LvD/mGGuPs6sVx2Rs3qW6VtQguC4qApx5DEG9EQCxsWI+sb9Y4sfuZn5W76DrQyCdRCVOFkUsOKxeDIsc1egHlyZ/ytePgjczQxyUVGY7Uq9/F6W0kMQHCcF4miqo1GOi4ppBIzxGT277mPJRMDM2/Z9TG0EXAGiIiefYMmxkpOqRZjLAhHK4vhD4GXMICeO2U24Jkv11TwFQaAKhUgx5ngDpoBLK/RtuYOl675Ez64fIGl1KmBKNIsqOMlxkqEaEwSq+lLgBuDzzBCnqsxahkhy/hjBcYIUQEGEOSRQnepoLOixPAPjYqLaKAs33dY4Iuva8wD4DGzcCJhmUwRnPFY8KCAQBAp/LCq3AhVmgEOF2ao6yMvLffpcPE+ZKtQTwTmInFJPBfXh+zbm1tEZGDmWFeUB5j/y3YmA+Sod+x8B9eBiMI5W4iRvFEEwxSiX1or6U8C/MwNcvajMRuOdUuqp6++gJxYyWS68/Ln7eNWLdtE/r86mbR188H+X8937u8P3oc8FCoVDM5rHX1Ee3cPCTd9k0SO3UBzeCgjYCBBajQKWyaBRDgkCl/Mb1ZJ8Ehhimrk0FmajZQO8rlriIlWesloi3PiSXfzOLz5Cgworlla54txh/t+fns037+qlVFCC2b7e7LEWcn/kinJ5aBuL1t/C/E23URjZBcaBjQGhdQmWvFHKpCBQAZdyWlfK/wX+imnmukaYdayntyb66xiesjwXFs5L+ZmX7gAEEgNTip2TP37H/d14bzBGCWb31pkxkJoCeN+44LKxorz5W7jKAJipFeU2oIAjx5ATBIdTAUHfIiofAfYyjZww+9QtP2XhTE5AlsOKRTX6ulPIhSOosLLxczn7Bg3GEMxSquAKESZy9Gy9n4WPfJX5Ww+uKA+BmxrwtxXBkGPwIATBERRW5OjPA+9gGrkMZTYxnl5n+FUVTogxMDgSUU8MxY4ccg4RZWjMMV41GCGYzSwsGl3H2V/7AMUN9yFp5dCKctvyODIeTxAY4Ze95z3AbqaJQ5lVROQNKnoKJyhyyqadRb7y7QW86sXbwFjwAk4B4dO3LmJ4zFIuKsHspAhxDOft/SKlka+B6W0ETPsTXPg65ycWLDoYNsAfME2cEWYNL9Kj8ItME2fhbz6yEms91z9rH8VSxsBQzEc+s5z/umkxpQKzXTg2M9ARZ+BKoIbZIibjCQWB8EaQfwZ2MQ0cIswWBm4EPW36gkYZqzre9i+n8YFPL6e7K2XfQMyOPQWiSBEhmOWcKJ1RFVSYTSJJEeGJBMFihJ8F3j5NQcOs4A09NtdfUqaXs4pX2LSjiGoRYyCeEyETKOAkp2xq4IXZQoFIwtFZ8ORU9Y058i/APk6Qy5RZwRpepjlnMgOMTIbL3BNYySnNsqABIZYMEZ5QEAisMJbXAH/PCXLG0vb21Ijnj+mb4m5Qz7QIAgUMOUVqgDCbxCYlCJ6MKCj6i3sXynuBCifA7VlI2+sf4kVxlcvUM62CwOIpzLKgUaAgKUHwZFTA5py1bDs/AXyIE+CWbaetqSBi9Jc80y8IDDkFnX0dTWRShCD40bzTX1DkY0DCU+RyR3tTrhR4LtMsCBQQ9UR+tgWNEJERBMdEeRpWnw3cxFPkMEo7k1zeCDhmQBA4UqxPQZhVnKYEwbEynp8/oaAxnrY1ulNWdyzWV4ow7YJAgbJUEfGzbkZjJUNQguBYeOX6eUbOA37IU+B6RWhXbon+dAI9zIAgUKDLVTEos43B4yQnUxcCJ/iRBApDXt8A/CpPgRvySjvKPB3G8JPCzAgCVeiJK4goKLOCqEd8huCxeDKC4Ngo/DiedwCDHCennrZUynhuvcA5KDMiCBTojiogHlRmQcB48qhIre8UtsqZ1IcjBCUIjoUKKwuZ3AB8hOPk4kxoR2msbxRlxgSBBzpdFVBA2jZg0Jw87mS8dxXjfavJu/pIBgyqIEIQHBNRSGJ9AyL/ASjHwSUF2o7COaI8nxkUBKrQYaogSrsRnwNKVuhuhMvYvNVkhS5AcXlGJAYhIgiO09UevRL4HsfBeZR2EyfyijSmLMqMCoKSVAEPWFqfIt4DSlrqY2zeqYz3riCPO2BqNgOgBqwFhDYQqAoAIkqzCTjNedVxB43mtBeRUhbrT4oyo4JAgRLtcHSmiM9BDPWO+Yz1r6HSs4w8KjV+XHzG0Ywo1kDuQQhaVT0VjAHvQUSInTZ/Nd7wciPydmCIY+QiK7QTD89COY8ZFgQKFLUK+JYOGDWOWtcSRvtPo9azhNzEiOaYPOWJiIA1Sp4LCEELSjLhGWfexY3P+RC1pMQ/f+UXeGTXKURWaSphdZZyHfAJjpHLUtqKi/Q1npMjCCKtAdqCA36PGkeldxVj/adS61yEGjsZMD7lR5gKGoIWleXCgu5B/vAn3snKFWtBlN7SCG/8139GVZp6jGYE6jV97XEFTVpT2kWlLIvmwbWcDEEgEOU1UG2ZgBHNyV2RavcyxuafRq3cD2IOHZEdCwURMEYBIWg9uUJXcZwlvbugVgaXsWrBFiKXk+YOoXm8h2I31xRgNbCZY+B6umkfRZ7rKywRZlYQKIIViKUOSEsETBZ1UOld2ehgklLfoeG/ZhwPBYwBawkrzi0qsrDtwGL+5/ZX8MqrPkFWL/CRb76OauKIndJs6unLM7kO+DeOgcsToV10JPqaNAKUGRUECsQCJUlApWkryoKSxl2Mz1vdWFNOit0IiviMEyEyFTQErciIknvHn336N7jl/udRSwrcu/k8Iqu0iiTSVx9z0CSR0g6sl9UKzxBlVlIVVMEYJWg+VShaT9nVOal0MkTMRKWlHsbnncJ43yqyyRXlxo9PB0GxRglalzWKqvCtBy9FBAqR0kpEuRzkHOABfgQnKrSDzPI84+mfrYO/3EMhUqqJUIxaYmc+bJzZlLKtA4YZpx7yFGze2CA7sPAK0r7FZIetKE8nY8AaQAVQgtYkohRjWlWH8bwQeGDWfE2AoC9VZp/MC/O7Bnnri/+O5Qs3cfNdN/Cxb78SmrpZEqhCbNLJjsYLM0chq4MrMLz0Qg6cex2bRq9idE8nkWSYPGUmiIAxoDx1QaBWX6xe/hbwPAnnhZZnhGUqXI3Oxm4GfuY5H+alz/wPyGIuXXUfa7efxZ3rz6cY00xhRmMyimaGgkZzyBKISgysega7z7iWoaUXYYoF/P3SCBixyoxQEAPWKKBND/R6ImQ5GAPFOBwftxNVnqY5pwPr2v5mAF/gOnL6mIVEYGnfLvAOkhKURpnXMYQSND1oJKUgCSBMG59BnuALPQyccs1EwLyA4YVn402EzevYtIpoETAz/MVnYG1zj85yL3gPF505xtLFNUZGHfc+1M14zVCIlLYQFK3R639k0LT6QDDOReqi1ymzlMLHv/NKLjr1Hnq7DvD9e6/jro0XEzmCJlIgIqUgdVABmY6ASck6+tm36ir2nH4tY/PXAGDyFJvVTvrZvzWK0hzeC3Hk+fUbN/Gya/dQLKXgLXf8oJc//MfT2La7SBzCpi0oXBfJkx+fOSdCK6sYXWAyns0sVYiU7667jBv/4QMs6dvND7eeS6VewFklaC5HRsQJdjR5CupJuhaz99RnsWfNsxvPwogqJksApVmMBRGaolqHN7x8F69+yXbILSQODFxx8QF+942GN/3J2WiYU7YHy9N3bNCVwGaegNuxSWll3QvlilKPLvQ5zOaw2bp/KZv2LCWOCCHTAhSwpIgmIBwnfTRgaj0r2HPac9k7ETAHr4kRzRvdSytwTZrR5F7o6fQ8/8r9gEAuNHggt1xx/jCnr6zwwMaOMKdsA6p09S3l6U8aNH1LaWmurNf7nFkvsjpRBM2ioHrYew9RWod8CLQAGBALGECefEUZqCw4g12nP58Dq55GvTz/0PFYi9AmdjSqUIg9pWIOCEdQKERKuehRpU0ErsSLgY/xBJwr0bKMl06vejUzJAjUAwomAhuDLYEpCJGF8bifz5Vfy7xsiFXZVvrzfZT8IGBAYkBoUIW8DjZmdMn57DrzOg6suIIs7sLkSWPA33JUcFYx0pwHEQ8MOR7e0sGpp44Chkc5z/adJbbsLIY5ZTtRno5IHzDI43AgtKrhbs7sGuEMhGkXhIARIOqCeL4Q94DrBAyI0DDEIv5KfwUBYvWsTjdxcf1erqp9k/OT+8HXwAu4AgMrn8aeM17A4NKLyKMSJjsYMJXWPhq0IAIKCCePCIiBf//vFVxwxihLl40DAihp3fHPn1jJnoGIclFpE4GwMk64CPgaj8PFCS2rXOFpCDFtJvdCmoM1tNLdRMHhx2LdUFoO8TzBRICC6mGvgAARk3IMD8VruH+iPtXxEi6r3cWra//N6kUR209/IQMLz8Xb+NEV5XbgDJMUkJM/l3xoU5mff/u5vO76XaxeMc7wcMz/3rKI2+7qDSHTbhSTW572hEGTW1qWy/Q6lfa7TqYUJ5yxZBv7Ruazf7SHglOUoNnUgzFQPkUoLwOJQD1ozo8kQKwQA5lEfKP8NO6YqCsXJTx3YY0uWyfNarQNBWMUEVClKQqxsmlHiT/8p1MpxlBPwQgUC0r7CbzRFwB/wuNw3mirzmfmqeEStL06md6OYd792j/kmnNuY/2u0/id/3wXD2w/vQWu9g4hYwvQebpQWACaN+opMUBJIQe+sTlm+5DwE+emLOrISHKhHRx+dNZMkdOJAq9CqaCI0KYChQuNyjJgR9tcqpkZvUyURbSRegrPPe+bXHPBVyEtcdqp9/CKKz/N2m2/gSIIStCkkClC95kQzQPNmBYWsBY2DkV8+Ae9vP6CIRZ3tk/YWAMitAQjStsLenOjzwD+i6O43CgtyculiDraiAiMVLtALbgEUMaqXShB0yiYCLrOEKI+0IxpV7Cwa8zysR/2cONFQ/QVPKmn5VmryPQNaIJAQC5/3KABoQWJiD6DNlOI4Btrr+bfv/RLvOiyL/DDTRfxn996Nc4QupkmUYXOU4S4HzRjxhQsbBtxfOahbl53/hBGwCstSxWcDUdV0ysQ1SuACEg5jBNVWo0aehQuF23Db8VTx99+8Rf4l5t+niQXjEBkFeVkCzSHwgIoLQbNmXGxhR/ujbljR4lrVlWoZ0IrsyFoplkgcImKLDx6TuMQodWo5yKDLqQNGVEKDlSF2CoAStAMxkF5pYAAnhkngBG4bUsH5y6s013w5J6WJQLGMG2CQKEL1UseGzSqtJoCPD0BEdqXiBI0j+ZQXCJEXaCek8YZ2Fsx3LO7yAvXjJHkglfwKqiC0jrksK8LFmG6BMHVwOc4jKMFiXARylMTBArGQmERIJx0kYE7dpQpO09sob+c0V/K6Yg8CmReaAUi4CzTKghUuIijOBVai5euOnq28NQFgSkz2c3kzVkbHqwa/vfBbkTACI2gOXN+nYsX11jenTbCxitNZ4yiTJ8gEOV0PPOB/UxxktNajC4BziAIjpVyiDB1xYyABTxNYQQQUCZr77hlx1iZ7+8ocdXKKs9ePUZktHndjYaOZsYEq3CsOiJocLQUUTlPVQsEwZNQD3gQe7AO/Zjmk+U6QQyop6nksC7HArVc+PL6MttHHK88e4SuQt6UsFFABKxTQABlmgSBQeVC4C6mOFRoJap6OcGTCQEjU7cu9wuuA0x02LclVyAZVGwBUFqOFbBucg069T381PlDlCJP7oVmsAZUmV5BoHoZ8P6W3ToT5QIVguAxNAfbAR0rIZ4vGMckZZLQUFoqAGhOyyo6WLc/4qYNnbz8rBF8E3JRRDDWoEy7ILigZbfOjJcONXoqjyMIIRP3Q9fpgi3/iEsxDaC0vNjC7TtKnLewxhn9yUm8I01QY1HxdMkoTroAwzQKghWozAMGWu7oLDe6WmApRwtCyPRBz9mCONCMJ6e0BSNQz+FbWztYMy89aQGDeopje+k58DDl8Tob7fOoaBGDMk2CYKEYPR24HcCJUVpFlLIyjehGaQgC1anr/U8TJALNmVUiA1uGI/aMWRZ15swIEVQs4jNKwzvpPLCB0uhOnE9ItA8RBWU6BUExyzn10aDJclqGWDkdVYLgUR6KS8B1gubMOiIwlghbhmOWd1fImT4qBhWL8QkdQzvoPPAIhfF9GJ/jjWuU9YrBo0yvIBAjpzHFiRFaRS6cI8qkIFAwMRQXCeqZlQTIFfZXLF4B4cSJwRuDTauNgOkY2Eihsh9UUWMbAQOgCE4yLDkoIEybIDCiZzHFGVFahnI2QTBFPcTdgikwq1mBSmLIvOCsnlAHgxhsUqFraAudBzYS14ZQpmYzwmM0gkY8yjQLAuVsFRyQOaU1uFzKip7qDUETeS8T1TpXyLvyoQcvZzPlqVNjUQxRbZjOwc10TJSrjwHgjeOJKIIjx+CZAUGw0ov0AvudF6EVpJaVRukmaAqvQq0GnWWlsytj/1CEVyjGStMoiAMM4Jm1vEIpUqwoelwbZAaAuDpE54FNlIe24NIKKgY1lmNhJMdJxgwIgk6Tc2ojaExOSxDR1UAHJ13gPYDyhpfv5seevZeuUsb23UXe898ruWNtV1PDRhVQZjUR6C9lWAM5P5oaB+opjO2j6+AG2fAObF5DZWr+cpxiSZgBQVCY+vv6HU5EaQUCKxQsJ11QT4W3vHYrv/j6zUwSVq8a44IzR/nld57LnQ80L2x8Cujs7mY6ImV5T0rmBTH6I1aUc0oju+gcmAwYyTMwBm8inqqCpCjTLwgUVgO0zowmlZVprIhyEgVpJpyytMZrfmw3qEBqmGTo7k14/Yt3cPeDZ+IVjHByGchGFc0FDKDMOpmH1X0Zyzszcg/OPP6KsvUppeFtdB1YT2FsL+Iz1FjUOk6UI2O6BYEA3rMSwKmnJWSOlaKcZEGWw8oldXq7MsiFI8nEz9Xo6coZHXcYq5xMYiAbm6yoB1SZVRQwwFUrKjirJLkcETDeTq4ol4d3NI7I4vEDgJ8MGOOYLpGkTLcgUMAalgM4a2gB4lR0GcpJFlihMfiv1gylTg+ewygDQxGVqsUITaE5VHcqUY/Mutvs6xlcuqTOuQvqpF4A8DZqlEvG6TmwlY7BLcTVAfTR4b9jmp3kZYBwglBPQBWcg2KsiDBrKSxFKDkVmk6ML5PLYk66IIqUdZvLfP3OebzouTvBOfCAU/DCp29dRK0ulIpKM4iB+j6o90NxMfiMWaGew4runOvPGEWA3MSoGLoHN7Fo407qG8YoZSMogjeOmeQkZ+YF1bpw5uoq11wySKGY8+AjnXzr3j6YepxgllqoaIdTlGarDkix2MVSMZxkgchkvfu9p5Dm8OwrB+gqerbvjPnwp5bzpW/Np1hQmkYAhdENiikIUR9oDihtySskOazqyXj1+SN0dTqSXOjcv55F629h4ZavMbLvPHZkF854wAAIEEnGzApqdeG6Zwzwx7+0nr7+KgA+t/zXF5bwp+87FVUQYTZaVEil7Aqp0Gx5Kn0q2iMEzeCsMjQS8bt/dwanrajS1ZGxY0+BHftiSoUWaO0FfAIjDygda4TiQsCAekBpeQqoQuohtvC0FXWuOzOh23lKOx5iybovMW/7nZjaCDgldzGaGyBnmoX15ibIcmHZooTf/7kN9PXXIHEAGKf85Et2sHZjF5/48kLKRWUWKuYq/S5XodmiBSxTxRA0jXOKKjy8pYRXcJaW+j++GPAJjD6kJAegtERw3YCACCBMqyQFryCcGCOTVYqU8+alXLLSc3b3CL0772X+uluYt+MuSKtgY4hKIAknWyQpCDMkSFK49OxhFiyoQ2p5VCYQw9UXD/DpWxfgVTCizDZZzDKXxTSdKIsImk4ECrHSsgwNtT1Q36+4TiZKMDGIZVqoghG48oIaXR0e709s0aJgPf2dwpI+Yb4ZZuHOO5l3+0307F4LeQKu0AiYZookm8FLNQNVsJYnZI1iBJTZyaALnUFpNoWFBMExEgsopCOQDiso00YVIgfXvniMJfMzslxO6A6y3BZw4weYt/l7LHz4Zsr714PPwcbgirSCiJRg5hRiuOehLoYGY3r765AYGqwCyvfu76Val7Y9OvMqpBkgEFsQUQ6nqgucqtJsgixUjkMQCIgw7VTBRAoGqpkhzzluOvVdL8WRvSzcfBuLHrmF0uBmwICNwDlahU51NJEoiiBo697Fl4AIFCMQ0baagW7eUeQvPria3/+FDZQ6EiYJX7x1CZ+6ZRHFuH1DxpmcS864jyQp8sPtZ4OCCIeRBQ4kdDRBMAWFOAJrAeW4eBujYhoPWC7a8DUWTlQ8uhtEwBVpRYpgG0GTkmhMK8q9UIrrXHfhbVSTIt966Kq2m2cUC8r/3rKQrbtKPPfKA8RFz9p1nXzl2wtIMmmEUbtRBMj5rZf9Ba+6+uNkueNvPverfOjrr8VZEJQpCwwtwAvzaAFBoEAhVkSUYyIG7woTVaRzYCNrbn8PF375d1l+z38Sj+8DVwAb08ocOZFkKK3He8GahD985bv48zf8Cn//c2/iTS/8F9KMtlOMlbse6OLd71nN2/7xVP7nqwvJPW0ZMgB5Dsv79/LyKz4DanBRwque/t/0livknkkCqvQ6VZpOlE6EpgsCVaEQ+R89nBUhtwWMz+je+wBLHvoK87bdga0NgW0M+NvmP6+TqaBRQGgpaQ5nLNrNtRfcArlDTMZLLv8c//7V/0s9LSCibTav0YmaHbsXIjBaLbN/tJ8lS9aDydk1uIRa6jBMUUAoO4QmE2PQDqX5gkAV4lgx5skDxmZ1+rd9n8WP3Dy5opxUplaUy7QbQ44lR2k91sC+kXls3HsKZ53+PQAe2nEmlSTGGaVdCe3PGmVgrIff/eg7ef1zPkStXubfbv55amlMZJXDFB1NJlBSKNMigiCOwIg+ZoPM2xiXjLNg27dY/PBN9O66D/LkUAfTpiweR9a6DxOPd/JHn/wDXnfNR6kmZT74tRtRFUBpriCyyh3rL+LeTRfhFTIPBacohwiUndBcAkWFDlpAEKhCIVKMARTUOHIbE9cGGwP+JetuomP/w+CzyYBxRdqZIhjxWPEorSl2yg+3nslvfPhtKFBwEFtFCVrlfx+vgsjke+UxSo4mU7QAlFrlvFoEQAnmcNDECs6R2YjC6B7mb/oWi9Z/jfLABkDAxuAcs4WhETQt/zezyAkCQKuFTGBEeRKFVgiaSJC4Fb5l0hqmtkAEZxQlmItBExUs5epeFjz8NRZMBExhZCeIAVdkNjIohpxWJyhtKYgdTScRUKCJ0ly45uzvc8MVn2HX/uV88OuvZ6xWxhqCOSazcNaBW7ngy/+AHRoEE4ErMnsJIh5BUYJgRkSOJhOIgJgmSTJhzaJt/PXP/Bod3XvBZhSiOn/26bdgjRLMLWpgyeiDWLsbXC+znQIGj8UTBDMkNjRbk4Mm97Codw8dPXvx1R5IC5y9/CGMIZhjFCESiF0GJmKuEJTIJAhBMCMiQ/NZwNEkkYWHdpzO/euejimNkGcFPvv9G8g9wRwUCZRNDRDmCgVKUiMIZog1NJmCaf6efg9vfv/f8Jvv/1ve+I8f4LN3Xk/BKcEcXAQwSperg86loBFKUscQBDPCOppMwLTCQ2EHRnv57B3XYQRiByIEc4wCkcnocFVQw9whlEwVEYJgJhhH85lWeQLZWYK5HjSS02Frc6yjgYIkCMz0VwUEIWjCtT9B4CSnaOqQC8dFOUQf53OD8BjKkUQopoZC6ojE84QElEkqAPrYzwIKqChPToglQVAUYZoFgTiCIGhQwJFRliqoHBkEhz4fWQgIYISalamC1CmpUfKp8gdLFDUKoiQGcgEz9T4VsCi5y/lObZBNtW3E3gNgVbC5AcB4wXmDUcH6g2UO/djUZ+uFKDdEmcUdfM0doqACioKAF300hIxRSmSIF1QABESZLkHgaD5PEDSjiVYQBXJQD5qDoYDzOVVxJNaS2cmQGImVfSXYV1b2d+SMFXOqsSePcnKXkUQ5SexJo4xarNScklgltZ7MKrmdChvjwSgCgGJQhElGPE48t4449td3gXiekBfIDW6qotzi/KHPcWYppG6ibKNKqaNUjykljnI9oqtaaFR3NaazbqknVTSuQx4juWASAQU1gAGVqfcCoByHIPBy0Us9TXY5cAfTLQhUjggSD6iZLBHAebLOAbRnN653F65zP/0927h26acYLtXY36GMFjOSQoZzKc5mWJtOdSegoqiAoFgFg2JRppoWBBAVBEBpECbpobA7QiQ5Xxw+n021hSA5T0oO/0OPei+H/7wCwmFnaeAFUcGoNDqfqNJFPj4fV+0iGu8hGp2HrXQTjfcRjfbhKj1EI/3YpAAK4gEFODqIlCA4StoKQXMpcCdB8BSJCuoBD6qThUIeQVauYnp2E3Xtw3bvxS7YSLz0IQp92ykVR+gojBAVhzGFCiauYiQn80UiBTNVqIAeHg7CTIkl40vD57GpthgkY8Yc3ZmIB+NB/KH3KpAWkaSAyQrYtIAb7yUeWExxcDHx4BKikXlElV5cpQs31odNQAVo1KH3agCUYE6qt0LQXAzczY8UBALKZOVANvVSBDqHiUpDaP827Mr7KC9eR0fXHsqd+3A9uxvdSsHVKOIxKqgaUEER8FPvMYcCpUkKJuNLQ+excSpomk50KnwA/NRnPRRGWYSt9GCrnUQHQ2hwEcV9KylNVGFwSePHG1W3iIJaUAMqc+gYLqi2QtBcANwFOILgKOIFMvAecgsaK6Y4Sr70YeIV99HZu43SvK24JQ9T7N1BVzxKwSYgHgGMN6h34C2qgkdoYY2g+crQuayvLQHJaHmHh47kIBw6R0sj4pEFFBrdz2KK+1dQ2rOK4oEVuGoHkllMBmqYCiAlmJVGWyFozgLuJHz5WaACGaiCB0Qg7R6Fxesp9m+iOBEm8cp76Vn4CD3lfdjiGNYmRCqQOdQ7vBoUoV0VJOOmkXN4pLoEJKetiYLJD1UeIUkRk5Qp7l9KefepFPespjiwbOLzcqKxTlAmayp8VJS2Fww4mk1JEeohaOYgL6Cg+WRlRWDRdgrzdhCvvpPO075Lb/8GCr27iMqDlCTD5g4/FShkEaQxGS0pfO+KCuSuUQ2iqE3Jy0OMrz7A+Jp7AEEqXVPHbkvo2H4GHdvOojjxPh5eiK2aqW4HkDbteoKkFTqalcD3gCUEs5yABzLIPeRlj+3eg5x6Nx2n3E7vwocpLn2Acu8OOkyCqIC3jdKJ8gizXUEybhk5i4eqy0By5gSTH7aIoKBgR+dT3L+M0t5T6Nx6Dh07zsCN9+CqEQqoa6MFg2Cro9lEE1TqBLOTCqTgFdSBdowhp9xF54r76FjzXbpW3k1v+QDW1bCAZDE+dXgi5iIFDMqc4m2jDpeXhxk/ZT/jp/6A/Zd/HlProLTnFDq3nUV55xl0bD+LaLQbyQUEvANEaUlB6mg2lQSoE8wScsQDkHkBdPWDlFffQ/ncm5i/9Id09OxqrBTH3qBZAX/wtd5BTgAg4W+YR4WP4uMq46fcx/iauyEpNVapO3aeRtfGi+nYfnqj87F1QQ2TZQGUlhDUHU0nVdBx2legAgpkkCn4vmGi+ZspnPtV+k+/je5l99PRtZeCF/zUBpjWOsgQfrQQNIGACqQxEIN4su79DPfuYfi8b2LGeynuW9E4Yut+5DJKe1cRjXaCCGpb4IgtqDiaTJWawChCOwoBk0BmQDoqmDV3MO/8r9B12rfoXfQwHa6KKJDF+EawBNNwnXmgBvKD5QAa3U5l5QNUVq1l7zM+RXEiaDq2nU3PI5fSsfVcXKWMeEGjJm2xBVVHk4moIlRQ2kAgXvAZKJCX69izv0fvWd+k57yvMH/eBuLiKDaL8AfDJS8TnNyts3DMBrVFm6ktXc+BS25q3GLQs/5SujZdSOeW83DjRZCT+dxOIFBxQvPFhtFaDkLQumuqoBkkJcWdeg+dl36KBWd+na4F6ykXRjBpgdw7tNZJRvDUCQYFlOApyqLJEiXp38m+ieDZd8XnKexfTvdE6PSuu7KxUOCqFu9m+o62wAvjcuHLPE2n/JvAz9F6QsDUIY3ALNxG8cIv0H/el+k75Xa641HwDs0ivFqmRxBLzu3jp3DX2GoQzzQJxIPNwWaQxo2g6XnoSvoefAaFgcVIBj4KgTMTBD7ojNJ0CntoHUEm5DloZ43okptYfP5EuJzzVXo792BRNC2Q1ztpG+HoLFADmXm006mseIDKyrXsuep/6N54EX1rr6Z7/WW4aoQ3oE6ZHoHCPqe0At0DQhMFXtAMvIJfvIPOi77I/Cs+2lhHLkZVSErkB4tgJikgBDP/bFfh0UWCofO/ztDZ36G4dzXz7ns2vQ9dSXHfYhBQd+ILBIHucaA0ncouhGYJAZNAVlSiM+6m7+kfZdE5N9HTvwmXOfI8Jq91cvIERjwnSaAGkhKIUlu8kZ3L1rH3Gf9L9yNX0H/v8+ncdiYmOaGNtUDNbocamk2F3YJyEgVe8An4jhR74VdZds0HWXjm1+hwFchj/Jwb6oejs3Cha9SorGOYgUu/yMAFtza21frveR49D18xeawWh8A5XiLsdSI0XVHYU1e8giGYWbngM8g7a5Se/jn6n/FhFq2+nZKtomk5zF5C0AS5myxRRs+4g9E1dzeuvOm/+zp6H3wG0VgBHx3renSQ5exxWU7TeWHYGPYDCwlmjgc/byelSz7L4qf/J/1L7ydWJc+K5HkHrSEwBK3zQHIRUMZXPNCovU/7DP33PJ++tc/AjfeBEDy5cTE6+P8BW01E00mieO8AAAAASUVORK5CYII=) no-repeat 150% 100%;\n background-color: #2238b3;\n background-size: 205px;\n background-blend-mode: luminosity;\n }\n .leftnav__header__title {\n font-family: var(--text-font-family);\n font-weight: 300;\n color: #fff;\n margin: 0;\n padding: 0;\n line-height: 1.5;\n }\n .leftnav__header__version {\n color: #aab3ed;\n font-family: var(--text-font-family);\n font-size: 14px;\n line-height: 1.5;\n }\n @media screen and (max-width: 767px) {\n .lh-leftnav {\n display: none;\n }\n }\n @media print {\n .lh-leftnav {\n display: none;\n }\n }\n </style>\n <nav class=\"lh-leftnav\">\n <div class=\"leftnav__header\">\n <h1 class=\"leftnav__header__title\">Lighthouse</h1>\n <div class=\"leftnav__header__version\"><!-- fill me --></div>\n </div>\n <template id=\"tmpl-lh-leftnav__items\">\n <a href=\"#\" class=\"lh-leftnav__item\">\n <span class=\"leftnav-item__category\"><!-- fill me --></span>\n <span class=\"leftnav-item__score\"><!-- fill me --></span>\n </a>\n </template>\n </nav>\n</template>\n\n<!-- Lighthouse header -->\n<template id=\"tmpl-lh-heading\">\n <style>\n :root {\n --report-header-height: 58px;\n --report-header-bg-color: #fafafa;\n }\n .lh-header {\n display: flex;\n height: var(--report-header-height);\n width: var(--report-width);\n max-width: 100%; /* support text-overflow on url */\n border-bottom: 1px solid var(--report-secondary-border-color);\n position: fixed;\n z-index: 1;\n will-change: transform;\n background-color: var(--report-header-bg-color);\n margin-left: var(--report-menu-width);\n align-items: center;\n padding: 0 calc(var(--default-padding) * 2);\n }\n .lh-metadata {\n flex: 1 1 0;\n padding-right: calc(var(--default-padding) / 2);\n line-height: 20px;\n color: var(--secondary-text-color);\n overflow-x: hidden;\n }\n .lh-metadata__results {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .lh-metadata__url {\n color: currentColor;\n }\n .lh-export {\n position: relative;\n }\n .lh-export__button {\n background-color: #fff;\n border: 1px solid var(--report-border-color);\n border-radius: 3px;\n cursor: pointer;\n outline: none;\n height: 32px;\n width: 48px;\n background-repeat: no-repeat;\n background-size: 20px;\n background-position: 50% 50%;\n }\n .lh-export__button:focus,\n .lh-export__button.active {\n box-shadow: 1px 1px 3px #ccc;\n }\n .lh-export__button.active + .lh-export__dropdown {\n opacity: 1;\n clip: rect(0, 164px, 200px, 0);\n }\n .lh-export__dropdown {\n position: absolute;\n background-color: #fff;\n border: 1px solid var(--report-border-color);\n border-radius: 3px;\n padding: calc(var(--default-padding) / 2) 0;\n cursor: pointer;\n top: 36px;\n right: 0;\n box-shadow: 1px 1px 3px #ccc;\n min-width: 125px;\n clip: rect(0, 164px, 0, 0);\n opacity: 0;\n transition: all 200ms cubic-bezier(0,0,0.2,1);\n }\n .lh-export__dropdown a {\n display: block;\n color: currentColor;\n text-decoration: none;\n white-space: nowrap;\n padding: 0 12px;\n line-height: 2;\n }\n .lh-export__dropdown a:hover,\n .lh-export__dropdown a:focus {\n background-color: #efefef;\n outline: none;\n }\n .lh-export__dropdown .report-icon {\n cursor: pointer;\n background-repeat: no-repeat;\n background-position: 8px 50%;\n background-size: 18px;\n background-color: transparent;\n text-indent: 18px;\n }\n /* copy icon needs slight adjustments to look great */\n .lh-export__dropdown .report-icon--copy {\n background-size: 16px;\n background-position: 9px 50%;\n }\n .lh-config {\n display: flex;\n }\n .lh-env {\n padding: var(--default-padding) 0 var(--default-padding) calc(var(--default-padding) * 2);\n left: 0;\n top: 100%;\n position: absolute;\n width: 100%;\n background-color: var(--report-header-bg-color);\n border-top: 1px solid var(--report-secondary-border-color);\n border-bottom: 1px solid var(--report-secondary-border-color);\n }\n .lh-env__title {\n font-size: var(--header-font-size);\n }\n .lh-env__items {\n margin: var(--default-padding) 0 0 0;\n }\n .lh-config__timestamp {\n margin-right: 6px;\n }\n .lh-config__settings-toggle {\n margin-left: 6px;\n }\n .lh-config__timestamp,\n .lh-config__settings-toggle summary {\n color: var(--secondary-text-color);\n }\n .lh-config__settings-toggle summary {\n display: flex;\n align-items: center;\n }\n .lh-config__settings-toggle .lh-toggle-arrow {\n width: 16px;\n height: 16px;\n margin-left: 2px;\n }\n .lh-config__settings-toggle[open] .lh-toggle-arrow {\n transform: rotateZ(90deg);\n }\n .lh-config__settings-toggle summary::-moz-list-bullet {\n display: none;\n }\n .lh-config__settings-toggle summary::-webkit-details-marker {\n display: none;\n }\n @media screen and (max-width: 767px) {\n .lh-export__dropdown {\n right: 0;\n left: initial;\n }\n .lh-header {\n padding: 0 var(--default-padding);\n margin-left: 0;\n }\n }\n @media print {\n .lh-header {\n display: none;\n margin-left: 0;\n }\n }\n </style>\n <div class=\"lh-header\">\n <div class=\"lh-metadata\">\n <div class=\"lh-metadata__results\">Results for: <a href=\"\" class=\"lh-metadata__url\" target=\"_blank\" rel=\"noopener\"><!-- fill me --></a></div>\n <div class=\"lh-config\">\n <span class=\"lh-config__timestamp\"><!-- fill me --></span> •\n <details class=\"lh-config__settings-toggle\">\n <summary>\n <span>Runtime settings</span>\n <span class=\"lh-toggle-arrow\" title=\"See report's runtime settings\"></span>\n </summary>\n <div class=\"lh-env\">\n <div class=\"lh-env__title\">Runtime environment</div>\n <ul class=\"lh-env__items\">\n <template id=\"tmpl-lh-env__items\">\n <li class=\"lh-env__item\">\n <span class=\"lh-env__name\"><!-- fill me --></span>\n <span class=\"lh-env__description\"><!-- fill me --></span>:\n <b class=\"lh-env__enabled\"><!-- fill me --></b>\n </li>\n </template>\n </ul>\n </div>\n </details>\n </div>\n </div>\n <div class=\"lh-export\">\n <button class=\"report-icon report-icon--share lh-export__button\" title=\"Export report\"></button>\n <div class=\"lh-export__dropdown\">\n <a href=\"#\" class=\"report-icon report-icon--print\" data-action=\"print\">Print...</a>\n <a href=\"#\" class=\"report-icon report-icon--copy\" data-action=\"copy\">Copy JSON</a>\n <a href=\"#\" class=\"report-icon report-icon--download\" data-action=\"save-html\">Save as HTML</a>\n <a href=\"#\" class=\"report-icon report-icon--download\" data-action=\"save-json\">Save as JSON</a>\n </div>\n </div>\n </div>\n</template>\n\n<!-- Lighthouse footer -->\n<template id=\"tmpl-lh-footer\">\n <style>\n .lh-footer {\n text-align: center;\n line-height: 90px;\n background-color: var(--report-header-bg-color);\n border-top: 1px solid var(--report-secondary-border-color);\n }\n </style>\n <footer class=\"lh-footer\">\n Generated by <b>Lighthouse</b> <span class=\"lh-footer__version\"><!-- fill me --></span> on\n <span class=\"lh-footer__timestamp\"><!-- fill me --></span> |\n <a href=\"https://github.com/GoogleChrome/Lighthouse/issues\" target=\"_blank\" rel=\"noopener\">File an issue</a>\n </footer>\n</template>\n\n<!-- Lighthouse score gauge -->\n<template id=\"tmpl-lh-gauge\">\n <style>\n .lh-gauge {\n --circle-size: 80px;\n --circle-size-half: calc(var(--circle-size) / 2);\n --circle-background: #eee;\n --circle-border-width: 8px;\n --inset-size: calc(var(--circle-size) - var(--circle-border-width));\n --inset-color: #fff;\n --transition-length: 1s;\n width: var(--circle-size);\n height: var(--circle-size);\n background-color: var(--circle-background);\n border-radius: 50%;\n }\n .lh-gauge--pass {\n --circle-color: var(--pass-color);\n color: var(--circle-color);\n }\n .lh-gauge--average {\n --circle-color: var(--average-color);\n color: var(--circle-color);\n }\n .lh-gauge--fail {\n --circle-color: var(--fail-color);\n color: var(--circle-color);\n }\n .lh-gauge__mask,\n .lh-gauge__fill {\n width: var(--circle-size);\n height: var(--circle-size);\n position: absolute;\n transition: transform var(--transition-length);\n border-radius: 50%;\n }\n .lh-gauge__mask {\n clip: rect(0px, var(--circle-size), var(--circle-size), var(--circle-size-half));\n }\n .lh-gauge__mask .lh-gauge__fill {\n clip: rect(0px, var(--circle-size-half), var(--circle-size), 0px);\n background-color: var(--circle-color);\n backface-visibility: hidden;\n }\n .lh-gauge__percentage {\n --spacer: calc((var(--circle-size) - var(--inset-size)) / 2);\n width: var(--inset-size);\n height: var(--inset-size);\n position: absolute;\n margin-left: var(--spacer);\n margin-top: var(--spacer);\n background-color: var(--inset-color);\n border-radius: inherit;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: calc(var(--circle-size) / 3);\n }\n .lh-gauge__wrapper {\n display: inline-flex;\n align-items: center;\n flex-direction: column;\n text-decoration: none;\n color: inherit;\n flex: 1;\n }\n .lh-gauge__label {\n font-size: 16px;\n margin-top: var(--default-padding);\n }\n @media screen and (max-width: 767px) {\n .lh-gauge__label {\n font-size: 12px;\n }\n }\n </style>\n <a href=\"#\" class=\"lh-gauge__wrapper\">\n <div class=\"lh-gauge\" data-progress=\"0\">\n <div class=\"lh-gauge__circle\">\n <div class=\"lh-gauge__mask lh-gauge__mask--full\">\n <div class=\"lh-gauge__fill\"></div>\n </div>\n <div class=\"lh-gauge__mask lh-gauge__mask--half\">\n <div class=\"lh-gauge__fill\"></div>\n <div class=\"lh-gauge__fill lh-gauge__fill--fix\"></div>\n </div>\n </div>\n <div class=\"lh-gauge__percentage\"></div>\n </div>\n <div class=\"lh-gauge__label\"><!-- fill me --></div>\n </a>\n</template>\n"; class ReportGeneratorV2 { /** @@ -15701,15 +16054,18 @@ // to check that there are artifacts specified in the config, and throw if not. if (validPassesAndAudits || validArtifactsAndAudits) { if (validPassesAndAudits) { + // Set up the driver and run gatherers. opts.driver = opts.driverMock || new Driver(connection); - // Finally set up the driver to gather. run = run.then(_ => GatherRunner.run(config.passes, opts)); } else if (validArtifactsAndAudits) { - run = run.then(_ => { - return Object.assign(GatherRunner.instantiateComputedArtifacts(), config.artifacts); - }); + run = run.then(_ => config.artifacts); } + // Add computed artifacts. + run = run.then(artifacts => { + return Object.assign({}, artifacts, Runner.instantiateComputedArtifacts()); + }); + // Basic check that the traces (gathered or loaded) are valid. run = run.then(artifacts => { for (const passName of Object.keys(artifacts.traces || {})) { @@ -15722,6 +16078,12 @@ return artifacts; }); + + run = run.then(artifacts => { + log.log('status', 'Analyzing and running audits...'); + return artifacts; + }); + // Run each audit sequentially, the auditResults array has all our fine work const auditResults = []; for (const audit of config.audits) { @@ -15737,8 +16099,8 @@ } else if (config.auditResults) { // If there are existing audit results, surface those here. // Instantiate and return artifacts for consistency. - const artifacts = Object.assign(GatherRunner.instantiateComputedArtifacts(), - config.artifacts || {}); + const artifacts = Object.assign({}, config.artifacts || {}, + Runner.instantiateComputedArtifacts()); run = run.then(_ => { return { artifacts, @@ -15754,6 +16116,8 @@ // Format and aggregate results before returning. run = run .then(runResults => { + log.log('status', 'Generating results...'); + const resultsById = runResults.auditResults.reduce((results, audit) => { results[audit.name] = audit; return results; @@ -15831,6 +16195,7 @@ // Fill remaining audit result fields. }).then(auditResult => Audit.generateAuditResult(audit, auditResult)) .catch(err => { + log.warn(audit.meta.name, `Caught exception: ${err.message}`); if (err.fatal) { throw err; } @@ -15856,7 +16221,7 @@ ]; const fileList = [ - ...["accessibility","audit.js","byte-efficiency","cache-start-url.js","content-width.js","critical-request-chains.js","deprecations.js","dobetterweb","estimated-input-latency.js","first-meaningful-paint.js","is-on-https.js","load-fast-enough-for-pwa.js","manifest-short-name-length.js","multi-check-audit.js","redirects-http.js","service-worker.js","speed-index-metric.js","splash-screen.js","themed-omnibox.js","time-to-interactive.js","user-timings.js","viewport.js","webapp-install-banner.js","without-javascript.js","works-offline.js"], + ...["accessibility","audit.js","byte-efficiency","cache-start-url.js","content-width.js","critical-request-chains.js","deprecations.js","dobetterweb","estimated-input-latency.js","first-interactive.js","first-meaningful-paint.js","is-on-https.js","load-fast-enough-for-pwa.js","manifest-short-name-length.js","multi-check-audit.js","redirects-http.js","service-worker.js","speed-index-metric.js","splash-screen.js","themed-omnibox.js","time-to-interactive.js","user-timings.js","viewport.js","webapp-install-banner.js","without-javascript.js","works-offline.js"], ...["appcache-manifest.js","dom-size.js","external-anchors-use-rel-noopener.js","geolocation-on-start.js","link-blocking-first-paint.js","no-console-time.js","no-datenow.js","no-document-write.js","no-mutation-events.js","no-old-flexbox.js","no-websql.js","notification-on-start.js","script-blocking-first-paint.js","uses-http2.js","uses-passive-event-listeners.js"].map(f => `dobetterweb/${f}`), ...["accesskeys.js","aria-allowed-attr.js","aria-required-attr.js","aria-required-children.js","aria-required-parent.js","aria-roles.js","aria-valid-attr-value.js","aria-valid-attr.js","audio-caption.js","axe-audit.js","button-name.js","bypass.js","color-contrast.js","definition-list.js","dlitem.js","document-title.js","duplicate-id.js","frame-title.js","html-has-lang.js","html-lang-valid.js","image-alt.js","input-image-alt.js","label.js","layout-table.js","link-name.js","list.js","listitem.js","meta-refresh.js","meta-viewport.js","object-alt.js","tabindex.js","td-headers-attr.js","th-has-data-cells.js","valid-lang.js","video-caption.js","video-description.js"] .map(f => `accessibility/${f}`), @@ -15882,6 +16247,25 @@ } /** + * @return {!ComputedArtifacts} + */ + static instantiateComputedArtifacts() { + const computedArtifacts = {}; + ["computed-artifact.js","critical-request-chains.js","first-interactive.js","manifest-values.js","network-throughput.js","pushed-requests.js","screenshots.js","speedline.js","trace-of-tab.js","tracing-model.js"].forEach(function(filename) { + // Skip base class. + if (filename === 'computed-artifact.js') return; + + // Drop `.js` suffix to keep browserify import happy. + filename = filename.replace(/\.js$/, ''); + const ArtifactClass = require('./gather/computed/' + filename); + const artifact = new ArtifactClass(computedArtifacts); + // define the request* function that will be exposed on `artifacts` + computedArtifacts['request' + artifact.name] = artifact.request.bind(artifact); + }); + return computedArtifacts; + } + + /** * Resolves the location of the specified plugin and returns an absolute * string path to the file. Used for loading custom audits and gatherers. * Throws an error if no plugin is found. @@ -58747,7 +59131,8 @@ } const screenshotTraceCategory = 'disabled-by-default-devtools.screenshot'; -function extractFramesFromTimeline(timeline) { +function extractFramesFromTimeline(timeline, opts) { + opts = opts || {}; let trace; trace = typeof timeline === 'string' ? fs.readFileSync(timeline, 'utf-8') : timeline; try { @@ -58758,7 +59143,7 @@ let events = trace.traceEvents || trace; events = events.sort((a, b) => a.ts - b.ts).filter(e => e.ts !== 0); - const startTs = events[0].ts / 1000; + const startTs = (opts.timeOrigin || events[0].ts) / 1000; const endTs = events[events.length - 1].ts / 1000; const rawScreenshots = events.filter(e => e.cat.includes(screenshotTraceCategory)); @@ -58868,10 +59253,11 @@ /** * Retrieve speed index informations * @param {string|Array|DevtoolsTimelineModel} timeline + * @param {?Object} opts * @return {Promise} resolving with an object containing the speed index informations */ -module.exports = function (timeline) { - return frame.extractFramesFromTimeline(timeline).then(function (data) { +module.exports = function (timeline, opts) { + return frame.extractFramesFromTimeline(timeline, opts).then(function (data) { const frames = data.frames; speedIndex.calculateVisualProgress(frames); speedIndex.calculatePerceptualProgress(frames); @@ -58929,7 +59315,9 @@ function calculateFrameSimilarity(frame, target) { const defaultImageConfig = { - channels: 3 + // image-ssim uses this to interpret the arraybuffer NOT the desired channels to consider + // jpeg-js encodes each pixel with an alpha channel set to 0xFF, so 4 channel interpretation is required + channels: 4 }; const frameData = Object.assign(frame.getParsedImage(), defaultImageConfig); @@ -59017,6 +59405,7 @@ } module.exports = { + calculateFrameSimilarity, calculateVisualProgress, calculatePerceptualProgress, calculateSpeedIndexes @@ -59024,6 +59413,6 @@ },{"image-ssim":294}],304:[function(require,module,exports){ module.exports={ - "version": "1.7.0-alpha.1" + "version": "2.0.0-alpha.1" } },{}]},{},[197]);
diff --git a/third_party/WebKit/Source/devtools/front_end/components/Linkifier.js b/third_party/WebKit/Source/devtools/front_end/components/Linkifier.js index 6184cc95..ff079047 100644 --- a/third_party/WebKit/Source/devtools/front_end/components/Linkifier.js +++ b/third_party/WebKit/Source/devtools/front_end/components/Linkifier.js
@@ -303,13 +303,8 @@ return; Components.Linkifier._bindUILocation(anchor, uiLocation); - var text = uiLocation.linkText(); - var info = Components.Linkifier._linkInfo(anchor); - info.originalLinkText = text; - text = text.replace(/([a-f0-9]{7})[a-f0-9]{13}[a-f0-9]*/g, '$1\u2026'); - if (this._maxLength) - text = text.trimMiddle(this._maxLength); - anchor.textContent = text; + var text = uiLocation.linkText(true /* skipTrim */); + Components.Linkifier._setTrimmedText(anchor, text, this._maxLength); var titleText = uiLocation.uiSourceCode.url(); if (typeof uiLocation.lineNumber === 'number') @@ -396,9 +391,7 @@ link.title = title; if (href) link.href = href; - link.textContent = text; - if (maxLength) - link.textContent = link.textContent.trimMiddle(maxLength); + Components.Linkifier._setTrimmedText(link, text, maxLength); link[Components.Linkifier._infoSymbol] = { icon: null, enableDecorator: false, @@ -408,8 +401,7 @@ lineNumber: null, columnNumber: null, revealable: null, - fallback: null, - originalLinkText: text + fallback: null }; if (!preventClick) link.addEventListener('click', Components.Linkifier._handleClick, false); @@ -419,12 +411,70 @@ } /** - * @param {?Element} link - * @return {?string} + * @param {!Element} link + * @param {string} text + * @param {number=} maxLength */ - static originalLinkText(link) { - var info = this._linkInfo(link); - return info ? info.originalLinkText : null; + static _setTrimmedText(link, text, maxLength) { + link.removeChildren(); + if (maxLength && text.length > maxLength) { + var middleSplit = splitMiddle(text, maxLength); + appendTextWithoutHashes(middleSplit[0]); + appendHiddenText(middleSplit[1]); + appendTextWithoutHashes(middleSplit[2]); + } else { + appendTextWithoutHashes(text); + } + + /** + * @param {string} string + */ + function appendHiddenText(string) { + var ellipsisNode = link.createChild('span', 'devtools-link-ellipsis').createTextChild('\u2026'); + ellipsisNode[Components.Linkifier._untruncatedNodeTextSymbol] = string; + } + + /** + * @param {string} string + */ + function appendTextWithoutHashes(string) { + var hashSplit = TextUtils.TextUtils.splitStringByRegexes(string, [/[a-f0-9]{20,}/g]); + for (var match of hashSplit) { + if (match.regexIndex === -1) { + link.createTextChild(match.value); + } else { + link.createTextChild(match.value.substring(0, 7)); + appendHiddenText(match.value.substring(7)); + } + } + } + + /** + * @param {string} string + * @param {number} maxLength + * @return {!Array<string>} + */ + function splitMiddle(string, maxLength) { + var leftIndex = Math.floor(maxLength / 2); + var rightIndex = string.length - Math.ceil(maxLength / 2) + 1; + + // Do not truncate between characters that use multiple code points (emojis). + if (string.codePointAt(rightIndex - 1) >= 0x10000) { + rightIndex++; + leftIndex++; + } + if (leftIndex > 0 && string.codePointAt(leftIndex - 1) >= 0x10000) + leftIndex--; + return [string.substring(0, leftIndex), string.substring(leftIndex, rightIndex), string.substring(rightIndex)]; + } + } + + /** + * @param {!Node} node + * @return {string} + */ + static untruncatedNodeText(node) { + return node[Components.Linkifier._untruncatedNodeTextSymbol] || node.textContent; } /** @@ -549,6 +599,7 @@ Components.Linkifier._sourceCodeAnchors = Symbol('Linkifier.anchors'); Components.Linkifier._infoSymbol = Symbol('Linkifier.info'); +Components.Linkifier._untruncatedNodeTextSymbol = Symbol('Linkifier.untruncatedNodeText'); /** * @typedef {{ @@ -560,8 +611,7 @@ * lineNumber: ?number, * columnNumber: ?number, * revealable: ?Object, - * fallback: ?Element, - * originalLinkText: string + * fallback: ?Element * }} */ Components._LinkInfo;
diff --git a/third_party/WebKit/Source/devtools/front_end/console/ConsoleContextSelector.js b/third_party/WebKit/Source/devtools/front_end/console/ConsoleContextSelector.js index 70494e5..127d7cee 100644 --- a/third_party/WebKit/Source/devtools/front_end/console/ConsoleContextSelector.js +++ b/third_party/WebKit/Source/devtools/front_end/console/ConsoleContextSelector.js
@@ -25,7 +25,10 @@ this._selectElement.addEventListener('change', this._executionContextChanged.bind(this), false); UI.context.addFlavorChangeListener(SDK.ExecutionContext, this._executionContextChangedExternally, this); + UI.context.addFlavorChangeListener(SDK.DebuggerModel.CallFrame, this._callFrameSelectedInUI, this); SDK.targetManager.observeModels(SDK.RuntimeModel, this); + SDK.targetManager.addModelListener( + SDK.DebuggerModel, SDK.DebuggerModel.Events.CallFrameSelected, this._callFrameSelectedInModel, this); } /** @@ -88,6 +91,7 @@ if (executionContext === UI.context.flavor(SDK.ExecutionContext)) this._select(newOption); + this._updateOptionDisabledState(newOption); /** * @param {!Element} option @@ -225,4 +229,33 @@ return this._selectElement[this._selectElement.selectedIndex]; return null; } + + /** + * @param {!Common.Event} event + */ + _callFrameSelectedInModel(event) { + var debuggerModel = /** @type {!SDK.DebuggerModel} */ (event.data); + var options = this._selectElement.options; + for (var i = 0; i < options.length; i++) { + if (options[i].__executionContext.debuggerModel === debuggerModel) + this._updateOptionDisabledState(options[i]); + } + } + + /** + * @param {!Element} option + */ + _updateOptionDisabledState(option) { + var executionContext = option.__executionContext; + var callFrame = executionContext.debuggerModel.selectedCallFrame(); + var callFrameContext = callFrame && callFrame.script.executionContext(); + option.disabled = callFrameContext && executionContext !== callFrameContext; + } + + _callFrameSelectedInUI() { + var callFrame = UI.context.flavor(SDK.DebuggerModel.CallFrame); + var callFrameContext = callFrame && callFrame.script.executionContext(); + if (callFrameContext) + UI.context.setFlavor(SDK.ExecutionContext, callFrameContext); + } };
diff --git a/third_party/WebKit/Source/devtools/front_end/console/ConsoleViewMessage.js b/third_party/WebKit/Source/devtools/front_end/console/ConsoleViewMessage.js index cd636dcd..bbf5555 100644 --- a/third_party/WebKit/Source/devtools/front_end/console/ConsoleViewMessage.js +++ b/third_party/WebKit/Source/devtools/front_end/console/ConsoleViewMessage.js
@@ -1040,11 +1040,7 @@ toExportString() { var lines = []; var nodes = this.contentElement().childTextNodes(); - var messageContent = ''; - for (var i = 0; i < nodes.length; ++i) { - var originalLinkText = Components.Linkifier.originalLinkText(nodes[i].parentElement); - messageContent += typeof originalLinkText === 'string' ? originalLinkText : nodes[i].textContent; - } + var messageContent = nodes.map(Components.Linkifier.untruncatedNodeText).join(''); for (var i = 0; i < this.repeatCount(); ++i) lines.push(messageContent); return lines.join('\n');
diff --git a/third_party/WebKit/Source/devtools/front_end/console/ConsoleViewport.js b/third_party/WebKit/Source/devtools/front_end/console/ConsoleViewport.js index 120d13c0..b98dc1c 100644 --- a/third_party/WebKit/Source/devtools/front_end/console/ConsoleViewport.js +++ b/third_party/WebKit/Source/devtools/front_end/console/ConsoleViewport.js
@@ -420,8 +420,11 @@ } var textLines = []; - for (var i = startSelection.item; i <= endSelection.item; ++i) - textLines.push(this._providerElement(i).element().deepTextContent()); + for (var i = startSelection.item; i <= endSelection.item; ++i) { + var element = this._providerElement(i).element(); + var lineContent = element.childTextNodes().map(Components.Linkifier.untruncatedNodeText).join(''); + textLines.push(lineContent); + } var endSelectionElement = this._providerElement(endSelection.item).element(); if (endSelection.node && endSelection.node.isSelfOrDescendant(endSelectionElement)) { @@ -453,10 +456,15 @@ offset = container.textContent.length; } } + var chars = 0; var node = itemElement; while ((node = node.traverseNextTextNode(itemElement)) && !node.isSelfOrDescendant(container)) - chars += node.textContent.length; + chars += Components.Linkifier.untruncatedNodeText(node).length; + // If the selection offset is at the end of a link's ellipsis, use the untruncated length as offset. + var untruncatedContainerLength = Components.Linkifier.untruncatedNodeText(container).length; + if (offset === 1 && untruncatedContainerLength > offset) + offset = untruncatedContainerLength; return chars + offset; }
diff --git a/third_party/WebKit/Source/devtools/front_end/sdk/DebuggerModel.js b/third_party/WebKit/Source/devtools/front_end/sdk/DebuggerModel.js index c778cd30..78bfe1a 100644 --- a/third_party/WebKit/Source/devtools/front_end/sdk/DebuggerModel.js +++ b/third_party/WebKit/Source/devtools/front_end/sdk/DebuggerModel.js
@@ -689,11 +689,10 @@ * @param {?SDK.DebuggerModel.CallFrame} callFrame */ setSelectedCallFrame(callFrame) { - this._selectedCallFrame = callFrame; - if (!this._selectedCallFrame) + if (this._selectedCallFrame === callFrame) return; - - this.dispatchEventToListeners(SDK.DebuggerModel.Events.CallFrameSelected, callFrame); + this._selectedCallFrame = callFrame; + this.dispatchEventToListeners(SDK.DebuggerModel.Events.CallFrameSelected, this); } /**
diff --git a/third_party/WebKit/Source/devtools/front_end/sources/CallStackSidebarPane.js b/third_party/WebKit/Source/devtools/front_end/sources/CallStackSidebarPane.js index 66bd6c65..f827cb0 100644 --- a/third_party/WebKit/Source/devtools/front_end/sources/CallStackSidebarPane.js +++ b/third_party/WebKit/Source/devtools/front_end/sources/CallStackSidebarPane.js
@@ -367,7 +367,7 @@ var location = this._itemLocation(item); if (location) { var uiLocation = Bindings.debuggerWorkspaceBinding.rawLocationToUILocation(location); - itemText += ' (' + uiLocation.linkText() + ')'; + itemText += ' (' + uiLocation.linkText(true /* skipTrim */) + ')'; } text.push(itemText); }
diff --git a/third_party/WebKit/Source/devtools/front_end/sources/JavaScriptSourceFrame.js b/third_party/WebKit/Source/devtools/front_end/sources/JavaScriptSourceFrame.js index 6237e7238..9bed9fd2 100644 --- a/third_party/WebKit/Source/devtools/front_end/sources/JavaScriptSourceFrame.js +++ b/third_party/WebKit/Source/devtools/front_end/sources/JavaScriptSourceFrame.js
@@ -1233,27 +1233,38 @@ if (this._muted && !this.uiSourceCode().isDirty()) this._restoreBreakpointsIfConsistentScripts(); } - if (newScriptFile) - this._scriptFileForDebuggerModel.set(debuggerModel, newScriptFile); + if (!newScriptFile) + return; + this._scriptFileForDebuggerModel.set(debuggerModel, newScriptFile); + newScriptFile.addEventListener(Bindings.ResourceScriptFile.Events.DidMergeToVM, this._didMergeToVM, this); + newScriptFile.addEventListener(Bindings.ResourceScriptFile.Events.DidDivergeFromVM, this._didDivergeFromVM, this); + if (this.loaded) + newScriptFile.checkMapping(); + this._showSourceMapInfobar(newScriptFile.hasSourceMapURL()); + } - if (newScriptFile) { - newScriptFile.addEventListener(Bindings.ResourceScriptFile.Events.DidMergeToVM, this._didMergeToVM, this); - newScriptFile.addEventListener(Bindings.ResourceScriptFile.Events.DidDivergeFromVM, this._didDivergeFromVM, this); - if (this.loaded) - newScriptFile.checkMapping(); - if (newScriptFile.hasSourceMapURL()) { - var sourceMapInfobar = UI.Infobar.create( - UI.Infobar.Type.Info, Common.UIString('Source Map detected.'), - Common.settings.createSetting('sourceMapInfobarDisabled', false)); - if (sourceMapInfobar) { - sourceMapInfobar.createDetailsRowMessage(Common.UIString( - 'Associated files should be added to the file tree. You can debug these resolved source files as regular JavaScript files.')); - sourceMapInfobar.createDetailsRowMessage(Common.UIString( - 'Associated files are available via file tree or %s.', - UI.shortcutRegistry.shortcutTitleForAction('quickOpen.show'))); - this.attachInfobars([sourceMapInfobar]); - } + /** + * @param {boolean} show + */ + _showSourceMapInfobar(show) { + if (this._sourceMapInfobar) { + if (!show) { + this._sourceMapInfobar.dispose(); + delete this._sourceMapInfobar; } + return; + } + this._sourceMapInfobar = UI.Infobar.create( + UI.Infobar.Type.Info, Common.UIString('Source Map detected.'), + Common.settings.createSetting('sourceMapInfobarDisabled', false)); + if (this._sourceMapInfobar) { + this._sourceMapInfobar.createDetailsRowMessage(Common.UIString( + 'Associated files should be added to the file tree. You can debug these resolved source files as regular JavaScript files.')); + this._sourceMapInfobar.createDetailsRowMessage(Common.UIString( + 'Associated files are available via file tree or %s.', + UI.shortcutRegistry.shortcutTitleForAction('quickOpen.show'))); + this._sourceMapInfobar.setCloseCallback(() => delete this._sourceMapInfobar); + this.attachInfobars([this._sourceMapInfobar]); } }
diff --git a/third_party/WebKit/Source/devtools/front_end/workspace/UISourceCode.js b/third_party/WebKit/Source/devtools/front_end/workspace/UISourceCode.js index 50924e38..5c0232ba 100644 --- a/third_party/WebKit/Source/devtools/front_end/workspace/UISourceCode.js +++ b/third_party/WebKit/Source/devtools/front_end/workspace/UISourceCode.js
@@ -667,10 +667,11 @@ } /** + * @param {boolean=} skipTrim * @return {string} */ - linkText() { - var linkText = this.uiSourceCode.displayName(); + linkText(skipTrim) { + var linkText = this.uiSourceCode.displayName(skipTrim); if (typeof this.lineNumber === 'number') linkText += ':' + (this.lineNumber + 1); return linkText;
diff --git a/third_party/WebKit/Source/platform/AsyncFileSystemCallbacks.h b/third_party/WebKit/Source/platform/AsyncFileSystemCallbacks.h index 6defd14..dad7c6d0 100644 --- a/third_party/WebKit/Source/platform/AsyncFileSystemCallbacks.h +++ b/third_party/WebKit/Source/platform/AsyncFileSystemCallbacks.h
@@ -51,11 +51,11 @@ AsyncFileSystemCallbacks() : block_until_completion_(false) {} // Called when a requested operation is completed successfully. - virtual void DidSucceed() { ASSERT_NOT_REACHED(); } + virtual void DidSucceed() { NOTREACHED(); } // Called when a requested file system is opened. virtual void DidOpenFileSystem(const String& name, const KURL& root_url) { - ASSERT_NOT_REACHED(); + NOTREACHED(); } // Called when a filesystem URL is resolved. @@ -64,32 +64,32 @@ FileSystemType, const String& file_path, bool is_directory) { - ASSERT_NOT_REACHED(); + NOTREACHED(); } // Called when a file metadata is read successfully. - virtual void DidReadMetadata(const FileMetadata&) { ASSERT_NOT_REACHED(); } + virtual void DidReadMetadata(const FileMetadata&) { NOTREACHED(); } // Called when a snapshot file is created successfully. virtual void DidCreateSnapshotFile(const FileMetadata&, PassRefPtr<BlobDataHandle> snapshot) { - ASSERT_NOT_REACHED(); + NOTREACHED(); } // Called when a directory entry is read. virtual void DidReadDirectoryEntry(const String& name, bool is_directory) { - ASSERT_NOT_REACHED(); + NOTREACHED(); } // Called after a chunk of directory entries have been read (i.e. indicates // it's good time to call back to the application). If hasMore is true there // can be more chunks. - virtual void DidReadDirectoryEntries(bool has_more) { ASSERT_NOT_REACHED(); } + virtual void DidReadDirectoryEntries(bool has_more) { NOTREACHED(); } // Called when an AsyncFileWrter has been created successfully. virtual void DidCreateFileWriter(std::unique_ptr<WebFileWriter>, long long length) { - ASSERT_NOT_REACHED(); + NOTREACHED(); } // Called when there was an error.
diff --git a/third_party/WebKit/Source/platform/Crypto.cpp b/third_party/WebKit/Source/platform/Crypto.cpp index 55a2b63..c14ec7f 100644 --- a/third_party/WebKit/Source/platform/Crypto.cpp +++ b/third_party/WebKit/Source/platform/Crypto.cpp
@@ -23,7 +23,7 @@ return kWebCryptoAlgorithmIdSha512; }; - ASSERT_NOT_REACHED(); + NOTREACHED(); return kWebCryptoAlgorithmIdSha256; }
diff --git a/third_party/WebKit/Source/platform/DateComponents.cpp b/third_party/WebKit/Source/platform/DateComponents.cpp index 5192eec..43a1606 100644 --- a/third_party/WebKit/Source/platform/DateComponents.cpp +++ b/third_party/WebKit/Source/platform/DateComponents.cpp
@@ -664,7 +664,7 @@ case kInvalid: break; } - ASSERT_NOT_REACHED(); + NOTREACHED(); return InvalidMilliseconds(); } @@ -683,7 +683,7 @@ switch (effective_format) { default: - ASSERT_NOT_REACHED(); + NOTREACHED(); // Fallback to None. case kNone: return String::Format("%02d:%02d", hour_, minute_); @@ -714,7 +714,7 @@ case kInvalid: break; } - ASSERT_NOT_REACHED(); + NOTREACHED(); return String("(Invalid DateComponents)"); }
diff --git a/third_party/WebKit/Source/platform/Decimal.cpp b/third_party/WebKit/Source/platform/Decimal.cpp index eedf405..ec3459e 100644 --- a/third_party/WebKit/Source/platform/Decimal.cpp +++ b/third_party/WebKit/Source/platform/Decimal.cpp
@@ -107,7 +107,7 @@ if (rhs_class == Decimal::EncodedData::kClassInfinity) return kRHSIsInfinity; - ASSERT_NOT_REACHED(); + NOTREACHED(); return kBothFinite; } @@ -119,7 +119,7 @@ return rhs_; case kResultIsUnknown: default: - ASSERT_NOT_REACHED(); + NOTREACHED(); return lhs_; } } @@ -426,7 +426,7 @@ return lhs.IsZero() ? Nan() : Infinity(result_sign); } - ASSERT_NOT_REACHED(); + NOTREACHED(); return Nan(); } @@ -628,7 +628,7 @@ return Zero(kPositive); default: - ASSERT_NOT_REACHED(); + NOTREACHED(); return Nan(); } } @@ -829,7 +829,7 @@ return Nan(); default: - ASSERT_NOT_REACHED(); + NOTREACHED(); return Nan(); } } @@ -922,7 +922,7 @@ break; default: - ASSERT_NOT_REACHED(); + NOTREACHED(); return ""; }
diff --git a/third_party/WebKit/Source/platform/Length.cpp b/third_party/WebKit/Source/platform/Length.cpp index 324682a7..8a945e5a 100644 --- a/third_party/WebKit/Source/platform/Length.cpp +++ b/third_party/WebKit/Source/platform/Length.cpp
@@ -112,7 +112,7 @@ case kCalculated: return GetCalculationValue().GetPixelsAndPercent(); default: - ASSERT_NOT_REACHED(); + NOTREACHED(); return PixelsAndPercent(0, 0); } }
diff --git a/third_party/WebKit/Source/platform/Length.h b/third_party/WebKit/Source/platform/Length.h index 6d27e23..f5abee5a 100644 --- a/third_party/WebKit/Source/platform/Length.h +++ b/third_party/WebKit/Source/platform/Length.h
@@ -126,7 +126,7 @@ const Length& operator*=(float v) { if (IsCalculated()) { - ASSERT_NOT_REACHED(); + NOTREACHED(); return *this; } @@ -147,7 +147,7 @@ int IntValue() const { if (IsCalculated()) { - ASSERT_NOT_REACHED(); + NOTREACHED(); return 0; } return GetIntValue(); @@ -180,7 +180,7 @@ void SetValue(int value) { if (IsCalculated()) { - ASSERT_NOT_REACHED(); + NOTREACHED(); return; } SetValue(kFixed, value);
diff --git a/third_party/WebKit/Source/platform/LengthFunctions.cpp b/third_party/WebKit/Source/platform/LengthFunctions.cpp index fa2ab12..54b1a42 100644 --- a/third_party/WebKit/Source/platform/LengthFunctions.cpp +++ b/third_party/WebKit/Source/platform/LengthFunctions.cpp
@@ -51,10 +51,10 @@ case kDeviceWidth: case kDeviceHeight: case kMaxSizeNone: - ASSERT_NOT_REACHED(); + NOTREACHED(); return 0; } - ASSERT_NOT_REACHED(); + NOTREACHED(); return 0; } @@ -80,10 +80,10 @@ case kDeviceWidth: case kDeviceHeight: case kMaxSizeNone: - ASSERT_NOT_REACHED(); + NOTREACHED(); return LayoutUnit(); } - ASSERT_NOT_REACHED(); + NOTREACHED(); return LayoutUnit(); } @@ -111,10 +111,10 @@ case kDeviceWidth: case kDeviceHeight: case kMaxSizeNone: - ASSERT_NOT_REACHED(); + NOTREACHED(); return LayoutUnit(); } - ASSERT_NOT_REACHED(); + NOTREACHED(); return LayoutUnit(); }
diff --git a/third_party/WebKit/Source/platform/SharedBuffer.cpp b/third_party/WebKit/Source/platform/SharedBuffer.cpp index ab64898..ec40d4b 100644 --- a/third_party/WebKit/Source/platform/SharedBuffer.cpp +++ b/third_party/WebKit/Source/platform/SharedBuffer.cpp
@@ -200,7 +200,7 @@ return segment == segments - 1 ? segmented_size - position : kSegmentSize - position_in_segment; } - ASSERT_NOT_REACHED(); + NOTREACHED(); return 0; } @@ -240,7 +240,7 @@ } if (position != buffer_length) { - ASSERT_NOT_REACHED(); + NOTREACHED(); // Don't return the incomplete SkData. return nullptr; }
diff --git a/third_party/WebKit/Source/platform/SharedBufferChunkReader.cpp b/third_party/WebKit/Source/platform/SharedBufferChunkReader.cpp index db01abfb..5b6c280c 100644 --- a/third_party/WebKit/Source/platform/SharedBufferChunkReader.cpp +++ b/third_party/WebKit/Source/platform/SharedBufferChunkReader.cpp
@@ -106,7 +106,7 @@ return !chunk.IsEmpty(); } } - ASSERT_NOT_REACHED(); + NOTREACHED(); return false; }
diff --git a/third_party/WebKit/Source/platform/StorageQuotaCallbacks.h b/third_party/WebKit/Source/platform/StorageQuotaCallbacks.h index 6a40b89..5aa4457 100644 --- a/third_party/WebKit/Source/platform/StorageQuotaCallbacks.h +++ b/third_party/WebKit/Source/platform/StorageQuotaCallbacks.h
@@ -50,13 +50,13 @@ virtual void DidQueryStorageUsageAndQuota(unsigned long long usage_in_bytes, unsigned long long quota_in_bytes) { - ASSERT_NOT_REACHED(); + NOTREACHED(); } virtual void DidGrantStorageQuota(unsigned long long usage_in_bytes, unsigned long long granted_quota_in_bytes) { - ASSERT_NOT_REACHED(); + NOTREACHED(); } - virtual void DidFail(WebStorageQuotaError) { ASSERT_NOT_REACHED(); } + virtual void DidFail(WebStorageQuotaError) { NOTREACHED(); } }; } // namespace blink
diff --git a/third_party/WebKit/Source/platform/animation/TimingFunction.cpp b/third_party/WebKit/Source/platform/animation/TimingFunction.cpp index 86e7cdc..13ae1b95 100644 --- a/third_party/WebKit/Source/platform/animation/TimingFunction.cpp +++ b/third_party/WebKit/Source/platform/animation/TimingFunction.cpp
@@ -219,7 +219,7 @@ return (frame == rhs); } default: - ASSERT_NOT_REACHED(); + NOTREACHED(); } return false; }
diff --git a/third_party/WebKit/Source/platform/geometry/LayoutRectOutsets.cpp b/third_party/WebKit/Source/platform/geometry/LayoutRectOutsets.cpp index 950edd6..06c97f9 100644 --- a/third_party/WebKit/Source/platform/geometry/LayoutRectOutsets.cpp +++ b/third_party/WebKit/Source/platform/geometry/LayoutRectOutsets.cpp
@@ -75,7 +75,7 @@ case WritingMode::kVerticalRl: return right_; } - ASSERT_NOT_REACHED(); + NOTREACHED(); return top_; } @@ -88,7 +88,7 @@ case WritingMode::kVerticalRl: return left_; } - ASSERT_NOT_REACHED(); + NOTREACHED(); return bottom_; } @@ -126,7 +126,7 @@ right_ = value; break; default: - ASSERT_NOT_REACHED(); + NOTREACHED(); top_ = value; } } @@ -143,7 +143,7 @@ left_ = value; break; default: - ASSERT_NOT_REACHED(); + NOTREACHED(); bottom_ = value; } }
diff --git a/third_party/WebKit/Source/platform/scroll/ScrollAnimatorCompositorCoordinator.cpp b/third_party/WebKit/Source/platform/scroll/ScrollAnimatorCompositorCoordinator.cpp index 3b83f482..1986012 100644 --- a/third_party/WebKit/Source/platform/scroll/ScrollAnimatorCompositorCoordinator.cpp +++ b/third_party/WebKit/Source/platform/scroll/ScrollAnimatorCompositorCoordinator.cpp
@@ -67,7 +67,7 @@ case RunState::kWaitingToCancelOnCompositor: return true; } - ASSERT_NOT_REACHED(); + NOTREACHED(); return false; } @@ -158,7 +158,7 @@ case RunState::kIdle: case RunState::kPostAnimationCleanup: case RunState::kRunningOnMainThread: - ASSERT_NOT_REACHED(); + NOTREACHED(); break; case RunState::kWaitingToSendToCompositor: case RunState::kWaitingToCancelOnCompositorButNewScroll: @@ -343,7 +343,7 @@ case RunState::kRunningOnCompositorButNeedsAdjustment: return String("RunningOnCompositorButNeedsAdjustment"); } - ASSERT_NOT_REACHED(); + NOTREACHED(); return String(); }
diff --git a/third_party/WebKit/Source/platform/scroll/ScrollTypes.h b/third_party/WebKit/Source/platform/scroll/ScrollTypes.h index 8971c625..63a564a 100644 --- a/third_party/WebKit/Source/platform/scroll/ScrollTypes.h +++ b/third_party/WebKit/Source/platform/scroll/ScrollTypes.h
@@ -142,7 +142,7 @@ case kScrollRightIgnoringWritingMode: return kScrollRight; default: - ASSERT_NOT_REACHED(); + NOTREACHED(); break; } return kScrollUp; @@ -159,7 +159,7 @@ case kScrollRight: return kScrollRightIgnoringWritingMode; default: - ASSERT_NOT_REACHED(); + NOTREACHED(); break; } return kScrollUpIgnoringWritingMode;
diff --git a/third_party/WebKit/Source/platform/scroll/ScrollableArea.cpp b/third_party/WebKit/Source/platform/scroll/ScrollableArea.cpp index f78f1271..62247f2 100644 --- a/third_party/WebKit/Source/platform/scroll/ScrollableArea.cpp +++ b/third_party/WebKit/Source/platform/scroll/ScrollableArea.cpp
@@ -131,7 +131,7 @@ case kScrollByPrecisePixel: return PixelStep(orientation); default: - ASSERT_NOT_REACHED(); + NOTREACHED(); return 0.0f; } } @@ -193,7 +193,7 @@ UserScrollHelper(clamped_offset, behavior); break; default: - ASSERT_NOT_REACHED(); + NOTREACHED(); } } @@ -258,7 +258,7 @@ ScrollType) { // TODO(bokan): This should really be implemented here but ScrollAlignment is // in Core which is a dependency violation. - ASSERT_NOT_REACHED(); + NOTREACHED(); return LayoutRect(); }
diff --git a/third_party/WebKit/Source/platform/scroll/Scrollbar.cpp b/third_party/WebKit/Source/platform/scroll/Scrollbar.cpp index 65b7f5cd..33c8b5b6 100644 --- a/third_party/WebKit/Source/platform/scroll/Scrollbar.cpp +++ b/third_party/WebKit/Source/platform/scroll/Scrollbar.cpp
@@ -362,7 +362,7 @@ scroll_pos_ = pressed_pos_; return true; default: - ASSERT_NOT_REACHED(); + NOTREACHED(); return true; } break; @@ -388,7 +388,7 @@ MoveThumb(scroll_pos_, false); return true; default: - ASSERT_NOT_REACHED(); + NOTREACHED(); return true; } break;
diff --git a/third_party/WebKit/Source/platform/scroll/ScrollbarTheme.cpp b/third_party/WebKit/Source/platform/scroll/ScrollbarTheme.cpp index aad4568..26770324 100644 --- a/third_party/WebKit/Source/platform/scroll/ScrollbarTheme.cpp +++ b/third_party/WebKit/Source/platform/scroll/ScrollbarTheme.cpp
@@ -426,7 +426,7 @@ case kForwardButtonEndPart: return DisplayItem::kScrollbarForwardButtonEnd; default: - ASSERT_NOT_REACHED(); + NOTREACHED(); return DisplayItem::kScrollbarBackButtonStart; } } @@ -439,7 +439,7 @@ case kForwardTrackPart: return DisplayItem::kScrollbarForwardTrack; default: - ASSERT_NOT_REACHED(); + NOTREACHED(); return DisplayItem::kScrollbarBackTrack; } }
diff --git a/third_party/WebKit/Source/platform/testing/WebLayerTreeViewImplForTesting.cpp b/third_party/WebKit/Source/platform/testing/WebLayerTreeViewImplForTesting.cpp index d9989f9..96140f67 100644 --- a/third_party/WebKit/Source/platform/testing/WebLayerTreeViewImplForTesting.cpp +++ b/third_party/WebKit/Source/platform/testing/WebLayerTreeViewImplForTesting.cpp
@@ -143,7 +143,7 @@ } void WebLayerTreeViewImplForTesting::DidFailToInitializeCompositorFrameSink() { - ASSERT_NOT_REACHED(); + NOTREACHED(); } void WebLayerTreeViewImplForTesting::RegisterViewportLayers(
diff --git a/third_party/WebKit/Source/platform/weborigin/SecurityPolicy.cpp b/third_party/WebKit/Source/platform/weborigin/SecurityPolicy.cpp index ad2b98b..b148192 100644 --- a/third_party/WebKit/Source/platform/weborigin/SecurityPolicy.cpp +++ b/third_party/WebKit/Source/platform/weborigin/SecurityPolicy.cpp
@@ -141,7 +141,7 @@ case kReferrerPolicyNoReferrerWhenDowngrade: break; case kReferrerPolicyDefault: - ASSERT_NOT_REACHED(); + NOTREACHED(); break; }
diff --git a/tools/chrome_proxy/webdriver/smoke.py b/tools/chrome_proxy/webdriver/smoke.py index ad799d6..ff99fdf 100644 --- a/tools/chrome_proxy/webdriver/smoke.py +++ b/tools/chrome_proxy/webdriver/smoke.py
@@ -65,6 +65,33 @@ self.assertHasChromeProxyViaHeader(response) self.assertEqual(200, response.status) + # Verify unique page IDs are sent in the Chrome-Proxy header. + def testPageID(self): + with TestDriver() as t: + t.AddChromeArg('--enable-spdy-proxy-auth') + page_identifiers = [] + page_loads = 5 + + for i in range (0, page_loads): + t.LoadURL('http://check.googlezip.net/test.html') + responses = t.GetHTTPResponses() + self.assertEqual(2, len(responses)) + pid_in_page_count = 0 + page_id = '' + for response in responses: + self.assertHasChromeProxyViaHeader(response) + self.assertEqual(200, response.status) + chrome_proxy_header = response.request_headers['chrome-proxy'] + chrome_proxy_directives = chrome_proxy_header.split(',') + for directive in chrome_proxy_directives: + if 'pid=' in directive: + pid_in_page_count = pid_in_page_count+1 + page_id = directive.split('=')[1] + self.assertNotEqual('', page_id) + self.assertNotIn(page_id, page_identifiers) + page_identifiers.append(page_id) + self.assertEqual(1, pid_in_page_count) + # Ensure that block causes resources to load from the origin directly. def testCheckBlockIsWorking(self): with TestDriver() as t:
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl index 35590f7a..06c801cd 100644 --- a/tools/mb/mb_config.pyl +++ b/tools/mb/mb_config.pyl
@@ -200,6 +200,7 @@ 'Mac deterministic': 'release_bot_mac_strip', 'Mac deterministic (dbg)': 'debug_bot', 'Mojo ChromiumOS': 'chromeos_with_codecs_ozone_release_trybot', + 'Mojo Linux': 'release_bot', 'Mojo Windows': 'release_bot_x86', 'Ozone Linux': 'release_bot_ozone_linux', 'Site Isolation Android': 'android_release_bot_minimal_symbols_arm64', @@ -579,6 +580,8 @@ # builds (and we only store baselines for release builds). 'linux_layout_tests_layout_ng': 'release_bot', + 'linux_mojo': 'release_trybot', + 'linux_mojo_chromeos': 'release_trybot', 'linux_nacl_sdk_build': 'release_bot', 'linux_nacl_sdk': 'release_bot', 'linux_optional_gpu_tests_rel': 'gpu_fyi_tests_release_trybot', @@ -664,6 +667,7 @@ 'win_clang_rel': 'clang_official_release_trybot_x86', 'win_clang_x64_dbg': 'win_clang_debug_bot', 'win_clang_x64_rel': 'clang_official_release_trybot', + 'win_mojo': 'release_trybot_x86', 'win_nacl_sdk': 'release_bot_x86', 'win_nacl_sdk_build': 'release_bot_x86', 'win_optional_gpu_tests_rel': 'gpu_tests_deqp_gles_release_trybot_x86',
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index da10d7f..f33d38a 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml
@@ -17037,6 +17037,17 @@ </summary> </histogram> +<histogram name="Event.MainThreadEventQueue.FlushQueueNoBeginMainFrame" + enum="BooleanHit"> + <owner>dtapuska@chromium.org</owner> + <summary> + Whether the Begin Main Frame was not received and the queue generated a + flush queue after a given timeout. + + Team: input-dev@chromium.org. + </summary> +</histogram> + <histogram name="Event.MainThreadEventQueue.NonContinuous.QueueingTime" units="microseconds"> <owner>dtapuska@chromium.org</owner> @@ -29405,6 +29416,23 @@ </summary> </histogram> +<histogram name="Memory.Experimental.CompressedPagesPerSecond" units="pages/s"> + <owner>bashi@chromium.org</owner> + <summary> + The number of pages compressed per second. Recorded every 60 seconds. Only + recorded on macOS. + </summary> +</histogram> + +<histogram name="Memory.Experimental.DecompressedPagesPerSecond" + units="pages/s"> + <owner>bashi@chromium.org</owner> + <summary> + The number of pages decompressed per second. Recorded every 60 seconds. Only + recorded on macOS. + </summary> +</histogram> + <histogram name="Memory.Experimental.Gpu.PhysicalFootprint.MacOS" units="MB"> <owner>erikchen@chromium.org</owner> <summary> @@ -103655,6 +103683,7 @@ <int value="-891856063" label="MidiManagerAndroid:enabled"/> <int value="-885601782" label="enable-contextual-search"/> <int value="-884864731" label="WebPaymentsSingleAppUiSkip:enabled"/> + <int value="-881854123" label="enable-heap-profiling"/> <int value="-881054479" label="WebAssemblyStreaming:disabled"/> <int value="-876148583" label="ArcBootCompletedBroadcast:disabled"/> <int value="-867087281" label="enable-virtual-keyboard"/>
diff --git a/ui/app_list/app_list_model.cc b/ui/app_list/app_list_model.cc index db59138..6ee9d525 100644 --- a/ui/app_list/app_list_model.cc +++ b/ui/app_list/app_list_model.cc
@@ -356,6 +356,9 @@ } void AppListModel::SetSearchEngineIsGoogle(bool is_google) { + if (search_engine_is_google_ == is_google) + return; + search_engine_is_google_ = is_google; for (auto& observer : observers_) observer.OnSearchEngineIsGoogleChanged(is_google);
diff --git a/ui/app_list/search_box_model.cc b/ui/app_list/search_box_model.cc index a0c210e..877d677 100644 --- a/ui/app_list/search_box_model.cc +++ b/ui/app_list/search_box_model.cc
@@ -27,8 +27,7 @@ SearchBoxModel::SpeechButtonProperty::~SpeechButtonProperty() { } -SearchBoxModel::SearchBoxModel() { -} +SearchBoxModel::SearchBoxModel() {} SearchBoxModel::~SearchBoxModel() { } @@ -67,8 +66,8 @@ observer.SelectionModelChanged(); } -void SearchBoxModel::SetText(const base::string16& text) { - if (text_ == text) +void SearchBoxModel::Update(const base::string16& text, bool is_voice_query) { + if (text_ == text && is_voice_query_ == is_voice_query) return; // Log that a new search has been commenced whenever the text box text @@ -77,8 +76,9 @@ UMA_HISTOGRAM_ENUMERATION("Apps.AppListSearchCommenced", 1, 2); } text_ = text; + is_voice_query_ = is_voice_query; for (auto& observer : observers_) - observer.TextChanged(); + observer.Update(); } void SearchBoxModel::AddObserver(SearchBoxModelObserver* observer) {
diff --git a/ui/app_list/search_box_model.h b/ui/app_list/search_box_model.h index 2b60ff27..fe53744 100644 --- a/ui/app_list/search_box_model.h +++ b/ui/app_list/search_box_model.h
@@ -68,9 +68,11 @@ return selection_model_; } - // Sets/gets the text for the search box's Textfield. - void SetText(const base::string16& text); + // Sets/gets the text for the search box's Textfield and the voice search + // flag. + void Update(const base::string16& text, bool is_voice_query); const base::string16& text() const { return text_; } + bool is_voice_query() const { return is_voice_query_; } void AddObserver(SearchBoxModelObserver* observer); void RemoveObserver(SearchBoxModelObserver* observer); @@ -81,6 +83,7 @@ base::string16 accessible_name_; gfx::SelectionModel selection_model_; base::string16 text_; + bool is_voice_query_ = false; base::ObserverList<SearchBoxModelObserver> observers_;
diff --git a/ui/app_list/search_box_model_observer.h b/ui/app_list/search_box_model_observer.h index b23a5f6..9dfd2e8 100644 --- a/ui/app_list/search_box_model_observer.h +++ b/ui/app_list/search_box_model_observer.h
@@ -21,8 +21,8 @@ // Invoked when selection model is changed. virtual void SelectionModelChanged() = 0; - // Invoked when text is changed. - virtual void TextChanged() = 0; + // Invoked when text or voice search flag is changed. + virtual void Update() = 0; protected: virtual ~SearchBoxModelObserver() {}
diff --git a/ui/app_list/search_controller.cc b/ui/app_list/search_controller.cc index 83cca8d..cae0583 100644 --- a/ui/app_list/search_controller.cc +++ b/ui/app_list/search_controller.cc
@@ -36,21 +36,21 @@ SearchController::~SearchController() { } -void SearchController::Start(bool is_voice_query) { +void SearchController::Start() { Stop(); base::string16 query; base::TrimWhitespace(search_box_->text(), base::TRIM_ALL, &query); + is_voice_query_ = search_box_->is_voice_query(); + dispatching_query_ = true; for (const auto& provider : providers_) - provider->Start(is_voice_query, query); + provider->Start(is_voice_query_, query); dispatching_query_ = false; query_for_recommendation_ = query.empty() ? true : false; - is_voice_query_ = is_voice_query; - OnResultsChanged(); stop_timer_.Start(FROM_HERE,
diff --git a/ui/app_list/search_controller.h b/ui/app_list/search_controller.h index f8f69583..ea57747a 100644 --- a/ui/app_list/search_controller.h +++ b/ui/app_list/search_controller.h
@@ -33,7 +33,7 @@ History* history); virtual ~SearchController(); - void Start(bool is_voice_query); + void Start(); void Stop(); void OpenResult(SearchResult* result, int event_flags);
diff --git a/ui/app_list/views/app_list_main_view.cc b/ui/app_list/views/app_list_main_view.cc index 04118b8f..d70092f 100644 --- a/ui/app_list/views/app_list_main_view.cc +++ b/ui/app_list/views/app_list_main_view.cc
@@ -87,7 +87,8 @@ contents_view_->layer()->SetFillsBoundsOpaquely(false); contents_view_->layer()->SetMasksToBounds(true); - delegate_->StartSearch(); + // Clear the old query and start search. + search_box_view_->ClearSearch(); } void AppListMainView::ShowAppListWhenReady() {
diff --git a/ui/app_list/views/search_box_view.cc b/ui/app_list/views/search_box_view.cc index 2d70b80..72c7e2b 100644 --- a/ui/app_list/views/search_box_view.cc +++ b/ui/app_list/views/search_box_view.cc
@@ -304,7 +304,7 @@ void SearchBoxView::UpdateModel() { // Temporarily remove from observer to ignore notifications caused by us. model_->search_box()->RemoveObserver(this); - model_->search_box()->SetText(search_box_->text()); + model_->search_box()->Update(search_box_->text(), false); model_->search_box()->SetSelectionModel(search_box_->GetSelectionModel()); model_->search_box()->AddObserver(this); } @@ -422,7 +422,7 @@ search_box_->SelectSelectionModel(model_->search_box()->selection_model()); } -void SearchBoxView::TextChanged() { +void SearchBoxView::Update() { search_box_->SetText(model_->search_box()->text()); NotifyQueryChanged(); }
diff --git a/ui/app_list/views/search_box_view.h b/ui/app_list/views/search_box_view.h index 3c37a29..69a1d4c 100644 --- a/ui/app_list/views/search_box_view.h +++ b/ui/app_list/views/search_box_view.h
@@ -103,7 +103,7 @@ void SpeechRecognitionButtonPropChanged() override; void HintTextChanged() override; void SelectionModelChanged() override; - void TextChanged() override; + void Update() override; // Overridden from SpeechUIModelObserver: void OnSpeechRecognitionStateChanged(
diff --git a/ui/compositor/layer_unittest.cc b/ui/compositor/layer_unittest.cc index a9fbea59..5f95f47 100644 --- a/ui/compositor/layer_unittest.cc +++ b/ui/compositor/layer_unittest.cc
@@ -2216,7 +2216,8 @@ // Starts an animation and tests that incrementing compositor frame count can // be used to report animation smoothness metrics. -TEST_F(LayerWithRealCompositorTest, ReportMetrics) { +// Flaky test crbug.com/709080 +TEST_F(LayerWithRealCompositorTest, DISABLED_ReportMetrics) { std::unique_ptr<Layer> root(CreateLayer(LAYER_SOLID_COLOR)); GetCompositor()->SetRootLayer(root.get()); LayerAnimator* animator = root->GetAnimator();
diff --git a/ui/compositor/test/in_process_context_factory.cc b/ui/compositor/test/in_process_context_factory.cc index aecb9b79..f96cbaf8 100644 --- a/ui/compositor/test/in_process_context_factory.cc +++ b/ui/compositor/test/in_process_context_factory.cc
@@ -332,7 +332,18 @@ data->surface_handle = widget; #else gpu::GpuSurfaceTracker* tracker = gpu::GpuSurfaceTracker::Get(); - data->surface_handle = tracker->AddSurfaceForNativeWidget(widget); + data->surface_handle = tracker->AddSurfaceForNativeWidget( + gpu::GpuSurfaceTracker::SurfaceRecord( + widget +#if defined(OS_ANDROID) + // We have to provide a surface too, but we don't have one. For + // now, we don't proide it, since nobody should ask anyway. + // If we ever provide a valid surface here, then GpuSurfaceTracker + // can be more strict about enforcing it. + , + nullptr +#endif + )); #endif }
diff --git a/ui/views/animation/ink_drop_host_view.cc b/ui/views/animation/ink_drop_host_view.cc index 9ffa42c8..9748c8b 100644 --- a/ui/views/animation/ink_drop_host_view.cc +++ b/ui/views/animation/ink_drop_host_view.cc
@@ -76,6 +76,7 @@ break; case ui::ET_GESTURE_END: case ui::ET_GESTURE_SCROLL_BEGIN: + case ui::ET_GESTURE_TAP_CANCEL: if (current_ink_drop_state == InkDropState::ACTIVATED) return; ink_drop_state = InkDropState::HIDDEN; @@ -87,7 +88,8 @@ if (ink_drop_state == InkDropState::HIDDEN && (current_ink_drop_state == InkDropState::ACTION_TRIGGERED || current_ink_drop_state == InkDropState::ALTERNATE_ACTION_TRIGGERED || - current_ink_drop_state == InkDropState::DEACTIVATED)) { + current_ink_drop_state == InkDropState::DEACTIVATED || + current_ink_drop_state == InkDropState::HIDDEN)) { // These InkDropStates automatically transition to the HIDDEN state so we // don't make an explicit call. Explicitly animating to HIDDEN in this // case would prematurely pre-empt these animations.
diff --git a/ui/webui/resources/cr_elements/network/cr_network_icon.html b/ui/webui/resources/cr_elements/network/cr_network_icon.html index 9158262..9936f489 100644 --- a/ui/webui/resources/cr_elements/network/cr_network_icon.html +++ b/ui/webui/resources/cr_elements/network/cr_network_icon.html
@@ -2,10 +2,11 @@ <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> <link rel="import" href="chrome://resources/cr_elements/network/cr_onc_types.html"> <link rel="import" href="chrome://resources/cr_elements/network/network_icons.html"> +<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> <dom-module id="cr-network-icon"> <template> - <style> + <style include="cr-hidden-style"> :host { display: inline-flex; overflow: hidden; @@ -13,11 +14,6 @@ position: relative; } - /* Included for options UI. TODO(stevenjb): Remove. */ - [hidden] { - display: none !important; - } - #icon { height: 20px; opacity: .65; /* Equivalent to #5a5a5a */
diff --git a/ui/webui/resources/cr_elements/network/cr_network_list.html b/ui/webui/resources/cr_elements/network/cr_network_list.html index 09c967b..0df1997 100644 --- a/ui/webui/resources/cr_elements/network/cr_network_list.html +++ b/ui/webui/resources/cr_elements/network/cr_network_list.html
@@ -1,13 +1,15 @@ <link rel="import" href="chrome://resources/html/polymer.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html"> <link rel="import" href="chrome://resources/cr_elements/cr_scrollable_behavior.html"> <link rel="import" href="chrome://resources/cr_elements/network/cr_network_list_item.html"> <link rel="import" href="chrome://resources/cr_elements/network/cr_onc_types.html"> +<link rel="import" href="chrome://resources/cr_elements/network/cr_onc_types.html"> +<link rel="import" href="chrome://resources/cr_elements/shared_style_css.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-list/iron-list.html"> <dom-module id="cr-network-list"> <template> - <style include="cr-shared-style"> + <style include="cr-shared-style iron-flex"> :host { display: inline-flex; }
diff --git a/ui/webui/resources/cr_elements/network/cr_network_list_item.html b/ui/webui/resources/cr_elements/network/cr_network_list_item.html index 21e5110..153801d8 100644 --- a/ui/webui/resources/cr_elements/network/cr_network_list_item.html +++ b/ui/webui/resources/cr_elements/network/cr_network_list_item.html
@@ -1,7 +1,7 @@ <link rel="import" href="chrome://resources/cr_elements/icons.html"> <link rel="import" href="chrome://resources/html/polymer.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-keys/iron-a11y-keys.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> +<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/iron-flex-layout-classes.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-icon-button/paper-icon-button-light.html"> <link rel="import" href="chrome://resources/cr_elements/network/cr_network_icon.html"> <link rel="import" href="chrome://resources/cr_elements/network/cr_onc_types.html"> @@ -11,7 +11,7 @@ <dom-module id="cr-network-list-item"> <template> - <style include="cr-shared-style"> + <style include="cr-shared-style iron-flex"> :host { display: inline-flex; outline: none;
diff --git a/ui/webui/resources/cr_elements/network/cr_network_select.html b/ui/webui/resources/cr_elements/network/cr_network_select.html index d0d12d1..34a8e49 100644 --- a/ui/webui/resources/cr_elements/network/cr_network_select.html +++ b/ui/webui/resources/cr_elements/network/cr_network_select.html
@@ -1,5 +1,4 @@ <link rel="import" href="chrome://resources/html/polymer.html"> -<link rel="import" href="chrome://resources/polymer/v1_0/iron-flex-layout/classes/iron-flex-layout.html"> <link rel="import" href="chrome://resources/cr_elements/network/cr_network_list.html"> <link rel="import" href="chrome://resources/cr_elements/network/cr_onc_types.html"> @@ -9,8 +8,12 @@ :host { display: inline-flex; } + + #networkList { + flex: 1; + } </style> - <cr-network-list id="neworkList" class ="flex" + <cr-network-list id="networkList" on-selected="onNetworkListItemSelected_" on-network-connected="onNetworkConnected_" networks="[[networkStateList_]]" custom-items="[[customItems]]"
diff --git a/ui/webui/resources/cr_elements/network/cr_network_select.js b/ui/webui/resources/cr_elements/network/cr_network_select.js index 9d66e5e..0349141 100644 --- a/ui/webui/resources/cr_elements/network/cr_network_select.js +++ b/ui/webui/resources/cr_elements/network/cr_network_select.js
@@ -57,7 +57,7 @@ }, focus: function() { - this.$.neworkList.focus(); + this.$.networkList.focus(); }, /**
diff --git a/ui/webui/resources/cr_elements/policy/cr_policy_indicator.html b/ui/webui/resources/cr_elements/policy/cr_policy_indicator.html index cff4f8c..223ab3a6 100644 --- a/ui/webui/resources/cr_elements/policy/cr_policy_indicator.html +++ b/ui/webui/resources/cr_elements/policy/cr_policy_indicator.html
@@ -1,4 +1,5 @@ <link rel="import" href="chrome://resources/cr_elements/icons.html"> +<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> <link rel="import" href="chrome://resources/html/polymer.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-tooltip/paper-tooltip.html"> @@ -6,7 +7,7 @@ <link rel="import" href="cr_policy_vars_css.html"> <dom-module id="cr-policy-indicator"> - <style> + <style include="cr-hidden-style"> :host { @apply(--cr-policy-indicator); }
diff --git a/ui/webui/resources/cr_elements/policy/cr_policy_network_indicator.html b/ui/webui/resources/cr_elements/policy/cr_policy_network_indicator.html index 29dd122..c4b4d40a 100644 --- a/ui/webui/resources/cr_elements/policy/cr_policy_network_indicator.html +++ b/ui/webui/resources/cr_elements/policy/cr_policy_network_indicator.html
@@ -1,4 +1,5 @@ <link rel="import" href="chrome://resources/cr_elements/icons.html"> +<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> <link rel="import" href="chrome://resources/html/polymer.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-tooltip/paper-tooltip.html"> @@ -7,7 +8,7 @@ <link rel="import" href="cr_policy_vars_css.html"> <dom-module id="cr-policy-network-indicator"> - <style> + <style include="cr-hidden-style"> :host { @apply(--cr-policy-indicator); }
diff --git a/ui/webui/resources/cr_elements/policy/cr_policy_pref_indicator.html b/ui/webui/resources/cr_elements/policy/cr_policy_pref_indicator.html index 9024713..2c97372 100644 --- a/ui/webui/resources/cr_elements/policy/cr_policy_pref_indicator.html +++ b/ui/webui/resources/cr_elements/policy/cr_policy_pref_indicator.html
@@ -1,4 +1,5 @@ <link rel="import" href="chrome://resources/cr_elements/icons.html"> +<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html"> <link rel="import" href="chrome://resources/html/polymer.html"> <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-tooltip/paper-tooltip.html"> @@ -6,7 +7,7 @@ <link rel="import" href="cr_policy_vars_css.html"> <dom-module id="cr-policy-pref-indicator"> - <style> + <style include="cr-hidden-style"> :host { @apply(--cr-policy-indicator); }