Hello. I am Paul Kinlan.

I lead the Chrome and the Open Web Developer Relations team at Google. Exploring the intersection of modern web design and future-facing technologies.

2 min read

Quick Picture in Picture Bookmarklet

I created a simple bookmarklet for quickly enabling Picture-in-Picture mode for videos, even on sites that disable it. Drag the "Quick PIP" bookmarklet link to your bookmarks bar. When clicked, it activates PIP for the first actively playing video on the page or in any same-origin iframe. The bookmarklet's code is concise and avoids polluting the global scope. It efficiently finds the first playing video and requests Picture-in-Picture mode.

Stay in the loop.

I'm trialing a newsletter. Join for monthly insights into web dev, Chrome, and the open web.

alternate_email

Get in touch

Open to chat about Chrome or Web development.

Book a consultation
1 min read

Getting a list of Blink Components

This post provides a quick way to retrieve and filter the list of Blink components from a JSON file hosted by Chromium. The provided JavaScript snippets demonstrate how to fetch and process the component list, filtering for entries that begin with "Bli". The next step is figuring out how to programmatically get a list of OWNERS.
1 min read

River Dee in Llangollen before and after heavy rain

I love driving through North Wales, especially Llangollen, a town nestled by the River Dee. In summer, the river's flow is gentle enough to paddle in (with caution!). But after a recent heavy rain (post-Storm Dennis), the river transformed! The power and height of the water were incredible, almost reaching the footpath wall. Check out Llangollen - it's worth a visit, rain or shine!
2 min read

Scroll to text bookmarklet

Just saw that Scroll To Text Fragment is launching in Chrome 81! This feature lets you link to specific text within a page, which is awesome. I created a bookmarklet that grabs your selected text and generates a link using the new :~:text= fragment identifier. Drag the "Find in page" link to your bookmarks bar to try it out. The bookmarklet currently selects whole words, but I'm planning on adding some logic to handle partial word selections better. You can also easily modify the bookmarklet to copy the generated link to the clipboard instead of opening a new window.
2 min read

What do you want from a Web Browser Developer Relations team?

Celebrating my 10th anniversary at Google working on Chrome and leading a Developer Relations team. As we plan for the next few years, I'm reflecting on how we can improve Developer Satisfaction. Inspired by recent feedback on Apple's developer relations, I'm curious to hear your thoughts on what a web browser developer relations team should prioritize. What can we do more of? Less of? How can we best support you and your team? Share your opinions, especially broad strategic ideas.
4 min read

Thinking about Developer Satisfaction and Web Developers

This post discusses the importance of developer satisfaction, particularly for web developers, and how the MDN Web Developer Needs Assessment has influenced Chrome's web platform priorities for 2020. My hypothesis is that improving the web platform will lead to increased developer satisfaction, more content creation, and happier end-users. Based on the MDN survey data, key areas for improvement include browser compatibility, testing, documentation, debugging, framework integration, and privacy & security. Chrome is committed to working with the web ecosystem to address these challenges and increase developer productivity and satisfaction. We'll share more specific plans in the coming weeks and welcome your feedback on these focus areas and how Chrome can better engage with the developer community.
2 min read

Hiring: Chrome Privacy Sandbox Developer Advocate

It's looking like 2020 will be a big year for Privacy across the web and our team (Chrome) is no exception.

Chrome has a rather large number of projects that are coming in the following years that will continue to improve the privacy of all users on the web and we need the help of an awesome Developer Advocate to ensure that the entire cross-browser privacy story is heard, understood and implemented across the web.

The Developer Advocate role will help to accelerate the adoption of security and privacy related primitives from all browsers (think about all the great work browsers like Firefox, Safari, Brave etc are doing) across the web ecosystem and to make sure that our engineering and product teams are prioritising the needs of users and developers. It's not going to be easy, because a lot of these changes impact the way developers build sites today; for example, the Same-Site change that is landing in Chrome imminently requires developers of widgets and anything that is hosted on a 3rd party origin meant to be used in a 1st party context, to declare that the correct SameSite attribute, lest they be automatically set to SameSite=Lax, which will restrict their usage slightly.

