Hot reset Chrome extension

  • Tutorial

The other day there was a desire to write a simple extension for Google Chrome. Faced such a problem that after changes to the extension code, the browser does not restart it automatically. This makes development very difficult because after each Cmd-S in the editor, you have to click "Reload" in the list of extensions, and then refresh the page to restart the content scripts.


After a short research, it turned out that Chrome provides all the necessary APIs in order to implement similar functionality for its extension on its own.


A ready-made embedded solution lies on github.com/xpl/crx-hotreload , and in this article I will describe how it is implemented.


We use the File and Directory Entries API to recursively obtain a list of files in a folder:


const filesInDirectory = dir => new Promise (resolve =>
    dir.createReader ().readEntries (entries =>
        Promise.all (entries.filter (e => e.name[0] !== '.').map (e =>
            e.isDirectory
                ? filesInDirectory (e)
                : new Promise (resolve => e.file (resolve))
        ))
        .then (files => [].concat (...files))
        .then (resolve)
    )
)

We generate a "combined" timestamp from all timestamps of the received files and their names:


const timestampForFilesInDirectory = dir =>
        filesInDirectory (dir).then (files =>
            files.map (f => f.name + f.lastModifiedDate).join ())

Thus, we can detect not only changes in files, but also their removal / addition / renaming.


Watchdog checking changes every 1000ms:


const watchChanges = (dir, lastTimestamp) => {
    timestampForFilesInDirectory (dir).then (timestamp => {
        if (!lastTimestamp || (lastTimestamp === timestamp)) {
            setTimeout (() => watchChanges (dir, timestamp), 1000) // retry after 1s
        } else {
            reload ()
        }
    })
}

Rebooting the extension and active tab:


const reload = () => {
    chrome.tabs.query ({ active: true, currentWindow: true }, tabs => {
        if (tabs[0]) { chrome.tabs.reload (tabs[0].id) }
        chrome.runtime.reload ()
    })
}

Rebooting the tab is called before runtime.reload, otherwise it will not work - the call runtime.reloadstops the script. But since reloading the tab works asynchronously, in the end everything reloads in the correct order - although it looks illogical in the code.


Well, and the final touch - we’ll launch a dogdog set on a folder with an extension code. But we do this only if the extension is loaded in developer mode, through the "Load unpacked extension" :


chrome.management.getSelf (self => {
    if (self.installType === 'development') {
        chrome.runtime.getPackageDirectoryEntry (dir => watchChanges (dir))
    }
})

Thus, we eliminate the need for the developer to bother manually cutting this watchdog from the production build.


Finally


That, in general, is all. It is completely incomprehensible, however, how to test such things. It is unlikely that any Selenium will help here, or still? Feedback is welcome.


Also popular now: