diff options
author | 2013-06-07 11:59:57 -0700 | |
---|---|---|
committer | 2013-06-07 11:59:57 -0700 | |
commit | 8690d56ff0bbd1031e8cc6788dd2159aac6b7adb (patch) | |
tree | d573336305dc756f357f20dd986f77cb3ceafb9b /src/js/fiveui/js | |
parent | f42930c2226d07482725b03ad522c106c0bdec8b (diff) |
Reorganize the build system
* Move all javascript source to src/js
* Update the test runner, etc.
* Do less javascript compilation
Diffstat (limited to 'src/js/fiveui/js')
-rw-r--r-- | src/js/fiveui/js/background.js | 210 | ||||
-rw-r--r-- | src/js/fiveui/js/chan.js | 53 | ||||
-rw-r--r-- | src/js/fiveui/js/entry.js | 277 | ||||
-rw-r--r-- | src/js/fiveui/js/ffcheck.js | 3 | ||||
-rw-r--r-- | src/js/fiveui/js/messenger.js | 141 | ||||
-rw-r--r-- | src/js/fiveui/js/options.js | 217 | ||||
-rw-r--r-- | src/js/fiveui/js/rules.js | 292 | ||||
-rw-r--r-- | src/js/fiveui/js/set.js | 58 | ||||
-rw-r--r-- | src/js/fiveui/js/settings.js | 389 | ||||
-rw-r--r-- | src/js/fiveui/js/state.js | 202 | ||||
-rw-r--r-- | src/js/fiveui/js/update-manager.js | 51 | ||||
-rw-r--r-- | src/js/fiveui/js/url-pat.js | 140 | ||||
-rw-r--r-- | src/js/fiveui/js/utils.js | 145 |
13 files changed, 2178 insertions, 0 deletions
diff --git a/src/js/fiveui/js/background.js b/src/js/fiveui/js/background.js new file mode 100644 index 0000000..c0d4e07 --- /dev/null +++ b/src/js/fiveui/js/background.js @@ -0,0 +1,210 @@ +/* + * Module : background.js + * Copyright : (c) 2011-2012, Galois, Inc. + * + * Maintainer : + * Stability : Provisional + * Portability: Portable + * + * 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. + */ + +var fiveui = fiveui || {}; + +(function() { +/** + * @constructor + * + * @param {!function(!string):!string} dataLoader + */ +fiveui.Background = function(settings, updateWidget, loadScripts, dataLoader) { + // Initialize a storage mechanism for FiveUI. + // Chrome utilizes HTML5's localStorage API as a backend, but other + // contexts (such as Firefox) would supply a different backend + // storage to the settings constructor. + // + // The data in settings is persisted across executions of the + // context and the plugin. It is used for long-term storage of user + // preferences, rule sets, and similarly long-lived FiveUI settings. + this.settings = settings; + + // the state of all managed tabs + this.state = new fiveui.State(settings); + + // how we communicate with the status widget + this.updateWidget = updateWidget; + + // how we signal the browser to load a content script + this.loadScripts = loadScripts; + + this.dataLoader = dataLoader; +}; + +fiveui.Background.prototype._registerComputeListeners = function(port, tabState){ + var bg = this; + port.on('ReportProblem', function(request) { + var problem = fiveui.Problem.fromJSON(request); + if(tabState.addProblem(problem) && tabState.uiPort != null) { + bg.updateWidget(tabState); + tabState.uiPort.emit('ShowProblem', problem); + } + }); + port.on('ReportStats', function (stats) { + if (tabState.addStats(stats) && tabState.uiPort != null) { + bg.updateWidget(tabState); + tabState.uiPort.emit('ShowStats', stats); + } + }); +}; + +fiveui.Background.prototype._registerUiListeners = function(port, tabState){ + var bg = this; + port.on('Position', function(request) { + tabState.winState.x = request.left; + tabState.winState.y = request.top; + }); + port.on('Size', function(request) { + tabState.winState.width = request.width; + tabState.winState.height = request.height; + }); + port.on('CloseUI', function(request) { + tabState.winState.closed = true; + }); + port.on('ClearProblems', function(request) { + tabState.clearProblems(); + bg.updateWidget(tabState); + }); + port.on('MaskRules', function(request) { + _.each(tabState.computePorts, function(cp) { + cp.emit('MaskRules', null); + }); + }); + port.on('UnmaskRules', function(request) { + _.each(tabState.computePorts, function(cp) { + cp.emit('UnmaskRules', null); + }); + }); +}; + +/** + * Accept a new connection from a content script. + */ +fiveui.Background.prototype.connect = function(tabId, port, url, isUiPort) { + + var tabState = this.state.acquireTabState(tabId); + + if (isUiPort) { + tabState.uiPort = port; + this._registerUiListeners(port, tabState); + port.emit('RestoreUI', tabState.toEmit()); + this.updateWidget(tabState); + } else { + tabState.computePorts.push(port); + this._registerComputeListeners(port, tabState); + + // get the rule set and send it down to the injected page: + var pat = this.settings.checkUrl(url); + if (!pat) { + console.err('could not find url pattern for tab.url, but one was strongly expected'); + } else { + var ruleSet = this.settings.getRuleSet(pat.rule_id); + + port.emit('SetRules', ruleSet); + } + } +}; + +/** + * @param {!number} tabId + * @param {!string} url + * @param {*} data + */ +fiveui.Background.prototype.pageLoad = function(tabId, url, data) { + var pat = this.settings.checkUrl(url); + + if (null == pat) { + this.updateWidget(null); + } else { + var tabState = this.state.acquireTabState(tabId); + tabState.computePorts = []; + + this.updateWidget(tabState); + + var dependencies = []; + var ruleSet = this.settings.getRuleSet(pat.rule_id); + + if (ruleSet && ruleSet.dependencies ) { + dependencies = ruleSet.dependencies; + } + + var computeScripts = _.flatten( + [ [ this.dataLoader('jquery/jquery-1.8.3.js') + , this.dataLoader('md5.js') + , this.dataLoader('injected/prelude.js') + , this.dataLoader('injected/jquery-plugins.js') + ] + , dependencies + , [ this.dataLoader('injected/compute.js') + ] + ]); + this.loadScripts(tabId, computeScripts, true, data); + + var uiScripts = _.flatten( + [ this.dataLoader('jquery/bundled.css') + , this.dataLoader('jquery/jquery-1.8.3.js') + , this.dataLoader('jquery/jquery-ui-1.9.2.custom.js') + , this.dataLoader('injected/injected.css') + , this.dataLoader('injected/prelude.js') + , this.dataLoader('injected/ui.js') + , this.dataLoader('injected/jquery-plugins.js') + ]); + this.loadScripts(tabId, uiScripts, false, data); + } +}; + +/** + * Updates the widget according to the tab state of the specified tab. + * + * @param {!number} tabId Id of the tab that is currently active, and + * thus, dictating the widget display. + */ +fiveui.Background.prototype.activate = function(tabId) { + var tabState = this.state.getTabState(tabId); + this.updateWidget(tabState); +}; + +/** + * Stop tracking the state of a tab. + * + * @param {!number} tabId Id of the tab to free the state of. + */ +fiveui.Background.prototype.removeTab = function(tabId) { + this.state.removeTabState(tabId); +}; + +/** + * Request that the user interface be restored, if it is closed. + */ +fiveui.Background.prototype.showUI = function(tabId) { + var tabState = this.state.getTabState(tabId); + if(null == tabState) { + return; + } + + if(tabState.winState.closed) { + tabState.winState.closed = false; + tabState.uiPort.emit('ShowUI', null); + } +}; + +})(); diff --git a/src/js/fiveui/js/chan.js b/src/js/fiveui/js/chan.js new file mode 100644 index 0000000..47c6145 --- /dev/null +++ b/src/js/fiveui/js/chan.js @@ -0,0 +1,53 @@ +/* + * Module : chan.js + * Copyright : (c) 2011-2012, Galois, Inc. + * + * Maintainer : + * Stability : Provisional + * Portability: Portable + * + * 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. + */ + +var fiveui = fiveui || {}; + +(function() { + +/** + * @constructor + */ +fiveui.Chan = function() { + this.fns = {}; +}; + +_.extend(fiveui.Chan.prototype, { + + /** + * @param {!string} type + * @param {!function(*)} fn + */ + on: function(type, fn) { + this.fns[type] = fn; + }, + + /** + * @param {!string} type + * @param {*} data + */ + emit: function(type, data) { + this.chan.fns[type](data); + } + +}); + +})(); diff --git a/src/js/fiveui/js/entry.js b/src/js/fiveui/js/entry.js new file mode 100644 index 0000000..025034a --- /dev/null +++ b/src/js/fiveui/js/entry.js @@ -0,0 +1,277 @@ +/* + * Module : entry.js + * Copyright : (c) 2011-2012, Galois, Inc. + * + * Maintainer : + * Stability : Provisional + * Portability: Portable + * + * 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. + */ + + +var fiveui = fiveui || {}; + +(function() { + +var editable = function(el, model, placeholder) { + + el.prop('contenteditable', true).addClass('editable'); + + // prevent newlines + el.on('keypress', function(e) { + return e.which != 13; + }); + + var addPlaceholder = function() { + el.addClass('placeholder') + .text(placeholder) + .one('click keypress paste', remPlaceholder); + }; + + var remPlaceholder = function() { + el.removeClass('placeholder').text(''); + }; + + // if the model is new, set the placeholder, and a listener to clear it + if(model.isNew()) { + addPlaceholder(); + } + + el.on('blur', function() { + if(_.isEmpty(el.text())) { + addPlaceholder(); + } + }); + + el.focus(); + +}; + + +/** UrlPat Entry Elements ****************************************************/ + +fiveui.UrlPatEntry = Backbone.View.extend({ + + tagName: 'li', + + className: 'entry', + + events: { + 'click span.save' : 'save', + 'click span.remove' : 'remove', + 'click span.edit' : 'edit', + }, + + initialize:function() { + this.listenTo(this.model, 'remove', function() { + this.$el.remove(); + this.stopListening(); + }); + }, + + viewTemplate: _.template( + [ '<div>' + , ' <span class="button remove">x</span>' + , ' <span class="button edit">edit</span>' + , ' <span><%= regex %></span>' + , ' <span><%= rule_name %></span>' + , '</div>' + ].join('')), + + render:function() { + var attrs = _.clone(this.model.attributes); + var ruleSet = this.options.rules.model.findWhere({ id: attrs.rule_id }) + attrs.rule_name = ruleSet.get('name'); + this.$el.html(this.viewTemplate(attrs)); + return this; + }, + + editTemplate: _.template( + [ '<div>' + , ' <span class="button remove">x</span>' + , ' <span class="button save">save</span>' + , ' <span class="regex"></span>' + , ' <span class="rules"></span>' + , '</div>' + ].join('')), + + edit:function() { + var attrs = this.model.attributes; + this.$el.html(this.editTemplate(attrs)); + + this.$el.find('.rules').append(this.options.rules.render().$el); + editable(this.$el.find('.regex'), this.model, 'url pattern'); + return this; + }, + + remove: function() { + this.model.destroy(); + }, + + save:function() { + var regex = this.$el.find('.regex').text(); + var rule_id = parseInt(this.options.rules.$el.val()); + this.model.save({ regex : regex, rule_id : rule_id }, { + success: _.bind(this.render, this), + error: _.bind(this.edit, this) + }); + }, + +}); + + +/** Rule Set View ************************************************************/ + +fiveui.RulesView = Backbone.View.extend({ + + tagName: 'select', + + initialize:function() { + this.listenTo(this.model, 'sync', this.update); + this.listenTo(this.model, 'remove', this.update); + }, + + optionTemplate:_.template( + '<option value="<%= id %>"><%= name %></option>' + ), + + update:function() { + if(this.model.length == 0) { + return this.remove(); + } else { + return this.render(); + } + }, + + remove:function() { + this.stopListening(); + this.$el.remove(); + + this.trigger('remove'); + + return this; + }, + + render:function() { + + var scope = this; + + this.$el.children().remove(); + + var text = this.model.foldl(function(body,ruleSet) { + return body + scope.optionTemplate(ruleSet.attributes); + }, ''); + + this.$el.html(text); + + return this; + }, + +}); + + +/** Rule Entry Elements ******************************************************/ + +fiveui.RuleSetEntry = Backbone.View.extend({ + + tagName: 'li', + + className: 'entry', + + events: { + 'click .save' : 'save', + 'click .remove' : 'remove', + 'click .edit' : 'edit', + 'click .reload' : 'reload', + }, + + viewTemplate: _.template( + [ '<div class="content">' + , ' <span class="button remove">x</span>' + , ' <span class="button edit">edit</span>' + , ' <span class="button reload">reload</span>' + , ' <span class="title"><%= name %></span>' + , '</div>' + ].join('')), + + render:function() { + var attrs = this.model.attributes; + this.$el.html(this.viewTemplate(attrs)); + return this; + }, + + editTemplate: _.template( + [ '<div class="content">' + , ' <span class="button remove">x</span>' + , ' <span class="button save">save</span>' + , ' <span class="source"><%= source %></span>' + , '</div>' + ].join('')), + + edit:function() { + var attrs = this.model.attributes; + this.$el.html(this.editTemplate(attrs)); + + editable(this.$el.find('.source'), this.model, + 'http://example.com/manifest.json') + + return this; + }, + + errorTemplate: _.template('<div class="error"><%= message %></div>'), + + editError:function(target, message) { + this.edit(); + + this.$el.append(this.errorTemplate({ message: message })); + + return this; + }, + + save: function() { + var source = this.$el.find('.source').text(); + this.model.set('source', source); + this.model.save({}, { + success: _.bind(this.render, this), + error: _.bind(this.editError, this) + }); + }, + + reload:function() { + this.model.save({}, { + success: _.bind(this.render, this), + error: _.bind(this.edit, this) + }); + }, + + remove:function() { + var self = this; + + this.model.destroy({ + wait:true, + + success:function() { + self.$el.remove(); + self.stopListening(); + }, + + error:function() { + console.log('communicate failure somehow...'); + } + }); + }, + +}); + +})(); diff --git a/src/js/fiveui/js/ffcheck.js b/src/js/fiveui/js/ffcheck.js new file mode 100644 index 0000000..2157ed0 --- /dev/null +++ b/src/js/fiveui/js/ffcheck.js @@ -0,0 +1,3 @@ +if (! /Firefox[\/\s](\d+\.\d+)/.test(navigator.userAgent)) { + window.onload=fiveui.chrome.options.init; +} diff --git a/src/js/fiveui/js/messenger.js b/src/js/fiveui/js/messenger.js new file mode 100644 index 0000000..fc1962c --- /dev/null +++ b/src/js/fiveui/js/messenger.js @@ -0,0 +1,141 @@ +/* + * Module : messenger.js + * Copyright : (c) 2011-2012, Galois, Inc. + * + * Maintainer : + * Stability : Provisional + * Portability: Portable + * + * 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. + */ + +var fiveui = fiveui || {}; + +(function() { + +/** + * @constructor + * @param {{on: function(!string, function(*)), emit: function(!string, *)}} + * channel The object containing on and emit. + */ +fiveui.Messenger = function(channel) { + this.callbacks = {}; + this.handlers = {}; + + this.channel = channel; + this.channel.on(fiveui.Messenger.type, _.bind(this._handler, this)); +}; + +fiveui.Messenger.type = "fiveui_messaging_type"; + +_.extend(fiveui.Messenger.prototype, { + + /** + * @param {!string} type The message type to send. + * @param {?Object} data The payload (which can be null). + * @param {?function(?Object)} callback An optional callback to be + * invoked in response. + */ + send: function(type, data, callback){ + var id = null; + if (callback) { + id = this._newId(); + this.callbacks[id] = callback; + } + + var payload = new fiveui.Messenger.Payload(false, type, data, id); + this.channel.emit(fiveui.Messenger.type, payload); + }, + + /** + * Register a handler for the specified message type. + * + * @param {!string} type The message type. + * @param {!function(*)} callback The function to call when a message + * of the specified type is received. + */ + register: function(type, callback) { + if(null == this.handlers[type]) { + this.handlers[type] = []; + } + this.handlers[type].push(callback); + }, + + _handler: function(payload) { + if (payload.isCallback && payload.id != null) { + // this is a callback invocation, lookup the callback and invoke it: + this.callbacks[payload.id](payload.data); + + // remove the callback: + this._remove(payload.id); + } else { + // look up a handler and invoke it, passing in the response fn: + var hs = this.handlers[payload.type]; + if (hs && hs.length > 0) { + + // this is a new incomming message. + // create a response function: + var respond = function(respData) { + this.channel.emit(fiveui.Messenger.type, + new fiveui.Messenger.Payload(true, payload.type, respData, payload.id)); + }; + + // iterate over the handlers, invoking them with the response callback. + _.each(hs, function(h) { + h(payload.data, _.bind(respond, this)); + }, this); + } + } + }, + + /** + * Remove a callback from the map of callbacks. + * + * @param {!number} callbackId The id of the callback to remove. + */ + _remove: function(callbackId) { + delete this.callbacks[callbackId]; + }, + + /** + * @return {!number} The next unique id for a callback. + */ + _newId: function() { + var list = Object.keys(this.callbacks); + return fiveui.utils.getNewId(list); + } + +}); + +/** + * @constructor + * @param {!boolean} isCallback True if this is in response to a + * message, false if this is requesting a callback. + * @param {!string} type + * @param {?Object} data + * @param {!number} id Callback id to invoke, or in which this is a response. + */ +fiveui.Messenger.Payload = function(isCallback, type, data, id) { + this.isCallback = isCallback; + this.type = type; + this.id = id; + this.__defineGetter__('data', function() { + return JSON.parse(this.rawData); + }); + this.__defineSetter__('data', function(obj){ + this.rawData = JSON.stringify(obj); + }); + this.data = data; +}; + +})(); diff --git a/src/js/fiveui/js/options.js b/src/js/fiveui/js/options.js new file mode 100644 index 0000000..2b29f8d --- /dev/null +++ b/src/js/fiveui/js/options.js @@ -0,0 +1,217 @@ +/* + * Module : options.js + * Copyright : (c) 2011-2012, Galois, Inc. + * + * Maintainer : + * Stability : Provisional + * Portability: Portable + * + * 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. + */ + +var fiveui = fiveui || {}; + +(function() { + + +/***************************************************************** + * + * Misc. utility functions + * + ****************************************************************/ + +var setClickHandler = function(sel, fn, scope) { + var boundFn = fn; + if (scope) { + boundFn = _.bind(fn, scope); + } + + sel.on('click', boundFn) +}; + +/** + * @param {!string} selectEltId The id of the select element to + * search for selected items. + * @return {!Array.<!number>} An array of 'value' entries from the + * selected elements. This may be empty, but it will not be null. + */ +var findSelectedIds = function(elt) { + var opts = elt.find('option:checked'); + return _.map(opts, function(o) { return o.value; }); +}; + + +fiveui.options = fiveui.options || {}; + +/** + * @param {{on: function(!string, function(*)), emit: function(!string, *)}} port + */ +fiveui.options.init = function(port) { + + var msg = new fiveui.Messenger(port); + var update = new fiveui.UpdateManager(msg); + + ruleSets = new fiveui.RuleSets([], { url: msg }); + urlPats = new fiveui.UrlPats([], { url: msg }); + + + /** UrlPat list entries ****************************************************/ + + var urlPatEntries = jQuery('#urlPatEntries'); + var addUrlPat = jQuery('#addUrlPat'); + + addUrlPat.prop('disabled', true); + + addUrlPat.on('click', function() { + urlPats.add(new fiveui.UrlPatModel({}, { url : msg })); + }); + + // when a new rule set is sync'd, make sure that the add url pattern button is + // enabled. + ruleSets.on('sync', function() { + if(ruleSets.length > 0) { + addUrlPat.prop('disabled', false); + } + }); + + // when a rule set is destroyed, and the collection is now empty, disable the + // add url pattern button. + ruleSets.on('destroy', function(model,col) { + if(col.length <= 0) { + addUrlPat.prop('disabled', true); + } + }); + + // handle new url patterns being added to the collection. + urlPats.on('add', function(model) { + var view = new fiveui.UrlPatEntry({ + model: model, + rules: new fiveui.RulesView({ model: ruleSets }) + }); + urlPatEntries.append(view.$el); + + if(model.isNew()) { + view.edit(); + } else { + view.render(); + } + }); + + + /** RuleSet list entries ***************************************************/ + + var ruleSetEntries = jQuery('#ruleSetEntries'); + + // handle clicks to the 'add' button on the rule sets page + jQuery('#addRsButton').on('click', function() { + ruleSets.add(new fiveui.RuleSetModel({}, { url : msg })); + }); + + // render a ruleset added to the collection + ruleSets.on('add', function(model) { + var entry = new fiveui.RuleSetEntry({ model: model }) + ruleSetEntries.append(entry.$el); + + if(model.isNew()) { + entry.edit(); + } else { + addUrlPat.prop('disabled', false); + entry.render(); + } + }); + + + /** Basics *****************************************************************/ + + var windowDisplayDefault = jQuery('#windowDisplayDefault'); + + windowDisplayDefault.on('change', function() { + msg.send('setDisplayDefault', windowDisplayDefault.prop('checked')) + }); + + + /** Tab Management *********************************************************/ + + /** + * Select a tab header by Element reference. + * + * @param {!Element} clicked The navigation element to focus. + * @return {void} + */ + var selectNav = function(clicked) { + var nav = clicked.parent(); + + nav.find('div.selected', nav).removeClass('selected'); + + jQuery('div.editorPane').hide(); + + clicked.addClass('selected'); + }; + + /** + * Focus a tab, by Element reference. + * + * @param {!Element} el The tab element to focus. + * @return {void} + */ + var selectSection = function(el) { + var cont = el.parent(); + + // hide all sections + cont.find('>section').removeClass('selected').hide(); + + // display this section + el.addClass('selected').show(); + }; + + /** + * A combination of selectNav and selectSection that will first lookup the + * elements associated with a tab, then return a new function that when + * called, will focus both the navigation element, and the tab it controls. + * + * @param {!string} id The id of the tab to focus. + * @return {function()} + */ + var select = function(id) { + var sel = jQuery(id); + return function() { + selectNav(jQuery(this)); + selectSection(sel); + }; + }; + + // listen to click events on navigation elements + setClickHandler(jQuery('#url-defaults'), select('#tab-url-defaults')); + setClickHandler(jQuery('#rule-sets'), select('#tab-rule-sets')); + setClickHandler(jQuery('#basics'), select('#tab-basics')); + + // select the url patterns tab by default + selectNav(jQuery('#url-defaults')); + selectSection(jQuery('#tab-url-defaults')); + + + /** Pre-populate UI elements ***********************************************/ + + msg.send('getDisplayDefault', null, function(def) { + jQuery('#windowDisplayDefault').prop('checked', def); + }); + + // pre-populate the rule set and url pattern lists + ruleSets.fetch({ + success:function() { + urlPats.fetch(); + } + }); +}; + +})(); diff --git a/src/js/fiveui/js/rules.js b/src/js/fiveui/js/rules.js new file mode 100644 index 0000000..314d50b --- /dev/null +++ b/src/js/fiveui/js/rules.js @@ -0,0 +1,292 @@ +/* + * Module : rules.js + * Copyright : (c) 2011-2012, Galois, Inc. + * + * Maintainer : + * Stability : Provisional + * Portability: Portable + * + * 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. + */ + +var fiveui = fiveui || {}; + +(function() { + +/** + * @constructor + * @param {!string} module A Javascript module that defines the rule. + */ +fiveui.Rule = function(module) { + this.module = module; +}; + +fiveui.Rule.defaults = function(obj) { + return _.defaults(obj, { + module: '', + }); +}; + +/** + * Create a Rule from a JSON object. + * + * @param {!Object} obj The object to take settings from. + * @return {!fiveui.Rule} A populated Rule object. + */ +fiveui.Rule.fromJSON = function(obj) { + return new fiveui.Rule(obj.module); +}; + +/** + * @constructor + * @param {!number} id The unique RuleSet identifier. + * @param {!string} name A human-readable name for this RuleSet. + * @param {!string} desc A human-readable description of the Rule Set. + * @param {!string} source The url where the manifest can be retrieved + * @param {!Array.<fiveui.Rule>} rules An Array of Rules. + * @param {?Array.<string>} deps Dependencies that this RuleSet requires. + */ +fiveui.RuleSet = function(id, name, desc, source, rules, deps) { + this.id = id; + this.name = name; + this.description = desc; + this.source = source; + this.rules = rules || []; + this.dependencies = deps || []; +}; + +/** + * Create a Rule Setfrom a JSON object. + * + * @param {!number} id A unique id for the rehydrated Rule. + * @param {!Object} obj The object to take settings from. + * @return {!fiveui.RuleSet} A populated RuleSet object. + */ +fiveui.RuleSet.fromJSON = function(id, obj) { + var rules = (/** @type {!Array.<!fiveui.Rule>} */ + _.map(obj.rules, fiveui.Rule.fromJSON)); + + return new fiveui.RuleSet(id, obj.name, obj.description, obj.source, + rules, obj.dependencies); +}; + + +fiveui.RuleSet.defaults = function(obj) { + return _.defaults(obj, { + name: '', + description: '', + rules: [], + dependencies: [] + }); +}; + + +/** + * Options is an object that can contain a success and error continuation. + */ +fiveui.RuleSet.load = function(manifest_url, options) { + + _.defaults(options, { + success: function() {}, + error: function() { throw "failed when loading url"; } + }); + + var match = manifest_url.match(/\/[^\/]*$/); + + if(match) { + var base_url = manifest_url.substring(0,match.index); + + // iterate over rules, retrieving the + var loadRules = function(manifest, rules) { + + if(rules.length == 0) { + options.success(manifest); + } else { + + // XXX there's likely problems here, how should we make sure that the + // url is what we expect? + var rule_file = fiveui.Rule.defaults(rules.pop()); + var rule_url = base_url + '/' + rule_file; + + fiveui.ajax.get(rule_url, { + + success: function(text) { + manifest.rules.push(new fiveui.Rule(text)); + loadRules(manifest, rules); + }, + + error: options.error + }); + + } + }; + + // fetch the manifest, and load its rules + fiveui.ajax.get(manifest_url, { + + success: function(text) { + try { + var sanitized = fiveui.utils.filterJSON(text,'json'); + var manifest = JSON.parse(sanitized); + + } catch(e) { + // XXX incoming error continuation is empty + // (and we may have syntax error details in e) + options.error('failed to parse manifest'); + return; + } + + fiveui.RuleSet.defaults(manifest); + + var rules = manifest.rules; + manifest.rules = []; + loadRules(manifest, rules); + }, + + error: function() { + options.error('failed to retrieve manifest'); + }, + }); + + + } else { + options.error("unable to parse manifest url"); + } + +}; + + +/******************************************************************************* + * Models for RuleSet + ******************************************************************************/ + +/** + * The model for an single set of rules. + */ +fiveui.RuleSetModel = Backbone.Model.extend({ + + defaults: { + id: null, + name: '', + description: '', + source: '', + rules: [], + dependencies: [], + }, + + sync: function(method, model, options) { + + _.defaults(options, { + success:function() {}, + error: function() {} + }); + + var msg = this.url; + var id = model.get('id'); + var source = model.get('source'); + + switch(method) { + + case 'update': + case 'create': + var rsMethod = method == 'update' ? 'updateRuleSet' : 'addRuleSet'; + + msg.send('loadRuleSet', source, function(obj) { + if(!obj.error) { + obj.id = id; + obj.source = source; + + msg.send(rsMethod, obj, options.success); + } else { + options.error(obj.error); + } + }); + break; + + case 'delete': + msg.send('remRuleSet', id, function(obj) { + if(obj.removed) { + options.success(); + } else { + options.error(); + } + }); + break; + + case 'read': + msg.send('getRuleSet', id, function(rs) { + model.set({ + title: rs.name, + descr: rs.description, + source: rs.source, + }); + }); + break; + + default: + break; + } + } + +}, { + + /** + * Generate a RuleSetModel from a RuleSet + */ + fromRuleSet: function(ruleSet,msg) { + return new fiveui.RuleSetModel({ + id: ruleSet.id, + name: ruleSet.name, + description: ruleSet.description, + rules: ruleSet.rules, + dependencies:ruleSet.dependencies, + source: ruleSet.source, + }, { url : msg }); + }, + +}); + + +/** + * The model for a collection of rule sets + */ +fiveui.RuleSets = Backbone.Collection.extend({ + + model: fiveui.RuleSetModel, + + sync: function(method, collection, options) { + _.defaults(options, { + success:function() {}, + error:function() {} + }); + + var self = this; + var msg = this.url; + + switch(method) { + + case 'read': + msg.send('getRuleSets', null, function(ruleSets) { + options.success(_.map(ruleSets, function(rs) { + return fiveui.RuleSetModel.fromRuleSet(rs, msg); + })); + }); + break; + + } + } + +}); + + +})(); diff --git a/src/js/fiveui/js/set.js b/src/js/fiveui/js/set.js new file mode 100644 index 0000000..7015a3e --- /dev/null +++ b/src/js/fiveui/js/set.js @@ -0,0 +1,58 @@ + + +(function() { + +Set = function() { + this.elems = {}; +}; + +_.extend(Set.prototype, { + + add: function(obj) { + var hash = this._getHash(obj); + if(this.elems[hash] == undefined) { + this.elems[hash] = obj; + } + }, + + remove: function(obj) { + var hash = this._getHash(obj); + if(this.elems[hash]) { + delete this.elems[hash]; + } + }, + + member: function(obj) { + var hash = this._getHash(obj); + return !!this.elems[hash]; + }, + + contains: function(obj) { + return this.member(obj); + }, + + size: function () { + return _.size(this.elems); + }, + + isEmpty: function() { + return this.size() == 0; + }, + + each: function(k, cxt) { + _.each(this.elems, k, cxt); + }, + + _getHash: function(obj) { + var str = obj.toString(); + + // the same hash function that java uses for String.hashCode + return _.reduce(str, function(hash, c) { + hash = ((hash << 5) - hash) + c.charCodeAt(); + return hash & hash; + }, 0); + } + +}); + +})(); diff --git a/src/js/fiveui/js/settings.js b/src/js/fiveui/js/settings.js new file mode 100644 index 0000000..a17a9e0 --- /dev/null +++ b/src/js/fiveui/js/settings.js @@ -0,0 +1,389 @@ +/* + * Module : settings.js + * Copyright : (c) 2011-2012, Galois, Inc. + * + * Maintainer : + * Stability : Provisional + * Portability: Portable + * + * 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. + */ + +var fiveui = fiveui || {}; + +(function() { + +/** + * Create a new instance of the Settings object + * + * @constructor + * @param {!Storage} store The Storage instance to use. + */ +fiveui.Settings = function(store) { + this.store = store; +}; + +_.extend(fiveui.Settings.prototype, { + + /** + * Retrieve an object associated with the key. + * + * @param {!string} key The key to look up. + * @return {?Object} The returned JavaScript object. + */ + get: function(key) { + var value = this.store.getItem(key); + if (value == null) { + return null; + } else { + return JSON.parse(value); + } + }, + + /** + * Store an object with the given key. + * + * @param {string} key the key. + * @param {Object} value the value. + * @return {void} + */ + set: function(key, value) { + this.store.setItem(key, JSON.stringify(value)); + }, + + /** + * @param {string} key The local storage entry to remove. + * @return {void} + */ + remove: function(key) { + this.store.removeItem(key); + }, + + /** + * Set the display default. + * + * @param {!boolean} def Whether or not to display the FiveUI Window + * when problems are reported. + */ + setDisplayDefault: function(def) { + this.set('displayDefault', def); + }, + + /** + * Get the display default. + * + * @return {!boolean} true to show the window as soon as problems are + * reported, false otherwise. + */ + getDisplayDefault: function() { + var def = this.get('displayDefault'); + + // double negation to normalize funny things like null + return !!def; + }, + + /** + * Add a mapping from url patterns to RuleSets (via rule set ids) + * + * @param {!string} url_pat A regular expression (actually, a glob) to match URLs against. + * @param {!number} rule_id The id of the RuleSet to use with any matching URL. + * @return {!number} The id of the new url pattern. + */ + addUrl: function(url_pat, rule_id) { + var pats = this.getUrls(); + + var new_id = fiveui.utils.getNewId(pats); + + this.updateUrl(new_id, url_pat, rule_id); + + // add it to the patterns list + pats.push(new_id); + this.set('urls', pats); + + return new_id; + }, + + updateUrl: function(id, url_pat, rule_id) { + this.set('urls.' + id, new fiveui.UrlPat(id, url_pat, rule_id)); + return id; + }, + + /** + * Retrieve the list of url patterns. + * + * @return {Array.<number>} An ordered list of the currently active + * url patterns. + */ + getUrls: function() { + return (/** @type {Array.<number>} */ this.get('urls')) || []; + }, + + /** + * Retrieve a specific url pattern. + * + * @param {!number} url_id The id of the url pattern to retrieve. + * @return {?fiveui.UrlPat} The matching UrlPat or null, + * if no pattern exists for url_id. + */ + getUrlPat: function(url_id) { + return /** @type {?fiveui.UrlPat} */ this.getById(url_id, 'urls', fiveui.UrlPat.fromJSON); + }, + + /** + * Remove a UrlPat from the persistent storage. + * + * @param {!number} pat_id The id of the UrlPat to remove. + * @return {void} + */ + remUrlPat: function(pat_id) { + this.remById(pat_id, 'urls'); + }, + + /** + * @param {!string} url A url to compare against the list of ordered + * url patterns in local storage. + * @return {?fiveui.UrlPat} The matching pattern, or null, if no + * mapping was found. + */ + checkUrl: function(url) { + var pats = this.getUrls(); + + // check for a possible match + for (var i = 0; i < pats.length; ++i) { + var pat = this.getUrlPat(pats[i]); + if (pat.match(url)) { + return pat; + } + } + + return null; + }, + + /** + * Retrieve the list of rule sets. + * + * @return {!Array.<number>} An ordered list of the configured rule sets. + */ + getRuleSets: function() { + return (/** @type {!Array.<number>} */ this.get('ruleSet')) || []; + }, + + + /** + * @param {!Object} ruleSet The new rule set, as an anonymous JSON object. + * @return {!fiveui.RuleSet} The new RuleSet object. + */ + addRuleSet: function(ruleSet) { + var ids = this.getRuleSets(); + var id = fiveui.utils.getNewId(ids); + + var newRS = this.updateRuleSet(id, ruleSet); + ids.push(id); + this.set('ruleSet', ids); + + return newRS; + }, + + /** + * Change a rule set without generating a new id. + * + * @param {!number} ruleSetId The id of the ruleset that is being modified. + * @param {!Object} ruleSet The rule set, as an anonymous JSON object. + * @return {!fiveui.RuleSet} The new RuleSet object. + */ + updateRuleSet: function(ruleSetId, ruleSet) { + var newRS = fiveui.RuleSet.fromJSON(ruleSetId, ruleSet); + this.set('ruleSet.'+ruleSetId, newRS); + return newRS; + }, + + /** + * @param {!number} id The id of the RuleSet to retrieve. + * @return {?fiveui.RuleSet} The RuleSet, or null, if no RuleSet was found. + */ + getRuleSet: function(id) { + return /** @type {?fiveui.RuleSet} */ this.getById(id, 'ruleSet', fiveui.RuleSet.fromJSON); + }, + + /** + * @param {!number} id The id of the rule set to remove. + * @return {!Array.<fiveui.UrlPat>} null if the remove succeeded, otherwise, + * returns the list of UrlPats that use this + * rule set, if any. + */ + remRuleSet: function(id) { + var matches = _.map(this.getRuleSetUrlPats(id), function(id) { + return this.getUrlPat(id); + }, this); + + if (0 == matches.length) { + this.remById(id, 'ruleSet'); + } + + return matches; + }, + + /** + * @param {!number} ruleSetId The rule set to retrieve url patterns for. + * @return {Array.<number>} Url pattern ids associated with this rule set. + */ + getRuleSetUrlPats: function(ruleSetId) { + var urls = this.getUrls(); + var patIds = []; + + _.each(urls, function(patId) { + var pat = this.getUrlPat(patId); + if(pat.rule_id == ruleSetId) { + patIds.push(patId); + } + }, this); + + return patIds; + }, + + /** + * @param {!number} id The nuber of the element to retrieve. + * @param {!string} listName The name of the portion of the localstorage hierarchy to search for id. + * @param {!function(number, !Object): *} fromJSON A deserialization function. + * + * @return {*} Either null, or the result of fromJSON. + */ + getById: function(id, listName, fromJSON) { + var obj = this.get(listName + '.' + id); + if (!obj) { + return null; + } + + return fromJSON(id, obj); + }, + + /** + * @param {!number} id The nuber of the element to retrieve. + * @param {!string} listName The name of the portion of the + * localstorage hierarchy to search for id. + */ + remById: function(id, listName) { + // remove it from the list of ids: + var ids = this.get(listName) || []; + + for (var i = 0; i < ids.length; ++i) { + if (ids[i] == id) { + ids.splice(i, 1); + this.set(listName, ids); + this.remove(listName + '.' + id); + break; + } + } + } +}); + + +/** + * @param {!fiveui.Chan} chan + * @param {!fiveui.Settings} settings + * @return {void} + */ +fiveui.Settings.manager = function(chan, settings) { + + var msg = new fiveui.Messenger(chan); + + msg.register('addRuleSet', function(ruleSet,respond){ + var newRS = settings.addRuleSet(ruleSet); + respond(newRS); + }); + + msg.register('updateRuleSet', function(updatedRS,respond){ + var newRS = settings.updateRuleSet(updatedRS.id, updatedRS); + respond(newRS); + }); + + msg.register('remRuleSet', function(ruleSetId, respond) { + var pats = settings.remRuleSet(ruleSetId); + respond({ + id: ruleSetId, + pats: pats, + removed: pats.length == 0 + }); + }); + + msg.register('getRuleSetUrlPats', function(ruleSetId, respond) { + var pats = settings.getUrls(); + var patIds = []; + + _.each(pats, function(patId) { + var pat = settings.getUrlPat(patId); + if(pat.rule_id == ruleSetId) { + patIds.push(patId); + } + }); + + respond(patIds); + }); + + // Retrieve the manifest, and return the object to the caller. Invokes the + // caller with `null` when the manifest fails to load. + msg.register('loadRuleSet', function(url, respond) { + fiveui.RuleSet.load(url, { + success:respond, + + error:function(msg) { + respond({ error : msg }); + }, + }); + }); + + msg.register('getRuleSet', function(ruleSetId, respond){ + respond(settings.getRuleSet(ruleSetId)); + }); + + msg.register('getRuleSets', function(unused, respond) { + var ruleSets = _.map(settings.getRuleSets(), + _.bind(settings.getRuleSet, settings)); + respond(ruleSets); + }); + + msg.register('getUrlPats', function(unused, respond){ + respond(_.map(settings.getUrls(), _.bind(settings.getUrlPat, settings))); + }); + + msg.register('addUrlPat', function(url, respond){ + var urlId = settings.addUrl(url.regex, url.rule_id); + respond(settings.getUrlPat(urlId)); + }); + + msg.register('updateUrlPat', function(pat, respond) { + var obj = settings.getUrlPat(pat.id); + settings.updateUrl(pat.id, pat.regex, pat.rule_id); + respond(pat); + }); + + msg.register('getUrlPat', function(urlPatId, respond){ + respond(settings.getUrlPat(urlPatId)); + }); + + msg.register('remUrlPat', function(urlPatId, respond){ + settings.remUrlPat(urlPatId); + respond(true); + }); + + msg.register('setDisplayDefault', function(def) { + settings.setDisplayDefault(def); + }); + + msg.register('getDisplayDefault', function(ignored, respond) { + respond(settings.getDisplayDefault()); + }); + +}; + +})(); diff --git a/src/js/fiveui/js/state.js b/src/js/fiveui/js/state.js new file mode 100644 index 0000000..226a439 --- /dev/null +++ b/src/js/fiveui/js/state.js @@ -0,0 +1,202 @@ +/* + * Module : state.js + * Copyright : (c) 2011-2012, Galois, Inc. + * + * Maintainer : + * Stability : Provisional + * Portability: Portable + * + * 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. + */ + +var fiveui = fiveui || {}; + +(function() { + +/** + * @constructor + * @param {!number} x The x-offset of the embedded dialog. + * @param {!number} y The y-offset the dialog. + * @param {!number} width The width of the dalog. + * @param {!number} height The width of the dalog. + * @param {!boolean} closed True if the window is closed, false if it is open. + */ +fiveui.WinState = function(x, y, width, height, closed) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + this.closed = closed; +}; + +/** + * @constructor + * @param {string} name The name of the rule that this problem represents. + * @param {string} descr Short description of the problem. + * @param {string} url The url that the problem occurred at. + * @param {number} severity The severity of the problem + */ +fiveui.Problem = function(name, descr, url, severity, hash) { + this.name = name || ''; + this.descr = descr || ''; + this.url = url || ''; + this.severity = severity || 0; + this.hash = hash; +}; + +/** + * @param {!Object} obj The JSON object to use as a template for a problem. + * @return {!fiveui.Problem} The problem that the object represents. + */ +fiveui.Problem.fromJSON = function(obj) { + return new fiveui.Problem(obj.name, obj.descr, obj.url, obj.severity, obj.hash); +}; + +/** + * @constructor + * @param {fiveui.WinState} winState The location of the FiveUI windown in + * the injected page. Null if the window + * is hidden. + * @param {fiveui.ChromePort} uiPort The port used to communicate with + * the corresponding tab. + */ +fiveui.TabState = function(tabId, winState, uiPort) { + this.tabId = tabId; + this.winState = winState; + this.uiPort = uiPort; + this.computePorts = []; + this.problems = []; + this.seenProblems = new Set(); + this.stats = {}; +}; + +_.extend(fiveui.TabState.prototype, { + + addProblem: function(prob) { + if(!this.seenProblems.contains(prob.hash)) { + this.problems.push(prob); + this.seenProblems.add(prob.hash); + return true; + } + else { + return false; + } + }, + + addStats: function (stats) { + this.stats = stats; + return true; + }, + + clearProblems: function() { + this.problems = []; + this.seenProblems = new Set(); + }, + + clearStats: function() { + for (var p in fiveui.stats.zero) { this.stats[p] = fiveui.stats.zero[p]; } + }, + + /* + * Returns a copy of only the attributes in a TabState that are needed for + * interpage communication. + */ + toEmit: function() { + return { winState: this.winState, problems: this.problems, stats: this.stats }; + } + +}); + +/** + * @constructor + * @param {!fiveui.Settings} settings The settings object to obtain + * defaults from. + */ +fiveui.State = function(settings) { + this.tabs = {}; + this.settings = settings; +}; + +_.extend(fiveui.State.prototype, { + + /** + * @param {!number} tabId The id of the tab to retrieve state for. + * + * @return {?fiveui.TabState} The stored state of the tab, or null, if + * no state exists for the requested tab. + */ + getTabState: function(tabId) { + return this.tabs[tabId] || null; + }, + + /** + * Like getTabState, but creates an initial tab state if none exists. + * + * @param {!number} tabId The id of the tab to retrieve state for. + * @param {!fiveui.ChromePort} port The port to use for communication + * with the corresponding tab. + * @return {!fiveui.TabState} Either an initial state if none existed, or the + * state that exists already. + */ + acquireTabState: function(tabId, port) { + var ts = this.getTabState(tabId); + + if(null == ts) { + var closed = ! this.settings.getDisplayDefault(); + + // in the future, get these defaults from the settings instance. + var ws = new fiveui.WinState(10, 10, 300, 300, closed); + ts = new fiveui.TabState(tabId, ws, port); + this.setTabState(ts); + } + + return ts; + }, + + /** + * @param {!fiveui.TabState} ts The state to store. + * @return {void} + */ + setTabState: function(ts) { + this.tabs[ts.tabId] = ts; + }, + + /** + * Update the state of a tab, if and only if the tabId exists in the state. + * + * @param {!number} tabId The id of the tab to store state for. + * @param {function(fiveui.TabState): fiveui.TabState} fn A function + * that modifies the tab state. + * + * @return {void} + */ + adjust: function(tabId, fn) { + var tState = this.getTabState(tabId); + if (tState) { + this.tabs[tabId] = fn(tState); + } + }, + + /** + * Remove the state of a tab. + * + * @param {!number} tabId The id of the tab to remove the state of. + * @return {void} + */ + removeTabState: function(tabId) { + delete this.tabs[tabId]; + } + +}); + +})(); diff --git a/src/js/fiveui/js/update-manager.js b/src/js/fiveui/js/update-manager.js new file mode 100644 index 0000000..8013d2f --- /dev/null +++ b/src/js/fiveui/js/update-manager.js @@ -0,0 +1,51 @@ +/* + * Module : update-manager.js + * Copyright : (c) 2011-2012, Galois, Inc. + * + * Maintainer : + * Stability : Provisional + * Portability: Portable + * + * 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. + */ + +(function() { + + +fiveui.UpdateManager = function(msg) { + var manager = this; + + // fired when the rule set gets updated + msg.register('updateRuleSet', function(newRuleSet) { + manager.trigger('updateRuleSet.' + newRuleSet.id, false, newRuleSet); + + // update the associated url patterns + msg.send('getRuleSetPatIds', null, function(patIds) { + _.each(patIds, function(patId) { + manager.trigger('updateUrlPat.' + patId); + }); + }); + }); + + // fired when the url pat gets removed + msg.register('remUrlPat', function(id) { + var evt = 'remUrlPat.' + id; + manager.trigger(evt); + manager.off(evt); + }); + +}; + +_.extend(fiveui.UpdateManager.prototype, Backbone.Events); + +})(); diff --git a/src/js/fiveui/js/url-pat.js b/src/js/fiveui/js/url-pat.js new file mode 100644 index 0000000..d723021 --- /dev/null +++ b/src/js/fiveui/js/url-pat.js @@ -0,0 +1,140 @@ + +(function() { + +/** + * Create a new Url Pattern to map urls to Rule Sets. + * + * @constructor + * @param {!number} id New id for this UrlPat. + * @param {!string} regex The pattern that is used to match Urls. + * @param {!number} rule_id Unique id of the RuleSet to use for matching URLs. + */ +fiveui.UrlPat = function(id, regex, rule_id) { + this.id = id; + this.regex = regex; + this.rule_id = rule_id; +}; + +/** + * Create a Url Pattern from a JSON object. + * + * @param {!number} id The id to use for the restored object. + * @param {!Object} obj The object to take settings from. + * @return {!fiveui.UrlPat} A populated UrlPat object. + */ +fiveui.UrlPat.fromJSON = function(id, obj) { + return new fiveui.UrlPat(id, obj.regex, obj.rule_id); +}; + +/** + * Create a regular expression from a globbed pattern. + * + * @param {!string} str The globbed url. + * @return {!RegExp} A compiled regular expression. + */ +fiveui.UrlPat.compile = function(str) { + var regex = str.replace(/\./g, '\.') + .replace(/\*/g, '.*'); + return new RegExp(regex); +}; + +/** + * Test a string Url against the regular expression held in a Url Pattern. + * + * @param {!string} url The Url the string to test. + * @return {!boolean} If the Url matched the regular expression. + */ +fiveui.UrlPat.prototype.match = function(url) { + var pat = fiveui.UrlPat.compile(this.regex); + return pat.test(url); +}; + + + +fiveui.UrlPatModel = Backbone.Model.extend({ + + defaults: { + id: null, + regex: '', + rule_id: null, + }, + + sync:function(method, model, options) { + _.defaults(options, { + success:function() {}, + error:function() {} + }); + + var msg = model.url; + var id = model.get('id'); + + switch(method) { + case 'read': + msg.send('getUrlPat', id, function(pat) { + model.set(pat); + options.success(); + }); + break; + + case 'update': + msg.send('updateUrlPat', _.clone(model.attributes), options.success); + break; + + case 'create': + msg.send('addUrlPat', _.clone(model.attributes), options.success); + break; + + case 'delete': + msg.send('remUrlPat', id, function(res) { + if(res) { + options.success({}); + } else { + options.error({}); + } + }); + break; + } + } + +}, { + + fromUrlPat: function(pat, msg) { + return new fiveui.UrlPatModel({ + id: pat.id, + regex: pat.regex, + rule_id: pat.rule_id + }, { url : msg }); + } + +}); + + +fiveui.UrlPats = Backbone.Collection.extend({ + + model: fiveui.UrlPatModel, + + sync:function(method, collection, options) { + + _.defaults(options, { + success:function() {}, + error:function() {} + }); + + var msg = this.url; + + switch(method) { + + case 'read': + msg.send('getUrlPats', null, function(pats) { + options.success(_.map(pats, function(pat) { + return fiveui.UrlPatModel.fromUrlPat(pat, msg); + })); + }); + break; + } + + } + +}); + +})(); diff --git a/src/js/fiveui/js/utils.js b/src/js/fiveui/js/utils.js new file mode 100644 index 0000000..0ab3128 --- /dev/null +++ b/src/js/fiveui/js/utils.js @@ -0,0 +1,145 @@ +/* + * Module : utils.js + * Copyright : (c) 2011-2012, Galois, Inc. + * + * Maintainer : + * Stability : Provisional + * Portability: Portable + * + * 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. + */ + +var fiveui = fiveui || {}; + +(function() { + +fiveui.utils = fiveui.utils || {}; + +/** + * Chooses a function based on the current browser. + */ +fiveui.utils.pick = function (mozFn, chrFn) { + if (typeof chrome != 'undefined' ) { + return chrFn; + } else { + return mozFn; + } +}; + +/** + * Create a browser-independent settings object. This uses different + * backing storage depending on the browser: + * + * - Firefox: simple-storage + * - Chrome: localStorage + * + * @return {!fiveui.Settings} A settings object that will persist data + * between browser invocations. + */ +// fiveui.utils.getSettings = fiveui.utils.pick( +// function() { +// // var ss = require('simple-storage'); +// var ss = {storage: {}}; // XXX hack, obviously. + +// var storageWrapper = new fiveui.utils.StorageWrapper(ss.storage); +// return new fiveui.Settings(storageWrapper); +// }, +// function() { +// return new fiveui.Settings(localStorage); +// }); + +/** + * Get an ID by adding one to the max id in current use. + * + * @param {Array.<number>} list the list of ids that the new id must + * not conflict with. + * @return {!number} A unique id for UrlPats. + */ +fiveui.utils.getNewId = function(list) { + // make sure we have a non-null, non-empty list: + if (list === null || list.length == 0) { + return 0; + } else { + return 1 + Math.max.apply(Math, list); + } +}; + + +/** + * Remove c-style comments + * + * There's probably a faster way to do this. + */ +var removeComments = function(data) { + + var state = 0; + var toEOL = 1; + var toEOC = 2; + + var sanitized = ''; + var len = data.length; + var s = 0, e = 0; + + for(; e < len; ++e) { + switch(state) { + case toEOL: + if(data[e] == '\n') { + state = 0; + s = e + 1; + } + break; + + case toEOC: + if(data[e] == '*' && data[e+1] == '/') { + state = 0; + s = e + 2; + e = e + 1; + } + break; + + default: + if(data[e] == '/') { + if(data[e+1] == '/') { + sanitized = sanitized + data.substring(s,e); + state = toEOL; + e = e + 1; + } else if(data[e+1] == '*') { + sanitized = sanitized + data.substring(s,e); + state = toEOC; + e = e + 1; + } + } + break; + } + } + + if(state == 0 && s < e) { + sanitized = sanitized + data.substring(s,e); + } + + return sanitized; +}; + + +/** + * Filter out comments, and other things that aren't appropriate in JSON. + */ +fiveui.utils.filterJSON = function(data, type) { + if(type == 'json') { + return removeComments(data); + } else { + return data; + } +}; + +})(); |