Codepen.io also jsfiddle stop's working with `localStorage access` - javascript

Here is the log:
build.js:8325 Uncaught DOMException: Failed to read the 'localStorage' property from 'Window': Access is denied for this document.
Source code :
/**
* LocalStorageMemory save and load js objects in localStorage.
*/
var localStorageMemory = {
localStorage: window.localStorage,
/**
* save Put the object into storage.
* #example Usage : save("MyObjectKey", myObject )
* #method save
* #param {String} Name Name of localstorage key
* #param {object} Value Any object we can store.
* #return {false | object} What ever we are stored intro localStorage.
*/
save: function(name, obj) {
try {
return localStorage.setItem(name, JSON.stringify(obj));
} catch (e) {
console.log("Something wrong in LocalStorageMemory class , method save! -> ", e);
return false;
}
},
/**
* Load saved object from storage. Retrieve the object from storage or
* return false.
* #example Usage : var giveMeMyObject = load("MyObjectKey")
* #function load
* #param {String} Name Name of localstorage key
* #return {false | object} What ever we are stored intro localStorage.
*/
load: function(name) {
if (localStorage.getItem(name) === "undefined" || localStorage.getItem(name) == null || localStorage.getItem(name) === "") {
console.warn("LocalStorageMemory method load return's: ", localStorage.getItem(name));
return false;
} else {
return JSON.parse(localStorage.getItem(name));
}
}
}
/**********CREATE INSTANCE***********/
var saveAnyDataObj = {
name: "wins",
numberOfWins: c++
};
localStorageMemory.save("MyObject1", saveAnyDataObj);
var getMyMemory = localStorageMemory.load("MyObject1");
console.log("MyObject1 = ", getMyMemory);
var c = 0;
setInterval(function() {
var saveAnyDataObj = {
name: "wins",
numberOfWins: c++
};
localStorageMemory.save("MyObject1", saveAnyDataObj);
getMyMemory = localStorageMemory.load("MyObject1");
document.getElementById("result").innerHTML = getMyMemory.numberOfWins;
}, 1000);
Try it :
https://codepen.io/zlatnaspirala/pen/NRYKzJ

It's one of many sandbox values for iframes: allow-same-origin. There is no way you can defeat it.
My guess why JS Fiddle and Codepen don't allow it since one script could put some form of secrets into localstorage and another (potentially malicious) script could read those as they are on the same origin.
You can read why Stack Exchange disallows it here: https://meta.stackoverflow.com/a/345386/23528

Related

Trouble with javascript, protractor, and classes

