Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for browsers which don’t support Promises #114

Closed
wants to merge 10 commits into from
55 changes: 41 additions & 14 deletions src/browser-polyfill.js
Expand Up @@ -6,7 +6,45 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

if (typeof browser === "undefined") {
/**
* Returns true if the given object is an object with a `then` method, and can
* therefore be assumed to behave as a Promise.
*
* @param {*} value The value to test.
* @returns {boolean} True if the value is thenable.
*/
const isThenable = value => {
return value && typeof value === "object" && typeof value.then === "function";
};

/**
* Checks if the current browser supports
* the Promise-based WebExtension APIs.
*
* @returns {boolean} True if yes.
*/
const supportsPromises = () => {
if (typeof browser === "undefined") {
return false;
}
// If `browser.runtime.lastError` doesn’t exist, assume promises are supported.
ExE-Boss marked this conversation as resolved.
Show resolved Hide resolved
if (browser.runtime && typeof browser.runtime.lastError === "undefined") {
return true;
}
if (browser.extension && typeof browser.extension.lastError === "undefined") {
return true;
}
try {
return isThenable(browser.runtime.getPlatformInfo());
} catch (error) { /* silence ESLint */ }
// Content scripts can’t access the platform info
try {
return isThenable(browser.i18n.getAcceptLanguages());
} catch (error) { /* silence ESLint */ }
return false;
};

if (!supportsPromises()) {
const SEND_RESPONSE_DEPRECATION_WARNING = `
Returning a Promise is the preferred way to send a reply from an
onMessage/onMessageExternal listener, as the sendResponse will be
Expand All @@ -19,7 +57,7 @@ if (typeof browser === "undefined") {
// contents of a function until the first time it's called, and since it will
// never actually need to be called, this allows the polyfill to be included
// in Firefox nearly for free.
const wrapAPIs = () => {
const wrapAPIs = chrome => {
// NOTE: apiMetadata is associated to the content of the api-metadata.json file
// at build time by replacing the following "include" with the content of the
// JSON file.
Expand Down Expand Up @@ -54,17 +92,6 @@ if (typeof browser === "undefined") {
}
}

/**
* Returns true if the given object is an object with a `then` method, and can
* therefore be assumed to behave as a Promise.
*
* @param {*} value The value to test.
* @returns {boolean} True if the value is thenable.
*/
const isThenable = value => {
return value && typeof value === "object" && typeof value.then === "function";
};

/**
* Creates and returns a function which, when called, will resolve or reject
* the given promise based on how it is called:
Expand Down Expand Up @@ -488,7 +515,7 @@ if (typeof browser === "undefined") {

// The build process adds a UMD wrapper around this file, which makes the
// `module` variable available.
module.exports = wrapAPIs(); // eslint-disable-line no-undef
module.exports = wrapAPIs(typeof browser !== "undefined" ? browser : chrome); // eslint-disable-line no-undef
} else {
module.exports = browser; // eslint-disable-line no-undef
}
36 changes: 34 additions & 2 deletions test/test-browser-global.js
@@ -1,6 +1,6 @@
"use strict";

const {deepEqual, equal, ok} = require("chai").assert;
const {deepEqual, equal, notEqual, ok} = require("chai").assert;

const {setupTestDOMWindow} = require("./setup");

Expand All @@ -12,7 +12,7 @@ describe("browser-polyfill", () => {
});
});

it("does not override the global browser namespace if it already exists", () => {
it("does not override the global browser namespace if it already exists and supports promises", () => {
const fakeChrome = {
runtime: {lastError: null},
};
Expand All @@ -26,6 +26,38 @@ describe("browser-polyfill", () => {
});
});

it("wraps the global browser namespace if it doesn’t support promises", () => {
const fakePlatformInfo = {
os: "test",
arch: "test",
};

const fakeBrowser = {
runtime: {
lastError: null,
getPlatformInfo: (cb) => {
if (typeof cb !== "function") {
throw new Error(cb + " is not a callback");
}
cb(fakePlatformInfo);
},
},
};

return setupTestDOMWindow(undefined, fakeBrowser).then(window => {
console.log(window.browser.runtime.getPlatformInfo());
notEqual(window.browser, fakeBrowser,
"The existing browser has been wrapped");
return Promise.all([
window,
window.browser.runtime.getPlatformInfo(),
]);
}).then(([window, platformInfo]) => {
deepEqual(platformInfo, fakePlatformInfo,
"The wrapped browser returns the expected value");
});
});

describe("browser wrapper", () => {
it("supports custom properties defined using Object.defineProperty", () => {
const fakeChrome = {};
Expand Down