Index: lib/content/snippets.js |
=================================================================== |
--- a/lib/content/snippets.js |
+++ b/lib/content/snippets.js |
@@ -10,18 +10,124 @@ |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
* GNU General Public License for more details. |
* |
* You should have received a copy of the GNU General Public License |
* along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
*/ |
+/* eslint-env webextensions */ |
/* eslint no-console: "off" */ |
"use strict"; |
+/** |
+ * Injects JavaScript code into the document using a temporary |
+ * <code>script</code> element. |
+ * |
+ * @param {string} code The code to inject. |
+ * @param {Array.<function|string>} [dependencies] A list of dependencies |
+ * to inject along with the code. A dependency may be either a function or a |
+ * string containing some executable code. |
+ */ |
+function injectCode(code, dependencies = []) |
+{ |
+ for (let dependency of dependencies) |
+ code += dependency; |
+ |
+ let script = document.createElement("script"); |
+ |
+ script.type = "application/javascript"; |
+ script.async = false; |
+ |
+ // Firefox 58 only bypasses site CSPs when assigning to 'src', |
+ // while Chrome 67 only bypasses site CSPs when using 'textContent'. |
+ if (browser.runtime.getURL("").startsWith("chrome-extension://")) |
+ { |
+ script.textContent = code; |
+ document.documentElement.appendChild(script); |
+ } |
+ else |
+ { |
+ let url = URL.createObjectURL(new Blob([code])); |
+ script.src = url; |
+ document.documentElement.appendChild(script); |
+ URL.revokeObjectURL(url); |
+ } |
+ |
+ document.documentElement.removeChild(script); |
+} |
+ |
+/** |
+ * Safely converts a function and an optional list of arguments into a string |
+ * of code containing a function call. The function is converted to its string |
+ * representation using the <code>Function.prototype.toString</code> method. |
+ * Each argument is stringified using <code>JSON.stringify</code>. The |
+ * generated code begins with the <code>"use strict"</code> directive. |
+ * |
+ * @param {function} func The function to convert. |
+ * @param {...*} [params] The arguments to convert. |
+ * |
+ * @returns {string} The generated code containing the function call. |
+ */ |
+function stringifyFunctionCall(func, ...params) |
+{ |
+ // Call the original Function.prototype.toString to avoid any arbitrary code |
+ // execution. |
+ func = Function.prototype.toString.call(func); |
+ |
+ // Call JSON.stringify on the arguments to avoid any arbitrary code |
+ // execution. |
+ params = params.map(JSON.stringify); |
+ |
+ return `"use strict";(${func})(${params.join(",")});`; |
+} |
+ |
+/** |
+ * Wraps a function and its dependencies into an injector. The injector, when |
+ * called with zero or more arguments, generates code that calls the function, |
+ * with the given arguments, if any, and injects the code, along with any |
+ * dependencies, into the document using a temporary <code>script</code> |
+ * element. |
+ * |
+ * @param {function} injectable The function to wrap into an injector. |
+ * @param {...(function|string)} [dependencies] Any dependencies of the |
+ * function. A dependency may be either a function or a string containing |
+ * some executable code. |
+ * |
+ * @returns {function} The generated injector. |
+ */ |
+function makeInjector(injectable, ...dependencies) |
+{ |
+ return (...args) => injectCode(stringifyFunctionCall(injectable, ...args), |
+ dependencies); |
+} |
+ |
+/** |
+ * Logs its arguments to the console. This may be used for testing and |
+ * debugging. |
+ * |
+ * @param {...*} [args] The arguments to log. |
+ */ |
function log(...args) |
{ |
console.log(...args); |
} |
exports.log = log; |
+ |
+/** |
+ * Similar to {@link log}, but does the logging in the context of the document |
+ * rather than the content script. This may be used for testing and debugging, |
+ * especially to verify that the injection of snippets into the document is |
+ * working without any errors. |
+ * |
+ * @param {...*} [args] The arguments to log. |
+ */ |
+function trace(...args) |
+{ |
+ // We could simply use console.log here, but the goal is to demonstrate the |
+ // usage of snippet dependencies. |
+ log(...args); |
+} |
+ |
+exports.trace = makeInjector(trace, log); |