I'm having some trouble understanding class scope and how to utilize objects, prototypes, etc, within the system I'm having. It seems to me that I'm doing things correctly, but it isn't working and I'm hoping someone can explain it to me or get me pointed in the right direction.
So here's the situation:
I'm trying to create a generic page object and verification object that would handle an input box with an associated text label. Things do appear to work if I have the verification in the same class as the page object structure, but I've recently learned that is bad design.
This is an Angular 2 rc1 site. I'm using protractor 3.3.0 and have ensured that the project version of selenium and chromedriver are up-to-date by using webdriver-manager.
So here's what I've done (filenames are in the comments at the start of each section):
'use strict';
/*
* InputLabelPageObject.js
*
* This object will provide basic methods for an Input box with an attached label.
* It is expected that the label will have an element called "label" and an element called "input"
*/
module.exports = InputLabelPageObject;
/**
* Create an object that will provide methods for an input/label combination of elements.
*
* #param container The selector for the __container of the input/label combination of elements.
*/
function InputLabelPageObject(container) {
this.Container = container;
}
InputLabelPageObject.prototype = {
constructor: InputLabelPageObject,
/**
* Return the element for the label of the input/label combination of elements.
*
* #returns {protractor.element}
*/
getLabel: function () {
return this.Container.$('label');
},
/**
* Return the element for the input of the input/label combination of elements.
*
* #returns {ElementFinder}
*/
getInput: function () {
return this.Container.$('input');
},
/**
* Return the text shown in the input of the input/label combination of elements.
*
* #returns {Promise}
*/
getValue: function () {
return this.getInput().getAttribute('value');
},
/**
* Get the placeholder text shown in the input of the input/label combination of elements.
*
* #returns {Promise}
*/
getPlaceholder: function () {
return this.getInput().getAttribute('placeholder');
},
/**
* Clears the input element then puts the text from data into the input element.
*
* #param data The text to be entered into the input element.
*/
sendKeys: function (data) {
var el = this.getInput();
el.clear().then(function () {
return el.sendKeys(data);
});
}
};
--
'use strict';
/*
* InputLabelVerification.js
*
* Provide verification methods associated with an Input and Label
* combination of elements.
*/
module.exports = InputLabelVerifications;
var inputLabelPageObject;
function InputLabelVerifications(inputLabelPageObject) {
this.__setPageObject(inputLabelPageObject);
}
InputLabelVerifications.prototype = {
constructor: InputLabelVerifications,
__setPageObject: function (ilpo) {
inputLabelPageObject = ilpo;
},
/**
* Verify the text on the label of the input/label combination of elements.
*
* #param expected The expected text on the label.
*/
verifyText: function (expected) {
//console.log('Asserting text [' + expected + ']');
expect(inputLabelPageObject.getLabel()).toEqual(expected);
},
/**
* Verify the text shown in the input of the input/label combination of elements.
*
* #param expected The expected text in the input element.
*/
verifyValue: function (expected) {
//console.log('Asserting input value [' + expected + ']');
expect(inputLabelPageObject.getValue()).toEqual(expected);
},
/**
* Verify the placeholder text shown in the input of the input/label combination of elements.
*
* #param expected The expected text of the placeholder.
*/
verifyPlaceholder: function (expected) {
//console.log('Verifying placeholder text [' + expected + ']');
expect(inputLabelPageObject.getPlaceholder()).toEqual(expected);
}
};
--
'use strict';
/*
* LoginPageObject.js
*
*/
var InputLabelPageObject = require('./generics/InputLabelPageObject.js');
module.exports = LoginPageObject;
var __container = $('login-component');
var username = new InputLabelPageObject(__container.$('form:nth-child(2) > div:nth-child(1)'));
var password = new InputLabelPageObject(__container.$('form:nth-child(2) > div:nth-child(2)'));
/**
* Create an object that contains the methods necessary to perform actions against the LoginPageObject page.
*
* #param url The base URL string. If not undefined, it will load the url+'/login' page.
* #constructor new LoginPageObject('http://localhost:9000');
*/
function LoginPageObject(url) {
if (url) {
this.loadPage(url)
}
}
LoginPageObject.prototype = {
constructor: LoginPageObject,
loadPage: function (url) {
url = url + '/login';
console.log('Loading page: '+ url);
browser.get(url);
},
welcome: {
/**
* Return the element for the Welcome text
*
* #returns {ElementFinder}
*/
get: function () {
return __container.$('section:first-child h1:first-child');
},
},
/**
* Return an InputLabelPageObject object specific for the username input and label elements.
*/
username: username,
/**
* Return an InputLabelPageObject object specific for the password input and label elements.
*/
password: password,
loginButton: {
/**
* Return the element for the login button.
*
* #returns {ElementFinder}
*/
get: function () {
return __container.$('form > button');
},
/**
* Click the LoginPageObject button.
* #returns {*|void|webdriver.promise.Promise<void>|ActionSequence|!webdriver.promise.Promise.<void>}
*/
click: function () {
return this.get().click();
}
}
};
--
'use strict';
/*
* LoginPageVerifications.js
*/
var LoginPageObject = require('../pageObjects/LoginPageObject');
var verifyText = require('./generics/VerifyText');
var inputLabelVerifications = require('./generics/InputLabelVerifications');
module.exports = LoginPageVerifications;
var __loginPageObject = new LoginPageObject();
function LoginPageVerifications(url) {
if (url) {
__loginPageObject = new LoginPageObject(url);
}
}
LoginPageVerifications.prototype = {
constructor: LoginPageVerifications,
loginPageObject: new LoginPageObject(),
welcome: {
verifyText: function (expected) {
verifyText(__loginPageObject.welcome.get(), expected);
}
},
username: new inputLabelVerifications(__loginPageObject.username),
password: new inputLabelVerifications(__loginPageObject.password),
loginButton: {
verifyText: function (expected) {
verifyText(__loginPageObject.loginButton.get(), expected);
}
},
/**
* Performs the actions of logging in. That is, enter the username and password values,
* then click the LoginPageObject button. This does *not* verify page load.
*
* #param username The username to login with.
* #param password The password to login with.
*/
doLogin: function (username, password) {
var uPromise = __loginPageObject.username.sendKeys(username);
var pPromise = __loginPageObject.password.sendKeys(password);
protractor.promise.asap(this.username.verifyValue(username));
protractor.promise.asap(this.password.verifyValue(password));
protractor.promise.all([uPromise, pPromise]).then(this.loginButton.click());
},
/**
* Verifies all page elements' text or other default attributes.
*
* #param welcomeText The expected Welcome text
* #param userText The expected username label text.
* #param userPlaceholder The expected username's input element's placeholder text.
* #param passText The expected password label text.
* #param passPlaceholder The expected password's input element's placeholder text.
* #param loginText The expected login button text.
*/
verifyPage: function (welcomeText, userText, userPlaceholder, passText, passPlaceholder, loginText) {
this.welcome.verifyText(welcomeText);
this.username.verifyText(userText);
this.username.verifyPlaceholder(userPlaceholder);
this.password.verifyText(passText);
this.password.verifyPlaceholder(passPlaceholder);
this.loginButton.verifyText(loginText);
}
};
--
'use strict';
/*
* login-spec.js
*/
var LoginPageVerifications = require('../components/actions/LoginPageVerifications');
var myUrl = 'http://localhost:3000';
describe('My Login Page test', function() {
var loginPage;
beforeAll(function() {
loginPage = new LoginPageVerifications(myUrl);
});
it('should verify username input and label values', function() {
var welcomeText = 'Thank you for visiting my login page';
var userText = 'Username';
var userPlaceholder = 'Enter your username';
var passText = 'Password';
var passPlaceholder = 'Enter your password';
var loginText = 'Login';
loginPage.username.verifyText(userText);
// loginPage.verifyPage(welcomeText, userText, userPlaceholder, passText, passPlaceholder, loginText);
});
});
The results that I'm usually seeing:
If in InputLabelVerification.js I leave out var inputLabelPageObject or try to set the value only in the constructor function, I get Failed: Cannot read property 'getLabel' of undefined. So, I've figured that I must set it the way I have it shown above.
The closest I seem to get is when I get the following response:
A Jasmine spec timed out. Resetting the WebDriver Control Flow.
F
Failures:
1) My Login Page test should verify username input and label values
Expected ({ ptor_: ({ controlFlow: Function, schedule: Function,
setFileDetector: Function, getSession: Function, getCapabilities: Function,
quit: Function, actions: Function, touchActions: Function,
executeScript: Function, executeAsyncScript: Function, call: Function,
wait: Function, sleep: Function, getWindowHandle... }) }) to equal 'Username'.
In the LoginPageVerification.js, I've tested and ensured that the other verifications (welcome and loginButton) work fine.
Also, if from login-spec.js I add this line:
expect(loginPage.loginPageObject.username.getLabel().getText()).toEqual(userText);
this expect passes the test.
I've got the answer. And now I feel silly. I'll post as the answer when I can in 2 days. (This only took me several days to get it right.)
Essentially, I'm calling .getLabel() from the InputLabelPageObject from InputLabelVerifications, which returns an ElementFinder... it does not return a string as is needed. (oops)
In InputLabelPageObject, I added the following line:
getText: function() {
this.getLabel().getText();
}
and then called that function in the InputLabelVerifications class,
verifyText: function (expected) {
//console.log('Asserting text [' + expected + ']');
expect(inputLabelPageObject.getText()).toEqual(expected);
},
and all is working as expected.

