├── README.md ├── phpunit.xml.dist ├── .gitignore ├── .editorconfig ├── composer.json ├── wp-lazy-loading.php ├── tests └── phpunit │ ├── bootstrap.php │ └── tests │ └── media.php ├── .travis.yml ├── readme.txt ├── functions.php └── LICENSE /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://img.shields.io/travis/com/WordPress/wp-lazy-loading/master.svg)](https://travis-ci.com/WordPress/wp-lazy-loading/) 2 | # Lazy Loading Feature Plugin 3 | 4 | WordPress feature plugin for testing and experimenting with automatically adding the "loading" HTML attribute. 5 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | ./tests/phpunit/tests/ 20 | 21 | 22 | 23 | 24 | 25 | ./src 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ############ 2 | ## IDEs 3 | ############ 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | *.swp 9 | *~.nib 10 | local.properties 11 | .classpath 12 | .settings/ 13 | .loadpath 14 | .externalToolBuilders/ 15 | *.launch 16 | .cproject 17 | .buildpath 18 | nbproject/ 19 | 20 | ############ 21 | ## Vendor 22 | ############ 23 | 24 | node_modules/ 25 | vendor/ 26 | 27 | ############ 28 | ## OSes 29 | ############ 30 | 31 | [Tt]humbs.db 32 | [Dd]esktop.ini 33 | *.DS_store 34 | .DS_store? 35 | 36 | ############ 37 | ## Misc 38 | ############ 39 | 40 | tests/logs 41 | bin/ 42 | tmp/ 43 | *.tmp 44 | *.bak 45 | *.log 46 | *.[Cc]ache 47 | *.cpr 48 | *.orig 49 | *.php.in 50 | .idea/ 51 | .sass-cache/* 52 | temp/ 53 | ._* 54 | .Trashes 55 | .svn 56 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # WordPress Coding Standards 2 | # https://make.wordpress.org/core/handbook/best-practices/coding-standards/ 3 | 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | indent_size = 4 10 | tab_width = 4 11 | indent_style = tab 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.txt] 21 | trim_trailing_whitespace = false 22 | 23 | [*.json] 24 | insert_final_newline = false 25 | indent_style = space 26 | indent_size = 2 27 | 28 | [.*rc] 29 | insert_final_newline = false 30 | indent_style = space 31 | indent_size = 2 32 | 33 | [*.yml] 34 | insert_final_newline = false 35 | indent_style = space 36 | indent_size = 2 37 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wordpress/wp-lazy-loading", 3 | "description": "WordPress feature plugin for testing and experimenting with automatically adding the loading HTML attribute.", 4 | "version": "1.0", 5 | "license": "GPL-2.0-or-later", 6 | "type": "wordpress-plugin", 7 | "keywords": [ 8 | "feature plugin", 9 | "lazy loading" 10 | ], 11 | "homepage": "https://wordpress.org/plugins/wp-lazy-loading/", 12 | "support": { 13 | "issues": "https://github.com/WordPress/wp-lazy-loading/issues" 14 | }, 15 | "config": { 16 | "platform": { 17 | "php": "5.6" 18 | } 19 | }, 20 | "require": { 21 | "php": ">=5.6" 22 | }, 23 | "require-dev": { 24 | "phpunit/phpunit": ">4.8.20 <6.0" 25 | }, 26 | "scripts": { 27 | "test": "phpunit" 28 | } 29 | } -------------------------------------------------------------------------------- /wp-lazy-loading.php: -------------------------------------------------------------------------------- 1 | array( basename( TESTS_PLUGIN_DIR ) . '/wp-lazy-loading.php' ), 41 | ); 42 | 43 | // Load the WordPress tests environment. 44 | require_once $test_root . '/includes/bootstrap.php'; 45 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | dist: xenial 3 | language: php 4 | services: 5 | - mysql 6 | cache: 7 | directories: 8 | - vendor 9 | - $HOME/.composer/cache 10 | matrix: 11 | include: 12 | - php: 7.4 13 | env: TEST=1 WP_VERSION=latest 14 | - php: 5.6 15 | env: TEST=1 WP_VERSION=latest 16 | dist: trusty 17 | - php: 7.4 18 | env: TEST=1 WP_VERSION=master 19 | allow_failures: 20 | - php: 7.4 21 | env: TEST=1 WP_VERSION=master 22 | before_install: 23 | - composer install 24 | before_script: 25 | - | 26 | if [[ "$TEST" == "1" ]]; then 27 | if [[ "$WP_VERSION" == "latest" ]]; then 28 | curl -s http://api.wordpress.org/core/version-check/1.7/ > /tmp/wp-latest.json 29 | WP_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') 30 | fi 31 | PLUGIN_SLUG=$(basename $(pwd)) 32 | export WP_DEVELOP_DIR=/tmp/wordpress/ 33 | git clone --depth=50 --branch="$WP_VERSION" git://develop.git.wordpress.org/ /tmp/wordpress 34 | cd .. 35 | cp -r "$PLUGIN_SLUG" "/tmp/wordpress/src/wp-content/plugins/$PLUGIN_SLUG" 36 | cd /tmp/wordpress/ 37 | cp wp-tests-config-sample.php wp-tests-config.php 38 | sed -i "s/youremptytestdbnamehere/wordpress_tests/" wp-tests-config.php 39 | sed -i "s/yourusernamehere/travis/" wp-tests-config.php 40 | sed -i "s/yourpasswordhere//" wp-tests-config.php 41 | mysql -e "CREATE DATABASE wordpress_tests;" -uroot 42 | cd "/tmp/wordpress/src/wp-content/plugins/$PLUGIN_SLUG" 43 | fi 44 | - phpenv rehash 45 | script: 46 | - | 47 | if [[ "$TEST" == "1" ]]; then 48 | composer run-script test 49 | fi 50 | notifications: 51 | email: false -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === Lazy Loading Feature Plugin === 2 | Contributors: wordpressdotorg, azaozz, flixos90 3 | Tags: feature plugin, lazy loading 4 | Requires at least: 5.3 5 | Tested up to: 5.4 6 | Stable tag: 1.1 7 | Requires PHP: 5.6.20 8 | License: GPLv2 or later 9 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 10 | 11 | WordPress feature plugin for testing and experimenting with automatically adding the "loading" HTML attribute. Not for production use. 12 | 13 | == Description == 14 | 15 | Lazy Loading Feature Plugin is an official plugin maintained by the WordPress team. It is intended for testing of automatically adding the `loading` HTML attribute to images and other elements that support it. 16 | 17 | More information about the `loading` attribute: 18 | Description: [https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-loading](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-loading). 19 | HTML Specification: [https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-loading](https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-loading). 20 | 21 | Currently the `loading` attribute is supported in the following browsers: [https://caniuse.com/#feat=loading-lazy-attr](https://caniuse.com/#feat=loading-lazy-attr). 22 | 23 | To test, install and enable the plugin. It will automatically add `loading="lazy"` attributes to all images in all new and existing posts, pages, and text widgets on the front-end. 24 | 25 | Then use one of the browsers that support it (Chrome, Opera, Firefox, Edge, Android, etc.) and visit the site. Best would be to test over a slower connection, with a phone, etc. and test web pages that have a lot of images, like gallery posts. 26 | 27 | = Things to look for = 28 | 29 | * Obvious bugs, for example images are missing. 30 | * Try to scroll down as soon as the page loads. All images should be at their places, and the page shouldn't "jump" when images are loaded. 31 | 32 | = Note to developers = 33 | 34 | This plugin is intended for testing. If the tests are successful, this functionality will be added to WordPress, but the exact code may change, perhaps significantly. 35 | 36 | When testing, please also test the filters added by this plugin, and provide feedback at [https://github.com/WordPress/wp-lazy-loading](https://github.com/WordPress/wp-lazy-loading) or at [https://core.trac.wordpress.org/ticket/44427](https://core.trac.wordpress.org/ticket/44427). 37 | 38 | == Changelog == 39 | 40 | Please see the Github repository: [https://github.com/WordPress/wp-lazy-loading](https://github.com/WordPress/wp-lazy-loading). 41 | -------------------------------------------------------------------------------- /tests/phpunit/tests/media.php: -------------------------------------------------------------------------------- 1 | attachment->create_upload_object( $filename ); 13 | } 14 | 15 | public static function wpTearDownAfterClass() { 16 | wp_delete_post( self::$large_id, true ); 17 | self::$large_id = null; 18 | } 19 | 20 | /** 21 | * @ticket 44427 22 | */ 23 | function test_wp_lazy_load_content_media() { 24 | $img = get_image_tag( self::$large_id, '', '', '', 'medium' ); 25 | $img_xhtml = str_replace( ' />', '/>', $img ); 26 | $img_html5 = str_replace( ' />', '>', $img ); 27 | $iframe = ''; 28 | 29 | $lazy_img = str_replace( '', ' loading="eager" />', $img ); 35 | 36 | $content = ' 37 |

