Skip to content

Commit

Permalink
Add support for browsers which don’t support Promises
Browse files Browse the repository at this point in the history
  • Loading branch information
ExE-Boss committed May 14, 2018
1 parent 6f9cfdf commit dc836be
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 17 deletions.
67 changes: 52 additions & 15 deletions src/browser-polyfill.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,61 @@
* 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 defined and has an entry with
* the name `key` and the value of object[key] isn’t undefined.
*
* @param {*} object The object to test.
* @param {*} key The entry to test for.
* @returns {boolean} True if the value for obj[key] is defined.
*/
const isDefined = (object, key) => {
if (typeof object !== "object" || object === null) {
return false;
}
return key in object && typeof object[key] !== "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";
};

if (!isDefined(window, "browser") || !(() => {
// If `browser.runtime.lastError` doesn’t exist, assume promies are supported.
let supportsPromises = !(isDefined(window.browser, "runtime")
? isDefined(window.browser.runtime, "lastError")
: isDefined(window.browser, "extension")
&& isDefined(window.browser.extension, "lastError"));
if (supportsPromises) {
return true;
}
if (!supportsPromises) {
try {
supportsPromises |= isThenable(window.browser.runtime.getPlatformInfo());
} catch (e) {
try {
// Microsoft Edge doesn’t support `browser.runtime.getPlatformInfo()`
supportsPromises |= isThenable(window.browser.windows.get(browser.windows.WINDOW_ID_CURRENT));
} catch (e2) {
// Do nothing.
}
}
}
return supportsPromises;
})()) {
// Wrapping the bulk of this polyfill in a one-time-use function is a minor
// optimization for Firefox. Since Spidermonkey does not fully parse the
// 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 @@ -47,17 +95,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 @@ -387,7 +424,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(window.browser || window.chrome); // eslint-disable-line no-undef
} else {
module.exports = browser; // eslint-disable-line no-undef
module.exports = window.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

0 comments on commit dc836be

Please sign in to comment.