Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,63 @@
define(["exports"], function (exports) {
"use strict";
/*global MainController*/
/*global TouchForwarderController*/
var BLOCKED_APPS = ["app://keyboard.gaiamobile.org/manifest.webapp"];
var SYSTEM_APP = "app://system.gaiamobile.org/manifest.webapp";
// If injecting into an app that was already running at the time
// the Customizer was enabled, simply initialize it.
if (document.documentElement) {
initialize();
}
// Otherwise, we need to wait for the DOM to be ready before
// starting initialization since add-ons are usually (always?)
// injected *before* `document.documentElement` is defined.
else {
window.addEventListener("DOMContentLoaded", initialize);
}
function initialize() {
if (document.documentElement.dataset.customizerInit) {
console.log("[Customizer] Customizer already initialized; Aborting");
return;
}
var request = navigator.mozApps.getSelf();
request.onsuccess = function () {
var manifestURL = request.result && request.result.manifestURL;
if (BLOCKED_APPS.find(function (a) {
return a === manifestURL;
})) {
console.log("[Customizer] BLOCKING injection into " + manifestURL);
return;
}
console.log("[Customizer] Injecting into " + manifestURL);
boot(manifestURL);
};
request.onerror = function () {
console.error("[Customizer] An error occurred getting the manifestURL");
};
function boot(manifestURL) {
window.__customizer__ = {};
document.documentElement.dataset.customizerInit = true;
if (manifestURL === SYSTEM_APP) {
window.__customizer__.touchForwarderController = new TouchForwarderController();
return;
}
window.__customizer__.mainController = new MainController({
manifestURL: manifestURL,
lazyLoadModules: true
});
}
}
});

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,51 @@
define(["exports"], function (exports) {
"use strict";
var _extends = function (child, parent) {
child.prototype = Object.create(parent.prototype, {
constructor: {
value: child,
enumerable: false,
writable: true,
configurable: true
}
});
child.__proto__ = parent;
};
var AppendChildController = (function (Controller) {
var AppendChildController = function AppendChildController(options) {
Controller.call(this, options);
};
_extends(AppendChildController, Controller);
AppendChildController.prototype.teardown = function () {
this.view = null;
this.target = null;
};
AppendChildController.prototype.open = function (target) {
this.target = target;
this.view.open();
};
AppendChildController.prototype.submit = function (tagName) {
var child = document.createElement(tagName);
if (!child) {
window.alert("Error creating " + tagName);
return;
}
AddonService.generate(this.target, function (generator) {
generator.opAppendChild(tagName);
});
};
return AppendChildController;
})(Controller);
exports["default"] = AppendChildController;
});

View file

@ -0,0 +1,100 @@
define(["exports"], function (exports) {
"use strict";
var _extends = function (child, parent) {
child.prototype = Object.create(parent.prototype, {
constructor: {
value: child,
enumerable: false,
writable: true,
configurable: true
}
});
child.__proto__ = parent;
};
var CopyMoveController = (function (Controller) {
var CopyMoveController = function CopyMoveController(options) {
Controller.call(this, options);
};
_extends(CopyMoveController, Controller);
CopyMoveController.prototype.teardown = function () {
this.view = null;
this.target = null;
};
CopyMoveController.prototype.open = function (target) {
this.target = target;
this.view.domTree.filter = "#" + window.__customizer__.mainController.view.el.id;
this.view.domTree.setRoot(document.documentElement);
this.view.domTree.render();
this.view.modal.open();
};
CopyMoveController.prototype.cancel = function () {
this.view.modal.close();
};
CopyMoveController.prototype.select = function () {
this.destination = this.view.domTree.selectedNode;
this.view.dialog.open();
};
CopyMoveController.prototype.setMode = function (mode) {
this.mode = mode;
};
CopyMoveController.prototype.before = function () {
var _this = this;
AddonService.generate(this.target, function (generator) {
var op = generator["op" + _this.mode + "Before"];
op.call(generator, _this.destination);
_this.view.dialog.close();
_this.view.modal.close();
});
};
CopyMoveController.prototype.after = function () {
var _this2 = this;
AddonService.generate(this.target, function (generator) {
var op = generator["op" + _this2.mode + "After"];
op.call(generator, _this2.destination);
_this2.view.dialog.close();
_this2.view.modal.close();
});
};
CopyMoveController.prototype.prepend = function () {
var _this3 = this;
AddonService.generate(this.target, function (generator) {
var op = generator["op" + _this3.mode + "Prepend"];
op.call(generator, _this3.destination);
_this3.view.dialog.close();
_this3.view.modal.close();
});
};
CopyMoveController.prototype.append = function () {
var _this4 = this;
AddonService.generate(this.target, function (generator) {
var op = generator["op" + _this4.mode + "Append"];
op.call(generator, _this4.destination);
_this4.view.dialog.close();
_this4.view.modal.close();
});
};
return CopyMoveController;
})(Controller);
exports["default"] = CopyMoveController;
});

View file

@ -0,0 +1,81 @@
define(["exports"], function (exports) {
"use strict";
var _extends = function (child, parent) {
child.prototype = Object.create(parent.prototype, {
constructor: {
value: child,
enumerable: false,
writable: true,
configurable: true
}
});
child.__proto__ = parent;
};
var EditController = (function (Controller) {
var EditController = function EditController(options) {
Controller.call(this, options);
};
_extends(EditController, Controller);
EditController.prototype.teardown = function () {
this.view = null;
this.target = null;
this.changes = null;
};
EditController.prototype.open = function (target) {
this.target = target;
this.changes = {};
this.view.setTarget(target);
this.view.open();
};
EditController.prototype.close = function () {
if (this.changes.innerHTML || this.changes.script || this.changes.createAttributes || this.changes.removeAttributes || this.changes.properties) {
if (window.confirm("Are you sure you want to discard your changes?")) {
this.view.close();
}
} else {
this.view.close();
}
};
EditController.prototype.save = function () {
var _this = this;
AddonService.generate(this.target, function (generator) {
if (_this.changes.innerHTML) {
generator.opInnerHTML(_this.changes.innerHTML);
}
if (_this.changes.script) {
generator.opScript(_this.changes.script);
}
if (_this.changes.createAttributes) {
generator.opCreateAttributes(_this.changes.createAttributes);
}
if (_this.changes.removeAttributes) {
generator.opRemoveAttributes(_this.changes.removeAttributes);
}
if (_this.changes.properties) {
generator.opSetProperties(_this.changes.properties);
}
_this.changes = {};
_this.close();
});
};
return EditController;
})(Controller);
exports["default"] = EditController;
});

View file

