From d7a22672cb6601ee5b618be1eac0b56f2b4e904f Mon Sep 17 00:00:00 2001 From: Sam Sudar Date: Sun, 14 Mar 2021 13:50:19 -0700 Subject: [PATCH] Expose global commands at chrome://extensions/shortcuts Also adds a tab jump list, but might split that out if this PR has interest. --- background_scripts/bg_utils.js | 113 +++++++++ background_scripts/commands.js | 6 + background_scripts/main.js | 66 +++++- manifest.json | 26 +++ package-lock.json | 410 ++++++++++++++++++++++++++++++++- 5 files changed, 619 insertions(+), 2 deletions(-) diff --git a/background_scripts/bg_utils.js b/background_scripts/bg_utils.js index d53040e3e..e36ea02ae 100644 --- a/background_scripts/bg_utils.js +++ b/background_scripts/bg_utils.js @@ -10,6 +10,7 @@ class TabRecency { this.cache = {}; this.lastVisited = null; this.lastVisitedTime = null; + this.jumpList = null; chrome.tabs.onActivated.addListener(activeInfo => this.register(activeInfo.tabId)); chrome.tabs.onRemoved.addListener(tabId => this.deregister(tabId)); @@ -31,6 +32,31 @@ class TabRecency { } } + getJumpBackTabId({count}) { + let backTabId = -1; + if (!this.jumpList) { + // getTabsByRecency might not include the current tab, eg if it was just + // opened. Tabs aren't added until they have been seen for some time and + // then an event is fired (like navigating back to the window). Add the + // current tab if it hasn't been added. + const tabs = this.getTabsByRecency().reverse(); + if (tabs.length > 0 && tabs[tabs.length - 1] !== this.current) { + tabs.push(this.current); + } + this.jumpList = new TabJumpList(tabs); + } + backTabId = this.jumpList.getJumpBackTabId({count}); + return backTabId === -1 ? this.current : backTabId; + } + + getJumpForwardTabId({count}) { + let forwardTabId = -1; + if (this.jumpList) { + forwardTabId = this.jumpList.getJumpForwardTabId({count}); + } + return forwardTabId === -1 ? this.current : forwardTabId; + } + register(tabId) { const currentTime = new Date(); // Register tabId if it has been visited for at least @timeDelta ms. Tabs which are visited only for a @@ -39,6 +65,10 @@ class TabRecency { this.cache[this.lastVisited] = ++this.timestamp; } + if (this.jumpList && !this.jumpList.isCoherent(tabId)) { + this.jumpList = null; + } + this.current = (this.lastVisited = tabId); this.lastVisitedTime = currentTime; } @@ -49,6 +79,11 @@ class TabRecency { this.lastVisited = (this.lastVisitedTime = null); } delete this.cache[tabId]; + + if (this.jumpList) { + const jumpListInvalidated = this.jumpList.deregister(tabId); + if (jumpListInvalidated) this.jumpList = null; + } } // Recently-visited tabs get a higher score (except the current tab, which gets a low score). @@ -69,6 +104,84 @@ class TabRecency { } } +// TabJumpList maintains a list of visited tabs. When no jumps have occurred, +// the list is all open tabs--the current (i.e. most recently visited) tab is +// the last element. The tab visited the longest in the past is the 0th tab. +// Jumping backwards moves through the open tabs. The index is maintained, +// allowing jumping forward to move again back to newer tabs. A manual +// navigation through a mechanism other than a jump resets the jump list. +class TabJumpList { + + constructor(tabs) { + this.tabs = tabs; + this.activeIdx = tabs.length - 1; + // Tabs can be deleted after a TabJumpList has flattened the tabs into an + // array. Rather than O(N) look through the tabs, we'll just maintain + // deleted IDs and skip them. + this.deletedTabs = new Set(); + } + + isCoherent(currentTabId) { + return this.tabs[this.activeIdx] === currentTabId; + } + + // Returns true if deregistering this tab invalidates the jump list. + deregister(tabId) { + if (this.tabs[this.activeIdx] == tabId) return true; + + this.deletedTabs.add(tabId); + return false; + } + + getJumpBackTabId({count = 1}) { + let candidateIdx = -1; + let need = count; + for (let i = this.activeIdx - 1; i >= 0; i--) { + let candidateId = this.tabs[i]; + if (this.deletedTabs.has(candidateId)) { + continue; + } + candidateIdx = i; + need--; + if (need <= 0) { + break; + } + } + + if (candidateIdx === -1) { + // We're at the oldest tab. + return -1; + } + + this.activeIdx = candidateIdx; + return this.tabs[this.activeIdx]; + } + + getJumpForwardTabId({count = 1}) { + let candidateIdx = -1; + let need = count; + for (let i = this.activeIdx + 1; i < this.tabs.length; i++) { + let candidateId = this.tabs[i]; + if (this.deletedTabs.has(candidateId)) { + continue; + } + candidateIdx = i; + need--; + if (need <= 0) { + break; + } + } + + if (candidateIdx === -1) { + // We're at the newest tab. + return -1; + } + + this.activeIdx = candidateIdx; + return this.tabs[this.activeIdx]; + } +} + var BgUtils = { tabRecency: new TabRecency(), diff --git a/background_scripts/commands.js b/background_scripts/commands.js index a6715f5f8..2b23cf23e 100644 --- a/background_scripts/commands.js +++ b/background_scripts/commands.js @@ -235,6 +235,8 @@ const Commands = { "previousTab", "nextTab", "visitPreviousTab", + "jumpBackTabList", + "jumpForwardTabList", "firstTab", "lastTab", "duplicateTab", @@ -340,6 +342,8 @@ const defaultKeyMappings = { "gt": "nextTab", "gT": "previousTab", "^": "visitPreviousTab", + "": "jumpBackTabList", + "": "jumpForwardTabList", "<<": "moveTabLeft", ">>": "moveTabRight", "g0": "firstTab", @@ -423,6 +427,8 @@ const commandDescriptions = { nextTab: ["Go one tab right", { background: true }], previousTab: ["Go one tab left", { background: true }], visitPreviousTab: ["Go to previously-visited tab", { background: true }], + jumpBackTabList: ["Jump back in visited tab list", { background: true }], + jumpForwardTabList: ["Jump forward in visited tab list", { background: true }], firstTab: ["Go to the first tab", { background: true }], lastTab: ["Go to the last tab", { background: true }], diff --git a/background_scripts/main.js b/background_scripts/main.js index 441983ade..a33d95567 100644 --- a/background_scripts/main.js +++ b/background_scripts/main.js @@ -332,6 +332,16 @@ const BackgroundCommands = { return selectSpecificTab({id: tabIds[(count-1) % tabIds.length]}); }, + jumpBackTabList({count}) { + const tabId = BgUtils.tabRecency.getJumpBackTabId({count}); + return selectSpecificTab({id: tabId}); + }, + + jumpForwardTabList({count}) { + const tabId = BgUtils.tabRecency.getJumpForwardTabId({count}); + return selectSpecificTab({id: tabId}); + }, + reload({count, tabId, registryEntry, tab: {windowId}}){ const bypassCache = registryEntry.options.hard != null ? registryEntry.options.hard : false; return chrome.tabs.query({windowId}, function(tabs) { @@ -350,6 +360,58 @@ const BackgroundCommands = { } }; +chrome.commands.onCommand.addListener(function(command) { + + const sendCommandToCurrentTab = function(requestName) { + chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { + if (tabs[0]) { + const tabId = tabs[0].id; + chrome.tabs.sendMessage(tabId, { + name: 'runInTopFrame', + registryEntry: { + command: requestName, + optionList: [], + }, + }); + } + }); + }; + + switch (command) { + case "jump-back-tab": + BackgroundCommands.jumpBackTabList({count: 1}); + break; + case "jump-forward-tab": + BackgroundCommands.jumpForwardTabList({count: 1}); + break; + case "open-vomnibox": + sendCommandToCurrentTab("Vomnibar.activate"); + break; + case "open-vomnibox-new-tab": + sendCommandToCurrentTab("Vomnibar.activateInNewTab"); + break; + case "open-vomnibox-tab": + sendCommandToCurrentTab("Vomnibar.activateTabSelection"); + break; + case "open-vomnibox-bookmark": + sendCommandToCurrentTab("Vomnibar.activateBookmarks"); + break; + case "open-vomnibox-bookmark-new-tab": + sendCommandToCurrentTab("Vomnibar.activateBookmarksInNewTab"); + break; + case "switch-to-previous-tab": + chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { + if (tabs[0]) { + const tabId = tabs[0].id; + BackgroundCommands.visitPreviousTab({count: 1, tab: { id: tabId} }); + } + }); + break; + default: + console.error('unrecognized command: ', command); + } +}); + var forCountTabs = (count, currentTab, callback) => chrome.tabs.query({currentWindow: true}, function(tabs) { const activeTabIndex = currentTab.index; const startTabIndex = Math.max(0, Math.min(activeTabIndex, tabs.length - count)); @@ -613,7 +675,9 @@ var portHandlers = { }; var sendRequestHandlers = { - runBackgroundCommand(request) { return BackgroundCommands[request.registryEntry.command](request); }, + runBackgroundCommand(request) { + return BackgroundCommands[request.registryEntry.command](request); + }, // getCurrentTabUrl is used by the content scripts to get their full URL, because window.location cannot help // with Chrome-specific URLs like "view-source:http:..". getCurrentTabUrl({tab}) { return tab.url; }, diff --git a/manifest.json b/manifest.json index d3f7dd230..5bc560bf2 100644 --- a/manifest.json +++ b/manifest.json @@ -7,6 +7,32 @@ "48": "icons/icon48.png", "128": "icons/icon128.png" }, "minimum_chrome_version": "69.0", + "commands": { + "jump-back-tab": { + "description": "Jump back in the tab stack." + }, + "jump-forward-tab": { + "description": "Jump forward in the tab stack." + }, + "open-vomnibox": { + "description": "Open the vomnibox for the same tab." + }, + "open-vomnibox-new-tab": { + "description": "Open the vomnibox for a new tab." + }, + "open-vomnibox-tab": { + "description": "Open the vomnibox with tabs." + }, + "open-vomnibox-bookmark": { + "description": "Open the vomnibox with history." + }, + "open-vomnibox-bookmark-new-tab": { + "description": "Open the vomnibox with history." + }, + "switch-to-previous-tab": { + "description": "Switch to the previously used tab." + } + }, "background": { "scripts": [ "lib/utils.js", diff --git a/package-lock.json b/package-lock.json index 26dd21ba2..9e5d5f8a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,416 @@ { "name": "vimium", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "path": "^0.11.14", + "puppeteer": "^3.1.0" + } + }, + "node_modules/@types/node": { + "version": "14.0.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.4.tgz", + "integrity": "sha512-k3NqigXWRzQZVBDS5D1U70A5E8Qk4Kh+Ha/x4M8Bt9pF0X05eggfnC9+63Usc9Q928hRUIpIhTQaXsZwZBl4Ew==", + "optional": true + }, + "node_modules/@types/yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "node_modules/base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "node_modules/bl": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz", + "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "engines": { + "node": "*" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.0.tgz", + "integrity": "sha512-i42GQ498yibjdvIhivUsRslx608whtGoFIhF26Z7O4MYncBxp8CwalOs1lnHy21A9sIohWO2+uiE4SRtC9JXDg==", + "dependencies": { + "@types/yauzl": "^2.9.1", + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.12.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "node_modules/get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/https-proxy-agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "dependencies": { + "agent-base": "5", + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "node_modules/mime": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.5.tgz", + "integrity": "sha512-3hQhEUF027BuxZjQA3s7rIv/7VCQPa27hN9u9g87sEkWaKwQPuXOkVKtOeiyUrnWqTDiOs8Ed2rwg733mB0R5w==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/path/-/path-0.11.14.tgz", + "integrity": "sha1-y8dWk1XLPIOv60rOQ+z/lSMeWn0=" + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/puppeteer": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-3.1.0.tgz", + "integrity": "sha512-jLa9sqdVx0tPnr2FcwAq+8DSjGhSM4YpkwOf3JE22Ycyqm71SW7B5uGfTyMGFoLCmbCozbLZclCjasPb0flTRw==", + "hasInstallScript": true, + "dependencies": { + "debug": "^4.1.0", + "extract-zip": "^2.0.0", + "https-proxy-agent": "^4.0.0", + "mime": "^2.0.3", + "progress": "^2.0.1", + "proxy-from-env": "^1.0.0", + "rimraf": "^3.0.2", + "tar-fs": "^2.0.0", + "unbzip2-stream": "^1.3.3", + "ws": "^7.2.3" + }, + "engines": { + "node": ">=10.18.1" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/tar-fs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.0.tgz", + "integrity": "sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.0.0" + } + }, + "node_modules/tar-stream": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz", + "integrity": "sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==", + "dependencies": { + "bl": "^4.0.1", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "node_modules/unbzip2-stream": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.2.tgz", + "integrity": "sha512-pZMVAofMrrHX6Ik39hCk470kulCbmZ2SWfQLPmTWqfJV/oUm0gn1CblvHdUu4+54Je6Jq34x8kY6XjTy6dMkOg==", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/ws": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz", + "integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==", + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + } + }, "dependencies": { "@types/node": { "version": "14.0.4",