summaryrefslogtreecommitdiffstats
path: root/browser/extensions/webcompat/shims/facebook-sdk.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/extensions/webcompat/shims/facebook-sdk.js')
-rw-r--r--browser/extensions/webcompat/shims/facebook-sdk.js198
1 files changed, 198 insertions, 0 deletions
diff --git a/browser/extensions/webcompat/shims/facebook-sdk.js b/browser/extensions/webcompat/shims/facebook-sdk.js
new file mode 100644
index 0000000000..95928be857
--- /dev/null
+++ b/browser/extensions/webcompat/shims/facebook-sdk.js
@@ -0,0 +1,198 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Bug 1226498 - Shim Facebook SDK
+ *
+ * The Facebook SDK is commonly used by sites to allow users to authenticate
+ * for logins, but it is blocked by strict tracking protection. It is possible
+ * to shim the SDK and allow users to still opt into logging in via Facebook.
+ * It is also possible to replace any Facebook widgets or comments with
+ * placeholders that the user may click to opt into loading the content.
+ */
+
+if (!window.FB) {
+ const originalUrl = document.currentScript.src;
+ const pendingParses = [];
+
+ function getGUID() {
+ return (
+ Math.random()
+ .toString(36)
+ .substring(2) + Date.now().toString(36)
+ );
+ }
+
+ const shimId = "FacebookSDK";
+
+ const sendMessageToAddon = (function() {
+ const pendingMessages = new Map();
+ const channel = new MessageChannel();
+ channel.port1.onerror = console.error;
+ channel.port1.onmessage = event => {
+ const { messageId, response } = event.data;
+ const resolve = pendingMessages.get(messageId);
+ if (resolve) {
+ pendingMessages.delete(messageId);
+ resolve(response);
+ }
+ };
+ function reconnect() {
+ const detail = {
+ pendingMessages: [...pendingMessages.values()],
+ port: channel.port2,
+ shimId,
+ };
+ window.dispatchEvent(new CustomEvent("ShimConnects", { detail }));
+ }
+ window.addEventListener("ShimHelperReady", reconnect);
+ reconnect();
+ return function(message) {
+ const messageId = getGUID();
+ return new Promise(resolve => {
+ const payload = {
+ message,
+ messageId,
+ shimId,
+ };
+ pendingMessages.set(messageId, resolve);
+ channel.port1.postMessage(payload);
+ });
+ };
+ })();
+
+ let ready = false;
+ let initInfo;
+ const needPopup =
+ !/app_runner/.test(window.name) && !/iframe_canvas/.test(window.name);
+ const popupName = getGUID();
+
+ if (needPopup) {
+ const oldWindowOpen = window.open;
+ window.open = function(href, name, params) {
+ try {
+ const url = new URL(href);
+ if (
+ url.protocol === "https:" &&
+ (url.hostname === "m.facebook.com" ||
+ url.hostname === "www.facebook.com") &&
+ url.pathname.endsWith("/oauth")
+ ) {
+ name = popupName;
+ }
+ } catch (_) {}
+ return oldWindowOpen.call(window, href, name, params);
+ };
+ }
+
+ async function allowFacebookSDK(callback) {
+ await sendMessageToAddon("optIn");
+
+ window.FB = undefined;
+ const oldInit = window.fbAsyncInit;
+ window.fbAsyncInit = () => {
+ ready = true;
+ if (typeof initInfo !== "undefined") {
+ window.FB.init(initInfo);
+ } else if (oldInit) {
+ oldInit();
+ }
+ if (callback) {
+ callback();
+ }
+ };
+
+ const s = document.createElement("script");
+ s.src = originalUrl;
+ await new Promise((resolve, reject) => {
+ s.onerror = reject;
+ s.onload = function() {
+ for (const args of pendingParses) {
+ window.FB.XFBML.parse.apply(window.FB.XFBML, args);
+ }
+ resolve();
+ };
+ document.head.appendChild(s);
+ });
+ }
+
+ function buildPopupParams() {
+ const { outerWidth, outerHeight, screenX, screenY } = window;
+ const { width, height } = window.screen;
+ const w = Math.min(width, 400);
+ const h = Math.min(height, 400);
+ const ua = navigator.userAgent;
+ const isMobile = ua.includes("Mobile") || ua.includes("Tablet");
+ const left = screenX + (screenX < 0 ? width : 0) + (outerWidth - w) / 2;
+ const top = screenY + (screenY < 0 ? height : 0) + (outerHeight - h) / 2.5;
+ let params = `left=${left},top=${top},width=${w},height=${h},scrollbars=1,toolbar=0,location=1`;
+ if (!isMobile) {
+ params = `${params},width=${w},height=${h}`;
+ }
+ return params;
+ }
+
+ async function doLogin(a, b) {
+ window.FB.login(a, b);
+ }
+
+ function proxy(name, fn) {
+ return function() {
+ if (ready) {
+ return window.FB[name].apply(this, arguments);
+ }
+ return fn.apply(this, arguments);
+ };
+ }
+
+ window.FB = {
+ api: proxy("api", () => {}),
+ AppEvents: {
+ EventNames: {},
+ logPageView: () => {},
+ },
+ Event: {
+ subscribe: () => {},
+ },
+ getAccessToken: proxy("getAccessToken", () => null),
+ getAuthResponse: proxy("getAuthResponse", () => {
+ return { status: "" };
+ }),
+ getLoginStatus: proxy("getLoginStatus", cb => {
+ cb({ status: "" });
+ }),
+ getUserID: proxy("getUserID", () => {}),
+ init: _initInfo => {
+ if (ready) {
+ doLogin(_initInfo);
+ } else {
+ initInfo = _initInfo; // in case the site is not using fbAsyncInit
+ }
+ },
+ login: (a, b) => {
+ // We have to load Facebook's script, and then wait for it to call
+ // window.open. By that time, the popup blocker will likely trigger.
+ // So we open a popup now with about:blank, and then make sure FB
+ // will re-use that same popup later.
+ if (needPopup) {
+ window.open("about:blank", popupName, buildPopupParams());
+ }
+ allowFacebookSDK(() => {
+ doLogin(a, b);
+ });
+ },
+ logout: proxy("logout", cb => cb()),
+ XFBML: {
+ parse: e => {
+ pendingParses.push([e]);
+ },
+ },
+ };
+
+ if (window.fbAsyncInit) {
+ window.fbAsyncInit();
+ }
+}