Can't get this keyword to work within a node module

I'm trying to build a small wrapper library for the node redis module.
var redis = require('redis'),
client = redis.createClient();
module.exports = {
/*
* Time To Live
*
* Time in seconds the cache will remain in
* memory.
*/
ttl: 120,
/*
* Time To Refresh
*
* Time buffer in seconds when the cache should
* be refreshed. On a cache check, if the ttl
* is less than the ttr, the cache will be
* refreshed.
*/
ttr: 60,
/*
* Check Cache
*
* Middleware to check if the request is cached
* before executing the full request.
*/
check: function (req, res, next) {
var key = req.url.slice(1, req.url.length).replace('/', ':');
client.get(key, function (err, value) {
if (value !== null) {
res.send(value);
client.ttl(key, function (err, ttl) {
if (ttl < this.ttr) {
return next();
}
return;
});
} else {
return next();
}
})
},
/*
* Set Cache
*
* Takes a key and a value and stores it in redis.
* Also takes the full response object "res" and
* handles sending the response if it has not
* already been sent.
*/
set: function (url, value, res) {
var key = url.slice(1, url.length).replace('/', ':');
client.set(key, value);
client.expire(key, this.ttl);
if (!res.headersSent) {
res.send(value);
}
return;
},
/*
* Keygen Cache
*
* Takes a urls substring and creates a keyname
* in line with redis best practices
*/
keygen: function (url) {
var key = url.slice(0,1).replace('/', ':');
console.log(key);
return key;
}
};
I can't figure out how to use the 'this' keyword properly. If I try to reference this.ttl or this.ttr or call this.keygen from another function within the module.exports object it always turns up undefined. What is a structure I should be using to enable referencing functions internal to the same object?
You have nested functions in your check function, so the this-keyword relates to the inner function context and not to your object.
First solution:
function (req, res, next) {
var _this = this;
var key = req.url.slice(1, req.url.length).replace('/', ':');
client.get(key, function (err, value) {
if (value !== null) {
res.send(value);
client.ttl(key, function (err, ttl) {
if (ttl < _this.ttr) {
return next();
}
return;
});
} else {
return next();
}
})
}
And if you are using the function as a callback function bind your object to the callback function like this:
// replace "yourModule" with the name of your module
var yourModule = require('./yourModule.js');
// an example for expressjs (use get, post ... or something else)
app.get('/some/url', yourModule.check.bind(yourModule));
Second solution (IMHO the better one):
Another solution is to use local variables in your module (these are only visible in your module -> and this is what you want).
This is way more easier:
/*
* Time To Live
*
* Time in seconds the cache will remain in
* memory.
*/
var ttl = 120;
/*
* Time To Refresh
*
* Time buffer in seconds when the cache should
* be refreshed. On a cache check, if the ttl
* is less than the ttr, the cache will be
* refreshed.
*/
var ttr = 60;
/*
* Check Cache
*
* Middleware to check if the request is cached
* before executing the full request.
*/
var check = function (req, res, next) {
var key = req.url.slice(1, req.url.length).replace('/', ':');
client.get(key, function (err, value) {
if (value !== null) {
res.send(value);
client.ttl(key, function (err, ttl) {
if (ttl < ttr) {
return next();
}
return;
});
} else {
return next();
}
})
};
/*
* Set Cache
*
* Takes a key and a value and stores it in redis.
* Also takes the full response object "res" and
* handles sending the response if it has not
* already been sent.
*/
var set = function (url, value, res) {
var key = url.slice(1, url.length).replace('/', ':');
client.set(key, value);
client.expire(key, ttl);
if (!res.headersSent) {
res.send(value);
}
return;
};
/*
* Keygen Cache
*
* Takes a urls substring and creates a keyname
* in line with redis best practices
*/
var keygen = function (url) {
var key = url.slice(0,1).replace('/', ':');
console.log(key);
return key;
};
/**
* PUBLIC API
*/
module.exports = {
check: check,
set: set,
keygen: keygen
};
Now because you are not relying on the context of the module you can simply do this:
// replace "yourModule" with the name of your module
var yourModule = require('./yourModule.js');
// an example for expressjs (use get, post ... or something else)
app.get('/some/url', yourModule.check);

Websql queued execution - iOS6 Mobile Safari