Image, standard.

38 | %1$s 39 | 40 |

Image, XHTML 1.0 style (no space before the closing slash).

41 | %2$s 42 | 43 |

Image, HTML 5.0 style.

44 | %3$s 45 | 46 |

Image, with pre-existing "loading" attribute.

47 | %5$s 48 | 49 |

Iframe, standard. Should not be modified.

50 | %4$s'; 51 | 52 | $content_unfiltered = sprintf( $content, $img, $img_xhtml, $img_html5, $iframe, $img_eager ); 53 | $content_filtered = sprintf( $content, $lazy_img, $lazy_img_xhtml, $lazy_img_html5, $iframe, $img_eager ); 54 | 55 | // Do not add srcset and sizes while testing. 56 | add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' ); 57 | 58 | $this->assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) ); 59 | 60 | remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' ); 61 | } 62 | 63 | /** 64 | * @ticket 44427 65 | */ 66 | function test_wp_lazy_load_content_media_opted_in() { 67 | $img = get_image_tag( self::$large_id, '', '', '', 'medium' ); 68 | 69 | $lazy_img = str_replace( 'assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) ); 85 | remove_filter( 'wp_lazy_loading_enabled', '__return_true' ); 86 | remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' ); 87 | } 88 | 89 | /** 90 | * @ticket 44427 91 | */ 92 | function test_wp_lazy_load_content_media_opted_out() { 93 | $img = get_image_tag( self::$large_id, '', '', '', 'medium' ); 94 | 95 | $content = ' 96 |

