Eli Grey

pmxdr: postMessage cross-domain request library

pmxdr is a cross-domain HTTP request JavaScript library. pmxdr stands for postMessage cross-domain requester. As the name implies, it makes use of the HTML5 postMessage API to make HTTP requests. It requires that a pmxdr host be on the target domain and it respects all HTTP access control headers, even on browsers that don’t support them but do support postMessage, like Firefox 3.

You can download the pmxdr client library and the pmxdr host library (includes an example .htaccess file to help Apache users with PHP set it up) under the latest GNU GPL license and an MIT-style license. The host library must be able to be accessed from /pmxdr/api to be able to interact with the client library.

Read more at the pmxdr project page and try out the demo of it in action.

The following is a very simple example of how to use pmxdr to do a cross-domain POST request (impossible with the normal method of inserting a script tag):

// The requesting domain is example.net
// example.net doesn't want to give any control to example.com
pmxdr.request({
  method   : "post",
  uri      : "http://example.com/search.json",
  data     : "q=foo",
  callback : loadSearchJSON
})

Usually, in this hypothetical example, example.net would have to put a script tag with an src of http://example.com/search.json?q=foo&callback=loadSearchJSON and give example.com full control of example.net. This provides a secure cross-domain way to use APIs like this.

APNG feature detection

I have made a simple script that utilizes the HTML5 <canvas> API in only 9 functional lines of JavaScript to detect if a browser supports APNG images. It can be useful for deciding when to serve a client browser APNG images instead of GIF images.
This will set the variable, apng_supported to true if the browser supports APNG.
I have also created a demo that uses this script.

(function() {
	"use strict";
	var apngTest = new Image(),
	ctx = document.createElement("canvas").getContext("2d");
	apngTest.onload = function () {
		ctx.drawImage(apngTest, 0, 0);
		self.apng_supported = ctx.getImageData(0, 0, 1, 1).data[3] === 0;
	};
	apngTest.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACGFjVEwAAAABAAAAAcMq2TYAAAANSURBVAiZY2BgYPgPAAEEAQB9ssjfAAAAGmZjVEwAAAAAAAAAAQAAAAEAAAAAAAAAAAD6A+gBAbNU+2sAAAARZmRBVAAAAAEImWNgYGBgAAAABQAB6MzFdgAAAABJRU5ErkJggg==";
	// frame 1 (skipped on apng-supporting browsers): [0, 0, 0, 255]
	// frame 2: [0, 0, 0, 0]
}());

JavaScript Shell 1.4 Extended

Whenever I open up the JavaScript Shell 1.4 to test some JavaScript out, I don’t like that I have to use the default JavaScript version while testing out code. Generators, let expressions, and various other improvements to JavaScript can be pretty useful but they require an explicit JavaScript version to be declared, so I modified it and added support that will allow JavaScript code up to 2.0 (you can test higher versions by increasing _JS_version_check.end). I call it JavaScript Shell 1.4 Extended. Think of it as the unofficial 1.5 version. If you want the bookmarklet, it’s on this bookmarklet page.

This works by redefining the main shell input eval function multiple times under different JavaScript versions, incrementing by 0.1 for each tag added (they are later removed).

There are also a few other various modifications which you can read about on my JavaScript Shell Extended project page.

JIL 0.0.4

At this time I would be introducing Noteboard 2.1 but since the source files to that are on my corrupted hard drive, I began working on a updated version of JIL. One new great feature is chainable method calls.

Changelog for JIL 0.0.4:

  • JIL is now a function with properties, not just an object. Usage: JIL([prop [, arg1 [, ect]]]); The JIL function either returns itself or a property specified in the first argument if there is only one argument and it is not a method.
    • JIL("get", "OpenID", console.log) is the same as JIL.get("OpenID", console.log) and returns JIL
    • JIL("trust") will run JIL.trust() and returns JIL
    • JIL("origin") will return JIL.origin
    • JIL("undefined property that doesn't exit") returns JIL
  • JIL method calls are now chainable
  • No longer removes listeners if they are not already set
  • Added JIL.requestCallbacks.reset([key])
  • Replaced all single quotes with double quotes because I felt like it
  • JIL.loadFrame([readyCallback]) now passes readyCallback the JIL function
  • Added JIL.changeHost([origin [, path[, callback]]]) for easy host changing
  • JIL.query now only adds a callback if it is a function
  • JIL.handleMessage renamed to JIL.handleResponse
  • Grouped the fields first then the methods in the initialization for orginization
  • JIL.generateID removed and an anonymous ran-once function named safeRandID with collision prevention was placed inside JIL.query
  • JIL.frame.contentWindow.postMessage’s second argument is now always JIL.origin instead of “*”

You can download JIL 0.0.4 from code.eligrey.com/jdata/jil/0.0.4/

Cross-browser accessors

Xccessors is a script I made which implements the legacy methods for defining and looking up object accessors (getters and setters) of objects in JavaScript using ECMAScript 3.1’s accessors standard. This is aimed at adding support for the legacy method in IE8 RC1. Read more on the Xccessors project page. There is also a demo you can try out.

Update: I just tested the script in IE8 RC1 and it seems that IE8 RC1 only supports getters and setters on the DOM and the window object. So as long as you are setting an accessor on the window object or a DOM element, Xccessors should work fine.

jData now supports IE8 beta 2