Since the release of iOS6, my web app has hit a series of bugs, one of the worst being what I'm almost 100% positive is websql transactions being queued. When I first load the app in mobile safari( ipad ), the transactions work fine. Then, if I close safari and open it again the transactions seem to be queued and never execute.
If I open the dev tools and run a simple alert, the methods will fire, if I just hit reload the transactions work fine as well, or if I delay the running of the db transactions by 1sec or something it works fine as well.
I do not want to run a setTimeout to run the transactions.
Is this a caching issue that safari has now since implemented?
If anyone has ANY good ideas on how to fix this please answer below.
Thanks in advance.
It may not be bug. You may be using series of transaction unnecessarily. You could use mulitple requests per transaction. onsuccess callback, you can reuse the transaction. It should work. At the same time, limit number of requests per transaction. setTimeout should never necessary.
Here is how a single transaction is used to insert multiple objects
/**
* #param {goog.async.Deferred} df
* #param {string} store_name table name.
* #param {!Array.<!Object>} objects object to put.
* #param {!Array.<(Array|string|number)>=} opt_keys
*/
ydn.db.req.WebSql.prototype.putObjects = function (df, store_name, objects, opt_keys) {
var table = this.schema.getStore(store_name);
if (!table) {
throw new ydn.db.NotFoundError(store_name);
}
var me = this;
var result_keys = [];
var result_count = 0;
/**
* Put and item at i. This ydn.db.core.Storage will invoke callback to df if all objects
* have been put, otherwise recursive call to itself at next i+1 item.
* #param {number} i
* #param {SQLTransaction} tx
*/
var put = function (i, tx) {
// todo: handle undefined or null object
var out;
if (goog.isDef(opt_keys)) {
out = table.getIndexedValues(objects[i], opt_keys[i]);
} else {
out = table.getIndexedValues(objects[i]);
}
//console.log([obj, JSON.stringify(obj)]);
var sql = 'INSERT OR REPLACE INTO ' + table.getQuotedName() +
' (' + out.columns.join(', ') + ') ' +
'VALUES (' + out.slots.join(', ') + ');';
/**
* #param {SQLTransaction} transaction transaction.
* #param {SQLResultSet} results results.
*/
var success_callback = function (transaction, results) {
result_count++;
result_keys[i] = goog.isDef(out.key) ? out.key : results.insertId;
if (result_count == objects.length) {
df.callback(result_keys);
} else {
var next = i + ydn.db.req.WebSql.RW_REQ_PER_TX;
if (next < objects.length) {
put(next, transaction);
}
}
};
/**
* #param {SQLTransaction} tr transaction.
* #param {SQLError} error error.
*/
var error_callback = function (tr, error) {
if (ydn.db.req.WebSql.DEBUG) {
window.console.log([sql, out, tr, error]);
}
df.errback(error);
return true; // roll back
};
//console.log([sql, out.values]);
tx.executeSql(sql, out.values, success_callback, error_callback);
};
if (objects.length > 0) {
// send parallel requests
for (var i = 0; i < ydn.db.req.WebSql.RW_REQ_PER_TX && i < objects.length; i++) {
put(i, this.getTx());
}
} else {
df.callback([]);
}
};
Regarding transaction queue, it is better to handle by the application rather than by the SQLite for robustness. Basically we can watch transaction complete event before starting a new transaction. It is also fine to run multiple transactions as long as they are under control. Out of control will be opening transactions under a loop. Generally I will open only couple of transactions.
Here is how transaction is queued:
/**
* Create a new isolated transaction. After creating a transaction, use
* {#link #getTx} to received an active transaction. If transaction is not
* active, it return null. In this case a new transaction must re-create.
* #export
* #param {Function} trFn function that invoke in the transaction.
* #param {!Array.<string>} store_names list of keys or
* store name involved in the transaction.
* #param {ydn.db.TransactionMode=} opt_mode mode, default to 'readonly'.
* #param {function(ydn.db.TransactionEventTypes, *)=} oncompleted
* #param {...} opt_args
* #override
*/
ydn.db.tr.TxStorage.prototype.transaction = function (trFn, store_names, opt_mode, oncompleted, opt_args) {
//console.log('tr starting ' + trFn.name);
var scope_name = trFn.name || '';
var names = store_names;
if (goog.isString(store_names)) {
names = [store_names];
} else if (!goog.isArray(store_names) ||
(store_names.length > 0 && !goog.isString(store_names[0]))) {
throw new ydn.error.ArgumentException("storeNames");
}
var mode = goog.isDef(opt_mode) ? opt_mode : ydn.db.TransactionMode.READ_ONLY;
var outFn = trFn;
if (arguments.length > 4) { // handle optional parameters
var args = Array.prototype.slice.call(arguments, 4);
outFn = function () {
// Prepend the bound arguments to the current arguments.
var newArgs = Array.prototype.slice.call(arguments);
//newArgs.unshift.apply(newArgs, args); // pre-apply
newArgs = newArgs.concat(args); // post-apply
return trFn.apply(this, newArgs);
}
}
outFn.name = scope_name;
var me = this;
if (this.mu_tx_.isActive()) {
//console.log(this + ' active')
this.pushTxQueue(outFn, store_names, mode, oncompleted);
} else {
//console.log(this + ' not active')
var transaction_process = function (tx) {
me.mu_tx_.up(tx, scope_name);
// now execute transaction process
outFn(me);
me.mu_tx_.out(); // flag transaction callback scope is over.
// transaction is still active and use in followup request handlers
};
var completed_handler = function (type, event) {
me.mu_tx_.down(type, event);
/**
* #preserve_try
*/
try {
if (goog.isFunction(oncompleted)) {
oncompleted(type, event);
}
} catch (e) {
// swallow error. document it publicly.
// this is necessary to continue transaction queue
if (goog.DEBUG) {
throw e;
}
} finally {
me.popTxQueue_();
}
};
if (ydn.db.tr.TxStorage.DEBUG) {
window.console.log(this + ' transaction ' + mode + ' open for ' + JSON.stringify(names) + ' in ' + scope_name);
}
this.storage_.newTransaction(transaction_process, names, mode, completed_handler);
}
};
As it turns out, initializing Facebook before websql was causing the problem. After commenting out FB the app behaved properly, which is why setTimeout solved the issue as well; the fb api was ready. How the thread of execution gets blocked, I don't know.
So, to anyone using FB and then trying to execute websql transactions...delay FB!
Though, websql is still running a bit slow on safari load...

HTML5 appcache, Get a list of cached files in the client

