KeithHenry / chromeExtensionAsync

Promise wrapper for the Chrome extension API so that it can be used with async/await rather than callbacks
MIT License
229 stars 32 forks source link

Suggestion: identify exhaustive whitelist of promisable functions #28

Open dlh3 opened 3 years ago

dlh3 commented 3 years ago

@KeithHenry I noticed in a comment from last year that you didn't have a way to reliably detect the promiseable functions in the chrome API.

I took a stab at generating something that might help.

I started by collecting all documented permissions:

Array.from(
    document.querySelector("table:not([class])")
        .children[0]
        .children)
            .map(row => row.id)
            .join('", "');

I added those to an extension, then executed this from the background:

apis = {};
Object.keys(chrome)
  .filter(k => typeof chrome[k] === "object")
  .forEach(k => 
      Object.keys(chrome[k])
        .filter(j => typeof chrome[k][j] === "function")
        .forEach(j => {
            try {
                // trollolololol, or pass an unlikely number of arguments
                chrome[k][j](undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
            } catch (err) {
                if (err.message.includes("No matching signature.") && err.message.includes("function callback")) {
                    apis[k] = apis[k] || {};
                    apis[k][j] = `chrome.${k}.${j}`;
                }
            }
        }));
console.log(apis);
JSON.stringify(apis);

And that yielded this:

{
    "alarms": {
        "clear": "chrome.alarms.clear",
        "clearAll": "chrome.alarms.clearAll",
        "get": "chrome.alarms.get",
        "getAll": "chrome.alarms.getAll"
    },
    "bookmarks": {
        "create": "chrome.bookmarks.create",
        "get": "chrome.bookmarks.get",
        "getChildren": "chrome.bookmarks.getChildren",
        "getRecent": "chrome.bookmarks.getRecent",
        "getSubTree": "chrome.bookmarks.getSubTree",
        "getTree": "chrome.bookmarks.getTree",
        "move": "chrome.bookmarks.move",
        "remove": "chrome.bookmarks.remove",
        "removeTree": "chrome.bookmarks.removeTree",
        "search": "chrome.bookmarks.search",
        "update": "chrome.bookmarks.update"
    },
    "browserAction": {
        "disable": "chrome.browserAction.disable",
        "enable": "chrome.browserAction.enable",
        "getBadgeBackgroundColor": "chrome.browserAction.getBadgeBackgroundColor",
        "getBadgeText": "chrome.browserAction.getBadgeText",
        "getPopup": "chrome.browserAction.getPopup",
        "getTitle": "chrome.browserAction.getTitle",
        "setBadgeBackgroundColor": "chrome.browserAction.setBadgeBackgroundColor",
        "setBadgeText": "chrome.browserAction.setBadgeText",
        "setIcon": "chrome.browserAction.setIcon",
        "setPopup": "chrome.browserAction.setPopup",
        "setTitle": "chrome.browserAction.setTitle"
    },
    "browsingData": {
        "remove": "chrome.browsingData.remove",
        "removeAppcache": "chrome.browsingData.removeAppcache",
        "removeCache": "chrome.browsingData.removeCache",
        "removeCacheStorage": "chrome.browsingData.removeCacheStorage",
        "removeCookies": "chrome.browsingData.removeCookies",
        "removeDownloads": "chrome.browsingData.removeDownloads",
        "removeFileSystems": "chrome.browsingData.removeFileSystems",
        "removeFormData": "chrome.browsingData.removeFormData",
        "removeHistory": "chrome.browsingData.removeHistory",
        "removeIndexedDB": "chrome.browsingData.removeIndexedDB",
        "removeLocalStorage": "chrome.browsingData.removeLocalStorage",
        "removePasswords": "chrome.browsingData.removePasswords",
        "removePluginData": "chrome.browsingData.removePluginData",
        "removeServiceWorkers": "chrome.browsingData.removeServiceWorkers",
        "removeWebSQL": "chrome.browsingData.removeWebSQL",
        "settings": "chrome.browsingData.settings"
    },
    "clipboard": {
        "setImageData": "chrome.clipboard.setImageData"
    },
    "contextMenus": {
        "create": "chrome.contextMenus.create",
        "remove": "chrome.contextMenus.remove",
        "removeAll": "chrome.contextMenus.removeAll",
        "update": "chrome.contextMenus.update"
    },
    "cookies": {
        "get": "chrome.cookies.get",
        "getAll": "chrome.cookies.getAll",
        "getAllCookieStores": "chrome.cookies.getAllCookieStores",
        "remove": "chrome.cookies.remove",
        "set": "chrome.cookies.set"
    },
    "debugger": {
        "attach": "chrome.debugger.attach",
        "detach": "chrome.debugger.detach",
        "getTargets": "chrome.debugger.getTargets",
        "sendCommand": "chrome.debugger.sendCommand"
    },
    "declarativeNetRequest": {
        "getDynamicRules": "chrome.declarativeNetRequest.getDynamicRules",
        "getEnabledRulesets": "chrome.declarativeNetRequest.getEnabledRulesets",
        "getMatchedRules": "chrome.declarativeNetRequest.getMatchedRules",
        "updateDynamicRules": "chrome.declarativeNetRequest.updateDynamicRules",
        "updateEnabledRulesets": "chrome.declarativeNetRequest.updateEnabledRulesets"
    },
    "desktopCapture": {
        "chooseDesktopMedia": "chrome.desktopCapture.chooseDesktopMedia"
    },
    "downloads": {
        "acceptDanger": "chrome.downloads.acceptDanger",
        "cancel": "chrome.downloads.cancel",
        "download": "chrome.downloads.download",
        "erase": "chrome.downloads.erase",
        "getFileIcon": "chrome.downloads.getFileIcon",
        "pause": "chrome.downloads.pause",
        "removeFile": "chrome.downloads.removeFile",
        "resume": "chrome.downloads.resume",
        "search": "chrome.downloads.search"
    },
    "extension": {
        "isAllowedFileSchemeAccess": "chrome.extension.isAllowedFileSchemeAccess",
        "isAllowedIncognitoAccess": "chrome.extension.isAllowedIncognitoAccess"
    },
    "fontSettings": {
        "clearDefaultFixedFontSize": "chrome.fontSettings.clearDefaultFixedFontSize",
        "clearDefaultFontSize": "chrome.fontSettings.clearDefaultFontSize",
        "clearFont": "chrome.fontSettings.clearFont",
        "clearMinimumFontSize": "chrome.fontSettings.clearMinimumFontSize",
        "getDefaultFixedFontSize": "chrome.fontSettings.getDefaultFixedFontSize",
        "getDefaultFontSize": "chrome.fontSettings.getDefaultFontSize",
        "getFont": "chrome.fontSettings.getFont",
        "getFontList": "chrome.fontSettings.getFontList",
        "getMinimumFontSize": "chrome.fontSettings.getMinimumFontSize",
        "setDefaultFixedFontSize": "chrome.fontSettings.setDefaultFixedFontSize",
        "setDefaultFontSize": "chrome.fontSettings.setDefaultFontSize",
        "setFont": "chrome.fontSettings.setFont",
        "setMinimumFontSize": "chrome.fontSettings.setMinimumFontSize"
    },
    "gcm": {
        "register": "chrome.gcm.register",
        "send": "chrome.gcm.send",
        "unregister": "chrome.gcm.unregister"
    },
    "history": {
        "addUrl": "chrome.history.addUrl",
        "deleteAll": "chrome.history.deleteAll",
        "deleteRange": "chrome.history.deleteRange",
        "deleteUrl": "chrome.history.deleteUrl",
        "getVisits": "chrome.history.getVisits",
        "search": "chrome.history.search"
    },
    "i18n": {
        "detectLanguage": "chrome.i18n.detectLanguage",
        "getAcceptLanguages": "chrome.i18n.getAcceptLanguages"
    },
    "identity": {
        "getAuthToken": "chrome.identity.getAuthToken",
        "getProfileUserInfo": "chrome.identity.getProfileUserInfo",
        "launchWebAuthFlow": "chrome.identity.launchWebAuthFlow",
        "removeCachedAuthToken": "chrome.identity.removeCachedAuthToken"
    },
    "idle": {
        "queryState": "chrome.idle.queryState"
    },
    "instanceID": {
        "deleteID": "chrome.instanceID.deleteID",
        "deleteToken": "chrome.instanceID.deleteToken",
        "getCreationTime": "chrome.instanceID.getCreationTime",
        "getID": "chrome.instanceID.getID",
        "getToken": "chrome.instanceID.getToken"
    },
    "management": {
        "createAppShortcut": "chrome.management.createAppShortcut",
        "generateAppForLink": "chrome.management.generateAppForLink",
        "get": "chrome.management.get",
        "getAll": "chrome.management.getAll",
        "getPermissionWarningsById": "chrome.management.getPermissionWarningsById",
        "getPermissionWarningsByManifest": "chrome.management.getPermissionWarningsByManifest",
        "getSelf": "chrome.management.getSelf",
        "launchApp": "chrome.management.launchApp",
        "setEnabled": "chrome.management.setEnabled",
        "setLaunchType": "chrome.management.setLaunchType",
        "uninstall": "chrome.management.uninstall",
        "uninstallSelf": "chrome.management.uninstallSelf"
    },
    "notifications": {
        "clear": "chrome.notifications.clear",
        "create": "chrome.notifications.create",
        "getAll": "chrome.notifications.getAll",
        "getPermissionLevel": "chrome.notifications.getPermissionLevel",
        "update": "chrome.notifications.update"
    },
    "pageCapture": {
        "saveAsMHTML": "chrome.pageCapture.saveAsMHTML"
    },
    "permissions": {
        "getAll": "chrome.permissions.getAll"
    },
    "runtime": {
        "getBackgroundPage": "chrome.runtime.getBackgroundPage",
        "getPackageDirectoryEntry": "chrome.runtime.getPackageDirectoryEntry",
        "getPlatformInfo": "chrome.runtime.getPlatformInfo",
        "openOptionsPage": "chrome.runtime.openOptionsPage",
        "requestUpdateCheck": "chrome.runtime.requestUpdateCheck",
        "restartAfterDelay": "chrome.runtime.restartAfterDelay",
        "setUninstallURL": "chrome.runtime.setUninstallURL"
    },
    "sessions": {
        "getDevices": "chrome.sessions.getDevices",
        "getRecentlyClosed": "chrome.sessions.getRecentlyClosed",
        "restore": "chrome.sessions.restore"
    },
    "tabCapture": {
        "capture": "chrome.tabCapture.capture",
        "captureOffscreenTab": "chrome.tabCapture.captureOffscreenTab",
        "getCapturedTabs": "chrome.tabCapture.getCapturedTabs",
        "getMediaStreamId": "chrome.tabCapture.getMediaStreamId"
    },
    "tabs": {
        "captureVisibleTab": "chrome.tabs.captureVisibleTab",
        "create": "chrome.tabs.create",
        "detectLanguage": "chrome.tabs.detectLanguage",
        "discard": "chrome.tabs.discard",
        "duplicate": "chrome.tabs.duplicate",
        "executeScript": "chrome.tabs.executeScript",
        "get": "chrome.tabs.get",
        "getAllInWindow": "chrome.tabs.getAllInWindow",
        "getCurrent": "chrome.tabs.getCurrent",
        "getSelected": "chrome.tabs.getSelected",
        "getZoom": "chrome.tabs.getZoom",
        "getZoomSettings": "chrome.tabs.getZoomSettings",
        "goBack": "chrome.tabs.goBack",
        "goForward": "chrome.tabs.goForward",
        "highlight": "chrome.tabs.highlight",
        "insertCSS": "chrome.tabs.insertCSS",
        "move": "chrome.tabs.move",
        "query": "chrome.tabs.query",
        "reload": "chrome.tabs.reload",
        "remove": "chrome.tabs.remove",
        "setZoom": "chrome.tabs.setZoom",
        "setZoomSettings": "chrome.tabs.setZoomSettings",
        "update": "chrome.tabs.update"
    },
    "topSites": {
        "get": "chrome.topSites.get"
    },
    "tts": {
        "getVoices": "chrome.tts.getVoices",
        "isSpeaking": "chrome.tts.isSpeaking",
        "speak": "chrome.tts.speak"
    },
    "webNavigation": {
        "getAllFrames": "chrome.webNavigation.getAllFrames",
        "getFrame": "chrome.webNavigation.getFrame"
    },
    "webRequest": {
        "handlerBehaviorChanged": "chrome.webRequest.handlerBehaviorChanged"
    },
    "windows": {
        "create": "chrome.windows.create",
        "get": "chrome.windows.get",
        "getAll": "chrome.windows.getAll",
        "getCurrent": "chrome.windows.getCurrent",
        "getLastFocused": "chrome.windows.getLastFocused",
        "remove": "chrome.windows.remove",
        "update": "chrome.windows.update"
    }
}

Hope this helps!

KeithHenry commented 3 years ago

@dlh3 Cheers. That's a really nice way of programmatically finding all the methods with a callback function.

I think there is a route to a programmatically configured API here, but the current list (while not dynamic) is mostly tested and includes some manual removals (such as #26).

I suspect the risk of breaking existing implementation with this change might be higher than the potential benefit of supporting new or missing APIs.

Next steps would be to compare this generated list to the existing one, and test and manually add missing ones.

dlh3 commented 3 years ago

Happy to help. I agree that a programmatic solution could follow from this, though I believe it would be fairly inefficient. My intent was really to provide a way for generating a list for the purpose of comparing against the currently implemented list.