@ -0,0 +1,229 @@
define(["exports"], function (exports) {
"use strict";
var _extends = function (child, parent) {
child.prototype = Object.create(parent.prototype, {
constructor: {
value: child,
enumerable: false,
writable: true,
configurable: true
}
});
child.__proto__ = parent;
};
var MainController = (function (Controller) {
var MainController = function MainController(options) {
Controller.call(this, options);
this._waitToBeOpened();
this._visibilitychangeHandler = this._visibilitychangeHandler.bind(this);
window.addEventListener("visibilitychange", this._visibilitychangeHandler);
// Wait 1 second before checking with Customizer Launcher to
// prevent the Customizer DOM from getting wiped out by the
// app initialization.
setTimeout(this._checkOpenFromLauncher.bind(this), 1000);
console.log("[Customizer] Initialized MainController", this);
};
_extends(MainController, Controller);
MainController.prototype.teardown = function () {
window.removeEventListener("visibilitychange", this._visibilitychangeHandler);
for (var property in this) {
if (this[property] instanceof Controller) {
this[property].teardown();
this[property] = null;
}
}
this.view.customizer.setRootNode(null);
this.view = null;
console.log("[Customizer] Cleaned up controllers and views");
window.__customizer__.mainController = new MainController({
manifestURL: this.manifestURL,
lazyLoadModules: false
});
};
MainController.prototype._lazyLoadModules = function () {
var _this = this;
return new Promise(function (resolve, reject) {
/*jshint evil:true*/
if (_this.lazyLoadModules) {
var source = window.localStorage.getItem("__CUSTOMIZER__componentsSource");
if (!source) {
reject();
return;
}
eval.call(window, source);
console.log("[Customizer] Lazy-loaded modules");
}
resolve();
});
};
MainController.prototype._initControllers = function () {
var _this2 = this;
return new Promise(function (resolve) {
var editView = new EditView();
var viewSourceView = new ViewSourceView();
var appendChildView = new AppendChildView();
var copyMoveView = new CopyMoveView();
var mainView = new MainView({
editView: editView,
viewSourceView: viewSourceView,
appendChildView: appendChildView,
copyMoveView: copyMoveView
});
_this2.editController = new EditController({
view: editView
});
_this2.viewSourceController = new ViewSourceController({
view: viewSourceView
});
_this2.appendChildController = new AppendChildController({
view: appendChildView
});
_this2.copyMoveController = new CopyMoveController({
view: copyMoveView
});
_this2.view = mainView;
mainView.init(_this2);
console.log("[Customizer] Initialized controllers and views");
resolve();
});
};
MainController.prototype._waitToBeOpened = function () {
var _this3 = this;
Gesture.detect({
type: "swipe", // Swipe:
numFingers: 2, // with two fingers,
startRegion: { // from bottom 30% of the screen (10% for SHB),
x0: 0, y0: 0.8, x1: 1, y1: 1.1
},
endRegion: { // up into the top 75% of the screen,
x0: 0, y0: 0, x1: 1, y1: 0.75
},
maxTime: 1000 }).then(function () {
return _this3.open();
});
};
MainController.prototype._waitToBeClosed = function () {
var _this4 = this;
Gesture.detect({
type: "swipe", // Swipe:
numFingers: 2, // with two fingers,
startRegion: { // from the middle ~half of the screen
x0: 0, y0: 0.3, x1: 1, y1: 0.7
},
endRegion: { // down into the bottom quarter of the screen
x0: 0, y0: 0.75, x1: 1, y1: 1
},
maxTime: 1000 }).then(function () {
return _this4.close();
});
};
MainController.prototype._checkOpenFromLauncher = function () {
var _this5 = this;
var requestXHR = new XMLHttpRequest();
try {
requestXHR.open("GET", "http://localhost:3215/request", true);
requestXHR.onload = function () {
if (requestXHR.responseText !== _this5.manifestURL) {
return;
}
_this5.open();
var confirmXHR = new XMLHttpRequest();
confirmXHR.open("GET", "http://localhost:3215/confirm?url=" + _this5.manifestURL, true);
console.log("Sending HTTP request confirmation to Customizer Launcher");
confirmXHR.send();
};
console.log("Sending HTTP request check to Customizer Launcher");
requestXHR.send();
} catch (e) {
console.log("HTTP request failed to Customizer Launcher", e);
}
};
MainController.prototype._visibilitychangeHandler = function () {
if (!document.hidden) {
this._checkOpenFromLauncher();
}
};
MainController.prototype.open = function () {
var _this6 = this;
if (this._isOpen) {
return;
}
this._isOpen = true;
this._lazyLoadModules().then(function () {
return _this6._initControllers();
}).then(function () {
return _this6.view.open();
}).then(function () {
_this6.view.customizer.setRootNode(document.documentElement);
_this6._waitToBeClosed();
});
};
MainController.prototype.close = function () {
var _this7 = this;
if (!this._isOpen) {
return;
}
this.view.close().then(function () {
return _this7.teardown();
});
this._isOpen = false;
};
MainController.prototype.openAddonManager = function () {
var activity = new MozActivity({
name: "configure",
data: {
target: "device",
section: "addons",
options: {
manifestURL: this.manifestURL
}
}
});
activity.onerror = function (e) {
console.error("Error opening \"Settings > Add-ons\" panel", e);
};
};
return MainController;
})(Controller);
exports["default"] = MainController;
});

View file

@ -0,0 +1,109 @@
define(["exports"], function (exports) {
"use strict";
var _extends = function (child, parent) {
child.prototype = Object.create(parent.prototype, {
constructor: {
value: child,
enumerable: false,
writable: true,
configurable: true
}
});
child.__proto__ = parent;
};
/*global Controller*/
// When injected in the System app, this selector can identify
// the currently-focused <iframe> (app window).
var ACTIVE_WINDOW_SELECTOR = "#windows > .active iframe";
var TouchForwarderController = (function (Controller) {
var TouchForwarderController = function TouchForwarderController(options) {
Controller.call(this, options);
var firstTouchStartEvt = null;
var isForwarding = false;
window.addEventListener("touchstart", function (evt) {
if (evt.touches.length === 1) {
firstTouchStartEvt = evt;
return;
}
if (evt.touches.length !== 2) {
return;
}
var iframe = document.querySelector(ACTIVE_WINDOW_SELECTOR);
iframe.sendTouchEvent.apply(iframe, unsynthesizeEvent(firstTouchStartEvt));
iframe.sendTouchEvent.apply(iframe, unsynthesizeEvent(evt));
isForwarding = true;
});
window.addEventListener("touchmove", function (evt) {
if (!isForwarding) {
return;
}
var iframe = document.querySelector(ACTIVE_WINDOW_SELECTOR);
iframe.sendTouchEvent.apply(iframe, unsynthesizeEvent(evt));
});
window.addEventListener("touchend", function (evt) {
if (!isForwarding) {
return;
}
if (evt.touches.length === 0) {
isForwarding = false;
}
var iframe = document.querySelector(ACTIVE_WINDOW_SELECTOR);
iframe.sendTouchEvent.apply(iframe, unsynthesizeEvent(evt));
});
console.log("[Customizer] Initialized TouchForwarderController", this);
};
_extends(TouchForwarderController, Controller);
return TouchForwarderController;
})(Controller);
exports["default"] = TouchForwarderController;
/**
* Taken from System app:
* https://github.com/mozilla-b2g/gaia/blob/600fd8249960b8256af9de67d9171025bb9a3ff3/apps/system/js/touch_forwarder.js#L93
*/
function unsynthesizeEvent(e) {
var type = e.type;
var relevantTouches = (e.type === "touchend") ? e.changedTouches : e.touches;
var identifiers = [];
var xs = [];
var ys = [];
var rxs = [];
var rys = [];
var rs = [];
var fs = [];
var modifiers = 0;
for (var i = 0; i < relevantTouches.length; i++) {
var t = relevantTouches[i];
identifiers.push(t.identifier);
xs.push(t.pageX);
ys.push(t.pageY - 50); // Shift Y position up to remain in-bounds
rxs.push(t.radiusX);
rys.push(t.radiusY);
rs.push(t.rotationAngle);
fs.push(t.force);
}
return [type, identifiers, xs, ys, rxs, rys, rs, fs, xs.length, modifiers];
}
});

View file

@ -0,0 +1,73 @@
define(["exports"], function (exports) {
"use strict";
var _extends = function (child, parent) {
child.prototype = Object.create(parent.prototype, {
constructor: {
value: child,
enumerable: false,
writable: true,
configurable: true
}
});
child.__proto__ = parent;
};
var ViewSourceController = (function (Controller) {
var ViewSourceController = function ViewSourceController(options) {
Controller.call(this, options);
};
_extends(ViewSourceController, Controller);
ViewSourceController.prototype.teardown = function () {
this.view = null;
this.target = null;
};
ViewSourceController.prototype.open = function (target) {
console.log("view source controller open");
this.target = target;
var url = target.src || target.href;
var filename = url.substring(url.lastIndexOf("/") + 1);
this.view.setTitle(filename);
this.view.setSource("Loading...");
this.fetchAndDisplay(url);
this.view.open();
};
ViewSourceController.prototype.close = function () {
this.view.close();
this.view.setSource("");
};
ViewSourceController.prototype.fetchAndDisplay = function (url) {
var self = this;
try {
var xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.responseType = "text";
xhr.send();
xhr.onload = function () {
if (xhr.status === 200) {
self.view.setSource(xhr.response);
} else {
self.view.setSource(xhr.status + ":" + xhr.statusText);
}
};
xhr.onerror = function () {
self.view.setSource(xhr.error.name);
};
} catch (e) {
// Surprisingly, the xhr.send() call above can throw an exception
// if the URL is malformed.
self.view.setSource(e.toString());
}
};
return ViewSourceController;
})(Controller);
exports["default"] = ViewSourceController;
});

View file