I just now added support for IE8 beta 2 in jData 0.0.3. IE8 beta 2 has always had (for the most part), correct implementations of localStorage and postMessage, the two things that jData is built around. It was relatively easy to add support for IE as all I had to do was use attachEvent (window.onmessage doesn’t work in IE8 beta 2, which is also true for Firefox 3), fix Array.prototype.indexOf, and make get requests return null instead of undefined (which JSON wouldn’t stringify) when they should return null for it to work. Undefined objects in localStorage are supposed to return null (like how Firefox and Safari handle it), but IE8 beta 2 returns undefined.

I also added a new request type to jData, called “untrust”. Untrust is useful for giving a website the ability to remove itself from the trusted hosts list without telling the user to go to the jData Manager (which was also updated to work in IE8 beta 2). Untrust always returns true because if the host is already untrusted (default), nothing else needs to be done.

jData completely redone

I have recently completely redid all of jData to make a much securer version. I have also dropped the HTTP query parameter support due to most new advanced browsers support postMessage & localStorage (except Opera, which seems to currently only support postMessage).

The old version wasn’t practical due to having no security system that asked the user if they allowed an action, and the messages were just eval()’d right away. The new version features a much more reliable trust sytem that asks the user for confirmation before anything is set by an untrusted host. Getting data has no restrictions, though, like always. Only setting and removing data and requesting to become a trusted host prompt for user confirmation.

The jDataQuery() snippet I made is now obsolete (but still works as long as you make valid JSON requests that comply with the jData API Reference), and is replaced by JIL, an interface library for jData I also made today.

Tagged: , ,

jData’s major flaw (fixed)

jData is a great concept and all, but where it fails is that ANYONE can add/modify/delete item values. An example of when this works out good could be a when a legitimate website sets public.website to the website a user specifies and other websites auto-fill form fields with this data. An example of when it doesn’t work out is when any malicious website sets public.website to something like “example.com/BUY_MY_PRODUCT” without the user’s consent and websites auto-fill (or even worse, remove the option entirely and just use public.website) this data into form fields and the user submits the form, effectively advertising a company without knowledge of doing so.
Because of this flaw, I am going to rewrite jData 0.0.1 (and make a complete client-side interaction library) to work with trust-model that uses JSON for communication; version 0.0.2. The resulting code will obviously be larger (I will try to keep it compact) than the previous 300-byte version with no verification or JSON. For native JSON-supporting browsers like Firefox 3.1, the code will be an extra 2.73kb smaller (due to no need for the YUI compressed, then packed, json2.js), which is larger than the 1.5 to 2.0KB I expect the new jData implementation to be without json2.js. Every request to get data will always be trusted, but attempts to set and delete data will require user confirmation. User confirmation can be done away with if the user agrees to add a website issuing a request to become a trusted host. The HTTP query parameters API will stay (for the most part) exactly the same but will add user confirmation to set and delete requests if the requesting host is not trusted.

As a result of rewriting jData on a trust model, the standards page will be changed slightly (and moved to an actual page, instead of a post) and a main “jData” page will be added.

Update: All done

Tagged:

New jData API

Update: Please note that this information is obsolete and does not work with the new completely-rewritten jData host library.

When not using the postMessage API but using the HTTP query parameters API, you used to have to use the “callback” parameter and do something like callback=location.href="http://example.com/%3Fvalue="+encodeURIComponent(jdata)+"%26item="+locacation.search.replace… (and some code to figure out what item is being used) to be able to add support for browsers that support localStorage but not postMessage via server-side. I have simplified this by adding an “r” parameter. The r parameter is a URI that the jData frame will redirect to and you can include two different variables in the URI.

The first parameter is %i, which is automatically replaced with the item being accessed. The second parameter is %v, which is the value of the item being accessed.

Example: http://jdata.eligrey.com/?get=personal.fullname&r=http://example.com/?item=%i&value=%v

This redirects to http://example.com/?item=personal.fullname&value=Elijah%20Grey for me. If an item is not set, %v will either be null (Firefox & Safari nightlies) or undefined (IE) depending on the browser, but as not to confuse with a string of “null” or “undefined”, I have it return an empty string when it is undefined or null.

Tagged: ,

HTML 5 dataset support

Update: Getting (not setting) data-* attributes through Element.dataset now works in IE8 with the help of Xccessors.

I have made an implementation of HTML 5 dataset (data-* attributes) support that almost conforms to the HTML 5 spec* and works in Firefox 1.5+, Opera 9.5+, Safari, and Google Chrome. It uses getters and setters to simulate a psuedo-native HTML 5 dataset API. The code is licensed CC GNU LGPL and can be downloaded here. I have uploaded a demo (also in XHTML 5) that you can try out. The script includes these extra functions: Element.removeDataAttribute(name), Element.setDataAttribute(name, value), and Element.setDataAttributes() (all explained below).

*You cannot set new items the standard way like Element.dataset.name = value (already existing items can though). You either have to add new items by doing Element.setDataAttribute(name, value) for single additions and Element.setDataAttributes(object full of {name, value:String} pairs) to add multiple items at once. Instead of delete element.dataset[name], you must use element.dataset[name] = undefined or Element.removeDataAttribute(name) to remove data-name. Example of setting values:

var foo = document.createElement('div');
foo.setDataAttributes({'bar':'blah', 'lorem':'Lorem ipsum.'});
foo.dataset.bar == 'blah';
foo.dataset.bar = 'eligrey.com';
foo.dataset.bar == 'eligrey.com';
foo.setDataAttribute('foobar', foo.dataset.lorem);
foo.dataset.foobar == 'Lorem ipsum.'