In my project, I'm trying to use HTML5 appcache to cache static resources like CSS and JS, and "user specific" files such as images and videos. When I say user specific images/videos, I'm trying to have separate files for each user and I need to control order of the file download as well.
Given the scenario, my manifest file will be dynamically loaded for every user. Is there a way where I can get a list of resources that are already cached in client side?
If not, is is possible to read the ".appcache" file in client?
Yes. You can use AJAX request to get the manifest cache file and then read it.
However, this does not guarantee that the browser in the question has the files available.
Below is an sample code
Which checks if we have cached HTML5 app or not
If we are not in a cached state then count loaded resources in the manifest and display a progress bar according to the manifest cache entry count (total) and do a manual AJAX GET request for all URLs to warm up the cache. The browser will do this itself, but this way we can get some progress information out of the process.
When cache is in a known good state, move forward
Disclaimer: not tested to work since 2010
/**
* HTML5 offline manifest preloader.
*
* Load all manifest cached entries, so that they are immediately available during the web app execution.
* Display some nice JQuery progress while loading.
*
* #copyright 2010 mFabrik Research Oy
*
* #author Mikko Ohtamaa, http://opensourcehacker.com
*/
/**
* Preloader class constructor.
*
* Manifest is retrieved via HTTP GET and parsed.
* All cache entries are loaded using HTTP GET.
*
* Local storage attribute "preloaded" is used to check whether loading needs to be performed,
* as it is quite taxing operation.
*
* To debug this code and force retrieving of all manifest URLs, add reloaded=true HTTP GET query parameter:
*
*
*
* #param {Function} endCallback will be called when all offline entries are loaded
*
* #param {Object} progressMonitor ProgressMonitor object for which the status of the loading is reported.
*/
function Preloader(endCallback, progressMonitor, debug) {
if(!progressMonitor) {
throw "progressMonitor must be defined";
}
this.endCallback = endCallback;
this.progressMonitor = progressMonitor;
this.logging = debug; // Flag to control console.log() output
}
Preloader.prototype = {
/**
* Load HTML5 manifest and parse its data
*
* #param data: String, manifest file data
* #return Array of cache entries
*
* #throw: Exception if parsing fails
*/
parseManifest : function(data) {
/* Declare some helper string functions
*
* http://rickyrosario.com/blog/javascript-startswith-and-endswith-implementation-for-strings/
*
*/
function startswith(str, prefix) {
return str.indexOf(prefix) === 0;
}
var entries = [];
var sections = ["NETWORK", "CACHE", "FALLBACK"];
var currentSection = "CACHE";
var lines = data.split(/\r\n|\r|\n/);
var i;
if(lines.length <= 1) {
throw "Manifest does not contain text lines";
}
var firstLine = lines[0];
if(!(startswith(firstLine, "CACHE MANIFEST"))) {
throw "Invalid cache manifest header:" + firstLine;
}
for(i=1; i<lines.length; i++) {
var line = lines[i];
this.debug("Parsing line:" + line);
// If whitespace trimmed line is empty, skip it
line = jQuery.trim(line);
if(line == "") {
continue;
}
if(line[0] == "#") {
// skip comment;
continue;
}
// Test for a new section
var s = 0;
var sectionDetected = false;
for(s=0; s<sections.length; s++) {
var section = sections[s];
if(startswith(line, section + ":")) {
currentSection = section;
sectionDetected = true;
}
}
if(sectionDetected) {
continue;
}
// Otherwise assume we can check for cached url
if(currentSection == "CACHE") {
entries.push(line);
}
}
return entries;
},
/**
* Manifest is given as an <html> attribute.
*/
extractManifestURL : function() {
var url = $("html").attr("manifest");
if(url === null) {
alert("Preloader cannot find manifest URL from <html> tag");
return null;
}
return url;
},
isPreloaded : function() {
// May be null or false
return localStorage.getItem("preloaded") == true;
},
setPreloaded : function(status) {
localStorage.setItem("preloaded", status);
},
/**
* Check whether we need to purge offline cache.
*
*/
isForcedReload : function() {
// http://www.netlobo.com/url_query_string_javascript.html
function getQueryParam(name) {
name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
var regexS = "[\\?&]"+name+"=([^&#]*)";
var regex = new RegExp( regexS );
var results = regex.exec( window.location.href );
if (results == null) {
return "";
} else {
return results[1];
}
}
if(getQueryParam("reload") == "true") {
return true;
}
return false;
},
/**
* Do everything necessary to set-up offline application
*/
load : function() {
this.debug("Entering preloader");
if (window.applicationCache) {
this.debug("ApplicationCache status " + window.applicationCache.status);
this.debug("Please see http://www.w3.org/TR/html5/offline.html#applicationcache");
} else {
this.silentError("The browser does not support HTML5 applicationCache object");
return;
}
var cold;
if(this.isPreloaded()) {
// We have succesfully completed preloading before
// ...move forward
forceReload = this.isForcedReload();
if (forceReload == true) {
applicationCache.update();
} else {
this.endCallback();
return;
}
cold = false;
} else {
cold = true;
}
var url = this.extractManifestURL();
if(url === null) {
return;
}
this.progressMonitor.startProgress(cold);
$.get(url, {}, jQuery.proxy(manifestLoadedCallback, this));
function manifestLoadedCallback(data, textStatus, xhr) {
this.debug("Manifest retrieved");
var text = data;
manifestEntries = this.parseManifest(text);
this.debug("Parsed manifest entries:" + manifestEntries.length);
this.populateCache(manifestEntries);
}
},
/**
* Bootstrap async loading of cache entries.
*
* #param {Object} entrires
*/
populateCache : function(entries) {
this.manifestEntries = entries;
this.currentEntry = 0;
this.maxEntry = entries.length;
this.loadNextEntry();
},
/**
* Make AJAX request to next entry and update progress bar.
*
*/
loadNextEntry : function() {
if(this.currentEntry >= this.maxEntry) {
this.setPreloaded(true);
this.progressMonitor.endProgress();
this.endCallback();
}
var entryURL = this.manifestEntries[this.currentEntry];
this.debug("Loading entry: " + entryURL);
function done() {
this.currentEntry++;
this.progressMonitor.updateProgress(this.currentEntry, this.maxEntries);
this.loadNextEntry();
}
this.debug("Preloader fetching:" + entryURL + " (" + this.currentEntry + " / " + this.maxEntry + ")");
$.get(entryURL, {}, jQuery.proxy(done, this));
},
/**
* Write to debug console
*
* #param {String} msg
*/
debug : function(msg) {
if(this.logging) {
console.log(msg);
}
},
/**
* Non-end user visible error message
*
* #param {Object} msg
*/
silentError : function(msg) {
console.log(msg);
}
};
function ProgressMonitor() {
}
ProgressMonitor.prototype = {
/**
* Start progress bar... initialize as 0 / 0
*/
startProgress : function(coldVirgin) {
$("#web-app-loading-progress-monitor").show();
if(coldVirgin) {
$("#web-app-loading-progress-monitor .first-time").show();
}
},
endProgress : function() {
},
updateProgress : function(currentEntry, maxEntries) {
}
};
I have also been working on a solution for discovering which file is being cached, and have come up with the following.
.htaccess wrapper for the directory we are grabbing files to appcache.
#.htaccess
<FilesMatch "\.(mp4|mpg|MPG|m4a|wav|WAV|jpg|JPG|bmp|BMP|png|PNG|gif|GIF)$">
SetHandler autho
</FilesMatch>
Action autho /www/restricted_access/auth.php
then my auth.php file returns the file (in chunks) to the browser, but also logs at the same time to the server (I use a DB table) with an earlier declared APPID.
That way while the 'progress' event is detected, an AJAX call can be made to retrieve the last entry for APPID, which contains the file name and how much data has been sent.
The advantage of using this method is that its transparent to other methods accessing the files in the '.htaccess wrapped' folder, and in my case also includes authentication.
When not authorized to access a file for whatever reason I return 'Not Authorized' headers.