@ -0,0 +1,70 @@
define(["exports"], function (exports) {
"use strict";
(function (window) {
"use strict";
var proto = Object.create(HTMLElement.prototype);
var template = "<style scoped>\n .container {\n position: relative;\n width: 100%;\n height: 100%;\n }\n .line-numbers {\n background: #95368c;\n color: #fff;\n font-family: Consolas,Monaco,\"Andale Mono\",monospace;\n font-size: 14px;\n line-height: 1.2em;\n position: absolute;\n padding: 0 2px;\n top: 0;\n left: 0;\n width: 28px;\n height: 100%;\n overflow: hidden;\n }\n textarea {\n background: #000;\n border: none;\n color: #fff;\n font-family: Consolas,Monaco,\"Andale Mono\",monospace;\n font-size: 14px;\n line-height: 1.2em;\n position: absolute;\n top: 0;\n left: 32px;\n width: calc(100% - 32px);\n height: 100%;\n margin: 0;\n padding: 0;\n -moz-user-select: text !important;\n }\n .line-numbers,\n textarea {\n font-family: Consolas,Monaco,\"Andale Mono\",monospace;\n font-size: 14px;\n line-height: 1.2em;\n }\n</style>\n<div class=\"container\">\n <div class=\"line-numbers\">\n <div>1</div>\n </div>\n <textarea autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n inputmode=\"verbatim\"\n x-inputmode=\"verbatim\"\n spellcheck=\"false\"\n wrap=\"off\"></textarea>\n</div>";
proto.createdCallback = function () {
var _this = this;
var value = this.innerHTML;
this.shadow = this.createShadowRoot();
this.shadow.innerHTML = template;
this.lineNumbers = this.shadow.querySelector(".line-numbers");
this.textarea = this.shadow.querySelector("textarea");
this.textarea.value = value;
this.textarea.addEventListener("keyup", function () {
_this.dispatchEvent(new CustomEvent("change"));
updateLineNumbers(_this);
});
this.textarea.addEventListener("scroll", function () {
_this.lineNumbers.scrollTop = _this.textarea.scrollTop;
});
};
Object.defineProperty(proto, "value", {
get: function () {
return this.textarea.value;
},
set: function (value) {
this.textarea.value = value;
updateLineNumbers(this);
}
});
function updateLineNumbers(element) {
var html = "";
if (element.value.length < 5000) {
var lines = element.value.split("\n").length;
if (lines === element.lineNumbers.childElementCount) {
return;
}
for (var i = 1; i <= lines; i++) {
html += "<div>" + i + "</div>";
}
}
element.lineNumbers.innerHTML = html;
element.lineNumbers.scrollTop = element.textarea.scrollTop;
}
try {
document.registerElement("fxos-code-editor", { prototype: proto });
} catch (e) {
if (e.name !== "NotSupportedError") {
throw e;
}
}
})(window);
});

View file

@ -0,0 +1,58 @@
define(["exports"], function (exports) {
"use strict";
(function (window) {
"use strict";
var shadowHTML = "<style scoped>\n.overlay {\n display: none;\n position: absolute;\n box-sizing: border-box;\n pointer-events: none;\n z-index: 9999999; /* above the app, but below the other customizer elements */\n background-color: #00caf2;\n border: 2px dotted #fff;\n outline: 1px solid #00caf2;\n opacity: 0.75;\n}\n.label {\n background-color: rgba(0, 0, 0, 0.5);\n border-bottom-right-radius: 2px;\n color: #fff;\n font-family: 'FiraSans';\n font-size: 10px;\n line-height: 1.2em;\n text-overflow: ellipsis;\n white-space: nowrap;\n display: inline-block;\n position: absolute;\n padding: 0 2px;\n top: 0;\n left: 0;\n max-width: 100%;\n overflow: hidden;\n}\n</style>\n<div class=\"overlay\">\n <div class=\"label\"></div>\n</div>";
var proto = Object.create(HTMLElement.prototype);
proto.createdCallback = function () {
this.shadow = this.createShadowRoot();
this.shadow.innerHTML = shadowHTML;
this.overlay = this.shadow.querySelector(".overlay");
this.label = this.shadow.querySelector(".label");
};
proto.highlight = function (element) {
// Figure out where the element is
var rect = element && element.getBoundingClientRect();
// If the element has zero size, hide the highlight
if (!rect || (rect.width === 0 && rect.height === 0)) {
this.overlay.style.display = "none";
} else {
// Otherwise, highlight the element.
// Note that we use add the document scroll offsets to the element
// coordinates to get document coordinates instead of screen coordinates.
this.overlay.style.left = (rect.left + window.pageXOffset) + "px";
this.overlay.style.top = (rect.top + window.pageYOffset) + "px";
this.overlay.style.width = rect.width + "px";
this.overlay.style.height = rect.height + "px";
this.overlay.style.display = "block";
// Set the label to properly identify the element.
this.label.textContent = element.tagName;
if (element.id) {
this.label.textContent += "#" + element.id;
}
// And try to move the element so that it is on screen
element.scrollIntoView({ behavior: "smooth" });
}
};
proto.hide = function () {
this.overlay.style.display = "none";
};
try {
document.registerElement("fxos-customizer-highlighter", { prototype: proto });
} catch (e) {
if (e.name !== "NotSupportedError") {
throw e;
}
}
})(window);
});

View file

