diff options
Diffstat (limited to 'browser/extensions/webcompat/shims/facebook-sdk.js')
-rw-r--r-- | browser/extensions/webcompat/shims/facebook-sdk.js | 198 |
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(); + } +} |