(function() { var result;"use strict"; var _slice = Array.prototype.slice; var _toArray = function (arr) { return Array.isArray(arr) ? arr : Array.from(arr); }; var _extends = function (child, parent) { child.prototype = Object.create(parent.prototype, { constructor: { value: child, enumerable: false, writable: true, configurable: true } }); child.__proto__ = parent; }; // Copyright 2012 Kap IT (http://www.kapit.fr/) // // Licensed under the Apache License, Version 2.0 (the 'License'); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an 'AS IS' BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Author : François de Campredon (http://francois.de-campredon.fr/), // Object.observe Shim // =================== // *See [The harmony proposal page](http://wiki.ecmascript.org/doku.php?id=harmony:observe)* (function (global) { "use strict"; if (typeof Object.observe === "function") { return; } // Utilities // --------- // setImmediate shim used to deliver changes records asynchronously // use setImmediate if available var setImmediate = global.setImmediate || global.msSetImmediate, clearImmediate = global.clearImmediate || global.msClearImmediate; if (!setImmediate) { // fallback on setTimeout if not setImmediate = function (func, args) { return setTimeout(func, 0, args); }; clearImmediate = function (id) { clearTimeout(id); }; } // WeakMap // ------- var PrivateMap; if (typeof WeakMap !== "undefined") { //use weakmap if defined PrivateMap = WeakMap; } else { //else use ses like shim of WeakMap var HIDDEN_PREFIX = "__weakmap:" + (Math.random() * 1000000000 >>> 0), counter = new Date().getTime() % 1000000000, mascot = {}; PrivateMap = function () { this.name = HIDDEN_PREFIX + (Math.random() * 1000000000 >>> 0) + (counter++ + "__"); }; PrivateMap.prototype = { has: function (key) { return key && key.hasOwnProperty(this.name); }, get: function (key) { var value = key && key[this.name]; return value === mascot ? undefined : value; }, set: function (key, value) { Object.defineProperty(key, this.name, { value: typeof value === "undefined" ? mascot : value, enumerable: false, writable: true, configurable: true }); }, "delete": function (key) { return delete key[this.name]; } }; var getOwnPropertyName = Object.getOwnPropertyNames; Object.defineProperty(Object, "getOwnPropertyNames", { value: function fakeGetOwnPropertyNames(obj) { return getOwnPropertyName(obj).filter(function (name) { return name.substr(0, HIDDEN_PREFIX.length) !== HIDDEN_PREFIX; }); }, writable: true, enumerable: false, configurable: true }); } // Internal Properties // ------------------- // An ordered list used to provide a deterministic ordering in which callbacks are called. // [Corresponding Section in ECMAScript wiki](http://wiki.ecmascript.org/doku.php?id=harmony:observe_internals#observercallbacks) var observerCallbacks = []; // This object is used as the prototype of all the notifiers that are returned by Object.getNotifier(O). // [Corresponding Section in ECMAScript wiki](http://wiki.ecmascript.org/doku.php?id=harmony:observe_internals#notifierprototype) var NotifierPrototype = Object.create(Object.prototype); // Used to store immediate uid reference var changeDeliveryImmediateUid; // Used to schedule a call to _deliverAllChangeRecords function setUpChangesDelivery() { clearImmediate(changeDeliveryImmediateUid); changeDeliveryImmediateUid = setImmediate(_deliverAllChangeRecords); } Object.defineProperty(NotifierPrototype, "notify", { value: function notify(changeRecord) { var notifier = this; if (Object(notifier) !== notifier) { throw new TypeError("this must be an Object, given " + notifier); } if (!notifier.__target) { return; } if (Object(changeRecord) !== changeRecord) { throw new TypeError("changeRecord must be an Object, given " + changeRecord); } var type = changeRecord.type; if (typeof type !== "string") { throw new TypeError("changeRecord.type must be a string, given " + type); } var changeObservers = changeObserversMap.get(notifier); if (!changeObservers || changeObservers.length === 0) { return; } var target = notifier.__target, newRecord = Object.create(Object.prototype, { object: { value: target, writable: false, enumerable: true, configurable: false } }); for (var prop in changeRecord) { if (prop !== "object") { var value = changeRecord[prop]; Object.defineProperty(newRecord, prop, { value: value, writable: false, enumerable: true, configurable: false }); } } Object.preventExtensions(newRecord); _enqueueChangeRecord(notifier.__target, newRecord); }, writable: true, enumerable: false, configurable: true }); Object.defineProperty(NotifierPrototype, "performChange", { value: function performChange(changeType, changeFn) { var notifier = this; if (Object(notifier) !== notifier) { throw new TypeError("this must be an Object, given " + notifier); } if (!notifier.__target) { return; } if (typeof changeType !== "string") { throw new TypeError("changeType must be a string given " + notifier); } if (typeof changeFn !== "function") { throw new TypeError("changeFn must be a function, given " + changeFn); } _beginChange(notifier.__target, changeType); var error, changeRecord; try { changeRecord = changeFn.call(undefined); } catch (e) { error = e; } _endChange(notifier.__target, changeType); if (typeof error !== "undefined") { throw error; } var changeObservers = changeObserversMap.get(notifier); if (changeObservers.length === 0) { return; } var target = notifier.__target, newRecord = Object.create(Object.prototype, { object: { value: target, writable: false, enumerable: true, configurable: false }, type: { value: changeType, writable: false, enumerable: true, configurable: false } }); if (typeof changeRecord !== "undefined") { for (var prop in changeRecord) { if (prop !== "object" && prop !== "type") { var value = changeRecord[prop]; Object.defineProperty(newRecord, prop, { value: value, writable: false, enumerable: true, configurable: false }); } } } Object.preventExtensions(newRecord); _enqueueChangeRecord(notifier.__target, newRecord); }, writable: true, enumerable: false, configurable: true }); // Implementation of the internal algorithm 'BeginChange' // described in the proposal. // [Corresponding Section in ECMAScript wiki](http://wiki.ecmascript.org/doku.php?id=harmony:observe_internals#beginchange) function _beginChange(object, changeType) { var notifier = Object.getNotifier(object), activeChanges = activeChangesMap.get(notifier), changeCount = activeChangesMap.get(notifier)[changeType]; activeChanges[changeType] = typeof changeCount === "undefined" ? 1 : changeCount + 1; } // Implementation of the internal algorithm 'EndChange' // described in the proposal. // [Corresponding Section in ECMAScript wiki](http://wiki.ecmascript.org/doku.php?id=harmony:observe_internals#endchange) function _endChange(object, changeType) { var notifier = Object.getNotifier(object), activeChanges = activeChangesMap.get(notifier), changeCount = activeChangesMap.get(notifier)[changeType]; activeChanges[changeType] = changeCount > 0 ? changeCount - 1 : 0; } // Implementation of the internal algorithm 'ShouldDeliverToObserver' // described in the proposal. // [Corresponding Section in ECMAScript wiki](http://wiki.ecmascript.org/doku.php?id=harmony:observe_internals#shoulddelivertoobserver) function _shouldDeliverToObserver(activeChanges, acceptList, changeType) { var doesAccept = false; if (acceptList) { for (var i = 0, l = acceptList.length; i < l; i++) { var accept = acceptList[i]; if (activeChanges[accept] > 0) { return false; } if (accept === changeType) { doesAccept = true; } } } return doesAccept; } // Map used to store corresponding notifier to an object var notifierMap = new PrivateMap(), changeObserversMap = new PrivateMap(), activeChangesMap = new PrivateMap(); // Implementation of the internal algorithm 'GetNotifier' // described in the proposal. // [Corresponding Section in ECMAScript wiki](http://wiki.ecmascript.org/doku.php?id=harmony:observe_internals#getnotifier) function _getNotifier(target) { if (!notifierMap.has(target)) { var notifier = Object.create(NotifierPrototype); // we does not really need to hide this, since anyway the host object is accessible from outside of the // implementation. we just make it unwritable Object.defineProperty(notifier, "__target", { value: target }); changeObserversMap.set(notifier, []); activeChangesMap.set(notifier, {}); notifierMap.set(target, notifier); } return notifierMap.get(target); } // map used to store reference to a list of pending changeRecords // in observer callback. var pendingChangesMap = new PrivateMap(); // Implementation of the internal algorithm 'EnqueueChangeRecord' // described in the proposal. // [Corresponding Section in ECMAScript wiki](http://wiki.ecmascript.org/doku.php?id=harmony:observe_internals#enqueuechangerecord) function _enqueueChangeRecord(object, changeRecord) { var notifier = Object.getNotifier(object), changeType = changeRecord.type, activeChanges = activeChangesMap.get(notifier), changeObservers = changeObserversMap.get(notifier); for (var i = 0, l = changeObservers.length; i < l; i++) { var observerRecord = changeObservers[i], acceptList = observerRecord.accept; if (_shouldDeliverToObserver(activeChanges, acceptList, changeType)) { var observer = observerRecord.callback, pendingChangeRecords = []; if (!pendingChangesMap.has(observer)) { pendingChangesMap.set(observer, pendingChangeRecords); } else { pendingChangeRecords = pendingChangesMap.get(observer); } pendingChangeRecords.push(changeRecord); } } setUpChangesDelivery(); } // map used to store a count of associated notifier to a function var attachedNotifierCountMap = new PrivateMap(); // Remove reference all reference to an observer callback, // if this one is not used anymore. // In the proposal the ObserverCallBack has a weak reference over observers, // Without this possibility we need to clean this list to avoid memory leak function _cleanObserver(observer) { if (!attachedNotifierCountMap.get(observer) && !pendingChangesMap.has(observer)) { attachedNotifierCountMap["delete"](observer); var index = observerCallbacks.indexOf(observer); if (index !== -1) { observerCallbacks.splice(index, 1); } } } // Implementation of the internal algorithm 'DeliverChangeRecords' // described in the proposal. // [Corresponding Section in ECMAScript wiki](http://wiki.ecmascript.org/doku.php?id=harmony:observe_internals#deliverchangerecords) function _deliverChangeRecords(observer) { var pendingChangeRecords = pendingChangesMap.get(observer); pendingChangesMap["delete"](observer); if (!pendingChangeRecords || pendingChangeRecords.length === 0) { return false; } try { observer.call(undefined, pendingChangeRecords); } catch (e) {} _cleanObserver(observer); return true; } // Implementation of the internal algorithm 'DeliverAllChangeRecords' // described in the proposal. // [Corresponding Section in ECMAScript wiki](http://wiki.ecmascript.org/doku.php?id=harmony:observe_internals#deliverallchangerecords) function _deliverAllChangeRecords() { var observers = observerCallbacks.slice(); var anyWorkDone = false; for (var i = 0, l = observers.length; i < l; i++) { var observer = observers[i]; if (_deliverChangeRecords(observer)) { anyWorkDone = true; } } return anyWorkDone; } Object.defineProperties(Object, { // Implementation of the public api 'Object.observe' // described in the proposal. // [Corresponding Section in ECMAScript wiki](http://wiki.ecmascript.org/doku.php?id=harmony:observe_public_api#object.observe) observe: { value: function observe(target, callback, accept) { if (Object(target) !== target) { throw new TypeError("target must be an Object, given " + target); } if (typeof callback !== "function") { throw new TypeError("observer must be a function, given " + callback); } if (Object.isFrozen(callback)) { throw new TypeError("observer cannot be frozen"); } var acceptList; if (typeof accept === "undefined") { acceptList = ["add", "update", "delete", "reconfigure", "setPrototype", "preventExtensions"]; } else { if (Object(accept) !== accept) { throw new TypeError("accept must be an object, given " + accept); } var len = accept.length; if (typeof len !== "number" || len >>> 0 !== len || len < 1) { throw new TypeError("the 'length' property of accept must be a positive integer, given " + len); } var nextIndex = 0; acceptList = []; while (nextIndex < len) { var next = accept[nextIndex]; if (typeof next !== "string") { throw new TypeError("accept must contains only string, given" + next); } acceptList.push(next); nextIndex++; } } var notifier = _getNotifier(target), changeObservers = changeObserversMap.get(notifier); for (var i = 0, l = changeObservers.length; i < l; i++) { if (changeObservers[i].callback === callback) { changeObservers[i].accept = acceptList; return target; } } changeObservers.push({ callback: callback, accept: acceptList }); if (observerCallbacks.indexOf(callback) === -1) { observerCallbacks.push(callback); } if (!attachedNotifierCountMap.has(callback)) { attachedNotifierCountMap.set(callback, 1); } else { attachedNotifierCountMap.set(callback, attachedNotifierCountMap.get(callback) + 1); } return target; }, writable: true, configurable: true }, // Implementation of the public api 'Object.unobseve' // described in the proposal. // [Corresponding Section in ECMAScript wiki](http://wiki.ecmascript.org/doku.php?id=harmony:observe_public_api#object.unobseve) unobserve: { value: function unobserve(target, callback) { if (Object(target) !== target) { throw new TypeError("target must be an Object, given " + target); } if (typeof callback !== "function") { throw new TypeError("observer must be a function, given " + callback); } var notifier = _getNotifier(target), changeObservers = changeObserversMap.get(notifier); for (var i = 0, l = changeObservers.length; i < l; i++) { if (changeObservers[i].callback === callback) { changeObservers.splice(i, 1); attachedNotifierCountMap.set(callback, attachedNotifierCountMap.get(callback) - 1); _cleanObserver(callback); break; } } return target; }, writable: true, configurable: true }, // Implementation of the public api 'Object.deliverChangeRecords' // described in the proposal. // [Corresponding Section in ECMAScript wiki](http://wiki.ecmascript.org/doku.php?id=harmony:observe_public_api#object.deliverchangerecords) deliverChangeRecords: { value: function deliverChangeRecords(observer) { if (typeof observer !== "function") { throw new TypeError("callback must be a function, given " + observer); } while (_deliverChangeRecords(observer)) {} }, writable: true, configurable: true }, // Implementation of the public api 'Object.getNotifier' // described in the proposal. // [Corresponding Section in ECMAScript wiki](http://wiki.ecmascript.org/doku.php?id=harmony:observe_public_api#object.getnotifier) getNotifier: { value: function getNotifier(target) { if (Object(target) !== target) { throw new TypeError("target must be an Object, given " + target); } if (Object.isFrozen(target)) { return null; } return _getNotifier(target); }, writable: true, configurable: true } }); })(typeof global !== "undefined" ? global : this); // Copyright 2012 Kap IT (http://www.kapit.fr/) // // Licensed under the Apache License, Version 2.0 (the 'License'); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an 'AS IS' BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Author : François de Campredon (http://francois.de-campredon.fr/), // ObjectUtils // =========== (function (global) { "use strict"; /** * @namespace */ var ObserveUtils; if (typeof exports !== "undefined") { ObserveUtils = exports; } else { ObserveUtils = global.ObserveUtils = {}; } // Utilities // --------- // borrowing some array methods var arrSlice = Function.call.bind(Array.prototype.slice), arrMap = Function.call.bind(Array.prototype.map); // return true if the given property descriptor contains accessor function isAccessorDescriptor(desc) { if (typeof desc === "undefined") { return false; } return ("get" in desc || "set" in desc); } // getPropertyDescriptor shim // copied from [es6-shim](https://github.com/paulmillr/es6-shim) function getPropertyDescriptor(target, name) { var pd = Object.getOwnPropertyDescriptor(target, name), proto = Object.getPrototypeOf(target); while (typeof pd === "undefined" && proto !== null) { pd = Object.getOwnPropertyDescriptor(proto, name); proto = Object.getPrototypeOf(proto); } return pd; } // egal shim // copied from [the ecmascript wiki](http://wiki.ecmascript.org/doku.php?id=harmony:egal) function sameValue(x, y) { if (x === y) { // 0 === -0, but they are not identical return x !== 0 || 1 / x === 1 / y; } // NaN !== NaN, but they are identical. // NaNs are the only non-reflexive value, i.e., if x !== x, // then x is a NaN. // isNaN is broken: it converts its argument to number, so // isNaN('foo') => true return x !== x && y !== y; } // cast a value as number, and test if the obtained result // is a positive finite integer, throw an error otherwise function isPositiveFiniteInteger(value, errorMessage) { value = Number(value); if (isNaN(value) || !isFinite(value) || value < 0 || value % 1 !== 0) { throw new RangeError(errorMessage.replace("$", value)); } return value; } // defineObservableProperties Implementation // ------------------------------------------ // Uid generation helper var uidCounter = 0; // Define a property on an object that will call the Notifier.notify method when updated function defineObservableProperty(target, property, originalValue) { //we store the value in an non-enumerable property with generated unique name var internalPropName = "_" + (uidCounter++) + property; if (target.hasOwnProperty(property)) { Object.defineProperty(target, internalPropName, { value: originalValue, writable: true, enumerable: false, configurable: true }); } //then we create accessor method for our 'hidden' property, // that dispatch changesRecords when the value is updated Object.defineProperty(target, property, { get: function () { return this[internalPropName]; }, set: function (value) { if (!sameValue(value, this[internalPropName])) { var oldValue = this[internalPropName]; Object.defineProperty(this, internalPropName, { value: value, writable: true, enumerable: false, configurable: true }); var notifier = Object.getNotifier(this); notifier.notify({ type: "update", name: property, oldValue: oldValue }); } }, enumerable: true, configurable: true }); } // call defineObservableProperty for each property name passed as 'rest argument' /** * Define observable properties on the given object an return it. * * @param {Object} target * @param {...string} properties * @returns {Object} */ ObserveUtils.defineObservableProperties = function defineObservableProperties(target, properties) { if (Object(target) !== target) { throw new TypeError("target must be an Object, given " + target); } properties = arrSlice(arguments, 1); while (properties.length > 0) { var property = properties.shift(), descriptor = getPropertyDescriptor(target, property); if (!descriptor || !isAccessorDescriptor(descriptor)) { var originalValue = descriptor && descriptor.value; defineObservableProperty(target, property, originalValue); } } return target; }; // List Implementation // ------------------------------------------ /** * * @param length * @returns {*} * @constructor * @function */ function List(length) { if (arguments.length === 0) { length = 0; } // in this case we create a list with a given length if (arguments.length <= 1 && typeof length === "number") { if (this instanceof List) { this.length = length; } else { return new List(length); } } else { //here we create a list with initial values if (!(this instanceof List)) { return List.fromArray(arrSlice(arguments)); } else { for (var i = 0, l = arguments.length; i < l; i++) { this[i] = arguments[i]; } this.length = arguments.length; } } } /** * Observe a list * @param {List} list * @param {function} observer */ List.observe = function observe(list, observer) { Object.observe(list, observer, ["add", "update", "delete", "splice"]); }; /** * Unobserve a list * @param {List} list * @param {function} observer */ List.unobserve = function unobserve(list, observer) { Object.unobserve(list, observer); }; /** * Create a list from a given array * @param array * @returns {List} */ List.fromArray = function fromArray(array) { if (!Array.isArray(array)) { throw new Error(); } var list = new List(); for (var i = 0, l = array.length; i < l; i++) { list[i] = array[i]; } list.length = array.length; return list; }; Object.defineProperties(List.prototype, { /** * hidden value holder for the length property * @private */ _length: { value: 0, enumerable: false, configurable: true, writable: true }, /** * the length of the list * @property {number} length */ length: { get: function () { return this._length; }, set: function (value) { value = isPositiveFiniteInteger(value, "Invalid list length : $"); var notifier = Object.getNotifier(this), oldValue = this._length, removed = [], self = this; if (value !== oldValue) { notifier.performChange("splice", function () { Object.defineProperty(self, "_length", { value: value, enumerable: false, configurable: true, writable: true }); var returnValue; if (oldValue > value) { //delete values if the length have been decreased for (var i = value; i < oldValue; i++) { removed.push(self[i]); self["delete"](i); } returnValue = { index: value, removed: removed, addedCount: 0 }; } else { returnValue = { index: oldValue, removed: removed, addedCount: value - oldValue }; } notifier.notify({ type: "update", name: "length", oldValue: oldValue }); return returnValue; }); } }, enumerable: true, configurable: true } }); /** * Returns an Array copy of the list * @returns {Array} */ List.prototype.toArray = function toArray() { return arrSlice(this); }; /** * Returns an string representation of the list * @returns {string} */ List.prototype.toString = function toString() { return this.toArray().toString(); }; /** * Returns an json representation of the list * @returns {string} */ List.prototype.toJSON = function toJSON() { return this.toArray(); }; /** * set the givent value at the specified index. * @param {number} index * @param {*} value * @return {*} */ List.prototype.set = function set(index, value) { index = isPositiveFiniteInteger(index, "Invalid index : $"); var notifier = Object.getNotifier(this), len = this.length, self = this; if (index >= len) { notifier.performChange("splice", function () { self[index] = value; notifier.notify({ type: "add", name: index }); Object.defineProperty(self, "_length", { value: index + 1, enumerable: false, configurable: true, writable: true }); notifier.notify({ type: "update", name: "length", oldValue: len }); return { index: len, removed: [], addedCount: self.length - len }; }); } else if (!sameValue(value, this[index])) { var oldValue = this[index]; this[index] = value; notifier.notify({ type: "update", name: index, oldValue: oldValue }); } return value; }; /** * delete the value at the specified index. * @param {number} index * @return {boolean} */ List.prototype["delete"] = function del(index) { index = isPositiveFiniteInteger(index, "Invalid index : $"); if (this.hasOwnProperty(index)) { var oldValue = this[index]; if (delete this[index]) { var notifier = Object.getNotifier(this); notifier.notify({ type: "delete", name: index, oldValue: oldValue }); return true; } } return false; }; /** * create a new list resulting of the concatenation of all the List and array * passed as parameter with the addition of other values passed as parameter * @param {...*} args * @return {List} */ List.prototype.concat = function concat(args) { args = arrMap(arguments, function (item) { return (item instanceof List) ? item.toArray() : item; }); return List.fromArray(Array.prototype.concat.apply(this.toArray(), args)); }; /** * Joins all elements of a List into a string. * @param {string} [separator] * @return {string} */ List.prototype.join = function join(separator) { return this.toArray().join(separator); }; /** * Removes the last element from a List and returns that element. * @return {*} */ List.prototype.pop = function pop() { if (Object(this) !== this) { throw new TypeError("this mus be an object given : " + this); } var len = isPositiveFiniteInteger(this.length, "this must have a finite integer property 'length', given : $"); if (len === 0) { return void (0); } else { var newLen = len - 1, element = this[newLen], notifier = Object.getNotifier(this), self = this; notifier.performChange("splice", function () { delete self[newLen]; notifier.notify({ type: "delete", name: newLen, oldValue: element }); Object.defineProperty(self, "_length", { value: newLen, enumerable: false, configurable: true, writable: true }); notifier.notify({ type: "update", name: "length", oldValue: len }); return { index: newLen, removed: [element], addedCount: 0 }; }); return element; } }; /** * Mutates a List by appending the given elements and returning the new length of the array. * @param {...*} items * @return {number} */ List.prototype.push = function push() { if (arguments.length > 0) { var argumentsLength = arguments.length, elements = arguments, len = this.length, notifier = Object.getNotifier(this), self = this, i, index; notifier.performChange("splice", function () { for (i = 0; i < argumentsLength; i++) { index = len + i; // avoid the usage of the set function and manually // set the value and notify the changes to avoid the notification of // multiple length modification self[index] = elements[i]; notifier.notify({ type: "add", name: index }); } Object.defineProperty(self, "_length", { value: len + argumentsLength, enumerable: false, configurable: true, writable: true }); notifier.notify({ type: "update", name: "length", oldValue: len }); return { index: len, removed: [], addedCount: argumentsLength }; }); } return this.length; }; /** * Reverses a List in place. The first List element becomes the last and the last becomes the first. * @return {List} */ List.prototype.reverse = function reverse() { var copy = this.toArray(), arr = copy.slice().reverse(); for (var i = 0, l = arr.length; i < l; i++) { this.set(i, arr[i]); } return this; }; /** * Removes the first element from a List and returns that element. This method changes the length of the List. * @return {*} */ List.prototype.shift = function () { if (this.length === 0) { return void (0); } var arr = this.toArray(), element = arr.shift(), notifier = Object.getNotifier(this), self = this, len = this.length; notifier.performChange("splice", function () { for (var i = 0, l = arr.length; i < l; i++) { self.set(i, arr[i]); } self["delete"](len - 1); Object.defineProperty(self, "_length", { value: len - 1, enumerable: false, configurable: true, writable: true }); notifier.notify({ type: "update", name: "length", oldValue: len }); return { index: 0, removed: [element], addedCount: 0 }; }); return element; }; /** * Returns a shallow copy of a portion of an List. * @param {number} [start] * @param {number} [end] * @return {List} */ List.prototype.slice = function (start, end) { return List.fromArray(this.toArray().slice(start, end)); }; /** * Sorts the elements of a List in place and returns the List. * @param {function} [compareFn] * @return {List} */ List.prototype.sort = function (compareFn) { var copy = this.toArray(), arr = copy.slice().sort(compareFn); for (var i = 0, l = arr.length; i < l; i++) { this.set(i, arr[i]); } return this; }; /** * Changes the content of a List, adding new elements while removing old elements. * @return {List} */ List.prototype.splice = function () { var returnValue = [], argumentsLength = arguments.length; if (argumentsLength > 0) { var arr = this.toArray(), notifier = Object.getNotifier(this), len = this.length, self = this, index = arguments[0], i, l; returnValue = Array.prototype.splice.apply(arr, arguments); notifier.performChange("splice", function () { for (i = 0, l = arr.length; i < l; i++) { var oldValue = self[i]; if (!sameValue(oldValue, arr[i])) { self[i] = arr[i]; notifier.notify(i >= len ? { type: "add", name: i } : { type: "update", name: i, oldValue: oldValue }); } } if (len !== arr.length) { if (len > arr.length) { //delete values if the length have been decreased for (i = arr.length; i < len; i++) { self["delete"](i); } } Object.defineProperty(self, "_length", { value: arr.length, enumerable: false, configurable: true, writable: true }); notifier.notify({ type: "update", name: "length", oldValue: len }); } return { index: index, removed: returnValue, addedCount: argumentsLength >= 2 ? argumentsLength - 2 : 0 }; }); } return List.fromArray(returnValue); }; /** * Adds one or more elements to the beginning of a List and returns the new length of the List. * @return {number} */ List.prototype.unshift = function () { var argumentsLength = arguments.length; if (argumentsLength > 0) { var arr = this.toArray(), notifier = Object.getNotifier(this), len = this.length, self = this; Array.prototype.unshift.apply(arr, arguments); notifier.performChange("splice", function () { for (var i = 0, l = arr.length; i < l; i++) { var oldValue = self[i]; if (!sameValue(oldValue, arr[i])) { // avoid the usage of the set function and manually // set the value and notify the changes to avoid the notification of // multiple length modification self[i] = arr[i]; notifier.notify(i >= len ? { type: "add", name: i } : { type: "update", name: i, oldValue: oldValue }); } } if (len !== arr.length) { if (len > arr.length) { //delete values if the length have been decreased for (i = arr.length; i < len; i++) { self["delete"](i); } } Object.defineProperty(self, "_length", { value: arr.length, enumerable: false, configurable: true, writable: true }); notifier.notify({ type: "update", name: "length", oldValue: len }); } return { index: 0, removed: [], addedCount: argumentsLength }; }); } return this.length; }; /** * Apply a function against an accumulator and each value of the List (from left-to-right) as to reduce it to a single value. * @param {function} callback * @param {Object} [initialValue] * @return {Object} */ List.prototype.reduce = Array.prototype.reduce; /** * Apply a function simultaneously against two values of the array (from right-to-left) as to reduce it to a single value. * @param {function} callback * @param {Object} [initialValue] * @return {Object} */ List.prototype.reduceRight = Array.prototype.reduceRight; /** * Returns the first index at which a given element can be found in the List, or -1 if it is not present. * @param {Object} searchElement * @param {number} [fromIndex] * @return {number} */ List.prototype.indexOf = Array.prototype.indexOf; /** * Returns the last index at which a given element can be found in the List, or -1 if it is not present. The List is searched backwards, starting at fromIndex. * @param {Object} searchElement * @param {number} [fromIndex] * @return {number} */ List.prototype.lastIndexOf = Array.prototype.lastIndexOf; /** * Tests whether all elements in the List pass the test implemented by the provided function. * @param {function} callback * @param {Object} [thisObject] * @return {boolean} */ List.prototype.every = Array.prototype.every; /** * Creates a new List with all elements that pass the test implemented by the provided function * @param {function} callback * @param {Object} [thisObject] * @return {List} */ List.prototype.filter = function (callback, thisObject) { return List.fromArray(this.toArray().filter(callback, thisObject)); }; /** * Executes a provided function once per List element. * @param {function} callback * @param {Object} [thisObject] * @return {void} */ List.prototype.forEach = Array.prototype.forEach; /** * Creates a new List with the results of calling a provided function on every element in this List. * @param {function} callback * @param {Object} [thisObject] * @return {List} */ List.prototype.map = function (callback, thisObject) { return List.fromArray(this.toArray().map(callback, thisObject)); }; /** * Tests whether some element in the List passes the test implemented by the provided function. * @param {function} callback * @param {Object} [thisObject] * @return {boolean} */ List.prototype.some = Array.prototype.some; ObserveUtils.List = List; })(this); /** * Model */ var Model = (function () { var Model = function Model(properties) { var _this = this; properties = properties || {}; for (var key in properties) { this[key] = properties[key]; } this.observableProperties = {}; Object.observe(this, function (changes) { changes.forEach(function (change) { var handlers = _this.observableProperties[change.name]; if (handlers) { handlers.forEach(function (handler) { handler(change); }); } }); }); }; Model.prototype.on = function (property, handler) { if (typeof handler !== "function") { return; } if (!this.observableProperties[property]) { ObserveUtils.defineObservableProperties(this, property); this.observableProperties[property] = []; } this.observableProperties[property].push(handler); }; return Model; })(); /** * View */ var events = {}; var View = (function () { var View = function View(options) { options = options || {}; for (var key in options) { this[key] = options[key]; } if (!this.el) { this.el = document.createElement("div"); } }; View.prototype.init = function (controller) { this.controller = controller; return this; }; View.prototype.layout = function (template) { return template; }; View.prototype.render = function (params) { var innerHTML = ""; if (params) { if (typeof params === "object") { for (var index in params) { var param = params[index]; innerHTML += this.template(param); } } else { for (var i = 0; i < params.length; i++) { var param = params[i]; innerHTML += this.template(param); } } } else { innerHTML = this.template(); } this.el.innerHTML = this.layout(innerHTML); }; View.prototype.template = function () { return ""; }; View.prototype.$ = function (selector) { return this.el.querySelector(selector); }; View.prototype.$$ = function (selector) { return this.el.querySelectorAll(selector); }; View.prototype.on = function (type, selector, handler, scope) { var controller = this.controller; scope = scope || this.el; if (!events[type]) { events[type] = []; window.addEventListener(type, delegateHandler, true); } events[type].push({ selector: selector, handler: handler, controller: controller, scope: scope }); }; View.prototype.off = function (type, selector, handler) { if (!events[type]) { return; } events[type] = events[type].filter(function (delegate) { if (typeof handler === "function") { return delegate.selector !== selector || delegate.handler !== handler; } return delegate.selector !== selector; }); }; return View; })(); /** * Forward an event based on the target's `[data-action]` attr to the controller. * e.g. "click" on a `\n \n \n \n \n"; proto.createdCallback = function () { this.shadow = this.createShadowRoot(); this.shadow.innerHTML = template; this.settingsButton = this.shadow.querySelector("[data-action=\"settings\"]"); this.gaiaDomTree = this.shadow.querySelector("customizer-gaia-dom-tree"); this.gaiaToolbar = this.shadow.querySelector("customizer-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 node var headTreeNode = this.gaiaDomTree.treeMap.get(head); if (headTreeNode) { this.gaiaDomTree.expandNode(headTreeNode); } // Pre-expand the node var bodyTreeNode = this.gaiaDomTree.treeMap.get(body); if (bodyTreeNode) { this.gaiaDomTree.expandNode(bodyTreeNode); } }; proto._watchScrolling = function () { var _this4 = this; this.gaiaDomTree.shadowRoot.addEventListener("scroll", function (evt) { if (_this4._scrollTimeout) { clearTimeout(_this4._scrollTimeout); } _this4._scrollTimeout = setTimeout(function () { _this4.settingsButton.classList.remove("scrolling"); }, 500); _this4.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 _this5 = 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 (!_this5._shadowContains(mutations[i].target)) { var selectedNode = _this5.gaiaDomTree.selectedNode; _this5.gaiaDomTree.render(); _this5.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); (function (window) { "use strict"; var HTML_ESCAPE_CHARS = { "&": "&", "<": "<", ">": ">", "\"": """, "'": "'", "/": "/" }; var CONSTANT_PROPERTY_REGEX = /^[A-Z_]+$/; var proto = Object.create(HTMLElement.prototype); var template = "\n
\n\n Enter new attribute name\n"; proto.createdCallback = function () { var _this6 = 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 was clicked. var target = evt.target.closest("a"); if (target) { evt.preventDefault(); // Handle case where was clicked. if (evt.target.dataset.action === "remove") { _this6._handleRemoveAttributeClick(target); } // Otherwise, treat as regular list item click. else { _this6._handleListItemClick(target); } return; } // Handle case where was clicked. target = evt.target.closest("customizer-gaia-button"); if (target) { evt.preventDefault(); _this6._handleButtonClick(target); return; } }); // Filter the list of properties 500ms after a keypress // in the search box. this.container.addEventListener("keyup", function (evt) { var textInput = evt.target.closest("customizer-gaia-text-input"); if (!textInput) { return; } clearTimeout(_this6._searchTimeout); _this6._searchTimeout = setTimeout(function () { var value = textInput.value.toLowerCase(); var items = [].slice.apply(_this6.container.querySelectorAll("customizer-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 // is opened. this.newAttributeName.addEventListener("opened", function () { _this6.newAttributeName.els.input.focus(); }); // Reset the value when closed. this.newAttributeName.addEventListener("closed", function () { _this6.newAttributeName.els.input.value = ""; }); // Create a new attribute when the // is submitted. this.newAttributeName.els.submit.addEventListener("click", function () { try { var name = _this6.newAttributeName.els.input.value; var attribute = document.createAttribute(name); _this6.target.setNamedItem(attribute); _this6.dispatchEvent(new CustomEvent("createattribute", { detail: JSON.stringify({ keyPath: _this6.keyPath, expression: "['" + _this6.keyPath.join("']['") + "']", name: name }) })); } catch (e) { window.alert("Invalid attribute name"); return; } _this6.render(); }); }; proto.render = function () { var _this7 = this; clearTimeout(this._searchTimeout); this.target = this.rootTarget; this.keyPath.forEach(function (key) { return _this7.target = _this7.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 _this8 = this; switch (target.dataset.action) { case "cancel": this.keyPath.pop(); break; case "save": (function () { var value = _this8.container.querySelector("fxos-code-editor").value; var target = _this8.rootTarget; _this8.keyPath.forEach(function (key, index) { if (index < _this8.keyPath.length - 1) { target = target[key]; return; } target[key] = value; }); _this8.dispatchEvent(new CustomEvent("save", { detail: JSON.stringify({ keyPath: _this8.keyPath, expression: "['" + _this8.keyPath.join("']['") + "']", value: value }) })); _this8.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 = "" + target + "\n
\n Cancel\n Save\n
"; return page; } /** * Helper for rendering the list page. */ function renderTargetList(target, isRoot) { var list = document.createElement("customizer-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 = "
\n \n
\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 = "

" + property + " = " + value + "

"; // Append "X" button to remove attributes. if (target instanceof window.NamedNodeMap) { a.innerHTML += ""; } return a; } /** * Helper for rendering the "Up One Level [..]" list item. */ function renderBackItem() { var a = document.createElement("a"); a.href = "../"; a.innerHTML = "

Up One Level [..]

"; return a; } /** * Helper for rendering the "Create New Attribute" * list item. */ function renderAddAttributeItem() { var a = document.createElement("a"); a.href = "#add-attribute"; a.innerHTML = "

Create New Attribute

\n"; 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); /* global Controller */ /* global AddonService */ 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); /* global Controller */ /* global AddonService */ 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 _this9 = this; AddonService.generate(this.target, function (generator) { var op = generator["op" + _this9.mode + "Before"]; op.call(generator, _this9.destination); _this9.view.dialog.close(); _this9.view.modal.close(); }); }; CopyMoveController.prototype.after = function () { var _this10 = this; AddonService.generate(this.target, function (generator) { var op = generator["op" + _this10.mode + "After"]; op.call(generator, _this10.destination); _this10.view.dialog.close(); _this10.view.modal.close(); }); }; CopyMoveController.prototype.prepend = function () { var _this11 = this; AddonService.generate(this.target, function (generator) { var op = generator["op" + _this11.mode + "Prepend"]; op.call(generator, _this11.destination); _this11.view.dialog.close(); _this11.view.modal.close(); }); }; CopyMoveController.prototype.append = function () { var _this12 = this; AddonService.generate(this.target, function (generator) { var op = generator["op" + _this12.mode + "Append"]; op.call(generator, _this12.destination); _this12.view.dialog.close(); _this12.view.modal.close(); }); }; return CopyMoveController; })(Controller); /* global Controller */ /* global AddonService */ 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 _this13 = this; AddonService.generate(this.target, function (generator) { if (_this13.changes.innerHTML) { generator.opInnerHTML(_this13.changes.innerHTML); } if (_this13.changes.script) { generator.opScript(_this13.changes.script); } if (_this13.changes.createAttributes) { generator.opCreateAttributes(_this13.changes.createAttributes); } if (_this13.changes.removeAttributes) { generator.opRemoveAttributes(_this13.changes.removeAttributes); } if (_this13.changes.properties) { generator.opSetProperties(_this13.changes.properties); } _this13.changes = {}; _this13.close(); }); }; return EditController; })(Controller); /*global MozActivity*/ /*global EditView*/ /*global ViewSourceView*/ /*global AppendChildView*/ /*global CopyMoveView*/ /*global MainView*/ /*global EditController*/ /*global ViewSourceController*/ /*global AppendChildController*/ /*global CopyMoveController*/ /*global Controller*/ /*global Gesture*/ 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 _this14 = this; return new Promise(function (resolve, reject) { /*jshint evil:true*/ if (_this14.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 _this15 = 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 }); _this15.editController = new EditController({ view: editView }); _this15.viewSourceController = new ViewSourceController({ view: viewSourceView }); _this15.appendChildController = new AppendChildController({ view: appendChildView }); _this15.copyMoveController = new CopyMoveController({ view: copyMoveView }); _this15.view = mainView; mainView.init(_this15); console.log("[Customizer] Initialized controllers and views"); resolve(); }); }; MainController.prototype._waitToBeOpened = function () { var _this16 = 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 _this16.open(); }); }; MainController.prototype._waitToBeClosed = function () { var _this17 = 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 _this17.close(); }); }; MainController.prototype._checkOpenFromLauncher = function () { var _this18 = this; var requestXHR = new XMLHttpRequest(); try { requestXHR.open("GET", "http://localhost:3215/request", true); requestXHR.onload = function () { if (requestXHR.responseText !== _this18.manifestURL) { return; } _this18.open(); var confirmXHR = new XMLHttpRequest(); confirmXHR.open("GET", "http://localhost:3215/confirm?url=" + _this18.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 _this19 = this; if (this._isOpen) { return; } this._isOpen = true; this._lazyLoadModules().then(function () { return _this19._initControllers(); }).then(function () { return _this19.view.open(); }).then(function () { _this19.view.customizer.setRootNode(document.documentElement); _this19._waitToBeClosed(); }); }; MainController.prototype.close = function () { var _this20 = this; if (!this._isOpen) { return; } this.view.close().then(function () { return _this20.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); /*global Controller*/ // When injected in the System app, this selector can identify // the currently-focused