213 lines
6.4 KiB
JavaScript
213 lines
6.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/. */
|
|
|
|
/**
|
|
* OpenSearchLoader is used for loading OpenSearch definitions from content.
|
|
*/
|
|
|
|
/* eslint no-shadow: error, mozilla/no-aArgs: error */
|
|
|
|
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
|
|
|
const lazy = XPCOMUtils.declareLazy({
|
|
HiddenBrowserManager: "resource://gre/modules/HiddenFrame.sys.mjs",
|
|
OpenSearchParser:
|
|
"moz-src:///toolkit/components/search/OpenSearchParser.sys.mjs",
|
|
SearchEngineInstallError:
|
|
"moz-src:///toolkit/components/search/SearchUtils.sys.mjs",
|
|
SearchUtils: "moz-src:///toolkit/components/search/SearchUtils.sys.mjs",
|
|
logConsole: () =>
|
|
console.createInstance({
|
|
prefix: "OpenSearchLoader",
|
|
maxLogLevel: lazy.SearchUtils.loggingEnabled ? "Debug" : "Warn",
|
|
}),
|
|
});
|
|
|
|
/**
|
|
* @import {OpenSearchProperties} from "./OpenSearchParser.sys.mjs"
|
|
*/
|
|
|
|
/**
|
|
* Retrieves the engine data from a URI and returns it.
|
|
*
|
|
* @param {nsIURI} sourceURI
|
|
* The uri from which to load the OpenSearch engine data.
|
|
* @param {string} [lastModified]
|
|
* The UTC date when the engine was last updated, if any.
|
|
* @param {OriginAttributesDictionary} [originAttributes]
|
|
* The origin attributes of the site loading the manifest. If none are
|
|
* specified, the origin attributes will be formed of the first party domain
|
|
* based on the domain of the manifest.
|
|
* @returns {Promise<OpenSearchProperties>}
|
|
* The properties of the loaded OpenSearch engine.
|
|
*/
|
|
export async function loadAndParseOpenSearchEngine(
|
|
sourceURI,
|
|
lastModified,
|
|
originAttributes
|
|
) {
|
|
if (!sourceURI) {
|
|
throw new TypeError("No URI");
|
|
}
|
|
if (!/^https?$/i.test(sourceURI.scheme)) {
|
|
throw new TypeError(
|
|
"Unsupported URI scheme passed to SearchEngine constructor"
|
|
);
|
|
}
|
|
|
|
lazy.logConsole.debug("Downloading OpenSearch engine from:", sourceURI.spec);
|
|
|
|
let xmlData = await loadEngineXML(sourceURI, lastModified, originAttributes);
|
|
|
|
lazy.logConsole.debug("Loading search plugin");
|
|
|
|
let engineData = await parseXMLData(xmlData);
|
|
|
|
engineData.installURL = sourceURI;
|
|
return engineData;
|
|
}
|
|
|
|
/**
|
|
* Parses OpenSearch XML data, using a hidden browser content process when
|
|
* available, or directly in the current process as a fallback (e.g., xpcshell).
|
|
*
|
|
* @param {number[]} xmlData
|
|
* The loaded search engine XML data as an array of bytes.
|
|
* @returns {Promise<OpenSearchProperties>}
|
|
* The extracted engine properties.
|
|
*/
|
|
async function parseXMLData(xmlData) {
|
|
// In the xpcshell parse directly in the parent process.
|
|
if (Cu.isInAutomation && Services.env.exists("XPCSHELL_TEST_PROFILE_DIR")) {
|
|
let result = lazy.OpenSearchParser.parseXMLData(xmlData);
|
|
if ("error" in result) {
|
|
lazy.logConsole.error(
|
|
"parseXMLData: Failed to init engine!",
|
|
result.error
|
|
);
|
|
throw new lazy.SearchEngineInstallError("corrupted", result.error);
|
|
}
|
|
return result.data;
|
|
}
|
|
return parseInHiddenBrowser(xmlData);
|
|
}
|
|
|
|
/**
|
|
* Parses OpenSearch XML data by loading it in a hidden browser and extracting
|
|
* engine data via a child actor in the content process.
|
|
*
|
|
* @param {number[]} xmlData
|
|
* The loaded search engine XML data as an array of bytes.
|
|
* @returns {Promise<OpenSearchProperties>}
|
|
* The extracted engine properties.
|
|
*/
|
|
async function parseInHiddenBrowser(xmlData) {
|
|
return lazy.HiddenBrowserManager.withHiddenBrowser(
|
|
async browser => {
|
|
let { promise, resolve } = Promise.withResolvers();
|
|
|
|
let progressListener = {
|
|
QueryInterface: ChromeUtils.generateQI([
|
|
"nsIWebProgressListener",
|
|
"nsISupportsWeakReference",
|
|
]),
|
|
onStateChange(webProgress, _request, flags) {
|
|
if (
|
|
!(flags & Ci.nsIWebProgressListener.STATE_STOP) ||
|
|
!(flags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)
|
|
) {
|
|
return;
|
|
}
|
|
browser.removeProgressListener(progressListener);
|
|
resolve();
|
|
},
|
|
};
|
|
|
|
browser.addProgressListener(
|
|
progressListener,
|
|
Ci.nsIWebProgress.NOTIFY_STATE_NETWORK
|
|
);
|
|
|
|
browser.loadURI(Services.io.newURI("about:blank"), {
|
|
triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal(
|
|
{}
|
|
),
|
|
});
|
|
|
|
await promise;
|
|
|
|
let actor =
|
|
browser.browsingContext.currentWindowGlobal.getActor(
|
|
"OpenSearchLoader"
|
|
);
|
|
let result = await actor.sendQuery(
|
|
"OpenSearchLoader:getEngineData",
|
|
xmlData
|
|
);
|
|
|
|
if ("error" in result) {
|
|
lazy.logConsole.error(
|
|
"parseInHiddenBrowser: Failed to init engine!",
|
|
result.error
|
|
);
|
|
throw new lazy.SearchEngineInstallError("corrupted", result.error);
|
|
}
|
|
|
|
return result.data;
|
|
},
|
|
{ messageManagerGroup: "opensearch" }
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Loads the engine XML from the given URI.
|
|
*
|
|
* @param {nsIURI} sourceURI
|
|
* The uri from which to load the OpenSearch engine data.
|
|
* @param {string} [lastModified]
|
|
* The UTC date when the engine was last updated, if any.
|
|
* @param {object} [originAttributes]
|
|
* The origin attributes to use to load the manifest.
|
|
* @returns {Promise}
|
|
* A promise that is resolved with the data if the engine is successfully loaded
|
|
* and rejected otherwise.
|
|
*/
|
|
function loadEngineXML(sourceURI, lastModified, originAttributes = null) {
|
|
var chan = lazy.SearchUtils.makeChannel(
|
|
sourceURI,
|
|
// OpenSearchEngine is loading a definition file for a search engine,
|
|
// TYPE_DOCUMENT captures that load best.
|
|
Ci.nsIContentPolicy.TYPE_DOCUMENT,
|
|
originAttributes
|
|
);
|
|
|
|
// we collect https telemetry for all top-level (document) loads.
|
|
chan.loadInfo.httpsUpgradeTelemetry = sourceURI.schemeIs("https")
|
|
? Ci.nsILoadInfo.ALREADY_HTTPS
|
|
: Ci.nsILoadInfo.NO_UPGRADE;
|
|
|
|
if (lastModified && chan instanceof Ci.nsIHttpChannel) {
|
|
chan.setRequestHeader("If-Modified-Since", lastModified, false);
|
|
}
|
|
let loadPromise = Promise.withResolvers();
|
|
|
|
let loadHandler = data => {
|
|
if (!data) {
|
|
loadPromise.reject(new lazy.SearchEngineInstallError("download-failure"));
|
|
return;
|
|
}
|
|
loadPromise.resolve(data);
|
|
};
|
|
|
|
var listener = new lazy.SearchUtils.LoadListener(
|
|
chan,
|
|
/(^text\/|xml$)/,
|
|
loadHandler
|
|
);
|
|
chan.notificationCallbacks = listener;
|
|
chan.asyncOpen(listener);
|
|
|
|
return loadPromise.promise;
|
|
}
|