diff --git src/wp-includes/default-filters.php src/wp-includes/default-filters.php
index a196f4ff19..9fb06cb083 100644
--- src/wp-includes/default-filters.php
+++ src/wp-includes/default-filters.php
@@ -169,6 +169,8 @@ add_filter( 'widget_text_content', 'capital_P_dangit', 11 );
 add_filter( 'widget_text_content', 'wptexturize'          );
 add_filter( 'widget_text_content', 'convert_smilies',  20 );
 add_filter( 'widget_text_content', 'wpautop'              );
+add_filter( 'widget_text_content', 'shortcode_unautop'    );
+add_filter( 'widget_text_content', 'do_shortcode',     11 ); // Runs after wpautop() but note that $post global will be null when shortcodes run.
 
 add_filter( 'date_i18n', 'wp_maybe_decline_date' );
 
diff --git src/wp-includes/widgets/class-wp-widget-text.php src/wp-includes/widgets/class-wp-widget-text.php
index a79e0daa6c..13a737ab6e 100644
--- src/wp-includes/widgets/class-wp-widget-text.php
+++ src/wp-includes/widgets/class-wp-widget-text.php
@@ -183,11 +183,14 @@ class WP_Widget_Text extends WP_Widget {
 	 *
 	 * @since 2.8.0
 	 *
+	 * @global WP_Post $post
+	 *
 	 * @param array $args     Display arguments including 'before_title', 'after_title',
 	 *                        'before_widget', and 'after_widget'.
 	 * @param array $instance Settings for the current Text widget instance.
 	 */
 	public function widget( $args, $instance ) {
+		global $post;
 
 		/** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
 		$title = apply_filters( 'widget_title', empty( $instance['title'] ) ? '' : $instance['title'], $instance, $this->id_base );
@@ -205,16 +208,22 @@ class WP_Widget_Text extends WP_Widget {
 		}
 
 		/*
-		 * Just-in-time temporarily upgrade Visual Text widget shortcode handling
-		 * (with support added by plugin) from the widget_text filter to
-		 * widget_text_content:11 to prevent wpautop from corrupting HTML output
-		 * added by the shortcode.
+		 * Suspend legacy plugin-supplied do_shortcode() for 'widget_text' filter for the visual Text widget to prevent
+		 * shortcodes being processed twice. Now do_shortcode() is added to the 'widget_text_content' filter in core itself
+		 * and it applies after wpautop() to prevent corrupting HTML output added by the shortcode. When do_shortcode() is
+		 * added to 'widget_text_content' then do_shortcode() will be manually called when in legacy mode as well.
 		 */
 		$widget_text_do_shortcode_priority = has_filter( 'widget_text', 'do_shortcode' );
-		$should_upgrade_shortcode_handling = ( $is_visual_text_widget && false !== $widget_text_do_shortcode_priority );
-		if ( $should_upgrade_shortcode_handling ) {
+		$should_suspend_legacy_shortcode_support = ( $is_visual_text_widget && false !== $widget_text_do_shortcode_priority );
+		if ( $should_suspend_legacy_shortcode_support ) {
 			remove_filter( 'widget_text', 'do_shortcode', $widget_text_do_shortcode_priority );
-			add_filter( 'widget_text_content', 'do_shortcode', 11 );
+		}
+
+		// Nullify the $post global during widget rendering to prevent shortcodes from running with the unexpected context.
+		$suspended_post = null;
+		if ( isset( $post ) ) {
+			$suspended_post = $post;
+			$post = null;
 		}
 
 		/**
@@ -244,14 +253,36 @@ class WP_Widget_Text extends WP_Widget {
 			 * @param WP_Widget_Text $this     Current Text widget instance.
 			 */
 			$text = apply_filters( 'widget_text_content', $text, $instance, $this );
+		} else {
+			if ( ! empty( $instance['filter'] ) ) {
+				$text = wpautop( $text ); // Back-compat for instances prior to 4.8.
+			}
+
+			/*
+			 * Manually do shortcodes on the content when the core-added filter is present. It is added by default
+			 * in core by adding do_shortcode() to the 'widget_text_content' filter to apply after wpautop().
+			 * Since the legacy Text widget runs wpautop() after 'widget_text' filters are applied, the widget in
+			 * legacy mode here manually applies do_shortcode() on the content unless the default
+			 * core filter for 'widget_text_content' has been removed, or if do_shortcode() has already
+			 * been applied via a plugin adding do_shortcode() to 'widget_text' filters.
+			 */
+			if ( has_filter( 'widget_text_content', 'do_shortcode' ) && ! $widget_text_do_shortcode_priority ) {
+
+				if ( ! empty( $instance['filter'] ) ) {
+					$text = shortcode_unautop( $text );
+				}
+
+				$text = do_shortcode( $text );
+			}
+		}
 
-		} elseif ( ! empty( $instance['filter'] ) ) {
-			$text = wpautop( $text ); // Back-compat for instances prior to 4.8.
+		// Restore post global.
+		if ( isset( $suspended_post ) ) {
+			$post = $suspended_post;
 		}
 
-		// Undo temporary upgrade of the plugin-supplied shortcode handling.
-		if ( $should_upgrade_shortcode_handling ) {
-			remove_filter( 'widget_text_content', 'do_shortcode', 11 );
+		// Undo suspension of legacy plugin-supplied shortcode handling.
+		if ( $should_suspend_legacy_shortcode_support ) {
 			add_filter( 'widget_text', 'do_shortcode', $widget_text_do_shortcode_priority );
 		}
 
diff --git tests/phpunit/tests/widgets/text-widget.php tests/phpunit/tests/widgets/text-widget.php
index f7e810fd04..a3e348f8a6 100644
--- tests/phpunit/tests/widgets/text-widget.php
+++ tests/phpunit/tests/widgets/text-widget.php
@@ -222,7 +222,21 @@ class Test_WP_Widget_Text extends WP_UnitTestCase {
 	 *
 	 * @var string
 	 */
-	protected $example_shortcode_content = "<p>One\nTwo\n\nThree</p>\n<script>\ndocument.write('Test1');\n\ndocument.write('Test2');\n</script>";
+	protected $example_shortcode_content = "<p class='sortcodep'>One\nTwo\n\nThree\n\nThis is testing the <code>[example note='This will not get processed since it is part of shortcode output itself.']</code> shortcode.</p>\n<script>\ndocument.write('Test1');\n\ndocument.write('Test2');\n</script>";
+
+	/**
+	 * The captured global post during shortcode rendering.
+	 *
+	 * @var WP_Post|null
+	 */
+	protected $post_during_shortcode = null;
+
+	/**
+	 * Number of times the shortcode was rendered.
+	 *
+	 * @var int
+	 */
+	protected $shortcode_render_count = 0;
 
 	/**
 	 * Do example shortcode.
@@ -230,15 +244,21 @@ class Test_WP_Widget_Text extends WP_UnitTestCase {
 	 * @return string Shortcode content.
 	 */
 	function do_example_shortcode() {
+		$this->post_during_shortcode = get_post();
+		$this->shortcode_render_count++;
 		return $this->example_shortcode_content;
 	}
 
 	/**
-	 * Test widget method when a plugin has added shortcode support.
+	 * Test widget method with shortcodes.
 	 *
 	 * @covers WP_Widget_Text::widget
 	 */
 	function test_widget_shortcodes() {
+		global $post;
+		$post_id = $this->factory()->post->create();
+		$post = get_post( $post_id );
+
 		$args = array(
 			'before_title'  => '<h2>',
 			'after_title'   => "</h2>\n",
@@ -246,47 +266,100 @@ class Test_WP_Widget_Text extends WP_UnitTestCase {
 			'after_widget'  => "</section>\n",
 		);
 		$widget = new WP_Widget_Text();
-		add_filter( 'widget_text', 'do_shortcode' );
 		add_shortcode( 'example', array( $this, 'do_example_shortcode' ) );
 
 		$base_instance = array(
 			'title' => 'Example',
-			'text' => "This is an example:\n\n[example]",
+			'text' => "This is an example:\n\n[example]\n\nHello.",
 			'filter' => false,
 		);
 
-		// Legacy Text Widget.
+		// Legacy Text Widget without wpautop.
 		$instance = array_merge( $base_instance, array(
 			'filter' => false,
 		) );
+		$this->shortcode_render_count = 0;
 		ob_start();
 		$widget->widget( $args, $instance );
 		$output = ob_get_clean();
+		$this->assertEquals( 1, $this->shortcode_render_count );
+		$this->assertNotContains( '[example]', $output, 'Expected shortcode to be processed in legacy widget with plugin adding filter' );
 		$this->assertContains( $this->example_shortcode_content, $output, 'Shortcode was applied without wpautop corrupting it.' );
-		$this->assertEquals( 10, has_filter( 'widget_text', 'do_shortcode' ), 'Filter was restored.' );
+		$this->assertNotContains( '<p>' . $this->example_shortcode_content . '</p>', $output, 'Expected shortcode_unautop() to have run.' );
+		$this->assertNull( $this->post_during_shortcode );
 
-		// Visual Text Widget.
+		// Legacy Text Widget with wpautop.
 		$instance = array_merge( $base_instance, array(
-			'filter' => 'content',
+			'filter' => true,
+			'visual' => false,
 		) );
+		$this->shortcode_render_count = 0;
 		ob_start();
 		$widget->widget( $args, $instance );
 		$output = ob_get_clean();
+		$this->assertEquals( 1, $this->shortcode_render_count );
+		$this->assertNotContains( '[example]', $output, 'Expected shortcode to be processed in legacy widget with plugin adding filter' );
 		$this->assertContains( $this->example_shortcode_content, $output, 'Shortcode was applied without wpautop corrupting it.' );
-		$this->assertEquals( 10, has_filter( 'widget_text', 'do_shortcode' ), 'Filter was restored.' );
-		$this->assertFalse( has_filter( 'widget_text_content', 'do_shortcode' ), 'Filter was removed.' );
+		$this->assertNotContains( '<p>' . $this->example_shortcode_content . '</p>', $output, 'Expected shortcode_unautop() to have run.' );
+		$this->assertNull( $this->post_during_shortcode );
 
-		// Visual Text Widget with properly-used widget_text_content filter.
+		// Legacy text widget with plugin adding shortcode support as well.
+		add_filter( 'widget_text', 'do_shortcode' );
+		$this->shortcode_render_count = 0;
+		ob_start();
+		$widget->widget( $args, $instance );
+		$output = ob_get_clean();
+		$this->assertEquals( 1, $this->shortcode_render_count );
+		$this->assertNotContains( '[example]', $output, 'Expected shortcode to be processed in legacy widget with plugin adding filter' );
+		$this->assertContains( wpautop( $this->example_shortcode_content ), $output, 'Shortcode was applied *with* wpautop() applying to shortcode output since plugin used legacy filter.' );
+		$this->assertNull( $this->post_during_shortcode );
 		remove_filter( 'widget_text', 'do_shortcode' );
-		add_filter( 'widget_text_content', 'do_shortcode', 11 );
+
 		$instance = array_merge( $base_instance, array(
-			'filter' => 'content',
+			'filter' => true,
+			'visual' => true,
 		) );
+
+		// Visual Text Widget with only core-added widget_text_content filter for do_shortcode.
+		$this->assertFalse( has_filter( 'widget_text', 'do_shortcode' ) );
+		$this->assertEquals( 11, has_filter( 'widget_text_content', 'do_shortcode' ), 'Expected core to have set do_shortcode as widget_text_content filter.' );
+		$this->shortcode_render_count = 0;
 		ob_start();
 		$widget->widget( $args, $instance );
 		$output = ob_get_clean();
+		$this->assertEquals( 1, $this->shortcode_render_count );
 		$this->assertContains( $this->example_shortcode_content, $output, 'Shortcode was applied without wpautop corrupting it.' );
-		$this->assertFalse( has_filter( 'widget_text', 'do_shortcode' ), 'Filter was not erroneously restored.' );
+		$this->assertNotContains( '<p>' . $this->example_shortcode_content . '</p>', $output, 'Expected shortcode_unautop() to have run.' );
+		$this->assertFalse( has_filter( 'widget_text', 'do_shortcode' ), 'The widget_text filter still lacks do_shortcode handler.' );
+		$this->assertEquals( 11, has_filter( 'widget_text_content', 'do_shortcode' ), 'The widget_text_content filter still has do_shortcode handler.' );
+		$this->assertNull( $this->post_during_shortcode );
+
+		// Visual Text Widget with both filters applied added, one from core and another via plugin.
+		add_filter( 'widget_text', 'do_shortcode' );
+		$this->shortcode_render_count = 0;
+		ob_start();
+		$widget->widget( $args, $instance );
+		$output = ob_get_clean();
+		$this->assertEquals( 1, $this->shortcode_render_count );
+		$this->assertContains( $this->example_shortcode_content, $output, 'Shortcode was applied without wpautop corrupting it.' );
+		$this->assertNotContains( '<p>' . $this->example_shortcode_content . '</p>', $output, 'Expected shortcode_unautop() to have run.' );
+		$this->assertEquals( 10, has_filter( 'widget_text', 'do_shortcode' ), 'Expected do_shortcode to be restored to widget_text.' );
+		$this->assertNull( $this->post_during_shortcode );
+		$this->assertNull( $this->post_during_shortcode );
+		remove_filter( 'widget_text', 'do_shortcode' );
+
+		// Visual Text Widget with shortcode handling disabled via plugin removing filter.
+		remove_filter( 'widget_text_content', 'do_shortcode', 11 );
+		remove_filter( 'widget_text', 'do_shortcode' );
+		$this->shortcode_render_count = 0;
+		ob_start();
+		$widget->widget( $args, $instance );
+		$output = ob_get_clean();
+		$this->assertEquals( 0, $this->shortcode_render_count );
+		$this->assertContains( '[example]', $output );
+		$this->assertNotContains( $this->example_shortcode_content, $output );
+		$this->assertFalse( has_filter( 'widget_text', 'do_shortcode' ) );
+		$this->assertFalse( has_filter( 'widget_text_content', 'do_shortcode' ) );
 	}
 
 	/**