@ -0,0 +1,192 @@
define(["exports"], function (exports) {
"use strict";
(function (window) {
"use strict";
var proto = Object.create(HTMLElement.prototype);
var template = "<style scoped>\n[data-icon]:before {\n font-family: \"gaia-icons\";\n content: attr(data-icon);\n display: inline-block;\n font-weight: 500;\n font-style: normal;\n text-decoration: inherit;\n text-transform: none;\n text-rendering: optimizeLegibility;\n font-size: 30px;\n}\n[data-customizer-icon]:before {\n font-family: \"customizer-icons\";\n content: attr(data-customizer-icon);\n display: inline-block;\n font-weight: 500;\n font-style: normal;\n text-decoration: inherit;\n text-transform: none;\n text-rendering: optimizeLegibility;\n font-size: 30px;\n}\ngaia-dom-tree {\n width: 100%;\n height: calc(100% - 46px);\n}\n.pin {\n position: absolute;\n top: 0;\n right: 0;\n margin: 1rem !important;\n opacity: 1;\n transition: opacity 0.5s ease-in-out;\n}\n.pin.scrolling {\n pointer-events: none;\n opacity: 0;\n}\n</style>\n<gaia-button circular class=\"pin\" data-action=\"settings\">\n <i data-icon=\"settings\"></i>\n</gaia-button>\n<gaia-dom-tree></gaia-dom-tree>\n<gaia-toolbar>\n <button data-customizer-icon=\"edit\" data-action=\"edit\" disabled></button>\n <button data-customizer-icon=\"copy\" data-action=\"copyOrMove\" disabled></button>\n <button data-icon=\"add\" data-action=\"append\" disabled></button>\n <button data-customizer-icon=\"source\" data-action=\"viewSource\" disabled></button>\n <button data-icon=\"delete\" data-action=\"remove\" disabled></button>\n</gaia-toolbar>";
proto.createdCallback = function () {
this.shadow = this.createShadowRoot();
this.shadow.innerHTML = template;
this.settingsButton = this.shadow.querySelector("[data-action=\"settings\"]");
this.gaiaDomTree = this.shadow.querySelector("gaia-dom-tree");
this.gaiaToolbar = this.shadow.querySelector("gaia-toolbar");
this.settingsButton.addEventListener("click", this._handleMenuAction.bind(this));
this.gaiaDomTree.addEventListener("click", this._handleSelected.bind(this));
this.gaiaToolbar.addEventListener("click", this._handleAction.bind(this));
this._watchScrolling();
this.gaiaDomTree.addEventListener("contextmenu", function (evt) {
evt.stopPropagation();
});
this._rootNodeClickHandler = this._handleClick.bind(this);
};
proto.setRootNode = function (rootNode) {
// If we already have a root node defined, disconnect from it first
if (this._root) {
this.unwatchChanges();
this.gaiaDomTree.setRoot(null);
this.gaiaDomTree.render();
this._root.removeEventListener("click", this._rootNodeClickHandler);
this._root = null;
}
if (!rootNode) {
return;
}
// Set up the new root node
this._root = rootNode;
rootNode.addEventListener("click", this._rootNodeClickHandler);
this.gaiaDomTree.setRoot(rootNode);
this.gaiaDomTree.render();
this.watchChanges();
// Pre-expand the root node
var rootTreeNode = this.gaiaDomTree.treeMap.get(rootNode);
if (rootTreeNode) {
this.gaiaDomTree.expandNode(rootTreeNode);
}
var head = rootNode.querySelector("head");
var body = rootNode.querySelector("body");
if (!head || !body) {
return;
}
// Pre-expand the <head> node
var headTreeNode = this.gaiaDomTree.treeMap.get(head);
if (headTreeNode) {
this.gaiaDomTree.expandNode(headTreeNode);
}
// Pre-expand the <body> node
var bodyTreeNode = this.gaiaDomTree.treeMap.get(body);
if (bodyTreeNode) {
this.gaiaDomTree.expandNode(bodyTreeNode);
}
};
proto._watchScrolling = function () {
var _this = this;
this.gaiaDomTree.shadowRoot.addEventListener("scroll", function (evt) {
if (_this._scrollTimeout) {
clearTimeout(_this._scrollTimeout);
}
_this._scrollTimeout = setTimeout(function () {
_this.settingsButton.classList.remove("scrolling");
}, 500);
_this.settingsButton.classList.add("scrolling");
}, true);
};
proto._shadowContains = function (el) {
var customizerRootView = document.body.querySelector(".fxos-customizer-main-view");
if (!el || el == document.documentElement) {
return false;
} else if (el == customizerRootView) {
return true;
}
return this._shadowContains(el.parentNode || el.host);
};
proto.watchChanges = function () {
var _this2 = this;
var OBSERVER_CONFIG = {
childList: true,
attributes: true,
characterData: true,
subtree: true
};
this._observer = new MutationObserver(function (mutations) {
// Only re-render if a mutation occurred in the app itself, and is
// outside of the customizer addon. This depends on the customizer
// root element having the class "fxos-customizer-main-view"
for (var i = mutations.length - 1; i >= 0; i--) {
if (!_this2._shadowContains(mutations[i].target)) {
var selectedNode = _this2.gaiaDomTree.selectedNode;
_this2.gaiaDomTree.render();
_this2.select(selectedNode);
return;
}
}
});
this._observer.observe(this._root, OBSERVER_CONFIG);
};
proto.unwatchChanges = function () {
this._observer.disconnect();
this._observer = null;
};
proto.select = function (node) {
this.gaiaDomTree.select(node);
};
proto._handleMenuAction = function (e) {
var action = e.target.dataset.action;
if (action) {
this.dispatchEvent(new CustomEvent("menu", {
detail: action
}));
}
};
proto._handleSelected = function (e) {
e.stopPropagation();
var selectedNode = this.gaiaDomTree.selectedNode;
if (!selectedNode) {
return;
}
var selected = this._selected = (selectedNode.nodeType === Node.TEXT_NODE) ? selectedNode.parentNode : selectedNode;
[].forEach.call(this.gaiaToolbar.querySelectorAll("button"), function (button) {
button.disabled = !selected;
});
this.gaiaToolbar.querySelector("[data-action=\"viewSource\"]").disabled = (selected.tagName !== "SCRIPT" || !selected.hasAttribute("src")) && (selected.tagName !== "LINK" || !selected.hasAttribute("href"));
this.dispatchEvent(new CustomEvent("selected", {
detail: this._selected
}));
};
proto._handleAction = function (e) {
this.dispatchEvent(new CustomEvent("action:" + e.target.dataset.action, {
detail: this._selected
}));
};
proto._handleClick = function (e) {
if (e.target === this.gaiaDomTree) {
return;
}
this.select(e.target);
};
try {
document.registerElement("fxos-customizer", { prototype: proto });
} catch (e) {
if (e.name !== "NotSupportedError") {
throw e;
}
}
})(window);
});

View file

