CINXE.COM

Chromium Blog: April 2021

<!DOCTYPE html> <html class='v2 list-page' dir='ltr' itemscope='' itemtype='http://schema.org/Blog' lang='en' xmlns='http://www.w3.org/1999/xhtml' xmlns:b='http://www.google.com/2005/gml/b' xmlns:data='http://www.google.com/2005/gml/data' xmlns:expr='http://www.google.com/2005/gml/expr'> <head> <link href='https://www.blogger.com/static/v1/widgets/3566091532-css_bundle_v2.css' rel='stylesheet' type='text/css'/> <title> Chromium Blog: April 2021 </title> <meta content='width=device-width, height=device-height, initial-scale=1.0' name='viewport'/> <meta content='IE=Edge' http-equiv='X-UA-Compatible'/> <meta content='Chromium Blog' property='og:title'/> <meta content='en_US' property='og:locale'/> <meta content='https://blog.chromium.org/2021/04/' property='og:url'/> <meta content='Chromium Blog' property='og:site_name'/> <!-- Twitter Card properties --> <meta content='Chromium Blog' property='og:title'/> <meta content='summary' name='twitter:card'/> <meta content='@ChromiumDev' name='twitter:creator'/> <link href='https://fonts.googleapis.com/css?family=Roboto:400italic,400,500,500italic,700,700italic' rel='stylesheet' type='text/css'/> <link href='https://fonts.googleapis.com/icon?family=Material+Icons' rel='stylesheet'/> <script src='https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js' type='text/javascript'></script> <!-- End --> <style id='page-skin-1' type='text/css'><!-- /* <Group description="Header Color" selector="header"> <Variable name="header.background.color" description="Header Background" type="color" default="#ffffff"/> </Group> */ .header-outer { border-bottom: 1px solid #e0e0e0; background: #ffffff; } html, .Label h2, #sidebar .rss a, .BlogArchive h2, .FollowByEmail h2.title, .widget .post h2 { font-family: Roboto, sans-serif; } .plusfollowers h2.title, .post h2.title, .widget h2.title { font-family: Roboto, sans-serif; } .widget-item-control { height: 100%; } .widget.Header, #header { position: relative; height: 100%; width: 100%; } } .widget.Header .header-logo1 { float: left; margin-right: 15px; padding-right: 15px; border-right: 1px solid #ddd; } .header-title h2 { color: rgba(0,0,0,.54); display: inline-block; font-size: 40px; font-family: Roboto, sans-serif; font-weight: normal; line-height: 76px; vertical-align: top; } .header-inner { background-repeat: no-repeat; background-position: right 0px; } .post-author, .byline-author { font-size: 14px; font-weight: normal; color: #757575; color: rgba(0,0,0,.54); } .post-content .img-border { border: 1px solid rgb(235, 235, 235); padding: 4px; } .header-title a { text-decoration: none !important; } pre { border: 1px solid #bbbbbb; margin-top: 1em 0 0 0; padding: 0.99em; overflow-x: auto; overflow-y: auto; } pre, code { font-size: 9pt; background-color: #fafafa; line-height: 125%; font-family: monospace; } pre, code { color: #060; font: 13px/1.54 "courier new",courier,monospace; } .header-left .header-logo1 { width: 128px !important; } .header-desc { line-height: 20px; margin-top: 8px; } .fb-custom img, .twitter-custom img, .gplus-share img { cursor: pointer; opacity: 0.54; } .fb-custom img:hover, .twitter-custom img:hover, .gplus-share img:hover { opacity: 0.87; } .fb-like { width: 80px; } .post .share { float: right; } #twitter-share{ border: #CCC solid 1px; border-radius: 3px; background-image: -webkit-linear-gradient(top,#ffffff,#dedede); } .twitter-follow { background: url(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEimKBWDeRb1pqsbNiP9AFLyFDZHzXGYEJZRELMrZ6iI0yz4KeMPH_7tPsrMq9PpJ3H6riC_UohpWMn83_Z1N2sTuTTrVL7i6TrNzpO9oFg4e8VFK4zFJb1rfamWfc8RxG8Fhz2RgRgHN10u/s1600/twitter-bird.png) no-repeat left center; padding-left: 18px; font: normal normal normal 11px/18px 'Helvetica Neue',Arial,sans-serif; font-weight: bold; text-shadow: 0 1px 0 rgba(255,255,255,.5); cursor: pointer; margin-bottom: 10px; } .twitter-fb { padding-top: 2px; } .fb-follow-button { background: -webkit-linear-gradient(#4c69ba, #3b55a0); background: -moz-linear-gradient(#4c69ba, #3b55a0); background: linear-gradient(#4c69ba, #3b55a0); border-radius: 2px; height: 18px; padding: 4px 0 0 3px; width: 57px; border: #4c69ba solid 1px; } .fb-follow-button a { text-decoration: none !important; text-shadow: 0 -1px 0 #354c8c; text-align: center; white-space: nowrap; font-size: 11px; color: white; vertical-align: top; } .fb-follow-button a:visited { color: white; } .fb-follow { padding: 0px 5px 3px 0px; width: 14px; vertical-align: bottom; } .gplus-wrapper { margin-top: 3px; display: inline-block; vertical-align: top; } .twitter-custom, .gplus-share { margin-right: 12px; } .fb-follow-button{ margin: 10px auto; } /** CUSTOM CODE **/ --></style> <style id='template-skin-1' type='text/css'><!-- .header-outer { clear: both; } .header-inner { margin: auto; padding: 0px; } .footer-outer { background: #f5f5f5; clear: both; margin: 0; } .footer-inner { margin: auto; padding: 0px; } .footer-inner-2 { /* Account for right hand column elasticity. */ max-width: calc(100% - 248px); } .google-footer-outer { clear: both; } .cols-wrapper, .google-footer-outer, .footer-inner, .header-inner { max-width: 978px; margin-left: auto; margin-right: auto; } .cols-wrapper { margin: auto; clear: both; margin-top: 60px; margin-bottom: 60px; overflow: hidden; } .col-main-wrapper { float: left; width: 100%; } .col-main { margin-right: 278px; max-width: 660px; } .col-right { float: right; width: 248px; margin-left: -278px; } /* Tweaks for layout mode. */ body#layout .google-footer-outer { display: none; } body#layout .header-outer, body#layout .footer-outer { background: none; } body#layout .header-inner { height: initial; } body#layout .cols-wrapper { margin-top: initial; margin-bottom: initial; } --></style> <!-- start all head --> <meta content='text/html; charset=UTF-8' http-equiv='Content-Type'/> <meta content='blogger' name='generator'/> <link href='https://blog.chromium.org/favicon.ico' rel='icon' type='image/x-icon'/> <link href='https://blog.chromium.org/2021/04/' rel='canonical'/> <link rel="alternate" type="application/atom+xml" title="Chromium Blog - Atom" href="https://blog.chromium.org/feeds/posts/default" /> <link rel="alternate" type="application/rss+xml" title="Chromium Blog - RSS" href="https://blog.chromium.org/feeds/posts/default?alt=rss" /> <link rel="service.post" type="application/atom+xml" title="Chromium Blog - Atom" href="https://www.blogger.com/feeds/2471378914199150966/posts/default" /> <!--Can't find substitution for tag [blog.ieCssRetrofitLinks]--> <meta content='https://blog.chromium.org/2021/04/' property='og:url'/> <meta content='Chromium Blog' property='og:title'/> <meta content='News and developments from the open source browser project' property='og:description'/> <!-- end all head --> <base target='_self'/> <style> html { font-family: Roboto, sans-serif; -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; } body { padding: 0; /* This ensures that the scroll bar is always present, which is needed */ /* because content render happens after page load; otherwise the header */ /* would "bounce" in-between states. */ min-height: 150%; } h2 { font-size: 16px; } h1, h2, h3, h4, h5 { line-height: 2em; } html, h4, h5, h6 { font-size: 14px; } a, a:visited { /* Ensures links meet minimum contrast ratios. */ color: #3974d6; text-decoration: none; } a:focus, a:hover, a:active { text-decoration: none; } .Header { margin-top: 15px; } .Header h1 { font-size: 32px; font-weight: 300; line-height: 32px; height: 42px; } .header-inner .Header .titlewrapper { padding: 0; margin-top: 30px; } .header-inner .Header .descriptionwrapper { padding: 0; margin: 0; } .cols-wrapper { margin-top: 56px; } .header-outer, .cols-wrapper, .footer-outer, .google-footer-outer { padding: 0 60px; } .header-inner { height: 256px; position: relative; } html, .header-inner a { color: #212121; color: rgba(0,0,0,.87); } .header-inner .google-logo { display: inline-block; background-size: contain; z-index: 1; height: 70px; overflow: hidden; margin-top: 4px; margin-right: 8px; } .header-left { position: absolute; top: 50%; -webkit-transform: translateY(-50%); transform: translateY(-50%); margin-top: 12px; width: 100%; } .google-logo { margin-left: -4px; } .google-logo img{ height:70px; } #google-footer { position: relative; font-size: 13px; list-style: none; text-align: right; } #google-footer a { color: #444; } #google-footer ul { margin: 0; padding: 0; height: 144px; line-height: 144px; } #google-footer ul li { display: inline; } #google-footer ul li:before { color: #999; content: "\00b7"; font-weight: bold; margin: 5px; } #google-footer ul li:first-child:before { content: ''; } #google-footer .google-logo-dark { left: 0; margin-top: -16px; position: absolute; top: 50%; } /** Sitemap links. **/ .footer-inner-2 { font-size: 14px; padding-top: 42px; padding-bottom: 74px; } .footer-inner-2 .HTML h2 { color: #212121; color: rgba(0,0,0,.87); font-size: 14px; font-weight: 500; padding-left: 0; margin: 10px 0; } .footer-inner-2 .HTML ul { font-weight: normal; list-style: none; padding-left: 0; } .footer-inner-2 .HTML li { line-height: 24px; padding: 0; } .footer-inner-2 li a { color: rgba(65,132,243,.87); } /** Archive widget. **/ .BlogArchive { font-size: 13px; font-weight: normal; } .BlogArchive .widget-content { display: none; } .BlogArchive h2, .Label h2 { color: #4184F3; text-decoration: none; } .BlogArchive .hierarchy li { display: inline-block; } /* Specificity needed here to override widget CSS defaults. */ .BlogArchive #ArchiveList ul li, .BlogArchive #ArchiveList ul ul li { margin: 0; padding-left: 0; text-indent: 0; } .BlogArchive .intervalToggle { cursor: pointer; } .BlogArchive .expanded .intervalToggle .new-toggle { -ms-transform: rotate(180deg); transform: rotate(180deg); } .BlogArchive .new-toggle { float: right; padding-top: 3px; opacity: 0.87; } #ArchiveList { text-transform: uppercase; } #ArchiveList .expanded > ul:last-child { margin-bottom: 16px; } #ArchiveList .archivedate { width: 100%; } /* Months */ .BlogArchive .items { max-width: 150px; margin-left: -4px; } .BlogArchive .expanded .items { margin-bottom: 10px; overflow: hidden; } .BlogArchive .items > ul { float: left; height: 32px; } .BlogArchive .items a { padding: 0 4px; } .Label { font-size: 13px; font-weight: normal; } .sidebar-icon { display: inline-block; width: 24px; height: 24px; vertical-align: middle; margin-right: 12px; margin-top: -1px } .Label a { margin-right: 4px; } .Label .widget-content { display: none; } .FollowByEmail { font-size: 13px; font-weight: normal; } .FollowByEmail h2 { background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAALCAYAAACZIGYHAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAUBJREFUeNrMkSGLAlEUhb+ZB4JFi8mx2cz+ApvhRUGTcUCrNqNJDYIi+DO0GUwmQXDK2DSIoGgZcSaIjDrzwrK4ssvChj1w0733O+fdp+m6PozH4yQSCfb7Pa7r8pOi0SjJZBLP8zgej4gAIMvlMuPxmADIYrHger1+C6lUKmo+NJ/NZojb7SZDWiwWo1qtks1msW2bw+HwZdkwDHq9HvV6nel0SqvVYrvdIh6Ph3Qch+VyqRYLhQJSSjRNw7IsfN9XgGKxSLfbJZfL0e/3aTabrFYr7vc7IujLcOh8PqunrNdr0uk0pVKJVCpFJBJRgEajweVyod1uMxgM2O12BAGUgRbU8DV2JpOhVquRz+cRQii3+XxOp9NRN3jVR5LPOp1OjEYjlSL8hclkgmmabDabt4d+m+S30vkD/R/IU4ABAPTZgnZdmG/PAAAAAElFTkSuQmCC"); background-repeat: no-repeat; background-position: 0 50%; text-indent: 30px; } .FollowByEmail .widget-content { display: none; } .searchBox input { border: 1px solid #eee; color: #212121; color: rgba(0,0,0,.87); font-size: 14px; padding: 8px 8px 8px 40px; width: 164px; font-family: Roboto, sans-serif; background: url("https://www.gstatic.com/images/icons/material/system/1x/search_grey600_24dp.png") 8px center no-repeat; } .searchBox ::-webkit-input-placeholder { /* WebKit, Blink, Edge */ color: rgba(0,0,0,.54); } .searchBox :-moz-placeholder { /* Mozilla Firefox 4 to 18 */ color: #000; opacity: 0.54; } .searchBox ::-moz-placeholder { /* Mozilla Firefox 19+ */ color: #000; opacity: 0.54; } .searchBox :-ms-input-placeholder { /* Internet Explorer 10-11 */ color: #757575; } .widget-item-control { margin-top: 0px; } .section { margin: 0; padding: 0; } #sidebar-top { border: 1px solid #eee; } #sidebar-top > div { margin: 16px 0; } .widget ul { line-height: 1.6; } /*main post*/ .post { margin-bottom:30px; } #main .post .title { margin: 0; } #main .post .title a { color: #212121; color: rgba(0,0,0,.87); font-weight: normal; font-size: 24px; } #main .post .title a:hover { text-decoration:none; color: #3974d6; } .message, #main .post .post-header { margin: 0; padding: 0; } #main .post .post-header .caption, #main .post .post-header .labels-caption, #main .post .post-footer .caption, #main .post .post-footer .labels-caption { color: #444; font-weight: 500; } #main .tr-caption-container td { text-align: left; } #main .post .tr-caption { color: #757575; color: rgba(0,0,0,.54); display: block; max-width: 560px; padding-bottom: 20px; } #main .post .tr-caption-container { line-height: 24px; margin: -1px 0 0 0 !important; padding: 4px 0; text-align: left; } #main .post .post-header .published{ font-size:11px; font-weight:bold; } .post-header .publishdate { font-size: 17px; font-weight:normal; color: #757575; color: rgba(0,0,0,.54); } #main .post .post-footer{ font-size:12px; padding-bottom: 21px; } .label-footer { margin-bottom: 12px; margin-top: 12px; } .comment-img { margin-right: 16px; opacity: 0.54; vertical-align: middle; } #main .post .post-header .published { margin-bottom: 40px; margin-top: -2px; } .post .post-content { color: #212121; color: rgba(0,0,0,.87); font-size: 17px; margin: 25px 0 36px 0; line-height: 32px; } .post-body .post-content ul, .post-body .post-content ol { margin: 16px 0; padding: 0 48px; } .post-summary { display: none; } /* Another old-style caption. */ .post-content div i, .post-content div + i { font-size: 14px; font-style: normal; color: #757575; color: rgba(0,0,0,.54); display: block; line-height: 24px; margin-bottom: 16px; text-align: left; } /* Another old-style caption (with link) */ .post-content a > i { color: #4184F3 !important; } /* Old-style captions for images. */ .post-content .separator + div:not(.separator) { margin-top: -16px; } /* Capture section headers. */ .post-content br + br + b, .post-content .space + .space + b, .post-content .separator + b { display: inline-block; margin-bottom: 8px; margin-top: 24px; } .post-content li { line-height: 32px; } /* Override all post images/videos to left align. */ .post-content .separator, .post-content > div { text-align: left; } .post-content .separator > a, .post-content .separator > span { margin-left: 0 !important; } .post-content img { max-width: 100%; height: auto; width: auto; } .post-content .tr-caption-container img { margin-bottom: 12px; } .post-content iframe, .post-content embed { max-width: 100%; } .post-content .carousel-container { margin-bottom: 48px; } #main .post-content b { font-weight: 500; } /* These are the main paragraph spacing tweaks. */ #main .post-content br { content: ' '; display: block; padding: 4px; } .post-content .space { display: block; height: 8px; } .post-content iframe + .space, .post-content iframe + br { padding: 0 !important; } #main .post .jump-link { margin-bottom:10px; } .post-content img, .post-content iframe { margin: 30px 0 20px 0; } .post-content > img:first-child, .post-content > iframe:first-child { margin-top: 0; } .col-right .section { padding: 0 16px; } #aside { background:#fff; border:1px solid #eee; border-top: 0; } #aside .widget { margin:0; } #aside .widget h2, #ArchiveList .toggle + a.post-count-link { color: #212121; color: rgba(0,0,0,.87); font-weight: 400 !important; margin: 0; } #ArchiveList .toggle { float: right; } #ArchiveList .toggle .material-icons { padding-top: 4px; } #sidebar .tab { cursor: pointer; } #sidebar .tab .arrow { display: inline-block; float: right; } #sidebar .tab .icon { display: inline-block; vertical-align: top; height: 24px; width: 24px; margin-right: 13px; margin-left: -1px; margin-top: 1px; color: #757575; color: rgba(0,0,0,.54); } #sidebar .widget-content > :first-child { padding-top: 8px; } #sidebar .active .tab .arrow { -ms-transform: rotate(180deg); transform: rotate(180deg); } #sidebar .arrow { color: #757575; color: rgba(0,0,0,.54); } #sidebar .widget h2 { font-size: 14px; line-height: 24px; display: inline-block; } #sidebar .widget .BlogArchive { padding-bottom: 8px; } #sidebar .widget { border-bottom: 1px solid #eee; box-shadow: 0px 1px 0 white; margin-bottom: 0; padding: 14px 0; min-height: 20px; } #sidebar .widget:last-child { border-bottom: none; box-shadow: none; margin-bottom: 0; } #sidebar ul { margin: 0; padding: 0; } #sidebar ul li { list-style:none; padding:0; } #sidebar ul li a { line-height: 32px; } #sidebar .archive { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAYCAYAAADzoH0MAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAE1JREFUeNpiNDY23s9AAWBioBCwYBM8c+YMVsUmJibEGYBNMS5DaeMFfDYSZQA2v9I3FrB5AZeriI4FmnrBccCT8mhmGs1MwyAzAQQYAKEWG9zm9QFEAAAAAElFTkSuQmCC"); height: 24px; line-height: 24px; padding-left: 30px; } #sidebar .labels { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAARCAYAAAA7bUf6AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAUxJREFUeNpiNDY23s9AAMycOfM7UF05kHkZmzwTMkdSUhKrIcXFxZy3bt3qBjIN8RrS09PDsHnzZjCNDr58+cKQlpbGDjSoHcg1w2oIyAUODg5gARCNzUVIBrUCuVYYhjx//pzhwIEDYAEQDeJjA1CDWIAGNQK59jBxRuSABbkAlwHIgIeHh2HWrFn/1NTU2oDcvSgBS4wBSC5iArqoCsj1YGIgEyAZVMoEchqlBjEB/cZAiUHg2AEGznpKDAImxOeM////B4VLKtBvEUCngZ1ILKivr3/u6+ubBzJAGZQ9gC5aQoqLgAY8BhkAZL4BuQQkxgXE34A4BuiiZEIuAhrwEGhAEZD5DpzYoIaA2UAM4kQADUrHZRDUgAIg8wO2XAwzbQXQa5OweQ1owB10AyA6gS7BgX1u3ry5397eHow3bdo0EyjGi00tQIABANPgyAH1q1eaAAAAAElFTkSuQmCC"); height: 20px; line-height: 20px; padding-left: 30px; } #sidebar .rss a { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAX5JREFUeNqsVDGSgkAQHL2rIiIikohIc/EBRkbwAIwuwgfwAXiAD9AHSI7kEkECRCb6AIyINDLx7K0aa6kT7uq0q7YYtnZ7umdnt7darXbr9Zpegeu61DNNc0dvwCcH4/GYJpMJnc9nOhwOVJbl/4hAAokMECZJQtvt9k+kH7qufyEYDAakqqqYxFdRFBqNRmTbNg2HQ0rTlK7XayvR0xqBdDqdkuM4dE/0ULhYLOh4PHYrknG5XGi/31MYhuL/nkwonM1mlGUZ1XXdrsiyLGEDhY7juJEZ1u5tIixDGdYhmYw+B7CAzPP5nDabjdgIAgCksMX1832/3drtdqPT6SQWapomiGEFNkDEdpDMMAzK81ys/7XYy+XyoQgq2WoURSIJ2iIIgp/WZCCTvFm2wgeAU31aI3Q2GhIDMeB53qPYPIcm5VrxXIOIOxsDMStjVawAc1VViRgN22lNBiuQN3GR+SY07hpOoStmFQAKXRRFY93bnpG+fONfedi+BRgAbkS8Fxp7QQIAAAAASUVORK5CYII="); } #sidebar .subscription a { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAALCAYAAACZIGYHAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAUBJREFUeNrMkSGLAlEUhb+ZB4JFi8mx2cz+ApvhRUGTcUCrNqNJDYIi+DO0GUwmQXDK2DSIoGgZcSaIjDrzwrK4ssvChj1w0733O+fdp+m6PozH4yQSCfb7Pa7r8pOi0SjJZBLP8zgej4gAIMvlMuPxmADIYrHger1+C6lUKmo+NJ/NZojb7SZDWiwWo1qtks1msW2bw+HwZdkwDHq9HvV6nel0SqvVYrvdIh6Ph3Qch+VyqRYLhQJSSjRNw7IsfN9XgGKxSLfbJZfL0e/3aTabrFYr7vc7IujLcOh8PqunrNdr0uk0pVKJVCpFJBJRgEajweVyod1uMxgM2O12BAGUgRbU8DV2JpOhVquRz+cRQii3+XxOp9NRN3jVR5LPOp1OjEYjlSL8hclkgmmabDabt4d+m+S30vkD/R/IU4ABAPTZgnZdmG/PAAAAAElFTkSuQmCC"); } #sidebar-bottom { background: #f5f5f5; border-top:1px solid #eee; } #sidebar-bottom .widget { border-bottom: 1px solid #e0e0e0; padding: 15px 0; text-align: center; } #sidebar-bottom > div:last-child { border-bottom: 0; } #sidebar-bottom .text { line-height: 20px; } /* Home, forward, and backward pagination. */ .blog-pager { border-top : 1px #e0e0e0 solid; padding-top: 10px; margin-top: 15px; text-align: right !important; } #blog-pager { margin-botom: 0; margin-top: -14px; padding: 16px 0 0 0; } #blog-pager a { display: inline-block; } .blog-pager i.disabled { opacity: 0.2 !important; } .blog-pager i { color: black; margin-left: 16px; opacity: 0.54; } .blog-pager i:hover, .blog-pager i:active { opacity: 0.87; } #blog-pager-older-link, #blog-pager-newer-link { float: none; } .gplus-profile { background-color: #fafafa; border: 1px solid #eee; overflow: hidden; width: 212px; } .gplus-profile-inner { margin-left: -1px; margin-top: -1px; } /* Sidebar follow buttons. */ .followgooglewrapper { padding: 12px 0 0 0; } .loading { visibility: hidden; } .detail-page .post-footer .cmt_iframe_holder { padding-top: 40px !important; } /** Desktop **/ @media (max-width: 900px) { .col-right { display: none; } .col-main { margin-right: 0; min-width: initial; } .footer-outer { display: none; } .cols-wrapper { min-width: initial; } .google-footer-outer { background-color: #f5f5f5; } } /** Tablet **/ @media (max-width: 712px) { .header-outer, .cols-wrapper, .footer-outer, .google-footer-outer { padding: 0 40px; } } /* An extra breakpoint accommodating for long blog titles. */ @media (max-width: 600px) { .header-left { height: 100%; position: initial; top: inherit; margin-top: 0; -webkit-transform: initial; transform: initial; } .header-title { margin-top: 18px; } .header-inner { height: auto; margin-bottom: 32px; margin-top: 32px; } .header-desc { margin-top: 12px; } .header-inner .google-logo { height: 70px; margin-top: 3px; } .header-inner .google-logo img { height: 70px; } .header-title h2 { font-size: 32px; line-height: 76px; } } /** Mobile/small desktop window; also landscape. **/ @media (max-width: 480px), (max-height: 480px) { .header-outer, .cols-wrapper, .footer-outer, .google-footer-outer { padding: 0 16px; } .cols-wrapper { margin-top: 0; } .post-header .publishdate, .post .post-content { font-size: 16px; } .post .post-content { line-height: 28px; margin-bottom: 30px; } .post { margin-top: 30px; } .byline-author { display: block; font-size: 12px; line-height: 24px; margin-top: 6px; } #main .post .title a { font-weight: 500; color: #4c4c4c; color: rgba(0,0,0,.70); } #main .post .post-header { padding-bottom: 12px; } #main .post .post-header .published { margin-bottom: -8px; margin-top: 3px; } .post .read-more { display: block; margin-top: 14px; } .post .tr-caption { font-size: 12px; } #main .post .title a { font-size: 20px; line-height: 30px; } .post-content iframe { /* iframe won't keep aspect ratio when scaled down. */ max-height: 240px; } .post-content .separator img, .post-content .tr-caption-container img, .post-content iframe { margin-left: -16px; max-width: inherit; width: calc(100% + 32px); } .post-content table, .post-content td { width: 100%; } #blog-pager { margin: 0; padding: 16px 0; } /** List page tweaks. **/ .list-page .post-original { display: none; } .list-page .post-summary { display: block; } .list-page .comment-container { display: none; } .list-page #blog-pager { padding-top: 0; border: 0; margin-top: -8px; } .list-page .label-footer { display: none; } .list-page #main .post .post-footer { border-bottom: 1px solid #eee; margin: -16px 0 0 0; padding: 0 0 20px 0; } .list-page .post .share { display: none; } /** Detail page tweaks. **/ .detail-page .post-footer .cmt_iframe_holder { padding-top: 32px !important; } .detail-page .label-footer { margin-bottom: 0; } .detail-page #main .post .post-footer { padding-bottom: 0; } .detail-page #comments { display: none; } } [data-about-pullquote], [data-is-preview], [data-about-syndication] { display: none; } </style> <noscript> <style> .loading { visibility: visible }</style> </noscript> <script type='text/javascript'> (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); ga('create', 'UA-37592578-1', 'auto', 'blogger'); ga('blogger.send', 'pageview'); </script> <link href='https://www.blogger.com/dyn-css/authorization.css?targetBlogID=2471378914199150966&amp;zx=dd422b0a-07e2-4e80-8415-27bb05d534cd' media='none' onload='if(media!=&#39;all&#39;)media=&#39;all&#39;' rel='stylesheet'/><noscript><link href='https://www.blogger.com/dyn-css/authorization.css?targetBlogID=2471378914199150966&amp;zx=dd422b0a-07e2-4e80-8415-27bb05d534cd' rel='stylesheet'/></noscript> <meta name='google-adsense-platform-account' content='ca-host-pub-1556223355139109'/> <meta name='google-adsense-platform-domain' content='blogspot.com'/> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?display=swap&family=Roboto"></head> <body> <script type='text/javascript'> //<![CDATA[ var axel = Math.random() + ""; var a = axel * 10000000000000; document.write('<iframe src="https://2542116.fls.doubleclick.net/activityi;src=2542116;type=gblog;cat=googl0;ord=ord=' + a + '?" width="1" height="1" frameborder="0" style="display:none"></iframe>'); //]]> </script> <noscript> <img alt='' height='1' src='https://ad.doubleclick.net/ddm/activity/src=2542116;type=gblog;cat=googl0;ord=1?' width='1'/> </noscript> <!-- Header --> <div class='header-outer'> <div class='header-inner'> <div class='section' id='header'><div class='widget Header' data-version='1' id='Header1'> <div class='header-left'> <div class='header-title'> <a class='google-logo' href='https://blog.chromium.org/'> <img alt="Chromium Blog" height="50" src="//1.bp.blogspot.com/-vkF7AFJOwBk/VkQxeAGi1mI/AAAAAAAARYo/57denvsQ8zA/s1600-r/logo_chromium.png"> </a> <a href='/.'> <h2> Chromium Blog </h2> </a> </div> <div class='header-desc'> News and developments from the open source browser project </div> </div> </div></div> </div> </div> <!-- all content wrapper start --> <div class='cols-wrapper loading'> <div class='col-main-wrapper'> <div class='col-main'> <div class='section' id='main'><div class='widget Blog' data-version='1' id='Blog1'> <div class='post' data-id='83084614319231284' itemscope='' itemtype='http://schema.org/BlogPosting'> <h2 class='title' itemprop='name'> <a href='https://blog.chromium.org/2021/04/chrome-91-handwriting-recognition-webxr.html' itemprop='url' title='Chrome 91: Handwriting Recognition, WebXR Plane Detection and More'> Chrome 91: Handwriting Recognition, WebXR Plane Detection and More </a> </h2> <div class='post-header'> <div class='published'> <span class='publishdate' itemprop='datePublished'> Thursday, April 22, 2021 </span> </div> </div> <div class='post-body'> <div class='post-content' itemprop='articleBody'> <script type='text/template'> <p>Unless otherwise noted, changes described below apply to the newest Chrome beta channel release for Android, Chrome OS, Linux, macOS, and Windows. Learn more about the features listed here through the provided links or from the list on <a href="https://www.chromestatus.com/features#milestone%3D76">ChromeStatus.com</a>. Chrome 91 is beta as of April 22, 2021.</p> <h2>Origin Trials</h2> <p>This version of Chrome introduces the origin trials described below. Origin trials allow you to try new features and give feedback on usability, practicality, and effectiveness to the web standards community. To register for any of the origin trials currently supported in Chrome, including the ones described below, visit the <a href="https://developers.chrome.com/origintrials/#/trials/active">Chrome Origin Trials dashboard</a>. To learn more about origin trials in Chrome, visit the <a href="https://web.dev/origin-trials/">Origin Trials Guide for Web Developers</a>. Microsoft Edge runs its own origin trials separate from Chrome. To learn more, see the <a href="https://developer.microsoft.com/en-us/microsoft-edge/origin-trials/">Microsoft Edge Origin Trials Developer Console</a>. </p> <h3>New Origin Trials</h3> <h4>Declarative Link Capturing for PWAs</h4> <p>The new Web App Manifest member called <code>capture_links</code> controls what happens when the user navigates to a page within scope of an installed web app. It allows sites to automatically open a new PWA window when the user clicks a link to their app or to have a single window mode like mobile apps. <a href="https://developer.chrome.com/origintrials/#/view_trial/4285175045443026945">Sign up for the origin trial</a> and learn more on the origin trial dashboard.</p> <h4>WebTransport</h4> <p>WebTransport is a protocol framework that enables clients constrained by the Web security model to communicate with a remote server using a secure multiplexed transport.</p> <p>Currently, Web application developers have two APIs for bidirectional communications with a remote server: <code>WebSockets</code> and <code>RTCDataChannel</code>. <code>WebSockets</code> are TCP-based, thus having all of the drawbacks of TCP (head of line blocking, lack of support for unreliable data transport) that make it a poor fit for latency-sensitive applications. <code>RTCDataChannel</code> is based on the Stream Control Transmission Protocol (SCTP), which does not have these drawbacks; however, it is designed to be used in a peer-to-peer context, which causes its use in client-server settings to be fairly low. <code>WebTransport</code> provides a client-server API that supports bidirectional transfer of both unreliable and reliable data, using UDP-like datagrams and cancellable streams. <code>WebTransport</code> calls are visible in the Network panel of DevTools and identified as such in the Type column. </p> <p>For more information, see <a href="https://web.dev/webtransport/">Experimenting with WebTransport</a>. <a href="https://www.google.com/url?q=https://developer.chrome.com/origintrials/%23/view_trial/793759434324049921&amp;sa=D&amp;source=editors&amp;ust=1619104452220000&amp;usg=AOvVaw2ge7dt3GG_6ucYabUaGf_l">Sign up for the origin trial</a> and learn more on the origin trial dashboard.</p> <h4>WebXR Plane Detection API</h4> <p>WebXR applications can now retrieve data about planes (flat surfaces) in the user's environment, allowing better user experiences with less processing power. Without this feature plane detection requires custom computer vision algorithms using data from <code>MediaDevices.getUserMedia()</code>. These solutions usually fall short of quality and accuracy expectations for AR experiences and don't support world scale. <a href="https://developer.chrome.com/origintrials/#/view_trial/1154047404513689601">Sign up for the origin tria</a>l and learn more on the dashboard.</p> <h3>Completed Origin Trials</h3> <p>The following features, previously in a Chrome origin trial, are now enabled by default.</p> <h4>WebAssembly SIMD</h4> <p>WebAssembly SIMD exposes hardware SIMD instructions to WebAssembly applications in a platform-independent way. This introduces a new 128-bit type that can represent different types of packed data, and several vector operations that work on packed data. SIMD can boost performance by exploiting data level parallelism and is also useful when compiling native code to WebAssembly. For more information, see the V8 feature explainer for<a href="https://v8.dev/features/simd"> WebAssembly SIMD</a>.</p> <h2>Other features in this release</h2> <h3>Align performance API timer resolution to cross-origin isolated capability</h3> <p>Coarsening of <code>performance.now()</code> and related timestamps based on site isolation status is <a href="https://developer.chrome.com/blog/cross-origin-isolated-hr-timers">now consistent across platforms</a>. This decreases the resolution on desktop from 5 microseconds to 100 microseconds in non-isolated contexts. It also increases their resolution on Android from 100 microseconds to 5 microseconds in cross-origin isolated contexts, where it's safe to do so.</p> <h3>Clipboard: Read-Only Files Support</h3> <p>On desktop, apps can now <a href="https://www.chromestatus.com/features/5671807392677888">read files from the clipboard</a> (but not write files to the clipboard). For files on the clipboard, apps have read-only access.</p> <pre>async function onPaste(e) { let file = e.clipboardData.files[0]; let contents = await file.text(); } </pre> <h3>CSS</h3> <h4>Custom Counter Styles</h4> <p>The <a href="https://www.chromestatus.com/feature/5692693659254784">CSS <code>@counter-style</code> rule</a> allows web authors to specify and use custom counter styles in list markers and CSS counters. This helps internationalization. This change implements all of the features in <a href="https://drafts.csswg.org/css-counter-styles-3">CSS Counter Styles Level 3</a> except:</p> <ul> <li>Image symbols, which no browsers support, and is 'at-risk' per the spec</li> <li>The <code>speak-as</code> descriptor, which is an accessibility feature </li> <li>The <code>symbols()</code> function.</li> </ul> <h4>Single &lt;compound-selector&gt; for :host() and :host-context()</h4> <p>The <code>:host()</code> and <code>:host-context()</code> pseudo-classes <a href="https://www.chromestatus.com/feature/5755183847964672">now accept a single <code>&lt;compound-selector&gt;</code></a> in addition to a <code>&lt;compound-selector-list&gt;</code>.</p> <h3>Form Controls Visual Refresh on Android</h3> <p>Form controls have a new, refreshed appearance, with better accessibility and touch support. This was a collaboration between Microsoft and Google, and if you'd like additional information, you can view a <a href="https://www.youtube.com/watch?v=ZFvPLrKZywA">past CDS talk</a> or the <a href="https://blogs.windows.com/msedgedev/2019/10/15/form-controls-microsoft-edge-chromium/">Microsoft's blog post</a>.</p> <p>In this release, we have brought the same form controls UX to Android as already launched on other platforms. The new form controls include automatically darkening form controls and scrollbars when in dark mode.</p> <p>Dark mode is an accessibility feature that allows web authors to enable their web pages to be viewed in dark mode. When enabled, users are able to view dark mode supported websites by toggling the dark mode settings on their Android devices. dark mode is easier on the eyes in a low light environment and lowers battery consumption.</p> <h3>GravitySensor Interface</h3> <p>The <code>GravitySensor</code> interface provides <a href="https://www.chromestatus.com/feature/5384099747332096">a three-axis reading of the gravity force</a>. It's already possible to derive readings close to those provided by this interface removing the <code>LinerAccelerometer</code> reading from the <code>Accelerometer</code> reading.</p> <h3>Suggested file name and location for the File System Access API</h3> <p>When using the File System Access API, web apps can now <a href="https://www.chromestatus.com/feature/6013006146174976">suggest the name and location of a file or directory</a> that is being created or loaded. This provides a better user experience and brings web apps closer to the behavior of system apps. For more about the File System Access API, see <a href="https://web.dev/file-system-access/">The File System Access API: simplifying access to local files</a>.</p> <h3>WebOTP API: cross-origin iframe support</h3> <p>The WebOTP API is now <a href="https://web.dev/web-otp-iframe">usable in cross-origin iframes</a> when enabled by a permission policy. The WebOTP API gives developers the ability to programmatically read one time codes from specially-formatted SMS messages addressed to their origin to reduce user friction. Many sites embed iframes that handle authentication.</p> <h3>WebSockets over HTTP/2</h3> <p>Chrome supports <a href="https://www.chromestatus.com/feature/6251293127475200">WebSockets over HTTP/2</a> in Chromium as specified in <a href="https://tools.ietf.org/html/rfc8441">RFC 8441</a>. This is only used for secure WebSockets requests, and only when there is already an HTTP/2 connection where the server has already advertised support for WebSockets over HTTP/2 via the HTTP/2 SETTINGS parameter defined in the specification.</p> <h3>Credentials sharing for sites affiliated with Digital Asset Links</h3> <p>Since 2015 developers have used Digital Asset Links (DALs) to associate Android apps with websites to assist users with logging in. If you employ multiple domains that share the same account management backend, you can now also associate them with one another to enable users to <a href="https://developer.chrome.com/blog/site-affiliation/">save credentials once and have the Chrome password manager suggest them to any of the affiliated websites</a>. For more information, see <a href="https://developer.chrome.com/blog/site-affiliation/">Enable Chrome to share login credentials across affiliated sites</a>.</p> <h2>JavaScript</h2> <p>This version of Chrome incorporates version 9.1 of the V8 JavaScript engine. It specifically includes the changes listed below. You can find a complete <a href="https://v8.dev/blog">list of recent features</a> in the V8 release notes.</p> <h3>ES Modules for service workers ('module' type option)</h3> <p>JavaScript now supports <a href="https://www.chromestatus.com/feature/4609574738853888">modules in service workers</a>. Setting <code>'module'</code> type by the constructor's type attribute, worker scripts are loaded as ES modules and the <code>import</code> statement is available on worker contexts. With this feature, web developers can more easily write programs in a composable way and share them among a page and workers.</p> <h3>Checks for Private Fields</h3> <p>Developers can now<a href="https://www.chromestatus.com/feature/5006138707804160"> test for the existence of private fields</a> in an object using the syntax <code>#foo in obj</code>.</p> <h2>Deprecations, and Removals</h2> <p>This version of Chrome introduces the deprecation listed below. Visit ChromeStatus.com for lists of <a href="https://www.chromestatus.com/features#browsers.chrome.status:%22Removed%22">previous removals</a>.</p> <h3 style="text-align: left;"><span style="font-size: 18.72px;">Remove alert(), confirm(), and prompt() for Cross Origin iframes</span></h3> <p>Chrome allows iframes to trigger Javascript dialogs. For example it shows &#8220;&lt;URL&gt; says ...&#8221; when the iframe is the same origin as the top frame, and &#8220;An embedded page on this page says...&#8221; when the iframe is cross-origin. This is confusing, and has led to spoofs where sites pretend the message comes from Chrome or a different website.&nbsp;</p><p>Chrome 91 deprecates this ability.&nbsp;<a href="https://chromestatus.com/feature/5148698084376576">Removing support</a> for cross origin iframes&#8217; ability to call <code>alert()</code>, <code>confirm()</code>, and <code>prompt()</code> will prevent this kind of spoofing, and unblock further UI simplifications. For example, this means notexample.com will no longer be able to call <code>window.alert()</code>, <code>window.prompt()</code>, or <code>window.confirm()</code> if embedded in an iframe on example.com.</p> <span itemprop='author' itemscope='itemscope' itemtype='http://schema.org/Person'> <meta content='https://plus.google.com/116899029375914044550' itemprop='url'/> </span> </script> <noscript> <p>Unless otherwise noted, changes described below apply to the newest Chrome beta channel release for Android, Chrome OS, Linux, macOS, and Windows. Learn more about the features listed here through the provided links or from the list on <a href="https://www.chromestatus.com/features#milestone%3D76">ChromeStatus.com</a>. Chrome 91 is beta as of April 22, 2021.</p> <h2>Origin Trials</h2> <p>This version of Chrome introduces the origin trials described below. Origin trials allow you to try new features and give feedback on usability, practicality, and effectiveness to the web standards community. To register for any of the origin trials currently supported in Chrome, including the ones described below, visit the <a href="https://developers.chrome.com/origintrials/#/trials/active">Chrome Origin Trials dashboard</a>. To learn more about origin trials in Chrome, visit the <a href="https://web.dev/origin-trials/">Origin Trials Guide for Web Developers</a>. Microsoft Edge runs its own origin trials separate from Chrome. To learn more, see the <a href="https://developer.microsoft.com/en-us/microsoft-edge/origin-trials/">Microsoft Edge Origin Trials Developer Console</a>. </p> <h3>New Origin Trials</h3> <h4>Declarative Link Capturing for PWAs</h4> <p>The new Web App Manifest member called <code>capture_links</code> controls what happens when the user navigates to a page within scope of an installed web app. It allows sites to automatically open a new PWA window when the user clicks a link to their app or to have a single window mode like mobile apps. <a href="https://developer.chrome.com/origintrials/#/view_trial/4285175045443026945">Sign up for the origin trial</a> and learn more on the origin trial dashboard.</p> <h4>WebTransport</h4> <p>WebTransport is a protocol framework that enables clients constrained by the Web security model to communicate with a remote server using a secure multiplexed transport.</p> <p>Currently, Web application developers have two APIs for bidirectional communications with a remote server: <code>WebSockets</code> and <code>RTCDataChannel</code>. <code>WebSockets</code> are TCP-based, thus having all of the drawbacks of TCP (head of line blocking, lack of support for unreliable data transport) that make it a poor fit for latency-sensitive applications. <code>RTCDataChannel</code> is based on the Stream Control Transmission Protocol (SCTP), which does not have these drawbacks; however, it is designed to be used in a peer-to-peer context, which causes its use in client-server settings to be fairly low. <code>WebTransport</code> provides a client-server API that supports bidirectional transfer of both unreliable and reliable data, using UDP-like datagrams and cancellable streams. <code>WebTransport</code> calls are visible in the Network panel of DevTools and identified as such in the Type column. </p> <p>For more information, see <a href="https://web.dev/webtransport/">Experimenting with WebTransport</a>. <a href="https://www.google.com/url?q=https://developer.chrome.com/origintrials/%23/view_trial/793759434324049921&amp;sa=D&amp;source=editors&amp;ust=1619104452220000&amp;usg=AOvVaw2ge7dt3GG_6ucYabUaGf_l">Sign up for the origin trial</a> and learn more on the origin trial dashboard.</p> <h4>WebXR Plane Detection API</h4> <p>WebXR applications can now retrieve data about planes (flat surfaces) in the user's environment, allowing better user experiences with less processing power. Without this feature plane detection requires custom computer vision algorithms using data from <code>MediaDevices.getUserMedia()</code>. These solutions usually fall short of quality and accuracy expectations for AR experiences and don't support world scale. <a href="https://developer.chrome.com/origintrials/#/view_trial/1154047404513689601">Sign up for the origin tria</a>l and learn more on the dashboard.</p> <h3>Completed Origin Trials</h3> <p>The following features, previously in a Chrome origin trial, are now enabled by default.</p> <h4>WebAssembly SIMD</h4> <p>WebAssembly SIMD exposes hardware SIMD instructions to WebAssembly applications in a platform-independent way. This introduces a new 128-bit type that can represent different types of packed data, and several vector operations that work on packed data. SIMD can boost performance by exploiting data level parallelism and is also useful when compiling native code to WebAssembly. For more information, see the V8 feature explainer for<a href="https://v8.dev/features/simd"> WebAssembly SIMD</a>.</p> <h2>Other features in this release</h2> <h3>Align performance API timer resolution to cross-origin isolated capability</h3> <p>Coarsening of <code>performance.now()</code> and related timestamps based on site isolation status is <a href="https://developer.chrome.com/blog/cross-origin-isolated-hr-timers">now consistent across platforms</a>. This decreases the resolution on desktop from 5 microseconds to 100 microseconds in non-isolated contexts. It also increases their resolution on Android from 100 microseconds to 5 microseconds in cross-origin isolated contexts, where it's safe to do so.</p> <h3>Clipboard: Read-Only Files Support</h3> <p>On desktop, apps can now <a href="https://www.chromestatus.com/features/5671807392677888">read files from the clipboard</a> (but not write files to the clipboard). For files on the clipboard, apps have read-only access.</p> <pre>async function onPaste(e) { let file = e.clipboardData.files[0]; let contents = await file.text(); } </pre> <h3>CSS</h3> <h4>Custom Counter Styles</h4> <p>The <a href="https://www.chromestatus.com/feature/5692693659254784">CSS <code>@counter-style</code> rule</a> allows web authors to specify and use custom counter styles in list markers and CSS counters. This helps internationalization. This change implements all of the features in <a href="https://drafts.csswg.org/css-counter-styles-3">CSS Counter Styles Level 3</a> except:</p> <ul> <li>Image symbols, which no browsers support, and is 'at-risk' per the spec</li> <li>The <code>speak-as</code> descriptor, which is an accessibility feature </li> <li>The <code>symbols()</code> function.</li> </ul> <h4>Single &lt;compound-selector&gt; for :host() and :host-context()</h4> <p>The <code>:host()</code> and <code>:host-context()</code> pseudo-classes <a href="https://www.chromestatus.com/feature/5755183847964672">now accept a single <code>&lt;compound-selector&gt;</code></a> in addition to a <code>&lt;compound-selector-list&gt;</code>.</p> <h3>Form Controls Visual Refresh on Android</h3> <p>Form controls have a new, refreshed appearance, with better accessibility and touch support. This was a collaboration between Microsoft and Google, and if you'd like additional information, you can view a <a href="https://www.youtube.com/watch?v=ZFvPLrKZywA">past CDS talk</a> or the <a href="https://blogs.windows.com/msedgedev/2019/10/15/form-controls-microsoft-edge-chromium/">Microsoft's blog post</a>.</p> <p>In this release, we have brought the same form controls UX to Android as already launched on other platforms. The new form controls include automatically darkening form controls and scrollbars when in dark mode.</p> <p>Dark mode is an accessibility feature that allows web authors to enable their web pages to be viewed in dark mode. When enabled, users are able to view dark mode supported websites by toggling the dark mode settings on their Android devices. dark mode is easier on the eyes in a low light environment and lowers battery consumption.</p> <h3>GravitySensor Interface</h3> <p>The <code>GravitySensor</code> interface provides <a href="https://www.chromestatus.com/feature/5384099747332096">a three-axis reading of the gravity force</a>. It's already possible to derive readings close to those provided by this interface removing the <code>LinerAccelerometer</code> reading from the <code>Accelerometer</code> reading.</p> <h3>Suggested file name and location for the File System Access API</h3> <p>When using the File System Access API, web apps can now <a href="https://www.chromestatus.com/feature/6013006146174976">suggest the name and location of a file or directory</a> that is being created or loaded. This provides a better user experience and brings web apps closer to the behavior of system apps. For more about the File System Access API, see <a href="https://web.dev/file-system-access/">The File System Access API: simplifying access to local files</a>.</p> <h3>WebOTP API: cross-origin iframe support</h3> <p>The WebOTP API is now <a href="https://web.dev/web-otp-iframe">usable in cross-origin iframes</a> when enabled by a permission policy. The WebOTP API gives developers the ability to programmatically read one time codes from specially-formatted SMS messages addressed to their origin to reduce user friction. Many sites embed iframes that handle authentication.</p> <h3>WebSockets over HTTP/2</h3> <p>Chrome supports <a href="https://www.chromestatus.com/feature/6251293127475200">WebSockets over HTTP/2</a> in Chromium as specified in <a href="https://tools.ietf.org/html/rfc8441">RFC 8441</a>. This is only used for secure WebSockets requests, and only when there is already an HTTP/2 connection where the server has already advertised support for WebSockets over HTTP/2 via the HTTP/2 SETTINGS parameter defined in the specification.</p> <h3>Credentials sharing for sites affiliated with Digital Asset Links</h3> <p>Since 2015 developers have used Digital Asset Links (DALs) to associate Android apps with websites to assist users with logging in. If you employ multiple domains that share the same account management backend, you can now also associate them with one another to enable users to <a href="https://developer.chrome.com/blog/site-affiliation/">save credentials once and have the Chrome password manager suggest them to any of the affiliated websites</a>. For more information, see <a href="https://developer.chrome.com/blog/site-affiliation/">Enable Chrome to share login credentials across affiliated sites</a>.</p> <h2>JavaScript</h2> <p>This version of Chrome incorporates version 9.1 of the V8 JavaScript engine. It specifically includes the changes listed below. You can find a complete <a href="https://v8.dev/blog">list of recent features</a> in the V8 release notes.</p> <h3>ES Modules for service workers ('module' type option)</h3> <p>JavaScript now supports <a href="https://www.chromestatus.com/feature/4609574738853888">modules in service workers</a>. Setting <code>'module'</code> type by the constructor's type attribute, worker scripts are loaded as ES modules and the <code>import</code> statement is available on worker contexts. With this feature, web developers can more easily write programs in a composable way and share them among a page and workers.</p> <h3>Checks for Private Fields</h3> <p>Developers can now<a href="https://www.chromestatus.com/feature/5006138707804160"> test for the existence of private fields</a> in an object using the syntax <code>#foo in obj</code>.</p> <h2>Deprecations, and Removals</h2> <p>This version of Chrome introduces the deprecation listed below. Visit ChromeStatus.com for lists of <a href="https://www.chromestatus.com/features#browsers.chrome.status:%22Removed%22">previous removals</a>.</p> <h3 style="text-align: left;"><span style="font-size: 18.72px;">Remove alert(), confirm(), and prompt() for Cross Origin iframes</span></h3> <p>Chrome allows iframes to trigger Javascript dialogs. For example it shows &#8220;&lt;URL&gt; says ...&#8221; when the iframe is the same origin as the top frame, and &#8220;An embedded page on this page says...&#8221; when the iframe is cross-origin. This is confusing, and has led to spoofs where sites pretend the message comes from Chrome or a different website.&nbsp;</p><p>Chrome 91 deprecates this ability.&nbsp;<a href="https://chromestatus.com/feature/5148698084376576">Removing support</a> for cross origin iframes&#8217; ability to call <code>alert()</code>, <code>confirm()</code>, and <code>prompt()</code> will prevent this kind of spoofing, and unblock further UI simplifications. For example, this means notexample.com will no longer be able to call <code>window.alert()</code>, <code>window.prompt()</code>, or <code>window.confirm()</code> if embedded in an iframe on example.com.</p> <span itemprop='author' itemscope='itemscope' itemtype='http://schema.org/Person'> <meta content='https://plus.google.com/116899029375914044550' itemprop='url'/> </span> </noscript> </div> </div> <div class='share'> <span class='twitter-custom social-wrapper' data-href='http://twitter.com/share?text=Chromium Blog:Chrome 91: Handwriting Recognition, WebXR Plane Detection and More&url=https://blog.chromium.org/2021/04/chrome-91-handwriting-recognition-webxr.html&via=ChromiumDev'> <img alt='Share on Twitter' height='24' src='https://www.gstatic.com/images/icons/material/system/2x/post_twitter_black_24dp.png' width='24'/> </span> <span class='fb-custom social-wrapper' data-href='https://www.facebook.com/sharer.php?u=https://blog.chromium.org/2021/04/chrome-91-handwriting-recognition-webxr.html'> <img alt='Share on Facebook' height='24' src='https://www.gstatic.com/images/icons/material/system/2x/post_facebook_black_24dp.png' width='24'/> </span> </div> <div class='post-footer'> <div class='cmt_iframe_holder' data-href='https://blog.chromium.org/2021/04/chrome-91-handwriting-recognition-webxr.html' data-viewtype='FILTERED_POSTMOD'></div> <a href='https://plus.google.com/112374322230920073195' rel='author' style='display:none;'> Google </a> <div class='label-footer'> <span class='labels-caption'> Labels: </span> <span class='labels'> <a class='label' href='https://blog.chromium.org/search/label/beta' rel='tag'> beta </a> </span> </div> </div> </div> <div class='post' data-id='6876489512346638549' itemscope='' itemtype='http://schema.org/BlogPosting'> <h2 class='title' itemprop='name'> <a href='https://blog.chromium.org/2021/04/digging-for-performance-gold.html' itemprop='url' title='Digging for performance gold: finding hidden performance wins'> Digging for performance gold: finding hidden performance wins </a> </h2> <div class='post-header'> <div class='published'> <span class='publishdate' itemprop='datePublished'> Thursday, April 22, 2021 </span> </div> </div> <div class='post-body'> <div class='post-content' itemprop='articleBody'> <script type='text/template'> <i>We are fortunate that so many people choose Chrome as their browser to get things done, which is why we are continually investing in making Chrome more performant. But with software as complex as Chrome, there is a lot of performance left hidden in areas we aren&#8217;t actively working on. In our latest post in the <a href="https://blog.chromium.org/search/label/the%20fast%20and%20the%20curious">The Fast and the Curious</a> series, we investigate how to diagnose, find, and fix performance problems that normally go undetected.</i><br /><br /><br /><br /><h2 style="text-align: left;"><span style="font-size: x-large;">The 1%</span></h2>Our metrics show that while Chrome is fast on average, it can be noticeably slow at times. Such user-pain is visible in the 99th percentile of many of our metrics but unreproducible, and thus quite hard to act upon. Deeper analysis in the data shows that the long-tail of performance is not 1% of users on slow machines but rather many users, 1% of the time.<br /><br />Let&#8217;s talk about 1%. 1% is quite large in practice. The core metric we use is &#8220;jank&#8221; which is a noticeable delay between when the user gives input and when software reacts to it. Chrome measures jank every 30 seconds, so Jank in 1% of samples for a given user means jank once every 50 minutes. To that user, Chrome feels slow at those moments. Now the problem: can we find and fix the root causes of all the ways Chrome can be momentarily slow for our users?<br /><br /><div><br /></div><div><br /></div><div><h2 style="text-align: left;"><span style="font-size: x-large;">Approach</span></h2>As engineers, our training in optimization is to focus on improving the algorithmic performance of the components we own. The last 3 years of analyzing the immensely complex codebase of Chrome however have taught us that the real issue is often cross-cutting: multiple unrelated features&#8217; long-tail performance issues, sharing the same systemic root cause(s). Applying local expertise and optimization is likely to miss the global optimum. It is necessary to disregard our initial intuition and assume ignorance, forcing us to dig beyond what is immediately apparent and find the underlying root cause by relentlessly exposing what we don&#8217;t know.</div><div><br /></div><div><br /></div><div><br /><h2 style="text-align: left;"><span style="font-size: x-large;">Chasing Invisible Bugs</span></h2>How do we find bugs that are unforeseen, unreproducible, unowned, and essentially invisible?<br /><br />First, define a scenario. For this work, we focus on user-visible Jank, which we <a href="https://chromium.googlesource.com/chromium/src/+/master/tools/metrics/histograms/README.md">measure in the field</a> as a way to systematically identify moments where Chrome feels slow.<br /><br />Second, gather high actionability bug reports in the field. For this we rely on Chrome&#8217;s <a href="https://source.chromium.org/search?q=BackgroundTracing&amp;ss=chromium">BackgroundTracing</a> infrastructure to generate what we call Slow Reports. A subset of Canary users who have opted in to sharing anonymized metrics have circular-buffer tracing enabled to examine specific scenarios. If a preconfigured threshold on a metric of interest is hit, the trace buffer is captured, anonymized, and uploaded to Google servers.<br /><br />Such a bug report might look like this:</div><div><br /></div><div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuamYfrtx4rDkQAW9KBorT5zdM8EkMamWGuPsnDU8VW7iJfMzm0j_NozS7ak7z1xQEMuIXFL_6LLMX7VsfkJFlEjwv_9C5Uhyphenhyphen6ywYquTNFb02c6NrXJegyZzka-pPVUJbLPFDoEprW8L6F/s1351/image1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="190" data-original-width="1351" height="92" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuamYfrtx4rDkQAW9KBorT5zdM8EkMamWGuPsnDU8VW7iJfMzm0j_NozS7ak7z1xQEMuIXFL_6LLMX7VsfkJFlEjwv_9C5Uhyphenhyphen6ywYquTNFb02c6NrXJegyZzka-pPVUJbLPFDoEprW8L6F/w654-h92/image1.png" width="654" /></a></div><span style="font-size: xx-small;"><div style="text-align: center;">chrome://tracing view of a 2 seconds Jank on AutocompleteController::UpdateResult() on an otherwise healthy machine</div></span><br /><br />We have a culprit! Let&#8217;s optimize AutocompleteController? No! We don&#8217;t know why yet: keep assuming ignorance!<br /><br />By augmenting BackgroundTracing with stack sampling, we were able to find a recurring stack under stalled AutoComplete events:</div><div><span style="font-family: inherit; font-size: 11pt; white-space: pre-wrap;"><br /></span></div><div><span style="font-family: inherit; font-size: 11pt; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>RegEnumValueW</span></div><div><span id="docs-internal-guid-4a961e85-7fff-418a-ea39-3b655b9b59f5"><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>RegEnumValueWStub</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>base::win::RegistryValueIterator::Read()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>gfx::`anonymous namespace\'::CachedFontLinkSettings::GetLinkedFonts</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>gfx::internal::LinkedFontsIterator::GetLinkedFonts()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>gfx::internal::LinkedFontsIterator::NextFont(gfx::Font *)</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>gfx::GetFallbackFonts(gfx::Font const &amp;)</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>gfx::RenderTextHarfBuzz::ShapeRuns(...)</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>gfx::RenderTextHarfBuzz::ItemizeAndShapeText(...)</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>gfx::RenderTextHarfBuzz::EnsureLayoutRunList()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>gfx::RenderTextHarfBuzz::EnsureLayout()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>gfx::RenderTextHarfBuzz::GetStringSizeF()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>gfx::RenderTextHarfBuzz::GetStringSize()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>OmniboxTextView::CalculatePreferredSize()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>OmniboxTextView::ReapplyStyling()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>OmniboxTextView::SetText...)</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>OmniboxResultView::Invalidate()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>OmniboxResultView::SetMatch(AutocompleteMatch const &amp;)</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>OmniboxPopupContentsView::UpdatePopupAppearance()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>OmniboxPopupModel::OnResultChanged()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>OmniboxEditModel::OnCurrentMatchChanged()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>OmniboxController::OnResultChanged(bool)</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>AutocompleteController::UpdateResult(bool,bool)</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>AutocompleteController::Start(AutocompleteInput const &amp;)</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>(...)</span></p><div><br /></div></span></div><div>Ah ha! Autocomplete is not at fault. Time to optimize GetFallbackFonts()?! But wait&#8230; Why is GetFallbackFonts() even called in the first place?<br /><br />And before we figure that out, how do we know this is the #1 root cause of our overall long-tail performance issue? We&#8217;ve only looked at one trace so far after all...</div><div><br /></div><div><br /></div><div><br /><h2 style="text-align: left;"><span style="font-size: x-large;">The Measurement Conundrum</span></h2>The metrics tell us how many users are affected and how bad it is, but they do not highlight the root cause.<br /><br />Slow Reports tell us what the problem is for a specific user but not how many users are affected. And while we can query our corpus of Slow Report traces, it comes with inherent biases that make it impossible to correlate 1:1 with metrics. For instance, because Chrome only reports the first instance of bad performance per-session and only for users of the Canary/Dev channel, there&#8217;s both a startup and a population bias.<br /><br />This is the measurement conundrum. The more actionability (data) a tool provides, the fewer scenarios it captures and the more bias it incurs. Depth vs. breadth.<br /><br />Tools that attempt to do both sit somewhere in the middle, where they use aggregation over a large dataset and risk showing aggregate results based on flawed input (e.g. circular buffer tracing having dropped the interesting portion and contributing to a biased aggregate).<br /><br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJgG5FJ_GzIKmhTioVzKejDcx8y6ohL5lUoHr6LYV9k4LbdVmu4ZebMXleU1TSc2Ri8KQCAxzomAn1ZvHYsiQOB3yuJNNlZH_YZq4I1h4ITZO1Pn2DZIKqk08oTAAu_IGv__P6kPxyH9J_/s1140/image4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="702" data-original-width="1140" height="334" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJgG5FJ_GzIKmhTioVzKejDcx8y6ohL5lUoHr6LYV9k4LbdVmu4ZebMXleU1TSc2Ri8KQCAxzomAn1ZvHYsiQOB3yuJNNlZH_YZq4I1h4ITZO1Pn2DZIKqk08oTAAu_IGv__P6kPxyH9J_/w542-h334/image4.png" width="542" /></a></div><div style="text-align: center;"><br /></div><br /><br />Thus we scientifically opted for the least engineering-minded option: open a bunch of Slow Report traces manually. This gave us the most actionability over a top-level issue we&#8217;d already quantified.<br /><br />After opening dozens of traces it turned out that a great majority showed variations of the aforementioned fonts issue. While this didn&#8217;t give us a precise #users-affected, it was enough for us to believe it was the main cause of user pain seen in the metrics.</div><div><br /></div><div><br /></div><div><br /></div><div><br /><h2 style="text-align: left;"><span style="font-size: x-large;">Fallback Fonts</span></h2>We dug into why GetFallbackFonts() had to be called in the first place. In the example above, the caller is trying to determine the size in pixels of a Unicode string rendered by a given font.<br /><br />If a substring within it is from a <a href="https://en.wikipedia.org/wiki/Unicode_block">Unicode Block</a> that can&#8217;t be rendered by the given font, GetFallbackFont() is used to request the system recommended fallback font for it. If that fails, GetFallbackFonts() is invoked to try all the <a href="https://docs.microsoft.com/en-us/globalization/input/font-technology">linked fonts</a> and determine the one that can best render it; that second fallback is much slower.<br /><br />GetFallbackFont() should never fail, but in practice it&#8217;s not that simple. The reliable way to do this on Windows is to query <a href="https://docs.microsoft.com/en-us/windows/win32/directwrite/introducing-directwrite">DirectWrite</a>; however, DirectWrite was added in Windows 7, when Chrome still supported Windows XP. Therefore the GetFallbackFont() logic was forced to stick to a less reliable <a href="https://chromium.googlesource.com/chromium/src/+/22aed04422b04b2cf04f7b7d61392da4e9a2c85a/ui/gfx/font_fallback_win.cc#303">heuristic using Uniscribe+GDI</a> in order to work on both versions of the OS. Since things worked most of the time, no one noticed that this could have been cleaned up when Chrome later dropped support for Windows XP. With new tooling to investigate long-tail performance, this turned out to be the number one cause of jank (unnecessarily invoking GetFallbackFonts()).<br /><br />We <a href="https://chromium-review.googlesource.com/c/chromium/src/+/1663504/">fixed</a> that, reducing the amount of calls to GetFallbackFonts() by 4x.</div><div><br /></div><div><br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoS0pmpqWu93gtMQh1cRD6OcC3F60XoaMDHxOjzg2DVQLrht0IPOwW-ump4C81YHswCaF_NiejtJZo8fCdZGmW3PgANB4m8R-Y9WVJDFjzbuyIB0FiRHYnMVK6bQlBg_K5tfrErbMHmhyb/s1244/image3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="487" data-original-width="1244" height="258" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoS0pmpqWu93gtMQh1cRD6OcC3F60XoaMDHxOjzg2DVQLrht0IPOwW-ump4C81YHswCaF_NiejtJZo8fCdZGmW3PgANB4m8R-Y9WVJDFjzbuyIB0FiRHYnMVK6bQlBg_K5tfrErbMHmhyb/w660-h258/image3.png" width="660" /></a></div><div style="text-align: center;"><br /></div><br /><br />Still not zero though, and still seeing instances of the aforementioned AutoComplete issue in our Slow Reports. Keep digging. DirectWrite&#8217;s GetFallbackFont() failing was unexpected, but since Slow Reports are anonymized, no user-generated strings can be uploaded -- and therefore, finding which codepoints were problematic was tricky. We teamed up with our privacy experts to instrument Unicode Block and Script of text blocks going through <a href="https://en.wikipedia.org/wiki/HarfBuzz">HarfBuzz</a> so that we could ensure no leakage of <a href="https://en.wikipedia.org/wiki/Personal_data">Personally Identifiable Information</a>.</div><div><br /></div><div><br /></div><div>&nbsp; <br /><h2 style="text-align: left;"><span style="font-size: x-large;">The Emoji Saga</span></h2>With this new recording enabled, the next wave of Slow Reports came back. The vast majority of reports indicated that font fallback was failing when DirectWrite was being asked to find a font for a codepoint (Unicode character) in <a href="https://www.compart.com/en/unicode/block/U+1F300">Miscellaneous Symbols and Pictographs</a>. We wrote a local script trying all codepoints in that Unicode Block and quickly found out which ones could be problematic: U+1F3FB - U+1F3FF are modifiers added in Unicode 8.0 and are meaningful only when paired with another codepoint. For instance, U+1F9D7 (🧗) when paired with U+1F3FF is 🧗🏿. No font can render U+1F3FF on its own, and font fallback would correctly error out after scanning all linked fonts when asked to find one. The bug was in the browser-side Unicode <a href="https://unicode.org/reports/tr29/">segmentation</a> logic which incorrectly broke down these two <a href="https://en.wikipedia.org/wiki/Code_point">codepoints</a> and asked DirectWrite to render them separately instead of keeping them as a single grapheme.<br /><br />But wait, doesn&#8217;t Chrome support modern Unicode..?! Indeed, it does, in Blink which renders the web content. But the browser-side logic was not updated to support modern emojis (with modifiers) because it didn&#8217;t use to draw emojis at all. It&#8217;s only when the browser UI (tab strip, bookmark bar, omnibox, etc.) was modernized to support Unicode circa 2018 that the legacy segmentation logic became an (invisible) problem.<br /><br />On top of that, the caching logic did not cache on error, so trying to render a modifier on its own caused a massive jank, every time, for users with a lot of fonts installed. Ironically, this cache had been added to amortize the cost of this misunderstood bottleneck when Unicode support was first added to browser UI. Diving deeper into the underlying implementation of our fonts logic, rather than stopping at the layer of the fonts APIs, not only fixed a major performance issue but also resulted in a correctness fix for other <a href="https://emojipedia.org/emoji/">emojis</a>. For instance, 🏳&#65039;&#8205;🌈 is encoded as U+1F3F3( 🏳&#65039;) + U+1F308 (🌈); before the itemization fix, browser UI would incorrectly render this grapheme as 🏳&#65039;🌈.<br /><br /><br /><br /><h2 style="text-align: left;"><span style="font-size: x-large;">And the journey continues...</span></h2>Our journey keeps going into various components of Chrome but it always follows the same basic playbook: assume ignorance and relentlessly investigate unforeseen, unreproducible, and unowned bugs. And while stack ranking issues is nigh impossible (see: measurement conundrum), fixing the top 5 findings from any given tool and zooming in on the long tail has always addressed the majority of the user pain in practice.<br /><br />Using this approach, we have reduced user-visible jank by a factor of 10X over the last 2.5 years and improved long-tail performance of many features caught in the cross-fire. <br /><br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijclE3wC3-F5EWITLOOC4zFxE49mBpRu35jBlA0_nW9f4vl7RrEfg3FFMCTVqPYVnkOlR1cBxGdX_EHNbBsc3UnjdSMmZfKHBXA5FBcf2V3QHskME6o2_cZetVtVkRhlXcgSAK2smXGrff/s905/image5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="489" data-original-width="905" height="347" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijclE3wC3-F5EWITLOOC4zFxE49mBpRu35jBlA0_nW9f4vl7RrEfg3FFMCTVqPYVnkOlR1cBxGdX_EHNbBsc3UnjdSMmZfKHBXA5FBcf2V3QHskME6o2_cZetVtVkRhlXcgSAK2smXGrff/w642-h347/image5.png" width="642" /></a></div><div style="text-align: center;"><span style="font-size: xx-small;">99th percentile of # of unresponsive 100ms intervals over a 30 seconds sample</span></div><div><br /></div><div><br /></div>Posted by Gabriel Charette 🤸🏼 and Etienne Bergeron 🕵🏻, Chrome Software Engineers<br /><br /><br /><br /><i>Data source for all statistics: <a href="https://www.google.com/chrome/privacy/whitepaper.html#usagestats">Real-world data</a> anonymously aggregated from Chrome clients.</i><br /></div> <span itemprop='author' itemscope='itemscope' itemtype='http://schema.org/Person'> <meta content='https://plus.google.com/116899029375914044550' itemprop='url'/> </span> </script> <noscript> <i>We are fortunate that so many people choose Chrome as their browser to get things done, which is why we are continually investing in making Chrome more performant. But with software as complex as Chrome, there is a lot of performance left hidden in areas we aren&#8217;t actively working on. In our latest post in the <a href="https://blog.chromium.org/search/label/the%20fast%20and%20the%20curious">The Fast and the Curious</a> series, we investigate how to diagnose, find, and fix performance problems that normally go undetected.</i><br /><br /><br /><br /><h2 style="text-align: left;"><span style="font-size: x-large;">The 1%</span></h2>Our metrics show that while Chrome is fast on average, it can be noticeably slow at times. Such user-pain is visible in the 99th percentile of many of our metrics but unreproducible, and thus quite hard to act upon. Deeper analysis in the data shows that the long-tail of performance is not 1% of users on slow machines but rather many users, 1% of the time.<br /><br />Let&#8217;s talk about 1%. 1% is quite large in practice. The core metric we use is &#8220;jank&#8221; which is a noticeable delay between when the user gives input and when software reacts to it. Chrome measures jank every 30 seconds, so Jank in 1% of samples for a given user means jank once every 50 minutes. To that user, Chrome feels slow at those moments. Now the problem: can we find and fix the root causes of all the ways Chrome can be momentarily slow for our users?<br /><br /><div><br /></div><div><br /></div><div><h2 style="text-align: left;"><span style="font-size: x-large;">Approach</span></h2>As engineers, our training in optimization is to focus on improving the algorithmic performance of the components we own. The last 3 years of analyzing the immensely complex codebase of Chrome however have taught us that the real issue is often cross-cutting: multiple unrelated features&#8217; long-tail performance issues, sharing the same systemic root cause(s). Applying local expertise and optimization is likely to miss the global optimum. It is necessary to disregard our initial intuition and assume ignorance, forcing us to dig beyond what is immediately apparent and find the underlying root cause by relentlessly exposing what we don&#8217;t know.</div><div><br /></div><div><br /></div><div><br /><h2 style="text-align: left;"><span style="font-size: x-large;">Chasing Invisible Bugs</span></h2>How do we find bugs that are unforeseen, unreproducible, unowned, and essentially invisible?<br /><br />First, define a scenario. For this work, we focus on user-visible Jank, which we <a href="https://chromium.googlesource.com/chromium/src/+/master/tools/metrics/histograms/README.md">measure in the field</a> as a way to systematically identify moments where Chrome feels slow.<br /><br />Second, gather high actionability bug reports in the field. For this we rely on Chrome&#8217;s <a href="https://source.chromium.org/search?q=BackgroundTracing&amp;ss=chromium">BackgroundTracing</a> infrastructure to generate what we call Slow Reports. A subset of Canary users who have opted in to sharing anonymized metrics have circular-buffer tracing enabled to examine specific scenarios. If a preconfigured threshold on a metric of interest is hit, the trace buffer is captured, anonymized, and uploaded to Google servers.<br /><br />Such a bug report might look like this:</div><div><br /></div><div><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuamYfrtx4rDkQAW9KBorT5zdM8EkMamWGuPsnDU8VW7iJfMzm0j_NozS7ak7z1xQEMuIXFL_6LLMX7VsfkJFlEjwv_9C5Uhyphenhyphen6ywYquTNFb02c6NrXJegyZzka-pPVUJbLPFDoEprW8L6F/s1351/image1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="190" data-original-width="1351" height="92" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuamYfrtx4rDkQAW9KBorT5zdM8EkMamWGuPsnDU8VW7iJfMzm0j_NozS7ak7z1xQEMuIXFL_6LLMX7VsfkJFlEjwv_9C5Uhyphenhyphen6ywYquTNFb02c6NrXJegyZzka-pPVUJbLPFDoEprW8L6F/w654-h92/image1.png" width="654" /></a></div><span style="font-size: xx-small;"><div style="text-align: center;">chrome://tracing view of a 2 seconds Jank on AutocompleteController::UpdateResult() on an otherwise healthy machine</div></span><br /><br />We have a culprit! Let&#8217;s optimize AutocompleteController? No! We don&#8217;t know why yet: keep assuming ignorance!<br /><br />By augmenting BackgroundTracing with stack sampling, we were able to find a recurring stack under stalled AutoComplete events:</div><div><span style="font-family: inherit; font-size: 11pt; white-space: pre-wrap;"><br /></span></div><div><span style="font-family: inherit; font-size: 11pt; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>RegEnumValueW</span></div><div><span id="docs-internal-guid-4a961e85-7fff-418a-ea39-3b655b9b59f5"><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>RegEnumValueWStub</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>base::win::RegistryValueIterator::Read()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>gfx::`anonymous namespace\'::CachedFontLinkSettings::GetLinkedFonts</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>gfx::internal::LinkedFontsIterator::GetLinkedFonts()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>gfx::internal::LinkedFontsIterator::NextFont(gfx::Font *)</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>gfx::GetFallbackFonts(gfx::Font const &amp;)</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>gfx::RenderTextHarfBuzz::ShapeRuns(...)</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>gfx::RenderTextHarfBuzz::ItemizeAndShapeText(...)</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>gfx::RenderTextHarfBuzz::EnsureLayoutRunList()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>gfx::RenderTextHarfBuzz::EnsureLayout()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>gfx::RenderTextHarfBuzz::GetStringSizeF()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>gfx::RenderTextHarfBuzz::GetStringSize()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>OmniboxTextView::CalculatePreferredSize()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>OmniboxTextView::ReapplyStyling()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>OmniboxTextView::SetText...)</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>OmniboxResultView::Invalidate()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>OmniboxResultView::SetMatch(AutocompleteMatch const &amp;)</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>OmniboxPopupContentsView::UpdatePopupAppearance()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>OmniboxPopupModel::OnResultChanged()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>OmniboxEditModel::OnCurrentMatchChanged()</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>OmniboxController::OnResultChanged(bool)</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>AutocompleteController::UpdateResult(bool,bool)</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>AutocompleteController::Start(AutocompleteInput const &amp;)</span></p><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-family: inherit; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span>&nbsp;&nbsp; &nbsp;</span>(...)</span></p><div><br /></div></span></div><div>Ah ha! Autocomplete is not at fault. Time to optimize GetFallbackFonts()?! But wait&#8230; Why is GetFallbackFonts() even called in the first place?<br /><br />And before we figure that out, how do we know this is the #1 root cause of our overall long-tail performance issue? We&#8217;ve only looked at one trace so far after all...</div><div><br /></div><div><br /></div><div><br /><h2 style="text-align: left;"><span style="font-size: x-large;">The Measurement Conundrum</span></h2>The metrics tell us how many users are affected and how bad it is, but they do not highlight the root cause.<br /><br />Slow Reports tell us what the problem is for a specific user but not how many users are affected. And while we can query our corpus of Slow Report traces, it comes with inherent biases that make it impossible to correlate 1:1 with metrics. For instance, because Chrome only reports the first instance of bad performance per-session and only for users of the Canary/Dev channel, there&#8217;s both a startup and a population bias.<br /><br />This is the measurement conundrum. The more actionability (data) a tool provides, the fewer scenarios it captures and the more bias it incurs. Depth vs. breadth.<br /><br />Tools that attempt to do both sit somewhere in the middle, where they use aggregation over a large dataset and risk showing aggregate results based on flawed input (e.g. circular buffer tracing having dropped the interesting portion and contributing to a biased aggregate).<br /><br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJgG5FJ_GzIKmhTioVzKejDcx8y6ohL5lUoHr6LYV9k4LbdVmu4ZebMXleU1TSc2Ri8KQCAxzomAn1ZvHYsiQOB3yuJNNlZH_YZq4I1h4ITZO1Pn2DZIKqk08oTAAu_IGv__P6kPxyH9J_/s1140/image4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="702" data-original-width="1140" height="334" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhJgG5FJ_GzIKmhTioVzKejDcx8y6ohL5lUoHr6LYV9k4LbdVmu4ZebMXleU1TSc2Ri8KQCAxzomAn1ZvHYsiQOB3yuJNNlZH_YZq4I1h4ITZO1Pn2DZIKqk08oTAAu_IGv__P6kPxyH9J_/w542-h334/image4.png" width="542" /></a></div><div style="text-align: center;"><br /></div><br /><br />Thus we scientifically opted for the least engineering-minded option: open a bunch of Slow Report traces manually. This gave us the most actionability over a top-level issue we&#8217;d already quantified.<br /><br />After opening dozens of traces it turned out that a great majority showed variations of the aforementioned fonts issue. While this didn&#8217;t give us a precise #users-affected, it was enough for us to believe it was the main cause of user pain seen in the metrics.</div><div><br /></div><div><br /></div><div><br /></div><div><br /><h2 style="text-align: left;"><span style="font-size: x-large;">Fallback Fonts</span></h2>We dug into why GetFallbackFonts() had to be called in the first place. In the example above, the caller is trying to determine the size in pixels of a Unicode string rendered by a given font.<br /><br />If a substring within it is from a <a href="https://en.wikipedia.org/wiki/Unicode_block">Unicode Block</a> that can&#8217;t be rendered by the given font, GetFallbackFont() is used to request the system recommended fallback font for it. If that fails, GetFallbackFonts() is invoked to try all the <a href="https://docs.microsoft.com/en-us/globalization/input/font-technology">linked fonts</a> and determine the one that can best render it; that second fallback is much slower.<br /><br />GetFallbackFont() should never fail, but in practice it&#8217;s not that simple. The reliable way to do this on Windows is to query <a href="https://docs.microsoft.com/en-us/windows/win32/directwrite/introducing-directwrite">DirectWrite</a>; however, DirectWrite was added in Windows 7, when Chrome still supported Windows XP. Therefore the GetFallbackFont() logic was forced to stick to a less reliable <a href="https://chromium.googlesource.com/chromium/src/+/22aed04422b04b2cf04f7b7d61392da4e9a2c85a/ui/gfx/font_fallback_win.cc#303">heuristic using Uniscribe+GDI</a> in order to work on both versions of the OS. Since things worked most of the time, no one noticed that this could have been cleaned up when Chrome later dropped support for Windows XP. With new tooling to investigate long-tail performance, this turned out to be the number one cause of jank (unnecessarily invoking GetFallbackFonts()).<br /><br />We <a href="https://chromium-review.googlesource.com/c/chromium/src/+/1663504/">fixed</a> that, reducing the amount of calls to GetFallbackFonts() by 4x.</div><div><br /></div><div><br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoS0pmpqWu93gtMQh1cRD6OcC3F60XoaMDHxOjzg2DVQLrht0IPOwW-ump4C81YHswCaF_NiejtJZo8fCdZGmW3PgANB4m8R-Y9WVJDFjzbuyIB0FiRHYnMVK6bQlBg_K5tfrErbMHmhyb/s1244/image3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="487" data-original-width="1244" height="258" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoS0pmpqWu93gtMQh1cRD6OcC3F60XoaMDHxOjzg2DVQLrht0IPOwW-ump4C81YHswCaF_NiejtJZo8fCdZGmW3PgANB4m8R-Y9WVJDFjzbuyIB0FiRHYnMVK6bQlBg_K5tfrErbMHmhyb/w660-h258/image3.png" width="660" /></a></div><div style="text-align: center;"><br /></div><br /><br />Still not zero though, and still seeing instances of the aforementioned AutoComplete issue in our Slow Reports. Keep digging. DirectWrite&#8217;s GetFallbackFont() failing was unexpected, but since Slow Reports are anonymized, no user-generated strings can be uploaded -- and therefore, finding which codepoints were problematic was tricky. We teamed up with our privacy experts to instrument Unicode Block and Script of text blocks going through <a href="https://en.wikipedia.org/wiki/HarfBuzz">HarfBuzz</a> so that we could ensure no leakage of <a href="https://en.wikipedia.org/wiki/Personal_data">Personally Identifiable Information</a>.</div><div><br /></div><div><br /></div><div>&nbsp; <br /><h2 style="text-align: left;"><span style="font-size: x-large;">The Emoji Saga</span></h2>With this new recording enabled, the next wave of Slow Reports came back. The vast majority of reports indicated that font fallback was failing when DirectWrite was being asked to find a font for a codepoint (Unicode character) in <a href="https://www.compart.com/en/unicode/block/U+1F300">Miscellaneous Symbols and Pictographs</a>. We wrote a local script trying all codepoints in that Unicode Block and quickly found out which ones could be problematic: U+1F3FB - U+1F3FF are modifiers added in Unicode 8.0 and are meaningful only when paired with another codepoint. For instance, U+1F9D7 (🧗) when paired with U+1F3FF is 🧗🏿. No font can render U+1F3FF on its own, and font fallback would correctly error out after scanning all linked fonts when asked to find one. The bug was in the browser-side Unicode <a href="https://unicode.org/reports/tr29/">segmentation</a> logic which incorrectly broke down these two <a href="https://en.wikipedia.org/wiki/Code_point">codepoints</a> and asked DirectWrite to render them separately instead of keeping them as a single grapheme.<br /><br />But wait, doesn&#8217;t Chrome support modern Unicode..?! Indeed, it does, in Blink which renders the web content. But the browser-side logic was not updated to support modern emojis (with modifiers) because it didn&#8217;t use to draw emojis at all. It&#8217;s only when the browser UI (tab strip, bookmark bar, omnibox, etc.) was modernized to support Unicode circa 2018 that the legacy segmentation logic became an (invisible) problem.<br /><br />On top of that, the caching logic did not cache on error, so trying to render a modifier on its own caused a massive jank, every time, for users with a lot of fonts installed. Ironically, this cache had been added to amortize the cost of this misunderstood bottleneck when Unicode support was first added to browser UI. Diving deeper into the underlying implementation of our fonts logic, rather than stopping at the layer of the fonts APIs, not only fixed a major performance issue but also resulted in a correctness fix for other <a href="https://emojipedia.org/emoji/">emojis</a>. For instance, 🏳&#65039;&#8205;🌈 is encoded as U+1F3F3( 🏳&#65039;) + U+1F308 (🌈); before the itemization fix, browser UI would incorrectly render this grapheme as 🏳&#65039;🌈.<br /><br /><br /><br /><h2 style="text-align: left;"><span style="font-size: x-large;">And the journey continues...</span></h2>Our journey keeps going into various components of Chrome but it always follows the same basic playbook: assume ignorance and relentlessly investigate unforeseen, unreproducible, and unowned bugs. And while stack ranking issues is nigh impossible (see: measurement conundrum), fixing the top 5 findings from any given tool and zooming in on the long tail has always addressed the majority of the user pain in practice.<br /><br />Using this approach, we have reduced user-visible jank by a factor of 10X over the last 2.5 years and improved long-tail performance of many features caught in the cross-fire. <br /><br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijclE3wC3-F5EWITLOOC4zFxE49mBpRu35jBlA0_nW9f4vl7RrEfg3FFMCTVqPYVnkOlR1cBxGdX_EHNbBsc3UnjdSMmZfKHBXA5FBcf2V3QHskME6o2_cZetVtVkRhlXcgSAK2smXGrff/s905/image5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="489" data-original-width="905" height="347" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijclE3wC3-F5EWITLOOC4zFxE49mBpRu35jBlA0_nW9f4vl7RrEfg3FFMCTVqPYVnkOlR1cBxGdX_EHNbBsc3UnjdSMmZfKHBXA5FBcf2V3QHskME6o2_cZetVtVkRhlXcgSAK2smXGrff/w642-h347/image5.png" width="642" /></a></div><div style="text-align: center;"><span style="font-size: xx-small;">99th percentile of # of unresponsive 100ms intervals over a 30 seconds sample</span></div><div><br /></div><div><br /></div>Posted by Gabriel Charette 🤸🏼 and Etienne Bergeron 🕵🏻, Chrome Software Engineers<br /><br /><br /><br /><i>Data source for all statistics: <a href="https://www.google.com/chrome/privacy/whitepaper.html#usagestats">Real-world data</a> anonymously aggregated from Chrome clients.</i><br /></div> <span itemprop='author' itemscope='itemscope' itemtype='http://schema.org/Person'> <meta content='https://plus.google.com/116899029375914044550' itemprop='url'/> </span> </noscript> </div> </div> <div class='share'> <span class='twitter-custom social-wrapper' data-href='http://twitter.com/share?text=Chromium Blog:Digging for performance gold: finding hidden performance wins&url=https://blog.chromium.org/2021/04/digging-for-performance-gold.html&via=ChromiumDev'> <img alt='Share on Twitter' height='24' src='https://www.gstatic.com/images/icons/material/system/2x/post_twitter_black_24dp.png' width='24'/> </span> <span class='fb-custom social-wrapper' data-href='https://www.facebook.com/sharer.php?u=https://blog.chromium.org/2021/04/digging-for-performance-gold.html'> <img alt='Share on Facebook' height='24' src='https://www.gstatic.com/images/icons/material/system/2x/post_facebook_black_24dp.png' width='24'/> </span> </div> <div class='post-footer'> <div class='cmt_iframe_holder' data-href='https://blog.chromium.org/2021/04/digging-for-performance-gold.html' data-viewtype='FILTERED_POSTMOD'></div> <a href='https://plus.google.com/112374322230920073195' rel='author' style='display:none;'> Google </a> <div class='label-footer'> <span class='labels-caption'> Labels: </span> <span class='labels'> <a class='label' href='https://blog.chromium.org/search/label/emoji' rel='tag'> emoji </a> , <a class='label' href='https://blog.chromium.org/search/label/fonts' rel='tag'> fonts </a> , <a class='label' href='https://blog.chromium.org/search/label/jank' rel='tag'> jank </a> , <a class='label' href='https://blog.chromium.org/search/label/long-tail' rel='tag'> long-tail </a> , <a class='label' href='https://blog.chromium.org/search/label/performance' rel='tag'> performance </a> , <a class='label' href='https://blog.chromium.org/search/label/the%20fast%20and%20the%20curious' rel='tag'> the fast and the curious </a> , <a class='label' href='https://blog.chromium.org/search/label/tracing' rel='tag'> tracing </a> </span> </div> </div> </div> <div class='post' data-id='7940413607251941672' itemscope='' itemtype='http://schema.org/BlogPosting'> <h2 class='title' itemprop='name'> <a href='https://blog.chromium.org/2021/04/help-users-log-in-across-affiliated.html' itemprop='url' title='Help users log in across affiliated sites in Chrome'> Help users log in across affiliated sites in Chrome </a> </h2> <div class='post-header'> <div class='published'> <span class='publishdate' itemprop='datePublished'> Thursday, April 22, 2021 </span> </div> </div> <div class='post-body'> <div class='post-content' itemprop='articleBody'> <script type='text/template'> <p>Chrome can generate a new password, automatically fill saved passwords, sync them and <a href="https://security.googleblog.com/2019/12/better-password-protections-in-chrome.html" target="_blank">warn users when passwords are compromised</a>. This means users do not need to maintain passwords themselves. However, if you employ multiple domains (for example, top-level domains such as <span style="font-family: courier;">https://www.example.com</span> and <span style="font-family: courier;">https://www.example.co.uk</span>) that share the same account management backend, Chrome may not offer to fill passwords across them. This can result in two entries for the same password in different domains, which may get out of sync. </p> <p>Starting in version 91, Chrome will offer to fill passwords saved to domains associated with <a href="https://digitalassetlinks.org/" target="_blank">Digital Asset Links</a> (DALs). DALs have been adopted since 2015, which allow you to <a href="https://developers.google.com/digital-asset-links/v1/create-statement" target="_blank">link Android apps and websites</a>. In Chrome 91, when you set up DALs between sites, Chrome can assist users with logging in across those sites. To make a DAL association, developers need to put a JSON file that follows the <a href="https://developers.google.com/digital-asset-links/v1/statements" target="_blank">DAL syntax</a> at <span style="font-family: courier;">/.well-known/assetlinks.json</span> on both domains.</p> <span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span style="border: none; display: inline-block; height: 340px; overflow: hidden; width: 624px;"><img height="351.00000000000006" src="https://lh4.googleusercontent.com/rczHJ4oYJmu669rWIw7QJMfIcNY47dRoYMjkqbPU9pLOqrTTqan2e_rAEMPn-jrKhABbYmLWJWdyZgPO71-i2OEiRamadGeIBOsJs3i9yhekqNlFeLZJP6Ss_IJjDkFjQAkYx_PrBk2IWxFS9qaTPjCQOF_D9ljI8L7kL4Yj0wR14iYo" style="margin-left: 0px; margin-top: -5.5px;" width="624" /></span></span> <p>To learn more about how to set up a DAL association, <a href="https://developer.chrome.com/blog/site-affiliation/" target="_blank">enable Chrome to share login credentials across affiliated sites</a>.</p> <span class="post-author">Posted by Eiji Kitamura, Developer Advocate and Ali Sarraf, Product Manager</span> <span itemprop='author' itemscope='itemscope' itemtype='http://schema.org/Person'> <meta content='https://plus.google.com/116899029375914044550' itemprop='url'/> </span> </script> <noscript> <p>Chrome can generate a new password, automatically fill saved passwords, sync them and <a href="https://security.googleblog.com/2019/12/better-password-protections-in-chrome.html" target="_blank">warn users when passwords are compromised</a>. This means users do not need to maintain passwords themselves. However, if you employ multiple domains (for example, top-level domains such as <span style="font-family: courier;">https://www.example.com</span> and <span style="font-family: courier;">https://www.example.co.uk</span>) that share the same account management backend, Chrome may not offer to fill passwords across them. This can result in two entries for the same password in different domains, which may get out of sync. </p> <p>Starting in version 91, Chrome will offer to fill passwords saved to domains associated with <a href="https://digitalassetlinks.org/" target="_blank">Digital Asset Links</a> (DALs). DALs have been adopted since 2015, which allow you to <a href="https://developers.google.com/digital-asset-links/v1/create-statement" target="_blank">link Android apps and websites</a>. In Chrome 91, when you set up DALs between sites, Chrome can assist users with logging in across those sites. To make a DAL association, developers need to put a JSON file that follows the <a href="https://developers.google.com/digital-asset-links/v1/statements" target="_blank">DAL syntax</a> at <span style="font-family: courier;">/.well-known/assetlinks.json</span> on both domains.</p> <span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span style="border: none; display: inline-block; height: 340px; overflow: hidden; width: 624px;"><img height="351.00000000000006" src="https://lh4.googleusercontent.com/rczHJ4oYJmu669rWIw7QJMfIcNY47dRoYMjkqbPU9pLOqrTTqan2e_rAEMPn-jrKhABbYmLWJWdyZgPO71-i2OEiRamadGeIBOsJs3i9yhekqNlFeLZJP6Ss_IJjDkFjQAkYx_PrBk2IWxFS9qaTPjCQOF_D9ljI8L7kL4Yj0wR14iYo" style="margin-left: 0px; margin-top: -5.5px;" width="624" /></span></span> <p>To learn more about how to set up a DAL association, <a href="https://developer.chrome.com/blog/site-affiliation/" target="_blank">enable Chrome to share login credentials across affiliated sites</a>.</p> <span class="post-author">Posted by Eiji Kitamura, Developer Advocate and Ali Sarraf, Product Manager</span> <span itemprop='author' itemscope='itemscope' itemtype='http://schema.org/Person'> <meta content='https://plus.google.com/116899029375914044550' itemprop='url'/> </span> </noscript> </div> </div> <div class='share'> <span class='twitter-custom social-wrapper' data-href='http://twitter.com/share?text=Chromium Blog:Help users log in across affiliated sites in Chrome&url=https://blog.chromium.org/2021/04/help-users-log-in-across-affiliated.html&via=ChromiumDev'> <img alt='Share on Twitter' height='24' src='https://www.gstatic.com/images/icons/material/system/2x/post_twitter_black_24dp.png' width='24'/> </span> <span class='fb-custom social-wrapper' data-href='https://www.facebook.com/sharer.php?u=https://blog.chromium.org/2021/04/help-users-log-in-across-affiliated.html'> <img alt='Share on Facebook' height='24' src='https://www.gstatic.com/images/icons/material/system/2x/post_facebook_black_24dp.png' width='24'/> </span> </div> <div class='post-footer'> <div class='cmt_iframe_holder' data-href='https://blog.chromium.org/2021/04/help-users-log-in-across-affiliated.html' data-viewtype='FILTERED_POSTMOD'></div> <a href='https://plus.google.com/112374322230920073195' rel='author' style='display:none;'> Google </a> <div class='label-footer'> </div> </div> </div> <div class='post' data-id='2865802626526343026' itemscope='' itemtype='http://schema.org/BlogPosting'> <h2 class='title' itemprop='name'> <a href='https://blog.chromium.org/2021/04/efficient-and-safe-allocations-everywhere.html' itemprop='url' title='Efficient And Safe Allocations Everywhere!'> Efficient And Safe Allocations Everywhere! </a> </h2> <div class='post-header'> <div class='published'> <span class='publishdate' itemprop='datePublished'> Monday, April 12, 2021 </span> </div> </div> <div class='post-body'> <div class='post-content' itemprop='articleBody'> <script type='text/template'> <i>In our constant work to improve performance, our engineers sometimes have to seek optimizations in places that most software developers don&#8217;t venture. In this post in our series, <a href="https://blog.chromium.org/search/label/the%20fast%20and%20the%20curious" target="_blank">The Fast and the Curious</a>, a team of senior engineers showed how they approached replacing the system-level memory allocator with an optimized version, yielding significant memory savings -- up to 22% on Windows.</i><div><span id="docs-internal-guid-205d813a-7fff-ffa5-8e84-59ee1ac08d8e"><br /></span><a href="https://chromium.googlesource.com/chromium/src/+/master/base/allocator/partition_allocator/PartitionAlloc.md">PartitionAlloc</a> is Chromium&#8217;s memory allocator, designed for lower fragmentation, higher speed, and stronger security and has been used extensively within <a href="https://www.chromium.org/blink">Blink</a> (Chromium&#8217;s rendering engine). <a href="https://blog.chromium.org/2021/03/advanced-memory-management-and-more.html">In Chrome 89</a> the entire Chromium codebase transitioned to using PartitionAlloc everywhere (by intercepting and replacing malloc() and new) on Windows 64-bit and Android. Data from the field demonstrates up to 22% memory savings, and up to 9% improvement in responsiveness and scroll latency of Chrome.</div><div style="text-align: center;"><span id="docs-internal-guid-37fac04a-7fff-d5bd-36e5-adf5479bcd56"><div align="left" dir="ltr" style="margin-left: 0pt;"><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;"><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyIXzfYbvVhMok6OVmRB_h9hkCdqCW70WSUrhgDM_vgUR7K3BSaREabmnx7oExFOjRQcJiTVA3l_cNS_a-iEIiX9WQbek3R3ExCtx7eSxXk-Wr9s4UY8TuEs8Fz4KEQr1hSXq-C8VevMz-/s605/image3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="148" data-original-width="605" height="132" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyIXzfYbvVhMok6OVmRB_h9hkCdqCW70WSUrhgDM_vgUR7K3BSaREabmnx7oExFOjRQcJiTVA3l_cNS_a-iEIiX9WQbek3R3ExCtx7eSxXk-Wr9s4UY8TuEs8Fz4KEQr1hSXq-C8VevMz-/w542-h132/image3.png" width="542" /></a></div></div></div><br /><div class="separator" style="clear: both; text-align: center;"><br /></div></span></div><div><span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">Here's a closer look at memory usage in the browser process for Windows as the M89 release began rolling out in early March:</p></span><div><br /><span><span face="Roboto, sans-serif"><div align="left" dir="ltr" style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; margin-left: 0pt; vertical-align: baseline; white-space: pre-wrap;"><span><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;"><br /></span></span></div><div align="left" dir="ltr" style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; margin-left: 0pt; vertical-align: baseline; white-space: pre-wrap;"><span><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;"><br /></span></span></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvJyoUiUBgdWxbpcF8JiGTtGdH9uZeGHM6AJfZTOWiDGNLxpykVtH_V4z4dtVO7QruiZe3woldnBNNAgA3DL31m8PPWl3aG19rRMc7qk480xajl6aWP5rq1hEEfwLrIz027jvADCckLsEZ/s1458/image2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="709" data-original-width="1458" height="256" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvJyoUiUBgdWxbpcF8JiGTtGdH9uZeGHM6AJfZTOWiDGNLxpykVtH_V4z4dtVO7QruiZe3woldnBNNAgA3DL31m8PPWl3aG19rRMc7qk480xajl6aWP5rq1hEEfwLrIz027jvADCckLsEZ/w524-h256/image2.png" width="524" /></a></div><br /><div dir="ltr" style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; margin-left: 0pt; text-align: center; vertical-align: baseline; white-space: pre-wrap;"><br /></div><div align="left" dir="ltr" style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; margin-left: 0pt; vertical-align: baseline; white-space: pre-wrap;"><br /></div></span><h4 style="text-align: left;"><br /></h4><h2 style="text-align: left;"><span face="Roboto, sans-serif" style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span id="docs-internal-guid-7831b3f4-7fff-68ce-e77b-058ddf504699"><span face="Roboto, sans-serif" style="font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;">Background</span></span></span></h2></span></div><span><span>Chrome is a multi-platform, multi-process, multi-threaded application, serving a wide range of needs, from small embedded <a href="https://developer.android.com/reference/android/webkit/WebView">WebViews</a> on Android to <a href="https://www.reddit.com/r/spacex/comments/gxb7j1/we_are_the_spacex_software_team_ask_us_anything/ft5zou3?utm_source=share&amp;utm_medium=web2x&amp;context=3">spacecraft</a>. Performance and memory footprint are of critical importance, requiring a tight integration between Chrome and its memory allocator. But heterogeneity across platforms can be prohibitive with each platform having a different implementation such as <a href="https://github.com/google/tcmalloc">tcmalloc</a> on Linux and Chrome OS, <a href="http://jemalloc.net/">jemalloc</a> or <a href="https://source.android.com/devices/tech/debug/scudo">scudo</a> on Android, and <a href="https://docs.microsoft.com/en-us/windows/win32/memory/low-fragmentation-heap">LFH</a> on Windows.<br /><br /><br /></span></span>When we started this project, our goals were to: 1) unify memory allocation across platforms, 2) target the lowest memory footprint without compromising security and performance, and 3) tailor the allocator to optimize the performance of Chrome. Thus we made the decision to use Chromium&#8217;s cross-platform allocator, to optimize memory usage for client rather than server workloads and to focus on meaningful end user activities, not micro-benchmarks that wouldn&#8217;t really matter in real world usage.</div><div><br /></div><div><br /></div><div><br /><span><span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span face="Roboto, sans-serif" style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><br /></span></p><h2 style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: left;"><span face="Roboto, sans-serif" style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span id="docs-internal-guid-39bf727d-7fff-c0cc-7eb2-d0f1bf5a0134"><span style="font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;">Allocator Security</span></span></span></h2><div><br /></div></span></span>PartitionAlloc was designed to support multiple independent partitions, i.e. non-overlapping regions of memory. We use these partitions throughout Blink to thwart some forms of type confusion attacks, such as ensuring strings are separated from layout objects. However, this approach only avoids collisions between types that are allocated from different partitions. Furthermore, PartitionAlloc buckets allocations by their sizes, to help avoid type confusion when potentially-colliding objects are of dissimilar size. These techniques work because PartitionAlloc doesn&#8217;t re-use address space. Once PartitionAlloc dedicates a region of address space to a certain partition and size bucket, it will always belong to that partition and size bucket.<br /><br /><br />Additionally, PartitionAlloc protects some of its metadata with guard pages (inaccessible ranges) around memory regions. Not all metadata is equal, however: free-list entries are stored within previously allocated regions, and thus surrounded by other allocations. To detect corrupted free-list entries and off-by-one overflows from client code, we encode and shadow them.<br />Finally, having our own allocator enables advanced security features like <a href="https://source.chromium.org/chromium/chromium/src/+/master:base/memory/checked_ptr.md">MiraclePtr</a> and <a href="https://source.chromium.org/chromium/chromium/src/+/master:base/allocator/partition_allocator/starscan/README.md">*Scan</a>.</div><div><br /></div><div><br /></div><div><br /><span><span><h2 style="text-align: left;"><span face="Roboto, sans-serif" style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;"><span id="docs-internal-guid-30a50237-7fff-5323-2761-bd58302d04c1"><span style="font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;">Architecture Details</span></span></span></span></h2></span></span><div>Each partition in PartitionAlloc uses a single, central, <a href="https://en.wikipedia.org/wiki/Slab_allocation" style="font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;">slab-based allocator</a> to conserve memory, with a minimal per-thread cache in front for scaling to multi-threaded workloads. This simplicity also pays performance dividends: we&#8217;ve extensively profiled and aggressively trimmed the allocator&#8217;s fast path, improving thread-local storage access, locks, reducing cache line fetches, and removing branches.<br /><br />PartitionAlloc pre-reserves slabs of virtual address space. They are gradually backed by physical memory, as allocation requests arrive. Small and medium-sized allocations are grouped in geometrically-spaced, size-segregated buckets, e.g. [241; 256], [257; 288]. Each slab is split into regions (called &#8220;slot spans&#8221;) that satisfy allocations (&#8220;slots&#8221;) from only one particular bucket, thereby increasing cache locality while lowering fragmentation. Conversely, larger allocations don&#8217;t go through the bucket logic and are fulfilled using the operating system&#8217;s primitives directly (mmap() on <a href="https://en.wikipedia.org/wiki/POSIX" style="font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;">POSIX</a> systems, and VirtualAlloc() on Windows).<br /><br />This central allocator is protected by a single per-partition lock. To mitigate the scalability problem arising from contention, we add a small, per-thread cache of small slots in front, yielding a three-tiered architecture:</div><div><br /></div><div><br /><span><span><span face="Roboto, sans-serif"><span><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_jbCHC4V3KykW49-WANX1Yck4poVIHTpkKuLMlrVie4iQDJtgIKmOVk_wURhCVCsuXAN5jTP4D_kyYLzA7UhOx40Q26eJp1WrwfyL9hqBFHE-SiNBFYXyJS9pfKYrtX_b9lUXig-Zpvie/s276/image1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="196" data-original-width="276" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_jbCHC4V3KykW49-WANX1Yck4poVIHTpkKuLMlrVie4iQDJtgIKmOVk_wURhCVCsuXAN5jTP4D_kyYLzA7UhOx40Q26eJp1WrwfyL9hqBFHE-SiNBFYXyJS9pfKYrtX_b9lUXig-Zpvie/s0/image1.png" /></a></div><br /><div style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: center; vertical-align: baseline; white-space: pre-wrap;"><br /></div></span></span></span></span>The first layer (Per-thread cache) holds a small amount of slots belonging to smaller and more commonly used buckets. Because these slots are stored per-thread, they can be allocated without a lock and only requiring a faster <a href="https://en.wikipedia.org/wiki/Thread-local_storage">thread-local storage</a> lookup, improving cache locality in the process. The per-thread cache has been tailored to satisfy the majority of requests by allocating from and releasing memory to the second layer in batches, amortizing lock acquisition, and further improving locality while not trapping excess memory.<br /><br />The second layer (Slot span free-lists) is invoked upon a per-thread cache miss. For each bucket size, PartitionAlloc knows a slot span with free slots associated with that size, and captures a slot from the free-list of that span. This is still a fast path, but slower than per-thread cache as it requires taking a lock. However, this section is only hit for larger allocations not supported by per-thread cache, or as a batch to fill the per-thread cache.<br /><br />Finally, if there are no free slots in the bucket, the third layer (Slot span management) either carves out space from a slab for a new slot span, or allocates an entirely new slab from the operating system, which is a slow but very infrequent operation.<br /><br />The overall performance and space-efficiency of the allocator hinges on the many tradeoffs across its layers such as how much to cache, how many buckets, and memory reclaiming policy. Please refer to <a href="https://chromium.googlesource.com/chromium/src/+/master/base/allocator/partition_allocator/PartitionAlloc.md">PartitionAlloc</a> to learn more about the design.<br /><br />All in all, we hope you will enjoy the additional memory savings and performance improvements brought by PartitionAlloc, ensuring a safer, leaner, and faster Chrome for users on Earth and in outer space alike. Stay tuned for further improvements, and support of more platforms coming in the near future.<br /><br />Posted by Benoît Lizé and Bartek Nowierski, Chrome Software Engineers</div><div><br /><i>Data source for all statistics: <a href="https://www.google.com/chrome/privacy/whitepaper.html#usagestats">Real-world data</a> anonymously aggregated from Chrome clients.<br />*The core metric measures jank -- delay handling user input -- every 30 seconds.</i><span><span><span face="Roboto, sans-serif"><span><div style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; vertical-align: baseline; white-space: pre-wrap;"><br /></div></span></span></span></span></div></div> <span itemprop='author' itemscope='itemscope' itemtype='http://schema.org/Person'> <meta content='https://plus.google.com/116899029375914044550' itemprop='url'/> </span> </script> <noscript> <i>In our constant work to improve performance, our engineers sometimes have to seek optimizations in places that most software developers don&#8217;t venture. In this post in our series, <a href="https://blog.chromium.org/search/label/the%20fast%20and%20the%20curious" target="_blank">The Fast and the Curious</a>, a team of senior engineers showed how they approached replacing the system-level memory allocator with an optimized version, yielding significant memory savings -- up to 22% on Windows.</i><div><span id="docs-internal-guid-205d813a-7fff-ffa5-8e84-59ee1ac08d8e"><br /></span><a href="https://chromium.googlesource.com/chromium/src/+/master/base/allocator/partition_allocator/PartitionAlloc.md">PartitionAlloc</a> is Chromium&#8217;s memory allocator, designed for lower fragmentation, higher speed, and stronger security and has been used extensively within <a href="https://www.chromium.org/blink">Blink</a> (Chromium&#8217;s rendering engine). <a href="https://blog.chromium.org/2021/03/advanced-memory-management-and-more.html">In Chrome 89</a> the entire Chromium codebase transitioned to using PartitionAlloc everywhere (by intercepting and replacing malloc() and new) on Windows 64-bit and Android. Data from the field demonstrates up to 22% memory savings, and up to 9% improvement in responsiveness and scroll latency of Chrome.</div><div style="text-align: center;"><span id="docs-internal-guid-37fac04a-7fff-d5bd-36e5-adf5479bcd56"><div align="left" dir="ltr" style="margin-left: 0pt;"><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;"><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyIXzfYbvVhMok6OVmRB_h9hkCdqCW70WSUrhgDM_vgUR7K3BSaREabmnx7oExFOjRQcJiTVA3l_cNS_a-iEIiX9WQbek3R3ExCtx7eSxXk-Wr9s4UY8TuEs8Fz4KEQr1hSXq-C8VevMz-/s605/image3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="148" data-original-width="605" height="132" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyIXzfYbvVhMok6OVmRB_h9hkCdqCW70WSUrhgDM_vgUR7K3BSaREabmnx7oExFOjRQcJiTVA3l_cNS_a-iEIiX9WQbek3R3ExCtx7eSxXk-Wr9s4UY8TuEs8Fz4KEQr1hSXq-C8VevMz-/w542-h132/image3.png" width="542" /></a></div></div></div><br /><div class="separator" style="clear: both; text-align: center;"><br /></div></span></div><div><span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;">Here's a closer look at memory usage in the browser process for Windows as the M89 release began rolling out in early March:</p></span><div><br /><span><span face="Roboto, sans-serif"><div align="left" dir="ltr" style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; margin-left: 0pt; vertical-align: baseline; white-space: pre-wrap;"><span><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;"><br /></span></span></div><div align="left" dir="ltr" style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; margin-left: 0pt; vertical-align: baseline; white-space: pre-wrap;"><span><span style="font-family: Arial; font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;"><br /></span></span></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvJyoUiUBgdWxbpcF8JiGTtGdH9uZeGHM6AJfZTOWiDGNLxpykVtH_V4z4dtVO7QruiZe3woldnBNNAgA3DL31m8PPWl3aG19rRMc7qk480xajl6aWP5rq1hEEfwLrIz027jvADCckLsEZ/s1458/image2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="709" data-original-width="1458" height="256" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvJyoUiUBgdWxbpcF8JiGTtGdH9uZeGHM6AJfZTOWiDGNLxpykVtH_V4z4dtVO7QruiZe3woldnBNNAgA3DL31m8PPWl3aG19rRMc7qk480xajl6aWP5rq1hEEfwLrIz027jvADCckLsEZ/w524-h256/image2.png" width="524" /></a></div><br /><div dir="ltr" style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; margin-left: 0pt; text-align: center; vertical-align: baseline; white-space: pre-wrap;"><br /></div><div align="left" dir="ltr" style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; margin-left: 0pt; vertical-align: baseline; white-space: pre-wrap;"><br /></div></span><h4 style="text-align: left;"><br /></h4><h2 style="text-align: left;"><span face="Roboto, sans-serif" style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span id="docs-internal-guid-7831b3f4-7fff-68ce-e77b-058ddf504699"><span face="Roboto, sans-serif" style="font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;">Background</span></span></span></h2></span></div><span><span>Chrome is a multi-platform, multi-process, multi-threaded application, serving a wide range of needs, from small embedded <a href="https://developer.android.com/reference/android/webkit/WebView">WebViews</a> on Android to <a href="https://www.reddit.com/r/spacex/comments/gxb7j1/we_are_the_spacex_software_team_ask_us_anything/ft5zou3?utm_source=share&amp;utm_medium=web2x&amp;context=3">spacecraft</a>. Performance and memory footprint are of critical importance, requiring a tight integration between Chrome and its memory allocator. But heterogeneity across platforms can be prohibitive with each platform having a different implementation such as <a href="https://github.com/google/tcmalloc">tcmalloc</a> on Linux and Chrome OS, <a href="http://jemalloc.net/">jemalloc</a> or <a href="https://source.android.com/devices/tech/debug/scudo">scudo</a> on Android, and <a href="https://docs.microsoft.com/en-us/windows/win32/memory/low-fragmentation-heap">LFH</a> on Windows.<br /><br /><br /></span></span>When we started this project, our goals were to: 1) unify memory allocation across platforms, 2) target the lowest memory footprint without compromising security and performance, and 3) tailor the allocator to optimize the performance of Chrome. Thus we made the decision to use Chromium&#8217;s cross-platform allocator, to optimize memory usage for client rather than server workloads and to focus on meaningful end user activities, not micro-benchmarks that wouldn&#8217;t really matter in real world usage.</div><div><br /></div><div><br /></div><div><br /><span><span><p dir="ltr" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span face="Roboto, sans-serif" style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><br /></span></p><h2 style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: left;"><span face="Roboto, sans-serif" style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span id="docs-internal-guid-39bf727d-7fff-c0cc-7eb2-d0f1bf5a0134"><span style="font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;">Allocator Security</span></span></span></h2><div><br /></div></span></span>PartitionAlloc was designed to support multiple independent partitions, i.e. non-overlapping regions of memory. We use these partitions throughout Blink to thwart some forms of type confusion attacks, such as ensuring strings are separated from layout objects. However, this approach only avoids collisions between types that are allocated from different partitions. Furthermore, PartitionAlloc buckets allocations by their sizes, to help avoid type confusion when potentially-colliding objects are of dissimilar size. These techniques work because PartitionAlloc doesn&#8217;t re-use address space. Once PartitionAlloc dedicates a region of address space to a certain partition and size bucket, it will always belong to that partition and size bucket.<br /><br /><br />Additionally, PartitionAlloc protects some of its metadata with guard pages (inaccessible ranges) around memory regions. Not all metadata is equal, however: free-list entries are stored within previously allocated regions, and thus surrounded by other allocations. To detect corrupted free-list entries and off-by-one overflows from client code, we encode and shadow them.<br />Finally, having our own allocator enables advanced security features like <a href="https://source.chromium.org/chromium/chromium/src/+/master:base/memory/checked_ptr.md">MiraclePtr</a> and <a href="https://source.chromium.org/chromium/chromium/src/+/master:base/allocator/partition_allocator/starscan/README.md">*Scan</a>.</div><div><br /></div><div><br /></div><div><br /><span><span><h2 style="text-align: left;"><span face="Roboto, sans-serif" style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;"><span style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;"><span id="docs-internal-guid-30a50237-7fff-5323-2761-bd58302d04c1"><span style="font-size: 16pt; font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;">Architecture Details</span></span></span></span></h2></span></span><div>Each partition in PartitionAlloc uses a single, central, <a href="https://en.wikipedia.org/wiki/Slab_allocation" style="font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;">slab-based allocator</a> to conserve memory, with a minimal per-thread cache in front for scaling to multi-threaded workloads. This simplicity also pays performance dividends: we&#8217;ve extensively profiled and aggressively trimmed the allocator&#8217;s fast path, improving thread-local storage access, locks, reducing cache line fetches, and removing branches.<br /><br />PartitionAlloc pre-reserves slabs of virtual address space. They are gradually backed by physical memory, as allocation requests arrive. Small and medium-sized allocations are grouped in geometrically-spaced, size-segregated buckets, e.g. [241; 256], [257; 288]. Each slab is split into regions (called &#8220;slot spans&#8221;) that satisfy allocations (&#8220;slots&#8221;) from only one particular bucket, thereby increasing cache locality while lowering fragmentation. Conversely, larger allocations don&#8217;t go through the bucket logic and are fulfilled using the operating system&#8217;s primitives directly (mmap() on <a href="https://en.wikipedia.org/wiki/POSIX" style="font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline;">POSIX</a> systems, and VirtualAlloc() on Windows).<br /><br />This central allocator is protected by a single per-partition lock. To mitigate the scalability problem arising from contention, we add a small, per-thread cache of small slots in front, yielding a three-tiered architecture:</div><div><br /></div><div><br /><span><span><span face="Roboto, sans-serif"><span><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_jbCHC4V3KykW49-WANX1Yck4poVIHTpkKuLMlrVie4iQDJtgIKmOVk_wURhCVCsuXAN5jTP4D_kyYLzA7UhOx40Q26eJp1WrwfyL9hqBFHE-SiNBFYXyJS9pfKYrtX_b9lUXig-Zpvie/s276/image1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="196" data-original-width="276" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_jbCHC4V3KykW49-WANX1Yck4poVIHTpkKuLMlrVie4iQDJtgIKmOVk_wURhCVCsuXAN5jTP4D_kyYLzA7UhOx40Q26eJp1WrwfyL9hqBFHE-SiNBFYXyJS9pfKYrtX_b9lUXig-Zpvie/s0/image1.png" /></a></div><br /><div style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; text-align: center; vertical-align: baseline; white-space: pre-wrap;"><br /></div></span></span></span></span>The first layer (Per-thread cache) holds a small amount of slots belonging to smaller and more commonly used buckets. Because these slots are stored per-thread, they can be allocated without a lock and only requiring a faster <a href="https://en.wikipedia.org/wiki/Thread-local_storage">thread-local storage</a> lookup, improving cache locality in the process. The per-thread cache has been tailored to satisfy the majority of requests by allocating from and releasing memory to the second layer in batches, amortizing lock acquisition, and further improving locality while not trapping excess memory.<br /><br />The second layer (Slot span free-lists) is invoked upon a per-thread cache miss. For each bucket size, PartitionAlloc knows a slot span with free slots associated with that size, and captures a slot from the free-list of that span. This is still a fast path, but slower than per-thread cache as it requires taking a lock. However, this section is only hit for larger allocations not supported by per-thread cache, or as a batch to fill the per-thread cache.<br /><br />Finally, if there are no free slots in the bucket, the third layer (Slot span management) either carves out space from a slab for a new slot span, or allocates an entirely new slab from the operating system, which is a slow but very infrequent operation.<br /><br />The overall performance and space-efficiency of the allocator hinges on the many tradeoffs across its layers such as how much to cache, how many buckets, and memory reclaiming policy. Please refer to <a href="https://chromium.googlesource.com/chromium/src/+/master/base/allocator/partition_allocator/PartitionAlloc.md">PartitionAlloc</a> to learn more about the design.<br /><br />All in all, we hope you will enjoy the additional memory savings and performance improvements brought by PartitionAlloc, ensuring a safer, leaner, and faster Chrome for users on Earth and in outer space alike. Stay tuned for further improvements, and support of more platforms coming in the near future.<br /><br />Posted by Benoît Lizé and Bartek Nowierski, Chrome Software Engineers</div><div><br /><i>Data source for all statistics: <a href="https://www.google.com/chrome/privacy/whitepaper.html#usagestats">Real-world data</a> anonymously aggregated from Chrome clients.<br />*The core metric measures jank -- delay handling user input -- every 30 seconds.</i><span><span><span face="Roboto, sans-serif"><span><div style="font-size: 11pt; font-variant-east-asian: normal; font-variant-numeric: normal; line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt; vertical-align: baseline; white-space: pre-wrap;"><br /></div></span></span></span></span></div></div> <span itemprop='author' itemscope='itemscope' itemtype='http://schema.org/Person'> <meta content='https://plus.google.com/116899029375914044550' itemprop='url'/> </span> </noscript> </div> </div> <div class='share'> <span class='twitter-custom social-wrapper' data-href='http://twitter.com/share?text=Chromium Blog:Efficient And Safe Allocations Everywhere!&url=https://blog.chromium.org/2021/04/efficient-and-safe-allocations-everywhere.html&via=ChromiumDev'> <img alt='Share on Twitter' height='24' src='https://www.gstatic.com/images/icons/material/system/2x/post_twitter_black_24dp.png' width='24'/> </span> <span class='fb-custom social-wrapper' data-href='https://www.facebook.com/sharer.php?u=https://blog.chromium.org/2021/04/efficient-and-safe-allocations-everywhere.html'> <img alt='Share on Facebook' height='24' src='https://www.gstatic.com/images/icons/material/system/2x/post_facebook_black_24dp.png' width='24'/> </span> </div> <div class='post-footer'> <div class='cmt_iframe_holder' data-href='https://blog.chromium.org/2021/04/efficient-and-safe-allocations-everywhere.html' data-viewtype='FILTERED_POSTMOD'></div> <a href='https://plus.google.com/112374322230920073195' rel='author' style='display:none;'> Google </a> <div class='label-footer'> <span class='labels-caption'> Labels: </span> <span class='labels'> <a class='label' href='https://blog.chromium.org/search/label/performance' rel='tag'> performance </a> , <a class='label' href='https://blog.chromium.org/search/label/the%20fast%20and%20the%20curious' rel='tag'> the fast and the curious </a> </span> </div> </div> </div> <div class='post' data-id='9010797736590693838' itemscope='' itemtype='http://schema.org/BlogPosting'> <h2 class='title' itemprop='name'> <a href='https://blog.chromium.org/2021/04/dont-copy-that-surface.html' itemprop='url' title='Don’t Copy That Surface'> Don&#8217;t Copy That Surface </a> </h2> <div class='post-header'> <div class='published'> <span class='publishdate' itemprop='datePublished'> Monday, April 5, 2021 </span> </div> </div> <div class='post-body'> <div class='post-content' itemprop='articleBody'> <script type='text/template'> <br /><i>This post is part of a new series <a href="https://blog.chromium.org/search/label/the%20fast%20and%20the%20curious">The Fast and the Curious</a> we're bringing you of deeper dives into the careful trade-offs and complex engineering that goes into making Chrome fast and reliable. This debugging adventure by Chrome developer and <a href="https://randomascii.wordpress.com/" target="_blank">blogger</a> Bruce Dawson reduced CPU usage by about 3% when using a webcam - a real help for those of us relying on video calls.</i><br /><div><i><br /></i></div><div>Video conferencing took on elevated importance in 2020. I&#8217;m not on the <a href="https://meet.google.com/" target="_blank">Google Meet</a> team but I do work on Chrome, so I fired up my <a href="https://randomascii.wordpress.com/2015/09/24/etw-central/" target="_blank">favorite profiler</a> during one of my daily meetings to see if I could find anything useful.<br /><div><div><div><br /></div><div>There is a lot going on during video conferencing, spread across multiple processes. With my usual dozens of tabs open there were 37 Chrome processes, with six of them actively participating in the video conference. In addition there were over 200 other processes running (87 copies of svchost.exe, for instance), with four of those involved in video conferencing. You may well wonder why it takes 10 processes to connect two people, so here is a list of the processes and their roles:<br /><br /><ul style="text-align: left;"><li>audiodg.exe - Windows Audio Device Graph Isolation, audio output</li><li>dwm.exe - Windows Desktop Window Manager, showing video</li><li>svchost.exe - Windows Camera Frame Server (webcam capture)</li><li>System - Windows system process, does miscellaneous tasks on behalf of processes</li><li>chrome.exe - browser process, the master control program</li><li>chrome.exe - renderer process, the Meet tab</li><li>chrome.exe - GPU process, in charge of rendering pages</li><li>chrome.exe - NetworkService utility process, talking to the network</li><li>chrome.exe - VideoCaptureService, talking to the Windows Camera Frame Server</li><li>chrome.exe - AudioService, controls audio input and output</li></ul><br />These tasks are spread across different processes for security and stability. If one of them crashes it can be restarted without taking everything down. If one of them is compromised due to a security bug then it is isolated from the rest of the system and the damage may be contained.<br /><br />This all makes good sense, but having this many processes involved can make performance-profiling challenging. It can be challenging to look through all of these processes to find areas of potential improvement. It is made more difficult by the fact that I know little about the Meet architecture.</div><div><br /></div><div><br /><h1 style="text-align: left;"><span style="font-size: large;">Analyzing a profile</span></h1>Video conferencing is CPU intensive - you have to record, compress, transmit, receive, decompress, and display both audio and video. The data below shows CPU samples recorded by Microsoft&#8217;s Event Tracing for Windows (ETW). This sampling profiler works by interrupting every running thread about 1,000 times a second and recording a call stack. I used Windows Performance Analyzer (WPA) to display the results. In the screenshot below I am looking at a 10-second period and over 16,000 samples (representing about 16 seconds of CPU time) were recorded across the 10 processes involved in video conferencing:</div><div><br /></div><div><div style="text-align: center;"><img height="195" src="https://lh5.googleusercontent.com/jzlLM_kx9rWqLFCR7Y2PlvT2OGUPwSpS4caexwk05o7FVObNRdVOvRSnSLMZsuCEAjDM9e22UGr-XwTaf312_HwIp-gEgvCihj_yqxfauDSAXRjv9mNCuiY9DwU4TZco2x184EXocQ" style="font-family: Arial; font-size: 11pt; margin-left: 0px; margin-top: 0px; white-space: pre-wrap;" width="624" /></div><br /><br />That&#8217;s a lot of samples to look through, but the call stacks are collated so that you can drill down on the busiest stacks. I didn&#8217;t find anything in the first Chrome process, but in the second one I did:</div><div><br /></div><div><div style="text-align: center;"><img height="481" src="https://lh5.googleusercontent.com/u4U4H83xvkp9Rz8HITeRs4y8Xw5E7iZsHxmGrk6mNT3AHy7Djupl5VC387-XKEkeGeAZ0w66Xg-Xu8Q4EOpf2Yq8QdIhVUtitNUU47qIjcTftrGvJMvHd7FZRcV8M29EwRjRGFJFag" style="font-family: Arial; font-size: 11pt; margin-left: 0px; margin-top: 0px; white-space: pre-wrap;" width="624" /></div><br />It doesn&#8217;t look like much, but I recognized immediately that the 124 samples in KiPageFault were worth investigating. Most of the CPU-intensive work in this trace was important and unavoidable work but I had a hunch that these samples represented avoidable work - something that I could fix. And, even though they represented just 0.75% of the samples I suspected that they indicated a somewhat greater cost.<br /><br />I recognized their importance immediately because this is something that I have seen before. <a href="https://en.wikipedia.org/wiki/Demand_paging" target="_blank">KiPageFault</a> means that the processor touched some memory that had been allocated, but was not currently in the process. This could mean that the pages had been removed from the process to save memory, but in an active process on a machine with lots of memory, that didn&#8217;t make sense. What was more likely was that this represented recently allocated memory.<br /><br />When a program allocates a small amount of memory, the local memory manager (sometimes called the &#8220;heap&#8221;) will usually have some available that it can give to the program. But if it doesn&#8217;t have an appropriate block of memory then it will ask the operating system (OS) for some. If a program allocates a large amount of memory (greater than a MB or so) then the heap will definitely ask for more memory. This is, in itself, a relatively cheap operation. The heap asks the OS for some memory, the OS says &#8220;sure&#8221;, then the OS makes note of the fact that it promised this memory, and that&#8217;s it. The OS does not, at that time, actually give the program any memory. This is the way of the world on Windows, Linux, Android and it is good but it can be confusing and surprising. If the process never touches the memory then the memory is never added to the process, but if the process does touch the memory then individual pages of zeroed memory are brought into the process. These are called <a href="https://stackoverflow.com/questions/5684365/what-causes-page-faults" target="_blank">demand-zero page faults</a> because zeroed pages are &#8220;faulted&#8221; into the process on demand.<br /><br />In other words, allocating a large block of memory is quite cheap, but doesn&#8217;t actually set up the promised memory. Then, when the program tries to use the memory and the CPU discovers that there is no memory at that address it triggers an exception, which wakes up the OS. The OS checks its records and realizes that it did in fact promise to put memory at that address so it then puts some there and restarts the program. This happens so quickly that if you&#8217;re not paying attention you will miss it, but it shows up when profiling as samples hitting in KiPageFault.<br /><br />This bizarre dance happens again for every 4-KiB block in the allocation - 4 KiB is the size of the pages that the CPU and the OS work on.<br /><br />The cost is small. Across this 10-second period only 124 samples - representing about 124 ms or 0.124 seconds - hit inside of KiPageFault. The total cost of the enclosing CopyImage_SSE4_1 function was about 240 ms, so the page faults accounted for more than half of this function, but barely a quarter of the cost of the OnSample function on line 15.<br /><br />The total costs of these page faults is modest but they hint at many other costs:<br /><ul style="text-align: left;"><li>If this memory is being allocated repeatedly (presumably every frame) then it must also be freed every frame. On line 26 we can see that the Release function which frees the memory uses another 64 samples.</li><li>When the pages are freed the operating system has to zero them (for security reasons) so that they are ready to be reused. This is done in the Windows System process - an almost entirely hidden cost. Sure enough when I looked in the System process I saw 138 samples in the MiZeroPageThread. I found that 87% of the KiPageFault samples in the entire system were in the CopyImage_SSE4_1 call so presumably 87% of the 138 samples in the MiZeroPageThread were due to this pattern.</li></ul><div style="text-align: center;"><span id="docs-internal-guid-898c4752-7fff-62e3-c338-b5cde4eeaf1f"><span style="border: none; display: inline-block; height: 133px; overflow: hidden; width: 358px;"><img height="133" src="https://lh4.googleusercontent.com/sVhd7AxlSjSNpKZvB1TdSqDrN_rwdgnWKynCNwK8TBv6Re52SXmZsk3QPSP5sO8-nz0HDFF02nPOv1Ts-u8o41_jKOzBTuwNdwoeWRzJew7kWIAd2JzFlHBV8d2MipPFObKAl0SPkQ" style="margin-left: 0px; margin-top: 0px;" width="358" /></span></span></div><br /><br />I analyzed these <a href="https://randomascii.wordpress.com/2014/12/10/hidden-costs-of-memory-allocation/" target="_blank">hidden costs of memory allocation</a> in a 2014 blog post. The basic memory architecture of Windows hasn&#8217;t changed since then so the hidden costs remain about the same.<br /><br />In addition to CPU samples my ETW trace contained call stacks for every call to VirtualAlloc. This WPA screenshot shows a 10-second period where the OnSample function does 298 allocations that are each 1.320 MB, roughly 30 per second:<br /><br /><div style="text-align: center;"><img height="416" src="https://lh6.googleusercontent.com/vJCAnXBCoeip77VshlRUB3aiIUr-imbJ8hOvxFdgRKTRNUcxElMNUgatIELYpGPfpgTwLtB-fOXgNjJgQrM7MwUb0PjW1KAft4efuf0hgVHD0XZld5rCRnknpAxHbRi5KgOJTHimlw" style="font-family: Arial; font-size: 11pt; margin-left: 0px; margin-top: 0px; white-space: pre-wrap;" width="624" /></div><br />At this point we can estimate that the cost of these repeated allocations is 124 (faulting in) plus 64 (freeing) plus 124 (87% of the zeroing samples) for a total of 312 samples. This gets us up to 1.9% of the total CPU cost of video conferencing. Fixing this is not going to change the world, but this is a change worth doing.<br /><br /></div><div><h1 style="text-align: left;"><span style="font-size: large;">But wait, there&#8217;s more!</span></h1>We are locking this buffer so that we can look at the contents, but it turns out we don&#8217;t actually want the lock call to copy the buffer at all. We just want the lock call to describe the buffer to us so that we can look at it in place. Therefore the entire cost of the MFCopyImage call is waste! That&#8217;s another 116 samples. In addition, in the CMF2DMediaBuffer::Unlock call on line 26 there is another call to CMF2DMediaBuffer::ContiguousCopyFrom. That&#8217;s because the Unlock call assumes that we might have modified the copy of the buffer, so it copies it back. So the 101 samples there are all waste as well!<br /><br />If we can examine this buffer without the alloc/copy/copy/free dance then we can save 312 samples plus 116 samples (the rest of the copying cost) plus 101 samples (the copying-it-back cost) for a total saving of 3.2%. This is getting better all the time.<br /><br />Note that sampled data is only statistically valid, and the actual percentages vary significantly depending on the computer and the exact workload. But, the point remains - it is a non-dramatic but worthwhile change to investigate.<br /><br />Despite spending years in the video-game business my knowledge of these graphics-buffer locking and unlocking APIs is weak. I ended up relying on the wisdom of my Twitter followers to come to the conclusion that the copying was entirely avoidable, and to get a rough pattern for how it could be fixed. After <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1071180" target="_blank">filing an overly verbose bug</a> I delegated the task of actually fixing it. The <a href="https://chromium-review.googlesource.com/c/chromium/src/+/2207580" target="_blank">fix landed in M85</a> and was deemed important enough that it was then <a href="https://chromium-review.googlesource.com/c/chromium/src/+/2219172" target="_blank">backported to M84</a>.<br /><br />You&#8217;d have to be paying very close attention to see the difference - spread across a Chrome process and the system process - but I hope that this helped some computers run a bit cooler and last longer on their batteries. And, while this inefficiency was found by profiling Google Meet, the improvement actually benefits any product that uses the webcam inside Chrome (and other Chromium-based browsers).<br /><br /></div><div><h1 style="text-align: left;"><span style="font-size: large;">Verification</span></h1>After the fix landed I compared two 10-second ETW traces from Chrome Canary before and after the change, each taken with no other programs running except a single Chrome tab running the Google Meet pre-meeting page. In both cases I looked at a 10-second period of time in the profiler. This showed:<br /><br /><br /><b>CPU time in OnSample:</b><br /></div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><div>Before: 458 ms (432 ms of which were in Lock/Unlock/KiPageFault)</div><div>After: 27 ms</div></blockquote><div><br /><br /><b>Allocations:</b><br /></div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><div>Before: 30 allocations per second of 1.32 MB (one per frame, running at 30 fps - a higher framerate would mean more allocations), totalling 396 MB over 10 seconds</div><div>After: 0 allocations</div></blockquote><div><br /><br /><b>CPU time in the System process's MiZeroPageThread:</b><br /></div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><div>Before: 36 ms</div><div>After: 0 ms</div></blockquote><div><br />These measurements showed - in three different ways - that the performance problem was fixed. The memory copying in OnSample was gone, the repeated allocations were gone, and the system process was doing less work. Mission accomplished, <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1071180#c18" target="_blank">bug closed</a>.<br /><br /><br /></div></div></div></div> <span itemprop='author' itemscope='itemscope' itemtype='http://schema.org/Person'> <meta content='https://plus.google.com/116899029375914044550' itemprop='url'/> </span> </script> <noscript> <br /><i>This post is part of a new series <a href="https://blog.chromium.org/search/label/the%20fast%20and%20the%20curious">The Fast and the Curious</a> we're bringing you of deeper dives into the careful trade-offs and complex engineering that goes into making Chrome fast and reliable. This debugging adventure by Chrome developer and <a href="https://randomascii.wordpress.com/" target="_blank">blogger</a> Bruce Dawson reduced CPU usage by about 3% when using a webcam - a real help for those of us relying on video calls.</i><br /><div><i><br /></i></div><div>Video conferencing took on elevated importance in 2020. I&#8217;m not on the <a href="https://meet.google.com/" target="_blank">Google Meet</a> team but I do work on Chrome, so I fired up my <a href="https://randomascii.wordpress.com/2015/09/24/etw-central/" target="_blank">favorite profiler</a> during one of my daily meetings to see if I could find anything useful.<br /><div><div><div><br /></div><div>There is a lot going on during video conferencing, spread across multiple processes. With my usual dozens of tabs open there were 37 Chrome processes, with six of them actively participating in the video conference. In addition there were over 200 other processes running (87 copies of svchost.exe, for instance), with four of those involved in video conferencing. You may well wonder why it takes 10 processes to connect two people, so here is a list of the processes and their roles:<br /><br /><ul style="text-align: left;"><li>audiodg.exe - Windows Audio Device Graph Isolation, audio output</li><li>dwm.exe - Windows Desktop Window Manager, showing video</li><li>svchost.exe - Windows Camera Frame Server (webcam capture)</li><li>System - Windows system process, does miscellaneous tasks on behalf of processes</li><li>chrome.exe - browser process, the master control program</li><li>chrome.exe - renderer process, the Meet tab</li><li>chrome.exe - GPU process, in charge of rendering pages</li><li>chrome.exe - NetworkService utility process, talking to the network</li><li>chrome.exe - VideoCaptureService, talking to the Windows Camera Frame Server</li><li>chrome.exe - AudioService, controls audio input and output</li></ul><br />These tasks are spread across different processes for security and stability. If one of them crashes it can be restarted without taking everything down. If one of them is compromised due to a security bug then it is isolated from the rest of the system and the damage may be contained.<br /><br />This all makes good sense, but having this many processes involved can make performance-profiling challenging. It can be challenging to look through all of these processes to find areas of potential improvement. It is made more difficult by the fact that I know little about the Meet architecture.</div><div><br /></div><div><br /><h1 style="text-align: left;"><span style="font-size: large;">Analyzing a profile</span></h1>Video conferencing is CPU intensive - you have to record, compress, transmit, receive, decompress, and display both audio and video. The data below shows CPU samples recorded by Microsoft&#8217;s Event Tracing for Windows (ETW). This sampling profiler works by interrupting every running thread about 1,000 times a second and recording a call stack. I used Windows Performance Analyzer (WPA) to display the results. In the screenshot below I am looking at a 10-second period and over 16,000 samples (representing about 16 seconds of CPU time) were recorded across the 10 processes involved in video conferencing:</div><div><br /></div><div><div style="text-align: center;"><img height="195" src="https://lh5.googleusercontent.com/jzlLM_kx9rWqLFCR7Y2PlvT2OGUPwSpS4caexwk05o7FVObNRdVOvRSnSLMZsuCEAjDM9e22UGr-XwTaf312_HwIp-gEgvCihj_yqxfauDSAXRjv9mNCuiY9DwU4TZco2x184EXocQ" style="font-family: Arial; font-size: 11pt; margin-left: 0px; margin-top: 0px; white-space: pre-wrap;" width="624" /></div><br /><br />That&#8217;s a lot of samples to look through, but the call stacks are collated so that you can drill down on the busiest stacks. I didn&#8217;t find anything in the first Chrome process, but in the second one I did:</div><div><br /></div><div><div style="text-align: center;"><img height="481" src="https://lh5.googleusercontent.com/u4U4H83xvkp9Rz8HITeRs4y8Xw5E7iZsHxmGrk6mNT3AHy7Djupl5VC387-XKEkeGeAZ0w66Xg-Xu8Q4EOpf2Yq8QdIhVUtitNUU47qIjcTftrGvJMvHd7FZRcV8M29EwRjRGFJFag" style="font-family: Arial; font-size: 11pt; margin-left: 0px; margin-top: 0px; white-space: pre-wrap;" width="624" /></div><br />It doesn&#8217;t look like much, but I recognized immediately that the 124 samples in KiPageFault were worth investigating. Most of the CPU-intensive work in this trace was important and unavoidable work but I had a hunch that these samples represented avoidable work - something that I could fix. And, even though they represented just 0.75% of the samples I suspected that they indicated a somewhat greater cost.<br /><br />I recognized their importance immediately because this is something that I have seen before. <a href="https://en.wikipedia.org/wiki/Demand_paging" target="_blank">KiPageFault</a> means that the processor touched some memory that had been allocated, but was not currently in the process. This could mean that the pages had been removed from the process to save memory, but in an active process on a machine with lots of memory, that didn&#8217;t make sense. What was more likely was that this represented recently allocated memory.<br /><br />When a program allocates a small amount of memory, the local memory manager (sometimes called the &#8220;heap&#8221;) will usually have some available that it can give to the program. But if it doesn&#8217;t have an appropriate block of memory then it will ask the operating system (OS) for some. If a program allocates a large amount of memory (greater than a MB or so) then the heap will definitely ask for more memory. This is, in itself, a relatively cheap operation. The heap asks the OS for some memory, the OS says &#8220;sure&#8221;, then the OS makes note of the fact that it promised this memory, and that&#8217;s it. The OS does not, at that time, actually give the program any memory. This is the way of the world on Windows, Linux, Android and it is good but it can be confusing and surprising. If the process never touches the memory then the memory is never added to the process, but if the process does touch the memory then individual pages of zeroed memory are brought into the process. These are called <a href="https://stackoverflow.com/questions/5684365/what-causes-page-faults" target="_blank">demand-zero page faults</a> because zeroed pages are &#8220;faulted&#8221; into the process on demand.<br /><br />In other words, allocating a large block of memory is quite cheap, but doesn&#8217;t actually set up the promised memory. Then, when the program tries to use the memory and the CPU discovers that there is no memory at that address it triggers an exception, which wakes up the OS. The OS checks its records and realizes that it did in fact promise to put memory at that address so it then puts some there and restarts the program. This happens so quickly that if you&#8217;re not paying attention you will miss it, but it shows up when profiling as samples hitting in KiPageFault.<br /><br />This bizarre dance happens again for every 4-KiB block in the allocation - 4 KiB is the size of the pages that the CPU and the OS work on.<br /><br />The cost is small. Across this 10-second period only 124 samples - representing about 124 ms or 0.124 seconds - hit inside of KiPageFault. The total cost of the enclosing CopyImage_SSE4_1 function was about 240 ms, so the page faults accounted for more than half of this function, but barely a quarter of the cost of the OnSample function on line 15.<br /><br />The total costs of these page faults is modest but they hint at many other costs:<br /><ul style="text-align: left;"><li>If this memory is being allocated repeatedly (presumably every frame) then it must also be freed every frame. On line 26 we can see that the Release function which frees the memory uses another 64 samples.</li><li>When the pages are freed the operating system has to zero them (for security reasons) so that they are ready to be reused. This is done in the Windows System process - an almost entirely hidden cost. Sure enough when I looked in the System process I saw 138 samples in the MiZeroPageThread. I found that 87% of the KiPageFault samples in the entire system were in the CopyImage_SSE4_1 call so presumably 87% of the 138 samples in the MiZeroPageThread were due to this pattern.</li></ul><div style="text-align: center;"><span id="docs-internal-guid-898c4752-7fff-62e3-c338-b5cde4eeaf1f"><span style="border: none; display: inline-block; height: 133px; overflow: hidden; width: 358px;"><img height="133" src="https://lh4.googleusercontent.com/sVhd7AxlSjSNpKZvB1TdSqDrN_rwdgnWKynCNwK8TBv6Re52SXmZsk3QPSP5sO8-nz0HDFF02nPOv1Ts-u8o41_jKOzBTuwNdwoeWRzJew7kWIAd2JzFlHBV8d2MipPFObKAl0SPkQ" style="margin-left: 0px; margin-top: 0px;" width="358" /></span></span></div><br /><br />I analyzed these <a href="https://randomascii.wordpress.com/2014/12/10/hidden-costs-of-memory-allocation/" target="_blank">hidden costs of memory allocation</a> in a 2014 blog post. The basic memory architecture of Windows hasn&#8217;t changed since then so the hidden costs remain about the same.<br /><br />In addition to CPU samples my ETW trace contained call stacks for every call to VirtualAlloc. This WPA screenshot shows a 10-second period where the OnSample function does 298 allocations that are each 1.320 MB, roughly 30 per second:<br /><br /><div style="text-align: center;"><img height="416" src="https://lh6.googleusercontent.com/vJCAnXBCoeip77VshlRUB3aiIUr-imbJ8hOvxFdgRKTRNUcxElMNUgatIELYpGPfpgTwLtB-fOXgNjJgQrM7MwUb0PjW1KAft4efuf0hgVHD0XZld5rCRnknpAxHbRi5KgOJTHimlw" style="font-family: Arial; font-size: 11pt; margin-left: 0px; margin-top: 0px; white-space: pre-wrap;" width="624" /></div><br />At this point we can estimate that the cost of these repeated allocations is 124 (faulting in) plus 64 (freeing) plus 124 (87% of the zeroing samples) for a total of 312 samples. This gets us up to 1.9% of the total CPU cost of video conferencing. Fixing this is not going to change the world, but this is a change worth doing.<br /><br /></div><div><h1 style="text-align: left;"><span style="font-size: large;">But wait, there&#8217;s more!</span></h1>We are locking this buffer so that we can look at the contents, but it turns out we don&#8217;t actually want the lock call to copy the buffer at all. We just want the lock call to describe the buffer to us so that we can look at it in place. Therefore the entire cost of the MFCopyImage call is waste! That&#8217;s another 116 samples. In addition, in the CMF2DMediaBuffer::Unlock call on line 26 there is another call to CMF2DMediaBuffer::ContiguousCopyFrom. That&#8217;s because the Unlock call assumes that we might have modified the copy of the buffer, so it copies it back. So the 101 samples there are all waste as well!<br /><br />If we can examine this buffer without the alloc/copy/copy/free dance then we can save 312 samples plus 116 samples (the rest of the copying cost) plus 101 samples (the copying-it-back cost) for a total saving of 3.2%. This is getting better all the time.<br /><br />Note that sampled data is only statistically valid, and the actual percentages vary significantly depending on the computer and the exact workload. But, the point remains - it is a non-dramatic but worthwhile change to investigate.<br /><br />Despite spending years in the video-game business my knowledge of these graphics-buffer locking and unlocking APIs is weak. I ended up relying on the wisdom of my Twitter followers to come to the conclusion that the copying was entirely avoidable, and to get a rough pattern for how it could be fixed. After <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1071180" target="_blank">filing an overly verbose bug</a> I delegated the task of actually fixing it. The <a href="https://chromium-review.googlesource.com/c/chromium/src/+/2207580" target="_blank">fix landed in M85</a> and was deemed important enough that it was then <a href="https://chromium-review.googlesource.com/c/chromium/src/+/2219172" target="_blank">backported to M84</a>.<br /><br />You&#8217;d have to be paying very close attention to see the difference - spread across a Chrome process and the system process - but I hope that this helped some computers run a bit cooler and last longer on their batteries. And, while this inefficiency was found by profiling Google Meet, the improvement actually benefits any product that uses the webcam inside Chrome (and other Chromium-based browsers).<br /><br /></div><div><h1 style="text-align: left;"><span style="font-size: large;">Verification</span></h1>After the fix landed I compared two 10-second ETW traces from Chrome Canary before and after the change, each taken with no other programs running except a single Chrome tab running the Google Meet pre-meeting page. In both cases I looked at a 10-second period of time in the profiler. This showed:<br /><br /><br /><b>CPU time in OnSample:</b><br /></div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><div>Before: 458 ms (432 ms of which were in Lock/Unlock/KiPageFault)</div><div>After: 27 ms</div></blockquote><div><br /><br /><b>Allocations:</b><br /></div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><div>Before: 30 allocations per second of 1.32 MB (one per frame, running at 30 fps - a higher framerate would mean more allocations), totalling 396 MB over 10 seconds</div><div>After: 0 allocations</div></blockquote><div><br /><br /><b>CPU time in the System process's MiZeroPageThread:</b><br /></div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><div>Before: 36 ms</div><div>After: 0 ms</div></blockquote><div><br />These measurements showed - in three different ways - that the performance problem was fixed. The memory copying in OnSample was gone, the repeated allocations were gone, and the system process was doing less work. Mission accomplished, <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1071180#c18" target="_blank">bug closed</a>.<br /><br /><br /></div></div></div></div> <span itemprop='author' itemscope='itemscope' itemtype='http://schema.org/Person'> <meta content='https://plus.google.com/116899029375914044550' itemprop='url'/> </span> </noscript> </div> </div> <div class='share'> <span class='twitter-custom social-wrapper' data-href='http://twitter.com/share?text=Chromium Blog:Don’t Copy That Surface&url=https://blog.chromium.org/2021/04/dont-copy-that-surface.html&via=ChromiumDev'> <img alt='Share on Twitter' height='24' src='https://www.gstatic.com/images/icons/material/system/2x/post_twitter_black_24dp.png' width='24'/> </span> <span class='fb-custom social-wrapper' data-href='https://www.facebook.com/sharer.php?u=https://blog.chromium.org/2021/04/dont-copy-that-surface.html'> <img alt='Share on Facebook' height='24' src='https://www.gstatic.com/images/icons/material/system/2x/post_facebook_black_24dp.png' width='24'/> </span> </div> <div class='post-footer'> <div class='cmt_iframe_holder' data-href='https://blog.chromium.org/2021/04/dont-copy-that-surface.html' data-viewtype='FILTERED_POSTMOD'></div> <a href='https://plus.google.com/112374322230920073195' rel='author' style='display:none;'> Google </a> <div class='label-footer'> <span class='labels-caption'> Labels: </span> <span class='labels'> <a class='label' href='https://blog.chromium.org/search/label/performance' rel='tag'> performance </a> , <a class='label' href='https://blog.chromium.org/search/label/the%20fast%20and%20the%20curious' rel='tag'> the fast and the curious </a> </span> </div> </div> </div> <div class='blog-pager' id='blog-pager'> <a class='home-link' href='https://blog.chromium.org/'> <i class='material-icons'> &#59530; </i> </a> <span id='blog-pager-newer-link'> <a class='blog-pager-newer-link' href='https://blog.chromium.org/search?updated-max=2021-07-20T09:58:00-07:00&max-results=7&reverse-paginate=true' id='Blog1_blog-pager-newer-link' title='Newer Posts'> <i class='material-icons'> &#58820; </i> </a> </span> <span id='blog-pager-older-link'> <a class='blog-pager-older-link' href='https://blog.chromium.org/search?updated-max=2021-04-05T09:48:00-07:00&max-results=7' id='Blog1_blog-pager-older-link' title='Older Posts'> <i class='material-icons'> &#58824; </i> </a> </span> </div> <div class='clear'></div> </div></div> </div> </div> <div class='col-right'> <div class='section' id='sidebar-top'><div class='widget HTML' data-version='1' id='HTML8'> <div class='widget-content'> <div class='searchBox'> <input type='text' title='Search This Blog' placeholder='Search blog ...' /> </div> </div> <div class='clear'></div> </div></div> <div id='aside'> <div class='section' id='sidebar'><div class='widget Label' data-version='1' id='Label1'> <div class='tab'> <img class='sidebar-icon' src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAYpJREFUeNrs2aFuwzAQBmAvKRkMKRjZA4QMDJaWFgyMjuzFRg37DIUlA3uFkoGQSaWzJU+tpri5O9+l/zSfdFJlpe59yTmyVedq1PjfcZMZ70NuQnaF8w8htyE/rABtpviXkLcK88c5HhLkMBfgVan43zfFBNGMjHVGT/s55KP2pAvidbGHd+nzKt1RKSLG3rKF1iPFv6UWiPke8i7kEqGdGsI1O+LYVdqJAjgirwkKYD0ytkJBUNbAMvX8V3q9PhUsYvU1sWD8SO/sQvx2ahxOiNoJCSBCoAHYCEQAC4EKICOQASQEOmAS8RcAFxFN5hiIiugpgC3wk9hQAHH/70EBHXUN7IER5EWMiBgo2+nzOKQv9SCAeEM/OQAkhE/ncccFICB87qzQMia5FsJfOui0zMnmRvipU1ormHQuxGTxUsAcCFLxJQBLBLn4UoAFglW8BkATwS5eC6CBEBWvCShBiIvXBkgQRcVbADiI4uKtABSESvGWgB9EzHt3+tNwyO0qa9SoIYtvAQYAqDJhaWWeMecAAAAASUVORK5CYII='/> <h2> Labels </h2> <i class='material-icons arrow'> &#58821; </i> </div> <div class='widget-content list-label-widget-content'> <ul> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/%24200K'> $200K </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/10th%20birthday'> 10th birthday </a> <span dir='ltr'> 4 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/abusive%20ads'> abusive ads </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/abusive%20notifications'> abusive notifications </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/accessibility'> accessibility </a> <span dir='ltr'> 3 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/ad%20blockers'> ad blockers </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/ad%20blocking'> ad blocking </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/advanced%20capabilities'> advanced capabilities </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/android'> android </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/anti%20abuse'> anti abuse </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/anti-deception'> anti-deception </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/background%20periodic%20sync'> background periodic sync </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/badging'> badging </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/benchmarks'> benchmarks </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/beta'> beta </a> <span dir='ltr'> 83 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/better%20ads%20standards'> better ads standards </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/billing'> billing </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/birthday'> birthday </a> <span dir='ltr'> 4 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/blink'> blink </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/browser'> browser </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/browser%20interoperability'> browser interoperability </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/bundles'> bundles </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/capabilities'> capabilities </a> <span dir='ltr'> 6 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/capable%20web'> capable web </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/cds'> cds </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/cds18'> cds18 </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/cds2018'> cds2018 </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chrome'> chrome </a> <span dir='ltr'> 35 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chrome%2081'> chrome 81 </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chrome%2083'> chrome 83 </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chrome%2084'> chrome 84 </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chrome%20ads'> chrome ads </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chrome%20apps'> chrome apps </a> <span dir='ltr'> 5 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/Chrome%20dev'> Chrome dev </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chrome%20dev%20summit'> chrome dev summit </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chrome%20dev%20summit%202018'> chrome dev summit 2018 </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chrome%20dev%20summit%202019'> chrome dev summit 2019 </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chrome%20developer'> chrome developer </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/Chrome%20Developer%20Center'> Chrome Developer Center </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chrome%20developer%20summit'> chrome developer summit </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chrome%20devtools'> chrome devtools </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/Chrome%20extension'> Chrome extension </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chrome%20extensions'> chrome extensions </a> <span dir='ltr'> 3 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/Chrome%20Frame'> Chrome Frame </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/Chrome%20lite'> Chrome lite </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/Chrome%20on%20Android'> Chrome on Android </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chrome%20on%20ios'> chrome on ios </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/Chrome%20on%20Mac'> Chrome on Mac </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/Chrome%20OS'> Chrome OS </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chrome%20privacy'> chrome privacy </a> <span dir='ltr'> 4 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chrome%20releases'> chrome releases </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chrome%20security'> chrome security </a> <span dir='ltr'> 10 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chrome%20web%20store'> chrome web store </a> <span dir='ltr'> 32 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chromedevtools'> chromedevtools </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chromeframe'> chromeframe </a> <span dir='ltr'> 3 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chromeos'> chromeos </a> <span dir='ltr'> 4 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chromeos.dev'> chromeos.dev </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/chromium'> chromium </a> <span dir='ltr'> 9 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/cloud%20print'> cloud print </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/coalition'> coalition </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/coalition%20for%20better%20ads'> coalition for better ads </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/contact%20picker'> contact picker </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/content%20indexing'> content indexing </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/cookies'> cookies </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/core%20web%20vitals'> core web vitals </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/csrf'> csrf </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/css'> css </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/cumulative%20layout%20shift'> cumulative layout shift </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/custom%20tabs'> custom tabs </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/dart'> dart </a> <span dir='ltr'> 8 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/dashboard'> dashboard </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/Data%20Saver'> Data Saver </a> <span dir='ltr'> 3 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/Data%20saver%20desktop%20extension'> Data saver desktop extension </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/day%202'> day 2 </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/deceptive%20installation'> deceptive installation </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/declarative%20net%20request%20api'> declarative net request api </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/design'> design </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/developer%20dashboard'> developer dashboard </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/Developer%20Program%20Policy'> Developer Program Policy </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/developer%20website'> developer website </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/devtools'> devtools </a> <span dir='ltr'> 13 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/digital%20event'> digital event </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/discoverability'> discoverability </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/DNS-over-HTTPS'> DNS-over-HTTPS </a> <span dir='ltr'> 4 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/DoH'> DoH </a> <span dir='ltr'> 4 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/emoji'> emoji </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/emscriptem'> emscriptem </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/enterprise'> enterprise </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/extensions'> extensions </a> <span dir='ltr'> 27 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/Fast%20badging'> Fast badging </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/faster%20web'> faster web </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/features'> features </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/feedback'> feedback </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/field%20data'> field data </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/first%20input%20delay'> first input delay </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/Follow'> Follow </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/fonts'> fonts </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/form%20controls'> form controls </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/frameworks'> frameworks </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/fugu'> fugu </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/fund'> fund </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/funding'> funding </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/gdd'> gdd </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/google%20earth'> google earth </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/google%20event'> google event </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/google%20io%202019'> google io 2019 </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/google%20web%20developer'> google web developer </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/googlechrome'> googlechrome </a> <span dir='ltr'> 12 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/harmful%20ads'> harmful ads </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/html5'> html5 </a> <span dir='ltr'> 11 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/HTTP%2F3'> HTTP/3 </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/HTTPS'> HTTPS </a> <span dir='ltr'> 4 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/iframes'> iframes </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/images'> images </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/incognito'> incognito </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/insecure%20forms'> insecure forms </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/intent%20to%20explain'> intent to explain </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/ios'> ios </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/ios%20Chrome'> ios Chrome </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/issue%20tracker'> issue tracker </a> <span dir='ltr'> 3 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/jank'> jank </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/javascript'> javascript </a> <span dir='ltr'> 5 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/lab%20data'> lab data </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/labelling'> labelling </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/largest%20contentful%20paint'> largest contentful paint </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/launch'> launch </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/lazy-loading'> lazy-loading </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/lighthouse'> lighthouse </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/linux'> linux </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/Lite%20Mode'> Lite Mode </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/Lite%20pages'> Lite pages </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/loading%20interventions'> loading interventions </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/loading%20optimizations'> loading optimizations </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/lock%20icon'> lock icon </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/long-tail'> long-tail </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/mac'> mac </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/manifest%20v3'> manifest v3 </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/metrics'> metrics </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/microsoft%20edge'> microsoft edge </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/mixed%20forms'> mixed forms </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/mobile'> mobile </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/na'> na </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/native%20client'> native client </a> <span dir='ltr'> 8 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/native%20file%20system'> native file system </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/New%20Features'> New Features </a> <span dir='ltr'> 5 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/notifications'> notifications </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/octane'> octane </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/open%20web'> open web </a> <span dir='ltr'> 4 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/origin%20trials'> origin trials </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/pagespeed%20insights'> pagespeed insights </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/pagespeedinsights'> pagespeedinsights </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/passwords'> passwords </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/payment%20handler'> payment handler </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/payment%20request'> payment request </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/payments'> payments </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/performance'> performance </a> <span dir='ltr'> 20 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/performance%20tools'> performance tools </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/permission%20UI'> permission UI </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/permissions'> permissions </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/play%20store'> play store </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/portals'> portals </a> <span dir='ltr'> 3 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/prefetching'> prefetching </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/privacy'> privacy </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/privacy%20sandbox'> privacy sandbox </a> <span dir='ltr'> 4 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/private%20prefetch%20proxy'> private prefetch proxy </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/profile%20guided%20optimization'> profile guided optimization </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/progressive%20web%20apps'> progressive web apps </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/Project%20Strobe'> Project Strobe </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/protection'> protection </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/pwa'> pwa </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/QUIC'> QUIC </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/quieter%20permissions'> quieter permissions </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/releases'> releases </a> <span dir='ltr'> 3 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/removals'> removals </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/rlz'> rlz </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/root%20program'> root program </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/safe%20browsing'> safe browsing </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/Secure%20DNS'> Secure DNS </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/security'> security </a> <span dir='ltr'> 36 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/site%20isolation'> site isolation </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/slow%20loading'> slow loading </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/sms%20receiver'> sms receiver </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/spam%20policy'> spam policy </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/spdy'> spdy </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/spectre'> spectre </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/speed'> speed </a> <span dir='ltr'> 4 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/ssl'> ssl </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/store%20listing'> store listing </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/strobe'> strobe </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/subscription%20pages'> subscription pages </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/suspicious%20site%20reporter%20extension'> suspicious site reporter extension </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/TCP'> TCP </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/the%20fast%20and%20the%20curious'> the fast and the curious </a> <span dir='ltr'> 23 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/TLS'> TLS </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/tools'> tools </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/tracing'> tracing </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/transparency'> transparency </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/trusted%20web%20activities'> trusted web activities </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/twa'> twa </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/user%20agent%20string'> user agent string </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/user%20data%20policy'> user data policy </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/v8'> v8 </a> <span dir='ltr'> 6 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/video'> video </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/wasm'> wasm </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/web'> web </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/web%20apps'> web apps </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/web%20assembly'> web assembly </a> <span dir='ltr'> 2 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/web%20developers'> web developers </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/web%20intents'> web intents </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/web%20packaging'> web packaging </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/web%20payments'> web payments </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/web%20platform'> web platform </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/web%20request%20api'> web request api </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/web%20vitals'> web vitals </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/web.dev'> web.dev </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/web.dev%20live'> web.dev live </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/webapi'> webapi </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/webassembly'> webassembly </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/webaudio'> webaudio </a> <span dir='ltr'> 3 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/webgl'> webgl </a> <span dir='ltr'> 7 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/webkit'> webkit </a> <span dir='ltr'> 5 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/WebM'> WebM </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/webmaster'> webmaster </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/webp'> webp </a> <span dir='ltr'> 5 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/webrtc'> webrtc </a> <span dir='ltr'> 6 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/websockets'> websockets </a> <span dir='ltr'> 5 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/webtiming'> webtiming </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/writable-files'> writable-files </a> <span dir='ltr'> 1 </span> </li> <li> <a dir='ltr' href='https://blog.chromium.org/search/label/yerba%20beuna%20center%20for%20the%20arts'> yerba beuna center for the arts </a> <span dir='ltr'> 1 </span> </li> </ul> <div class='clear'></div> </div> </div><div class='widget BlogArchive' data-version='1' id='BlogArchive1'> <div class='tab'> <i class='material-icons icon'> &#58055; </i> <h2> Archive </h2> <i class='material-icons arrow'> &#58821; </i> </div> <div class='widget-content'> <div id='ArchiveList'> <div id='BlogArchive1_ArchiveList'> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class='intervalToggle'> <span class='new-toggle' href='javascript:void(0)'> <i class='material-icons arrow'> &#58821; </i> </span> <a class='toggle' href='javascript:void(0)' style='display: none'> <span class='zippy'> <i class='material-icons'> &#58821; </i> &#160; </span> </a> <a class='post-count-link' href='https://blog.chromium.org/2025/'> 2025 </a> </div> <div class='items'> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2025/01/'> Jan </a> </div> <div class='items'> </div> </li> </ul> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class='intervalToggle'> <span class='new-toggle' href='javascript:void(0)'> <i class='material-icons arrow'> &#58821; </i> </span> <a class='toggle' href='javascript:void(0)' style='display: none'> <span class='zippy'> <i class='material-icons'> &#58821; </i> &#160; </span> </a> <a class='post-count-link' href='https://blog.chromium.org/2024/'> 2024 </a> </div> <div class='items'> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2024/12/'> Dec </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2024/08/'> Aug </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2024/06/'> Jun </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2024/05/'> May </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2024/04/'> Apr </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2024/03/'> Mar </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2024/02/'> Feb </a> </div> <div class='items'> </div> </li> </ul> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class='intervalToggle'> <span class='new-toggle' href='javascript:void(0)'> <i class='material-icons arrow'> &#58821; </i> </span> <a class='toggle' href='javascript:void(0)' style='display: none'> <span class='zippy'> <i class='material-icons'> &#58821; </i> &#160; </span> </a> <a class='post-count-link' href='https://blog.chromium.org/2023/'> 2023 </a> </div> <div class='items'> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2023/11/'> Nov </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2023/10/'> Oct </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2023/09/'> Sep </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2023/08/'> Aug </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2023/06/'> Jun </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2023/05/'> May </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2023/04/'> Apr </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2023/02/'> Feb </a> </div> <div class='items'> </div> </li> </ul> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class='intervalToggle'> <span class='new-toggle' href='javascript:void(0)'> <i class='material-icons arrow'> &#58821; </i> </span> <a class='toggle' href='javascript:void(0)' style='display: none'> <span class='zippy'> <i class='material-icons'> &#58821; </i> &#160; </span> </a> <a class='post-count-link' href='https://blog.chromium.org/2022/'> 2022 </a> </div> <div class='items'> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2022/12/'> Dec </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2022/09/'> Sep </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2022/08/'> Aug </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2022/06/'> Jun </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2022/05/'> May </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2022/04/'> Apr </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2022/03/'> Mar </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2022/02/'> Feb </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2022/01/'> Jan </a> </div> <div class='items'> </div> </li> </ul> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate expanded'> <div class='intervalToggle'> <span class='new-toggle' href='javascript:void(0)'> <i class='material-icons arrow'> &#58821; </i> </span> <a class='toggle' href='javascript:void(0)' style='display: none'> <span class='zippy toggle-open'> <i class='material-icons'> &#58823; </i> &#160; </span> </a> <a class='post-count-link' href='https://blog.chromium.org/2021/'> 2021 </a> </div> <div class='items'> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2021/12/'> Dec </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2021/11/'> Nov </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2021/10/'> Oct </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2021/09/'> Sep </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2021/08/'> Aug </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2021/07/'> Jul </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2021/06/'> Jun </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2021/05/'> May </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate expanded'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2021/04/'> Apr </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2021/03/'> Mar </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2021/02/'> Feb </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2021/01/'> Jan </a> </div> <div class='items'> </div> </li> </ul> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class='intervalToggle'> <span class='new-toggle' href='javascript:void(0)'> <i class='material-icons arrow'> &#58821; </i> </span> <a class='toggle' href='javascript:void(0)' style='display: none'> <span class='zippy'> <i class='material-icons'> &#58821; </i> &#160; </span> </a> <a class='post-count-link' href='https://blog.chromium.org/2020/'> 2020 </a> </div> <div class='items'> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2020/12/'> Dec </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2020/11/'> Nov </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2020/10/'> Oct </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2020/09/'> Sep </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2020/08/'> Aug </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2020/07/'> Jul </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2020/06/'> Jun </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2020/05/'> May </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2020/04/'> Apr </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2020/03/'> Mar </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2020/02/'> Feb </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2020/01/'> Jan </a> </div> <div class='items'> </div> </li> </ul> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class='intervalToggle'> <span class='new-toggle' href='javascript:void(0)'> <i class='material-icons arrow'> &#58821; </i> </span> <a class='toggle' href='javascript:void(0)' style='display: none'> <span class='zippy'> <i class='material-icons'> &#58821; </i> &#160; </span> </a> <a class='post-count-link' href='https://blog.chromium.org/2019/'> 2019 </a> </div> <div class='items'> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2019/12/'> Dec </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2019/11/'> Nov </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2019/10/'> Oct </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2019/09/'> Sep </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2019/08/'> Aug </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2019/07/'> Jul </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2019/06/'> Jun </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2019/05/'> May </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2019/04/'> Apr </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2019/03/'> Mar </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2019/02/'> Feb </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2019/01/'> Jan </a> </div> <div class='items'> </div> </li> </ul> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class='intervalToggle'> <span class='new-toggle' href='javascript:void(0)'> <i class='material-icons arrow'> &#58821; </i> </span> <a class='toggle' href='javascript:void(0)' style='display: none'> <span class='zippy'> <i class='material-icons'> &#58821; </i> &#160; </span> </a> <a class='post-count-link' href='https://blog.chromium.org/2018/'> 2018 </a> </div> <div class='items'> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2018/12/'> Dec </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2018/11/'> Nov </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2018/10/'> Oct </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2018/09/'> Sep </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2018/08/'> Aug </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2018/07/'> Jul </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2018/06/'> Jun </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2018/05/'> May </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2018/04/'> Apr </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2018/03/'> Mar </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2018/02/'> Feb </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2018/01/'> Jan </a> </div> <div class='items'> </div> </li> </ul> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class='intervalToggle'> <span class='new-toggle' href='javascript:void(0)'> <i class='material-icons arrow'> &#58821; </i> </span> <a class='toggle' href='javascript:void(0)' style='display: none'> <span class='zippy'> <i class='material-icons'> &#58821; </i> &#160; </span> </a> <a class='post-count-link' href='https://blog.chromium.org/2017/'> 2017 </a> </div> <div class='items'> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2017/12/'> Dec </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2017/11/'> Nov </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2017/10/'> Oct </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2017/09/'> Sep </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2017/08/'> Aug </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2017/07/'> Jul </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2017/06/'> Jun </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2017/05/'> May </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2017/04/'> Apr </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2017/03/'> Mar </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2017/02/'> Feb </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2017/01/'> Jan </a> </div> <div class='items'> </div> </li> </ul> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class='intervalToggle'> <span class='new-toggle' href='javascript:void(0)'> <i class='material-icons arrow'> &#58821; </i> </span> <a class='toggle' href='javascript:void(0)' style='display: none'> <span class='zippy'> <i class='material-icons'> &#58821; </i> &#160; </span> </a> <a class='post-count-link' href='https://blog.chromium.org/2016/'> 2016 </a> </div> <div class='items'> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2016/12/'> Dec </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2016/11/'> Nov </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2016/10/'> Oct </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2016/09/'> Sep </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2016/08/'> Aug </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2016/06/'> Jun </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2016/05/'> May </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2016/04/'> Apr </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2016/03/'> Mar </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2016/02/'> Feb </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2016/01/'> Jan </a> </div> <div class='items'> </div> </li> </ul> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class='intervalToggle'> <span class='new-toggle' href='javascript:void(0)'> <i class='material-icons arrow'> &#58821; </i> </span> <a class='toggle' href='javascript:void(0)' style='display: none'> <span class='zippy'> <i class='material-icons'> &#58821; </i> &#160; </span> </a> <a class='post-count-link' href='https://blog.chromium.org/2015/'> 2015 </a> </div> <div class='items'> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2015/12/'> Dec </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2015/11/'> Nov </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2015/10/'> Oct </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2015/09/'> Sep </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2015/08/'> Aug </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2015/07/'> Jul </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2015/06/'> Jun </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2015/05/'> May </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2015/04/'> Apr </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2015/03/'> Mar </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2015/02/'> Feb </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2015/01/'> Jan </a> </div> <div class='items'> </div> </li> </ul> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class='intervalToggle'> <span class='new-toggle' href='javascript:void(0)'> <i class='material-icons arrow'> &#58821; </i> </span> <a class='toggle' href='javascript:void(0)' style='display: none'> <span class='zippy'> <i class='material-icons'> &#58821; </i> &#160; </span> </a> <a class='post-count-link' href='https://blog.chromium.org/2014/'> 2014 </a> </div> <div class='items'> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2014/12/'> Dec </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2014/11/'> Nov </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2014/10/'> Oct </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2014/09/'> Sep </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2014/08/'> Aug </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2014/07/'> Jul </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2014/06/'> Jun </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2014/05/'> May </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2014/04/'> Apr </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2014/03/'> Mar </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2014/02/'> Feb </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2014/01/'> Jan </a> </div> <div class='items'> </div> </li> </ul> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class='intervalToggle'> <span class='new-toggle' href='javascript:void(0)'> <i class='material-icons arrow'> &#58821; </i> </span> <a class='toggle' href='javascript:void(0)' style='display: none'> <span class='zippy'> <i class='material-icons'> &#58821; </i> &#160; </span> </a> <a class='post-count-link' href='https://blog.chromium.org/2013/'> 2013 </a> </div> <div class='items'> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2013/12/'> Dec </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2013/11/'> Nov </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2013/10/'> Oct </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2013/09/'> Sep </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2013/08/'> Aug </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2013/07/'> Jul </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2013/06/'> Jun </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2013/05/'> May </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2013/04/'> Apr </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2013/03/'> Mar </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2013/02/'> Feb </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2013/01/'> Jan </a> </div> <div class='items'> </div> </li> </ul> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class='intervalToggle'> <span class='new-toggle' href='javascript:void(0)'> <i class='material-icons arrow'> &#58821; </i> </span> <a class='toggle' href='javascript:void(0)' style='display: none'> <span class='zippy'> <i class='material-icons'> &#58821; </i> &#160; </span> </a> <a class='post-count-link' href='https://blog.chromium.org/2012/'> 2012 </a> </div> <div class='items'> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2012/12/'> Dec </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2012/11/'> Nov </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2012/10/'> Oct </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2012/09/'> Sep </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2012/08/'> Aug </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2012/07/'> Jul </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2012/06/'> Jun </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2012/05/'> May </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2012/04/'> Apr </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2012/03/'> Mar </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2012/02/'> Feb </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2012/01/'> Jan </a> </div> <div class='items'> </div> </li> </ul> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class='intervalToggle'> <span class='new-toggle' href='javascript:void(0)'> <i class='material-icons arrow'> &#58821; </i> </span> <a class='toggle' href='javascript:void(0)' style='display: none'> <span class='zippy'> <i class='material-icons'> &#58821; </i> &#160; </span> </a> <a class='post-count-link' href='https://blog.chromium.org/2011/'> 2011 </a> </div> <div class='items'> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2011/12/'> Dec </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2011/11/'> Nov </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2011/10/'> Oct </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2011/09/'> Sep </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2011/08/'> Aug </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2011/07/'> Jul </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2011/06/'> Jun </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2011/05/'> May </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2011/04/'> Apr </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2011/03/'> Mar </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2011/02/'> Feb </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2011/01/'> Jan </a> </div> <div class='items'> </div> </li> </ul> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class='intervalToggle'> <span class='new-toggle' href='javascript:void(0)'> <i class='material-icons arrow'> &#58821; </i> </span> <a class='toggle' href='javascript:void(0)' style='display: none'> <span class='zippy'> <i class='material-icons'> &#58821; </i> &#160; </span> </a> <a class='post-count-link' href='https://blog.chromium.org/2010/'> 2010 </a> </div> <div class='items'> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2010/12/'> Dec </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2010/11/'> Nov </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2010/10/'> Oct </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2010/09/'> Sep </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2010/08/'> Aug </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2010/07/'> Jul </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2010/06/'> Jun </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2010/05/'> May </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2010/04/'> Apr </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2010/03/'> Mar </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2010/02/'> Feb </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2010/01/'> Jan </a> </div> <div class='items'> </div> </li> </ul> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class='intervalToggle'> <span class='new-toggle' href='javascript:void(0)'> <i class='material-icons arrow'> &#58821; </i> </span> <a class='toggle' href='javascript:void(0)' style='display: none'> <span class='zippy'> <i class='material-icons'> &#58821; </i> &#160; </span> </a> <a class='post-count-link' href='https://blog.chromium.org/2009/'> 2009 </a> </div> <div class='items'> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2009/12/'> Dec </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2009/11/'> Nov </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2009/09/'> Sep </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2009/08/'> Aug </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2009/07/'> Jul </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2009/06/'> Jun </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2009/05/'> May </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2009/04/'> Apr </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2009/03/'> Mar </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2009/02/'> Feb </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2009/01/'> Jan </a> </div> <div class='items'> </div> </li> </ul> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class='intervalToggle'> <span class='new-toggle' href='javascript:void(0)'> <i class='material-icons arrow'> &#58821; </i> </span> <a class='toggle' href='javascript:void(0)' style='display: none'> <span class='zippy'> <i class='material-icons'> &#58821; </i> &#160; </span> </a> <a class='post-count-link' href='https://blog.chromium.org/2008/'> 2008 </a> </div> <div class='items'> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2008/12/'> Dec </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2008/11/'> Nov </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2008/10/'> Oct </a> </div> <div class='items'> </div> </li> </ul> <ul class='hierarchy'> <li class='archivedate collapsed'> <div class=''> <a class='post-count-link' href='https://blog.chromium.org/2008/09/'> Sep </a> </div> <div class='items'> </div> </li> </ul> </div> </li> </ul> </div> </div> <div class='clear'></div> </div> </div><div class='widget HTML' data-version='1' id='HTML6'> <div class='widget-content'> <a href="http://blog.chromium.org/atom.xml"> <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAihJREFUeNrsWa9Pw0AU7viRMDFRBAkzJDMIBIhJJhCzk7NILIqMv4AEhdz+BCY3OYssAlGBoAJREpZwAlHEBO8lr8nSvNeVbu1dyX3JlzTrXfa+u/e9d7c5joWFhYVO1Fa8PwH2gK6m+BRwAvSlAdsrgr8E1jUuMH73GTAEzrkBWymTewZlihhLmgDXIAFuHgGVQOUF7OSYM1p6PgTuA1vAZlUEvAnPdapcMY0VICECekQ0XRfYrqoHsAGNgXfAoMomRiFDEhOZkkL3S88hMaB2LwXp0bj+ps2edpToZpjfoIDQtBeU+xjoDzP2G/gCPKZ5f8WsCAFJoJgOCcFdWSTeL9YQMSvTA1h9BkI5jaiXhLpSCL/8mVZY0UpyJ9ZdOkniu1dmJ96BpzQu9w6s28gcOq9j6pwLdR8/36NK5CQKwJSMrb2MhhSglBpt4UjsrdsnNu0B3J0HCozbCc4TjyY2srEgos/4RQljCzNxl4ireQD8FOq+T+W0mTB2g7njhlR+Sy2jsXFvU658U8YTbeaGpdIu7mWkEAq5ZtIjIhFZdtfX7QHckSvB2B6zC3VdAkZk0kAQwaXTk/CzTXK3wjIExCs6ZJpTnE4uY1KV+KzFzA3KTiFPENHJkOPcsfpLhwe4btoSuvUqAR+6TOxlCE6ZfKUsJLgsqGW8OpqAGx2X+sLxrwUog+JUeQRMDBIwyXOcnlPtPnL0/UsT/8LnOxYWFhZG4leAAQAAQHEaYuzHbAAAAABJRU5ErkJggg==" class="sidebar-icon" /> <h2>Feed</h2> </a> </div> <div class='clear'></div> </div></div> <div class='section' id='sidebar-bottom'><div class='widget HTML' data-version='1' id='HTML4'> <div class='widget-content'> <div class="share followgooglewrapper"> <button data-href="https://twitter.com/intent/follow?original_referer=http://blog.chromium.org/&amp;screen_name=ChromiumDev" onclick='sharingPopup(this);' id='twitter-share'><span class="twitter-follow">Follow @ChromiumDev</span></button> <script> function sharingPopup (button) { var url = button.getAttribute("data-href"); window.open( url,'popUpWindow','height=500,width=500,left=10,top=10,resizable=yes,scrollbars=yes,toolbar=yes,menubar=no,location=no,directories=no,status=yes'); } </script> </div> </div> <div class='clear'></div> </div><div class='widget HTML' data-version='1' id='HTML1'> <div class='widget-content'> Give us feedback in our <a href="http://support.google.com/bin/static.py?hl=en&page=portal_groups.cs">Product Forums</a>. </div> <div class='clear'></div> </div></div> </div> </div> <div style='clear:both;'></div> </div> <!-- Footer --> <div class='google-footer-outer loading'> <div id='google-footer'> <a href='//www.google.com/'> <img class='google-logo-dark' height='36' src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALgAAABICAYAAABFoT/eAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAACLVJREFUeNrsXd+L20YQ3vOprdLqiMXFXE2qB7dcwEcTSB7ykIc+9A/PQx/yEMq1TWhNuYIpJriNr7XpmZ5IxFEvmW2EKs3Ornb1w50PxIFP0kiz387OzM6uhGAwGAxGP3Ho+f7x7ri1O7LdccPqZjSNA4dEHsLfaHcEFedJom93x9Xu2OyOFTcBo6sED3fHZHeMEELrkAHJF0B8Rr+gDFsZ5n0luLTQ95AXs4W06D/tjpR50xtM4CjD0y48YGB4rnyZxNOzyA7zBHr+nLnDaJLg0mo/ALekCasg3Z4XbM0ZdTEgnDPeHY8bIne+Qz2GvwyGNwsuyT218KWvIIBMcwGpLiipcolecjMxfBDchNyS1EvxLiOSIecp31q6IJ/C3yrIrMqMm4jhg+AxkdwbIO3aUO4KjqqMjCT3uaazMBhWBJfuxH3CtRfiXf66DhSRZWbmlMnNaILgZxrXJQO/eO3wORZwvwm4JUxuhheCjzVBYAbW1ces45YDSoZrFNOEE835M8FT6oyeEnws8Fz3QnBxFKPHBMem4GU+m6fPGb0leCTwWcM5B36MPgeZI01gudyDdw3hPeXfo8L/rmCUWnuMMdqUL2WqWeRbhf+twfVsO7YagZGNC79fw7OthEVtkiJ4jJzTd3KPwf3CRqhhiTu23AP5sl0/0xiwISQXpNwLIJK87mHF+U8ddzzdmgKlGzlPYjyxGJQouIhNT4k9AqWEFkqfguIvagTWbcq3KW1WE3xS3m8NtA9WS451xofwjKT5kkDoK/b6mDk5FfXr1lWDL4BofZEv2/SRsK/EHGlGdBdu8QNRb8HMCFwt7Yy3DDI/QP7fx5z3VLhdlJEIs4rKNuXXJXdxZPdB7kfCzWqwCO4V1LHgLjInX3tQ1KzCR52Cz+vDj1dydeRuS74rcvs2Pi6fT5H8OaaUQPQPYcWwRSGXyhhscn5dpAnEFMkuEZetbfkTAnlSuH4DxisE+aMGeJAQ3lFl7C4LJE6QWCaCd583ORQ1jYAwjFctal7nOs2ZZvicwvlZx+RHGrcoAwKUVX8uwcc/9TT65INeDOr5shL9LDRB6QTeIy3zwfdh3WOi6axLCEhSjXU7F3h6LqggUtvyJxpynwu8tDkD98fXApOxRj8zoZ9MnGveYVIVZKaGrkBXCY65BCYNN9NkjpKOyQ81Q79JgdxS+Jn3SDTEXRI7SWzaiSTB32oI3nU3BvMfM0urhOVYgwKhuiAfc4tM07wXwm1ZRoQYSl2NUwiu01fEAHVcpixd745FvVz4dzUUc0o8rwoLy8ZSwU6CyFx1RP5II9+1bFPEFs9HWbNLiimDXE+vCm7u1CS47cofzD3aEhVY57mxRo5zlqdt+RFC1JUH2S7bcVXg4liTMakaBZZVxiTICRoivcn1sEUBlk24JmaC6kxUbYmWoqvyfck2xZGGnDFYa9MMzkYQ1ijkCX6qidybrgePiQ0QIQqoi6qRLeqQfIoRsEHaQJLBdHOnLGetSdm/IPcymJuS1PAnbQPH0MOw/39C1vL11DiLOqIsbDI8QcHvGiLnySi2qUXBicaqUSxN5LEB0g7Jt3ENXJLPJ5S1tnaZBoWbpRqrmjRE7qHmpSmNHdQcYrEUadoh+TbBnc9ri7iycI1kzPeNcLDIvbiqXpez9Tmdq6zGREPuzECBoxrPMiI2WtvyNwhJba2wy3JZ6ky5dD1lSvmZS3e4SPA1wcf1VTFHKX+cGwZzdUYcqpvUtvwrD/InDttVlyZeAKlNN5MKbAiurHhKIPlUuJvlTCCiDjSKSCsUmCFWbGLZwCESfK07JB8LvMYWVtw0D00JEHV8Mq2HkqPbE0oHLvvK2g0o8ETg+4cfwTlZDT9JDoWygu4uQQE/ivIvtcnfPkaCqhiupz7jWOAzqL/vjtcdkv9G4MVMt+EaylfuImiPAXEUjRF3pjjaHiPPZ6If9TGGAO4ZY0am6jOCb+DQ+ZCqLkIpOIPrdNfIjnFPY6nyFut7TS/fanrziOBOKMupKw94WaLMtuVnSFt9CPrWWdJE6PeltCX432DEBoh+5Dv8RRhdis8YAv9uyq4/JAwtlEApgBe9Cw9xDD3tdk4Jn0MDfiHwPHcRPxBePCMER3GuIx7kGlv9fkZ4V9lolx2Uv4X7hEj7qJ3LDoAMGbTRMRibu4L2xQ8bgt8AyU+Q+x7nYrvDnH4iuO5LxKsYwPVbkPMvKF9Zky9wXzRfVWizi62r9X5VHf55h+WHhDjGBZ4WRhyTr6z5SlCoLMxLSpBZFsQ9F80uQFbF/6aFWi+Ev51vzzsuX+msyzuQXXjUz8zEBy+zpq9yweXAoxJW4JbYrDS6gYDqGHxPl+TKeiBfxj9/EBIElPYeOA4y8/qRQfknjvSzgRgtq0Pw/M1eQeMdOSb2Bnrhr6Led+1vcp2x7oTFHMnedFW+Ivlty062BUt74oHgSj+vHepnhunn0JJAMtBZgDI/qmGtMujRv8DDpo47zBJ8UtPOuAR/7rKn8t9AJ0tBdmBAmJ/Fu71yxp4I3qh+DhyRqbi5Y1ShVPlSb8X7bRNcfgZFl+WRGYo7uecrWq1r8X5bhmzP5OdlDwsGRm1suSxkg5rYm7ConyGQ3Zl+DgSD8V/kPwrWBMG9YcBtyShBnTLdTiHgttw7qAW7cqh/ZnmPKr/6ignOaKsdyxbsToT5UkPsW00bJjijDXficcX/JsLs6w2BwGtherdckH3w/kNXRPVI0OqJQoHX42/66IMfMj/2huRjxIidgKV/W0JS+bsstDoTeAHcrI8E5zTh/sDkqxL5rZup55/3USlswfcHf4IrQplVDgW9XFlOqnwr6pVPMMEZTuC60EttvdzbLbaZ4PsFVa3nohhO+vW+yn/ZB2fUhpysmQrzBcTSai9EszuZMcEZ1lCFVrp9zGXhm69iLyY4oxFIa178lPe12I/P2DAYDAaDwWAwGAwGg8FgMBgMBoPBYDD2Cf8IMADDRGoQTe+E9AAAAABJRU5ErkJggg==' style='margin-top: -16px;' width='92'/> </a> <ul> <li> <a href='//www.google.com/'> Google </a> </li> <li> <a href='//www.google.com/policies/privacy/'> Privacy </a> </li> <li> <a href='//www.google.com/policies/terms/'> Terms </a> </li> </ul> </div> </div> <script type='text/javascript'> //<![CDATA[ // Social sharing popups. var postEl = document.getElementsByClassName('social-wrapper'); var postCount = postEl.length; for(i=0; i<postCount;i++){ postEl[i].addEventListener("click", function(event){ var postUrl = this.getAttribute("data-href"); window.open( postUrl,'popUpWindow','height=500,width=500,left=10,top=10,resizable=yes,scrollbars=yes,toolbar=yes,menubar=no,location=no,directories=no,status=yes'); });} //]]> </script> <script type='text/javascript'> //<![CDATA[ var BreakpointHandler = function() { this.initted = false; this.isHomePage = false; this.isMobile = false; }; BreakpointHandler.prototype.finalizeSummary = function(summaryHtml, lastNode) { // Use $.trim for IE8 compatibility summaryHtml = $.trim(summaryHtml).replace(/(<br>|\s)+$/,''); if (lastNode.nodeType == 3) { var lastChar = summaryHtml.slice(-1); if (!lastChar.match(/[.”"?]/)) { if (!lastChar.match(/[A-Za-z]/)) { summaryHtml = summaryHtml.slice(0, -1); } summaryHtml += ' ...'; } } else if (lastNode.nodeType == 1 && (lastNode.nodeName == 'I' || lastNode.nodeName == 'A')) { summaryHtml += ' ...'; } return summaryHtml; }; BreakpointHandler.prototype.generateSummaryFromContent = function(content, numWords) { var seenWords = 0; var summaryHtml = ''; for (var i=0; i < content.childNodes.length; i++) { var node = content.childNodes[i]; var nodeText; if (node.nodeType == 1) { if (node.hasAttribute('data-about-pullquote')) { continue; } nodeText = node.textContent; if (nodeText === undefined) { // innerText for IE8 nodeText = node.innerText; } if (node.nodeName == 'DIV' || node.nodeName == 'B') { // Don't end early if we haven't seen enough words. if (seenWords < 10) { continue; } if (i > 0) { summaryHtml = this.finalizeSummary(summaryHtml, content.childNodes[i-1]); } break; } summaryHtml += node.outerHTML; } else if (node.nodeType == 3) { nodeText = node.nodeValue; summaryHtml += nodeText + ' '; } var words = nodeText.match(/\S+\s*/g); if (!words) { continue; } var remain = numWords - seenWords; if (words.length >= remain) { summaryHtml = this.finalizeSummary(summaryHtml, node); break; } seenWords += words.length; } return summaryHtml; }; BreakpointHandler.prototype.detect = function() { var match, pl = /\+/g, search = /([^&=]+)=?([^&]*)/g, decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); }, query = window.location.search.substring(1); var urlParams = {}; while (match = search.exec(query)) urlParams[decode(match[1])] = decode(match[2]); this.isListPage = $('html').hasClass('list-page'); this.isMobile = urlParams['m'] === '1'; this.isHomePage = window.location.pathname == '/'; }; BreakpointHandler.prototype.initContent = function() { var self = this; $('.post').each(function(index) { var body = $(this).children('.post-body')[0]; var content = $(body).children('.post-content')[0]; $(content).addClass('post-original'); var data = $(content).children('script').html(); data = self.rewriteForSSL(data); // If exists, extract specified editor's preview. var match = data.match(/([\s\S]+?)<div data-is-preview.+?>([\s\S]+)<\/div>/m); if (match) { data = match[1]; } // Prevent big images from loading when they aren't needed. // This must be done as a pre-injection step, since image loading can't be // canceled once embedded into the DOM. if (self.isListPage && self.isMobile) { data = data.replace(/<(img|iframe) .+?>/g, ''); } // Insert template to be rendered as nodes. content.innerHTML = data; if (self.isListPage) { var summary = document.createElement('div'); $(summary).addClass('post-content'); $(summary).addClass('post-summary'); body.insertBefore(summary, content); if (match) { // Use provided summary. summary.innerHTML = match[2]; } else { // Generate a summary. // Summary generation relies on DOM, so it must occur after content is // inserted into the page. summary.innerHTML = self.generateSummaryFromContent(content, 30); } // Add read more link to summary. var titleAnchor = $(this).find('.title a')[0]; var link = titleAnchor.cloneNode(true); link.innerHTML = 'Read More'; $(link).addClass('read-more'); summary.appendChild(link); } }); // Firefox does not allow for proper styling of BR. if (navigator.userAgent.indexOf('Firefox') > -1) { $('.post-content br').replaceWith('<span class="space"></span>'); } $('.loading').removeClass('loading'); }; BreakpointHandler.prototype.process = function() { if (!this.initted) { var makeInsecureImageRegex = function(hosts) { var whitelist = hosts.join('|').replace(/\./g,'\\.'); // Normal image tags, plus input images (yes, this is possible!) return new RegExp('(<(img|input)[^>]+?src=("|\'))http:\/\/(' + whitelist +')', 'g'); }; this.sslImageRegex = makeInsecureImageRegex(BreakpointHandler.KNOWN_HTTPS_HOSTS); this.sslImageCurrentDomainRegex = makeInsecureImageRegex([window.location.hostname]); this.detect(); this.initContent(); this.initted = true; } }; BreakpointHandler.KNOWN_HTTPS_HOSTS = [ "www.google.org", "www.google.com", "services.google.com", "blogger.com", "draft.blogger.com", "www.blogger.com", "photos1.blogger.com", "photos2.blogger.com", "photos3.blogger.com", "blogblog.com", "img1.blogblog.com", "img2.blogblog.com", "www.blogblog.com", "www1.blogblog.com", "www2.blogblog.com", "0.bp.blogspot.com", "1.bp.blogspot.com", "2.bp.blogspot.com", "3.bp.blogspot.com", "4.bp.blogspot.com", "lh3.googleusercontent.com", "lh4.googleusercontent.com", "lh5.googleusercontent.com", "lh6.googleusercontent.com", "themes.googleusercontent.com", ]; BreakpointHandler.prototype.rewriteForSSL = function(html) { // Handle HTTP -> HTTPS source replacement of images, movies, and other embedded content. return html.replace(this.sslImageRegex, '$1https://$4') .replace(this.sslImageCurrentDomainRegex, '$1//$4') .replace(/(<(embed|iframe)[^>]+?src=("|'))http:\/\/([^"']*?(youtube|picasaweb\.google)\.com)/g, '$1https://$4') // Slideshow SWF takes a image host, so we need to rewrite that parameter. .replace(/(<embed[^>]+?feed=http(?=[^s]))/g, '$1s'); }; $(document).ready(function() { var handler = new BreakpointHandler(); handler.process(); // Top-level navigation. $(".BlogArchive .tab").click(function(ev) { ev.preventDefault(); $(this).parent().toggleClass('active'); $(this).siblings().slideToggle(300); }); $(".Label .tab").click(function(ev) { ev.preventDefault(); $(this).parent().toggleClass('active'); $(this).siblings().slideToggle(300); }); // Blog archive year expansion. $('.BlogArchive .intervalToggle').click(function(ev) { ev.preventDefault(); if ($(this).parent().hasClass('collapsed')) { $(this).parent().removeClass('collapsed'); $(this).parent().addClass('expanded'); } else { $(this).parent().removeClass('expanded'); $(this).parent().addClass('collapsed'); } }); // Reverse order of months. $('.BlogArchive .intervalToggle + div').each(function(_, items) { var year = $(this); year.children().each(function(_, month) { year.prepend(month); }); }); // Set anchors to open in new tab. $('.post-content img').parent().each(function(_, node) { if (node.nodeName == 'A') { $(this).attr('target', '_blank'); } }); // Process search requests. $('.searchBox input').on("keypress", function(ev) { if (ev.which == 13) { window.location.href = 'https://www.google.com/search?q=site%3A' + window.location.hostname + '%20' + encodeURIComponent ($(this).val()); } }); }); //]]> </script> <script type="text/javascript" src="https://www.blogger.com/static/v1/widgets/1455187647-widgets.js"></script> <script type='text/javascript'> window['__wavt'] = 'AOuZoY58m2Eh4g2e--MEXGmPMFtd1W7EGA:1743390389771';_WidgetManager._Init('//www.blogger.com/rearrange?blogID\x3d2471378914199150966','//blog.chromium.org/2021/04/','2471378914199150966'); _WidgetManager._SetDataContext([{'name': 'blog', 'data': {'blogId': '2471378914199150966', 'title': 'Chromium Blog', 'url': 'https://blog.chromium.org/2021/04/', 'canonicalUrl': 'https://blog.chromium.org/2021/04/', 'homepageUrl': 'https://blog.chromium.org/', 'searchUrl': 'https://blog.chromium.org/search', 'canonicalHomepageUrl': 'https://blog.chromium.org/', 'blogspotFaviconUrl': 'https://blog.chromium.org/favicon.ico', 'bloggerUrl': 'https://www.blogger.com', 'hasCustomDomain': true, 'httpsEnabled': true, 'enabledCommentProfileImages': true, 'gPlusViewType': 'FILTERED_POSTMOD', 'adultContent': false, 'analyticsAccountNumber': 'UA-37592578-1', 'encoding': 'UTF-8', 'locale': 'en', 'localeUnderscoreDelimited': 'en', 'languageDirection': 'ltr', 'isPrivate': false, 'isMobile': false, 'isMobileRequest': false, 'mobileClass': '', 'isPrivateBlog': false, 'isDynamicViewsAvailable': true, 'feedLinks': '\x3clink rel\x3d\x22alternate\x22 type\x3d\x22application/atom+xml\x22 title\x3d\x22Chromium Blog - Atom\x22 href\x3d\x22https://blog.chromium.org/feeds/posts/default\x22 /\x3e\n\x3clink rel\x3d\x22alternate\x22 type\x3d\x22application/rss+xml\x22 title\x3d\x22Chromium Blog - RSS\x22 href\x3d\x22https://blog.chromium.org/feeds/posts/default?alt\x3drss\x22 /\x3e\n\x3clink rel\x3d\x22service.post\x22 type\x3d\x22application/atom+xml\x22 title\x3d\x22Chromium Blog - Atom\x22 href\x3d\x22https://www.blogger.com/feeds/2471378914199150966/posts/default\x22 /\x3e\n', 'meTag': '', 'adsenseHostId': 'ca-host-pub-1556223355139109', 'adsenseHasAds': false, 'adsenseAutoAds': false, 'boqCommentIframeForm': true, 'loginRedirectParam': '', 'view': '', 'dynamicViewsCommentsSrc': '//www.blogblog.com/dynamicviews/4224c15c4e7c9321/js/comments.js', 'dynamicViewsScriptSrc': '//www.blogblog.com/dynamicviews/6c9901d4a96a9be5', 'plusOneApiSrc': 'https://apis.google.com/js/platform.js', 'disableGComments': true, 'interstitialAccepted': false, 'sharing': {'platforms': [{'name': 'Get link', 'key': 'link', 'shareMessage': 'Get link', 'target': ''}, {'name': 'Facebook', 'key': 'facebook', 'shareMessage': 'Share to Facebook', 'target': 'facebook'}, {'name': 'BlogThis!', 'key': 'blogThis', 'shareMessage': 'BlogThis!', 'target': 'blog'}, {'name': 'X', 'key': 'twitter', 'shareMessage': 'Share to X', 'target': 'twitter'}, {'name': 'Pinterest', 'key': 'pinterest', 'shareMessage': 'Share to Pinterest', 'target': 'pinterest'}, {'name': 'Email', 'key': 'email', 'shareMessage': 'Email', 'target': 'email'}], 'disableGooglePlus': true, 'googlePlusShareButtonWidth': 0, 'googlePlusBootstrap': '\x3cscript type\x3d\x22text/javascript\x22\x3ewindow.___gcfg \x3d {\x27lang\x27: \x27en\x27};\x3c/script\x3e'}, 'hasCustomJumpLinkMessage': false, 'jumpLinkMessage': 'Read more', 'pageType': 'archive', 'pageName': 'April 2021', 'pageTitle': 'Chromium Blog: April 2021'}}, {'name': 'features', 'data': {}}, {'name': 'messages', 'data': {'edit': 'Edit', 'linkCopiedToClipboard': 'Link copied to clipboard!', 'ok': 'Ok', 'postLink': 'Post Link'}}, {'name': 'template', 'data': {'name': 'custom', 'localizedName': 'Custom', 'isResponsive': false, 'isAlternateRendering': false, 'isCustom': true}}, {'name': 'view', 'data': {'classic': {'name': 'classic', 'url': '?view\x3dclassic'}, 'flipcard': {'name': 'flipcard', 'url': '?view\x3dflipcard'}, 'magazine': {'name': 'magazine', 'url': '?view\x3dmagazine'}, 'mosaic': {'name': 'mosaic', 'url': '?view\x3dmosaic'}, 'sidebar': {'name': 'sidebar', 'url': '?view\x3dsidebar'}, 'snapshot': {'name': 'snapshot', 'url': '?view\x3dsnapshot'}, 'timeslide': {'name': 'timeslide', 'url': '?view\x3dtimeslide'}, 'isMobile': false, 'title': 'Chromium Blog', 'description': 'News and developments from the open source browser project', 'url': 'https://blog.chromium.org/2021/04/', 'type': 'feed', 'isSingleItem': false, 'isMultipleItems': true, 'isError': false, 'isPage': false, 'isPost': false, 'isHomepage': false, 'isArchive': true, 'isLabelSearch': false, 'archive': {'year': 2021, 'month': 4, 'rangeMessage': 'Showing posts from April, 2021'}}}]); _WidgetManager._RegisterWidget('_HeaderView', new _WidgetInfo('Header1', 'header', document.getElementById('Header1'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_BlogView', new _WidgetInfo('Blog1', 'main', document.getElementById('Blog1'), {'cmtInteractionsEnabled': false}, 'displayModeFull')); _WidgetManager._RegisterWidget('_HTMLView', new _WidgetInfo('HTML8', 'sidebar-top', document.getElementById('HTML8'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_LabelView', new _WidgetInfo('Label1', 'sidebar', document.getElementById('Label1'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_BlogArchiveView', new _WidgetInfo('BlogArchive1', 'sidebar', document.getElementById('BlogArchive1'), {'languageDirection': 'ltr', 'loadingMessage': 'Loading\x26hellip;'}, 'displayModeFull')); _WidgetManager._RegisterWidget('_HTMLView', new _WidgetInfo('HTML6', 'sidebar', document.getElementById('HTML6'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_HTMLView', new _WidgetInfo('HTML4', 'sidebar-bottom', document.getElementById('HTML4'), {}, 'displayModeFull')); _WidgetManager._RegisterWidget('_HTMLView', new _WidgetInfo('HTML1', 'sidebar-bottom', document.getElementById('HTML1'), {}, 'displayModeFull')); </script> </body> </html>

Pages: 1 2 3 4 5 6 7 8 9 10