summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LICENSE373
-rw-r--r--Makefile19
-rw-r--r--README23
-rw-r--r--chrome.manifest1
-rw-r--r--config.js13
-rw-r--r--defaults/pref/config-prefs.js3
-rw-r--r--legacy/BootstrapLoader.jsm372
-rw-r--r--legacy/RDFDataSource.jsm1520
-rw-r--r--legacy/RDFManifestConverter.jsm110
-rw-r--r--legacy/boot.jsm7
10 files changed, 2441 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..14e2f77
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ 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/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..3927c7f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,19 @@
+.PHONY: all install
+
+files = chrome.manifest
+files += config.js
+files += defaults/pref/config-prefs.js
+files += legacy/boot.jsm
+files += legacy/BootstrapLoader.jsm
+files += legacy/RDFDataSource.jsm
+files += legacy/RDFManifestConverter.jsm
+archive = legacyfox.tar.gz
+mozilladir = /usr/lib64/firefox/
+
+all: $(archive)
+
+$(archive): $(files)
+ tar czf $(archive) -- $^
+
+install: $(archive)
+ tar xzf $(archive) -C $(mozilladir)
diff --git a/README b/README
new file mode 100644
index 0000000..645514d
--- /dev/null
+++ b/README
@@ -0,0 +1,23 @@
+=== LegacyFox ===
+
+Monkeypatching Firefox Quantum to run VimFx
+
+== Installation ==
+0. Install/upgrade Firefox from your distribution's repositories
+1. As root, issue `make install`
+2. Install VimFx from https://github.com/akhodakivskiy/VimFx/releases
+
+== Notes ==
+ * boot.jsm was lifted from https://github.com/xiaoxiaoflood/firefox-scripts.git
+ * BootstrapLoader.jsm, RDFDataSource.jsm, RDFManifestConverter.jsm from their
+ last commit in comm-central (8a37a90aab4ec643fce1e1ab33984613ce0b492d)
+ * config.js: own work
+
+== Disclaimer & License ==
+
+This project is neither endorsed nor recommended by Mozilla. Firefox is their
+trademark; you are not granted any right to distribute modified binary versions
+of the software containing the official branding.
+
+(C) 2018-2019 Mozilla, MPL v2
+(C) 2019 //gir.st/, MPL v2
diff --git a/chrome.manifest b/chrome.manifest
new file mode 100644
index 0000000..5da5b9e
--- /dev/null
+++ b/chrome.manifest
@@ -0,0 +1 @@
+content legacy legacy/
diff --git a/config.js b/config.js
new file mode 100644
index 0000000..35e6bbf
--- /dev/null
+++ b/config.js
@@ -0,0 +1,13 @@
+// keep this comment
+try {
+ // monkey-patching to disable signing and force-enable legacy extensions:
+ let Xdb = Cu.import('resource://gre/modules/addons/XPIDatabase.jsm', {});
+ Xdb.XPIDatabase['SIGNED_TYPES'].clear();
+ Xdb.AddonSettings = {
+ "REQUIRE_SIGNING": false,
+ "LANGPACKS_REQUIRE_SIGNING": false,
+ "ALLOW_LEGACY_EXTENSIONS": true,
+ };
+
+ Cu.import('chrome://legacy/content/boot.jsm'); // engage the bootstrap loader
+} catch(ex) {}
diff --git a/defaults/pref/config-prefs.js b/defaults/pref/config-prefs.js
new file mode 100644
index 0000000..57e8af3
--- /dev/null
+++ b/defaults/pref/config-prefs.js
@@ -0,0 +1,3 @@
+pref("general.config.obscure_value", 0);
+pref("general.config.filename", "config.js");
+pref("general.config.sandbox_enabled", false);
diff --git a/legacy/BootstrapLoader.jsm b/legacy/BootstrapLoader.jsm
new file mode 100644
index 0000000..d9dc4c8
--- /dev/null
+++ b/legacy/BootstrapLoader.jsm
@@ -0,0 +1,372 @@
+/* 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";
+
+var EXPORTED_SYMBOLS = ["BootstrapLoader"];
+
+const {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
+const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ AddonInternal: "resource://gre/modules/addons/XPIDatabase.jsm",
+ Blocklist: "resource://gre/modules/Blocklist.jsm",
+ ConsoleAPI: "resource://gre/modules/Console.jsm",
+ InstallRDF: "chrome://legacy/content/RDFManifestConverter.jsm",
+ Services: "resource://gre/modules/Services.jsm",
+});
+
+XPCOMUtils.defineLazyGetter(this, "BOOTSTRAP_REASONS", () => {
+ const {XPIProvider} = ChromeUtils.import("resource://gre/modules/addons/XPIProvider.jsm");
+ return XPIProvider.BOOTSTRAP_REASONS;
+});
+
+const {Log} = ChromeUtils.import("resource://gre/modules/Log.jsm");
+var logger = Log.repository.getLogger("addons.bootstrap");
+
+/**
+ * Valid IDs fit this pattern.
+ */
+var gIDTest = /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i;
+
+// Properties that exist in the install manifest
+const PROP_METADATA = ["id", "version", "type", "internalName", "updateURL",
+ "optionsURL", "optionsType", "aboutURL", "iconURL"];
+const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
+const PROP_LOCALE_MULTI = ["developers", "translators", "contributors"];
+
+// Map new string type identifiers to old style nsIUpdateItem types.
+// Retired values:
+// 32 = multipackage xpi file
+// 8 = locale
+// 256 = apiextension
+// 128 = experiment
+// theme = 4
+const TYPES = {
+ extension: 2,
+ dictionary: 64,
+};
+
+const COMPATIBLE_BY_DEFAULT_TYPES = {
+ extension: true,
+ dictionary: true,
+};
+
+const hasOwnProperty = Function.call.bind(Object.prototype.hasOwnProperty);
+
+function isXPI(filename) {
+ let ext = filename.slice(-4).toLowerCase();
+ return ext === ".xpi" || ext === ".zip";
+}
+
+/**
+ * Gets an nsIURI for a file within another file, either a directory or an XPI
+ * file. If aFile is a directory then this will return a file: URI, if it is an
+ * XPI file then it will return a jar: URI.
+ *
+ * @param {nsIFile} aFile
+ * The file containing the resources, must be either a directory or an
+ * XPI file
+ * @param {string} aPath
+ * The path to find the resource at, "/" separated. If aPath is empty
+ * then the uri to the root of the contained files will be returned
+ * @returns {nsIURI}
+ * An nsIURI pointing at the resource
+ */
+function getURIForResourceInFile(aFile, aPath) {
+ if (!isXPI(aFile.leafName)) {
+ let resource = aFile.clone();
+ if (aPath)
+ aPath.split("/").forEach(part => resource.append(part));
+
+ return Services.io.newFileURI(resource);
+ }
+
+ return buildJarURI(aFile, aPath);
+}
+
+/**
+ * Creates a jar: URI for a file inside a ZIP file.
+ *
+ * @param {nsIFile} aJarfile
+ * The ZIP file as an nsIFile
+ * @param {string} aPath
+ * The path inside the ZIP file
+ * @returns {nsIURI}
+ * An nsIURI for the file
+ */
+function buildJarURI(aJarfile, aPath) {
+ let uri = Services.io.newFileURI(aJarfile);
+ uri = "jar:" + uri.spec + "!/" + aPath;
+ return Services.io.newURI(uri);
+}
+
+var BootstrapLoader = {
+ name: "bootstrap",
+ manifestFile: "install.rdf",
+ async loadManifest(pkg) {
+ /**
+ * Reads locale properties from either the main install manifest root or
+ * an em:localized section in the install manifest.
+ *
+ * @param {Object} aSource
+ * The resource to read the properties from.
+ * @param {boolean} isDefault
+ * True if the locale is to be read from the main install manifest
+ * root
+ * @param {string[]} aSeenLocales
+ * An array of locale names already seen for this install manifest.
+ * Any locale names seen as a part of this function will be added to
+ * this array
+ * @returns {Object}
+ * an object containing the locale properties
+ */
+ function readLocale(aSource, isDefault, aSeenLocales) {
+ let locale = {};
+ if (!isDefault) {
+ locale.locales = [];
+ for (let localeName of aSource.locales || []) {
+ if (!localeName) {
+ logger.warn("Ignoring empty locale in localized properties");
+ continue;
+ }
+ if (aSeenLocales.includes(localeName)) {
+ logger.warn("Ignoring duplicate locale in localized properties");
+ continue;
+ }
+ aSeenLocales.push(localeName);
+ locale.locales.push(localeName);
+ }
+
+ if (locale.locales.length == 0) {
+ logger.warn("Ignoring localized properties with no listed locales");
+ return null;
+ }
+ }
+
+ for (let prop of [...PROP_LOCALE_SINGLE, ...PROP_LOCALE_MULTI]) {
+ if (hasOwnProperty(aSource, prop)) {
+ locale[prop] = aSource[prop];
+ }
+ }
+
+ return locale;
+ }
+
+ let manifestData = await pkg.readString("install.rdf");
+ let manifest = InstallRDF.loadFromString(manifestData).decode();
+
+ let addon = new AddonInternal();
+ for (let prop of PROP_METADATA) {
+ if (hasOwnProperty(manifest, prop)) {
+ addon[prop] = manifest[prop];
+ }
+ }
+
+ if (!addon.type) {
+ addon.type = "extension";
+ } else {
+ let type = addon.type;
+ addon.type = null;
+ for (let name in TYPES) {
+ if (TYPES[name] == type) {
+ addon.type = name;
+ break;
+ }
+ }
+ }
+
+ if (!(addon.type in TYPES))
+ throw new Error("Install manifest specifies unknown type: " + addon.type);
+
+ if (!addon.id)
+ throw new Error("No ID in install manifest");
+ if (!gIDTest.test(addon.id))
+ throw new Error("Illegal add-on ID " + addon.id);
+ if (!addon.version)
+ throw new Error("No version in install manifest");
+
+ addon.strictCompatibility = (!(addon.type in COMPATIBLE_BY_DEFAULT_TYPES) ||
+ manifest.strictCompatibility == "true");
+
+ // Only read these properties for extensions.
+ if (addon.type == "extension") {
+ if (manifest.bootstrap != "true") {
+ throw new Error("Non-restartless extensions no longer supported");
+ }
+
+ if (addon.optionsType &&
+ addon.optionsType != AddonManager.OPTIONS_TYPE_INLINE_BROWSER &&
+ addon.optionsType != AddonManager.OPTIONS_TYPE_TAB) {
+ throw new Error("Install manifest specifies unknown optionsType: " + addon.optionsType);
+ }
+ } else {
+ // Convert legacy dictionaries into a format the WebExtension
+ // dictionary loader can process.
+ if (addon.type === "dictionary") {
+ addon.loader = null;
+ let dictionaries = {};
+ await pkg.iterFiles(({path}) => {
+ let match = /^dictionaries\/([^\/]+)\.dic$/.exec(path);
+ if (match) {
+ let lang = match[1].replace(/_/g, "-");
+ dictionaries[lang] = match[0];
+ }
+ });
+ addon.startupData = {dictionaries};
+ }
+
+ // Only extensions are allowed to provide an optionsURL, optionsType,
+ // optionsBrowserStyle, or aboutURL. For all other types they are silently ignored
+ addon.aboutURL = null;
+ addon.optionsBrowserStyle = null;
+ addon.optionsType = null;
+ addon.optionsURL = null;
+ }
+
+ addon.defaultLocale = readLocale(manifest, true);
+
+ let seenLocales = [];
+ addon.locales = [];
+ for (let localeData of manifest.localized || []) {
+ let locale = readLocale(localeData, false, seenLocales);
+ if (locale)
+ addon.locales.push(locale);
+ }
+
+ let dependencies = new Set(manifest.dependencies);
+ addon.dependencies = Object.freeze(Array.from(dependencies));
+
+ let seenApplications = [];
+ addon.targetApplications = [];
+ for (let targetApp of manifest.targetApplications || []) {
+ if (!targetApp.id || !targetApp.minVersion ||
+ !targetApp.maxVersion) {
+ logger.warn("Ignoring invalid targetApplication entry in install manifest");
+ continue;
+ }
+ if (seenApplications.includes(targetApp.id)) {
+ logger.warn("Ignoring duplicate targetApplication entry for " + targetApp.id +
+ " in install manifest");
+ continue;
+ }
+ seenApplications.push(targetApp.id);
+ addon.targetApplications.push(targetApp);
+ }
+
+ // Note that we don't need to check for duplicate targetPlatform entries since
+ // the RDF service coalesces them for us.
+ addon.targetPlatforms = [];
+ for (let targetPlatform of manifest.targetPlatforms || []) {
+ let platform = {
+ os: null,
+ abi: null,
+ };
+
+ let pos = targetPlatform.indexOf("_");
+ if (pos != -1) {
+ platform.os = targetPlatform.substring(0, pos);
+ platform.abi = targetPlatform.substring(pos + 1);
+ } else {
+ platform.os = targetPlatform;
+ }
+
+ addon.targetPlatforms.push(platform);
+ }
+
+ addon.userDisabled = false;
+ addon.softDisabled = addon.blocklistState == Blocklist.STATE_SOFTBLOCKED;
+ addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
+
+ addon.userPermissions = null;
+
+ addon.icons = {};
+ if (await pkg.hasResource("icon.png")) {
+ addon.icons[32] = "icon.png";
+ addon.icons[48] = "icon.png";
+ }
+
+ if (await pkg.hasResource("icon64.png")) {
+ addon.icons[64] = "icon64.png";
+ }
+
+ return addon;
+ },
+
+ loadScope(addon) {
+ let file = addon.file || addon._sourceBundle;
+ let uri = getURIForResourceInFile(file, "bootstrap.js").spec;
+ let principal = Services.scriptSecurityManager.getSystemPrincipal();
+
+ let sandbox = new Cu.Sandbox(principal, {
+ sandboxName: uri,
+ addonId: addon.id,
+ wantGlobalProperties: ["ChromeUtils"],
+ metadata: { addonID: addon.id, URI: uri },
+ });
+
+ try {
+ Object.assign(sandbox, BOOTSTRAP_REASONS);
+
+ XPCOMUtils.defineLazyGetter(sandbox, "console", () =>
+ new ConsoleAPI({ consoleID: `addon/${addon.id}` }));
+
+ Services.scriptloader.loadSubScript(uri, sandbox);
+ } catch (e) {
+ logger.warn(`Error loading bootstrap.js for ${addon.id}`, e);
+ }
+
+ function findMethod(name) {
+ if (sandbox.name) {
+ return sandbox.name;
+ }
+
+ try {
+ let method = Cu.evalInSandbox(name, sandbox);
+ return method;
+ } catch (err) { }
+
+ return () => {
+ logger.warn(`Add-on ${addon.id} is missing bootstrap method ${name}`);
+ };
+ }
+
+ let install = findMethod("install");
+ let uninstall = findMethod("uninstall");
+ let startup = findMethod("startup");
+ let shutdown = findMethod("shutdown");
+
+ return {
+ install: (...args) => install(...args),
+
+ uninstall(...args) {
+ uninstall(...args);
+ // Forget any cached files we might've had from this extension.
+ Services.obs.notifyObservers(null, "startupcache-invalidate");
+ },
+
+ startup(...args) {
+ if (addon.type == "extension") {
+ logger.debug(`Registering manifest for ${file.path}\n`);
+ Components.manager.addBootstrappedManifestLocation(file);
+ }
+ return startup(...args);
+ },
+
+ shutdown(data, reason) {
+ try {
+ return shutdown(data, reason);
+ } catch (err) {
+ throw err;
+ } finally {
+ if (reason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
+ logger.debug(`Removing manifest for ${file.path}\n`);
+ Components.manager.removeBootstrappedManifestLocation(file);
+ }
+ }
+ },
+ };
+ },
+};
+
diff --git a/legacy/RDFDataSource.jsm b/legacy/RDFDataSource.jsm
new file mode 100644
index 0000000..fadaab1
--- /dev/null
+++ b/legacy/RDFDataSource.jsm
@@ -0,0 +1,1520 @@
+ /* 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/. */
+
+/**
+ * This module creates a new API for accessing and modifying RDF graphs. The
+ * goal is to be able to serialise the graph in a human readable form. Also
+ * if the graph was originally loaded from an RDF/XML the serialisation should
+ * closely match the original with any new data closely following the existing
+ * layout. The output should always be compatible with Mozilla's RDF parser.
+ *
+ * This is all achieved by using a DOM Document to hold the current state of the
+ * graph in XML form. This can be initially loaded and parsed from disk or
+ * a blank document used for an empty graph. As assertions are added to the
+ * graph, appropriate DOM nodes are added to the document to represent them
+ * along with any necessary whitespace to properly layout the XML.
+ *
+ * In general the order of adding assertions to the graph will impact the form
+ * the serialisation takes. If a resource is first added as the object of an
+ * assertion then it will eventually be serialised inside the assertion's
+ * property element. If a resource is first added as the subject of an assertion
+ * then it will be serialised at the top level of the XML.
+ */
+
+const NS_XML = "http://www.w3.org/XML/1998/namespace";
+const NS_XMLNS = "http://www.w3.org/2000/xmlns/";
+const NS_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+const NS_NC = "http://home.netscape.com/NC-rdf#";
+
+/* eslint prefer-template: 1 */
+
+function raw(strings) {
+ return strings.raw[0].replace(/\s+/, "");
+}
+
+// Copied from http://www.w3.org/TR/2000/REC-xml-20001006#CharClasses
+const XML_LETTER = raw`
+ \u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6
+ \u00F8-\u00FF\u0100-\u0131\u0134-\u013E\u0141-\u0148
+ \u014A-\u017E\u0180-\u01C3\u01CD-\u01F0\u01F4-\u01F5
+ \u01FA-\u0217\u0250-\u02A8\u02BB-\u02C1\u0386\u0388-\u038A
+ \u038C\u038E-\u03A1\u03A3-\u03CE\u03D0-\u03D6\u03DA\u03DC
+ \u03DE\u03E0\u03E2-\u03F3\u0401-\u040C\u040E-\u044F
+ \u0451-\u045C\u045E-\u0481\u0490-\u04C4\u04C7-\u04C8
+ \u04CB-\u04CC\u04D0-\u04EB\u04EE-\u04F5\u04F8-\u04F9
+ \u0531-\u0556\u0559\u0561-\u0586\u05D0-\u05EA\u05F0-\u05F2
+ \u0621-\u063A\u0641-\u064A\u0671-\u06B7\u06BA-\u06BE
+ \u06C0-\u06CE\u06D0-\u06D3\u06D5\u06E5-\u06E6\u0905-\u0939
+ \u093D\u0958-\u0961\u0985-\u098C\u098F-\u0990\u0993-\u09A8
+ \u09AA-\u09B0\u09B2\u09B6-\u09B9\u09DC-\u09DD\u09DF-\u09E1
+ \u09F0-\u09F1\u0A05-\u0A0A\u0A0F-\u0A10\u0A13-\u0A28
+ \u0A2A-\u0A30\u0A32-\u0A33\u0A35-\u0A36\u0A38-\u0A39
+ \u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8B\u0A8D
+ \u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2-\u0AB3
+ \u0AB5-\u0AB9\u0ABD\u0AE0\u0B05-\u0B0C\u0B0F-\u0B10
+ \u0B13-\u0B28\u0B2A-\u0B30\u0B32-\u0B33\u0B36-\u0B39
+ \u0B3D\u0B5C-\u0B5D\u0B5F-\u0B61\u0B85-\u0B8A\u0B8E-\u0B90
+ \u0B92-\u0B95\u0B99-\u0B9A\u0B9C\u0B9E-\u0B9F\u0BA3-\u0BA4
+ \u0BA8-\u0BAA\u0BAE-\u0BB5\u0BB7-\u0BB9\u0C05-\u0C0C
+ \u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39
+ \u0C60-\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8
+ \u0CAA-\u0CB3\u0CB5-\u0CB9\u0CDE\u0CE0-\u0CE1\u0D05-\u0D0C
+ \u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D60-\u0D61
+ \u0E01-\u0E2E\u0E30\u0E32-\u0E33\u0E40-\u0E45\u0E81-\u0E82
+ \u0E84\u0E87-\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F
+ \u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA-\u0EAB\u0EAD-\u0EAE\u0EB0
+ \u0EB2-\u0EB3\u0EBD\u0EC0-\u0EC4\u0F40-\u0F47\u0F49-\u0F69
+ \u10A0-\u10C5\u10D0-\u10F6\u1100\u1102-\u1103\u1105-\u1107
+ \u1109\u110B-\u110C\u110E-\u1112\u113C\u113E\u1140\u114C
+ \u114E\u1150\u1154-\u1155\u1159\u115F-\u1161\u1163\u1165
+ \u1167\u1169\u116D-\u116E\u1172-\u1173\u1175\u119E\u11A8
+ \u11AB\u11AE-\u11AF\u11B7-\u11B8\u11BA\u11BC-\u11C2\u11EB
+ \u11F0\u11F9\u1E00-\u1E9B\u1EA0-\u1EF9\u1F00-\u1F15
+ \u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57
+ \u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC
+ \u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB
+ \u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2126\u212A-\u212B
+ \u212E\u2180-\u2182\u3041-\u3094\u30A1-\u30FA\u3105-\u312C
+ \uAC00-\uD7A3\u4E00-\u9FA5\u3007\u3021-\u3029
+`;
+const XML_DIGIT = raw`
+ \u0030-\u0039\u0660-\u0669\u06F0-\u06F9\u0966-\u096F
+ \u09E6-\u09EF\u0A66-\u0A6F\u0AE6-\u0AEF\u0B66-\u0B6F
+ \u0BE7-\u0BEF\u0C66-\u0C6F\u0CE6-\u0CEF\u0D66-\u0D6F
+ \u0E50-\u0E59\u0ED0-\u0ED9\u0F20-\u0F29
+`;
+const XML_COMBINING = raw`
+ \u0300-\u0345\u0360-\u0361\u0483-\u0486\u0591-\u05A1
+ \u05A3-\u05B9\u05BB-\u05BD\u05BF\u05C1-\u05C2\u05C4
+ \u064B-\u0652\u0670\u06D6-\u06DC\u06DD-\u06DF\u06E0-\u06E4
+ \u06E7-\u06E8\u06EA-\u06ED\u0901-\u0903\u093C\u093E-\u094C
+ \u094D\u0951-\u0954\u0962-\u0963\u0981-\u0983\u09BC\u09BE
+ \u09BF\u09C0-\u09C4\u09C7-\u09C8\u09CB-\u09CD\u09D7
+ \u09E2-\u09E3\u0A02\u0A3C\u0A3E\u0A3F\u0A40-\u0A42
+ \u0A47-\u0A48\u0A4B-\u0A4D\u0A70-\u0A71\u0A81-\u0A83
+ \u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0B01-\u0B03
+ \u0B3C\u0B3E-\u0B43\u0B47-\u0B48\u0B4B-\u0B4D\u0B56-\u0B57
+ \u0B82-\u0B83\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7
+ \u0C01-\u0C03\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D
+ \u0C55-\u0C56\u0C82-\u0C83\u0CBE-\u0CC4\u0CC6-\u0CC8
+ \u0CCA-\u0CCD\u0CD5-\u0CD6\u0D02-\u0D03\u0D3E-\u0D43
+ \u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0E31\u0E34-\u0E3A
+ \u0E47-\u0E4E\u0EB1\u0EB4-\u0EB9\u0EBB-\u0EBC\u0EC8-\u0ECD
+ \u0F18-\u0F19\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84
+ \u0F86-\u0F8B\u0F90-\u0F95\u0F97\u0F99-\u0FAD\u0FB1-\u0FB7
+ \u0FB9\u20D0-\u20DC\u20E1\u302A-\u302F\u3099\u309A
+`;
+const XML_EXTENDER = raw`
+ \u00B7\u02D0\u02D1\u0387\u0640\u0E46\u0EC6\u3005
+ \u3031-\u3035\u309D-\u309E\u30FC-\u30FE
+`;
+const XML_NCNAMECHAR = String.raw`${XML_LETTER}${XML_DIGIT}\.\-_${XML_COMBINING}${XML_EXTENDER}`;
+const XML_NCNAME = new RegExp(`^[${XML_LETTER}_][${XML_NCNAMECHAR}]*$`);
+
+const URI_SUFFIX = /[A-Za-z_][0-9A-Za-z\.\-_]*$/;
+const INDENT = /\n([ \t]*)$/;
+const RDF_LISTITEM = /^http:\/\/www.w3.org\/1999\/02\/22-rdf-syntax-ns#_\d+$/;
+
+const RDF_NODE_INVALID_TYPES =
+ ["RDF", "ID", "about", "bagID", "parseType", "resource", "nodeID",
+ "li", "aboutEach", "aboutEachPrefix"];
+const RDF_PROPERTY_INVALID_TYPES =
+ ["Description", "RDF", "ID", "about", "bagID", "parseType", "resource",
+ "nodeID", "aboutEach", "aboutEachPrefix"];
+
+/**
+ * Whether to use properly namespaces attributes for rdf:about etc...
+ * When on this produces poor output in the event that the rdf namespace is the
+ * default namespace, and the parser recognises unnamespaced attributes and
+ * most of our rdf examples are unnamespaced so leaving off for the time being.
+ */
+const USE_RDFNS_ATTR = false;
+
+var EXPORTED_SYMBOLS = ["RDFLiteral", "RDFIntLiteral", "RDFDateLiteral",
+ "RDFBlankNode", "RDFResource", "RDFDataSource"];
+
+const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyGlobalGetters(this, ["DOMParser", "Element", "XMLSerializer", "fetch"]);
+
+ChromeUtils.defineModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+ChromeUtils.defineModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+
+function isAttr(obj) {
+ return obj && typeof obj == "object" && ChromeUtils.getClassName(obj) == "Attr";
+}
+function isDocument(obj) {
+ return obj && typeof obj == "object" && obj.nodeType == Element.DOCUMENT_NODE;
+}
+function isElement(obj) {
+ return Element.isInstance(obj);
+}
+function isText(obj) {
+ return obj && typeof obj == "object" && ChromeUtils.getClassName(obj) == "Text";
+}
+
+/**
+ * Logs an error message to the error console
+ */
+function ERROR(str) {
+ Cu.reportError(str);
+}
+
+function RDF_R(name) {
+ return NS_RDF + name;
+}
+
+function renameNode(domnode, namespaceURI, qname) {
+ if (isElement(domnode)) {
+ var newdomnode = domnode.ownerDocument.createElementNS(namespaceURI, qname);
+ if ("listCounter" in domnode)
+ newdomnode.listCounter = domnode.listCounter;
+ domnode.replaceWith(newdomnode);
+ while (domnode.firstChild)
+ newdomnode.appendChild(domnode.firstChild);
+ for (let attr of domnode.attributes) {
+ domnode.removeAttributeNode(attr);
+ newdomnode.setAttributeNode(attr);
+ }
+ return newdomnode;
+ } else if (isAttr(domnode)) {
+ if (domnode.ownerElement.hasAttribute(namespaceURI, qname))
+ throw new Error("attribute already exists");
+ var attr = domnode.ownerDocument.createAttributeNS(namespaceURI, qname);
+ attr.value = domnode.value;
+ domnode.ownerElement.setAttributeNode(attr);
+ domnode.ownerElement.removeAttributeNode(domnode);
+ return attr;
+ }
+ throw new Error("cannot rename node of this type");
+}
+
+function predicateOrder(a, b) {
+ return a.getPredicate().localeCompare(b.getPredicate());
+}
+
+/**
+ * Returns either an rdf namespaced attribute or an un-namespaced attribute
+ * value. Returns null if neither exists,
+ */
+function getRDFAttribute(element, name) {
+ if (element.hasAttributeNS(NS_RDF, name))
+ return element.getAttributeNS(NS_RDF, name);
+ if (element.hasAttribute(name))
+ return element.getAttribute(name);
+ return undefined;
+}
+
+/**
+ * Represents an assertion in the datasource
+ */
+class RDFAssertion {
+ constructor(subject, predicate, object) {
+ if (!(subject instanceof RDFSubject))
+ throw new Error("subject must be an RDFSubject");
+
+ if (typeof(predicate) != "string")
+ throw new Error("predicate must be a string URI");
+
+ if (!(object instanceof RDFLiteral) && !(object instanceof RDFSubject))
+ throw new Error("object must be a concrete RDFNode");
+
+ if (object instanceof RDFSubject && object._ds != subject._ds)
+ throw new Error("object must be from the same datasource as subject");
+
+ // The subject on this assertion, an RDFSubject
+ this._subject = subject;
+ // The predicate, a string
+ this._predicate = predicate;
+ // The object, an RDFNode
+ this._object = object;
+ // The datasource this assertion exists in
+ this._ds = this._subject._ds;
+ // Marks that _DOMnode is the subject's element
+ this._isSubjectElement = false;
+ // The DOM node that represents this assertion. Could be a property element,
+ // a property attribute or the subject's element for rdf:type
+ this._DOMNode = null;
+ }
+
+ /**
+ * Adds content to _DOMnode to store this assertion in the DOM document.
+ */
+ _applyToDOMNode() {
+ if (this._object instanceof RDFLiteral)
+ this._object._applyToDOMNode(this._ds, this._DOMnode);
+ else
+ this._object._addReferenceToElement(this._DOMnode);
+ }
+
+ /**
+ * Returns the DOM Element linked to the subject that this assertion is
+ * attached to.
+ */
+ _getSubjectElement() {
+ if (isAttr(this._DOMnode))
+ return this._DOMnode.ownerElement;
+ if (this._isSubjectElement)
+ return this._DOMnode;
+ return this._DOMnode.parentNode;
+ }
+
+ getSubject() {
+ return this._subject;
+ }
+
+ getPredicate() {
+ return this._predicate;
+ }
+
+ getObject() {
+ return this._object;
+ }
+}
+
+class RDFNode {
+ equals(rdfnode) {
+ return (rdfnode.constructor === this.constructor &&
+ rdfnode._value == this._value);
+ }
+}
+
+/**
+ * A simple literal value
+ */
+class RDFLiteral extends RDFNode {
+ constructor(value) {
+ super();
+ this._value = value;
+ }
+
+ /**
+ * This stores the value of the literal in the given DOM node
+ */
+ _applyToDOMNode(ds, domnode) {
+ if (isElement(domnode))
+ domnode.textContent = this._value;
+ else if (isAttr(domnode))
+ domnode.value = this._value;
+ else
+ throw new Error("cannot use this node for a literal");
+ }
+
+ getValue() {
+ return this._value;
+ }
+}
+
+/**
+ * A literal that is integer typed.
+ */
+class RDFIntLiteral extends RDFLiteral {
+ constructor(value) {
+ super(parseInt(value));
+ }
+
+ /**
+ * This stores the value of the literal in the given DOM node
+ */
+ _applyToDOMNode(ds, domnode) {
+ if (!isElement(domnode))
+ throw new Error("cannot use this node for a literal");
+
+ RDFLiteral.prototype._applyToDOMNode.call(this, ds, domnode);
+ var prefix = ds._resolvePrefix(domnode, `${NS_NC}parseType`);
+ domnode.setAttributeNS(prefix.namespaceURI, prefix.qname, "Integer");
+ }
+}
+
+/**
+ * A literal that represents a date.
+ */
+class RDFDateLiteral extends RDFLiteral {
+ constructor(value) {
+ if (!(value instanceof Date))
+ throw new Error("RDFDateLiteral must be constructed with a Date object");
+
+ super(value);
+ }
+
+ /**
+ * This stores the value of the literal in the given DOM node
+ */
+ _applyToDOMNode(ds, domnode) {
+ if (!isElement(domnode))
+ throw new Error("cannot use this node for a literal");
+
+ domnode.textContent = this._value.getTime();
+ var prefix = ds._resolvePrefix(domnode, `${NS_NC}parseType`);
+ domnode.setAttributeNS(prefix.namespaceURI, prefix.qname, "Date");
+ }
+}
+
+/**
+ * This is an RDF node that can be a subject so a resource or a blank node
+ */
+class RDFSubject extends RDFNode {
+ constructor(ds) {
+ super();
+ // A lookup of the assertions with this as the subject. Keyed on predicate
+ this._assertions = {};
+ // A lookup of the assertions with this as the object. Keyed on predicate
+ this._backwards = {};
+ // The datasource this subject belongs to
+ this._ds = ds;
+ // The DOM elements in the document that represent this subject. Array of Element
+ this._elements = [];
+ }
+
+ /**
+ * Creates a new Element in the document for holding assertions about this
+ * subject. The URI controls what tagname to use.
+ */
+ _createElement(uri) {
+ // Seek an appropriate reference to this node to add this node under
+ var parent = null;
+ for (var p in this._backwards) {
+ for (let back of this._backwards[p]) {
+ // Don't add under an rdf:type
+ if (back.getPredicate() == RDF_R("type"))
+ continue;
+ // The assertion already has a child node, probably one of ours
+ if (back._DOMnode.firstChild)
+ continue;
+ parent = back._DOMnode;
+ var element = this._ds._addElement(parent, uri);
+ this._removeReferenceFromElement(parent);
+ break;
+ }
+ if (parent)
+ break;
+ }
+
+ // No back assertions that are sensible to use
+ if (!parent)
+ element = this._ds._addElement(this._ds._document.documentElement, uri);
+
+ element.listCounter = 1;
+ this._applyToElement(element);
+ this._elements.push(element);
+ return element;
+ }
+
+ /**
+ * When a DOM node representing this subject is removed from the document
+ * we must remove the node and recreate any child assertions elsewhere.
+ */
+ _removeElement(element) {
+ var pos = this._elements.indexOf(element);
+ if (pos < 0)
+ throw new Error("invalid element");
+ this._elements.splice(pos, 1);
+ if (element.parentNode != element.ownerDocument.documentElement)
+ this._addReferenceToElement(element.parentNode);
+ this._ds._removeElement(element);
+
+ // Find all the assertions that are represented here and create new
+ // nodes for them.
+ for (var predicate in this._assertions) {
+ for (let assertion of this._assertions[predicate]) {
+ if (assertion._getSubjectElement() == element)
+ this._createDOMNodeForAssertion(assertion);
+ }
+ }
+ }
+
+ /**
+ * Creates a DOM node to represent the assertion in the document. If the
+ * assertion has rdf:type as the predicate then an attempt will be made to
+ * create a typed subject Element, otherwise a new property Element is
+ * created. For list items an attempt is made to find an appropriate container
+ * that an rdf:li element can be added to.
+ */
+ _createDOMNodeForAssertion(assertion) {
+ let elements;
+ if (RDF_LISTITEM.test(assertion.getPredicate())) {
+ // Find all the containers
+ elements = this._elements.filter(function(element) {
+ return (element.namespaceURI == NS_RDF && (element.localName == "Seq" ||
+ element.localName == "Bag" ||
+ element.localName == "Alt"));
+ });
+ if (elements.length > 0) {
+ // Look for one whose listCounter matches the item we want to add
+ var item = parseInt(assertion.getPredicate().substring(NS_RDF.length + 1));
+ for (let element of elements) {
+ if (element.listCounter == item) {
+ assertion._DOMnode = this._ds._addElement(element, RDF_R("li"));
+ assertion._applyToDOMNode();
+ element.listCounter++;
+ return;
+ }
+ }
+ // No good container to add to, shove in the first real container
+ assertion._DOMnode = this._ds._addElement(elements[0], assertion.getPredicate());
+ assertion._applyToDOMNode();
+ return;
+ }
+ // TODO No containers, this will end up in a non-container for now
+ } else if (assertion.getPredicate() == RDF_R("type")) {
+ // Try renaming an existing rdf:Description
+ for (let element of this.elements) {
+ if (element.namespaceURI == NS_RDF &&
+ element.localName == "Description") {
+ try {
+ var prefix = this._ds._resolvePrefix(element.parentNode, assertion.getObject().getURI());
+ element = renameNode(element, prefix.namespaceURI, prefix.qname);
+ assertion._DOMnode = element;
+ assertion._isSubjectElement = true;
+ return;
+ } catch (e) {
+ // If the type cannot be sensibly turned into a prefix then just set
+ // as a regular property
+ }
+ }
+ }
+ }
+
+ // Filter out all the containers
+ elements = this._elements.filter(function(element) {
+ return (element.namespaceURI != NS_RDF || (element.localName != "Seq" &&
+ element.localName != "Bag" &&
+ element.localName != "Alt"));
+ });
+ if (elements.length == 0) {
+ // Create a new node of the right type
+ if (assertion.getPredicate() == RDF_R("type")) {
+ try {
+ assertion._DOMnode = this._createElement(assertion.getObject().getURI());
+ assertion._isSubjectElement = true;
+ return;
+ } catch (e) {
+ // If the type cannot be sensibly turned into a prefix then just set
+ // as a regular property
+ }
+ }
+ elements[0] = this._createElement(RDF_R("Description"));
+ }
+ assertion._DOMnode = this._ds._addElement(elements[0], assertion.getPredicate());
+ assertion._applyToDOMNode();
+ }
+
+ /**
+ * Removes the DOM node representing the assertion.
+ */
+ _removeDOMNodeForAssertion(assertion) {
+ if (isAttr(assertion._DOMnode)) {
+ var parent = assertion._DOMnode.ownerElement;
+ parent.removeAttributeNode(assertion._DOMnode);
+ } else if (assertion._isSubjectElement) {
+ var domnode = renameNode(assertion._DOMnode, NS_RDF, "Description");
+ if (domnode != assertion._DOMnode) {
+ var pos = this._elements.indexOf(assertion._DOMnode);
+ this._elements.splice(pos, 1, domnode);
+ }
+ parent = domnode;
+ } else {
+ var object = assertion.getObject();
+ if (object instanceof RDFSubject && assertion._DOMnode.firstChild) {
+ // Object is a subject that has an Element inside this assertion's node.
+ for (let element of object._elements) {
+ if (element.parentNode == assertion._DOMnode) {
+ object._removeElement(element);
+ break;
+ }
+ }
+ }
+ parent = assertion._DOMnode.parentNode;
+ if (assertion._DOMnode.namespaceURI == NS_RDF &&
+ assertion._DOMnode.localName == "li")
+ parent.listCounter--;
+ this._ds._removeElement(assertion._DOMnode);
+ }
+
+ // If there are no assertions left using the assertion's containing dom node
+ // then remove it from the document.
+ // TODO could do with a quick lookup list for assertions attached to a node
+ for (var p in this._assertions) {
+ for (let assertion of this._assertions[p]) {
+ if (assertion._getSubjectElement() == parent)
+ return;
+ }
+ }
+ // No assertions left in this element.
+ this._removeElement(parent);
+ }
+
+ /**
+ * Parses the given Element from the DOM document
+ */
+ /* eslint-disable complexity */
+ _parseElement(element) {
+ this._elements.push(element);
+
+ // There might be an inferred rdf:type assertion in the element name
+ if (element.namespaceURI != NS_RDF ||
+ element.localName != "Description") {
+ if (element.namespaceURI == NS_RDF && element.localName == "li")
+ throw new Error("rdf:li is not a valid type for a subject node");
+ var assertion = new RDFAssertion(this, RDF_R("type"),
+ this._ds.getResource(element.namespaceURI + element.localName));
+ assertion._DOMnode = element;
+ assertion._isSubjectElement = true;
+ this._addAssertion(assertion);
+ }
+
+ // Certain attributes can be literal properties
+ for (let attr of element.attributes) {
+ if (attr.namespaceURI == NS_XML || attr.namespaceURI == NS_XMLNS ||
+ attr.nodeName == "xmlns")
+ continue;
+ if ((attr.namespaceURI == NS_RDF || !attr.namespaceURI) &&
+ (["nodeID", "about", "resource", "ID", "parseType"].includes(attr.localName)))
+ continue;
+ var object = null;
+ if (attr.namespaceURI == NS_RDF) {
+ if (attr.localName == "type")
+ object = this._ds.getResource(attr.nodeValue);
+ else if (attr.localName == "li")
+ throw new Error("rdf:li is not allowed as a property attribute");
+ else if (attr.localName == "aboutEach")
+ throw new Error("rdf:aboutEach is deprecated");
+ else if (attr.localName == "aboutEachPrefix")
+ throw new Error("rdf:aboutEachPrefix is deprecated");
+ else if (attr.localName == "aboutEach")
+ throw new Error("rdf:aboutEach is deprecated");
+ else if (attr.localName == "bagID")
+ throw new Error("rdf:bagID is deprecated");
+ }
+ if (!object)
+ object = new RDFLiteral(attr.nodeValue);
+ assertion = new RDFAssertion(this, attr.namespaceURI + attr.localName, object);
+ assertion._DOMnode = attr;
+ this._addAssertion(assertion);
+ }
+
+ var child = element.firstChild;
+ element.listCounter = 1;
+ while (child) {
+ if (isText(child) && /\S/.test(child.nodeValue)) {
+ ERROR(`Text ${child.nodeValue} is not allowed in a subject node`);
+ throw new Error("subject nodes cannot contain text content");
+ } else if (isElement(child)) {
+ object = null;
+ var predicate = child.namespaceURI + child.localName;
+ if (child.namespaceURI == NS_RDF) {
+ if (RDF_PROPERTY_INVALID_TYPES.includes(child.localName) &&
+ !child.localName.match(/^_\d+$/))
+ throw new Error(`${child.nodeName} is an invalid property`);
+ if (child.localName == "li") {
+ predicate = RDF_R(`_${element.listCounter}`);
+ element.listCounter++;
+ }
+ }
+
+ // Check for and bail out on unknown attributes on the property element
+ for (let attr of child.attributes) {
+ // Ignore XML namespaced attributes
+ if (attr.namespaceURI == NS_XML)
+ continue;
+ // These are reserved by XML for future use
+ if (attr.localName.substring(0, 3).toLowerCase() == "xml")
+ continue;
+ // We can handle these RDF attributes
+ if ((!attr.namespaceURI || attr.namespaceURI == NS_RDF) &&
+ ["resource", "nodeID"].includes(attr.localName))
+ continue;
+ // This is a special attribute we handle for compatibility with Mozilla RDF
+ if (attr.namespaceURI == NS_NC &&
+ attr.localName == "parseType")
+ continue;
+ throw new Error(`Attribute ${attr.nodeName} is not supported`);
+ }
+
+ var parseType = child.getAttributeNS(NS_NC, "parseType");
+ if (parseType && parseType != "Date" && parseType != "Integer") {
+ ERROR(`parseType ${parseType} is not supported`);
+ throw new Error("unsupported parseType");
+ }
+
+ var resource = getRDFAttribute(child, "resource");
+ var nodeID = getRDFAttribute(child, "nodeID");
+ if ((resource && (nodeID || parseType)) ||
+ (nodeID && (resource || parseType))) {
+ ERROR("Cannot use more than one of parseType, resource and nodeID on a single node");
+ throw new Error("Invalid rdf assertion");
+ }
+
+ if (resource !== undefined) {
+ var base = Services.io.newURI(element.baseURI);
+ object = this._ds.getResource(base.resolve(resource));
+ } else if (nodeID !== undefined) {
+ if (!nodeID.match(XML_NCNAME))
+ throw new Error("rdf:nodeID must be a valid XML name");
+ object = this._ds.getBlankNode(nodeID);
+ } else {
+ var hasText = false;
+ var childElement = null;
+ var subchild = child.firstChild;
+ while (subchild) {
+ if (isText(subchild) && /\S/.test(subchild.nodeValue)) {
+ hasText = true;
+ } else if (isElement(subchild)) {
+ if (childElement) {
+ new Error(`Multiple object elements found in ${child.nodeName}`);
+ }
+ childElement = subchild;
+ }
+ subchild = subchild.nextSibling;
+ }
+
+ if ((resource || nodeID) && (hasText || childElement)) {
+ ERROR("Assertion references a resource so should not contain additional contents");
+ throw new Error("assertion cannot contain multiple objects");
+ }
+
+ if (hasText && childElement) {
+ ERROR(`Both literal and resource objects found in ${child.nodeName}`);
+ throw new Error("assertion cannot contain multiple objects");
+ }
+
+ if (childElement) {
+ if (parseType) {
+ ERROR("Cannot specify a parseType for an assertion with resource object");
+ throw new Error("parseType is not valid in this context");
+ }
+ object = this._ds._getSubjectForElement(childElement);
+ object._parseElement(childElement);
+ } else if (parseType == "Integer") {
+ object = new RDFIntLiteral(child.textContent);
+ } else if (parseType == "Date") {
+ object = new RDFDateLiteral(new Date(child.textContent));
+ } else {
+ object = new RDFLiteral(child.textContent);
+ }
+ }
+
+ assertion = new RDFAssertion(this, predicate, object);
+ this._addAssertion(assertion);
+ assertion._DOMnode = child;
+ }
+ child = child.nextSibling;
+ }
+ }
+ /* eslint-enable complexity */
+
+ /**
+ * Adds a new assertion to the internal hashes. Should be called for every
+ * new assertion parsed or created programmatically.
+ */
+ _addAssertion(assertion) {
+ var predicate = assertion.getPredicate();
+ if (predicate in this._assertions)
+ this._assertions[predicate].push(assertion);
+ else
+ this._assertions[predicate] = [ assertion ];
+
+ var object = assertion.getObject();
+ if (object instanceof RDFSubject) {
+ // Create reverse assertion
+ if (predicate in object._backwards)
+ object._backwards[predicate].push(assertion);
+ else
+ object._backwards[predicate] = [ assertion ];
+ }
+ }
+
+ /**
+ * Removes an assertion from the internal hashes. Should be called for all
+ * assertions that are programmatically deleted.
+ */
+ _removeAssertion(assertion) {
+ var predicate = assertion.getPredicate();
+ if (predicate in this._assertions) {
+ var pos = this._assertions[predicate].indexOf(assertion);
+ if (pos >= 0)
+ this._assertions[predicate].splice(pos, 1);
+ if (this._assertions[predicate].length == 0)
+ delete this._assertions[predicate];
+ }
+
+ var object = assertion.getObject();
+ if (object instanceof RDFSubject) {
+ // Delete reverse assertion
+ if (predicate in object._backwards) {
+ pos = object._backwards[predicate].indexOf(assertion);
+ if (pos >= 0)
+ object._backwards[predicate].splice(pos, 1);
+ if (object._backwards[predicate].length == 0)
+ delete object._backwards[predicate];
+ }
+ }
+ }
+
+ /**
+ * Returns the ordinal assertions from this subject in order.
+ */
+ _getChildAssertions() {
+ var assertions = [];
+ for (var i in this._assertions) {
+ if (RDF_LISTITEM.test(i))
+ assertions.push(...this._assertions[i]);
+ }
+ assertions.sort(predicateOrder);
+ return assertions;
+ }
+
+ /**
+ * Compares this to another rdf node
+ */
+ equals(rdfnode) {
+ // subjects are created by the datasource so no two objects ever correspond
+ // to the same one.
+ return this === rdfnode;
+ }
+
+ /**
+ * Adds a new assertion with this as the subject
+ */
+ assert(predicate, object) {
+ if (predicate == RDF_R("type") && !(object instanceof RDFResource))
+ throw new Error("rdf:type must be an RDFResource");
+
+ var assertion = new RDFAssertion(this, predicate, object);
+ this._createDOMNodeForAssertion(assertion);
+ this._addAssertion(assertion);
+ }
+
+ /**
+ * Removes an assertion matching the predicate and node given, if such an
+ * assertion exists.
+ */
+ unassert(predicate, object) {
+ if (!(predicate in this._assertions))
+ return;
+
+ for (let assertion of this._assertions[predicate]) {
+ if (assertion.getObject().equals(object)) {
+ this._removeAssertion(assertion);
+ this._removeDOMNodeForAssertion(assertion);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Returns an array of all the predicates that exist in assertions from this
+ * subject.
+ */
+ getPredicates() {
+ return Object.keys(this._assertions);
+ }
+
+ /**
+ * Returns all objects in assertions with this subject and the given predicate.
+ */
+ getObjects(predicate) {
+ if (predicate in this._assertions)
+ return Array.from(this._assertions[predicate],
+ i => i.getObject());
+
+ return [];
+ }
+
+ /**
+ * Returns all of the ordinal children of this subject in order.
+ */
+ getChildren() {
+ return Array.from(this._getChildAssertions(),
+ i => i.getObject());
+ }
+
+ /**
+ * Removes the child at the given index. This is the index based on the
+ * children returned from getChildren. Forces a reordering of the later
+ * children.
+ */
+ removeChildAt(pos) {
+ if (pos < 0)
+ throw new Error("no such child");
+ var assertions = this._getChildAssertions();
+ if (pos >= assertions.length)
+ throw new Error("no such child");
+ for (var i = pos; i < assertions.length; i++) {
+ this._removeAssertion(assertions[i]);
+ this._removeDOMNodeForAssertion(assertions[i]);
+ }
+ var index = 1;
+ if (pos > 0)
+ index = parseInt(assertions[pos - 1].getPredicate().substring(NS_RDF.length + 1)) + 1;
+ for (let i = pos + 1; i < assertions.length; i++) {
+ assertions[i]._predicate = RDF_R(`_${index}`);
+ this._addAssertion(assertions[i]);
+ this._createDOMNodeForAssertion(assertions[i]);
+ index++;
+ }
+ }
+
+ /**
+ * Removes the child with the given object. It is unspecified which child is
+ * removed if the object features more than once.
+ */
+ removeChild(object) {
+ var assertions = this._getChildAssertions();
+ for (var pos = 0; pos < assertions.length; pos++) {
+ if (assertions[pos].getObject().equals(object)) {
+ for (var i = pos; i < assertions.length; i++) {
+ this._removeAssertion(assertions[i]);
+ this._removeDOMNodeForAssertion(assertions[i]);
+ }
+ var index = 1;
+ if (pos > 0)
+ index = parseInt(assertions[pos - 1].getPredicate().substring(NS_RDF.length + 1)) + 1;
+ for (let i = pos + 1; i < assertions.length; i++) {
+ assertions[i]._predicate = RDF_R(`_${index}`);
+ this._addAssertion(assertions[i]);
+ this._createDOMNodeForAssertion(assertions[i]);
+ index++;
+ }
+ return;
+ }
+ }
+ throw new Error("no such child");
+ }
+
+ /**
+ * Adds a new ordinal child to this subject.
+ */
+ addChild(object) {
+ var max = 0;
+ for (var i in this._assertions) {
+ if (RDF_LISTITEM.test(i))
+ max = Math.max(max, parseInt(i.substring(NS_RDF.length + 1)));
+ }
+ max++;
+ this.assert(RDF_R(`_${max}`), object);
+ }
+
+ /**
+ * This reorders the child assertions to remove duplicates and gaps in the
+ * sequence. Generally this will move all children to be under the same
+ * container element and all represented as an rdf:li
+ */
+ reorderChildren() {
+ var assertions = this._getChildAssertions();
+ for (let assertion of assertions) {
+ this._removeAssertion(assertion);
+ this._removeDOMNodeForAssertion(assertion);
+ }
+ var index = 1;
+ for (let assertion of assertions) {
+ assertion._predicate = RDF_R(`_${index}`);
+ this._addAssertion(assertion);
+ this._createDOMNodeForAssertion(assertion);
+ index++;
+ }
+ }
+
+ /**
+ * Returns the type of this subject or null if there is no specified type.
+ */
+ getType() {
+ var type = this.getProperty(RDF_R("type"));
+ if (type && type instanceof RDFResource)
+ return type.getURI();
+ return null;
+ }
+
+ /**
+ * Tests if a property exists for the given predicate.
+ */
+ hasProperty(predicate) {
+ return (predicate in this._assertions);
+ }
+
+ /**
+ * Retrieves the first property value for the given predicate.
+ */
+ getProperty(predicate) {
+ if (predicate in this._assertions)
+ return this._assertions[predicate][0].getObject();
+ return null;
+ }
+
+ /**
+ * Sets the property value for the given predicate, clearing any existing
+ * values.
+ */
+ setProperty(predicate, object) {
+ // TODO optimise by replacing the first assertion and clearing the rest
+ this.clearProperty(predicate);
+ this.assert(predicate, object);
+ }
+
+ /**
+ * Clears any existing properties for the given predicate.
+ */
+ clearProperty(predicate) {
+ if (!(predicate in this._assertions))
+ return;
+
+ var assertions = this._assertions[predicate];
+ while (assertions.length > 0) {
+ var assertion = assertions[0];
+ this._removeAssertion(assertion);
+ this._removeDOMNodeForAssertion(assertion);
+ }
+ }
+}
+
+/**
+ * Creates a new RDFResource for the datasource. Private.
+ */
+class RDFResource extends RDFSubject {
+ constructor(ds, uri) {
+ if (!(ds instanceof RDFDataSource))
+ throw new Error("datasource must be an RDFDataSource");
+
+ if (!uri)
+ throw new Error("An RDFResource requires a non-null uri");
+
+ super(ds);
+ // This is the uri that the resource represents.
+ this._uri = uri;
+ }
+
+ /**
+ * Sets attributes on the DOM element to mark it as representing this resource
+ */
+ _applyToElement(element) {
+ if (USE_RDFNS_ATTR) {
+ var prefix = this._ds._resolvePrefix(element, RDF_R("about"));
+ element.setAttributeNS(prefix.namespaceURI, prefix.qname, this._uri);
+ } else {
+ element.setAttribute("about", this._uri);
+ }
+ }
+
+ /**
+ * Adds a reference to this resource to the given property Element.
+ */
+ _addReferenceToElement(element) {
+ if (USE_RDFNS_ATTR) {
+ var prefix = this._ds._resolvePrefix(element, RDF_R("resource"));
+ element.setAttributeNS(prefix.namespaceURI, prefix.qname, this._uri);
+ } else {
+ element.setAttribute("resource", this._uri);
+ }
+ }
+
+ /**
+ * Removes any reference to this resource from the given property Element.
+ */
+ _removeReferenceFromElement(element) {
+ if (element.hasAttributeNS(NS_RDF, "resource"))
+ element.removeAttributeNS(NS_RDF, "resource");
+ if (element.hasAttribute("resource"))
+ element.removeAttribute("resource");
+ }
+
+ getURI() {
+ return this._uri;
+ }
+}
+
+/**
+ * Creates a new blank node. Private.
+ */
+class RDFBlankNode extends RDFSubject {
+ constructor(ds, nodeID) {
+ if (!(ds instanceof RDFDataSource))
+ throw new Error("datasource must be an RDFDataSource");
+
+ super(ds);
+ // The nodeID of this node. May be null if there is no ID.
+ this._nodeID = nodeID;
+ }
+
+ /**
+ * Sets attributes on the DOM element to mark it as representing this node
+ */
+ _applyToElement(element) {
+ if (!this._nodeID)
+ return;
+ if (USE_RDFNS_ATTR) {
+ var prefix = this._ds._resolvePrefix(element, RDF_R("nodeID"));
+ element.setAttributeNS(prefix.namespaceURI, prefix.qname, this._nodeID);
+ } else {
+ element.setAttribute("nodeID", this._nodeID);
+ }
+ }
+
+ /**
+ * Creates a new Element in the document for holding assertions about this
+ * subject. The URI controls what tagname to use.
+ */
+ _createNewElement(uri) {
+ // If there are already nodes representing this in the document then we need
+ // a nodeID to match them
+ if (!this._nodeID && this._elements.length > 0) {
+ this._ds._createNodeID(this);
+ for (let element of this._elements)
+ this._applyToElement(element);
+ }
+
+ return super._createNewElement.call(uri);
+ }
+
+ /**
+ * Adds a reference to this node to the given property Element.
+ */
+ _addReferenceToElement(element) {
+ if (this._elements.length > 0 && !this._nodeID) {
+ // In document elsewhere already
+ // Create a node ID and update the other nodes referencing
+ this._ds._createNodeID(this);
+ for (let element of this._elements)
+ this._applyToElement(element);
+ }
+
+ if (this._nodeID) {
+ if (USE_RDFNS_ATTR) {
+ let prefix = this._ds._resolvePrefix(element, RDF_R("nodeID"));
+ element.setAttributeNS(prefix.namespaceURI, prefix.qname, this._nodeID);
+ } else {
+ element.setAttribute("nodeID", this._nodeID);
+ }
+ } else {
+ // Add the empty blank node, this is generally right since further
+ // assertions will be added to fill this out
+ var newelement = this._ds._addElement(element, RDF_R("Description"));
+ newelement.listCounter = 1;
+ this._elements.push(newelement);
+ }
+ }
+
+ /**
+ * Removes any reference to this node from the given property Element.
+ */
+ _removeReferenceFromElement(element) {
+ if (element.hasAttributeNS(NS_RDF, "nodeID"))
+ element.removeAttributeNS(NS_RDF, "nodeID");
+ if (element.hasAttribute("nodeID"))
+ element.removeAttribute("nodeID");
+ }
+
+ getNodeID() {
+ return this._nodeID;
+ }
+}
+
+/**
+ * Creates a new RDFDataSource from the given document. The document will be
+ * changed as assertions are added and removed to the RDF. Pass a null document
+ * to start with an empty graph.
+ */
+class RDFDataSource {
+ constructor(document) {
+ // All known resources, indexed on URI
+ this._resources = {};
+ // All blank nodes
+ this._allBlankNodes = [];
+ // All blank nodes with IDs, indexed on ID
+ this._blankNodes = {};
+ // Suggested prefixes to use for namespaces, index is prefix, value is namespaceURI.
+ this._prefixes = {
+ rdf: NS_RDF,
+ NC: NS_NC,
+ };
+
+ if (!document) {
+ // Creating a document through xpcom leaves out the xml prolog so just parse
+ // something small
+ var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
+ createInstance(Ci.nsIDOMParser);
+ var doctext = `<?xml version="1.0"?>\n<rdf:RDF xmlns:rdf="${NS_RDF}"/>\n`;
+ document = parser.parseFromString(doctext, "text/xml");
+ }
+ // The underlying DOM document for this datasource
+ this._document = document;
+ this._parseDocument();
+ }
+
+ static loadFromString(text) {
+ let parser = new DOMParser();
+ let document = parser.parseFromString(text, "application/xml");
+
+ return new this(document);
+ }
+
+ static loadFromBuffer(buffer) {
+ let parser = new DOMParser();
+ let document = parser.parseFromBuffer(new Uint8Array(buffer), "application/xml");
+
+ return new this(document);
+ }
+
+ static async loadFromFile(uri) {
+ if (uri instanceof Ci.nsIFile)
+ uri = Services.io.newFileURI(uri);
+ else if (typeof(uri) == "string")
+ uri = Services.io.newURI(uri);
+
+ let resp = await fetch(uri.spec);
+ return this.loadFromBuffer(await resp.arrayBuffer());
+ }
+
+ get uri() {
+ return this._document.documentURI;
+ }
+
+ /**
+ * Creates a new nodeID for an unnamed blank node. Just node<number>.
+ */
+ _createNodeID(blanknode) {
+ var i = 1;
+ while (`node${i}` in this._blankNodes)
+ i++;
+ blanknode._nodeID = `node${i}`;
+ this._blankNodes[blanknode._nodeID] = blanknode;
+ }
+
+ /**
+ * Returns an rdf subject for the given DOM Element. If the subject has not
+ * been seen before a new one is created.
+ */
+ _getSubjectForElement(element) {
+ if (element.namespaceURI == NS_RDF &&
+ RDF_NODE_INVALID_TYPES.includes(element.localName))
+ throw new Error(`${element.nodeName} is not a valid class for a subject node`);
+
+ var about = getRDFAttribute(element, "about");
+ var id = getRDFAttribute(element, "ID");
+ var nodeID = getRDFAttribute(element, "nodeID");
+
+ if ((about && (id || nodeID)) ||
+ (nodeID && (id || about))) {
+ ERROR("More than one of about, ID and nodeID present on the same subject");
+ throw new Error("invalid subject in rdf");
+ }
+
+ if (about !== undefined) {
+ let base = Services.io.newURI(element.baseURI);
+ return this.getResource(base.resolve(about));
+ }
+ if (id !== undefined) {
+ if (!id.match(XML_NCNAME))
+ throw new Error("rdf:ID must be a valid XML name");
+ let base = Services.io.newURI(element.baseURI);
+ return this.getResource(base.resolve(`#${id}`));
+ }
+ if (nodeID !== undefined)
+ return this.getBlankNode(nodeID);
+ return this.getBlankNode(null);
+ }
+
+ /**
+ * Parses the document for subjects at the top level.
+ */
+ _parseDocument() {
+ if (!this._document.documentElement) {
+ ERROR("No document element in document");
+ throw new Error("document contains no root element");
+ }
+
+ if (this._document.documentElement.namespaceURI != NS_RDF ||
+ this._document.documentElement.localName != "RDF") {
+ ERROR(`${this._document.documentElement.nodeName} is not rdf:RDF`);
+ throw new Error("document does not appear to be RDF");
+ }
+
+ var domnode = this._document.documentElement.firstChild;
+ while (domnode) {
+ if (isText(domnode) && /\S/.test(domnode.nodeValue)) {
+ ERROR("RDF does not allow for text in the root of the document");
+ throw new Error("invalid markup in document");
+ } else if (isElement(domnode)) {
+ var subject = this._getSubjectForElement(domnode);
+ subject._parseElement(domnode);
+ }
+ domnode = domnode.nextSibling;
+ }
+ }
+
+ /**
+ * Works out a sensible namespace prefix to use for the given uri. node should
+ * be the parent of where the element is to be inserted, or the node that an
+ * attribute is to be added to. This will recursively walk to the top of the
+ * document finding an already registered prefix that matches for the uri.
+ * If none is found a new prefix is registered.
+ * This returns an object with keys namespaceURI, prefix, localName and qname.
+ * Pass null or undefined for badPrefixes for the first call.
+ */
+ _resolvePrefix(domnode, uri, badPrefixes) {
+ if (!badPrefixes)
+ badPrefixes = [];
+
+ // No known prefix, try to create one from the lookup list
+ if (!domnode || isDocument(domnode)) {
+ for (let i in this._prefixes) {
+ if (badPrefixes.includes(i))
+ continue;
+ if (this._prefixes[i] == uri.substring(0, this._prefixes[i].length)) {
+ var local = uri.substring(this._prefixes[i].length);
+ var test = URI_SUFFIX.exec(local);
+ // Remaining part of uri is a good XML Name
+ if (test && test[0] == local) {
+ this._document.documentElement.setAttributeNS(NS_XMLNS, `xmlns:${i}`, this._prefixes[i]);
+ return {
+ namespaceURI: this._prefixes[i],
+ prefix: i,
+ localName: local,
+ qname: i ? `${i}:${local}` : local,
+ };
+ }
+ }
+ }
+
+ // No match, make something up
+ test = URI_SUFFIX.exec(uri);
+ if (test) {
+ var namespaceURI = uri.substring(0, uri.length - test[0].length);
+ local = test[0];
+ let i = 1;
+ while (badPrefixes.includes(`NS${i}`))
+ i++;
+ this._document.documentElement.setAttributeNS(NS_XMLNS, `xmlns:NS${i}`, namespaceURI);
+ return {
+ namespaceURI,
+ prefix: `NS${i}`,
+ localName: local,
+ qname: `NS${i}:${local}`,
+ };
+ }
+ // There is no end part of this URI that is an XML Name
+ throw new Error(`invalid node name: ${uri}`);
+ }
+
+ for (let attr of domnode.attributes) {
+ // Not a namespace declaration, ignore this attribute
+ if (attr.namespaceURI != NS_XMLNS && attr.nodeName != "xmlns")
+ continue;
+
+ var prefix = attr.prefix ? attr.localName : "";
+ // Seen this prefix before, cannot use it
+ if (badPrefixes.includes(prefix))
+ continue;
+
+ // Namespace matches the start of the uri
+ if (attr.value == uri.substring(0, attr.value.length)) {
+ local = uri.substring(attr.value.length);
+ test = URI_SUFFIX.exec(local);
+ // Remaining part of uri is a good XML Name
+ if (test && test[0] == local) {
+ return {
+ namespaceURI: attr.value,
+ prefix,
+ localName: local,
+ qname: prefix ? `${prefix}:${local}` : local,
+ };
+ }
+ }
+
+ badPrefixes.push(prefix);
+ }
+
+ // No prefix found here, move up the document
+ return this._resolvePrefix(domnode.parentNode, uri, badPrefixes);
+ }
+
+ /**
+ * Guess the indent level within the given Element. The method looks for
+ * elements that are preceded by whitespace including a newline. The
+ * whitespace following the newline is presumed to be the indentation for the
+ * element.
+ * If the indentation cannot be guessed then it recurses up the document
+ * hierarchy until it can guess the indent or until the Document is reached.
+ */
+ _guessIndent(element) {
+ // The indent at document level is 0
+ if (!element || isDocument(element))
+ return "";
+
+ // Check the text immediately preceding each child node. One could be
+ // a valid indent
+ var pretext = "";
+ var child = element.firstChild;
+ while (child) {
+ if (isText(child)) {
+ pretext += child.nodeValue;
+ } else if (isElement(child)) {
+ var result = INDENT.exec(pretext);
+ if (result)
+ return result[1];
+ pretext = "";
+ }
+ child = child.nextSibling;
+ }
+
+ // pretext now contains any trailing text in the element. This can be
+ // the indent of the end tag. If so add a little to it.
+ result = INDENT.exec(pretext);
+ if (result)
+ return `${result[1]} `;
+
+ // Check the text immediately before this node
+ pretext = "";
+ var sibling = element.previousSibling;
+ while (sibling && isText(sibling)) {
+ pretext += sibling.nodeValue;
+ sibling = sibling.previousSibling;
+ }
+
+ // If there is a sensible indent then just add to it.
+ result = INDENT.exec(pretext);
+ if (result)
+ return `${result[1]} `;
+
+ // Last chance, get the indent level for the tag above and add to it
+ return `${this._guessIndent(element.parentNode)} `;
+ }
+
+ _addElement(parent, uri) {
+ var prefix = this._resolvePrefix(parent, uri);
+ var element = this._document.createElementNS(prefix.namespaceURI, prefix.qname);
+
+ if (parent.lastChild) {
+ // We want to insert immediately after the last child element
+ var last = parent.lastChild;
+ while (last && isText(last))
+ last = last.previousSibling;
+ // No child elements so insert at the start
+ if (!last)
+ last = parent.firstChild;
+ else
+ last = last.nextSibling;
+
+ let indent = this._guessIndent(parent);
+ parent.insertBefore(this._document.createTextNode(`\n${indent}`), last);
+ parent.insertBefore(element, last);
+ } else {
+ // No children, must indent our element and the end tag
+ let indent = this._guessIndent(parent.parentNode);
+ parent.append(`\n${indent} `, element, `\n${indent}`);
+ }
+ return element;
+ }
+
+ /**
+ * Removes the element from its parent. Should also remove surrounding
+ * white space as appropriate.
+ */
+ _removeElement(element) {
+ var parent = element.parentNode;
+ var sibling = element.previousSibling;
+ // Drop any text nodes immediately preceding the element
+ while (sibling && isText(sibling)) {
+ var temp = sibling;
+ sibling = sibling.previousSibling;
+ parent.removeChild(temp);
+ }
+
+ sibling = element.nextSibling;
+ // Drop the element
+ parent.removeChild(element);
+
+ // If the next node after element is now the first child then element was
+ // the first child. If there are no other child elements then remove the
+ // remaining child nodes.
+ if (parent.firstChild == sibling) {
+ while (sibling && isText(sibling))
+ sibling = sibling.nextSibling;
+ if (!sibling) {
+ // No other child elements
+ while (parent.lastChild)
+ parent.removeChild(parent.lastChild);
+ }
+ }
+ }
+
+ /**
+ * Requests that a given prefix be used for the namespace where possible.
+ * This must be called before any assertions are made using the namespace
+ * and the registration will not override any existing prefix used in the
+ * document.
+ */
+ registerPrefix(prefix, namespaceURI) {
+ this._prefixes[prefix] = namespaceURI;
+ }
+
+ /**
+ * Gets a blank node. nodeID may be null and if so a new blank node is created.
+ * If a nodeID is given then the blank node with that ID is returned or created.
+ */
+ getBlankNode(nodeID) {
+ if (nodeID && nodeID in this._blankNodes)
+ return this._blankNodes[nodeID];
+
+ if (nodeID && !nodeID.match(XML_NCNAME))
+ throw new Error("rdf:nodeID must be a valid XML name");
+
+ var rdfnode = new RDFBlankNode(this, nodeID);
+ this._allBlankNodes.push(rdfnode);
+ if (nodeID)
+ this._blankNodes[nodeID] = rdfnode;
+ return rdfnode;
+ }
+
+ /**
+ * Gets all blank nodes
+ */
+ getAllBlankNodes() {
+ return this._allBlankNodes.slice();
+ }
+
+ /**
+ * Gets the resource for the URI. The resource is created if it has not been
+ * used already.
+ */
+ getResource(uri) {
+ if (uri in this._resources)
+ return this._resources[uri];
+
+ var resource = new RDFResource(this, uri);
+ this._resources[uri] = resource;
+ return resource;
+ }
+
+ /**
+ * Gets all resources that have been used.
+ */
+ getAllResources() {
+ return Object.values(this._resources);
+ }
+
+ /**
+ * Returns all blank nodes and resources
+ */
+ getAllSubjects() {
+ return [...Object.values(this._resources),
+ ...this._allBlankNodes];
+ }
+
+ /**
+ * Saves the RDF/XML to a string.
+ */
+ serializeToString() {
+ var serializer = new XMLSerializer();
+ return serializer.serializeToString(this._document);
+ }
+
+ /**
+ * Saves the RDF/XML to a file.
+ */
+ async saveToFile(file) {
+ return OS.File.writeAtomic(file, new TextEncoder().encode(this.serializeToString()));
+ }
+}
diff --git a/legacy/RDFManifestConverter.jsm b/legacy/RDFManifestConverter.jsm
new file mode 100644
index 0000000..6eda163
--- /dev/null
+++ b/legacy/RDFManifestConverter.jsm
@@ -0,0 +1,110 @@
+ /* 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";
+
+var EXPORTED_SYMBOLS = ["InstallRDF"];
+
+ChromeUtils.defineModuleGetter(this, "RDFDataSource",
+ "chrome://legacy/content/RDFDataSource.jsm");
+
+const RDFURI_INSTALL_MANIFEST_ROOT = "urn:mozilla:install-manifest";
+
+function EM_R(aProperty) {
+ return `http://www.mozilla.org/2004/em-rdf#${aProperty}`;
+}
+
+function getValue(literal) {
+ return literal && literal.getValue();
+}
+
+function getProperty(resource, property) {
+ return getValue(resource.getProperty(EM_R(property)));
+}
+
+class Manifest {
+ constructor(ds) {
+ this.ds = ds;
+ }
+
+ static loadFromString(text) {
+ return new this(RDFDataSource.loadFromString(text));
+ }
+
+ static loadFromBuffer(buffer) {
+ return new this(RDFDataSource.loadFromBuffer(buffer));
+ }
+
+ static async loadFromFile(uri) {
+ return new this(await RDFDataSource.loadFromFile(uri));
+ }
+}
+
+class InstallRDF extends Manifest {
+ _readProps(source, obj, props) {
+ for (let prop of props) {
+ let val = getProperty(source, prop);
+ if (val != null) {
+ obj[prop] = val;
+ }
+ }
+ }
+
+ _readArrayProp(source, obj, prop, target, decode = getValue) {
+ let result = Array.from(source.getObjects(EM_R(prop)),
+ target => decode(target));
+ if (result.length) {
+ obj[target] = result;
+ }
+ }
+
+ _readArrayProps(source, obj, props, decode = getValue) {
+ for (let [prop, target] of Object.entries(props)) {
+ this._readArrayProp(source, obj, prop, target, decode);
+ }
+ }
+
+ _readLocaleStrings(source, obj) {
+ this._readProps(source, obj, ["name", "description", "creator", "homepageURL"]);
+ this._readArrayProps(source, obj, {
+ locale: "locales",
+ developer: "developers",
+ translator: "translators",
+ contributor: "contributors",
+ });
+ }
+
+ decode() {
+ let root = this.ds.getResource(RDFURI_INSTALL_MANIFEST_ROOT);
+ let result = {};
+
+ let props = ["id", "version", "type", "updateURL", "optionsURL",
+ "optionsType", "aboutURL", "iconURL",
+ "bootstrap", "unpack", "strictCompatibility"];
+ this._readProps(root, result, props);
+
+ let decodeTargetApplication = source => {
+ let app = {};
+ this._readProps(source, app, ["id", "minVersion", "maxVersion"]);
+ return app;
+ };
+
+ let decodeLocale = source => {
+ let localized = {};
+ this._readLocaleStrings(source, localized);
+ return localized;
+ };
+
+ this._readLocaleStrings(root, result);
+
+ this._readArrayProps(root, result, {"targetPlatform": "targetPlatforms"});
+ this._readArrayProps(root, result, {"targetApplication": "targetApplications"},
+ decodeTargetApplication);
+ this._readArrayProps(root, result, {"localized": "localized"},
+ decodeLocale);
+ this._readArrayProps(root, result, {"dependency": "dependencies"},
+ source => getProperty(source, "id"));
+
+ return result;
+ }
+}
diff --git a/legacy/boot.jsm b/legacy/boot.jsm
new file mode 100644
index 0000000..f1cae49
--- /dev/null
+++ b/legacy/boot.jsm
@@ -0,0 +1,7 @@
+let EXPORTED_SYMBOLS = [];
+
+const {AddonManager} = ChromeUtils.import('resource://gre/modules/AddonManager.jsm');
+if (AddonManager.addExternalExtensionLoader) {
+ const {BootstrapLoader} = ChromeUtils.import('chrome://legacy/content/BootstrapLoader.jsm');
+ AddonManager.addExternalExtensionLoader(BootstrapLoader);
+}