How to add Content Security Policy to Firefox extension - firefox-addon

I have a plugin which I have to support both on Chrome and Firefox browsers. The plugin does cross script loading.
In Chrome, by adding the content security policy in my manifest.json file, I could get away with it. How can I do it Firefox extension?

I couldn't find a simple solution for my problem and upon looking up some firefox plugin extensions i had to come up with my own solution as below. The below solution was tested on FF 24.0 but should work on other versions as well.
Cc["#mozilla.org/observer-service;1"].getService(Ci.nsIObserverService)
.addObserver(_httpExamineCallback, "http-on-examine-response", false);
function _httpExamineCallback(aSubject, aTopic, aData) {
var httpChannel = aSubject.QueryInterface(Ci.nsIHttpChannel);
if (httpChannel.responseStatus !== 200) {
return;
}
var cspRules;
var mycsp;
// thre is no clean way to check the presence of csp header. an exception
// will be thrown if it is not there.
// https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIHttpChannel
try {
cspRules = httpChannel.getResponseHeader("Content-Security-Policy");
mycsp = _getCspAppendingMyHostDirective(cspRules);
httpChannel.setResponseHeader('Content-Security-Policy', mycsp, false);
} catch (e) {
try {
// Fallback mechanism support
cspRules = httpChannel.getResponseHeader("X-Content-Security-Policy");
mycsp = _getCspAppendingMyHostDirective(cspRules);
httpChannel.setResponseHeader('X-Content-Security-Policy', mycsp, false);
} catch (e) {
// no csp headers defined
return;
}
}
};
/**
* #var cspRules : content security policy
* For my requirement i have to append rule just to 'script-src' directive. But you can
* modify this function to your need.
*
*/
function _getCspAppendingMyHostDirective(cspRules) {
var rules = cspRules.split(';'),
scriptSrcDefined = false,
defaultSrcIndex = -1;
for (var ii = 0; ii < rules.length; ii++) {
if ( rules[ii].toLowerCase().indexOf('script-src') != -1 ) {
rules[ii] = rules[ii] + ' <My CSP Rule gets appended here>';
scriptSrcDefined = true;
}
if (rules[ii].toLowerCase().indexOf('default-src') != -1) {
defaultSrcIndex = ii;
}
}
// few publishers will put every thing in the default (default-src) directive,
// without defining script-src. We need to modify those as well.
if ((!scriptSrcDefined) && (defaultSrcIndex != -1)) {
rules[defaultSrcIndex] = rules[defaultSrcIndex] + ' <My CSP rule gets appended here>';
}
return rules.join(';');
};

There are plans in the future to add content policy natively in the SDK (bug 852297), but there is a 3rd party module that should get you close to where you want to be: policy.js

Related

Safari dropping Web Socket connection due to idle/inactivity when page not in focus