How to do a custom auto-complete textbox for firefox toolbar

UPDATE: I found a solution
I had to do exactly as follows:
In the extension dir I created a new dir named "components".
Inside that dir I create a js file with my custom auto-complete name, in this case "simpleautocomplete.js".
The content of that js file is:
/*
* https://developer.mozilla.org/en/How_to_implement_custom_autocomplete_search_component
*/
const Ci = Components.interfaces;
const CLASS_ID = Components.ID("6224daa1-71a2-4d1a-ad90-01ca1c08e323");
const CLASS_NAME = "Simple AutoComplete";
const CONTRACT_ID = "#mozilla.org/autocomplete/search;1?name=simple-autocomplete";
try{
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
} catch (x) {
}
// Implements nsIAutoCompleteResult
function SimpleAutoCompleteResult(searchString, searchResult,
defaultIndex, errorDescription,
results, comments) {
this._searchString = searchString;
this._searchResult = searchResult;
this._defaultIndex = defaultIndex;
this._errorDescription = errorDescription;
this._results = results;
this._comments = comments;
}
SimpleAutoCompleteResult.prototype = {
_searchString: "",
_searchResult: 0,
_defaultIndex: 0,
_errorDescription: "",
_results: [],
_comments: [],
getLabelAt: function(index) { return this._results[index]; },
/**
* The original search string
*/
get searchString() {
return this._searchString;
},
/**
* The result code of this result object, either:
* RESULT_IGNORED (invalid searchString)
* RESULT_FAILURE (failure)
* RESULT_NOMATCH (no matches found)
* RESULT_SUCCESS (matches found)
*/
get searchResult() {
return this._searchResult;
},
/**
* Index of the default item that should be entered if none is selected
*/
get defaultIndex() {
return this._defaultIndex;
},
/**
* A string describing the cause of a search failure
*/
get errorDescription() {
return this._errorDescription;
},
/**
* The number of matches
*/
get matchCount() {
return this._results.length;
},
/**
* Get the value of the result at the given index
*/
getValueAt: function(index) {
return this._results[index];
},
/**
* Get the comment of the result at the given index
*/
getCommentAt: function(index) {
return this._comments[index];
},
/**
* Get the style hint for the result at the given index
*/
getStyleAt: function(index) {
if (!this._comments[index])
return null; // not a category label, so no special styling
if (index == 0)
return "suggestfirst"; // category label on first line of results
return "suggesthint"; // category label on any other line of results
},
/**
* Get the image for the result at the given index
* The return value is expected to be an URI to the image to display
*/
getImageAt : function (index) {
return "";
},
/**
* Remove the value at the given index from the autocomplete results.
* If removeFromDb is set to true, the value should be removed from
* persistent storage as well.
*/
removeValueAt: function(index, removeFromDb) {
this._results.splice(index, 1);
this._comments.splice(index, 1);
},
QueryInterface: function(aIID) {
if (!aIID.equals(Ci.nsIAutoCompleteResult) && !aIID.equals(Ci.nsISupports))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
}
};
// Implements nsIAutoCompleteSearch
function SimpleAutoCompleteSearch() {
}
SimpleAutoCompleteSearch.prototype = {
classID: CLASS_ID,
classDescription: CLASS_NAME,
contractID: CONTRACT_ID,
/*
* Search for a given string and notify a listener (either synchronously
* or asynchronously) of the result
*
* #param searchString - The string to search for
* #param searchParam - An extra parameter
* #param previousResult - A previous result to use for faster searchinig
* #param listener - A listener to notify when the search is complete
*/
startSearch: function(searchString, searchParam, result, listener) {
// This autocomplete source assumes the developer attached a JSON string
// to the the "autocompletesearchparam" attribute or "searchParam" property
// of the <textbox> element. The JSON is converted into an array and used
// as the source of match data. Any values that match the search string
// are moved into temporary arrays and passed to the AutoCompleteResult
if (searchParam.length > 0) {
var nativeJSON = Components.classes["#mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
var searchResults = nativeJSON.decode(searchParam);
var results = [];
var comments = [];
for (i=0; i<searchResults.length; i++) {
if (searchResults[i].value.indexOf(searchString) == 0) {
results.push(searchResults[i].value);
if (searchResults[i].comment)
comments.push(searchResults[i].comment);
else
comments.push(null);
}
}
var newResult = new SimpleAutoCompleteResult(searchString, Ci.nsIAutoCompleteResult.RESULT_SUCCESS, 0, "", results, comments);
listener.onSearchResult(this, newResult);
}
},
/*
* Stop an asynchronous search that is in progress
*/
stopSearch: function() {
},
QueryInterface: function(aIID) {
if (!aIID.equals(Ci.nsIAutoCompleteSearch) && !aIID.equals(Ci.nsISupports))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
},
_QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIAutoCompleteSearch])
};
// Factory
var SimpleAutoCompleteSearchFactory = {
singleton: null,
createInstance: function (aOuter, aIID) {
if (aOuter != null)
throw Components.results.NS_ERROR_NO_AGGREGATION;
if (this.singleton == null)
this.singleton = new SimpleAutoCompleteSearch();
return this.singleton.QueryInterface(aIID);
}
};
// Module
var SimpleAutoCompleteSearchModule = {
registerSelf: function(aCompMgr, aFileSpec, aLocation, aType) {
aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
aCompMgr.registerFactoryLocation(CLASS_ID, CLASS_NAME, CONTRACT_ID, aFileSpec, aLocation, aType);
},
unregisterSelf: function(aCompMgr, aLocation, aType) {
aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
aCompMgr.unregisterFactoryLocation(CLASS_ID, aLocation);
},
getClassObject: function(aCompMgr, aCID, aIID) {
if (!aIID.equals(Components.interfaces.nsIFactory))
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
if (aCID.equals(CLASS_ID))
return SimpleAutoCompleteSearchFactory;
throw Components.results.NS_ERROR_NO_INTERFACE;
},
canUnload: function(aCompMgr) { return true; }
};
// Module initialization
function NSGetModule(aCompMgr, aFileSpec) { return SimpleAutoCompleteSearchModule; }
if (XPCOMUtils.generateNSGetFactory){
var NSGetFactory = XPCOMUtils.generateNSGetFactory([SimpleAutoCompleteSearch]);
}
I added this code to chrome.manifest:
component {6224daa1-71a2-4d1a-ad90-01ca1c08e323} components/simpleautocomplete.js
contract #mozilla.org/autocomplete/search;1?name=simple-autocomplete {6224daa1-71a2-4d1a-ad90-01ca1c08e323}
In the xul file I added:
<textbox type="autocomplete" autocompletesearch="simple-autocomplete"
autocompletesearchparam='[{"value":"param1"},{"value":"param2"}]' />
important note: when setting the autocompletesearchparam attribute the assignment has to be insind two single quotes and not double quotes. only the values inside has to be in double quotes as in the xul above.
and that's it.
When I tested my extension and typed 'p' in the auto-complete textbox the words "param1" and "param2" poped-up.
I could also set the params in the js file as follows:
var searchTextField = document.getElementById("searchTextField");
var param1 = "Param1", param2 = "Param2";
paramsToSet = "[";
paramsToSet += "{\"value\" : \"" + param1 + "\"},";
paramsToSet += "{\"value\" : \"" + param2 + "\"},";
paramsToSet = paramsToSet.substring(0, paramsToSet.length-1); // to remove the last ","
paramsToSet += "]";
paramsToSet = paramsToSet.toLowerCase(); // important!
searchTextField.setAttribute("autocompletesearchparam", paramsToSet);
note1: notice that the auto-complete textbox only accepts text in small letters.
note2: if setting the params to the textbox is dynamically and taking some time, for example - when the client is typing in the textbox and the typed text is sent to a server so the response containing the params will be attached to the textbox, and this process is taking some time (about a second or half), then the popup auto-complete won't popup because it's autocompletesearchparam was empty when the client started to type. In this case it is possible to force the auto-complete to popup when you have the params to set in the textbox like this:
searchTextField.open = true;
I Found the solution by myself, hope it will helps someone.
I had to do exactly as follows:
In the extension dir I created a new dir named "components".
Inside that dir I create a js file with my custom auto-complete name, in this case "simpleautocomplete.js". The content of that js file is:
/*
* https://developer.mozilla.org/en/How_to_implement_custom_autocomplete_search_component
*/
const Ci = Components.interfaces;
const CLASS_ID = Components.ID("6224daa1-71a2-4d1a-ad90-01ca1c08e323");
const CLASS_NAME = "Simple AutoComplete";
const CONTRACT_ID = "#mozilla.org/autocomplete/search;1?name=simple-autocomplete";
try{
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
} catch (x) {
}
// Implements nsIAutoCompleteResult
function SimpleAutoCompleteResult(searchString, searchResult,
defaultIndex, errorDescription,
results, comments) {
this._searchString = searchString;
this._searchResult = searchResult;
this._defaultIndex = defaultIndex;
this._errorDescription = errorDescription;
this._results = results;
this._comments = comments;
}
SimpleAutoCompleteResult.prototype = {
_searchString: "",
_searchResult: 0,
_defaultIndex: 0,
_errorDescription: "",
_results: [],
_comments: [],
getLabelAt: function(index) { return this._results[index]; },
/**
* The original search string
*/
get searchString() {
return this._searchString;
},
/**
* The result code of this result object, either:
* RESULT_IGNORED (invalid searchString)
* RESULT_FAILURE (failure)
* RESULT_NOMATCH (no matches found)
* RESULT_SUCCESS (matches found)
*/
get searchResult() {
return this._searchResult;
},
/**
* Index of the default item that should be entered if none is selected
*/
get defaultIndex() {
return this._defaultIndex;
},
/**
* A string describing the cause of a search failure
*/
get errorDescription() {
return this._errorDescription;
},
/**
* The number of matches
*/
get matchCount() {
return this._results.length;
},
/**
* Get the value of the result at the given index
*/
getValueAt: function(index) {
return this._results[index];
},
/**
* Get the comment of the result at the given index
*/
getCommentAt: function(index) {
return this._comments[index];
},
/**
* Get the style hint for the result at the given index
*/
getStyleAt: function(index) {
if (!this._comments[index])
return null; // not a category label, so no special styling
if (index == 0)
return "suggestfirst"; // category label on first line of results
return "suggesthint"; // category label on any other line of results
},
/**
* Get the image for the result at the given index
* The return value is expected to be an URI to the image to display
*/
getImageAt : function (index) {
return "";
},
/**
* Remove the value at the given index from the autocomplete results.
* If removeFromDb is set to true, the value should be removed from
* persistent storage as well.
*/
removeValueAt: function(index, removeFromDb) {
this._results.splice(index, 1);
this._comments.splice(index, 1);
},
QueryInterface: function(aIID) {
if (!aIID.equals(Ci.nsIAutoCompleteResult) && !aIID.equals(Ci.nsISupports))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
}
};
// Implements nsIAutoCompleteSearch
function SimpleAutoCompleteSearch() {
}
SimpleAutoCompleteSearch.prototype = {
classID: CLASS_ID,
classDescription: CLASS_NAME,
contractID: CONTRACT_ID,
/*
* Search for a given string and notify a listener (either synchronously
* or asynchronously) of the result
*
* #param searchString - The string to search for
* #param searchParam - An extra parameter
* #param previousResult - A previous result to use for faster searchinig
* #param listener - A listener to notify when the search is complete
*/
startSearch: function(searchString, searchParam, result, listener) {
// This autocomplete source assumes the developer attached a JSON string
// to the the "autocompletesearchparam" attribute or "searchParam" property
// of the <textbox> element. The JSON is converted into an array and used
// as the source of match data. Any values that match the search string
// are moved into temporary arrays and passed to the AutoCompleteResult
if (searchParam.length > 0) {
var nativeJSON = Components.classes["#mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
var searchResults = nativeJSON.decode(searchParam);
var results = [];
var comments = [];
for (i=0; i<searchResults.length; i++) {
if (searchResults[i].value.indexOf(searchString) == 0) {
results.push(searchResults[i].value);
if (searchResults[i].comment)
comments.push(searchResults[i].comment);
else
comments.push(null);
}
}
var newResult = new SimpleAutoCompleteResult(searchString, Ci.nsIAutoCompleteResult.RESULT_SUCCESS, 0, "", results, comments);
listener.onSearchResult(this, newResult);
}
},
/*
* Stop an asynchronous search that is in progress
*/
stopSearch: function() {
},
QueryInterface: function(aIID) {
if (!aIID.equals(Ci.nsIAutoCompleteSearch) && !aIID.equals(Ci.nsISupports))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
},
_QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIAutoCompleteSearch])
};
// Factory
var SimpleAutoCompleteSearchFactory = {
singleton: null,
createInstance: function (aOuter, aIID) {
if (aOuter != null)
throw Components.results.NS_ERROR_NO_AGGREGATION;
if (this.singleton == null)
this.singleton = new SimpleAutoCompleteSearch();
return this.singleton.QueryInterface(aIID);
}
};
// Module
var SimpleAutoCompleteSearchModule = {
registerSelf: function(aCompMgr, aFileSpec, aLocation, aType) {
aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
aCompMgr.registerFactoryLocation(CLASS_ID, CLASS_NAME, CONTRACT_ID, aFileSpec, aLocation, aType);
},
unregisterSelf: function(aCompMgr, aLocation, aType) {
aCompMgr = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
aCompMgr.unregisterFactoryLocation(CLASS_ID, aLocation);
},
getClassObject: function(aCompMgr, aCID, aIID) {
if (!aIID.equals(Components.interfaces.nsIFactory))
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
if (aCID.equals(CLASS_ID))
return SimpleAutoCompleteSearchFactory;
throw Components.results.NS_ERROR_NO_INTERFACE;
},
canUnload: function(aCompMgr) { return true; }
};
// Module initialization
function NSGetModule(aCompMgr, aFileSpec) { return SimpleAutoCompleteSearchModule; }
if (XPCOMUtils.generateNSGetFactory){
var NSGetFactory = XPCOMUtils.generateNSGetFactory([SimpleAutoCompleteSearch]);
}
I added this code to chrome.manifest:
component {6224daa1-71a2-4d1a-ad90-01ca1c08e323} components/simpleautocomplete.js
contract #mozilla.org/autocomplete/search;1?name=simple-autocomplete {6224daa1-71a2-4d1a-ad90-01ca1c08e323}
In the xul file I added:
<textbox type="autocomplete" autocompletesearch="simple-autocomplete"
autocompletesearchparam='[{"value":"param1"},{"value":"param2"}]' />
important note: when setting the autocompletesearchparam attribute the assignment has to be insind two single quotes and not double quotes. only the values inside has to be in double quotes as in the xul above.
and that's it.
When I tested my extension and typed 'p' in the auto-complete textbox the words "param1" and "param2" poped-up.
I could also set the params in the js file as follows:
var searchTextField = document.getElementById("searchTextField");
var param1 = "Param1", param2 = "Param2";
paramsToSet = "[";
paramsToSet += "{\"value\" : \"" + param1 + "\"},";
paramsToSet += "{\"value\" : \"" + param2 + "\"},";
paramsToSet = paramsToSet.substring(0, paramsToSet.length-1); // to remove the last ","
paramsToSet += "]";
paramsToSet = paramsToSet.toLowerCase(); // important!
searchTextField.setAttribute("autocompletesearchparam", paramsToSet);
note1: notice that the auto-complete textbox only accepts text in small letters.
note2: if setting the params to the textbox is dynamically and taking some time, for example - when the client is typing in the textbox and the typed text is sent to a server so the response containing the params will be attached to the textbox, and this process is taking some time (about a second or half), then the popup auto-complete won't popup because it's autocompletesearchparam was empty when the client started to type. In this case it is possible to force the auto-complete to popup when you have the params to set in the textbox like this:
searchTextField.open = true;

Categories