There's going to be a lot of work to do, so being able to work with companies, frameworks and libraries in the ecosystem is going to be a key part of this role.

If you're interested, my email is paulkinlan@google.com - or you can apply on the Job posting directly.

1 min read

Correct image orientation for images - Chrome 81

Chrome 81 finally fixes a long-standing bug where images taken in portrait mode on phones were displayed in landscape. Now, images will respect the orientation from the EXIF data by default, unless overridden with the CSS attribute image-orientation: none. Check out the demo!
1 min read

Light fork of SimpleImage for Editor.js

I love Editor.js. It's a nice simple block editor that I use to write these posts. It has a host of good plugins that enable you to extend the capabilities of the editor, such as the SimpleImage tool that allows you to add images to the editor without having to upload them.

It's the SimpleImage that I briefly want to talk about. It's a good tool, but it has two problems, 1) I can only drag images on to the editor, I can't "add" an image; 2) It uses base64 data URL's to host the image, this is a waste of memory and it should be using blob URLs.

I wrote a simple fork that addresses these two pain points. The first is that it uses less memory because it uses blob URLs. The second is that now you can add images in when adding in a new Block to the editor.

In fact, the following images are added using this new way.

Add Image
Choose file
Image added
2 min read

Airhorner with added Web USB

This new year Andre Bandarra left me a little surprise on my desk: A physical airhorner built with Web USB!

Check it out, well actually it will be hard, Andre created a small sketch for an Arduino Uno that connects over USB that is not yet available, however the code on the site is rather neat and not too complex if you are experienced with any form of USB programming.

Andre's code connects to the device and waits for the user to approve, configures the connection, and then continuously reads from the device looking for the string 'ON' (which is a flag that is set when the button is pressed).

const HardwareButton = function(airhorn) {
  this.airhorn = airhorn;
  this.decoder = new TextDecoder();
  this.connected = false;
  const self = this;
  this._loopRead = async function() {
    if (!this.device) {
      console.log('no device');
      return;
    }

    try {
      const result = await this.device.transferIn(2, 64);
      const command = this.decoder.decode(result.data);
      if (command.trim() === 'ON') {
        airhorn.start({loop: true});
      } else {
        airhorn.stop();
      }
      self._loopRead();
    } catch (e) {
      console.log('Error reading data', e);
    }
  };

  this.connect = async function() {
    try {
      const device = await navigator.usb.requestDevice({
        filters: [{'vendorId': 0x2341, 'productId': 0x8057}]
      });
      this.device = device;
      await device.open();
      await device.selectConfiguration(1);
      await device.claimInterface(0);
      await device.selectAlternateInterface(0, 0);
      await device.controlTransferOut({
        'requestType': 'class',
        'recipient': 'interface',
        'request': 0x22,
        'value': 0x01,
        'index': 0x00,
      });
      self._loopRead();
    } catch (e) {
      console.log('Failed to Connect: ', e);
    }
  };

  this.disconnect = async function() {
    if (!this.device) {
      return;
    }

    await this.device.controlTransferOut({
      'requestType': 'class',
      'recipient': 'interface',
      'request': 0x22,
      'value': 0x00,
      'index': 0x00,
    });
    await this.device.close();
    this.device = null;
  };

  this.init = function() {
    const buttonDiv = document.querySelector('#connect');
    const button = buttonDiv.querySelector('button');
    button.addEventListener('click', this.connect.bind(this));
    button.addEventListener('touchend', this.connect.bind(this));
    if (navigator.usb) {
      buttonDiv.classList.add('available');
    }
  };

  this.init();
};

If you are interested in what the Arduino side of things looks like, Andre will release the code soon, but it's directly inspired by the WebUSB examples for Arduino.