Bug 875812 - Uplift Add-on SDK integration branch to Firefox

2593d0a01b...007eb037f0
This commit is contained in:
Dave Townsend 2013-05-24 09:29:33 -07:00
parent 3fd2b773dd
commit 021af0fcd2
16 changed files with 405 additions and 114 deletions

View File

@ -20,12 +20,69 @@ commands (for example `--help`). `cfx` supports the following global options:
-v, --verbose - enable lots of output
</pre>
"Command-specific options" are only
applicable to a subset of the commands.
"Command-specific options" are documented alongside the commands.
## Supported Commands ##
There are five supported cfx commands:
### cfx docs ###
<table>
<colgroup>
<col width="10%">
<col width="90%">
</colgroup>
<tr>
<td>
<a href="dev-guide/cfx-tool.html#cfx-docs"><code>cfx docs</code></a>
</td>
<td>
Display the documentation for the SDK.
</td>
</tr>
<tr>
<td>
<a href="dev-guide/cfx-tool.html#cfx-init"><code>cfx init</code></a>
</td>
<td>
Create a skeleton add-on as a starting point for your own add-on.
</td>
</tr>
<tr>
<td>
<a href="dev-guide/cfx-tool.html#cfx-run"><code>cfx run</code></a>
</td>
<td>
Launch an instance of Firefox with your add-on installed.
</td>
</tr>
<tr>
<td>
<a href="dev-guide/cfx-tool.html#cfx-test"><code>cfx test</code></a>
</td>
<td>
Runs your add-on's unit tests.
</td>
</tr>
<tr>
<td>
<a href="dev-guide/cfx-tool.html#cfx-xpi"><code>cfx xpi</code></a>
</td>
<td>
Package your add-on as an <a href="https://developer.mozilla.org/en/XPI">XPI</a>
file, which is the install file format for Firefox add-ons.
</td>
</tr>
</table>
There are also a number of
[internal commands](dev-guide/cfx-tool.html#internal-commands),
which are more likely to be useful to SDK developers than to add-on developers.
## <a name="cfx-docs">cfx docs</a> ##
This command displays the documentation for the SDK. The documentation is
shipped with the SDK in [Markdown](http://daringfireball.net/projects/markdown/)
@ -46,7 +103,8 @@ This command will regenerate only the HTML page you're reading.
This is useful if you're iteratively editing a single file, and don't want to wait for cfx to
regenerate the complete documentation tree.
### cfx init ####
## <a name="cfx-init">cfx init</a> ##
Create a new directory called "my-addon", change into it, and run `cfx init`.
This command will create an skeleton add-on, as a starting point for your
@ -73,14 +131,13 @@ own add-on development, with the following file structure:
<div style="clear:both"></div>
### cfx run ###
## <a name="cfx-run">cfx run</a> ##
This command is used to run the add-on. Called with no options it looks for a
file called `package.json` in the current directory, loads the corresponding
add-on, and runs it under the version of Firefox it finds in the platform's
default install path.
#### Supported Options #####
### Supported Options ####
You can point `cfx run` at a different `package.json` file using the
`--pkgdir` option, and pass arguments to your add-on using the
@ -190,7 +247,7 @@ See <a href="dev-guide/cfx-tool.html#profiledir">
</table>
#### Experimental Options ####
### Experimental Options ###
<table>
<colgroup>
@ -233,6 +290,25 @@ To launch the application, enter the following command:
</td>
</tr>
<tr>
<td>
<code>-o, --overload-modules</code>
</td>
<td>
<p>In early versions of the SDK, the SDK modules used by an add-on
were themselves included in the add-on. The SDK modules now ship as
part of Firefox. From Firefox 21 onwards, SDK add-ons built with
SDK 1.14 or higher will use the SDK modules that are built into Firefox,
even if the add-on includes its own copies of the SDK modules.</p>
<p>Use this flag to reverse that behavior: if this flag is set and
the add-on includes its own copies of the SDK modules, then the add-on
will use the SDK modules in the add-on, not the ones built into Firefox.</p>
<p>This flag is particularly useful for SDK developers or people working with
the development version of the SDK, who may want to run an add-on using newer
versions of the modules than than those shipping in Firefox.</p>
</td>
</tr>
<tr>
<td>
<code>--templatedir=TEMPLATEDIR</code>
@ -249,7 +325,7 @@ To launch the application, enter the following command:
</table>
#### Internal Options ####
### Internal Options ###
<table>
<colgroup>
@ -291,8 +367,7 @@ To launch the application, enter the following command:
</table>
### cfx test ###
## <a name="cfx-test">cfx test</a> ##
Run available tests for the specified package.
<span class="aside">Note the hyphen after "test" in the module name.
@ -310,7 +385,7 @@ See the
[reference documentation for the `assert` module](modules/sdk/test/assert.html)
for details.
#### Supported Options #####
### Supported Options ###
As with `cfx run` you can use options to control which host application binary
version to use, and to select a profile.
@ -416,7 +491,7 @@ times.
</table>
#### Experimental Options ####
### Experimental Options ###
<table>
<colgroup>
@ -461,16 +536,26 @@ To launch the application, enter the following command:
<tr>
<td>
<code>--use-server</code>
<code>-o, --overload-modules</code>
</td>
<td>
Run tests using a server previously started with <code>cfx develop</code>.
<p>In early versions of the SDK, the SDK modules used by an add-on
were themselves included in the add-on. The SDK modules now ship as
part of Firefox. From Firefox 21 onwards, SDK add-ons built with
SDK 1.14 or higher will use the SDK modules that are built into Firefox,
even if the add-on includes its own copies of the SDK modules.</p>
<p>Use this flag to reverse that behavior: if this flag is set and
the add-on includes its own copies of the SDK modules, then the add-on
will use the SDK modules in the add-on, not the ones built into Firefox.</p>
<p>This flag is particularly useful for SDK developers or people working with
the development version of the SDK, who may want to run an add-on using newer
versions of the modules than than those shipping in Firefox.</p>
</td>
</tr>
</table>
#### Internal Options ####
### Internal Options ###
<table>
<colgroup>
@ -545,8 +630,7 @@ To launch the application, enter the following command:
</table>
### cfx xpi ###
## <a name="cfx-xpi">cfx xpi</a> ##
This tool is used to package your add-on as an
[XPI](https://developer.mozilla.org/en/XPI) file, which is the install file
format for Mozilla add-ons.
@ -557,7 +641,7 @@ the current directory and creates the corresponding XPI file.
Once you have built an XPI file you can distribute your add-on by submitting
it to [addons.mozilla.org](http://addons.mozilla.org).
#### updateURL and updateLink ####
### updateURL and updateLink ###
If you choose to host the XPI yourself you should enable the host application
to find new versions of your add-on.
@ -597,7 +681,7 @@ So if we run the following command:
* an RDF file which embeds `https://example.com/addon/latest` as the value of
`updateLink`.
#### Supported Options ####
### Supported Options ###
As with `cfx run` you can point `cfx` at a different `package.json` file using
the `--pkgdir` option. You can also embed arguments in the XPI using the
@ -675,7 +759,7 @@ add-on whenever it is run.
</table>
#### Experimental Options ####
### Experimental Options ###
<table>
<colgroup>
@ -699,7 +783,7 @@ add-on whenever it is run.
</table>
#### Internal Options ####
### Internal Options ###
<table>
<colgroup>
@ -721,35 +805,7 @@ add-on whenever it is run.
</table>
## Experimental Commands ##
### cfx develop ###
This initiates an instance of a host application in development mode,
and allows you to pipe commands into it from another shell without
having to constantly restart it. Aside from convenience, for SDK
Platform developers this has the added benefit of making it easier to
detect leaks.
For example, in shell A, type:
<pre>
cfx develop
</pre>
In shell B, type:
<pre>
cfx test --use-server
</pre>
This will send `cfx test --use-server` output to shell A. If you repeat the
command in shell B, `cfx test --use-server` output will appear again in shell A
without restarting the host application.
`cfx develop` doesn't take any options.
## Internal Commands ##
## <a name="internal-commands">Internal Commands</a> ##
### cfx sdocs ###

View File

@ -6,20 +6,22 @@ The `request` module lets you make simple yet powerful network requests.
<api name="Request">
@class
The `Request` object is used to make `GET`, `POST` or `PUT` network requests.
It is constructed with a URL to which the request is sent. Optionally the user
may specify a collection of headers and content to send alongside the request
and a callback which will be executed once the request completes.
The `Request` object is used to make `GET`, `POST`, `PUT`, or `HEAD`
network requests. It is constructed with a URL to which the request is sent.
Optionally the user may specify a collection of headers and content to send
alongside the request and a callback which will be executed once the
request completes.
Once a `Request` object has been created a `GET` request can be executed by
calling its `get()` method, a `POST` request by calling its `post()` method,
or a `PUT` request by calling its `put()` method.
a `PUT` request by calling its `put()` method, or a `HEAD` request by calling
its `head()` method.
When the server completes the request, the `Request` object emits a "complete"
event. Registered event listeners are passed a `Response` object.
Each `Request` object is designed to be used once. Once `GET`, `POST` or `PUT`
are called, attempting to call either will throw an error.
Each `Request` object is designed to be used once. Once `GET`, `POST`, `PUT`,
or `HEAD` are called, attempting to call either will throw an error.
Since the request is not being made by any particular website, requests made
here are not subject to the same-domain restriction that requests made in web
@ -150,6 +152,12 @@ Make a `PUT` request.
@returns {Request}
</api>
<api name="head">
@method
Make a `HEAD` request.
@returns {Request}
</api>
<api name="complete">
@event
The `Request` object emits this event when the request has completed and a
@ -165,8 +173,8 @@ Listener functions are passed the response to the request as a `Response` object
<api name="Response">
@class
The Response object contains the response to a network request issued using a
`Request` object. It is returned by the `get()`, `post()` or `put()` method of a
`Request` object.
`Request` object. It is returned by the `get()`, `post()`, `put()`, or `head()`
method of a `Request` object.
All members of a `Response` object are read-only.
<api name="text">

View File

@ -11,11 +11,12 @@ module.metadata = {
const { Cc, Ci } = require('chrome');
const { descriptor, Sandbox, evaluate, main, resolveURI } = require('toolkit/loader');
const { once } = require('../system/events');
const { exit, env, staticArgs, name } = require('../system');
const { exit, env, staticArgs } = require('../system');
const { when: unload } = require('../system/unload');
const { loadReason } = require('../self');
const { rootURI } = require("@loader/options");
const globals = require('../system/globals');
const xulApp = require('../system/xul-app');
const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].
getService(Ci.nsIAppShellService);
@ -23,13 +24,20 @@ const NAME2TOPIC = {
'Firefox': 'sessionstore-windows-restored',
'Fennec': 'sessionstore-windows-restored',
'SeaMonkey': 'sessionstore-windows-restored',
'Thunderbird': 'mail-startup-done',
'*': 'final-ui-startup'
'Thunderbird': 'mail-startup-done'
};
// Set 'final-ui-startup' as default topic for unknown applications
let appStartup = 'final-ui-startup';
// Gets the topic that fit best as application startup event, in according with
// the current application (e.g. Firefox, Fennec, Thunderbird...)
const APP_STARTUP = NAME2TOPIC[name] || NAME2TOPIC['*'];
for (let name of Object.keys(NAME2TOPIC)) {
if (xulApp.is(name)) {
appStartup = NAME2TOPIC[name];
break;
}
}
// Initializes default preferences
function setDefaultPrefs(prefsURI) {
@ -66,7 +74,7 @@ function definePseudo(loader, id, exports) {
}
function wait(reason, options) {
once(APP_STARTUP, function() {
once(appStartup, function() {
startup(null, options);
});
}
@ -145,10 +153,10 @@ function run(options) {
program.main({
loadReason: loadReason,
staticArgs: staticArgs
}, {
staticArgs: staticArgs
}, {
print: function print(_) { dump(_ + '\n') },
quit: exit
quit: exit
});
}
} catch (error) {

View File

@ -566,7 +566,7 @@ ModuleTabTracker.prototype = {
_TAB_EVENTS: ["TabOpen", "TabClose", "TabSelect", "DOMContentLoaded",
"load", "MozAfterPaint"],
_safeTrackTab: function safeTrackTab(tab) {
tab.addEventListener("load", this, false);
tab.linkedBrowser.addEventListener("load", this, true);
tab.linkedBrowser.addEventListener("MozAfterPaint", this, false);
this._tabs.push(tab);
try {
@ -576,7 +576,7 @@ ModuleTabTracker.prototype = {
}
},
_safeUntrackTab: function safeUntrackTab(tab) {
tab.removeEventListener("load", this, false);
tab.linkedBrowser.removeEventListener("load", this, true);
tab.linkedBrowser.removeEventListener("MozAfterPaint", this, false);
var index = this._tabs.indexOf(tab);
if (index == -1)
@ -617,7 +617,11 @@ ModuleTabTracker.prototype = {
}
},
_safeLoad: function safeLoad(event) {
let tab = event.target;
let win = event.currentTarget.ownerDocument.defaultView;
let tabIndex = win.gBrowser.getBrowserIndexForDocument(event.target);
if (tabIndex == -1)
return;
let tab = win.gBrowser.tabContainer.getItemAtIndex(tabIndex);
let index = this._tabs.indexOf(tab);
if (index == -1)
console.error("internal error: tab not found");

View File

@ -11,6 +11,7 @@ const { Cc, Ci } = require('chrome');
const { EventEmitter } = require('../deprecated/events');
const { Trait } = require('../deprecated/traits');
const { when } = require('../system/unload');
const events = require('../system/events');
const { getInnerId, getOuterId, windows, isDocumentLoaded, isBrowser,
getMostRecentBrowserWindow, getMostRecentWindow } = require('../window/utils');
const errors = require('../deprecated/errors');
@ -68,6 +69,8 @@ function WindowTracker(delegate) {
for each (let window in getWindows())
this._regWindow(window);
windowWatcher.registerNotification(this);
this._onToplevelWindowReady = this._onToplevelWindowReady.bind(this);
events.on('toplevel-window-ready', this._onToplevelWindowReady);
require('../system/unload').ensure(this);
@ -116,6 +119,7 @@ WindowTracker.prototype = {
unload: function unload() {
windowWatcher.unregisterNotification(this);
events.off('toplevel-window-ready', this._onToplevelWindowReady);
for each (let window in getWindows())
this._unregWindow(window);
},
@ -128,14 +132,20 @@ WindowTracker.prototype = {
}
}),
_onToplevelWindowReady: function _onToplevelWindowReady({subject}) {
let window = subject;
// ignore private windows if they are not supported
if (ignoreWindow(window))
return;
this._regWindow(window);
},
observe: errors.catchAndLog(function observe(subject, topic, data) {
var window = subject.QueryInterface(Ci.nsIDOMWindow);
// ignore private windows if they are not supported
if (ignoreWindow(window))
return;
if (topic == 'domwindowopened')
this._regWindow(window);
else
if (topic == 'domwindowclosed')
this._unregWindow(window);
})
};

View File

@ -19,7 +19,8 @@ const { isPrivateBrowsingSupported } = require('./self');
const { isWindowPBSupported } = require('./private-browsing/utils');
const { Class } = require("./core/heritage");
const { merge } = require("./util/object");
const { WorkerHost, Worker, detach, attach } = require("./worker/utils");
const { WorkerHost, Worker, detach, attach,
requiresAddonGlobal } = require("./worker/utils");
const { Disposable } = require("./core/disposable");
const { contract: loaderContract } = require("./content/loader");
const { contract } = require("./util/contract");
@ -32,24 +33,6 @@ const { filter, pipe } = require("./event/utils");
const { getNodeView, getActiveView } = require("./view/core");
const { isNil, isObject } = require("./lang/type");
let isArray = Array.isArray;
let assetsURI = require("./self").data.url();
function isAddonContent({ contentURL }) {
return typeof(contentURL) === "string" && contentURL.indexOf(assetsURI) === 0;
}
function hasContentScript({ contentScript, contentScriptFile }) {
return (isArray(contentScript) ? contentScript.length > 0 :
!!contentScript) ||
(isArray(contentScriptFile) ? contentScriptFile.length > 0 :
!!contentScriptFile);
}
function requiresAddonGlobal(model) {
return isAddonContent(model) && !hasContentScript(model);
}
function getAttachEventType(model) {
let when = model.contentScriptWhen;
return requiresAddonGlobal(model) ? "sdk-panel-content-changed" :

View File

@ -50,12 +50,14 @@ const { validateOptions, validateSingleOption } = new OptionsValidator({
const REUSE_ERROR = "This request object has been used already. You must " +
"create a new one to make a new request."
// Utility function to prep the request since it's the same between GET and
// POST
// Utility function to prep the request since it's the same between
// request types
function runRequest(mode, target) {
let source = request(target)
let { xhr, url, content, contentType, headers, overrideMimeType } = source;
let isGetOrHead = (mode == "GET" || mode == "HEAD");
// If this request has already been used, then we can't reuse it.
// Throw an error.
if (xhr)
@ -63,11 +65,11 @@ function runRequest(mode, target) {
xhr = source.xhr = new XMLHttpRequest();
// Build the data to be set. For GET requests, we want to append that to
// the URL before opening the request.
// Build the data to be set. For GET or HEAD requests, we want to append that
// to the URL before opening the request.
let data = stringify(content);
// If the URL already has ? in it, then we want to just use &
if (mode == "GET" && data)
if (isGetOrHead && data)
url = url + (/\?/.test(url) ? "&" : "?") + data;
// open the request
@ -97,8 +99,8 @@ function runRequest(mode, target) {
};
// actually send the request.
// We don't want to send data on GET requests.
xhr.send(mode !== "GET" ? data : null);
// We don't want to send data on GET or HEAD requests.
xhr.send(!isGetOrHead ? data : null);
}
const Request = Class({
@ -138,6 +140,10 @@ const Request = Class({
put: function() {
runRequest('PUT', this);
return this;
},
head: function() {
runRequest('HEAD', this);
return this;
}
});
exports.Request = Request;

View File

@ -12,6 +12,8 @@ const { Cc, Ci, Cr } = require("chrome");
const { Class } = require("./core/heritage");
const base64 = require("./base64");
var tlds = Cc["@mozilla.org/network/effective-tld-service;1"]
.getService(Ci.nsIEffectiveTLDService);
var ios = Cc['@mozilla.org/network/io-service;1']
.getService(Ci.nsIIOService);
@ -19,6 +21,9 @@ var ios = Cc['@mozilla.org/network/io-service;1']
var resProt = ios.getProtocolHandler("resource")
.QueryInterface(Ci.nsIResProtocolHandler);
var URLParser = Cc["@mozilla.org/network/url-parser;1?auth=no"]
.getService(Ci.nsIURLParser);
function newURI(uriStr, base) {
try {
let baseURI = base ? ios.newURI(base, null, null) : null;
@ -92,11 +97,29 @@ function URL(url, base) {
port = uri.port == -1 ? null : uri.port;
} catch (e if e.result == Cr.NS_ERROR_FAILURE) {}
let uriData = [uri.path, uri.path.length, {}, {}, {}, {}, {}, {}];
URLParser.parsePath.apply(URLParser, uriData);
let [{ value: filepathPos }, { value: filepathLen },
{ value: queryPos }, { value: queryLen },
{ value: refPos }, { value: refLen }] = uriData.slice(2);
let hash = uri.ref ? "#" + uri.ref : "";
let pathname = uri.path.substr(filepathPos, filepathLen);
let search = uri.path.substr(queryPos, queryLen);
search = search ? "?" + search : "";
this.__defineGetter__("scheme", function() uri.scheme);
this.__defineGetter__("userPass", function() userPass);
this.__defineGetter__("host", function() host);
this.__defineGetter__("hostname", function() host);
this.__defineGetter__("port", function() port);
this.__defineGetter__("path", function() uri.path);
this.__defineGetter__("pathname", function() pathname);
this.__defineGetter__("hash", function() hash);
this.__defineGetter__("href", function() uri.spec);
this.__defineGetter__("origin", function() uri.prePath);
this.__defineGetter__("protocol", function() uri.scheme + ":");
this.__defineGetter__("search", function() search);
Object.defineProperties(this, {
toString: {
@ -235,6 +258,17 @@ const DataURL = Class({
exports.DataURL = DataURL;
let getTLD = exports.getTLD = function getTLD (url) {
let uri = newURI(url.toString());
let tld = null;
try {
tld = tlds.getPublicSuffix(uri);
} catch (e if
e.result == Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS ||
e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS) {}
return tld;
};
let isValidURI = exports.isValidURI = function (uri) {
try {
newURI(uri);

View File

@ -17,10 +17,34 @@ const { Loader } = require("../content/loader");
const { merge } = require("../util/object");
const { emit } = require("../event/core");
const LegacyWorker = WorkerTrait.resolve({
let assetsURI = require("../self").data.url();
let isArray = Array.isArray;
function isAddonContent({ contentURL }) {
return typeof(contentURL) === "string" && contentURL.indexOf(assetsURI) === 0;
}
function hasContentScript({ contentScript, contentScriptFile }) {
return (isArray(contentScript) ? contentScript.length > 0 :
!!contentScript) ||
(isArray(contentScriptFile) ? contentScriptFile.length > 0 :
!!contentScriptFile);
}
function requiresAddonGlobal(model) {
return isAddonContent(model) && !hasContentScript(model);
}
exports.requiresAddonGlobal = requiresAddonGlobal;
const LegacyWorker = WorkerTrait.compose(Loader).resolve({
_setListeners: "__setListeners",
}).compose(Loader, {
_injectInDocument: "__injectInDocument",
contentURL: "__contentURL"
}).compose({
_setListeners: function() {},
get contentURL() this._window.document.URL,
get _injectInDocument() requiresAddonGlobal(this),
attach: function(window) this._attach(window),
detach: function() this._workerCleanup()
});

View File

@ -49,8 +49,8 @@ function open(url, options) {
let tab = getActiveTab(chromeWindow);
tab.addEventListener("load", function ready(event) {
let { document } = getTabContentWindow(this);
tab.linkedBrowser.addEventListener("load", function ready(event) {
let { document } = getTabContentWindow(tab);
if (document.readyState === "complete" && document.URL === url) {
this.removeEventListener(event.type, ready);
@ -60,7 +60,7 @@ function open(url, options) {
resolve(document.defaultView);
}
})
}, true);
setTabURL(tab, url);
});

View File

@ -0,0 +1,3 @@
<script>
addon.postMessage("hello addon")
</script>

View File

@ -0,0 +1,26 @@
/* 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 { Panel } = require("sdk/panel")
const { data } = require("sdk/self")
exports["test addon global"] = function(assert, done) {
let panel = Panel({
contentURL: //"data:text/html,now?",
data.url("./index.html"),
onMessage: function(message) {
assert.pass("got message from panel script");
panel.destroy();
done();
},
onError: function(error) {
asser.fail(Error("failed to recieve message"));
done();
}
});
};
require("sdk/test/runner").runTestsFromModule(module);

View File

@ -0,0 +1,3 @@
{
"id": "test-privileged-addon"
}

View File

@ -218,6 +218,26 @@ exports.testInvalidJSON = function (test) {
});
}
exports.testHead = function (test) {
let srv = startServerAsync(port, basePath);
srv.registerPathHandler("/test-head",
function handle(request, response) {
response.setHeader("Content-Type", "text/plain", false);
});
test.waitUntilDone();
Request({
url: "http://localhost:" + port + "/test-head",
onComplete: function (response) {
test.assertEqual(response.text, "");
test.assertEqual(response.statusText, "OK");
test.assertEqual(response.headers["Content-Type"], "text/plain");
srv.stop(function() test.done());
}
}).head();
}
function runMultipleURLs (srv, test, options) {
let urls = [options.url, URL(options.url)];
let cb = options.onComplete;

View File

@ -50,15 +50,15 @@ function open(url, options) {
let tab = getActiveTab(chromeWindow);
tab.addEventListener("load", function ready(event) {
let { document } = getTabContentWindow(this);
tab.linkedBrowser.addEventListener("load", function ready(event) {
let { document } = getTabContentWindow(tab);
if (document.readyState === "complete" && document.URL === url) {
this.removeEventListener(event.type, ready);
resolve(document.defaultView);
}
})
}, true);
setTabURL(tab, url);
});

View File

@ -3,6 +3,13 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
var url = require("sdk/url");
var { Loader } = require("sdk/test/loader");
var { pathFor } = require("sdk/system");
var file = require("sdk/io/file");
var loader = Loader(module);
var httpd = loader.require("sdk/test/httpd");
var port = 8099;
var tabs = require("sdk/tabs");
exports.testResolve = function(test) {
test.assertEqual(url.URL("bar", "http://www.foo.com/").toString(),
@ -33,12 +40,49 @@ exports.testResolve = function(test) {
};
exports.testParseHttp = function(test) {
var info = url.URL("http://foo.com/bar");
var aUrl = "http://sub.foo.com/bar?locale=en-US&otherArg=%20x%20#myhash";
var info = url.URL(aUrl);
test.assertEqual(info.scheme, "http");
test.assertEqual(info.host, "foo.com");
test.assertEqual(info.protocol, "http:");
test.assertEqual(info.host, "sub.foo.com");
test.assertEqual(info.hostname, "sub.foo.com");
test.assertEqual(info.port, null);
test.assertEqual(info.userPass, null);
test.assertEqual(info.path, "/bar");
test.assertEqual(info.path, "/bar?locale=en-US&otherArg=%20x%20#myhash");
test.assertEqual(info.pathname, "/bar");
test.assertEqual(info.href, aUrl);
test.assertEqual(info.hash, "#myhash");
test.assertEqual(info.search, "?locale=en-US&otherArg=%20x%20");
};
exports.testParseHttpSearchAndHash = function (test) {
var info = url.URL("https://www.moz.com/some/page.html");
test.assertEqual(info.hash, "");
test.assertEqual(info.search, "");
var hashOnly = url.URL("https://www.sub.moz.com/page.html#justhash");
test.assertEqual(hashOnly.search, "");
test.assertEqual(hashOnly.hash, "#justhash");
var queryOnly = url.URL("https://www.sub.moz.com/page.html?my=query");
test.assertEqual(queryOnly.search, "?my=query");
test.assertEqual(queryOnly.hash, "");
var qMark = url.URL("http://www.moz.org?");
test.assertEqual(qMark.search, "");
test.assertEqual(qMark.hash, "");
var hash = url.URL("http://www.moz.org#");
test.assertEqual(hash.search, "");
test.assertEqual(hash.hash, "");
var empty = url.URL("http://www.moz.org?#");
test.assertEqual(hash.search, "");
test.assertEqual(hash.hash, "");
var strange = url.URL("http://moz.org?test1#test2?test3");
test.assertEqual(strange.search, "?test1");
test.assertEqual(strange.hash, "#test2?test3");
};
exports.testParseHttpWithPort = function(test) {
@ -163,10 +207,12 @@ exports.testStringInterface = function(test) {
var a = URL(EM);
// make sure the standard URL properties are enumerable and not the String interface bits
test.assertEqual(Object.keys(a), "scheme,userPass,host,port,path", "enumerable key list check for URL.");
test.assertEqual(Object.keys(a),
"scheme,userPass,host,hostname,port,path,pathname,hash,href,origin,protocol,search",
"enumerable key list check for URL.");
test.assertEqual(
JSON.stringify(a),
"{\"scheme\":\"about\",\"userPass\":null,\"host\":null,\"port\":null,\"path\":\"addons\"}",
"{\"scheme\":\"about\",\"userPass\":null,\"host\":null,\"hostname\":null,\"port\":null,\"path\":\"addons\",\"pathname\":\"addons\",\"hash\":\"\",\"href\":\"about:addons\",\"origin\":\"about:\",\"protocol\":\"about:\",\"search\":\"\"}",
"JSON.stringify should return a object with correct props and vals.");
// make sure that the String interface exists and works as expected
@ -261,6 +307,53 @@ exports.testURLFromURL = function(test) {
test.assertEqual(aURL.toString(), bURL.toString(), 'Making a URL from a URL works');
};
exports.testTLD = function(test) {
let urls = [
{ url: 'http://my.sub.domains.mozilla.co.uk', tld: 'co.uk' },
{ url: 'http://my.mozilla.com', tld: 'com' },
{ url: 'http://my.domains.mozilla.org.hk', tld: 'org.hk' },
{ url: 'chrome://global/content/blah', tld: 'global' },
{ url: 'data:text/plain;base64,QXdlc29tZSE=', tld: null },
{ url: 'https://1.2.3.4', tld: null }
];
urls.forEach(function (uri) {
test.assertEqual(url.getTLD(uri.url), uri.tld);
test.assertEqual(url.getTLD(url.URL(uri.url)), uri.tld);
});
}
exports.testWindowLocationMatch = function (test) {
let srv = serve();
let aUrl = 'http://localhost:' + port + '/index.html?q=aQuery#somehash';
let urlObject = url.URL(aUrl);
test.waitUntilDone();
tabs.open({
url: aUrl,
onReady: function (tab) {
tab.attach({
onMessage: function (loc) {
for (let prop in loc) {
test.assertEqual(urlObject[prop], loc[prop], prop + ' matches');
}
tab.close();
srv.stop(test.done.bind(test));
},
contentScript: '(' + function () {
let res = {};
// `origin` is `null` in this context???
let props = 'hostname,port,pathname,hash,href,protocol,search'.split(',');
props.forEach(function (prop) {
res[prop] = window.location[prop];
});
self.postMessage(res);
} + ')()'
});
}
})
};
function validURIs() {
return [
'http://foo.com/blah_blah',
@ -352,3 +445,16 @@ function invalidURIs () {
// 'http://10.1.1.254'
];
}
function serve () {
let basePath = pathFor("ProfD");
let filePath = file.join(basePath, 'index.html');
let content = "<html><head></head><body><h1>url tests</h1></body></html>";
let fileStream = file.open(filePath, 'w');
fileStream.write(content);
fileStream.close();
let srv = httpd.startServerAsync(port, basePath);
return srv;
}