fune/browser/extensions/webcompat-reporter/experimentalAPIs/tabExtras.js

156 lines
5.4 KiB
JavaScript

/* 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";
/* global ExtensionAPI, XPCOMUtils */
XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
function getInfoFrameScript(messageName) {
/* eslint-env mozilla/frame-script */
ChromeUtils.import("resource://gre/modules/Services.jsm");
function getInnerWindowId(window) {
return window.windowUtils.currentInnerWindowID;
}
function getInnerWindowIDsForAllFrames(window) {
const innerWindowID = getInnerWindowId(window);
let ids = [innerWindowID];
if (window.frames) {
for (let i = 0; i < window.frames.length; i++) {
ids = ids.concat(getInnerWindowIDsForAllFrames(window.frames[i]));
}
}
return ids;
}
function getLoggedMessages(window, includePrivate = false) {
const ids = getInnerWindowIDsForAllFrames(window);
return getConsoleMessages(ids).concat(getScriptErrors(ids, includePrivate))
.sort((a, b) => a.timeStamp - b.timeStamp)
.map(m => m.message);
}
function getConsoleMessages(windowIds) {
const ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
.getService(Ci.nsIConsoleAPIStorage);
let messages = [];
for (const id of windowIds) {
messages = messages.concat(ConsoleAPIStorage.getEvents(id) || []);
}
return messages.map(evt => {
const {columnNumber, filename, level, lineNumber, timeStamp} = evt;
const args = evt.arguments.map(arg => {
return "" + arg;
}).join(", ");
const message = `[console.${level}(${args}) ${filename}:${lineNumber}:${columnNumber}]`;
return {timeStamp, message};
});
}
function getScriptErrors(windowIds, includePrivate = false) {
const messages = Services.console.getMessageArray() || [];
return messages.filter(message => {
if (message instanceof Ci.nsIScriptError) {
if (!includePrivate && message.isFromPrivateWindow) {
return false;
}
if (windowIds && !windowIds.includes(message.innerWindowID)) {
return false;
}
return true;
}
// If this is not an nsIScriptError and we need to do window-based
// filtering we skip this message.
return false;
}).map(error => {
const {timeStamp, message} = error;
return {timeStamp, message};
});
}
sendAsyncMessage(messageName, {
hasMixedActiveContentBlocked: docShell.hasMixedActiveContentBlocked,
hasMixedDisplayContentBlocked: docShell.hasMixedDisplayContentBlocked,
hasTrackingContentBlocked: docShell.hasTrackingContentBlocked,
log: getLoggedMessages(content),
});
}
this.tabExtras = class extends ExtensionAPI {
getAPI(context) {
const {tabManager} = context.extension;
const {Management: {global: {windowTracker}}} =
ChromeUtils.import("resource://gre/modules/Extension.jsm", {});
return {
tabExtras: {
async loadURIWithPostData(tabId, url, postData, postDataContentType) {
const tab = tabManager.get(tabId);
if (!tab || !tab.browser) {
return Promise.reject("Invalid tab");
}
try {
new URL(url);
} catch (_) {
return Promise.reject("Invalid url");
}
if (typeof postData !== "string" && !(postData instanceof String)) {
return Promise.reject("postData must be a string");
}
const stringStream = Cc["@mozilla.org/io/string-input-stream;1"]
.createInstance(Ci.nsIStringInputStream);
stringStream.data = postData;
const post = Cc["@mozilla.org/network/mime-input-stream;1"]
.createInstance(Ci.nsIMIMEInputStream);
post.addHeader("Content-Type", postDataContentType ||
"application/x-www-form-urlencoded");
post.setData(stringStream);
return new Promise(resolve => {
const listener = {
onLocationChange(browser, webProgress, request, locationURI, flags) {
if (webProgress.isTopLevel &&
browser === tab.browser &&
locationURI.spec === url) {
windowTracker.removeListener("progress", listener);
resolve();
}
},
};
windowTracker.addListener("progress", listener);
let loadURIOptions = {
triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({}),
postData: post,
};
tab.browser.webNavigation.loadURI(url, loadURIOptions);
});
},
async getWebcompatInfo(tabId) {
return new Promise(resolve => {
const messageName = "WebExtension:GetWebcompatInfo";
const code = `${getInfoFrameScript.toString()};getInfoFrameScript("${messageName}")`;
const mm = tabManager.get(tabId).browser.messageManager;
mm.loadFrameScript(`data:,${encodeURI(code)}`, false);
mm.addMessageListener(messageName, function receiveFn(message) {
mm.removeMessageListener(messageName, receiveFn);
resolve(message.json);
});
});
},
},
};
}
};