We are facing this issues with our app, only in safari browser, especially on iOS devices.
Current behavior
Not sure if this is a known issue (I tried searching but found nothing). Safari for Mac appears to be silently dropping web socket connections due to inactivity/idle if the page/tab is not in focus.
The biggest issue is that in mobile iOS X is very persistent.
Steps to reproduce
Open Safari > Website loads > Put Safari in Idle and open any application or lock the device.
On wake up, Safari is closing the connection and the data is not displayed anymore, we get infinite loading of the modules where we request the data.
Expected behavior
Websockets should be kept alive via the heartbeat functionality. Not seeing this behavior in other browsers so unlikely to be the code.
Is this possibly some sort of power-saving feature that is overriding/ignoring the heartbeats?
import 'whatwg-fetch';
import Config from "../config/main";
import WS from "./websocket";
import Helpers from "./helperFunctions";
var Zergling = (function (WS, Config) {
'use strict';
var Zergling = {};
var subscriptions = {}, useWebSocket = false, sessionRequestIsInProgress = false, loginInProgress = false,
uiLogggedIn = false, // uiLogggedIn is the login state displayed in UI (sometimes it differs from real one, see delayedLogoutIfNotRestored func)
authData, session, connectionAvailable, isLoggedIn, longPollUrl;
Zergling.loginStates = {
LOGGED_OUT: 0,
LOGGED_IN: 1,
IN_PROGRESS: 2
};
Zergling.codes = { // Swarm response codes
OK: 0,
SESSION_LOST: 5,
NEED_TO_LOGIN: 12
};
function getLanguageCode (lng) {
if (Config.swarm.languageMap && Config.swarm.languageMap[lng]) {
return Config.swarm.languageMap[lng];
}
return lng;
}
//helper func for fetch
function checkStatus (response) {
if (response.status >= 200 && response.status < 300) {
return response;
} else {
var error = new Error(response.statusText);
error.response = response;
throw error;
}
}
//helper func for fetch
function parseJSON (response) {
return response.json();
}
/**
* #description returns randomly selected(taking weight into consideration) long poll url
* #returns {String} long polling URL
*/
function getLongPollUrl () {
if (!longPollUrl) {
longPollUrl = Helpers.getWeightedRandom(Config.swarm.url).url;
console.debug('long Polling URL selected:', longPollUrl);
}
return longPollUrl;
}
/**
* #description
* Applies the diff on object
* properties having null values in diff are removed from object, others' values are replaced.
*
* Also checks the 'price' field for changes and adds new field 'price_change' as sibling
* which indicates the change direction (1 - up, -1 down, null - unchanged)
*
* #param {Object} current current object
* #param {Object} diff received diff
*/
function destructivelyUpdateObject (current, diff) {
if (current === undefined || !(current instanceof Object)) {
throw new Error('wrong call');
}
for (var key in diff) {
if (!diff.hasOwnProperty(key)) continue;
var val = diff[key];
if (val === null) {
delete current[key];
} else if (typeof val !== 'object') {
current[key] = val;
} else { // diff[key] is Object
if (typeof current[key] !== 'object' || current[key] === null) {
current[key] = val;
} else {
var hasPrice = (current[key].price !== undefined);
var oldPrice;
if (hasPrice) {
oldPrice = current[key].price;
}
destructivelyUpdateObject(current[key], val);
if (hasPrice) {
current[key].price_change = (val.price === oldPrice) ? null : (oldPrice < val.price) * 2 - 1;
}
}
}
}
}
This is and iOS feature that protects users against code draining their battery...
Push notifications for background applications should be performed using iOS's push notification system rather than by keeping an open connection alive.
There are hacks around this limitation, but the truth is that the limitation is good for the users and shouldn't be circumvented.
Read the technical note in the link for more details.

List all indexeddb for one host in firefox addon

I figured if the devtool can list all created IndexedDB, then there should be an API to retrieve them...?
Dose anyone know how I get get a list of names with the help of a firefox SDK?
I did dig into the code and looked at the source. unfortunately there wasn't any convenient API that would pull out all the databases from one host.
The way they did it was to lurk around in the user profiles folder and look at all folder and files for .sqlite and make a sql query (multiple times in case there is an ongoing transaction) to each .sqlite and ask for the database name
it came down this peace of code
// striped down version of: https://dxr.mozilla.org/mozilla-central/source/devtools/server/actors/storage.js
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {async} = require("resource://gre/modules/devtools/async-utils");
const { setTimeout } = require("sdk/timers");
const promise = require("sdk/core/promise");
// A RegExp for characters that cannot appear in a file/directory name. This is
// used to sanitize the host name for indexed db to lookup whether the file is
// present in <profileDir>/storage/default/ location
const illegalFileNameCharacters = [
"[",
// Control characters \001 to \036
"\\x00-\\x24",
// Special characters
"/:*?\\\"<>|\\\\",
"]"
].join("");
const ILLEGAL_CHAR_REGEX = new RegExp(illegalFileNameCharacters, "g");
var OS = require("resource://gre/modules/osfile.jsm").OS;
var Sqlite = require("resource://gre/modules/Sqlite.jsm");
/**
* An async method equivalent to setTimeout but using Promises
*
* #param {number} time
* The wait time in milliseconds.
*/
function sleep(time) {
let deferred = promise.defer();
setTimeout(() => {
deferred.resolve(null);
}, time);
return deferred.promise;
}
var indexedDBHelpers = {
/**
* Fetches all the databases and their metadata for the given `host`.
*/
getDBNamesForHost: async(function*(host) {
let sanitizedHost = indexedDBHelpers.getSanitizedHost(host);
let directory = OS.Path.join(OS.Constants.Path.profileDir, "storage",
"default", sanitizedHost, "idb");
let exists = yield OS.File.exists(directory);
if (!exists && host.startsWith("about:")) {
// try for moz-safe-about directory
sanitizedHost = indexedDBHelpers.getSanitizedHost("moz-safe-" + host);
directory = OS.Path.join(OS.Constants.Path.profileDir, "storage",
"permanent", sanitizedHost, "idb");
exists = yield OS.File.exists(directory);
}
if (!exists) {
return [];
}
let names = [];
let dirIterator = new OS.File.DirectoryIterator(directory);
try {
yield dirIterator.forEach(file => {
// Skip directories.
if (file.isDir) {
return null;
}
// Skip any non-sqlite files.
if (!file.name.endsWith(".sqlite")) {
return null;
}
return indexedDBHelpers.getNameFromDatabaseFile(file.path).then(name => {
if (name) {
names.push(name);
}
return null;
});
});
} finally {
dirIterator.close();
}
return names;
}),
/**
* Removes any illegal characters from the host name to make it a valid file
* name.
*/
getSanitizedHost: function(host) {
return host.replace(ILLEGAL_CHAR_REGEX, "+");
},
/**
* Retrieves the proper indexed db database name from the provided .sqlite
* file location.
*/
getNameFromDatabaseFile: async(function*(path) {
let connection = null;
let retryCount = 0;
// Content pages might be having an open transaction for the same indexed db
// which this sqlite file belongs to. In that case, sqlite.openConnection
// will throw. Thus we retey for some time to see if lock is removed.
while (!connection && retryCount++ < 25) {
try {
connection = yield Sqlite.openConnection({ path: path });
} catch (ex) {
// Continuously retrying is overkill. Waiting for 100ms before next try
yield sleep(100);
}
}
if (!connection) {
return null;
}
let rows = yield connection.execute("SELECT name FROM database");
if (rows.length != 1) {
return null;
}
let name = rows[0].getResultByName("name");
yield connection.close();
return name;
})
};
module.exports = indexedDBHelpers.getDBNamesForHost;
If anyone want to use this then here is how you would use it
var getDBNamesForHost = require("./getDBNamesForHost");
getDBNamesForHost("http://example.com").then(names => {
console.log(names);
});
Think it would be cool if someone were to build a addon that adds indexedDB.mozGetDatabaseNames to work the same way as indexedDB.webkitGetDatabaseNames. I'm not doing that... will leave it up to you if you want. would be a grate dev tool to have ;)

Error with breeze 1.4.11 and IE8

Trying to download data with breeze 1.4.11 and IE8 throws the following exception:
Unable to either parse or import metadata: getters & setters can not be defined on this javascript engine
The error is caused by line 173 of b00_breeze.modelLibrary.backingStore.js
Created a GitHub repo to reproduce the bug.
Breeze uses the ViewModel of the hosting MVVM framework. That’s generally a good decision. Additionally, change tracking on entities is a fundamental concept of breeze.js (same for Entity Framework). It’s an easy task to track changes if the MVVM framework uses Observables with real getter and setters (e.g. Knockout). AngularJS on the other hands works with plain JavaScript objects. This makes change tracking difficulty. The only two reliable ways are ES5-properties (simple, but not supported by IE8) or a very deep integration in the $digest cycle. The breeze-team took the first-choice - what a pity for projects that have to support IE8!
Ok, let's analyze the root cause of the problem: change tracking
Do you really need that feature? At least in our project we decided for breeze.js/OData for reading and for a more "restful" approach when it comes to writing. If you don’t need those advanced features, than the following script should solve the issue:
/********************************************************
* A replacement for the "backingStore" modelLibrary
*
* This is a bare version of the original backingStore,
* without ANY change tracking - that's why it will work in IE8!
* (Object.defineProperty not required any more)
*
* This adapter is a "drop in" replacement for the "backingStore" adapter in Breeze core.
* It has the same adapter name so it will silently replace the original "backingStore" adapter
* when you load this script AFTER the breeze library.
* WARNING: For obvious reasons a lot of breeze magic will be lost!
*
* Author: Johannes Hoppe / haushoppe-its.de
*
* Copyright 2014 IdeaBlade, Inc. All Rights Reserved.
* Use, reproduction, distribution, and modification of this code is subject to the terms and
* conditions of the IdeaBlade Breeze license, available at http://www.breezejs.com/license
******************************************************/
(function (definition, window) {
if (window.breeze) {
definition(window.breeze);
} else if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
// CommonJS or Node
var b = require('breeze');
definition(b);
} else if (typeof define === "function" && define["amd"] && !window.breeze) {
// Requirejs / AMD
define(['breeze'], definition);
} else {
throw new Error("Can't find breeze");
}
}(function (breeze) {
"use strict";
var core = breeze.core;
var ctor = function () {
this.name = "backingStore";
this.A_BIG_FAT_WARNING = "This is a bare version of the backingStore! Change tracking won't work!";
};
var protoFn = ctor.prototype;
protoFn.initialize = function() {
};
protoFn.getTrackablePropertyNames = function (entity) {
var names = [];
for (var p in entity) {
if (p === "entityType") continue;
if (p === "_$typeName") continue;
var val = entity[p];
if (!core.isFunction(val)) {
names.push(p);
}
}
return names;
};
protoFn.initializeEntityPrototype = function (proto) {
proto.getProperty = function (propertyName) {
return this[propertyName];
};
proto.setProperty = function (propertyName, value) {
this[propertyName] = value;
return this;
};
};
// This method is called when an EntityAspect is first created - this will occur as part of the entityType.createEntity call.
// which can be called either directly or via standard query materialization
// entity is either an entity or a complexObject
protoFn.startTracking = function (entity, proto) {
// assign default values to the entity
var stype = entity.entityType || entity.complexType;
stype.getProperties().forEach(function (prop) {
var propName = prop.name;
var val = entity[propName];
if (prop.isDataProperty) {
if (prop.isComplexProperty) {
if (prop.isScalar) {
val = prop.dataType._createInstanceCore(entity, prop);
} else {
val = breeze.makeComplexArray([], entity, prop);
}
} else if (!prop.isScalar) {
val = breeze.makePrimitiveArray([], entity, prop);
} else if (val === undefined) {
val = prop.defaultValue;
}
} else if (prop.isNavigationProperty) {
if (val !== undefined) {
throw new Error("Cannot assign a navigation property in an entity ctor.: " + prop.Name);
}
if (prop.isScalar) {
// TODO: change this to nullstob later.
val = null;
} else {
val = breeze.makeRelationArray([], entity, prop);
}
} else {
throw new Error("unknown property: " + propName);
}
entity[propName] = val;
});
};
breeze.config.registerAdapter("modelLibrary", ctor);
}, this));
Download at: https://gist.github.com/JohannesHoppe/72d7916aeb08897bd256
This is a bare version of the original backingStore, without ANY change tracking - that's why it will work in IE8! (Object.defineProperty not required any more) This adapter is a "drop in" replacement for the "backingStore" adapter in Breeze core. It has the same adapter name so it will silently replace the original "backingStore" adapter when you load the script AFTER the breeze library.
Here is a demo to proof the functionality:
http://jsfiddle.net/Johannes_Hoppe/bcav9hzL/5/
JsFiddle does not support IE8, please use this direct link:
http://jsfiddle.net/Johannes_Hoppe/bcav9hzL/5/embedded/result/
Cheers!
The Breeze 'backingStore' model library is explicitly not supported in IE8. This is because the 'backingStore' implementation requires the full javascript 'Object.defineProperty' capability which is not available in IE8 and cannot be provided by any shim.
That said, the breeze 'knockout' and 'backbone' model library adapters should both work with IE8.
Also see: http://www.breezejs.com/documentation/prerequisites

jquery mobile plugin creation

I am trying to create a plugin for JQuery Mobile. Does anyone have a template or examples to help? Currently, I have the following defined in myplugin.js
(function ($) {
$.fn.myPlugin = function (options) {
var defaults = { e: 0 },
settings = $.extend({}, defaults, options);
var h= $.myPlugin.getHtml(options.e);
alert("here 1");
if ((h != null) && (h != undefined) && (h.length > 0)) {
alert("here 2");
this.html(h);
}
};
$.myPlugin = {
getHtml: function (e) {
var s = "";
return s;
}
};
})(jQuery);
I am trying to initialize an instance of this plugin like such:
$("#pluginInstance", "#myPage").myPlugin({ e: 0 });
Oddly, neither alert dialog appears. There aren't any errors in the console either. What am I doing wrong?
How about this or this?
If you look how JQM is designed, the syntax is pretty similar. I'm also sticking with it, when working on plugins I'm doing.

How to open the page in browser at the time of uninstalling the firefox addon

I want to open the link when the user uninstalls the addon, so for this what i have to code and under which event.
If anybody know about this then please help me out.
Currently this is what I am doing at the time of uninstall. But gBrowser.addTab(Website + 'uninstalled=true&token=' + uniqueguid); is not working over here.
var UninstallObserver = {
_uninstall : false,
observe : function(subject, topic, data) {
//===Write Code here for Delete File Uninsatll Time
//alert("Uninstall Time Delete File");
var Filename = "webmail";
// Delete all template file.
try{
var pref = Components.classes["#mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch);
var finished = "";
pref.setBoolPref("myextension.install.just_installed", false);
}
catch(e) {}
gBrowser.addTab(Website + 'uninstalled=true&token=' + uniqueguid);
var file = Components.classes["#mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
file.initWithPath(Components.classes["#mozilla.org/file/directory_service;1"].getService( Components.interfaces.nsIProperties).get("ProfD", Components.interfaces.nsIFile).path+"\\DefaultTemplate.txt");
if ( file.exists() == true )
{
var aFile = Components.classes["#mozilla.org/file/local;1"].createInstance();
if (aFile instanceof Components.interfaces.nsILocalFile)
{
aFile.initWithPath(Components.classes["#mozilla.org/file/directory_service;1"].getService( Components.interfaces.nsIProperties).get("ProfD", Components.interfaces.nsIFile).path + "\\DefaultTemplate.txt");
aFile.remove(false);
}
}
//=======
if (topic == "em-action-requested") {
subject.QueryInterface(Components.interfaces.nsIUpdateItem);
if (subject.id == MY_EXTENSION_UUID)
{
if (data == "item-uninstalled")
{
//==Delete File Whenever Uninstall
//alert("When Uninatall");
//===========
data = "item-cancel-action";
this._uninstall = true;
}
if (data == "disabled")
{
// alert("You are not allow to disable SysLocker.");
this._uninstall = true;
}
else if (data == "item-cancel-action")
{
this._uninstall = false;
}
}
}
else if (topic == "quit-application-granted")
{
data = "item-cancel-action";
if (this._uninstall)
{
//Code here to delete registry
}
this.unregister();
}
},
register : function() {
var observerService =
Components.classes["#mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService);
observerService.addObserver(this, "em-action-requested", false);
observerService.addObserver(this, "quit-application-granted", false);
},
unregister : function() {
var observerService =
Components.classes["#mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService);
observerService.removeObserver(this,"em-action-requested");
observerService.removeObserver(this,"quit-application-granted");
}
}
Thanks
0) What kind of extension is this? I assume it's a regular extension requiring restart; bootstrapped (restartless) extensions have their own uninstall notification.
1) Per the MDC docs, the em-action-requested notification was replaced with a different notification in Firefox 4+, are you testing with Firefox 4 or 3.6?
2) How exactly is gBrowser.addTab "not working over here"? Does the code get to that point? Do you get any messages in the Error Console (see that page for set up tips)? If you put your code in an XPCOM component (which is correct), you'll first have to get a reference to a browser window. See Working with windows in chrome code.
I don't think that the em-action-requested topic is posted to observers until the extension is actually uninstalled, which happens on restart (assuming it is not a restartless extension). When are you expecting the new tab to appear? I would try setting a pref when the uninstall topic is triggered and checking for that pref on startup. If it is there, you can display your tab and remove the pref.

Resources