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
60 changes: 47 additions & 13 deletions src/browser-polyfill.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,52 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

if (typeof browser === "undefined" || Object.getPrototypeOf(browser) !== Object.prototype) {
/**
* 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 `browser` namespace object is an actual
* `browser` namespace object and not an element with
* the `[id]` attribute with a value of `browser`.
*/
const HAS_BROWSER_NAMESPACE = (typeof browser !== "undefined" && Object.getPrototypeOf(browser) === Object.prototype);

/**
* Checks if the current browser supports
* the Promise-based WebExtension APIs.
*
* @returns {boolean} True if yes.
*/
const supportsPromises = () => {
if (!HAS_BROWSER_NAMESPACE) {
return false;
}
// If both `browser.runtime.lastError` and `browser.extension.lastError`
// don’t exist, assume promises are supported.
if (browser.runtime && !browser.runtime.hasOwnProperty("lastError")) {
if (!browser.extension || !browser.extension.hasOwnProperty("lastError")) {
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 CHROME_SEND_MESSAGE_CALLBACK_NO_RESPONSE_MESSAGE = "The message port closed before a response was received.";
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 removed from the specs (See https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onMessage)";

Expand Down Expand Up @@ -50,17 +95,6 @@ if (typeof browser === "undefined" || Object.getPrototypeOf(browser) !== Object.
}
}

/**
* 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 @@ -509,7 +543,7 @@ if (typeof browser === "undefined" || Object.getPrototypeOf(browser) !== Object.

// The build process adds a UMD wrapper around this file, which makes the
// `module` variable available.
module.exports = wrapAPIs(chrome); // eslint-disable-line no-undef
module.exports = wrapAPIs(HAS_BROWSER_NAMESPACE ? 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
Original file line number Diff line number Diff line change
@@ -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