You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
349 lines
12 KiB
349 lines
12 KiB
<?php |
|
/** |
|
* AMP Service Workers. |
|
* |
|
* @package AMP |
|
* @since 1.1 |
|
*/ |
|
|
|
/** |
|
* Class AMP_Service_Worker. |
|
*/ |
|
class AMP_Service_Worker { |
|
|
|
/** |
|
* Query var that is used to signal a request to install the service worker in an iframe. |
|
* |
|
* @link https://www.ampproject.org/docs/reference/components/amp-install-serviceworker#data-iframe-src-(optional) |
|
*/ |
|
const INSTALL_SERVICE_WORKER_IFRAME_QUERY_VAR = 'amp_install_service_worker_iframe'; |
|
|
|
/** |
|
* Init. |
|
*/ |
|
public static function init() { |
|
if ( ! class_exists( 'WP_Service_Workers' ) ) { |
|
return; |
|
} |
|
|
|
// Shim support for service worker installation from PWA feature plugin. |
|
add_filter( 'query_vars', [ __CLASS__, 'add_query_var' ] ); |
|
add_action( 'parse_request', [ __CLASS__, 'handle_service_worker_iframe_install' ] ); |
|
add_action( 'wp', [ __CLASS__, 'add_install_hooks' ] ); |
|
|
|
$theme_support = AMP_Theme_Support::get_theme_support_args(); |
|
if ( isset( $theme_support['service_worker'] ) && false === $theme_support['service_worker'] ) { |
|
return; |
|
} |
|
|
|
/* |
|
* The default-enabled options reflect which features are not commented-out in the AMP-by-Example service worker. |
|
* See <https://github.com/ampproject/amp-by-example/blob/e093edb401b1617859b5365e80b639d81b06f058/boilerplate-generator/templates/files/serviceworkerJs.js>. |
|
*/ |
|
$enabled_options = [ |
|
'cdn_script_caching' => true, |
|
'image_caching' => false, |
|
'google_fonts_caching' => false, |
|
]; |
|
if ( isset( $theme_support['service_worker'] ) && is_array( $theme_support['service_worker'] ) ) { |
|
$enabled_options = array_merge( |
|
$enabled_options, |
|
$theme_support['service_worker'] |
|
); |
|
} |
|
|
|
if ( $enabled_options['cdn_script_caching'] ) { |
|
add_action( 'wp_front_service_worker', [ __CLASS__, 'add_cdn_script_caching' ] ); |
|
} |
|
if ( $enabled_options['image_caching'] ) { |
|
add_action( 'wp_front_service_worker', [ __CLASS__, 'add_image_caching' ] ); |
|
} |
|
if ( $enabled_options['google_fonts_caching'] ) { |
|
add_action( 'wp_front_service_worker', [ __CLASS__, 'add_google_fonts_caching' ] ); |
|
} |
|
} |
|
|
|
/** |
|
* Add query var for iframe service worker request. |
|
* |
|
* @param array $vars Query vars. |
|
* @return array Amended query vars. |
|
*/ |
|
public static function add_query_var( $vars ) { |
|
$vars[] = self::INSTALL_SERVICE_WORKER_IFRAME_QUERY_VAR; |
|
return $vars; |
|
} |
|
|
|
/** |
|
* Add runtime caching for scripts loaded from the AMP CDN with a stale-while-revalidate strategy. |
|
* |
|
* @link https://github.com/ampproject/amp-by-example/blob/4593af61609898043302a101826ddafe7206bfd9/boilerplate-generator/templates/files/serviceworkerJs.js |
|
* |
|
* @param WP_Service_Worker_Scripts $service_workers Service worker registry. |
|
*/ |
|
public static function add_cdn_script_caching( $service_workers ) { |
|
if ( ! ( $service_workers instanceof WP_Service_Worker_Scripts ) ) { |
|
/* translators: %s: WP_Service_Worker_Cache_Registry. */ |
|
_doing_it_wrong( __METHOD__, sprintf( esc_html__( 'Please update to PWA v0.2. Expected argument to be %s.', 'amp' ), 'WP_Service_Worker_Cache_Registry' ), '1.1' ); |
|
return; |
|
} |
|
|
|
// Add AMP scripts to runtime cache which will then get stale-while-revalidate strategy. |
|
$service_workers->register( |
|
'amp-cdn-runtime-caching', |
|
static function() { |
|
$urls = AMP_Service_Worker::get_precached_script_cdn_urls(); |
|
if ( empty( $urls ) ) { |
|
return ''; |
|
} |
|
|
|
$js = file_get_contents( AMP__DIR__ . '/assets/js/amp-service-worker-runtime-precaching.js' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents, WordPress.WP.AlternativeFunctions.file_system_read_file_get_contents |
|
$js = preg_replace( '#/\*\s*global.+?\*/#', '', $js ); |
|
$js = str_replace( |
|
'URLS', |
|
wp_json_encode( $urls ), |
|
$js |
|
); |
|
return $js; |
|
} |
|
); |
|
|
|
// Serve the AMP Runtime from cache and check for an updated version in the background. See <https://github.com/ampproject/amp-by-example/blob/4593af61609898043302a101826ddafe7206bfd9/boilerplate-generator/templates/files/serviceworkerJs.js#L54-L58>. |
|
$service_workers->caching_routes()->register( |
|
'^https:\/\/cdn\.ampproject\.org\/.*', |
|
[ |
|
'strategy' => WP_Service_Worker_Caching_Routes::STRATEGY_STALE_WHILE_REVALIDATE, |
|
] |
|
); |
|
} |
|
|
|
/** |
|
* Add runtime image caching from the origin with a cache-first strategy. |
|
* |
|
* @link https://github.com/ampproject/amp-by-example/blob/4593af61609898043302a101826ddafe7206bfd9/boilerplate-generator/templates/files/serviceworkerJs.js#L60-L74 |
|
* |
|
* @param WP_Service_Worker_Scripts $service_workers Service workers. |
|
*/ |
|
public static function add_image_caching( $service_workers ) { |
|
if ( ! ( $service_workers instanceof WP_Service_Worker_Scripts ) ) { |
|
_doing_it_wrong( __METHOD__, esc_html__( 'Please update to PWA v0.2. Expected argument to be WP_Service_Worker_Scripts.', 'amp' ), '1.1' ); |
|
return; |
|
} |
|
|
|
$service_workers->caching_routes()->register( |
|
'^' . preg_quote( set_url_scheme( content_url( '/' ), 'https' ), '/' ) . '[^\?]+?\.(?:png|gif|jpg|jpeg|svg|webp)(\?.*)?$', |
|
[ |
|
'strategy' => WP_Service_Worker_Caching_Routes::STRATEGY_CACHE_FIRST, |
|
'cacheName' => 'images', |
|
'plugins' => [ |
|
'cacheableResponse' => [ |
|
'statuses' => [ 0, 200 ], |
|
], |
|
'expiration' => [ |
|
'maxEntries' => 60, |
|
'maxAgeSeconds' => MONTH_IN_SECONDS, |
|
], |
|
], |
|
] |
|
); |
|
} |
|
|
|
/** |
|
* Add runtime caching of Google Fonts with stale-while-revalidate strategy for stylesheets and cache-first strategy for webfont files. |
|
* |
|
* @link https://developers.google.com/web/tools/workbox/guides/common-recipes#google_fonts |
|
* @link https://github.com/ampproject/amp-by-example/blob/4593af61609898043302a101826ddafe7206bfd9/boilerplate-generator/templates/files/serviceworkerJs.js#L76-L103 |
|
* @link https://github.com/xwp/pwa-wp/blob/master/integrations/class-wp-service-worker-fonts-integration.php |
|
* |
|
* @param WP_Service_Worker_Scripts $service_workers Service workers. |
|
*/ |
|
public static function add_google_fonts_caching( $service_workers ) { |
|
if ( ! ( $service_workers instanceof WP_Service_Worker_Scripts ) ) { |
|
_doing_it_wrong( __METHOD__, esc_html__( 'Please update to PWA v0.2. Expected argument to be WP_Service_Worker_Scripts.', 'amp' ), '1.1' ); |
|
return; |
|
} |
|
|
|
// The PWA plugin also automatically adds runtime caching for Google Fonts when WP_SERVICE_WORKER_INTEGRATIONS_ENABLED is set. |
|
if ( class_exists( 'WP_Service_Worker_Fonts_Integration' ) ) { |
|
return; |
|
} |
|
|
|
// Cache the Google Fonts stylesheets with a stale while revalidate strategy. |
|
$service_workers->caching_routes()->register( |
|
'^https:\/\/fonts\.googleapis\.com', |
|
[ |
|
'strategy' => WP_Service_Worker_Caching_Routes::STRATEGY_STALE_WHILE_REVALIDATE, |
|
'cacheName' => 'google-fonts-stylesheets', |
|
] |
|
); |
|
|
|
// Cache the Google Fonts webfont files with a cache first strategy for 1 year. |
|
$service_workers->caching_routes()->register( |
|
'^https:\/\/fonts\.gstatic\.com', |
|
[ |
|
'strategy' => WP_Service_Worker_Caching_Routes::STRATEGY_CACHE_FIRST, |
|
'cacheName' => 'google-fonts-webfonts', |
|
'plugins' => [ |
|
'cacheableResponse' => [ |
|
'statuses' => [ 0, 200 ], |
|
], |
|
'expiration' => [ |
|
'maxAgeSeconds' => YEAR_IN_SECONDS, |
|
'maxEntries' => 30, |
|
], |
|
], |
|
] |
|
); |
|
} |
|
|
|
/** |
|
* Register URLs that will be precached in the runtime cache. (Yes, this sounds somewhat strange.) |
|
* |
|
* Note that the PWA plugin handles the precaching of custom logo, custom header, |
|
* and custom background. The PWA plugin also handles precaching & serving of the |
|
* offline/500 error pages and enabling navigation preload. |
|
* |
|
* @link https://github.com/ampproject/amp-by-example/blob/4593af61609898043302a101826ddafe7206bfd9/boilerplate-generator/templates/files/serviceworkerJs.js#L9-L22 |
|
* @see AMP_Service_Worker::add_cdn_script_caching() |
|
* |
|
* @return array Runtime pre-cached URLs. |
|
*/ |
|
public static function get_precached_script_cdn_urls() { |
|
|
|
// List of AMP scripts that we know will be used in WordPress always. |
|
$precached_handles = [ |
|
'amp-runtime', |
|
'amp-bind', // Used by comments. |
|
'amp-form', // Used by comments. |
|
'amp-install-serviceworker', |
|
]; |
|
|
|
$theme_support = AMP_Theme_Support::get_theme_support_args(); |
|
if ( ! empty( $theme_support['comments_live_list'] ) ) { |
|
$precached_handles[] = 'amp-live-list'; |
|
} |
|
|
|
if ( amp_get_analytics() ) { |
|
$precached_handles[] = 'amp-analytics'; |
|
} |
|
|
|
$urls = []; |
|
foreach ( $precached_handles as $handle ) { |
|
if ( wp_script_is( $handle, 'registered' ) ) { |
|
$urls[] = wp_scripts()->registered[ $handle ]->src; |
|
} |
|
} |
|
|
|
return $urls; |
|
} |
|
|
|
/** |
|
* Add hooks to install the service worker from AMP page. |
|
*/ |
|
public static function add_install_hooks() { |
|
if ( current_theme_supports( 'amp' ) && is_amp_endpoint() ) { |
|
add_action( 'wp_footer', [ __CLASS__, 'install_service_worker' ] ); |
|
|
|
// Prevent validation error due to the script that installs the service worker on non-AMP pages. |
|
foreach ( [ 'wp_print_scripts', 'wp_print_footer_scripts' ] as $action ) { |
|
$priority = has_action( $action, 'wp_print_service_workers' ); |
|
if ( false !== $priority ) { |
|
remove_action( $action, 'wp_print_service_workers', $priority ); |
|
} |
|
} |
|
} |
|
|
|
// Reader mode integration. |
|
add_action( 'amp_post_template_footer', [ __CLASS__, 'install_service_worker' ] ); |
|
add_filter( |
|
'amp_post_template_data', |
|
static function ( $data ) { |
|
$data['amp_component_scripts']['amp-install-serviceworker'] = true; |
|
return $data; |
|
} |
|
); |
|
} |
|
|
|
/** |
|
* Install service worker(s). |
|
* |
|
* @since 1.1 |
|
* @see wp_print_service_workers() |
|
* @link https://github.com/xwp/pwa-wp |
|
*/ |
|
public static function install_service_worker() { |
|
if ( ! function_exists( 'wp_service_workers' ) || ! function_exists( 'wp_get_service_worker_url' ) ) { |
|
return; |
|
} |
|
|
|
$src = wp_get_service_worker_url( WP_Service_Workers::SCOPE_FRONT ); |
|
$iframe_src = add_query_arg( |
|
self::INSTALL_SERVICE_WORKER_IFRAME_QUERY_VAR, |
|
WP_Service_Workers::SCOPE_FRONT, |
|
home_url( '/', 'https' ) |
|
); |
|
?> |
|
<amp-install-serviceworker |
|
src="<?php echo esc_url( $src ); ?>" |
|
data-iframe-src="<?php echo esc_url( $iframe_src ); ?>" |
|
layout="nodisplay" |
|
> |
|
</amp-install-serviceworker> |
|
<?php |
|
} |
|
|
|
/** |
|
* Handle request to install service worker via iframe. |
|
* |
|
* @see wp_print_service_workers() |
|
* @link https://www.ampproject.org/docs/reference/components/amp-install-serviceworker#data-iframe-src-(optional) |
|
*/ |
|
public static function handle_service_worker_iframe_install() { |
|
if ( ! isset( $GLOBALS['wp']->query_vars[ self::INSTALL_SERVICE_WORKER_IFRAME_QUERY_VAR ] ) ) { |
|
return; |
|
} |
|
|
|
$scope = (int) $GLOBALS['wp']->query_vars[ self::INSTALL_SERVICE_WORKER_IFRAME_QUERY_VAR ]; |
|
if ( WP_Service_Workers::SCOPE_ADMIN !== $scope && WP_Service_Workers::SCOPE_FRONT !== $scope ) { |
|
wp_die( |
|
esc_html__( 'No service workers registered for the requested scope.', 'amp' ), |
|
esc_html__( 'Service Worker Installation', 'amp' ), |
|
[ 'response' => 404 ] |
|
); |
|
} |
|
|
|
$front_scope = home_url( '/', 'relative' ); |
|
|
|
?> |
|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<title><?php esc_html_e( 'Service Worker Installation', 'amp' ); ?></title> |
|
</head> |
|
<body> |
|
<?php esc_html_e( 'Installing service worker...', 'amp' ); ?> |
|
<?php |
|
printf( |
|
'<script>navigator.serviceWorker.register( %s, %s );</script>', |
|
wp_json_encode( wp_get_service_worker_url( $scope ) ), |
|
wp_json_encode( [ 'scope' => $front_scope ] ) |
|
); |
|
?> |
|
</body> |
|
</html> |
|
<?php |
|
|
|
// Die in a way that can be unit tested. |
|
add_filter( |
|
'wp_die_handler', |
|
static function() { |
|
return static function() { |
|
die(); |
|
}; |
|
}, |
|
1 |
|
); |
|
wp_die(); |
|
} |
|
}
|
|
|