@ -0,0 +1,422 @@
define(["exports"], function (exports) {
"use strict";
(function (window) {
"use strict";
var HTML_ESCAPE_CHARS = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
"\"": "&quot;",
"'": "&#39;",
"/": "&#x2F;"
};
var CONSTANT_PROPERTY_REGEX = /^[A-Z_]+$/;
var proto = Object.create(HTMLElement.prototype);
var template = "<style scoped>\n [data-customizer-icon]:before {\n font-family: \"customizer-icons\";\n content: attr(data-customizer-icon);\n display: inline-block;\n font-weight: 500;\n font-style: normal;\n text-decoration: inherit;\n text-transform: none;\n text-rendering: optimizeLegibility;\n font-size: 30px;\n }\n [flex] {\n display: flex;\n justify-content: space-between;\n padding: 0 5px;\n }\n [flex] > * {\n flex: 1 1 auto;\n margin-right: 5px;\n margin-left: 5px;\n }\n #container {\n box-sizing: border-box;\n position: relative;\n width: 100%;\n height: 100%;\n overflow-x: hidden;\n overflow-y: auto;\n }\n gaia-list a {\n position: relative;\n text-decoration: none;\n }\n gaia-list [hidden] {\n display: none;\n }\n gaia-list h3 {\n font-size: 18px;\n max-width: calc(100% - 2em);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n gaia-list [data-customizer-icon] {\n position: absolute;\n padding: 1.6em 1em;\n top: 0;\n right: 0;\n }\n gaia-list .value {\n color: #00aac5;\n }\n fxos-code-editor {\n display: block;\n width: 100%;\n height: calc(100% - 100px);\n }\n</style>\n<div id=\"container\"></div>\n<gaia-dialog-prompt id=\"new-attribute-name\">\n Enter new attribute name\n</gaia-dialog-prompt>";
proto.createdCallback = function () {
var _this = this;
this.shadow = this.createShadowRoot();
this.shadow.innerHTML = template;
this.container = this.shadow.querySelector("#container");
this.newAttributeName = this.shadow.querySelector("#new-attribute-name");
this.keyPath = [];
// Handle all clicks within this component.
this.container.addEventListener("click", function (evt) {
// Handle case where <a href> was clicked.
var target = evt.target.closest("a");
if (target) {
evt.preventDefault();
// Handle case where <i data-action="remove"> was clicked.
if (evt.target.dataset.action === "remove") {
_this._handleRemoveAttributeClick(target);
}
// Otherwise, treat as regular list item click.
else {
_this._handleListItemClick(target);
}
return;
}
// Handle case where <gaia-button> was clicked.
target = evt.target.closest("gaia-button");
if (target) {
evt.preventDefault();
_this._handleButtonClick(target);
return;
}
});
// Filter the list of properties 500ms after a keypress
// in the <gaia-text-input> search box.
this.container.addEventListener("keyup", function (evt) {
var textInput = evt.target.closest("gaia-text-input");
if (!textInput) {
return;
}
clearTimeout(_this._searchTimeout);
_this._searchTimeout = setTimeout(function () {
var value = textInput.value.toLowerCase();
var items = [].slice.apply(_this.container.querySelectorAll("gaia-list a[data-search]"));
// Toggle each list items' [hidden] attribute if the search text
// isn't blank and the items' [data-search] value contains the
// current search text.
items.forEach(function (i) {
return i.hidden = value && i.dataset.search.indexOf(value) === -1;
});
}, 500);
});
// Automatically set focus to the input box when the
// <gaia-dialog-prompt> is opened.
this.newAttributeName.addEventListener("opened", function () {
_this.newAttributeName.els.input.focus();
});
// Reset the <gaia-dialog-prompt> value when closed.
this.newAttributeName.addEventListener("closed", function () {
_this.newAttributeName.els.input.value = "";
});
// Create a new attribute when the <gaia-dialog-prompt>
// is submitted.
this.newAttributeName.els.submit.addEventListener("click", function () {
try {
var name = _this.newAttributeName.els.input.value;
var attribute = document.createAttribute(name);
_this.target.setNamedItem(attribute);
_this.dispatchEvent(new CustomEvent("createattribute", {
detail: JSON.stringify({
keyPath: _this.keyPath,
expression: "['" + _this.keyPath.join("']['") + "']",
name: name
})
}));
} catch (e) {
window.alert("Invalid attribute name");
return;
}
_this.render();
});
};
proto.render = function () {
var _this2 = this;
clearTimeout(this._searchTimeout);
this.target = this.rootTarget;
this.keyPath.forEach(function (key) {
return _this2.target = _this2.target[key];
});
this.container.innerHTML = renderTargetPage(this.target, this.keyPath.length === 0).innerHTML;
this.container.scrollTo(0, 0);
};
proto.setRootTarget = function (rootTarget) {
this.rootTarget = rootTarget;
this.target = this.rootTarget;
this.keyPath = [];
this.render();
};
proto._handleButtonClick = function (target) {
var _this3 = this;
switch (target.dataset.action) {
case "cancel":
this.keyPath.pop();
break;
case "save":
(function () {
var value = _this3.container.querySelector("fxos-code-editor").value;
var target = _this3.rootTarget;
_this3.keyPath.forEach(function (key, index) {
if (index < _this3.keyPath.length - 1) {
target = target[key];
return;
}
target[key] = value;
});
_this3.dispatchEvent(new CustomEvent("save", {
detail: JSON.stringify({
keyPath: _this3.keyPath,
expression: "['" + _this3.keyPath.join("']['") + "']",
value: value
})
}));
_this3.keyPath.pop();
})();
break;
default:
break;
}
this.render();
};
proto._handleRemoveAttributeClick = function (target) {
var name = target.getAttribute("href");
this.target.removeNamedItem(name);
this.dispatchEvent(new CustomEvent("removeattribute", {
detail: JSON.stringify({
keyPath: this.keyPath,
expression: "['" + this.keyPath.join("']['") + "']",
name: name
})
}));
this.render();
};
proto._handleListItemClick = function (target) {
// If the link clicked has an `href` pointing
// to "../", go back to the previous level in
// the key path.
var href = target.getAttribute("href");
if (href === "../") {
this.keyPath.pop();
}
// Show "Create New Attribute" prompt.
else if (href === "#add-attribute") {
this.newAttributeName.open();
}
// Otherwise, push the next part of the key path.
else {
this.keyPath.push(href);
}
this.render();
};
/**
* Helper function for determining the type of
* page to render for the current target (editor
* or list).
*/
function renderTargetPage(target, isRoot) {
// If the target is not an object, assume that
// it is editable and render the editor page.
if (typeof target !== "object") {
return renderTargetEditor(target);
}
// Otherwise, the target is an enumerable object
// and we can render the list page.
return renderTargetList(target, isRoot);
}
/**
* Helper for rendering the editor page.
*/
function renderTargetEditor(target) {
var page = document.createElement("section");
page.innerHTML = "<fxos-code-editor>" + target + "</fxos-code-editor>\n<section flex>\n <gaia-button data-action=\"cancel\">Cancel</gaia-button>\n <gaia-button data-action=\"save\">Save</gaia-button>\n</section>";
return page;
}
/**
* Helper for rendering the list page.
*/
function renderTargetList(target, isRoot) {
var list = document.createElement("gaia-list");
if (!isRoot) {
list.appendChild(renderBackItem());
}
if (target instanceof window.NamedNodeMap) {
list.appendChild(renderAddAttributeItem());
}
getSortedProperties(target).forEach(function (property) {
list.appendChild(renderTargetListItem(target, property));
});
var page = document.createElement("section");
page.innerHTML = "<section flex>\n <gaia-text-input type=\"search\" placeholder=\"Search Properties\"></gaia-text-input>\n</section>\n" + list.outerHTML;
return page;
}
/**
* Helper for rendering a list item.
*/
function renderTargetListItem(target, property) {
var value = target[property];
if (value instanceof window.Attr) {
value = "'" + escapeHTML(value.value) + "'";
} else if (typeof value === "string") {
value = "'" + escapeHTML(value) + "'";
}
var a = document.createElement("a");
a.href = property;
a.dataset.search = property.toLowerCase();
a.innerHTML = "<h3>" + property + " = <span class=\"value\">" + value + "</span></h3>";
// Append "X" button to remove attributes.
if (target instanceof window.NamedNodeMap) {
a.innerHTML += "<i data-customizer-icon=\"remove\" data-action=\"remove\"></i>";
}
return a;
}
/**
* Helper for rendering the "Up One Level [..]" list item.
*/
function renderBackItem() {
var a = document.createElement("a");
a.href = "../";
a.innerHTML = "<h3>Up One Level [..]</h3>";
return a;
}
/**
* Helper for rendering the "Create New Attribute"
* list item.
*/
function renderAddAttributeItem() {
var a = document.createElement("a");
a.href = "#add-attribute";
a.innerHTML = "<h3>Create New Attribute</h3>\n<i data-customizer-icon=\"add\"></i>";
return a;
}
/**
* Helper for enumerating and sorting all direct
* properties for the specified target object.
* This also handles an `attributes` object as a
* special case and returns a sorted array of
* attribute names instead of an array-indexed
* list of `Attr` objects.
*/
function getSortedProperties(target) {
var properties = [];
// If the `target` is a `NamedNodeMap` (attributes),
// enumerate the attributes and push their names
// instead of treating it as properties.
if (target instanceof window.NamedNodeMap) {
for (var _iterator = target[Symbol.iterator](), _step; !(_step = _iterator.next()).done;) {
var attr = _step.value;
properties.push(attr.name);
}
properties = properties.sort();
}
// Otherwise, emumerate the properties as usual.
else {
for (var property in target) {
// Omit invalid properties.
if (!isValidProperty(target, property)) {
continue;
}
// Omit constants.
if (CONSTANT_PROPERTY_REGEX.test(property)) {
continue;
}
// Omit native functions unless the target object
// directly contains them (e.g. `Element.prototype`).
if (!target.hasOwnProperty(property) && isNativeFunction(target[property])) {
continue;
}
properties.push(property + "");
}
// Explicitly add the `nodeValue`, `textContent` and
// `value` properties for empty attributes so their
// values can be set.
if (target instanceof window.Attr && !target.value) {
properties.push("nodeValue");
properties.push("textContent");
properties.push("value");
}
properties = properties.sort();
// Append `__proto__` property to end of array after
// sorting (if the object has one).
if (target.__proto__) {
properties.push("__proto__");
}
}
return properties;
}
/**
* Escapes HTML strings so that <, > and quotation
* characters are properly rendered in the list items.
*/
function escapeHTML(html) {
return html.replace(/[&<>"'\/]/g, function (s) {
return HTML_ESCAPE_CHARS[s];
});
}
/**
* Determines if the specified property is valid for
* the target object.
* NOTE: The `try`/`catch` is necessary to catch
* exceptions for properties that cannot be directly
* accessed.
*/
function isValidProperty(target, property) {
try {
return !!target[property];
} catch (e) {
return false;
}
}
/**
* Determines if a function is native. Native functions
* are filtered from the list items unless they are direct
* members of the current target object. This is to
* provide consistency with object/property inspection on
* desktop DevTools.
*/
function isNativeFunction(value) {
return typeof value === "function" && !! ~value.toString().indexOf("[native code]");
}
try {
document.registerElement("fxos-inspector", { prototype: proto });
} catch (e) {
if (e.name !== "NotSupportedError") {
throw e;
}
}
})(window);
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,192 @@
define(["exports"], function (exports) {
"use strict";
var _extends = function (child, parent) {
child.prototype = Object.create(parent.prototype, {
constructor: {
value: child,
enumerable: false,
writable: true,
configurable: true
}
});
child.__proto__ = parent;
};
var AddonGenerator = (function (Model) {
var AddonGenerator = function AddonGenerator(properties) {
Model.call(this, properties);
this.operations = [];
this.id = this.id || ("addon" + Math.round(Math.random() * 100000000));
this.name = this.name || this.id;
this.packageMetadata = {
installOrigin: "http://gaiamobile.org",
manifestURL: "app://" + this.id + ".gaiamobile.org/update.webapp",
version: 1
};
this.packageManifest = {
name: this.name,
package_path: "/application.zip"
};
this.manifest = {
name: this.name,
description: "Generated by Customizer",
role: "addon",
type: "certified",
origin: "app://" + this.id + ".gaiamobile.org",
customizations: [{
filter: window.location.origin,
scripts: ["main.js"]
}]
};
};
_extends(AddonGenerator, Model);
AddonGenerator.prototype.generate = function () {
var script = "/*=AddonGenerator*/\nvar selector = '" + this.getSelector() + "';\nvar el = document.querySelector(selector);\nvar mo = new MutationObserver(function() {\n var newEl = document.querySelector(selector);\n if (newEl !== el) {\n el = newEl;\n setTimeout(exec, 1);\n mo.disconnect();\n }\n});\nmo.observe(document.documentElement, {\n childList: true,\n attributes: true,\n characterData: true,\n subtree: true\n});\nexec();\nfunction exec() {\n" + this.operations.join("\n") + "\n}\n/*==*/";
console.log("******** Generated SCRIPT ********", script);
var applicationZip = new JSZip();
applicationZip.file("manifest.webapp", JSON.stringify(this.manifest));
applicationZip.file("main.js", script);
var packageZip = new JSZip();
packageZip.file("metadata.json", JSON.stringify(this.packageMetadata));
packageZip.file("update.webapp", JSON.stringify(this.packageManifest));
packageZip.file("application.zip", applicationZip.generate({ type: "arraybuffer" }));
return packageZip.generate({ type: "arraybuffer" });
};
AddonGenerator.prototype.getSelector = function () {
return getSelector(this.target);
};
AddonGenerator.prototype.opAddEventListener = function (eventName, callback) {
this.operations.push("/*=AddonGenerator::addEventListener*/\nel.addEventListener('" + eventName + "', " + callback + ");\n/*==*/");
};
AddonGenerator.prototype.opAppendChild = function (childNodeName) {
this.operations.push("/*=AddonGenerator::appendChild*/\nel.appendChild(document.createElement('" + childNodeName + "');\n/*==*/");
};
AddonGenerator.prototype.opInnerHTML = function (html) {
this.operations.push("/*=AddonGenerator::innerHTML*/\nel.innerHTML = " + JSON.stringify(html) + ";\nif (el.tagName === 'SCRIPT') {\n eval(el.innerHTML);\n}\nelse {\n Array.prototype.forEach.call(el.querySelectorAll('script'), function(script) {\n eval(script.innerHTML);\n });\n}\n/*==*/");
};
AddonGenerator.prototype.opScript = function (script) {
this.operations.push("/*=AddonGenerator::innerHTML*/\n" + script + "\n/*==*/");
};
AddonGenerator.prototype.opRemove = function () {
this.operations.push("/*=AddonGenerator::remove*/\nel.parentNode.removeChild(el);\n/*==*/");
};
AddonGenerator.prototype.opCreateAttributes = function (attributes) {
for (var expression in attributes) {
this.createAttributeHelper(expression, attributes[expression]);
}
};
AddonGenerator.prototype.opRemoveAttributes = function (attributes) {
for (var expression in attributes) {
this.removeAttributeHelper(expression, attributes[expression]);
}
};
AddonGenerator.prototype.opSetProperties = function (properties) {
for (var expression in properties) {
this.setPropertyHelper(expression, properties[expression]);
}
};
AddonGenerator.prototype.opCopyAppend = function (destination) {
this.operations.push("/*=AddonGenerator::copyAppend*/\nvar destination = document.querySelector('" + getSelector(destination) + "');\nvar template = document.createElement('template');\ntemplate.innerHTML = `" + this.target.outerHTML.replace(/\`/g, "\\`") + "`;\nif (destination) {\n destination.appendChild(template.content);\n}\n/*==*/");
};
AddonGenerator.prototype.opCopyPrepend = function (destination) {
this.operations.push("/*=AddonGenerator::copyPrepend*/\nvar destination = document.querySelector('" + getSelector(destination) + "');\nvar template = document.createElement('template');\ntemplate.innerHTML = `" + this.target.outerHTML.replace(/\`/g, "\\`") + "`;\nif (destination) {\n destination.insertBefore(template.content, destination.firstChild);\n}\n/*==*/");
};
AddonGenerator.prototype.opCopyAfter = function (destination) {
this.operations.push("/*=AddonGenerator::copyAfter*/\nvar destination = document.querySelector('" + getSelector(destination) + "');\nvar template = document.createElement('template');\ntemplate.innerHTML = `" + this.target.outerHTML.replace(/\`/g, "\\`") + "`;\nif (destination && destination.parentNode) {\n if (destination.parentNode.lastChild === destination) {\n destination.parentNode.appendChild(template.content);\n }\n else {\n destination.parentNode.insertBefore(template.content, destination.nextSibling);\n }\n}\n/*==*/");
};
AddonGenerator.prototype.opCopyBefore = function (destination) {
this.operations.push("/*=AddonGenerator::copyBefore*/\nvar destination = document.querySelector('" + getSelector(destination) + "');\nvar template = document.createElement('template');\ntemplate.innerHTML = `" + this.target.outerHTML.replace(/\`/g, "\\`") + "`;\nif (destination && destination.parentNode) {\n destination.parentNode.insertBefore(template.content, destination);\n}\n/*==*/");
};
AddonGenerator.prototype.opMoveAppend = function (destination) {
this.operations.push("/*=AddonGenerator::moveAppend*/\nvar destination = document.querySelector('" + getSelector(destination) + "');\nif (destination) {\n destination.appendChild(el);\n}\n/*==*/");
};
AddonGenerator.prototype.opMovePrepend = function (destination) {
this.operations.push("/*=AddonGenerator::movePrepend*/\nvar destination = document.querySelector('" + getSelector(destination) + "');\nif (destination) {\n destination.insertBefore(el, destination.firstChild);\n}\n/*==*/");
};
AddonGenerator.prototype.opMoveAfter = function (destination) {
this.operations.push("/*=AddonGenerator::moveAfter*/\nvar destination = document.querySelector('" + getSelector(destination) + "');\nif (destination && destination.parentNode) {\n if (destination.parentNode.lastChild === destination) {\n destination.parentNode.appendChild(el);\n }\n else {\n destination.parentNode.insertBefore(el, destination.nextSibling);\n }\n}\n/*==*/");
};
AddonGenerator.prototype.opMoveBefore = function (destination) {
this.operations.push("/*=AddonGenerator::moveBefore*/\nvar destination = document.querySelector('" + getSelector(destination) + "');\nif (destination && destination.parentNode) {\n destination.parentNode.insertBefore(el, destination);\n}\n/*==*/");
};
AddonGenerator.prototype.createAttributeHelper = function (expression, name) {
var assignment = (expression[0] === "[") ? "el" + expression : "el." + expression;
this.operations.push("/*=AddonGenerator::createAttribute*/\n" + assignment + ".setNamedItem(document.createAttribute(" + JSON.stringify(name) + "));\n/*==*/");
};
AddonGenerator.prototype.removeAttributeHelper = function (expression, name) {
var assignment = (expression[0] === "[") ? "el" + expression : "el." + expression;
this.operations.push("/*=AddonGenerator::removeAttribute*/\n" + assignment + ".removeNamedItem(" + JSON.stringify(name) + ");\n/*==*/");
};
AddonGenerator.prototype.setPropertyHelper = function (expression, value) {
var assignment = (expression[0] === "[") ? "el" + expression : "el." + expression;
this.operations.push("/*=AddonGenerator::setProperty*/\n" + assignment + " = " + JSON.stringify(value) + ";\n/*==*/");
};
return AddonGenerator;
})(Model);
exports["default"] = AddonGenerator;
function getSelector(element) {
var current = element;
var path = [getSpecificSelector(current)];
while (!current.id && current.nodeName !== "HTML") {
current = current.parentNode;
path.push(getSpecificSelector(current));
}
return path.reverse().join(">");
}
function getSpecificSelector(element) {
var selector = element.nodeName;
if (element.id) {
selector += "#" + element.id;
return selector;
}
Array.prototype.forEach.call(element.classList, function (item) {
selector += "." + item;
});
return selector;
}
});

View file

@ -0,0 +1,124 @@
define(["exports"], function (exports) {
"use strict";
var _extends = function (child, parent) {
child.prototype = Object.create(parent.prototype, {
constructor: {
value: child,
enumerable: false,
writable: true,
configurable: true
}
});
child.__proto__ = parent;
};
var AddonMerger = (function (Model) {
var AddonMerger = function AddonMerger(properties) {
Model.call(this, properties);
this.blobs = [];
this.id = this.id || ("addon" + Math.round(Math.random() * 100000000));
this.name = this.name || this.id;
this.packageMetadata = {
installOrigin: "http://gaiamobile.org",
manifestURL: "app://" + this.id + ".gaiamobile.org/update.webapp",
version: 1
};
this.packageManifest = {
name: this.name,
package_path: "/application.zip"
};
this.manifest = {
name: this.name,
role: "addon",
type: "certified",
origin: "app://" + this.id + ".gaiamobile.org"
};
};
_extends(AddonMerger, Model);
AddonMerger.prototype.merge = function (callback) {
var _this = this;
if (typeof callback !== "function") {
return;
}
var scripts = [];
var error = false;
this.blobs.forEach(function (blob) {
blobToArrayBuffer(blob, function (arrayBuffer) {
if (error) {
return;
}
var zip = new JSZip();
zip.load(arrayBuffer);
var applicationZipFile = zip.file("application.zip");
if (!applicationZipFile) {
error = true;
callback();
return;
}
var applicationZip = new JSZip();
applicationZip.load(applicationZipFile.asArrayBuffer());
var scriptFile = applicationZip.file("main.js");
if (!scriptFile) {
error = true;
callback();
return;
}
scripts.push(scriptFile.asText());
if (scripts.length === _this.blobs.length) {
callback(bundle(_this, scripts.join("\n")));
}
});
});
};
AddonMerger.prototype.add = function (blob) {
this.blobs.push(blob);
};
return AddonMerger;
})(Model);
exports["default"] = AddonMerger;
function bundle(merger, script) {
var applicationZip = new JSZip();
applicationZip.file("manifest.webapp", JSON.stringify(merger.manifest));
applicationZip.file("main.js", script);
var packageZip = new JSZip();
packageZip.file("metadata.json", JSON.stringify(merger.packageMetadata));
packageZip.file("update.webapp", JSON.stringify(merger.packageManifest));
packageZip.file("application.zip", applicationZip.generate({ type: "arraybuffer" }));
return packageZip.generate({ type: "arraybuffer" });
}
function blobToArrayBuffer(blob, callback) {
var fileReader = new FileReader();
fileReader.onload = function () {
if (typeof callback === "function") {
callback(fileReader.result);
}
};
fileReader.readAsArrayBuffer(blob);
return fileReader.result;
}
});

View file

@ -0,0 +1,65 @@
define(["exports"], function (exports) {
"use strict";
/* global MozActivity */
/* global AddonGenerator */
var GENERATED_ADDON_COUNT_KEY = "__CUSTOMIZER__generatedAddonCount";
var AddonService = {};
AddonService.getGenerator = function (target) {
return new Promise(function (resolve, reject) {
var number = parseInt(localStorage.getItem(GENERATED_ADDON_COUNT_KEY), 10) || 0;
var name = window.prompt("Enter a name for this add-on", "Addon " + (number + 1));
if (!name) {
reject();
return;
}
var generator = new AddonGenerator({
target: target,
name: name
});
resolve(generator);
});
};
AddonService.generate = function (target, callback) {
if (typeof callback !== "function") {
return;
}
return AddonService.getGenerator(target).then(function (generator) {
callback(generator);
var addonBlob = new Blob([generator.generate()], { type: "application/zip" });
AddonService.install(addonBlob);
});
};
AddonService.install = function (blob) {
return new Promise(function (resolve, reject) {
var activity = new MozActivity({
name: "import-app",
data: {
blob: blob
}
});
activity.onsuccess = function () {
var number = parseInt(localStorage.getItem(GENERATED_ADDON_COUNT_KEY), 10) || 0;
localStorage.setItem(GENERATED_ADDON_COUNT_KEY, number + 1);
resolve();
};
activity.onerror = function (error) {
console.error("Unable to install the addon", error);
reject(error);
};
});
};
});

View file

@ -0,0 +1,67 @@
define(["exports"], function (exports) {
"use strict";
var _extends = function (child, parent) {
child.prototype = Object.create(parent.prototype, {
constructor: {
value: child,
enumerable: false,
writable: true,
configurable: true
}
});
child.__proto__ = parent;
};
/* global View */
var appendChildViewTemplate = "\n<style scoped>\n.shadow-host {\n z-index: 10000001;\n}\n</style>\n<gaia-dialog-prompt>Enter new element name, e.g. \"div\"</gaia-dialog-prompt>\n";
var AppendChildView = (function (View) {
var AppendChildView = function AppendChildView(options) {
View.call(this, options);
this.el.className = "fxos-customizer-append-child-view";
this.render();
};
_extends(AppendChildView, View);
AppendChildView.prototype.init = function (controller) {
var _this = this;
View.prototype.init.call(this, controller);
this.dialog = this.$("gaia-dialog-prompt");
// Automatically set focus to the input box when the
// <gaia-dialog-prompt> is opened.
this.dialog.addEventListener("opened", function () {
_this.dialog.els.input.focus();
});
// Reset the <gaia-dialog-prompt> value when closed.
this.dialog.addEventListener("closed", function () {
_this.dialog.els.input.value = "";
});
// Submit the new element tag name when the
// <gaia-dialog-prompt> is submitted.
this.dialog.els.submit.addEventListener("click", function () {
_this.controller.submit(_this.dialog.els.input.value);
});
};
AppendChildView.prototype.template = function () {
return appendChildViewTemplate;
};
AppendChildView.prototype.open = function () {
this.dialog.open();
};
return AppendChildView;
})(View);
exports["default"] = AppendChildView;
});

View file

@ -0,0 +1,66 @@
define(["exports"], function (exports) {
"use strict";
var _extends = function (child, parent) {
child.prototype = Object.create(parent.prototype, {
constructor: {
value: child,
enumerable: false,
writable: true,
configurable: true
}
});
child.__proto__ = parent;
};
/* global View */
var copyMoveViewTemplate = "<gaia-modal>\n <gaia-header>\n <button type=\"button\" data-action=\"cancel\">Cancel</button>\n <h1>Copy/Move</h1>\n <button type=\"button\" data-action=\"select\">Select</button>\n </gaia-header>\n <gaia-tabs selected=\"0\">\n <a href=\"#\" data-mode=\"Copy\">Copy</a>\n <a href=\"#\" data-mode=\"Move\">Move</a>\n </gaia-tabs>\n <section>\n <gaia-dom-tree></gaia-dom-tree>\n </section>\n <gaia-dialog>\n <button type=\"button\" data-action=\"before\">Insert Before</button>\n <button type=\"button\" data-action=\"after\">Insert After</button>\n <button type=\"button\" data-action=\"prepend\">Prepend</button>\n <button type=\"button\" data-action=\"append\">Append</button>\n <button type=\"button\" on-click=\"close\">Cancel</button>\n </gaia-dialog>\n</gaia-modal>";
var CopyMoveView = (function (View) {
var CopyMoveView = function CopyMoveView(options) {
View.call(this, options);
this.el.className = "fxos-customizer-copy-move-view";
this.render();
};
_extends(CopyMoveView, View);
CopyMoveView.prototype.init = function (controller) {
View.prototype.init.call(this, controller);
this.modal = this.$("gaia-modal");
this.tabs = this.$("gaia-tabs");
this.domTree = this.$("gaia-dom-tree");
this.dialog = this.$("gaia-dialog");
this.tabs.addEventListener("change", this._handleModeChange.bind(this));
this.on("click", "button", this._handleClick.bind(this));
this.on("contextmenu", "gaia-dom-tree", function (evt) {
evt.stopPropagation();
});
};
CopyMoveView.prototype.template = function () {
return copyMoveViewTemplate;
};
CopyMoveView.prototype._handleModeChange = function (evt) {
this.controller.setMode(this.tabs.selectedChild.dataset.mode);
};
CopyMoveView.prototype._handleClick = function (evt) {
var action = this.controller[evt.target.dataset.action];
if (typeof action === "function") {
action.call(this.controller, evt.target.dataset);
}
};
return CopyMoveView;
})(View);
exports["default"] = CopyMoveView;
});

View file

@ -0,0 +1,171 @@
define(["exports"], function (exports) {
"use strict";
var _extends = function (child, parent) {
child.prototype = Object.create(parent.prototype, {
constructor: {
value: child,
enumerable: false,
writable: true,
configurable: true
}
});
child.__proto__ = parent;
};
/* global View */
/* global esprima */
/* global html_beautify */
var editViewTemplate = "<gaia-modal>\n <style scoped>\n .gaia-modal {\n background: var(--background, #fff);\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n }\n .gaia-modal.active {\n display: block;\n }\n .tab-pane {\n box-sizing: border-box;\n display: none;\n position: absolute;\n top: 96px;\n bottom: 0;\n left: 0;\n width: 100%;\n height: auto;\n }\n .tab-pane.active {\n display: block;\n }\n textarea,\n input {\n -moz-user-select: text !important;\n }\n textarea,\n gaia-tabs,\n .tab-pane {\n background: #000;\n color: #fff;\n }\n fxos-code-editor {\n display: block;\n width: 100%;\n height: 100%;\n }\n .errors {\n background: #e51e1e;\n color: #fff;\n position: absolute;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 20px;\n overflow: hidden;\n z-index: 2;\n opacity: 0;\n transition: opacity 0.2s ease;\n pointer-events: none;\n }\n .errors.active {\n opacity: 1;\n }\n .errors.active + fxos-code-editor {\n height: calc(100% - 20px);\n }\n </style>\n <gaia-header>\n <button aria-hidden=\"true\" data-action=\"cancel\" data-icon=\"close\"></button>\n <h1>Edit</h1>\n <button data-action=\"save\">Save</button>\n </gaia-header>\n <gaia-tabs selected=\"0\">\n <a href=\"#\">HTML</a>\n <a href=\"#\">Script</a>\n <a href=\"#\">Properties</a>\n </gaia-tabs>\n <section class=\"tab-pane active\" data-id=\"html\">\n <fxos-code-editor></fxos-code-editor>\n </section>\n <section class=\"tab-pane\" data-id=\"script\">\n <div class=\"errors\"></div>\n <fxos-code-editor></fxos-code-editor>\n </section>\n <section class=\"tab-pane\" data-id=\"properties\">\n <fxos-inspector></fxos-inspector>\n </section>\n</gaia-modal>";
var EditView = (function (View) {
var EditView = function EditView(options) {
View.call(this, options);
this.el.className = "fxos-customizer-edit-view";
this.render();
};
_extends(EditView, View);
EditView.prototype.init = function (controller) {
var _this = this;
View.prototype.init.call(this, controller);
this.modal = this.$("gaia-modal");
this.header = this.$("gaia-header");
this.tabs = this.$("gaia-tabs");
this.htmlCodeEditor = this.$("section[data-id=\"html\"] > fxos-code-editor");
this.scriptCodeEditor = this.$("section[data-id=\"script\"] > fxos-code-editor");
this.propertyInspector = this.$("section[data-id=\"properties\"] > fxos-inspector");
this.scriptErrors = this.$("section[data-id=\"script\"] > .errors");
this.tabPanes = [].slice.apply(this.$$(".tab-pane"));
this.on("click", "button[data-action=\"cancel\"]", function () {
_this.controller.close();
});
this.on("click", "button[data-action=\"save\"]", function () {
_this.controller.save();
});
this.tabs.addEventListener("change", function () {
_this.tabPanes.forEach(function (tabPane, index) {
if (index === _this.tabs.selected) {
tabPane.classList.add("active");
} else {
tabPane.classList.remove("active");
}
});
});
this.htmlCodeEditor.addEventListener("change", function () {
_this.controller.changes.innerHTML = _this.htmlCodeEditor.value;
});
this.scriptCodeEditor.addEventListener("change", function () {
_this.controller.changes.script = _this.scriptCodeEditor.value;
clearTimeout(_this.validateScriptTimeout);
_this.validateScriptTimeout = setTimeout(_this.validateScript.bind(_this), 2000);
});
this.scriptCodeEditor.addEventListener("touchstart", function () {
clearTimeout(_this.validateScriptTimeout);
});
this.scriptCodeEditor.addEventListener("touchend", function () {
clearTimeout(_this.validateScriptTimeout);
_this.validateScriptTimeout = setTimeout(_this.validateScript.bind(_this), 2000);
});
this.propertyInspector.addEventListener("createattribute", function (evt) {
var detail = JSON.parse(evt.detail);
_this.controller.changes.createAttributes = _this.controller.changes.createAttributes || {};
_this.controller.changes.createAttributes[detail.expression] = detail.name;
});
this.propertyInspector.addEventListener("removeattribute", function (evt) {
var detail = JSON.parse(evt.detail);
_this.controller.changes.removeAttributes = _this.controller.changes.removeAttributes || {};
_this.controller.changes.removeAttributes[detail.expression] = detail.name;
});
this.propertyInspector.addEventListener("save", function (evt) {
var detail = JSON.parse(evt.detail);
_this.controller.changes.properties = _this.controller.changes.properties || {};
_this.controller.changes.properties[detail.expression] = detail.value;
});
this.el.addEventListener("contextmenu", function (evt) {
evt.stopPropagation();
});
};
EditView.prototype.template = function () {
return editViewTemplate;
};
EditView.prototype.open = function () {
this.modal.open();
};
EditView.prototype.close = function () {
this.modal.close();
};
EditView.prototype.setTarget = function (target) {
var _this2 = this;
var clonedTarget = target.cloneNode(true);
var html = clonedTarget.innerHTML.trim();
this.htmlCodeEditor.value = "Loading...";
setTimeout(function () {
html = html_beautify(html, {
indent_size: 2
});
_this2.htmlCodeEditor.value = html;
}, 1);
this.scriptCodeEditor.value = "/**\n * You can edit a script to be inserted\n * in the generated add-on here.\n *\n * Globals:\n * selector [String]\n * el [HTMLElement]\n * mo [MutationObserver]\n */\n\n//el.addEventListener('click', function(evt) {\n// alert('Clicked!');\n//});\n";
this.propertyInspector.setRootTarget(clonedTarget);
};
EditView.prototype.validateScript = function () {
var error;
try {
var syntax = esprima.parse(this.controller.changes.script);
if (syntax.errors && syntax.errors.length > 0) {
error = syntax.errors[0];
}
} catch (e) {
error = e;
}
if (error) {
this.scriptErrors.textContent = error.message;
this.scriptErrors.classList.add("active");
} else {
this.scriptErrors.textContent = "";
this.scriptErrors.classList.remove("active");
}
};
return EditView;
})(View);
exports["default"] = EditView;
});

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,69 @@
define(["exports"], function (exports) {
"use strict";
var _extends = function (child, parent) {
child.prototype = Object.create(parent.prototype, {
constructor: {
value: child,
enumerable: false,
writable: true,
configurable: true
}
});
child.__proto__ = parent;
};
/* global View */
var viewSourceViewTemplate = "\n<style scoped>\ngaia-modal {\n position: absolute;\n left: 0;\n top: 0;\n width: 100%;\n height: 100%;\n}\ngaia-header {\n height: 40px;\n border-bottom: solid white 1px;\n}\npre {\n background: #000;\n color: #fff;\n font-family: Consolas,Monaco,\"Andale Mono\",monospace;\n font-size: 14px;\n line-height: 1.2em;\n position: absolute;\n top: 50px;\n left: 0;\n width: 100%;\n height: calc(100% - 50px);\n overflow: scroll;\n padding: 5px;\n}\n</style>\n<gaia-modal>\n <gaia-header>\n <button data-action=\"close\">Close</button>\n <h1>Source View</h1>\n </gaia-header>\n <pre>\n </pre>\n</gaia-modal>";
var ViewSourceView = (function (View) {
var ViewSourceView = function ViewSourceView(options) {
View.call(this, options);
this.el.className = "fxos-customizer-view-source-view";
this.render();
};
_extends(ViewSourceView, View);
ViewSourceView.prototype.init = function (controller) {
var _this = this;
View.prototype.init.call(this, controller);
this.modal = this.$("gaia-modal");
this.title = this.$("h1");
this.pre = this.$("pre");
this.on("click", "button", function (evt) {
var action = _this.controller[evt.target.dataset.action];
if (typeof action === "function") {
action.call(_this.controller, evt.target.dataset);
}
});
};
ViewSourceView.prototype.template = function () {
return viewSourceViewTemplate;
};
ViewSourceView.prototype.setTitle = function (title) {
this.title.textContent = title;
};
ViewSourceView.prototype.setSource = function (source) {
this.pre.textContent = source;
};
ViewSourceView.prototype.open = function () {
this.modal.open();
};
ViewSourceView.prototype.close = function () {
this.modal.close();
};
return ViewSourceView;
})(View);
exports["default"] = ViewSourceView;
});