webextension-polyfill/test/test-proxied-properties.js

178 lines
7.5 KiB
JavaScript
Raw Normal View History

"use strict";
const {deepEqual, equal, ok} = require("chai").assert;
const sinon = require("sinon");
const {setupTestDOMWindow} = require("./setup");
describe("browser-polyfill", () => {
feat: Added devtools.inspectedWindow.eval and devtools.panels.create to wrapped APIs. To be able to wrap the devtools API namespaces, this patch applies the following changes: - fix: Prevent Proxy violation exception on the read-only/non-configurable devtools property by using an empty object with the `chrome` API object as its prototype as the root Proxy target (the Proxy instances returned for the `chrome` API object) and add a related test case. - fix: Added support for a new `singleCallbackArg` metadata property, which prevents devtools.panels.create to resolve an array of parameters (See the related Chromium issue at https://bugs.chromium.org/p/chromium/issues/detail?id=768159) and add the related test cases. - test: Changes to the test case related to proxy getter/setter behavior on non wrapped properties: in the "deletes proxy getter/setter that are not wrapped" test case from the "test-proxied-properties.js" test file, we ensure that when a getter/setter is called for a "non-wrapped" property, the getter/setter is going to affect the original target object, unfortunately this in not true anymore for the root object (the `chrome` API object) because we are using an empty object (which has the `chrome` API object as its prototype and it is not exposed outside of the polyfill sources) as the target of the Proxy instance related to it, this change to the target of the Proxy has been needed to prevent the TypeError exception raised by the Proxy instance when we try to access the "devtools" property (which is non-configurable and read-only on the `chrome` API object).
2017-09-22 21:38:44 +00:00
describe("proxies non-configurable read-only properties", () => {
it("creates a proxy that doesn't raise a Proxy violation exception", () => {
const fakeChrome = {"devtools": {}};
feat: Added devtools.inspectedWindow.eval and devtools.panels.create to wrapped APIs. To be able to wrap the devtools API namespaces, this patch applies the following changes: - fix: Prevent Proxy violation exception on the read-only/non-configurable devtools property by using an empty object with the `chrome` API object as its prototype as the root Proxy target (the Proxy instances returned for the `chrome` API object) and add a related test case. - fix: Added support for a new `singleCallbackArg` metadata property, which prevents devtools.panels.create to resolve an array of parameters (See the related Chromium issue at https://bugs.chromium.org/p/chromium/issues/detail?id=768159) and add the related test cases. - test: Changes to the test case related to proxy getter/setter behavior on non wrapped properties: in the "deletes proxy getter/setter that are not wrapped" test case from the "test-proxied-properties.js" test file, we ensure that when a getter/setter is called for a "non-wrapped" property, the getter/setter is going to affect the original target object, unfortunately this in not true anymore for the root object (the `chrome` API object) because we are using an empty object (which has the `chrome` API object as its prototype and it is not exposed outside of the polyfill sources) as the target of the Proxy instance related to it, this change to the target of the Proxy has been needed to prevent the TypeError exception raised by the Proxy instance when we try to access the "devtools" property (which is non-configurable and read-only on the `chrome` API object).
2017-09-22 21:38:44 +00:00
// Override the property to make it non-configurable (needed to be sure that
// the polyfill is correctly workarounding the Proxy TypeError).
feat: Added devtools.inspectedWindow.eval and devtools.panels.create to wrapped APIs. To be able to wrap the devtools API namespaces, this patch applies the following changes: - fix: Prevent Proxy violation exception on the read-only/non-configurable devtools property by using an empty object with the `chrome` API object as its prototype as the root Proxy target (the Proxy instances returned for the `chrome` API object) and add a related test case. - fix: Added support for a new `singleCallbackArg` metadata property, which prevents devtools.panels.create to resolve an array of parameters (See the related Chromium issue at https://bugs.chromium.org/p/chromium/issues/detail?id=768159) and add the related test cases. - test: Changes to the test case related to proxy getter/setter behavior on non wrapped properties: in the "deletes proxy getter/setter that are not wrapped" test case from the "test-proxied-properties.js" test file, we ensure that when a getter/setter is called for a "non-wrapped" property, the getter/setter is going to affect the original target object, unfortunately this in not true anymore for the root object (the `chrome` API object) because we are using an empty object (which has the `chrome` API object as its prototype and it is not exposed outside of the polyfill sources) as the target of the Proxy instance related to it, this change to the target of the Proxy has been needed to prevent the TypeError exception raised by the Proxy instance when we try to access the "devtools" property (which is non-configurable and read-only on the `chrome` API object).
2017-09-22 21:38:44 +00:00
Object.defineProperty(fakeChrome, "devtools", {
enumarable: true,
configurable: false,
writable: false,
value: {
inspectedWindow: {
eval: sinon.spy(),
},
},
});
return setupTestDOMWindow(fakeChrome).then(window => {
ok(window.browser.devtools.inspectedWindow,
"The non-configurable read-only property can be accessed");
const res = window.browser.devtools.inspectedWindow.eval("test");
ok(fakeChrome.devtools.inspectedWindow.eval.calledOnce,
"The target API method has been called once");
ok(res instanceof window.Promise, "The API method has been wrapped");
});
});
});
describe("proxies non-wrapped functions", () => {
it("should proxy non-wrapped methods", () => {
const fakeChrome = {
runtime: {
nonwrappedmethod: sinon.spy(),
},
};
return setupTestDOMWindow(fakeChrome).then(window => {
ok(window.browser.runtime.nonwrappedmethod);
const fakeCallback = () => {};
window.browser.runtime.nonwrappedmethod(fakeCallback);
const receivedCallback = fakeChrome.runtime.nonwrappedmethod.firstCall.args[0];
equal(fakeCallback, receivedCallback,
"The callback has not been wrapped for the nonwrappedmethod");
});
});
it("should proxy getters and setters", () => {
const fakeChrome = {
runtime: {myprop: "previous-value"},
nowrapns: {
nowrapkey: "previous-value",
nowrapkey2: "previous-value",
},
};
return setupTestDOMWindow(fakeChrome).then(window => {
// Check that the property values on the generated wrapper.
equal(window.browser.runtime.myprop, "previous-value",
"Got the expected result from setting a wrapped property name");
equal(window.browser.nowrapns.nowrapkey, "previous-value",
"Got the expected result from setting a wrapped property name");
// Update the properties on the generated wrapper.
const setResult = window.browser.runtime.myprop = "new-value";
const setResult2 = window.browser.nowrapns.nowrapkey = "new-value";
// Check the results of setting the new value of the wrapped properties.
equal(setResult, "new-value",
"Got the expected result from setting a wrapped property name");
equal(setResult2, "new-value",
"Got the expected result from setting a wrapped property name");
// Verify that the wrapped properties has been updated.
equal(window.browser.runtime.myprop, "new-value",
"Got the expected updated value from the browser property");
equal(window.browser.nowrapns.nowrapkey, "new-value",
"Got the expected updated value from the browser property");
// Verify that the target properties has been updated.
equal(window.chrome.runtime.myprop, "new-value",
"Got the expected updated value on the related chrome property");
equal(window.chrome.nowrapns.nowrapkey, "new-value",
"Got the expected updated value on the related chrome property");
// Set a property multiple times before read.
window.browser.nowrapns.nowrapkey2 = "new-value2";
window.browser.nowrapns.nowrapkey2 = "new-value3";
equal(window.chrome.nowrapns.nowrapkey2, "new-value3",
"Got the expected updated value on the related chrome property");
equal(window.browser.nowrapns.nowrapkey2, "new-value3",
"Got the expected updated value on the wrapped property");
});
});
it("deletes proxy getter/setter that are not wrapped", () => {
feat: Added devtools.inspectedWindow.eval and devtools.panels.create to wrapped APIs. To be able to wrap the devtools API namespaces, this patch applies the following changes: - fix: Prevent Proxy violation exception on the read-only/non-configurable devtools property by using an empty object with the `chrome` API object as its prototype as the root Proxy target (the Proxy instances returned for the `chrome` API object) and add a related test case. - fix: Added support for a new `singleCallbackArg` metadata property, which prevents devtools.panels.create to resolve an array of parameters (See the related Chromium issue at https://bugs.chromium.org/p/chromium/issues/detail?id=768159) and add the related test cases. - test: Changes to the test case related to proxy getter/setter behavior on non wrapped properties: in the "deletes proxy getter/setter that are not wrapped" test case from the "test-proxied-properties.js" test file, we ensure that when a getter/setter is called for a "non-wrapped" property, the getter/setter is going to affect the original target object, unfortunately this in not true anymore for the root object (the `chrome` API object) because we are using an empty object (which has the `chrome` API object as its prototype and it is not exposed outside of the polyfill sources) as the target of the Proxy instance related to it, this change to the target of the Proxy has been needed to prevent the TypeError exception raised by the Proxy instance when we try to access the "devtools" property (which is non-configurable and read-only on the `chrome` API object).
2017-09-22 21:38:44 +00:00
const fakeChrome = {runtime: {}};
return setupTestDOMWindow(fakeChrome).then(window => {
feat: Added devtools.inspectedWindow.eval and devtools.panels.create to wrapped APIs. To be able to wrap the devtools API namespaces, this patch applies the following changes: - fix: Prevent Proxy violation exception on the read-only/non-configurable devtools property by using an empty object with the `chrome` API object as its prototype as the root Proxy target (the Proxy instances returned for the `chrome` API object) and add a related test case. - fix: Added support for a new `singleCallbackArg` metadata property, which prevents devtools.panels.create to resolve an array of parameters (See the related Chromium issue at https://bugs.chromium.org/p/chromium/issues/detail?id=768159) and add the related test cases. - test: Changes to the test case related to proxy getter/setter behavior on non wrapped properties: in the "deletes proxy getter/setter that are not wrapped" test case from the "test-proxied-properties.js" test file, we ensure that when a getter/setter is called for a "non-wrapped" property, the getter/setter is going to affect the original target object, unfortunately this in not true anymore for the root object (the `chrome` API object) because we are using an empty object (which has the `chrome` API object as its prototype and it is not exposed outside of the polyfill sources) as the target of the Proxy instance related to it, this change to the target of the Proxy has been needed to prevent the TypeError exception raised by the Proxy instance when we try to access the "devtools" property (which is non-configurable and read-only on the `chrome` API object).
2017-09-22 21:38:44 +00:00
// Test getter/setter behavior for non wrapped properties on
// an API namespace (because the root target of the Proxy object
// is an empty object which has the chrome API object as its
// prototype and the empty object is not exposed outside of the
// polyfill sources).
window.browser.runtime.newns = {newkey: "test-value"};
feat: Added devtools.inspectedWindow.eval and devtools.panels.create to wrapped APIs. To be able to wrap the devtools API namespaces, this patch applies the following changes: - fix: Prevent Proxy violation exception on the read-only/non-configurable devtools property by using an empty object with the `chrome` API object as its prototype as the root Proxy target (the Proxy instances returned for the `chrome` API object) and add a related test case. - fix: Added support for a new `singleCallbackArg` metadata property, which prevents devtools.panels.create to resolve an array of parameters (See the related Chromium issue at https://bugs.chromium.org/p/chromium/issues/detail?id=768159) and add the related test cases. - test: Changes to the test case related to proxy getter/setter behavior on non wrapped properties: in the "deletes proxy getter/setter that are not wrapped" test case from the "test-proxied-properties.js" test file, we ensure that when a getter/setter is called for a "non-wrapped" property, the getter/setter is going to affect the original target object, unfortunately this in not true anymore for the root object (the `chrome` API object) because we are using an empty object (which has the `chrome` API object as its prototype and it is not exposed outside of the polyfill sources) as the target of the Proxy instance related to it, this change to the target of the Proxy has been needed to prevent the TypeError exception raised by the Proxy instance when we try to access the "devtools" property (which is non-configurable and read-only on the `chrome` API object).
2017-09-22 21:38:44 +00:00
ok("newns" in window.browser.runtime, "The custom namespace is in the wrapper");
ok("newns" in window.chrome.runtime, "The custom namespace is in the target");
feat: Added devtools.inspectedWindow.eval and devtools.panels.create to wrapped APIs. To be able to wrap the devtools API namespaces, this patch applies the following changes: - fix: Prevent Proxy violation exception on the read-only/non-configurable devtools property by using an empty object with the `chrome` API object as its prototype as the root Proxy target (the Proxy instances returned for the `chrome` API object) and add a related test case. - fix: Added support for a new `singleCallbackArg` metadata property, which prevents devtools.panels.create to resolve an array of parameters (See the related Chromium issue at https://bugs.chromium.org/p/chromium/issues/detail?id=768159) and add the related test cases. - test: Changes to the test case related to proxy getter/setter behavior on non wrapped properties: in the "deletes proxy getter/setter that are not wrapped" test case from the "test-proxied-properties.js" test file, we ensure that when a getter/setter is called for a "non-wrapped" property, the getter/setter is going to affect the original target object, unfortunately this in not true anymore for the root object (the `chrome` API object) because we are using an empty object (which has the `chrome` API object as its prototype and it is not exposed outside of the polyfill sources) as the target of the Proxy instance related to it, this change to the target of the Proxy has been needed to prevent the TypeError exception raised by the Proxy instance when we try to access the "devtools" property (which is non-configurable and read-only on the `chrome` API object).
2017-09-22 21:38:44 +00:00
equal(window.browser.runtime.newns.newkey, "test-value",
"Got the expected result from setting a wrapped property name");
feat: Added devtools.inspectedWindow.eval and devtools.panels.create to wrapped APIs. To be able to wrap the devtools API namespaces, this patch applies the following changes: - fix: Prevent Proxy violation exception on the read-only/non-configurable devtools property by using an empty object with the `chrome` API object as its prototype as the root Proxy target (the Proxy instances returned for the `chrome` API object) and add a related test case. - fix: Added support for a new `singleCallbackArg` metadata property, which prevents devtools.panels.create to resolve an array of parameters (See the related Chromium issue at https://bugs.chromium.org/p/chromium/issues/detail?id=768159) and add the related test cases. - test: Changes to the test case related to proxy getter/setter behavior on non wrapped properties: in the "deletes proxy getter/setter that are not wrapped" test case from the "test-proxied-properties.js" test file, we ensure that when a getter/setter is called for a "non-wrapped" property, the getter/setter is going to affect the original target object, unfortunately this in not true anymore for the root object (the `chrome` API object) because we are using an empty object (which has the `chrome` API object as its prototype and it is not exposed outside of the polyfill sources) as the target of the Proxy instance related to it, this change to the target of the Proxy has been needed to prevent the TypeError exception raised by the Proxy instance when we try to access the "devtools" property (which is non-configurable and read-only on the `chrome` API object).
2017-09-22 21:38:44 +00:00
const setRes = window.browser.runtime.newns = {newkey2: "new-value"};
equal(window.browser.runtime.newns.newkey2, "new-value",
"The new non-wrapped getter is cached");
deepEqual(setRes, {newkey2: "new-value"},
"Got the expected result from setting a new wrapped property name");
feat: Added devtools.inspectedWindow.eval and devtools.panels.create to wrapped APIs. To be able to wrap the devtools API namespaces, this patch applies the following changes: - fix: Prevent Proxy violation exception on the read-only/non-configurable devtools property by using an empty object with the `chrome` API object as its prototype as the root Proxy target (the Proxy instances returned for the `chrome` API object) and add a related test case. - fix: Added support for a new `singleCallbackArg` metadata property, which prevents devtools.panels.create to resolve an array of parameters (See the related Chromium issue at https://bugs.chromium.org/p/chromium/issues/detail?id=768159) and add the related test cases. - test: Changes to the test case related to proxy getter/setter behavior on non wrapped properties: in the "deletes proxy getter/setter that are not wrapped" test case from the "test-proxied-properties.js" test file, we ensure that when a getter/setter is called for a "non-wrapped" property, the getter/setter is going to affect the original target object, unfortunately this in not true anymore for the root object (the `chrome` API object) because we are using an empty object (which has the `chrome` API object as its prototype and it is not exposed outside of the polyfill sources) as the target of the Proxy instance related to it, this change to the target of the Proxy has been needed to prevent the TypeError exception raised by the Proxy instance when we try to access the "devtools" property (which is non-configurable and read-only on the `chrome` API object).
2017-09-22 21:38:44 +00:00
deepEqual(window.browser.runtime.newns, window.chrome.runtime.newns,
"chrome.newns and browser.newns are the same");
feat: Added devtools.inspectedWindow.eval and devtools.panels.create to wrapped APIs. To be able to wrap the devtools API namespaces, this patch applies the following changes: - fix: Prevent Proxy violation exception on the read-only/non-configurable devtools property by using an empty object with the `chrome` API object as its prototype as the root Proxy target (the Proxy instances returned for the `chrome` API object) and add a related test case. - fix: Added support for a new `singleCallbackArg` metadata property, which prevents devtools.panels.create to resolve an array of parameters (See the related Chromium issue at https://bugs.chromium.org/p/chromium/issues/detail?id=768159) and add the related test cases. - test: Changes to the test case related to proxy getter/setter behavior on non wrapped properties: in the "deletes proxy getter/setter that are not wrapped" test case from the "test-proxied-properties.js" test file, we ensure that when a getter/setter is called for a "non-wrapped" property, the getter/setter is going to affect the original target object, unfortunately this in not true anymore for the root object (the `chrome` API object) because we are using an empty object (which has the `chrome` API object as its prototype and it is not exposed outside of the polyfill sources) as the target of the Proxy instance related to it, this change to the target of the Proxy has been needed to prevent the TypeError exception raised by the Proxy instance when we try to access the "devtools" property (which is non-configurable and read-only on the `chrome` API object).
2017-09-22 21:38:44 +00:00
delete window.browser.runtime.newns.newkey2;
equal(window.browser.runtime.newns.newkey2, undefined,
"Got the expected result from setting a wrapped property name");
feat: Added devtools.inspectedWindow.eval and devtools.panels.create to wrapped APIs. To be able to wrap the devtools API namespaces, this patch applies the following changes: - fix: Prevent Proxy violation exception on the read-only/non-configurable devtools property by using an empty object with the `chrome` API object as its prototype as the root Proxy target (the Proxy instances returned for the `chrome` API object) and add a related test case. - fix: Added support for a new `singleCallbackArg` metadata property, which prevents devtools.panels.create to resolve an array of parameters (See the related Chromium issue at https://bugs.chromium.org/p/chromium/issues/detail?id=768159) and add the related test cases. - test: Changes to the test case related to proxy getter/setter behavior on non wrapped properties: in the "deletes proxy getter/setter that are not wrapped" test case from the "test-proxied-properties.js" test file, we ensure that when a getter/setter is called for a "non-wrapped" property, the getter/setter is going to affect the original target object, unfortunately this in not true anymore for the root object (the `chrome` API object) because we are using an empty object (which has the `chrome` API object as its prototype and it is not exposed outside of the polyfill sources) as the target of the Proxy instance related to it, this change to the target of the Proxy has been needed to prevent the TypeError exception raised by the Proxy instance when we try to access the "devtools" property (which is non-configurable and read-only on the `chrome` API object).
2017-09-22 21:38:44 +00:00
ok(!("newkey2" in window.browser.runtime.newns),
"The deleted property is not listed anymore");
});
});
});
fix: Lazily initialize API via the original target (#71) Originally, the polyfill created a Proxy with the original API object as the target. This was changed to `Object.create(chrome)` because not doing so would prevent the `browser.devtools` API from working because the devtools API object is assigned as a read-only & non-configurable property (#57). However, that action itself caused a new bug: Whenever an API object is dereferenced via the `browser` namespace, the original API is no longer available in the `chrome` namespace, and trying to access the API through `chrome` returns `undefined` plus the "Previous API instantiation failed" warning (#58). This is because Chrome lazily initializes fields in the `chrome` API, but on the object from which the property is accessed, while the polyfill accessed the property through an object with the prototype set to `chrome` instead of directly via chrome. To fix that, `Object.create(chrome)` was replaced with `Object.assign({}, chrome)`. This fixes both of the previous issues because 1) It is still a new object. 2) All lazily initialized fields are explicitly initialized. This fix created a new issue: In Chrome some APIs cannot be used even though they are visible in the API (e.g. `chrome.clipboard`), so calling `Object.assign({}, chrome)` causes an error to be printed to the console (#70). To solve this, I use `Object.create(chrome)` again as a proxy target, but dereference the API via the original target (`chrome`) to not regress on #58. Besides fixing the bug, this also reduces the performance impact of the API because all API fields are lazily initialized again, instead of upon start-up. This fixes #70.
2018-03-12 18:23:28 +00:00
describe("without side effects", () => {
it("should proxy non-wrapped methods", () => {
let lazyInitCount = 0;
const fakeChrome = {
get runtime() {
// Chrome lazily initializes API objects by replacing the getter with
// the value. The initialization is only allowed to occur once,
// after that `undefined` is returned and a warning is printed.
// https://chromium.googlesource.com/chromium/src/+/4d6b3a067994ce6dcf0ed9a9efd566c083736952/extensions/renderer/module_system.cc#414
//
// The polyfill should invoke the getter only once (on the global chrome object).
++lazyInitCount;
const onMessage = {
addListener(listener) {
equal(this, onMessage, "onMessage.addListener should be called on the original chrome.onMessage object");
},
};
const value = {onMessage};
Object.defineProperty(this, "runtime", {value});
return value;
},
};
return setupTestDOMWindow(fakeChrome).then(window => {
equal(lazyInitCount, 0, "chrome.runtime should not be initialized without explicit API call");
window.browser.runtime.onMessage.addListener(() => {});
equal(lazyInitCount, 1, "chrome.runtime should be initialized upon accessing browser.runtime");
window.browser.runtime.onMessage.addListener(() => {});
equal(lazyInitCount, 1, "chrome.runtime should be re-used upon accessing browser.runtime");
window.chrome.runtime.onMessage.addListener(() => {});
equal(lazyInitCount, 1, "chrome.runtime should be re-used upon accessing chrome.runtime");
});
});
});
});