Image, standard.

97 | %1$s'; 98 | $content = sprintf( $content, $img ); 99 | 100 | // Do not add srcset and sizes while testing. 101 | add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' ); 102 | 103 | // Disable globally for all tags. 104 | add_filter( 'wp_lazy_loading_enabled', '__return_false' ); 105 | 106 | $this->assertSame( $content, wp_filter_content_tags( $content ) ); 107 | remove_filter( 'wp_lazy_loading_enabled', '__return_false' ); 108 | remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' ); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /functions.php: -------------------------------------------------------------------------------- 1 | ]+>/', $content, $matches ) ) { 129 | return $content; 130 | } 131 | 132 | // List of the unique `img` tags found in $content. 133 | $images = array(); 134 | 135 | foreach ( $matches[0] as $image ) { 136 | if ( preg_match( '/wp-image-([0-9]+)/i', $image, $class_id ) ) { 137 | $attachment_id = absint( $class_id[1] ); 138 | 139 | if ( $attachment_id ) { 140 | /* 141 | * If exactly the same image tag is used more than once, overwrite it. 142 | * All identical tags will be replaced later with 'str_replace()'. 143 | */ 144 | $images[ $image ] = $attachment_id; 145 | continue; 146 | } 147 | } 148 | 149 | $images[ $image ] = 0; 150 | } 151 | 152 | // Reduce the array to unique attachment IDs. 153 | $attachment_ids = array_unique( array_filter( array_values( $images ) ) ); 154 | 155 | if ( count( $attachment_ids ) > 1 ) { 156 | /* 157 | * Warm the object cache with post and meta information for all found 158 | * images to avoid making individual database calls. 159 | */ 160 | _prime_post_caches( $attachment_ids, false, true ); 161 | } 162 | 163 | foreach ( $images as $image => $attachment_id ) { 164 | $filtered_image = $image; 165 | 166 | // Add 'srcset' and 'sizes' attributes if applicable. 167 | if ( $attachment_id > 0 && false === strpos( $filtered_image, ' srcset=' ) ) { 168 | $filtered_image = wp_img_tag_add_srcset_and_sizes_attr( $filtered_image, $context, $attachment_id ); 169 | } 170 | 171 | // Add 'loading' attribute if applicable. 172 | if ( $add_loading_attr && false === strpos( $filtered_image, ' loading=' ) ) { 173 | $filtered_image = wp_img_tag_add_loading_attr( $filtered_image, $context ); 174 | } 175 | 176 | if ( $filtered_image !== $image ) { 177 | $content = str_replace( $image, $filtered_image, $content ); 178 | } 179 | } 180 | 181 | return $content; 182 | } 183 | 184 | /** 185 | * Adds `loading` attribute to an existing `img` HTML tag. 186 | * 187 | * @since (TBD) 188 | * 189 | * @param string $image The HTML `img` tag where the attribute should be added. 190 | * @param string $context Additional context to pass to the filters. 191 | * @return string Converted `img` tag with `loading` attribute added. 192 | */ 193 | function wp_img_tag_add_loading_attr( $image, $context ) { 194 | /** 195 | * Filters the `loading` attribute value. Default `lazy`. 196 | * 197 | * Returning `false` or an empty string will not add the attribute. 198 | * Returning `true` will add the default value. 199 | * 200 | * @since (TBD) 201 | * 202 | * @param string $value The 'loading' attribute value, defaults to `lazy`. 203 | * @param string $image The HTML 'img' element to be filtered. 204 | * @param string $context Additional context about how the function was called or where the img tag is. 205 | */ 206 | $value = apply_filters( 'wp_img_tag_add_loading_attr', 'lazy', $image, $context ); 207 | 208 | if ( $value ) { 209 | if ( ! in_array( $value, array( 'lazy', 'eager' ), true ) ) { 210 | $value = 'lazy'; 211 | } 212 | 213 | return str_replace( ' 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | --------------------------------------------------------------------------------