mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Backed out 6d349e5ae0ed (bug 889157) for JP test bustage
This commit is contained in:
parent
d8503fcd52
commit
3b56ef362a
@ -22,8 +22,12 @@ Windows users using cmd.exe should instead run:
|
||||
|
||||
bin\activate.bat
|
||||
|
||||
Then go to https://addons.mozilla.org/developers/docs/sdk/latest/dev-guide to
|
||||
browse the SDK documentation.
|
||||
Then run:
|
||||
|
||||
cfx docs
|
||||
|
||||
This should start a documentation server and open a web browser
|
||||
with further instructions.
|
||||
|
||||
If you get an error when running cfx or have any other problems getting
|
||||
started, see the "Troubleshooting" guide at:
|
||||
|
@ -22,7 +22,7 @@ commands (for example `--help`). `cfx` supports the following global options:
|
||||
|
||||
"Command-specific options" are documented alongside the commands.
|
||||
|
||||
There are four supported cfx commands:
|
||||
There are five supported cfx commands:
|
||||
|
||||
<table>
|
||||
<colgroup>
|
||||
@ -30,6 +30,15 @@ There are four supported cfx commands:
|
||||
<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>
|
||||
@ -73,6 +82,27 @@ 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/)
|
||||
format. The first time this command is executed, and any time after the
|
||||
Markdown files on disk have changed, `cfx docs` will generate a set of HTML
|
||||
pages from them and launch a web browser to display them. If the Markdown files
|
||||
haven't changed, `cfx docs` just launches a browser initialized to the set of
|
||||
generated pages.
|
||||
|
||||
To regenerate the documentation associated with a single file, you can
|
||||
specify the file as an argument. For example:
|
||||
|
||||
<pre>
|
||||
cfx docs doc/dev-guide-source/addon-development/cfx-tool.md
|
||||
</pre>
|
||||
|
||||
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.
|
||||
|
||||
## <a name="cfx-init">cfx init</a> ##
|
||||
|
||||
Create a new directory called "my-addon", change into it, and run `cfx init`.
|
||||
@ -780,7 +810,8 @@ add-on whenever it is run.
|
||||
### cfx sdocs ###
|
||||
|
||||
Executing this command builds a static HTML version of the SDK documentation
|
||||
that can be hosted on a web server.
|
||||
that can be hosted on a web server without the special application support
|
||||
required by `cfx docs`.
|
||||
|
||||
#### Options ####
|
||||
|
||||
|
@ -5,203 +5,42 @@
|
||||
# console #
|
||||
|
||||
The `console` object enables your add-on to log messages. If you have started
|
||||
Firefox for your add-on from the command line with `cfx run` or `cfx test`
|
||||
then these messages appear in the command shell you used. If the add-on has
|
||||
been installed in Firefox, then the messages appear in the host application's
|
||||
the host application for your add-on from the command line (for example, by
|
||||
executing `cfx run` or `cfx test`) then these messages appear in the command
|
||||
shell you used. If the add-on has been installed in the host application, then
|
||||
the messages appear in the host application's
|
||||
[Error Console](https://developer.mozilla.org/en/Error_Console).
|
||||
|
||||
If you're developing your add-on using the
|
||||
[Add-on Builder](https://builder.addons.mozilla.org/) or are using
|
||||
the [Extension Auto-installer](https://addons.mozilla.org/en-US/firefox/addon/autoinstaller/),
|
||||
then the add-on is installed in Firefox, meaning that messages will appear in
|
||||
the Error Console. But see the discussion of
|
||||
[logging levels](dev-guide/console.html#Logging Levels): by default, messages
|
||||
logged using `log()`, `info()`, `trace()`, or `warn()` won't be logged in
|
||||
these situations.
|
||||
The `console` object has the following methods:
|
||||
|
||||
## Console Methods ##
|
||||
<code>console.**log**(*object*[, *object*, ...])</code>
|
||||
|
||||
All console methods except `exception()` and `trace()` accept one or
|
||||
more JavaScript objects as arguments and log them to the console.
|
||||
Logs an informational message to the shell.
|
||||
Depending on the console's underlying implementation and user interface,
|
||||
you may be able to examine the properties of non-primitive objects
|
||||
you may be able to introspect into the properties of non-primitive objects
|
||||
that are logged.
|
||||
|
||||
### <code>console.log(*object*[, *object*, ...])</code> ###
|
||||
|
||||
Logs the arguments to the console, preceded by "info:" and the name of your
|
||||
add-on:
|
||||
|
||||
console.log("This is an informational message");
|
||||
|
||||
<pre>
|
||||
info: my-addon: This is an informational message
|
||||
</pre>
|
||||
|
||||
### <code>console.info(*object*[, *object*, ...])</code> ###
|
||||
<code>console.**info**(*object*[, *object*, ...])</code>
|
||||
|
||||
A synonym for `console.log()`.
|
||||
|
||||
### <code>console.warn(*object*[, *object*, ...])</code> ###
|
||||
<code>console.**warn**(*object*[, *object*, ...])</code>
|
||||
|
||||
Logs the arguments to the console, preceded by "warn:" and the name of your
|
||||
add-on:
|
||||
Logs a warning message.
|
||||
|
||||
console.warn("This is a warning message");
|
||||
<code>console.**error**(*object*[, *object*, ...])</code>
|
||||
|
||||
<pre>
|
||||
warn: my-addon: This is a warning message
|
||||
</pre>
|
||||
Logs an error message.
|
||||
|
||||
### <code>console.error(*object*[, *object*, ...])</code> ###
|
||||
<code>console.**debug**(*object*[, *object*, ...])</code>
|
||||
|
||||
Logs the arguments to the console, preceded by "error:" and the name of your
|
||||
add-on:
|
||||
Logs a debug message.
|
||||
|
||||
console.error("This is an error message");
|
||||
|
||||
<pre>
|
||||
error: my-addon: This is an error message
|
||||
</pre>
|
||||
|
||||
### <code>console.debug(*object*[, *object*, ...])</code> ###
|
||||
|
||||
Logs the arguments to the console, preceded by "debug:" and the name of your
|
||||
add-on:
|
||||
|
||||
console.error("This is a debug message");
|
||||
|
||||
<pre>
|
||||
debug: my-addon: This is a debug message
|
||||
</pre>
|
||||
|
||||
### <code>console.exception(*exception*)</code> ###
|
||||
<code>console.**exception**(*exception*)</code>
|
||||
|
||||
Logs the given exception instance as an error, outputting information
|
||||
about the exception's stack traceback if one is available.
|
||||
|
||||
try {
|
||||
doThing();
|
||||
} catch (e) {
|
||||
console.exception(e);
|
||||
}
|
||||
<code>console.**trace**()</code>
|
||||
|
||||
function UserException(message) {
|
||||
this.message = message;
|
||||
this.name = "UserException";
|
||||
}
|
||||
|
||||
function doThing() {
|
||||
throw new UserException("Thing could not be done!");
|
||||
}
|
||||
|
||||
<pre>
|
||||
error: my-addon: An exception occurred.
|
||||
UserException: Thing could not be done!
|
||||
</pre>
|
||||
|
||||
### <code>console.trace()</code> ###
|
||||
|
||||
Logs a stack trace at the point the function is called.
|
||||
|
||||
<h2 id="Logging Levels">Logging Levels</h2>
|
||||
|
||||
Logging's useful, of course, especially during development. But the more
|
||||
logging there is, the more noise you see in the console output.
|
||||
Especially when debug logging shows up in a production environment, the
|
||||
noise can make it harder, not easier, to debug issues.
|
||||
|
||||
This is the problem that logging levels are designed to fix. The console
|
||||
defines a number of logging levels, from "more verbose" to "less verbose",
|
||||
and a number of different logging functions that correspond to these levels,
|
||||
which are arranged in order of "severity" from informational
|
||||
messages, through warnings, to errors.
|
||||
|
||||
At a given logging level, only calls to the corresponding functions and
|
||||
functions with a higher severity will have any effect.
|
||||
|
||||
For example, if the logging level is set to "info", then calls to `info()`,
|
||||
`log()`, `warn()`, and `error()` will all result in output being written.
|
||||
But if the logging level is "warn" then only calls to `warn()` and `error()`
|
||||
have any effect, and calls to `info()` and `log()` are simply discarded.
|
||||
|
||||
This means that the same code can be more verbose in a development
|
||||
environment than in a production environment - you just need to arrange for
|
||||
the appropriate logging level to be set.
|
||||
|
||||
The complete set of logging levels is given in the table below, along
|
||||
with the set of functions that will result in output at each level:
|
||||
|
||||
<table>
|
||||
<colgroup>
|
||||
<col width="10%">
|
||||
<col width="90%">
|
||||
</colgroup>
|
||||
|
||||
<tr>
|
||||
<th>Level</th>
|
||||
<th>Will log calls to:</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>all</td>
|
||||
<td>Any console method</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>debug</td>
|
||||
<td><code>debug()</code>, <code>log()</code>, <code>info()</code>, <code>trace()</code>, <code>warn()</code>, <code>exception()</code>, <code>error()</code></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>info</td>
|
||||
<td><code>log()</code>, <code>info()</code>, <code>trace()</code>, <code>warn()</code>, <code>exception()</code>, <code>error()</code></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>warn</td>
|
||||
<td><code>warn()</code>, <code>exception()</code>, <code>error()</code></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>error</td>
|
||||
<td><code>exception()</code>, <code>error()</code></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>off</td>
|
||||
<td>Nothing</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
### Setting the Logging Level ###
|
||||
|
||||
The logging level defaults to "error".
|
||||
|
||||
There are two system preferences that can be used to override this default:
|
||||
|
||||
* **extensions.sdk.console.logLevel**: if set, this determines the logging
|
||||
level for all installed SDK-based add-ons.
|
||||
|
||||
* **extensions.[extension-id].sdk.console.logLevel**: if set, this determines
|
||||
the logging level for the specified add-on. This overrides the global
|
||||
preference if both are set.
|
||||
|
||||
Both these preferences can be set programmatically using the
|
||||
[`preferences/service`](modules/sdk/preferences/service.html) API, or manually
|
||||
using [about:config](http://kb.mozillazine.org/About:config). The value for each
|
||||
preference is the desired logging level, given as a string.
|
||||
|
||||
When you run your add-on using `cfx run` or `cfx test`, the global
|
||||
**extensions.sdk.console.logLevel** preference is automatically set to "info".
|
||||
This means that calls to `console.log()` will appear in the console output.
|
||||
|
||||
When you install an add-on into Firefox, the logging level will be "error"
|
||||
by default (that is, unless you have set one of the two preferences). This
|
||||
means that messages written using `debug()`, `log()`, `info()`, `trace()`,
|
||||
and `warn()` will not appear in the console.
|
||||
|
||||
This includes add-ons being developed using the
|
||||
[Add-on Builder](https://builder.addons.mozilla.org/) or the
|
||||
[Extension Auto-installer](https://addons.mozilla.org/en-US/firefox/addon/autoinstaller/).
|
||||
Logs a stack trace at the point this function is called.
|
||||
|
@ -1,177 +0,0 @@
|
||||
<!-- 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/. -->
|
||||
|
||||
# Cross-domain Content Scripts #
|
||||
|
||||
By default, content scripts don't have any cross-domain privileges.
|
||||
In particular, they can't:
|
||||
|
||||
* [access content hosted in an `iframe`, if that content is served from a different domain](dev-guide/guides/content-scripts/cross-domain.html#Cross-domain iframes)
|
||||
* [make cross-domain XMLHttpRequests](dev-guide/guides/content-scripts/cross-domain.html#Cross-domain XMLHttpRequest)
|
||||
|
||||
However, you can enable these features for specific domains
|
||||
by adding them to your add-on's [package.json](dev-guide/package-spec.html)
|
||||
under the `"cross-domain-content"` key, which itself lives under the
|
||||
`"permissions"` key:
|
||||
|
||||
<pre>
|
||||
"permissions": {
|
||||
"cross-domain-content": ["http://example.org/", "http://example.com/"]
|
||||
}
|
||||
</pre>
|
||||
|
||||
* The domains listed must include the scheme and fully qualified domain name,
|
||||
and these must exactly match the domains serving the content - so in the
|
||||
example above, the content script will not be allowed to access content
|
||||
served from `https://example.com/`.
|
||||
* Wildcards are not allowed.
|
||||
* This feature is currently only available for content scripts, not for page
|
||||
scripts included in HTML files shipped with your add-on.
|
||||
|
||||
## Cross-domain iframes ##
|
||||
|
||||
The following "main.js" creates a page-worker which loads a local HTML file
|
||||
called "page.html", attaches a content script called "page.js" to the
|
||||
page, waits for messages from the script, and logs the payload.
|
||||
|
||||
//main.js
|
||||
var data = require("sdk/self").data;
|
||||
|
||||
var pageWorker = require("sdk/page-worker").Page({
|
||||
contentURL: data.url("page.html"),
|
||||
contentScriptFile: data.url("page-script.js")
|
||||
});
|
||||
|
||||
pageWorker.on("message", function(message) {
|
||||
console.log(message);
|
||||
});
|
||||
|
||||
The "page.html" file embeds an iframe whose content is
|
||||
served from "http://en.m.wikipedia.org/":
|
||||
|
||||
<pre class="brush: html">
|
||||
<!doctype html>
|
||||
<!-- page.html -->
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<iframe id="wikipedia" src="http://en.m.wikipedia.org/"></iframe>
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
|
||||
The "page-script.js" file locates "Today's Featured Article" and sends its
|
||||
content to "main.js":
|
||||
|
||||
// page-script.js
|
||||
var iframe = window.document.getElementById("wikipedia");
|
||||
var todaysFeaturedArticle = iframe.contentWindow.document.getElementById("mp-tfa");
|
||||
self.postMessage(todaysFeaturedArticle.textContent);
|
||||
|
||||
For this to work, we need to add the `"cross-domain-content"` key to
|
||||
"package.json":
|
||||
|
||||
<pre>
|
||||
"permissions": {
|
||||
"cross-domain-content": ["http://en.m.wikipedia.org/"]
|
||||
}
|
||||
</pre>
|
||||
|
||||
The add-on should successfully retrieve the iframe's content.
|
||||
|
||||
## Cross-domain XMLHttpRequest ##
|
||||
|
||||
The following add-on creates a panel whose content is the summary weather
|
||||
forecast for [Shetland](https://en.wikipedia.org/wiki/Shetland).
|
||||
If you want to try it out, you'll need to
|
||||
[register](http://www.metoffice.gov.uk/datapoint/support/API)
|
||||
and get an API key.
|
||||
|
||||
The "main.js":
|
||||
|
||||
* creates a panel whose content is supplied by "panel.html" and
|
||||
adds a content script "panel-script.js" to it
|
||||
* sends the panel a "show" message when it is shown
|
||||
* attaches the panel to a widget
|
||||
|
||||
<!-- terminate Markdown list -->
|
||||
|
||||
// main.js
|
||||
var data = require("sdk/self").data;
|
||||
|
||||
var forecast_panel = require("sdk/panel").Panel({
|
||||
height: 50,
|
||||
contentURL: data.url("panel.html"),
|
||||
contentScriptFile: data.url("panel-script.js")
|
||||
});
|
||||
|
||||
forecast_panel.on("show", function(){
|
||||
forecast_panel.port.emit("show");
|
||||
});
|
||||
|
||||
require("sdk/widget").Widget({
|
||||
id: "forecast",
|
||||
label: "Weather Forecast",
|
||||
contentURL: "http://www.metoffice.gov.uk/favicon.ico",
|
||||
panel: forecast_panel
|
||||
});
|
||||
|
||||
The "panel.html" just includes a `<div>` block for the forecast:
|
||||
|
||||
<pre class="brush: html">
|
||||
<!doctype HTML>
|
||||
<!-- panel.html -->
|
||||
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<div id="forecast_summary"></div>
|
||||
</body>
|
||||
</html>
|
||||
</pre>
|
||||
|
||||
The "panel-script.js" uses [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest)
|
||||
to fetch the latest forecast:
|
||||
|
||||
// panel-script.js
|
||||
|
||||
var url = "http://datapoint.metoffice.gov.uk/public/data/txt/wxfcs/regionalforecast/json/500?key=YOUR-API-KEY";
|
||||
|
||||
self.port.on("show", function () {
|
||||
var request = new XMLHttpRequest();
|
||||
request.open("GET", url, true);
|
||||
request.onload = function () {
|
||||
var jsonResponse = JSON.parse(request.responseText);
|
||||
var summary = getSummary(jsonResponse);
|
||||
var element = document.getElementById("forecast_summary");
|
||||
element.textContent = summary;
|
||||
};
|
||||
request.send();
|
||||
});
|
||||
|
||||
function getSummary(forecast) {
|
||||
return forecast.RegionalFcst.FcstPeriods.Period[0].Paragraph[0].$;
|
||||
}
|
||||
|
||||
|
||||
Finally, we need to add the `"cross-domain-content"` key to "package.json":
|
||||
|
||||
<pre>
|
||||
"permissions": {
|
||||
"cross-domain-content": ["http://datapoint.metoffice.gov.uk"]
|
||||
}
|
||||
</pre>
|
||||
|
||||
## Content Permissions and unsafeWindow ##
|
||||
|
||||
If you use `"cross-domain-content"`, then JavaScript values in content
|
||||
scripts will not be available from pages. Suppose your content script includes
|
||||
a line like:
|
||||
|
||||
// content-script.js:
|
||||
unsafeWindow.myCustomAPI = function () {};
|
||||
|
||||
If you have included the `"cross-domain-content"` key, when the page script
|
||||
tries to access `myCustomAPI` this will result in a "permission denied"
|
||||
exception.
|
@ -92,7 +92,5 @@ how to communicate between your add-on and its content scripts using the
|
||||
* [Communicating using <code>postMessage()</code>](dev-guide/guides/content-scripts/using-postmessage.html):
|
||||
how to communicate between your add-on and its content scripts using the
|
||||
<code>postMessage()</code> API
|
||||
* [Cross-domain Content Scripts](dev-guide/guides/content-scripts/cross-domain.html):
|
||||
how to enable a content script to interact with content served from other domains.
|
||||
* [Example](dev-guide/guides/content-scripts/reddit-example.html):
|
||||
a simple example add-on using content scripts
|
||||
|
@ -143,12 +143,6 @@ This page lists more theoretical in-depth articles about the SDK.
|
||||
|
||||
<tr>
|
||||
|
||||
<td>
|
||||
<h4><a href="dev-guide/guides/content-scripts/cross-domain.html">Cross-domain content scripts</a></h4>
|
||||
How to enable content scripts to interact with content served from different domains.
|
||||
</td>
|
||||
|
||||
|
||||
<td>
|
||||
<h4><a href="dev-guide/guides/content-scripts/reddit-example.html">Reddit example</a></h4>
|
||||
A simple add-on which uses content scripts.
|
||||
|
@ -53,77 +53,6 @@ accomplish most of what it needs using the supported APIs, then it might
|
||||
still be worth migrating: we'll add more supported APIs in future releases
|
||||
to meet important use cases.
|
||||
|
||||
## <a name="user-interface-components">User Interface Components</a>##
|
||||
|
||||
XUL-based add-ons typically implement a user interface using a combination
|
||||
of two techniques: XUL overlays and XUL windows.
|
||||
|
||||
### XUL Overlays ###
|
||||
|
||||
XUL overlays are used to modify existing windows such as the main browser
|
||||
window. In this way an extension can integrate its user interface into the
|
||||
browser: for example, adding menu items, buttons, and toolbars.
|
||||
|
||||
Because SDK-based extensions are restartless, they can't use XUL overlays. To
|
||||
add user interface components to the browser, there are a few different
|
||||
options. In order of complexity, the main options are:
|
||||
|
||||
* the SDK includes modules that implement some basic user interface
|
||||
components including [buttons](modules/sdk/widget.html),
|
||||
[dialogs](modules/sdk/panel.html), and
|
||||
[context menu items](modules/sdk/context-menu.html).
|
||||
|
||||
* there is a collection of
|
||||
[community-developed modules](https://github.com/mozilla/addon-sdk/wiki/Community-developed-modules)
|
||||
that includes various user interface components, including
|
||||
[toolbar buttons](https://github.com/voldsoftware/toolbarbutton-jplib) and
|
||||
[menu items](https://github.com/voldsoftware/menuitems-jplib).
|
||||
|
||||
* by using the SDK's
|
||||
[low-level APIs](dev-guide/guides/xul-migration.html#Using the Low-level APIs)
|
||||
you can directly modify the browser chrome.
|
||||
|
||||
### XUL Windows
|
||||
|
||||
XUL windows are used to define completely new windows to host user interface
|
||||
elements specific to the add-on.
|
||||
|
||||
The SDK generally expects you to specify your user interface using HTML, not
|
||||
XUL. However, you can include a
|
||||
[chrome.manifest file](https://developer.mozilla.org/en-US/docs/Chrome_Registration)
|
||||
in your add-on and it will be included in the generated XPI.
|
||||
|
||||
<ul class="tree">
|
||||
<li>my-addon
|
||||
<ul>
|
||||
<li class="highlight-tree-node">chrome
|
||||
<ul><li>content</li>
|
||||
<li>locale</li>
|
||||
<li>skin</li></ul>
|
||||
</li>
|
||||
<li class="highlight-tree-node">chrome.manifest</li>
|
||||
<li>data</li>
|
||||
<li>lib</li>
|
||||
<li>package.json</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
There are limitations on what you can do in this manifest file: for example,
|
||||
you can't register overlays, `resource:` URIs, or components. However, you
|
||||
can register a `chrome:` URI, with a skin and locale, and this means you
|
||||
can include XUL windows in an SDK-based add-on.
|
||||
|
||||
You can keep the "chrome.manifest" file in your add-on's root directory
|
||||
and create a directory there called "chrome". In that directory you can keep
|
||||
your "content", "locale", and "skin" subdirectories:
|
||||
|
||||
This allows you to refer to objects in these directories from "chrome.manifest" using a relative path, like "chrome/content".
|
||||
|
||||
This is provided only as a migration aid, and it's still a good idea to port XUL windows to HTML.
|
||||
|
||||
<div style="clear:both"></div>
|
||||
|
||||
## <a name="content-scripts">Content Scripts</a> ##
|
||||
|
||||
In a XUL-based add-on, code that uses XPCOM objects, code that manipulates
|
||||
@ -142,8 +71,27 @@ page script.
|
||||
|
||||
A XUL-based add-on will need to be reorganized to respect this distinction.
|
||||
|
||||
The main reason for this design is security: it reduces the risk that a
|
||||
malicious web page will be able to access privileged APIs.
|
||||
Suppose an add-on wants to make a cross-domain XMLHttpRequest based on some
|
||||
data extracted from a web page. In a XUL-based extension you would implement
|
||||
all this in a single script. An SDK-based equivalent would need to be
|
||||
structured like this:
|
||||
|
||||
* the main add-on code (1) attaches a content script to the page, and (2)
|
||||
registers a listener function for messages from the content script
|
||||
* the content script (3) extracts the data from the page and (4) sends
|
||||
it to the main add-on code in a message
|
||||
* the main add-on code (5) receives the message and (6) sends the request,
|
||||
using the SDK's [`request`](modules/sdk/request.html) API
|
||||
|
||||
<img class="image-center" src="static-files/media/xul-migration-cs.png"
|
||||
alt="Content script organization">
|
||||
|
||||
There are two related reasons for this design. The first is security: it
|
||||
reduces the risk that a malicious web page will be able to access privileged
|
||||
APIs. The second is the need to be compatible with the multi-process architecture
|
||||
planned for Firefox: after this is implemented in Firefox, all add-ons will
|
||||
need to use a similar pattern, so it's likely that a XUL-based add-on will
|
||||
need to be rewritten anyway.
|
||||
|
||||
There's much more information on content scripts in the
|
||||
[Working With Content Scripts](dev-guide/guides/content-scripts/index.html) guide.
|
||||
|
@ -179,13 +179,9 @@ directory the first time you run
|
||||
indicating whether or not the
|
||||
add-on supports private browsing. If this value is not <code>true</code>
|
||||
or is omitted, then the add-on will not see any private windows or
|
||||
objects, such as tabs, that are associated with private windows. See the
|
||||
documentation for the
|
||||
<a href="modules/sdk/private-browsing.html"><code>private-browsing</code> module</a>.</p>
|
||||
<p><strong><code>cross-domain-content</code></strong>: a list of domains for
|
||||
which content scripts are given cross-domain privileges to access content in
|
||||
iframes or to make XMLHTTPRequests. See the documentation for
|
||||
<a href="dev-guide/guides/content-scripts/cross-domain.html">enabling cross-domain content scripts</a>.</p>
|
||||
objects, such as tabs, that are associated with private windows. See the
|
||||
documentation for the
|
||||
<a href="modules/sdk/private-browsing.html"><code>private-browsing</code> module</a>.</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<div id="cse" style="width: 100%;">Loading</div>
|
||||
<script src="https://www.google.com/jsapi" type="text/javascript"></script>
|
||||
<script src="http://www.google.com/jsapi" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
function parseQueryFromUrl () {
|
||||
var queryParamName = "q";
|
||||
@ -38,7 +38,7 @@ google.setOnLoadCallback(function() {
|
||||
}, true);
|
||||
</script>
|
||||
|
||||
<link rel="stylesheet" href="https://www.google.com/cse/style/look/default.css" type="text/css" />
|
||||
<link rel="stylesheet" href="http://www.google.com/cse/style/look/default.css" type="text/css" />
|
||||
|
||||
<style type="text/css">
|
||||
#cse table, #cse tr, #cse td {
|
||||
|
@ -21,50 +21,39 @@ This tutorial does double-duty. It describes the general method for
|
||||
using an external, third-party module in your add-on, and it
|
||||
describes how to add a menu item using the `menuitems` module in particular.
|
||||
|
||||
First, create a new add-on. Make a directory called "clickme" wherever you
|
||||
like, navigate to it and run `cfx init`.
|
||||
|
||||
<pre>
|
||||
mkdir clickme
|
||||
cd clickme
|
||||
cfx init
|
||||
</pre>
|
||||
|
||||
The usual directory structure will be created:
|
||||
|
||||
<ul class="tree">
|
||||
<li>clickme
|
||||
<ul>
|
||||
<li>data</li>
|
||||
<li>docs
|
||||
<ul><li>main.md</li></ul>
|
||||
</li>
|
||||
<li>lib
|
||||
<ul><li>main.js</li></ul>
|
||||
</li>
|
||||
<li>package.json</li>
|
||||
<li>README.md</li>
|
||||
<li>tests
|
||||
<ul><li>test-main.js</li></ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div style="clear:both"></div>
|
||||
|
||||
## Installing `menuitems` ##
|
||||
|
||||
Create a directory under "clickme" called "packages".
|
||||
Then download the `menuitems` package from
|
||||
[https://github.com/erikvold/menuitems-jplib](https://github.com/erikvold/menuitems-jplib/zipball/51080383cbb0fe2a05f8992a8aae890f4c014176) and extract it into the "packages" directory you just created:
|
||||
First we'll download the `menuitems` package from
|
||||
[https://github.com/erikvold/menuitems-jplib](https://github.com/erikvold/menuitems-jplib/zipball/51080383cbb0fe2a05f8992a8aae890f4c014176).
|
||||
|
||||
Third-party packages like `menuitems` can be installed in three
|
||||
different places:
|
||||
|
||||
* in the `packages` directory under the SDK root. If you do this the package
|
||||
is available to any other add-ons you're developing using that SDK instance,
|
||||
and the package's documentation is visible through `cfx docs`.
|
||||
* in a `packages` directory you create under your add-on's root: if you
|
||||
do this, the package is only available to that add-on.
|
||||
* in a directory indicated using the `packages` key in
|
||||
your add-on's [package.json](dev-guide/package-spec.html). If you
|
||||
do this, you may not keep any packages in your add-on's `packages`
|
||||
directory, or they will not be found.
|
||||
|
||||
In this example we will install the package under the SDK root. From
|
||||
the SDK root directory, execute something like the following commands:
|
||||
|
||||
<pre>
|
||||
mkdir packages
|
||||
cd packages
|
||||
tar -xf ../erikvold-menuitems-jplib-d80630c.zip
|
||||
</pre>
|
||||
|
||||
Now if you run `cfx docs` you'll see a new section appear in the sidebar
|
||||
labeled "Third-Party APIs", which lists the modules in the `menuitems`
|
||||
package: this package contains a single module, also
|
||||
called `menuitems`.
|
||||
|
||||
Click on the module name and you'll see API documentation for the module.
|
||||
|
||||
## Module Dependencies ##
|
||||
|
||||
If third-party modules only depend on SDK modules, you can use them right
|
||||
@ -86,9 +75,12 @@ and adding it under the `packages` directory alongside `menuitems`.
|
||||
|
||||
## Using `menuitems` ##
|
||||
|
||||
The [documentation for the `menuitems` module](https://github.com/erikvold/menuitems-jplib/blob/master/docs/menuitems.md)
|
||||
tells us to create a menu item using `MenuItem()`. Of the options
|
||||
accepted by `MenuItem()`, we'll use this minimal set:
|
||||
We can use the `menuitems` module in exactly the same way we use built-in
|
||||
modules.
|
||||
|
||||
The documentation for the `menuitems` module tells us to we create a menu
|
||||
item using `MenuItem()`. Of the options accepted by `MenuItem()`, we'll use
|
||||
this minimal set:
|
||||
|
||||
* `id`: identifier for this menu item
|
||||
* `label`: text the item displays
|
||||
@ -97,7 +89,9 @@ accepted by `MenuItem()`, we'll use this minimal set:
|
||||
* `insertbefore`: identifier for the item before which we want our item to
|
||||
appear
|
||||
|
||||
<!--comment to terminate Markdown list -->
|
||||
Next, create a new add-on. Make a directory called 'clickme' wherever you
|
||||
like, navigate to it and run `cfx init`. Open `lib/main.js` and add the
|
||||
following code:
|
||||
|
||||
var menuitem = require("menuitems").Menuitem({
|
||||
id: "clickme",
|
||||
@ -132,6 +126,17 @@ console.
|
||||
|
||||
## Caveats ##
|
||||
|
||||
Third-party modules are a great way to use features not directly supported by
|
||||
the SDK, but because third party modules typically use low-level APIs,
|
||||
they may be broken by new releases of Firefox.
|
||||
Eventually we expect the availability of a rich set of third party packages
|
||||
will be one of the most valuable aspects of the SDK. Right now they're a great
|
||||
way to use features not supported by the supported APIs without the
|
||||
complexity of using the low-level APIs, but there are some caveats you should
|
||||
be aware of:
|
||||
|
||||
* our support for third party packages is still fairly immature. One
|
||||
consequence of this is that it's not always obvious where to find third-party
|
||||
packages, although the
|
||||
[Community Developed Modules](https://github.com/mozilla/addon-sdk/wiki/Community-developed-modules)
|
||||
page in the SDK's GitHub Wiki lists a number of packages.
|
||||
|
||||
* because third party modules typically use low-level APIs, they may be broken
|
||||
by new releases of Firefox.
|
||||
|
@ -143,6 +143,11 @@ This is the `cfx` command-line program. It's your primary interface to the
|
||||
Add-on SDK. You use it to launch Firefox and test your add-on, package your
|
||||
add-on for distribution, view documentation, and run unit tests.
|
||||
|
||||
## cfx docs ##
|
||||
|
||||
If you're reading these documents online, try running `cfx docs`. This will
|
||||
build the documentation for the SDK and display it in a browser.
|
||||
|
||||
## Problems? ##
|
||||
|
||||
Try the [Troubleshooting](dev-guide/tutorials/troubleshooting.html)
|
||||
|
@ -61,15 +61,6 @@ If you've installed the add-on in Firefox, or you're running the
|
||||
add-on in the Add-on Builder, then the messages appear in Firefox's
|
||||
[Error Console](https://developer.mozilla.org/en/Error_Console).
|
||||
|
||||
But note that **by default, calls to `console.log()` will not result
|
||||
in any output in the Error Console for any installed add-ons**: this
|
||||
includes add-ons installed using the Add-on Builder or using tools
|
||||
like the
|
||||
[Extension Auto-installer](https://addons.mozilla.org/en-US/firefox/addon/autoinstaller/).
|
||||
|
||||
See ["Logging Levels"](dev-guide/console.html#Logging Levels)
|
||||
in the console reference documentation for more information on this.
|
||||
|
||||
## Learning More ##
|
||||
|
||||
For the complete `console` API, see its
|
||||
|
@ -312,6 +312,7 @@ Modules not yet supported in Firefox Mobile are
|
||||
- [loader/sandbox](modules/sdk/loader/sandbox.html)
|
||||
- [net/url](modules/sdk/net/url.html)
|
||||
- [net/xhr](modules/sdk/net/xhr.html)
|
||||
- [page-mod/match-pattern](modules/sdk/page-mod/match-pattern.html)
|
||||
- [platform/xpcom](modules/sdk/platform/xpcom.html)
|
||||
- [preferences/service](modules/sdk/preferences/service.html)
|
||||
- [system/environment](modules/sdk/system/environment.html)
|
||||
@ -329,7 +330,6 @@ Modules not yet supported in Firefox Mobile are
|
||||
- [util/collection](modules/sdk/util/collection.html)
|
||||
- [util/deprecate](modules/sdk/util/deprecate.html)
|
||||
- [util/list](modules/sdk/util/list.html)
|
||||
- [util/match-pattern](modules/sdk/util/match-pattern.html)
|
||||
- util/registry
|
||||
- [util/uuid](modules/sdk/util/uuid.html)
|
||||
- [window/utils](modules/sdk/window/utils.html)
|
||||
|
@ -52,7 +52,7 @@ alt="ietf.org eaten by page-mod" />
|
||||
## Specifying the Match Pattern ##
|
||||
|
||||
The match pattern uses the
|
||||
[`match-pattern`](modules/sdk/util/match-pattern.html)
|
||||
[`match-pattern`](modules/sdk/page-mod/match-pattern.html)
|
||||
syntax. You can pass a single match-pattern string, or an array.
|
||||
|
||||
## Keeping the Content Script in a Separate File ##
|
||||
|
@ -371,6 +371,27 @@ Next we'll repackage the geolocation module.
|
||||
* delete the "main.js" that `cfx` generated, and copy "geolocation.js"
|
||||
there instead.
|
||||
|
||||
### Documentation ###
|
||||
|
||||
If you document your modules, people who install your package and
|
||||
execute `cfx docs` will see the documentation
|
||||
integrated with the SDK's own documentation.
|
||||
|
||||
You can document the geolocation module by creating a file called
|
||||
"geolocation.md" in your package's "doc" directory. This file is also
|
||||
written in Markdown, although you can optionally use some
|
||||
[extended syntax](https://wiki.mozilla.org/Jetpack/SDK/Writing_Documentation#APIDoc_Syntax)
|
||||
to document APIs.
|
||||
|
||||
Try it:
|
||||
|
||||
* add a "geolocation.md" under "doc"
|
||||
* copy your geolocation package under the "packages" directory in the SDK root
|
||||
* execute `cfx docs`
|
||||
|
||||
Once `cfx docs` has finished, you should see a new entry appear in the
|
||||
sidebar called "Third-Party APIs", which lists the geolocation module.
|
||||
|
||||
### Editing "package.json" ###
|
||||
|
||||
The "package.json" file in your package's root directory contains metadata
|
||||
|
@ -121,7 +121,7 @@ exported by the `context-menu` module.
|
||||
the patterns. These are the same match pattern strings that you use with
|
||||
the <a href="modules/sdk/page-mod.html"><code>page-mod</code></a>
|
||||
<code>include</code> property.
|
||||
<a href="modules/sdk/util/match-pattern.html">Read more about patterns</a>.
|
||||
<a href="modules/sdk/page-mod/match-pattern.html">Read more about patterns</a>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -803,7 +803,7 @@ top-level context menu.
|
||||
Creates a context that matches pages with particular URLs. See Specifying
|
||||
Contexts above.
|
||||
@param matchPattern {string,array}
|
||||
A [match pattern](modules/sdk/util/match-pattern.html) string, regexp or an
|
||||
A [match pattern](modules/sdk/page-mod/match-pattern.html) string, regexp or an
|
||||
array of match pattern strings or regexps.
|
||||
</api>
|
||||
</api>
|
||||
|
@ -303,7 +303,7 @@ Creates a page-mod.
|
||||
});
|
||||
|
||||
You can specify a set of URLs using a
|
||||
[regular expression](modules/sdk/util/match-pattern.html#Regular Expressions).
|
||||
[regular expression](modules/sdk/page-mod/match-pattern.html#Regular Expressions).
|
||||
The pattern must match the entire URL, not just a subset, and has
|
||||
`global`, `ignoreCase`, and `multiline` disabled.
|
||||
|
||||
@ -321,7 +321,7 @@ Creates a page-mod.
|
||||
contentScript: 'window.alert("Page matches ruleset");'
|
||||
});
|
||||
|
||||
See the [match-pattern](modules/sdk/util/match-pattern.html) module for
|
||||
See the [match-pattern](modules/sdk/page-mod/match-pattern.html) module for
|
||||
a detailed description of match pattern syntax.
|
||||
|
||||
@prop [contentScriptFile] {string,array}
|
||||
|
@ -174,7 +174,7 @@ add-on's [data](modules/sdk/self.html#data) directory, use `resource://`.
|
||||
You can specify patterns using a
|
||||
[regular expression](https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions):
|
||||
|
||||
var { MatchPattern } = require("sdk/util/match-pattern");
|
||||
var { MatchPattern } = require("sdk/page-mod/match-pattern");
|
||||
var pattern = new MatchPattern(/.*example.*/);
|
||||
|
||||
The regular expression is subject to restrictions based on those applied to the
|
||||
@ -233,7 +233,7 @@ pattern `/moz.*/` will not match the URL `http://mozilla.org`.
|
||||
|
||||
## Examples ##
|
||||
|
||||
var { MatchPattern } = require("sdk/util/match-pattern");
|
||||
var { MatchPattern } = require("sdk/page-mod/match-pattern");
|
||||
var pattern = new MatchPattern("http://example.com/*");
|
||||
console.log(pattern.test("http://example.com/")); // true
|
||||
console.log(pattern.test("http://example.com/foo")); // true
|
@ -33,6 +33,13 @@ listening to its `show` and `hide` events.
|
||||
|
||||
Opening a panel will close an already opened panel.
|
||||
|
||||
<div class="warning">
|
||||
If your add-on has
|
||||
<a href="modules/sdk/private-browsing.html#Opting into private browsing">opted into private browsing</a>,
|
||||
then you can't use panels in your add-on. This is due to a platform bug which we expect to
|
||||
be fixed in Firefox 21.
|
||||
</div>
|
||||
|
||||
## Panel Content ##
|
||||
|
||||
The panel's content is specified as HTML, which is loaded from the URL
|
||||
@ -387,9 +394,13 @@ then the panel's text will be invisible on OS X although it looks fine on Ubuntu
|
||||
|
||||
## Private Browsing ##
|
||||
|
||||
If your add-on has not
|
||||
If your add-on has
|
||||
[opted into private browsing](modules/sdk/private-browsing.html#Opting into private browsing),
|
||||
and it calls `panel.show()` when the currently active window is a
|
||||
then **you can't use panels in your add-on**. This is due to a platform bug which we expect to
|
||||
be fixed in Firefox 21.
|
||||
|
||||
If your add-on has not opted into private browsing, and it calls `panel.show()`
|
||||
when the currently active window is a
|
||||
[private window](modules/sdk/private-browsing.html#Per-window private browsing),
|
||||
then the panel will not be shown.
|
||||
|
||||
|
@ -51,10 +51,15 @@ active window is a private browser window
|
||||
* the [`selection`](modules/sdk/selection.html) module will not include
|
||||
any selections made in private browser windows
|
||||
|
||||
Add-ons that have opted in will see private windows, so they will need to
|
||||
Add-ons that have opted in:
|
||||
|
||||
* will see private windows, so they will need to
|
||||
use the `private-browsing` module to check whether objects are private,
|
||||
so as to avoid storing data derived from such objects.
|
||||
|
||||
* will not be able to use panels in their code. This is due to a platform
|
||||
restriction which will be fixed in Firefox 21.
|
||||
|
||||
Additionally, add-ons that use low-level modules such as
|
||||
[`window/utils`](modules/sdk/window/utils.html) may see private browser
|
||||
windows with certain functions, even if they have not explicitly opted
|
||||
|
@ -7,17 +7,27 @@
|
||||
|
||||
The `tabs` module provides easy access to tabs and tab-related events.
|
||||
|
||||
## Module-level Operations ##
|
||||
The module itself can be used like a basic list of all opened
|
||||
tabs across all windows. In particular, you can enumerate it:
|
||||
|
||||
### Open a Tab ###
|
||||
var tabs = require('sdk/tabs');
|
||||
for each (var tab in tabs)
|
||||
console.log(tab.title);
|
||||
|
||||
You can also access individual tabs by index:
|
||||
|
||||
var tabs = require('sdk/tabs');
|
||||
|
||||
tabs.on('ready', function () {
|
||||
console.log('first: ' + tabs[0].title);
|
||||
console.log('last: ' + tabs[tabs.length-1].title);
|
||||
});
|
||||
|
||||
You can open a new tab, specifying various properties including location:
|
||||
|
||||
var tabs = require("sdk/tabs");
|
||||
tabs.open("http://www.example.com");
|
||||
|
||||
### Track Tabs ###
|
||||
|
||||
You can register event listeners to be notified when tabs open, close, finish
|
||||
loading DOM content, or are made active or inactive:
|
||||
|
||||
@ -33,71 +43,6 @@ loading DOM content, or are made active or inactive:
|
||||
console.log('tab is loaded', tab.title, tab.url)
|
||||
});
|
||||
|
||||
### Access Tabs ###
|
||||
|
||||
The module itself can be used as a list of all opened
|
||||
tabs across all windows. In particular, you can enumerate it:
|
||||
|
||||
var tabs = require('sdk/tabs');
|
||||
for each (var tab in tabs)
|
||||
console.log(tab.title);
|
||||
|
||||
You can also access individual tabs by index:
|
||||
|
||||
var tabs = require('sdk/tabs');
|
||||
|
||||
tabs.on('ready', function () {
|
||||
console.log('first: ' + tabs[0].title);
|
||||
console.log('last: ' + tabs[tabs.length-1].title);
|
||||
});
|
||||
|
||||
You can access the currently active tab:
|
||||
|
||||
var tabs = require('sdk/tabs');
|
||||
|
||||
tabs.on('activate', function () {
|
||||
console.log('active: ' + tabs.activeTab.url);
|
||||
});
|
||||
|
||||
## Tab-level Operations ##
|
||||
|
||||
### Track a Tab ###
|
||||
|
||||
Given a tab, you can register event listeners to be notified when the
|
||||
tab is closed, activated or deactivated, or when the page hosted by the
|
||||
tab is loaded or retrieved from the
|
||||
["back-forward cache"](https://developer.mozilla.org/en-US/docs/Working_with_BFCache):
|
||||
|
||||
var tabs = require("sdk/tabs");
|
||||
|
||||
function onOpen(tab) {
|
||||
console.log(tab.url + " is open");
|
||||
tab.on("pageshow", logShow);
|
||||
tab.on("activate", logActivate);
|
||||
tab.on("deactivate", logDeactivate);
|
||||
tab.on("close", logClose);
|
||||
}
|
||||
|
||||
function logShow(tab) {
|
||||
console.log(tab.url + " is loaded");
|
||||
}
|
||||
|
||||
function logActivate(tab) {
|
||||
console.log(tab.url + " is activated");
|
||||
}
|
||||
|
||||
function logDeactivate(tab) {
|
||||
console.log(tab.url + " is deactivated");
|
||||
}
|
||||
|
||||
function logClose(tab) {
|
||||
console.log(tab.url + " is closed");
|
||||
}
|
||||
|
||||
tabs.on('open', onOpen);
|
||||
|
||||
### Manipulate a Tab ###
|
||||
|
||||
You can get and set various properties of tabs (but note that properties
|
||||
relating to the tab's content, such as the URL, will not contain valid
|
||||
values until after the tab's `ready` event fires). By setting the `url`
|
||||
@ -108,8 +53,6 @@ property you can load a new page in the tab:
|
||||
tab.url = "http://www.example.com";
|
||||
});
|
||||
|
||||
### Run Scripts in a Tab ###
|
||||
|
||||
You can attach a [content script](dev-guide/guides/content-scripts/index.html)
|
||||
to the page hosted in a tab, and use that to access and manipulate the page's
|
||||
content (see the
|
||||
@ -219,25 +162,25 @@ If present and true, then the new tab will be pinned as an
|
||||
[app tab](http://support.mozilla.com/en-US/kb/what-are-app-tabs).
|
||||
|
||||
@prop [onOpen] {function}
|
||||
A callback function that will be registered for the 'open' event.
|
||||
A callback function that will be registered for 'open' event.
|
||||
This is an optional property.
|
||||
@prop [onClose] {function}
|
||||
A callback function that will be registered for the 'close' event.
|
||||
A callback function that will be registered for 'close' event.
|
||||
This is an optional property.
|
||||
@prop [onReady] {function}
|
||||
A callback function that will be registered for the 'ready' event.
|
||||
A callback function that will be registered for 'ready' event.
|
||||
This is an optional property.
|
||||
@prop [onLoad] {function}
|
||||
A callback function that will be registered for the 'load' event.
|
||||
A callback function that will be registered for 'load' event.
|
||||
This is an optional property.
|
||||
@prop [onPageShow] {function}
|
||||
A callback function that will be registered for the 'pageshow' event.
|
||||
A callback function that will be registered for 'pageshow' event.
|
||||
This is an optional property.
|
||||
@prop [onActivate] {function}
|
||||
A callback function that will be registered for the 'activate' event.
|
||||
A callback function that will be registered for 'activate' event.
|
||||
This is an optional property.
|
||||
@prop [onDeactivate] {function}
|
||||
A callback function that will be registered for the 'deactivate' event.
|
||||
A callback function that will be registered for 'deactivate' event.
|
||||
This is an optional property.
|
||||
</api>
|
||||
|
||||
@ -397,12 +340,11 @@ Listeners are passed the tab object.
|
||||
@event
|
||||
|
||||
This event is emitted when the DOM for the tab's content is ready. It is
|
||||
equivalent to the
|
||||
[`DOMContentLoaded`](https://developer.mozilla.org/en-US/docs/Web/Reference/Events/DOMContentLoaded)
|
||||
event for the given content page.
|
||||
equivalent to the `DOMContentLoaded` event for the given content page.
|
||||
|
||||
A single tab will emit this event every time the DOM is loaded: so it will be
|
||||
emitted again if the tab's location changes or the content is reloaded.
|
||||
|
||||
After this event has been emitted, all properties relating to the tab's
|
||||
content can be used.
|
||||
|
||||
@ -414,19 +356,16 @@ Listeners are passed the tab object.
|
||||
@event
|
||||
|
||||
This event is emitted when the page for the tab's content is loaded. It is
|
||||
equivalent to the
|
||||
[`load`](https://developer.mozilla.org/en-US/docs/Web/Reference/Events/load)
|
||||
event for the given content page.
|
||||
equivalent to the `load` event for the given content page.
|
||||
|
||||
A single tab will emit this event every time the page is loaded: so it will be
|
||||
emitted again if the tab's location changes or the content is reloaded.
|
||||
This event is similar to the [`ready`](modules/sdk/tabs.html#ready) event,
|
||||
except that it can be used for pages that do not have a `DOMContentLoaded`
|
||||
event, like images.
|
||||
|
||||
After this event has been emitted, all properties relating to the tab's
|
||||
content can be used. For pages that have a `DOMContentLoaded` event, `load`
|
||||
is fired after `ready`.
|
||||
content can be used.
|
||||
|
||||
This is fired after the `ready` event on DOM content pages and can be used
|
||||
for pages that do not have a `DOMContentLoaded` event, like images.
|
||||
|
||||
@argument {Tab}
|
||||
Listeners are passed the tab object.
|
||||
@ -435,32 +374,23 @@ Listeners are passed the tab object.
|
||||
<api name="pageshow">
|
||||
@event
|
||||
|
||||
The `pageshow` event is emitted when the page for a tab's content is loaded.
|
||||
It is equivalent to the
|
||||
[`pageshow`](https://developer.mozilla.org/en-US/docs/DOM/Mozilla_event_reference/pageshow)
|
||||
event for the given content page.
|
||||
|
||||
This event is similar to the [`load`](modules/sdk/tabs.html#load) and
|
||||
[`ready`](modules/sdk/tabs.html#ready) events, except unlike
|
||||
`load` and `ready`, `pageshow` is triggered if the page was retrieved from the
|
||||
[bfcache](https://developer.mozilla.org/en-US/docs/Working_with_BFCache).
|
||||
This means that if the user loads a page, loads a new page, then
|
||||
moves back to the previous page using the "Back" button,
|
||||
the `pageshow` event is emitted when the user moves back to the previous
|
||||
page, while the `load` and `ready` events are not.
|
||||
|
||||
This event is *not* emitted when the tab is made the active tab: to get
|
||||
notified about that, you need to listen to the
|
||||
[`activate`](modules/sdk/tabs.html#activate) event.
|
||||
This event is emitted when the page for the tab's content is potentially
|
||||
from the cache. It is equivilent to the [pageshow](https://developer.mozilla.org/en-US/docs/DOM/Mozilla_event_reference/pageshow) event for the given
|
||||
content page.
|
||||
|
||||
After this event has been emitted, all properties relating to the tab's
|
||||
content can be used. It is emitted after `load` and `ready`.
|
||||
content can be used.
|
||||
|
||||
While the `ready` and `load` events will not be fired when a user uses the back
|
||||
or forward buttons to navigate history, the `pageshow` event will be fired.
|
||||
If the `persisted` argument is true, then the contents were loaded from the
|
||||
bfcache.
|
||||
|
||||
@argument {Tab}
|
||||
Listeners are passed the tab object.
|
||||
@argument {persisted}
|
||||
Listeners are passed a boolean value indicating whether or not the page
|
||||
was loaded from the [bfcache](https://developer.mozilla.org/en-US/docs/Working_with_BFCache).
|
||||
was loaded from the [bfcache](https://developer.mozilla.org/en-US/docs/Working_with_BFCache) or not.
|
||||
</api>
|
||||
|
||||
<api name="activate">
|
||||
|
BIN
addon-sdk/source/doc/static-files/media/xul-migration-cs.png
Normal file
BIN
addon-sdk/source/doc/static-files/media/xul-migration-cs.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@ -37,7 +37,12 @@ WindowTracker({
|
||||
// Augmenting the behavior of `hideChromeForLocation` method, as
|
||||
// suggested by https://developer.mozilla.org/en-US/docs/Hiding_browser_chrome
|
||||
XULBrowserWindow.hideChromeForLocation = function(url) {
|
||||
return isAddonURL(url) || hideChromeForLocation.call(this, url);
|
||||
if (url.indexOf(addonURL) === 0) {
|
||||
let rest = url.substr(addonURL.length);
|
||||
return rest.length === 0 || ['#','?'].indexOf(rest.charAt(0)) > -1
|
||||
}
|
||||
|
||||
return hideChromeForLocation.call(this, url);
|
||||
}
|
||||
},
|
||||
|
||||
@ -47,16 +52,8 @@ WindowTracker({
|
||||
}
|
||||
});
|
||||
|
||||
function isAddonURL(url) {
|
||||
if (url.indexOf(addonURL) === 0) {
|
||||
let rest = url.substr(addonURL.length);
|
||||
return ((rest.length === 0) || (['#','?'].indexOf(rest.charAt(0)) > -1));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function tabFilter(tab) {
|
||||
return isAddonURL(getURI(tab));
|
||||
return getURI(tab) === addonURL;
|
||||
}
|
||||
|
||||
function untrackTab(window, tab) {
|
||||
@ -65,7 +62,7 @@ function untrackTab(window, tab) {
|
||||
let { hideChromeForLocation } = windows(window);
|
||||
|
||||
if (hideChromeForLocation) {
|
||||
window.XULBrowserWindow.hideChromeForLocation = hideChromeForLocation.bind(window.XULBrowserWindow);
|
||||
window.XULBrowserWindow.hideChromeForLocation = hideChromeForLocation;
|
||||
windows(window).hideChromeForLocation = null;
|
||||
}
|
||||
|
||||
|
@ -1,48 +0,0 @@
|
||||
/* 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';
|
||||
|
||||
module.metadata = {
|
||||
'stability': 'experimental'
|
||||
};
|
||||
|
||||
let { request: hostReq, response: hostRes } = require('./host');
|
||||
let { defer: async } = require('../lang/functional');
|
||||
let { defer } = require('../core/promise');
|
||||
let { emit: emitSync, on, off } = require('../event/core');
|
||||
let { uuid } = require('../util/uuid');
|
||||
let emit = async(emitSync);
|
||||
|
||||
// Map of IDs to deferreds
|
||||
let requests = new Map();
|
||||
|
||||
function receive ({data, id, error}) {
|
||||
let request = requests.get(id);
|
||||
if (request) {
|
||||
if (error) request.reject(error);
|
||||
else request.resolve(serialize(data));
|
||||
requests.delete(id);
|
||||
}
|
||||
}
|
||||
on(hostRes, 'data', receive);
|
||||
|
||||
/*
|
||||
* Send is a helper to be used in client APIs to send
|
||||
* a request to host
|
||||
*/
|
||||
function send (eventName, data) {
|
||||
let id = uuid();
|
||||
let deferred = defer();
|
||||
requests.set(id, deferred);
|
||||
emit(hostReq, 'data', {
|
||||
id: id,
|
||||
data: serialize(data),
|
||||
event: eventName
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
exports.send = send;
|
||||
|
||||
function serialize (obj) JSON.parse(JSON.stringify(obj))
|
@ -1,12 +0,0 @@
|
||||
/* 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";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "experimental"
|
||||
};
|
||||
|
||||
exports.request = {};
|
||||
exports.response = {};
|
@ -30,7 +30,7 @@ let create = map(windowCreate, function({target, data, type}) {
|
||||
return { target: target.document, type: type, data: data }
|
||||
});
|
||||
|
||||
function streamEventsFrom({document}) {
|
||||
function readStates({document}) {
|
||||
// Map supported event types to a streams of those events on the given
|
||||
// `window` for the inserted document and than merge these streams into
|
||||
// single form stream off all window state change events.
|
||||
@ -43,15 +43,15 @@ function streamEventsFrom({document}) {
|
||||
return target instanceof Ci.nsIDOMDocument
|
||||
})
|
||||
}
|
||||
exports.streamEventsFrom = streamEventsFrom;
|
||||
|
||||
|
||||
let opened = windows(null, { includePrivate: true });
|
||||
let state = merge(opened.map(streamEventsFrom));
|
||||
let state = merge(opened.map(readStates));
|
||||
|
||||
|
||||
let futureReady = filter(windowEvents, function({type})
|
||||
type === "DOMContentLoaded");
|
||||
let futureWindows = map(futureReady, function({target}) target);
|
||||
let futureState = expand(futureWindows, streamEventsFrom);
|
||||
let futureState = expand(futureWindows, readStates);
|
||||
|
||||
exports.events = merge([insert, create, state, futureState]);
|
||||
|
@ -1,43 +0,0 @@
|
||||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* 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";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "unstable"
|
||||
};
|
||||
|
||||
let assetsURI = require("../self").data.url();
|
||||
let isArray = Array.isArray;
|
||||
|
||||
function isAddonContent({ contentURL }) {
|
||||
return typeof(contentURL) === "string" && contentURL.indexOf(assetsURI) === 0;
|
||||
}
|
||||
exports.isAddonContent = isAddonContent;
|
||||
|
||||
function hasContentScript({ contentScript, contentScriptFile }) {
|
||||
return (isArray(contentScript) ? contentScript.length > 0 :
|
||||
!!contentScript) ||
|
||||
(isArray(contentScriptFile) ? contentScriptFile.length > 0 :
|
||||
!!contentScriptFile);
|
||||
}
|
||||
exports.hasContentScript = hasContentScript;
|
||||
|
||||
function requiresAddonGlobal(model) {
|
||||
return isAddonContent(model) && !hasContentScript(model);
|
||||
}
|
||||
exports.requiresAddonGlobal = requiresAddonGlobal;
|
||||
|
||||
function getAttachEventType(model) {
|
||||
if (!model) return null;
|
||||
let when = model.contentScriptWhen;
|
||||
return requiresAddonGlobal(model) ? "document-element-inserted" :
|
||||
when === "start" ? "document-element-inserted" :
|
||||
when === "end" ? "load" :
|
||||
when === "ready" ? "DOMContentLoaded" :
|
||||
null;
|
||||
}
|
||||
exports.getAttachEventType = getAttachEventType;
|
||||
|
@ -15,7 +15,7 @@ const { URL, isValidURI } = require("./url");
|
||||
const { WindowTracker, browserWindowIterator } = require("./deprecated/window-utils");
|
||||
const { isBrowser, getInnerId } = require("./window/utils");
|
||||
const { Ci } = require("chrome");
|
||||
const { MatchPattern } = require("./util/match-pattern");
|
||||
const { MatchPattern } = require("./page-mod/match-pattern");
|
||||
const { Worker } = require("./content/worker");
|
||||
const { EventTarget } = require("./event/target");
|
||||
const { emit } = require('./event/core');
|
||||
|
@ -160,12 +160,8 @@ var Class = new function() {
|
||||
getDataProperties(prototype));
|
||||
|
||||
constructor.attributes = attributes;
|
||||
Object.defineProperty(constructor, 'prototype', {
|
||||
configurable: false,
|
||||
writable: false,
|
||||
value: prototype
|
||||
});
|
||||
return constructor;
|
||||
constructor.prototype = prototype;
|
||||
return freeze(constructor);
|
||||
};
|
||||
}
|
||||
Class.prototype = extend(null, obscure({
|
||||
|
@ -92,7 +92,7 @@ const eventEmitter = {
|
||||
*/
|
||||
_listeners: function listeners(type) {
|
||||
let events = this._events || (this._events = {});
|
||||
return (events.hasOwnProperty(type) && events[type]) || (events[type] = []);
|
||||
return events[type] || (events[type] = []);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1,446 +0,0 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// Adapted version of:
|
||||
// https://github.com/joyent/node/blob/v0.9.1/lib/path.js
|
||||
|
||||
|
||||
var system = require('../system');
|
||||
var isWindows = system.platform.indexOf('win') === 0;
|
||||
|
||||
|
||||
// resolves . and .. elements in a path array with directory names there
|
||||
// must be no slashes, empty elements, or device names (c:\) in the array
|
||||
// (so also no leading and trailing slashes - it does not distinguish
|
||||
// relative and absolute paths)
|
||||
function normalizeArray(parts, allowAboveRoot) {
|
||||
// if the path tries to go above the root, `up` ends up > 0
|
||||
var up = 0;
|
||||
for (var i = parts.length - 1; i >= 0; i--) {
|
||||
var last = parts[i];
|
||||
if (last === '.') {
|
||||
parts.splice(i, 1);
|
||||
} else if (last === '..') {
|
||||
parts.splice(i, 1);
|
||||
up++;
|
||||
} else if (up) {
|
||||
parts.splice(i, 1);
|
||||
up--;
|
||||
}
|
||||
}
|
||||
|
||||
// if the path is allowed to go above the root, restore leading ..s
|
||||
if (allowAboveRoot) {
|
||||
for (; up--; up) {
|
||||
parts.unshift('..');
|
||||
}
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
|
||||
if (isWindows) {
|
||||
// Regex to split a windows path into three parts: [*, device, slash,
|
||||
// tail] windows-only
|
||||
var splitDeviceRe =
|
||||
/^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?([\\\/])?([\s\S]*?)$/;
|
||||
|
||||
// Regex to split the tail part of the above into [*, dir, basename, ext]
|
||||
var splitTailRe =
|
||||
/^([\s\S]+[\\\/](?!$)|[\\\/])?((?:\.{1,2}$|[\s\S]+?)?(\.[^.\/\\]*)?)$/;
|
||||
|
||||
// Function to split a filename into [root, dir, basename, ext]
|
||||
// windows version
|
||||
var splitPath = function(filename) {
|
||||
// Separate device+slash from tail
|
||||
var result = splitDeviceRe.exec(filename),
|
||||
device = (result[1] || '') + (result[2] || ''),
|
||||
tail = result[3] || '';
|
||||
// Split the tail into dir, basename and extension
|
||||
var result2 = splitTailRe.exec(tail),
|
||||
dir = result2[1] || '',
|
||||
basename = result2[2] || '',
|
||||
ext = result2[3] || '';
|
||||
return [device, dir, basename, ext];
|
||||
};
|
||||
|
||||
// path.resolve([from ...], to)
|
||||
// windows version
|
||||
exports.resolve = function() {
|
||||
var resolvedDevice = '',
|
||||
resolvedTail = '',
|
||||
resolvedAbsolute = false;
|
||||
|
||||
for (var i = arguments.length - 1; i >= -1; i--) {
|
||||
var path;
|
||||
if (i >= 0) {
|
||||
path = arguments[i];
|
||||
} else if (!resolvedDevice) {
|
||||
path = system.pathFor('CurProcD');
|
||||
} else {
|
||||
// Windows has the concept of drive-specific current working
|
||||
// directories. If we've resolved a drive letter but not yet an
|
||||
// absolute path, get cwd for that drive. We're sure the device is not
|
||||
// an unc path at this points, because unc paths are always absolute.
|
||||
path = system.env['=' + resolvedDevice];
|
||||
// Verify that a drive-local cwd was found and that it actually points
|
||||
// to our drive. If not, default to the drive's root.
|
||||
if (!path || path.substr(0, 3).toLowerCase() !==
|
||||
resolvedDevice.toLowerCase() + '\\') {
|
||||
path = resolvedDevice + '\\';
|
||||
}
|
||||
}
|
||||
|
||||
// Skip empty and invalid entries
|
||||
if (typeof path !== 'string' || !path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var result = splitDeviceRe.exec(path),
|
||||
device = result[1] || '',
|
||||
isUnc = device && device.charAt(1) !== ':',
|
||||
isAbsolute = !!result[2] || isUnc, // UNC paths are always absolute
|
||||
tail = result[3];
|
||||
|
||||
if (device &&
|
||||
resolvedDevice &&
|
||||
device.toLowerCase() !== resolvedDevice.toLowerCase()) {
|
||||
// This path points to another device so it is not applicable
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!resolvedDevice) {
|
||||
resolvedDevice = device;
|
||||
}
|
||||
if (!resolvedAbsolute) {
|
||||
resolvedTail = tail + '\\' + resolvedTail;
|
||||
resolvedAbsolute = isAbsolute;
|
||||
}
|
||||
|
||||
if (resolvedDevice && resolvedAbsolute) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Replace slashes (in UNC share name) by backslashes
|
||||
resolvedDevice = resolvedDevice.replace(/\//g, '\\');
|
||||
|
||||
// At this point the path should be resolved to a full absolute path,
|
||||
// but handle relative paths to be safe (might happen when process.cwd()
|
||||
// fails)
|
||||
|
||||
// Normalize the tail path
|
||||
|
||||
function f(p) {
|
||||
return !!p;
|
||||
}
|
||||
|
||||
resolvedTail = normalizeArray(resolvedTail.split(/[\\\/]+/).filter(f),
|
||||
!resolvedAbsolute).join('\\');
|
||||
|
||||
return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) ||
|
||||
'.';
|
||||
};
|
||||
|
||||
// windows version
|
||||
exports.normalize = function(path) {
|
||||
var result = splitDeviceRe.exec(path),
|
||||
device = result[1] || '',
|
||||
isUnc = device && device.charAt(1) !== ':',
|
||||
isAbsolute = !!result[2] || isUnc, // UNC paths are always absolute
|
||||
tail = result[3],
|
||||
trailingSlash = /[\\\/]$/.test(tail);
|
||||
|
||||
// Normalize the tail path
|
||||
tail = normalizeArray(tail.split(/[\\\/]+/).filter(function(p) {
|
||||
return !!p;
|
||||
}), !isAbsolute).join('\\');
|
||||
|
||||
if (!tail && !isAbsolute) {
|
||||
tail = '.';
|
||||
}
|
||||
if (tail && trailingSlash) {
|
||||
tail += '\\';
|
||||
}
|
||||
|
||||
// Convert slashes to backslashes when `device` points to an UNC root.
|
||||
device = device.replace(/\//g, '\\');
|
||||
|
||||
return device + (isAbsolute ? '\\' : '') + tail;
|
||||
};
|
||||
|
||||
// windows version
|
||||
exports.join = function() {
|
||||
function f(p) {
|
||||
return p && typeof p === 'string';
|
||||
}
|
||||
|
||||
var paths = Array.prototype.filter.call(arguments, f);
|
||||
var joined = paths.join('\\');
|
||||
|
||||
// Make sure that the joined path doesn't start with two slashes
|
||||
// - it will be mistaken for an unc path by normalize() -
|
||||
// unless the paths[0] also starts with two slashes
|
||||
if (/^[\\\/]{2}/.test(joined) && !/^[\\\/]{2}/.test(paths[0])) {
|
||||
joined = joined.substr(1);
|
||||
}
|
||||
|
||||
return exports.normalize(joined);
|
||||
};
|
||||
|
||||
// path.relative(from, to)
|
||||
// it will solve the relative path from 'from' to 'to', for instance:
|
||||
// from = 'C:\\orandea\\test\\aaa'
|
||||
// to = 'C:\\orandea\\impl\\bbb'
|
||||
// The output of the function should be: '..\\..\\impl\\bbb'
|
||||
// windows version
|
||||
exports.relative = function(from, to) {
|
||||
from = exports.resolve(from);
|
||||
to = exports.resolve(to);
|
||||
|
||||
// windows is not case sensitive
|
||||
var lowerFrom = from.toLowerCase();
|
||||
var lowerTo = to.toLowerCase();
|
||||
|
||||
function trim(arr) {
|
||||
var start = 0;
|
||||
for (; start < arr.length; start++) {
|
||||
if (arr[start] !== '') break;
|
||||
}
|
||||
|
||||
var end = arr.length - 1;
|
||||
for (; end >= 0; end--) {
|
||||
if (arr[end] !== '') break;
|
||||
}
|
||||
|
||||
if (start > end) return [];
|
||||
return arr.slice(start, end - start + 1);
|
||||
}
|
||||
|
||||
var toParts = trim(to.split('\\'));
|
||||
|
||||
var lowerFromParts = trim(lowerFrom.split('\\'));
|
||||
var lowerToParts = trim(lowerTo.split('\\'));
|
||||
|
||||
var length = Math.min(lowerFromParts.length, lowerToParts.length);
|
||||
var samePartsLength = length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
if (lowerFromParts[i] !== lowerToParts[i]) {
|
||||
samePartsLength = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (samePartsLength == 0) {
|
||||
return to;
|
||||
}
|
||||
|
||||
var outputParts = [];
|
||||
for (var i = samePartsLength; i < lowerFromParts.length; i++) {
|
||||
outputParts.push('..');
|
||||
}
|
||||
|
||||
outputParts = outputParts.concat(toParts.slice(samePartsLength));
|
||||
|
||||
return outputParts.join('\\');
|
||||
};
|
||||
|
||||
exports.sep = '\\';
|
||||
|
||||
} else /* posix */ {
|
||||
|
||||
// Split a filename into [root, dir, basename, ext], unix version
|
||||
// 'root' is just a slash, or nothing.
|
||||
var splitPathRe =
|
||||
/^(\/?)([\s\S]+\/(?!$)|\/)?((?:\.{1,2}$|[\s\S]+?)?(\.[^.\/]*)?)$/;
|
||||
var splitPath = function(filename) {
|
||||
var result = splitPathRe.exec(filename);
|
||||
return [result[1] || '', result[2] || '', result[3] || '', result[4] || ''];
|
||||
};
|
||||
|
||||
// path.resolve([from ...], to)
|
||||
// posix version
|
||||
exports.resolve = function() {
|
||||
var resolvedPath = '',
|
||||
resolvedAbsolute = false;
|
||||
|
||||
for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
|
||||
var path = (i >= 0) ? arguments[i] : system.pathFor('CurProcD');
|
||||
|
||||
// Skip empty and invalid entries
|
||||
if (typeof path !== 'string' || !path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
resolvedPath = path + '/' + resolvedPath;
|
||||
resolvedAbsolute = path.charAt(0) === '/';
|
||||
}
|
||||
|
||||
// At this point the path should be resolved to a full absolute path, but
|
||||
// handle relative paths to be safe (might happen when process.cwd() fails)
|
||||
|
||||
// Normalize the path
|
||||
resolvedPath = normalizeArray(resolvedPath.split('/').filter(function(p) {
|
||||
return !!p;
|
||||
}), !resolvedAbsolute).join('/');
|
||||
|
||||
return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
|
||||
};
|
||||
|
||||
// path.normalize(path)
|
||||
// posix version
|
||||
exports.normalize = function(path) {
|
||||
var isAbsolute = path.charAt(0) === '/',
|
||||
trailingSlash = path.substr(-1) === '/';
|
||||
|
||||
// Normalize the path
|
||||
path = normalizeArray(path.split('/').filter(function(p) {
|
||||
return !!p;
|
||||
}), !isAbsolute).join('/');
|
||||
|
||||
if (!path && !isAbsolute) {
|
||||
path = '.';
|
||||
}
|
||||
if (path && trailingSlash) {
|
||||
path += '/';
|
||||
}
|
||||
|
||||
return (isAbsolute ? '/' : '') + path;
|
||||
};
|
||||
|
||||
|
||||
// posix version
|
||||
exports.join = function() {
|
||||
var paths = Array.prototype.slice.call(arguments, 0);
|
||||
return exports.normalize(paths.filter(function(p, index) {
|
||||
return p && typeof p === 'string';
|
||||
}).join('/'));
|
||||
};
|
||||
|
||||
|
||||
// path.relative(from, to)
|
||||
// posix version
|
||||
exports.relative = function(from, to) {
|
||||
from = exports.resolve(from).substr(1);
|
||||
to = exports.resolve(to).substr(1);
|
||||
|
||||
function trim(arr) {
|
||||
var start = 0;
|
||||
for (; start < arr.length; start++) {
|
||||
if (arr[start] !== '') break;
|
||||
}
|
||||
|
||||
var end = arr.length - 1;
|
||||
for (; end >= 0; end--) {
|
||||
if (arr[end] !== '') break;
|
||||
}
|
||||
|
||||
if (start > end) return [];
|
||||
return arr.slice(start, end - start + 1);
|
||||
}
|
||||
|
||||
var fromParts = trim(from.split('/'));
|
||||
var toParts = trim(to.split('/'));
|
||||
|
||||
var length = Math.min(fromParts.length, toParts.length);
|
||||
var samePartsLength = length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
if (fromParts[i] !== toParts[i]) {
|
||||
samePartsLength = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var outputParts = [];
|
||||
for (var i = samePartsLength; i < fromParts.length; i++) {
|
||||
outputParts.push('..');
|
||||
}
|
||||
|
||||
outputParts = outputParts.concat(toParts.slice(samePartsLength));
|
||||
|
||||
return outputParts.join('/');
|
||||
};
|
||||
|
||||
exports.sep = '/';
|
||||
}
|
||||
|
||||
|
||||
exports.dirname = function(path) {
|
||||
var result = splitPath(path),
|
||||
root = result[0],
|
||||
dir = result[1];
|
||||
|
||||
if (!root && !dir) {
|
||||
// No dirname whatsoever
|
||||
return '.';
|
||||
}
|
||||
|
||||
if (dir) {
|
||||
// It has a dirname, strip trailing slash
|
||||
dir = dir.substr(0, dir.length - 1);
|
||||
}
|
||||
|
||||
return root + dir;
|
||||
};
|
||||
|
||||
|
||||
exports.basename = function(path, ext) {
|
||||
var f = splitPath(path)[2];
|
||||
// TODO: make this comparison case-insensitive on windows?
|
||||
if (ext && f.substr(-1 * ext.length) === ext) {
|
||||
f = f.substr(0, f.length - ext.length);
|
||||
}
|
||||
return f;
|
||||
};
|
||||
|
||||
|
||||
exports.extname = function(path) {
|
||||
return splitPath(path)[3];
|
||||
};
|
||||
|
||||
|
||||
if (isWindows) {
|
||||
exports._makeLong = function(path) {
|
||||
path = '' + path;
|
||||
if (!path) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var resolvedPath = exports.resolve(path);
|
||||
|
||||
if (/^[a-zA-Z]\:\\/.test(resolvedPath)) {
|
||||
// path is local filesystem path, which needs to be converted
|
||||
// to long UNC path.
|
||||
return '\\\\?\\' + resolvedPath;
|
||||
} else if (/^\\\\[^?.]/.test(resolvedPath)) {
|
||||
// path is network UNC path, which needs to be converted
|
||||
// to long UNC path.
|
||||
return '\\\\?\\UNC\\' + resolvedPath.substring(2);
|
||||
}
|
||||
|
||||
return path;
|
||||
};
|
||||
} else {
|
||||
exports._makeLong = function(path) {
|
||||
return path;
|
||||
};
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
/* 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";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "experimental"
|
||||
};
|
||||
|
||||
|
||||
const { Cc, Ci, CC } = require("chrome");
|
||||
const { Class } = require("../core/heritage");
|
||||
|
||||
const Transcoder = CC("@mozilla.org/intl/scriptableunicodeconverter",
|
||||
"nsIScriptableUnicodeConverter");
|
||||
|
||||
var Buffer = Class({
|
||||
initialize: function initialize(subject, encoding) {
|
||||
subject = subject ? subject.valueOf() : 0;
|
||||
let length = typeof subject === "number" ? subject : 0;
|
||||
this.encoding = encoding || "utf-8";
|
||||
this.valueOf(Array.isArray(subject) ? subject : new Array(length));
|
||||
|
||||
if (typeof subject === "string") this.write(subject);
|
||||
},
|
||||
get length() {
|
||||
return this.valueOf().length;
|
||||
},
|
||||
get: function get(index) {
|
||||
return this.valueOf()[index];
|
||||
},
|
||||
set: function set(index, value) {
|
||||
return this.valueOf()[index] = value;
|
||||
},
|
||||
valueOf: function valueOf(value) {
|
||||
Object.defineProperty(this, "valueOf", {
|
||||
value: Array.prototype.valueOf.bind(value),
|
||||
configurable: false,
|
||||
writable: false,
|
||||
enumerable: false
|
||||
});
|
||||
},
|
||||
toString: function toString(encoding, start, end) {
|
||||
let bytes = this.valueOf().slice(start || 0, end || this.length);
|
||||
let transcoder = Transcoder();
|
||||
transcoder.charset = String(encoding || this.encoding).toUpperCase();
|
||||
return transcoder.convertFromByteArray(bytes, this.length);
|
||||
},
|
||||
toJSON: function toJSON() {
|
||||
return this.toString()
|
||||
},
|
||||
write: function write(string, offset, encoding) {
|
||||
offset = Math.max(offset || 0, 0);
|
||||
let value = this.valueOf();
|
||||
let transcoder = Transcoder();
|
||||
transcoder.charset = String(encoding || this.encoding).toUpperCase();
|
||||
let bytes = transcoder.convertToByteArray(string, {});
|
||||
value.splice.apply(value, [
|
||||
offset,
|
||||
Math.min(value.length - offset, bytes.length, bytes)
|
||||
].concat(bytes));
|
||||
return bytes;
|
||||
},
|
||||
slice: function slice(start, end) {
|
||||
return new Buffer(this.valueOf().slice(start, end));
|
||||
},
|
||||
copy: function copy(target, offset, start, end) {
|
||||
offset = Math.max(offset || 0, 0);
|
||||
target = target.valueOf();
|
||||
let bytes = this.valueOf();
|
||||
bytes.slice(Math.max(start || 0, 0), end);
|
||||
target.splice.apply(target, [
|
||||
offset,
|
||||
Math.min(target.length - offset, bytes.length),
|
||||
].concat(bytes));
|
||||
}
|
||||
});
|
||||
Buffer.isBuffer = function isBuffer(buffer) {
|
||||
return buffer instanceof Buffer
|
||||
};
|
||||
exports.Buffer = Buffer;
|
@ -1,906 +0,0 @@
|
||||
/* 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";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "experimental"
|
||||
};
|
||||
|
||||
const { Cc, Ci, CC } = require("chrome");
|
||||
|
||||
const { setTimeout } = require("../timers");
|
||||
const { Stream, InputStream, OutputStream } = require("./stream");
|
||||
const { Buffer } = require("./buffer");
|
||||
const { ns } = require("../core/namespace");
|
||||
const { Class } = require("../core/heritage");
|
||||
|
||||
const nsILocalFile = CC("@mozilla.org/file/local;1", "nsILocalFile",
|
||||
"initWithPath");
|
||||
const FileOutputStream = CC("@mozilla.org/network/file-output-stream;1",
|
||||
"nsIFileOutputStream", "init");
|
||||
const FileInputStream = CC("@mozilla.org/network/file-input-stream;1",
|
||||
"nsIFileInputStream", "init");
|
||||
const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
|
||||
"nsIBinaryInputStream", "setInputStream");
|
||||
const BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1",
|
||||
"nsIBinaryOutputStream", "setOutputStream");
|
||||
const StreamPump = CC("@mozilla.org/network/input-stream-pump;1",
|
||||
"nsIInputStreamPump", "init");
|
||||
|
||||
const { createOutputTransport, createInputTransport } =
|
||||
Cc["@mozilla.org/network/stream-transport-service;1"].
|
||||
getService(Ci.nsIStreamTransportService);
|
||||
|
||||
|
||||
const { REOPEN_ON_REWIND, DEFER_OPEN } = Ci.nsIFileInputStream;
|
||||
const { DIRECTORY_TYPE, NORMAL_FILE_TYPE } = Ci.nsIFile;
|
||||
const { NS_SEEK_SET, NS_SEEK_CUR, NS_SEEK_END } = Ci.nsISeekableStream;
|
||||
|
||||
const FILE_PERMISSION = parseInt("0666", 8);
|
||||
const PR_UINT32_MAX = 0xfffffff;
|
||||
// Values taken from:
|
||||
// http://mxr.mozilla.org/mozilla-central/source/nsprpub/pr/include/prio.h#615
|
||||
const PR_RDONLY = 0x01;
|
||||
const PR_WRONLY = 0x02;
|
||||
const PR_RDWR = 0x04;
|
||||
const PR_CREATE_FILE = 0x08;
|
||||
const PR_APPEND = 0x10;
|
||||
const PR_TRUNCATE = 0x20;
|
||||
const PR_SYNC = 0x40;
|
||||
const PR_EXCL = 0x80;
|
||||
|
||||
const FLAGS = {
|
||||
"r": PR_RDONLY,
|
||||
"r+": PR_RDWR,
|
||||
"w": PR_CREATE_FILE | PR_TRUNCATE | PR_WRONLY,
|
||||
"w+": PR_CREATE_FILE | PR_TRUNCATE | PR_RDWR,
|
||||
"a": PR_APPEND | PR_CREATE_FILE | PR_WRONLY,
|
||||
"a+": PR_APPEND | PR_CREATE_FILE | PR_RDWR
|
||||
};
|
||||
|
||||
function accessor() {
|
||||
let map = new WeakMap();
|
||||
return function(fd, value) {
|
||||
if (value === null) map.delete(fd);
|
||||
if (value !== undefined) map.set(fd, value);
|
||||
return map.get(fd);
|
||||
}
|
||||
}
|
||||
|
||||
let nsIFile = accessor();
|
||||
let nsIFileInputStream = accessor();
|
||||
let nsIFileOutputStream = accessor();
|
||||
let nsIBinaryInputStream = accessor();
|
||||
let nsIBinaryOutputStream = accessor();
|
||||
|
||||
// Just a contstant object used to signal that all of the file
|
||||
// needs to be read.
|
||||
const ALL = new String("Read all of the file");
|
||||
|
||||
function isWritable(mode) !!(mode & PR_WRONLY || mode & PR_RDWR)
|
||||
function isReadable(mode) !!(mode & PR_RDONLY || mode & PR_RDWR)
|
||||
|
||||
function isString(value) typeof(value) === "string"
|
||||
function isFunction(value) typeof(value) === "function"
|
||||
|
||||
function toArray(enumerator) {
|
||||
let value = [];
|
||||
while(enumerator.hasMoreElements())
|
||||
value.push(enumerator.getNext())
|
||||
return value
|
||||
}
|
||||
|
||||
function getFileName(file) file.QueryInterface(Ci.nsIFile).leafName
|
||||
|
||||
|
||||
function remove(path, recursive) {
|
||||
let fd = new nsILocalFile(path)
|
||||
if (fd.exists()) {
|
||||
fd.remove(recursive || false);
|
||||
}
|
||||
else {
|
||||
throw FSError("remove", "ENOENT", 34, path);
|
||||
}
|
||||
}
|
||||
|
||||
function Mode(mode, fallback) {
|
||||
return isString(mode) ? parseInt(mode) : mode || fallback;
|
||||
}
|
||||
function Flags(flag) {
|
||||
return !isString(flag) ? flag :
|
||||
FLAGS[flag] || Error("Unknown file open flag: " + flag);
|
||||
}
|
||||
|
||||
|
||||
function FSError(op, code, errno, path, file, line) {
|
||||
let error = Error(code + ", " + op + " " + path, file, line);
|
||||
error.code = code;
|
||||
error.path = path;
|
||||
error.errno = errno;
|
||||
return error;
|
||||
}
|
||||
|
||||
const ReadStream = Class({
|
||||
extends: InputStream,
|
||||
initialize: function initialize(path, options) {
|
||||
this.position = -1;
|
||||
this.length = -1;
|
||||
this.flags = "r";
|
||||
this.mode = FILE_PERMISSION;
|
||||
this.bufferSize = 64 * 1024;
|
||||
|
||||
options = options || {};
|
||||
|
||||
if ("flags" in options && options.flags)
|
||||
this.flags = options.flags;
|
||||
if ("bufferSize" in options && options.bufferSize)
|
||||
this.bufferSize = options.bufferSize;
|
||||
if ("length" in options && options.length)
|
||||
this.length = options.length;
|
||||
if ("position" in options && options.position !== undefined)
|
||||
this.position = options.position;
|
||||
|
||||
let { flags, mode, position, length } = this;
|
||||
let fd = isString(path) ? openSync(path, flags, mode) : path;
|
||||
this.fd = fd;
|
||||
|
||||
let input = nsIFileInputStream(fd);
|
||||
// Setting a stream position, unless it"s `-1` which means current position.
|
||||
if (position >= 0)
|
||||
input.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position);
|
||||
// We use `nsIStreamTransportService` service to transform blocking
|
||||
// file input stream into a fully asynchronous stream that can be written
|
||||
// without blocking the main thread.
|
||||
let transport = createInputTransport(input, position, length, false);
|
||||
// Open an input stream on a transport. We don"t pass flags to guarantee
|
||||
// non-blocking stream semantics. Also we use defaults for segment size &
|
||||
// count.
|
||||
let asyncInputStream = transport.openInputStream(null, 0, 0);
|
||||
let binaryInputStream = BinaryInputStream(asyncInputStream);
|
||||
nsIBinaryInputStream(fd, binaryInputStream);
|
||||
let pump = StreamPump(asyncInputStream, position, length, 0, 0, false);
|
||||
|
||||
InputStream.prototype.initialize.call(this, {
|
||||
input: binaryInputStream, pump: pump
|
||||
});
|
||||
this.read();
|
||||
},
|
||||
destroy: function() {
|
||||
closeSync(this.fd);
|
||||
InputStream.prototype.destroy.call(this);
|
||||
}
|
||||
});
|
||||
exports.ReadStream = ReadStream;
|
||||
exports.createReadStream = function createReadStream(path, options) {
|
||||
return new ReadStream(path, options);
|
||||
};
|
||||
|
||||
const WriteStream = Class({
|
||||
extends: OutputStream,
|
||||
initialize: function initialize(path, options) {
|
||||
this.drainable = true;
|
||||
this.flags = "w";
|
||||
this.position = -1;
|
||||
this.mode = FILE_PERMISSION;
|
||||
|
||||
options = options || {};
|
||||
|
||||
if ("flags" in options && options.flags)
|
||||
this.flags = options.flags;
|
||||
if ("mode" in options && options.mode)
|
||||
this.mode = options.mode;
|
||||
if ("position" in options && options.position !== undefined)
|
||||
this.position = options.position;
|
||||
|
||||
let { position, flags, mode } = this;
|
||||
// If pass was passed we create a file descriptor out of it. Otherwise
|
||||
// we just use given file descriptor.
|
||||
let fd = isString(path) ? openSync(path, flags, mode) : path;
|
||||
this.fd = fd;
|
||||
|
||||
let output = nsIFileOutputStream(fd);
|
||||
// Setting a stream position, unless it"s `-1` which means current position.
|
||||
if (position >= 0)
|
||||
output.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position);
|
||||
// We use `nsIStreamTransportService` service to transform blocking
|
||||
// file output stream into a fully asynchronous stream that can be written
|
||||
// without blocking the main thread.
|
||||
let transport = createOutputTransport(output, position, -1, false);
|
||||
// Open an output stream on a transport. We don"t pass flags to guarantee
|
||||
// non-blocking stream semantics. Also we use defaults for segment size &
|
||||
// count.
|
||||
let asyncOutputStream = transport.openOutputStream(null, 0, 0);
|
||||
// Finally we create a non-blocking binary output stream. This will allows
|
||||
// us to write buffers as byte arrays without any further transcoding.
|
||||
let binaryOutputStream = BinaryOutputStream(asyncOutputStream);
|
||||
nsIBinaryOutputStream(fd, binaryOutputStream);
|
||||
|
||||
// Storing output stream so that it can beaccessed later.
|
||||
OutputStream.prototype.initialize.call(this, {
|
||||
output: binaryOutputStream,
|
||||
asyncOutputStream: asyncOutputStream
|
||||
});
|
||||
},
|
||||
destroy: function() {
|
||||
closeSync(this.fd);
|
||||
OutputStream.prototype.destroy.call(this);
|
||||
}
|
||||
});
|
||||
exports.WriteStream = WriteStream;
|
||||
exports.createWriteStream = function createWriteStream(path, options) {
|
||||
return new WriteStream(path, options);
|
||||
};
|
||||
|
||||
const Stats = Class({
|
||||
initialize: function initialize(path) {
|
||||
let file = new nsILocalFile(path);
|
||||
if (!file.exists()) throw FSError("stat", "ENOENT", 34, path);
|
||||
nsIFile(this, file);
|
||||
},
|
||||
isDirectory: function() nsIFile(this).isDirectory(),
|
||||
isFile: function() nsIFile(this).isFile(),
|
||||
isSymbolicLink: function() nsIFile(this).isSymlink(),
|
||||
get mode() nsIFile(this).permissions,
|
||||
get size() nsIFile(this).fileSize,
|
||||
get mtime() nsIFile(this).lastModifiedTime,
|
||||
isBlockDevice: function() nsIFile(this).isSpecial(),
|
||||
isCharacterDevice: function() nsIFile(this).isSpecial(),
|
||||
isFIFO: function() nsIFile(this).isSpecial(),
|
||||
isSocket: function() nsIFile(this).isSpecial(),
|
||||
// non standard
|
||||
get exists() nsIFile(this).exists(),
|
||||
get hidden() nsIFile(this).isHidden(),
|
||||
get writable() nsIFile(this).isWritable(),
|
||||
get readable() nsIFile(this).isReadable()
|
||||
});
|
||||
exports.Stats = Stats;
|
||||
|
||||
const LStats = Class({
|
||||
extends: Stats,
|
||||
get size() this.isSymbolicLink() ? nsIFile(this).fileSizeOfLink :
|
||||
nsIFile(this).fileSize,
|
||||
get mtime() this.isSymbolicLink() ? nsIFile(this).lastModifiedTimeOfLink :
|
||||
nsIFile(this).lastModifiedTime,
|
||||
// non standard
|
||||
get permissions() this.isSymbolicLink() ? nsIFile(this).permissionsOfLink :
|
||||
nsIFile(this).permissions
|
||||
});
|
||||
|
||||
const FStat = Class({
|
||||
extends: Stats,
|
||||
initialize: function initialize(fd) {
|
||||
nsIFile(this, nsIFile(fd));
|
||||
}
|
||||
});
|
||||
|
||||
function noop() {}
|
||||
function Async(wrapped) {
|
||||
return function (path, callback) {
|
||||
let args = Array.slice(arguments);
|
||||
callback = args.pop();
|
||||
// If node is not given a callback argument
|
||||
// it just does not calls it.
|
||||
if (typeof(callback) !== "function") {
|
||||
args.push(callback);
|
||||
callback = noop;
|
||||
}
|
||||
setTimeout(function() {
|
||||
try {
|
||||
var result = wrapped.apply(this, args);
|
||||
if (result === undefined) callback(null);
|
||||
else callback(null, result);
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Synchronous rename(2)
|
||||
*/
|
||||
function renameSync(oldPath, newPath) {
|
||||
let source = new nsILocalFile(oldPath);
|
||||
let target = new nsILocalFile(newPath);
|
||||
if (!source.exists()) throw FSError("rename", "ENOENT", 34, oldPath);
|
||||
return source.moveTo(target.parent, target.leafName);
|
||||
};
|
||||
exports.renameSync = renameSync;
|
||||
|
||||
/**
|
||||
* Asynchronous rename(2). No arguments other than a possible exception are
|
||||
* given to the completion callback.
|
||||
*/
|
||||
let rename = Async(renameSync);
|
||||
exports.rename = rename;
|
||||
|
||||
/**
|
||||
* Test whether or not the given path exists by checking with the file system.
|
||||
*/
|
||||
function existsSync(path) {
|
||||
return new nsILocalFile(path).exists();
|
||||
}
|
||||
exports.existsSync = existsSync;
|
||||
|
||||
let exists = Async(existsSync);
|
||||
exports.exists = exists;
|
||||
|
||||
/**
|
||||
* Synchronous ftruncate(2).
|
||||
*/
|
||||
function truncateSync(path, length) {
|
||||
let fd = openSync(path, "w");
|
||||
ftruncateSync(fd, length);
|
||||
closeSync(fd);
|
||||
}
|
||||
exports.truncateSync = truncateSync;
|
||||
|
||||
/**
|
||||
* Asynchronous ftruncate(2). No arguments other than a possible exception are
|
||||
* given to the completion callback.
|
||||
*/
|
||||
function truncate(path, length, callback) {
|
||||
open(path, "w", function(error, fd) {
|
||||
if (error) return callback(error);
|
||||
ftruncate(fd, length, function(error) {
|
||||
if (error) {
|
||||
closeSync(fd);
|
||||
callback(error);
|
||||
}
|
||||
else {
|
||||
close(fd, callback);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
exports.truncate = truncate;
|
||||
|
||||
function ftruncate(fd, length, callback) {
|
||||
write(fd, new Buffer(length), 0, length, 0, function(error) {
|
||||
callback(error);
|
||||
});
|
||||
}
|
||||
exports.ftruncate = ftruncate;
|
||||
|
||||
function ftruncateSync(fd, length) {
|
||||
writeSync(fd, new Buffer(length), 0, length, 0);
|
||||
}
|
||||
exports.ftruncateSync = ftruncateSync;
|
||||
|
||||
function chownSync(path, uid, gid) {
|
||||
throw Error("Not implemented yet!!");
|
||||
}
|
||||
exports.chownSync = chownSync;
|
||||
|
||||
let chown = Async(chownSync);
|
||||
exports.chown = chown;
|
||||
|
||||
function lchownSync(path, uid, gid) {
|
||||
throw Error("Not implemented yet!!");
|
||||
}
|
||||
exports.lchownSync = chownSync;
|
||||
|
||||
let lchown = Async(lchown);
|
||||
exports.lchown = lchown;
|
||||
|
||||
/**
|
||||
* Synchronous chmod(2).
|
||||
*/
|
||||
function chmodSync (path, mode) {
|
||||
throw Error("Not implemented yet!!");
|
||||
};
|
||||
exports.chmodSync = chmodSync;
|
||||
/**
|
||||
* Asynchronous chmod(2). No arguments other than a possible exception are
|
||||
* given to the completion callback.
|
||||
*/
|
||||
let chmod = Async(chmodSync);
|
||||
exports.chmod = chmod;
|
||||
|
||||
/**
|
||||
* Synchronous chmod(2).
|
||||
*/
|
||||
function fchmodSync(fd, mode) {
|
||||
throw Error("Not implemented yet!!");
|
||||
};
|
||||
exports.fchmodSync = fchmodSync;
|
||||
/**
|
||||
* Asynchronous chmod(2). No arguments other than a possible exception are
|
||||
* given to the completion callback.
|
||||
*/
|
||||
let fchmod = Async(fchmodSync);
|
||||
exports.chmod = fchmod;
|
||||
|
||||
|
||||
/**
|
||||
* Synchronous stat(2). Returns an instance of `fs.Stats`
|
||||
*/
|
||||
function statSync(path) {
|
||||
return new Stats(path);
|
||||
};
|
||||
exports.statSync = statSync;
|
||||
|
||||
/**
|
||||
* Asynchronous stat(2). The callback gets two arguments (err, stats) where
|
||||
* stats is a `fs.Stats` object. It looks like this:
|
||||
*/
|
||||
let stat = Async(statSync);
|
||||
exports.stat = stat;
|
||||
|
||||
/**
|
||||
* Synchronous lstat(2). Returns an instance of `fs.Stats`.
|
||||
*/
|
||||
function lstatSync(path) {
|
||||
return new LStats(path);
|
||||
};
|
||||
exports.lstatSync = lstatSync;
|
||||
|
||||
/**
|
||||
* Asynchronous lstat(2). The callback gets two arguments (err, stats) where
|
||||
* stats is a fs.Stats object. lstat() is identical to stat(), except that if
|
||||
* path is a symbolic link, then the link itself is stat-ed, not the file that
|
||||
* it refers to.
|
||||
*/
|
||||
let lstat = Async(lstatSync);
|
||||
exports.lstat = lstat;
|
||||
|
||||
/**
|
||||
* Synchronous fstat(2). Returns an instance of `fs.Stats`.
|
||||
*/
|
||||
function fstatSync(fd) {
|
||||
return new FStat(fd);
|
||||
};
|
||||
exports.fstatSync = fstatSync;
|
||||
|
||||
/**
|
||||
* Asynchronous fstat(2). The callback gets two arguments (err, stats) where
|
||||
* stats is a fs.Stats object.
|
||||
*/
|
||||
let fstat = Async(fstatSync);
|
||||
exports.fstat = fstat;
|
||||
|
||||
/**
|
||||
* Synchronous link(2).
|
||||
*/
|
||||
function linkSync(source, target) {
|
||||
throw Error("Not implemented yet!!");
|
||||
};
|
||||
exports.linkSync = linkSync;
|
||||
|
||||
/**
|
||||
* Asynchronous link(2). No arguments other than a possible exception are given
|
||||
* to the completion callback.
|
||||
*/
|
||||
let link = Async(linkSync);
|
||||
exports.link = link;
|
||||
|
||||
/**
|
||||
* Synchronous symlink(2).
|
||||
*/
|
||||
function symlinkSync(source, target) {
|
||||
throw Error("Not implemented yet!!");
|
||||
};
|
||||
exports.symlinkSync = symlinkSync;
|
||||
|
||||
/**
|
||||
* Asynchronous symlink(2). No arguments other than a possible exception are
|
||||
* given to the completion callback.
|
||||
*/
|
||||
let symlink = Async(symlinkSync);
|
||||
exports.symlink = symlink;
|
||||
|
||||
/**
|
||||
* Synchronous readlink(2). Returns the resolved path.
|
||||
*/
|
||||
function readlinkSync(path) {
|
||||
return new nsILocalFile(path).target;
|
||||
};
|
||||
exports.readlinkSync = readlinkSync;
|
||||
|
||||
/**
|
||||
* Asynchronous readlink(2). The callback gets two arguments
|
||||
* `(error, resolvedPath)`.
|
||||
*/
|
||||
let readlink = Async(readlinkSync);
|
||||
exports.readlink = readlink;
|
||||
|
||||
/**
|
||||
* Synchronous realpath(2). Returns the resolved path.
|
||||
*/
|
||||
function realpathSync(path) {
|
||||
return new nsILocalFile(path).path;
|
||||
};
|
||||
exports.realpathSync = realpathSync;
|
||||
|
||||
/**
|
||||
* Asynchronous realpath(2). The callback gets two arguments
|
||||
* `(err, resolvedPath)`.
|
||||
*/
|
||||
let realpath = Async(realpathSync);
|
||||
exports.realpath = realpath;
|
||||
|
||||
/**
|
||||
* Synchronous unlink(2).
|
||||
*/
|
||||
let unlinkSync = remove;
|
||||
exports.unlinkSync = unlinkSync;
|
||||
|
||||
/**
|
||||
* Asynchronous unlink(2). No arguments other than a possible exception are
|
||||
* given to the completion callback.
|
||||
*/
|
||||
let unlink = Async(remove);
|
||||
exports.unlink = unlink;
|
||||
|
||||
/**
|
||||
* Synchronous rmdir(2).
|
||||
*/
|
||||
let rmdirSync = remove;
|
||||
exports.rmdirSync = rmdirSync;
|
||||
|
||||
/**
|
||||
* Asynchronous rmdir(2). No arguments other than a possible exception are
|
||||
* given to the completion callback.
|
||||
*/
|
||||
let rmdir = Async(rmdirSync);
|
||||
exports.rmdir = rmdir;
|
||||
|
||||
/**
|
||||
* Synchronous mkdir(2).
|
||||
*/
|
||||
function mkdirSync(path, mode) {
|
||||
try {
|
||||
return nsILocalFile(path).create(DIRECTORY_TYPE, Mode(mode));
|
||||
} catch (error) {
|
||||
// Adjust exception thorw to match ones thrown by node.
|
||||
if (error.name === "NS_ERROR_FILE_ALREADY_EXISTS") {
|
||||
let { fileName, lineNumber } = error;
|
||||
error = FSError("mkdir", "EEXIST", 47, path, fileName, lineNumber);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
exports.mkdirSync = mkdirSync;
|
||||
|
||||
/**
|
||||
* Asynchronous mkdir(2). No arguments other than a possible exception are
|
||||
* given to the completion callback.
|
||||
*/
|
||||
let mkdir = Async(mkdirSync);
|
||||
exports.mkdir = mkdir;
|
||||
|
||||
/**
|
||||
* Synchronous readdir(3). Returns an array of filenames excluding `"."` and
|
||||
* `".."`.
|
||||
*/
|
||||
function readdirSync(path) {
|
||||
try {
|
||||
return toArray(new nsILocalFile(path).directoryEntries).map(getFileName);
|
||||
}
|
||||
catch (error) {
|
||||
// Adjust exception thorw to match ones thrown by node.
|
||||
if (error.name === "NS_ERROR_FILE_TARGET_DOES_NOT_EXIST" ||
|
||||
error.name === "NS_ERROR_FILE_NOT_FOUND")
|
||||
{
|
||||
let { fileName, lineNumber } = error;
|
||||
error = FSError("readdir", "ENOENT", 34, path, fileName, lineNumber);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
exports.readdirSync = readdirSync;
|
||||
|
||||
/**
|
||||
* Asynchronous readdir(3). Reads the contents of a directory. The callback
|
||||
* gets two arguments `(error, files)` where `files` is an array of the names
|
||||
* of the files in the directory excluding `"."` and `".."`.
|
||||
*/
|
||||
let readdir = Async(readdirSync);
|
||||
exports.readdir = readdir;
|
||||
|
||||
/**
|
||||
* Synchronous close(2).
|
||||
*/
|
||||
function closeSync(fd) {
|
||||
let input = nsIFileInputStream(fd);
|
||||
let output = nsIFileOutputStream(fd);
|
||||
|
||||
// Closing input stream and removing reference.
|
||||
if (input) input.close();
|
||||
// Closing output stream and removing reference.
|
||||
if (output) output.close();
|
||||
|
||||
nsIFile(fd, null);
|
||||
nsIFileInputStream(fd, null);
|
||||
nsIFileOutputStream(fd, null);
|
||||
nsIBinaryInputStream(fd, null);
|
||||
nsIBinaryOutputStream(fd, null);
|
||||
};
|
||||
exports.closeSync = closeSync;
|
||||
/**
|
||||
* Asynchronous close(2). No arguments other than a possible exception are
|
||||
* given to the completion callback.
|
||||
*/
|
||||
let close = Async(closeSync);
|
||||
exports.close = close;
|
||||
|
||||
/**
|
||||
* Synchronous open(2).
|
||||
*/
|
||||
function openSync(path, flags, mode) {
|
||||
let [ fd, flags, mode, file ] =
|
||||
[ { path: path }, Flags(flags), Mode(mode), nsILocalFile(path) ];
|
||||
|
||||
// If trying to open file for just read that does not exists
|
||||
// need to throw exception as node does.
|
||||
if (!file.exists() && !isWritable(flags))
|
||||
throw FSError("open", "ENOENT", 34, path);
|
||||
|
||||
// If we want to open file in read mode we initialize input stream.
|
||||
if (isReadable(flags)) {
|
||||
let input = FileInputStream(file, flags, mode, DEFER_OPEN);
|
||||
nsIFileInputStream(fd, input);
|
||||
}
|
||||
|
||||
// If we want to open file in write mode we initialize output stream for it.
|
||||
if (isWritable(flags)) {
|
||||
let output = FileOutputStream(file, flags, mode, DEFER_OPEN);
|
||||
nsIFileOutputStream(fd, output);
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
exports.openSync = openSync;
|
||||
/**
|
||||
* Asynchronous file open. See open(2). Flags can be
|
||||
* `"r", "r+", "w", "w+", "a"`, or `"a+"`. mode defaults to `0666`.
|
||||
* The callback gets two arguments `(error, fd).
|
||||
*/
|
||||
let open = Async(openSync);
|
||||
exports.open = open;
|
||||
|
||||
/**
|
||||
* Synchronous version of buffer-based fs.write(). Returns the number of bytes
|
||||
* written.
|
||||
*/
|
||||
function writeSync(fd, buffer, offset, length, position) {
|
||||
if (length + offset > buffer.length) {
|
||||
throw Error("Length is extends beyond buffer");
|
||||
}
|
||||
else if (length + offset !== buffer.length) {
|
||||
buffer = buffer.slice(offset, offset + length);
|
||||
}
|
||||
let writeStream = new WriteStream(fd, { position: position,
|
||||
length: length });
|
||||
let output = nsIBinaryOutputStream(fd);
|
||||
// We write content as a byte array as this will avoid any transcoding
|
||||
// if content was a buffer.
|
||||
output.writeByteArray(buffer.valueOf(), buffer.length);
|
||||
output.flush();
|
||||
};
|
||||
exports.writeSync = writeSync;
|
||||
|
||||
/**
|
||||
* Write buffer to the file specified by fd.
|
||||
*
|
||||
* `offset` and `length` determine the part of the buffer to be written.
|
||||
*
|
||||
* `position` refers to the offset from the beginning of the file where this
|
||||
* data should be written. If `position` is `null`, the data will be written
|
||||
* at the current position. See pwrite(2).
|
||||
*
|
||||
* The callback will be given three arguments `(error, written, buffer)` where
|
||||
* written specifies how many bytes were written into buffer.
|
||||
*
|
||||
* Note that it is unsafe to use `fs.write` multiple times on the same file
|
||||
* without waiting for the callback.
|
||||
*/
|
||||
function write(fd, buffer, offset, length, position, callback) {
|
||||
if (!Buffer.isBuffer(buffer)) {
|
||||
// (fd, data, position, encoding, callback)
|
||||
let encoding = null;
|
||||
[ position, encoding, callback ] = Array.slice(arguments, 1);
|
||||
buffer = new Buffer(String(buffer), encoding);
|
||||
offset = 0;
|
||||
} else if (length + offset > buffer.length) {
|
||||
throw Error("Length is extends beyond buffer");
|
||||
} else if (length + offset !== buffer.length) {
|
||||
buffer = buffer.slice(offset, offset + length);
|
||||
}
|
||||
|
||||
let writeStream = new WriteStream(fd, { position: position,
|
||||
length: length });
|
||||
writeStream.on("error", callback);
|
||||
writeStream.write(buffer, function onEnd() {
|
||||
writeStream.destroy();
|
||||
if (callback)
|
||||
callback(null, buffer.length, buffer);
|
||||
});
|
||||
};
|
||||
exports.write = write;
|
||||
|
||||
/**
|
||||
* Synchronous version of string-based fs.read. Returns the number of
|
||||
* bytes read.
|
||||
*/
|
||||
function readSync(fd, buffer, offset, length, position) {
|
||||
let input = nsIFileInputStream(fd);
|
||||
// Setting a stream position, unless it"s `-1` which means current position.
|
||||
if (position >= 0)
|
||||
input.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position);
|
||||
// We use `nsIStreamTransportService` service to transform blocking
|
||||
// file input stream into a fully asynchronous stream that can be written
|
||||
// without blocking the main thread.
|
||||
let binaryInputStream = BinaryInputStream(input);
|
||||
let count = length === ALL ? binaryInputStream.available() : length;
|
||||
var bytes = binaryInputStream.readByteArray(count);
|
||||
buffer.copy.call(bytes, buffer, offset);
|
||||
|
||||
return bytes;
|
||||
};
|
||||
exports.readSync = readSync;
|
||||
|
||||
/**
|
||||
* Read data from the file specified by `fd`.
|
||||
*
|
||||
* `buffer` is the buffer that the data will be written to.
|
||||
* `offset` is offset within the buffer where writing will start.
|
||||
*
|
||||
* `length` is an integer specifying the number of bytes to read.
|
||||
*
|
||||
* `position` is an integer specifying where to begin reading from in the file.
|
||||
* If `position` is `null`, data will be read from the current file position.
|
||||
*
|
||||
* The callback is given the three arguments, `(error, bytesRead, buffer)`.
|
||||
*/
|
||||
function read(fd, buffer, offset, length, position, callback) {
|
||||
let bytesRead = 0;
|
||||
let readStream = new ReadStream(fd, { position: position, length: length });
|
||||
readStream.on("data", function onData(chunck) {
|
||||
chunck.copy(buffer, offset + bytesRead);
|
||||
bytesRead += chunck.length;
|
||||
});
|
||||
readStream.on("end", function onEnd() {
|
||||
callback(null, bytesRead, buffer);
|
||||
readStream.destroy();
|
||||
});
|
||||
};
|
||||
exports.read = read;
|
||||
|
||||
/**
|
||||
* Asynchronously reads the entire contents of a file.
|
||||
* The callback is passed two arguments `(error, data)`, where data is the
|
||||
* contents of the file.
|
||||
*/
|
||||
function readFile(path, encoding, callback) {
|
||||
if (isFunction(encoding)) {
|
||||
callback = encoding
|
||||
encoding = null
|
||||
}
|
||||
|
||||
let buffer = new Buffer();
|
||||
try {
|
||||
let readStream = new ReadStream(path);
|
||||
readStream.on("data", function(chunck) {
|
||||
chunck.copy(buffer, buffer.length);
|
||||
});
|
||||
readStream.on("error", function onError(error) {
|
||||
callback(error);
|
||||
readStream.destroy();
|
||||
});
|
||||
readStream.on("end", function onEnd() {
|
||||
callback(null, buffer);
|
||||
readStream.destroy();
|
||||
});
|
||||
} catch (error) {
|
||||
setTimeout(callback, 0, error);
|
||||
}
|
||||
};
|
||||
exports.readFile = readFile;
|
||||
|
||||
/**
|
||||
* Synchronous version of `fs.readFile`. Returns the contents of the path.
|
||||
* If encoding is specified then this function returns a string.
|
||||
* Otherwise it returns a buffer.
|
||||
*/
|
||||
function readFileSync(path, encoding) {
|
||||
let buffer = new Buffer();
|
||||
let fd = openSync(path, "r");
|
||||
try {
|
||||
readSync(fd, buffer, 0, ALL, 0);
|
||||
}
|
||||
finally {
|
||||
closeSync(fd);
|
||||
}
|
||||
return buffer;
|
||||
};
|
||||
exports.readFileSync = readFileSync;
|
||||
|
||||
/**
|
||||
* Asynchronously writes data to a file, replacing the file if it already
|
||||
* exists. data can be a string or a buffer.
|
||||
*/
|
||||
function writeFile(path, content, encoding, callback) {
|
||||
try {
|
||||
if (isFunction(encoding)) {
|
||||
callback = encoding
|
||||
encoding = null
|
||||
}
|
||||
if (isString(content))
|
||||
content = new Buffer(content, encoding);
|
||||
|
||||
let writeStream = new WriteStream(path);
|
||||
writeStream.on("error", function onError(error) {
|
||||
callback(error);
|
||||
writeStream.destroy();
|
||||
});
|
||||
writeStream.write(content, function onDrain() {
|
||||
writeStream.destroy();
|
||||
callback(null);
|
||||
});
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
}
|
||||
};
|
||||
exports.writeFile = writeFile;
|
||||
|
||||
/**
|
||||
* The synchronous version of `fs.writeFile`.
|
||||
*/
|
||||
function writeFileSync(filename, data, encoding) {
|
||||
throw Error("Not implemented");
|
||||
};
|
||||
exports.writeFileSync = writeFileSync;
|
||||
|
||||
|
||||
function utimesSync(path, atime, mtime) {
|
||||
throw Error("Not implemented");
|
||||
}
|
||||
exports.utimesSync = utimesSync;
|
||||
|
||||
let utimes = Async(utimesSync);
|
||||
exports.utimes = utimes;
|
||||
|
||||
function futimesSync(fd, atime, mtime, callback) {
|
||||
throw Error("Not implemented");
|
||||
}
|
||||
exports.futimesSync = futimesSync;
|
||||
|
||||
let futimes = Async(futimesSync);
|
||||
exports.futimes = futimes;
|
||||
|
||||
function fsyncSync(fd, atime, mtime, callback) {
|
||||
throw Error("Not implemented");
|
||||
}
|
||||
exports.fsyncSync = fsyncSync;
|
||||
|
||||
let fsync = Async(fsyncSync);
|
||||
exports.fsync = fsync;
|
||||
|
||||
|
||||
/**
|
||||
* Watch for changes on filename. The callback listener will be called each
|
||||
* time the file is accessed.
|
||||
*
|
||||
* The second argument is optional. The options if provided should be an object
|
||||
* containing two members a boolean, persistent, and interval, a polling value
|
||||
* in milliseconds. The default is { persistent: true, interval: 0 }.
|
||||
*/
|
||||
function watchFile(path, options, listener) {
|
||||
throw Error("Not implemented");
|
||||
};
|
||||
exports.watchFile = watchFile;
|
||||
|
||||
|
||||
function unwatchFile(path, listener) {
|
||||
throw Error("Not implemented");
|
||||
}
|
||||
exports.unwatchFile = unwatchFile;
|
||||
|
||||
function watch(path, options, listener) {
|
||||
throw Error("Not implemented");
|
||||
}
|
||||
exports.watch = watch;
|
@ -1,324 +0,0 @@
|
||||
/* 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";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "experimental"
|
||||
};
|
||||
|
||||
const { EventTarget } = require("../event/target");
|
||||
const { emit } = require("../event/core");
|
||||
const { Buffer } = require("./buffer");
|
||||
const { Class } = require("../core/heritage");
|
||||
const { setTimeout } = require("../timers");
|
||||
const { ns } = require("../core/namespace");
|
||||
|
||||
function isFunction(value) typeof value === "function"
|
||||
|
||||
function accessor() {
|
||||
let map = new WeakMap();
|
||||
return function(fd, value) {
|
||||
if (value === null) map.delete(fd);
|
||||
if (value !== undefined) map.set(fd, value);
|
||||
return map.get(fd);
|
||||
}
|
||||
}
|
||||
|
||||
let nsIInputStreamPump = accessor();
|
||||
let nsIAsyncOutputStream = accessor();
|
||||
let nsIInputStream = accessor();
|
||||
let nsIOutputStream = accessor();
|
||||
|
||||
|
||||
/**
|
||||
* Utility function / hack that we use to figure if output stream is closed.
|
||||
*/
|
||||
function isClosed(stream) {
|
||||
// We assume that stream is not closed.
|
||||
let isClosed = false;
|
||||
stream.asyncWait({
|
||||
// If `onClose` callback is called before outer function returns
|
||||
// (synchronously) `isClosed` will be set to `true` identifying
|
||||
// that stream is closed.
|
||||
onOutputStreamReady: function onClose() isClosed = true
|
||||
|
||||
// `WAIT_CLOSURE_ONLY` flag overrides the default behavior, causing the
|
||||
// `onOutputStreamReady` notification to be suppressed until the stream
|
||||
// becomes closed.
|
||||
}, stream.WAIT_CLOSURE_ONLY, 0, null);
|
||||
return isClosed;
|
||||
}
|
||||
/**
|
||||
* Utility function takes output `stream`, `onDrain`, `onClose` callbacks and
|
||||
* calls one of this callbacks depending on stream state. It is guaranteed
|
||||
* that only one called will be called and it will be called asynchronously.
|
||||
* @param {nsIAsyncOutputStream} stream
|
||||
* @param {Function} onDrain
|
||||
* callback that is called when stream becomes writable.
|
||||
* @param {Function} onClose
|
||||
* callback that is called when stream becomes closed.
|
||||
*/
|
||||
function onStateChange(stream, target) {
|
||||
let isAsync = false;
|
||||
stream.asyncWait({
|
||||
onOutputStreamReady: function onOutputStreamReady() {
|
||||
// If `isAsync` was not yet set to `true` by the last line we know that
|
||||
// `onOutputStreamReady` was called synchronously. In such case we just
|
||||
// defer execution until next turn of event loop.
|
||||
if (!isAsync)
|
||||
return setTimeout(onOutputStreamReady, 0);
|
||||
|
||||
// As it"s not clear what is a state of the stream (TODO: Is there really
|
||||
// no better way ?) we employ hack (see details in `isClosed`) to verify
|
||||
// if stream is closed.
|
||||
emit(target, isClosed(stream) ? "close" : "drain");
|
||||
}
|
||||
}, 0, 0, null);
|
||||
isAsync = true;
|
||||
}
|
||||
|
||||
function pump(stream) {
|
||||
let input = nsIInputStream(stream);
|
||||
nsIInputStreamPump(stream).asyncRead({
|
||||
onStartRequest: function onStartRequest() {
|
||||
emit(stream, "start");
|
||||
},
|
||||
onDataAvailable: function onDataAvailable(req, c, is, offset, count) {
|
||||
try {
|
||||
let bytes = input.readByteArray(count);
|
||||
emit(stream, "data", new Buffer(bytes, stream.encoding));
|
||||
} catch (error) {
|
||||
emit(stream, "error", error);
|
||||
stream.readable = false;
|
||||
}
|
||||
},
|
||||
onStopRequest: function onStopRequest() {
|
||||
stream.readable = false;
|
||||
emit(stream, "end");
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
|
||||
const Stream = Class({
|
||||
extends: EventTarget,
|
||||
initialize: function() {
|
||||
this.readable = false;
|
||||
this.writable = false;
|
||||
this.encoding = null;
|
||||
},
|
||||
setEncoding: function setEncoding(encoding) {
|
||||
this.encoding = String(encoding).toUpperCase();
|
||||
},
|
||||
pipe: function pipe(target, options) {
|
||||
let source = this;
|
||||
function onData(chunk) {
|
||||
if (target.writable) {
|
||||
if (false === target.write(chunk))
|
||||
source.pause();
|
||||
}
|
||||
}
|
||||
function onDrain() {
|
||||
if (source.readable) source.resume();
|
||||
}
|
||||
function onEnd() {
|
||||
target.end();
|
||||
}
|
||||
function onPause() {
|
||||
source.pause();
|
||||
}
|
||||
function onResume() {
|
||||
if (source.readable)
|
||||
source.resume();
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
source.removeListener("data", onData);
|
||||
target.removeListener("drain", onDrain);
|
||||
source.removeListener("end", onEnd);
|
||||
|
||||
target.removeListener("pause", onPause);
|
||||
target.removeListener("resume", onResume);
|
||||
|
||||
source.removeListener("end", cleanup);
|
||||
source.removeListener("close", cleanup);
|
||||
|
||||
target.removeListener("end", cleanup);
|
||||
target.removeListener("close", cleanup);
|
||||
}
|
||||
|
||||
if (!options || options.end !== false)
|
||||
target.on("end", onEnd);
|
||||
|
||||
source.on("data", onData);
|
||||
target.on("drain", onDrain);
|
||||
target.on("resume", onResume);
|
||||
target.on("pause", onPause);
|
||||
|
||||
source.on("end", cleanup);
|
||||
source.on("close", cleanup);
|
||||
|
||||
target.on("end", cleanup);
|
||||
target.on("close", cleanup);
|
||||
|
||||
emit(target, "pipe", source);
|
||||
},
|
||||
pause: function pause() {
|
||||
emit(this, "pause");
|
||||
},
|
||||
resume: function resume() {
|
||||
emit(this, "resume");
|
||||
},
|
||||
destroySoon: function destroySoon() {
|
||||
this.destroy();
|
||||
}
|
||||
});
|
||||
exports.Stream = Stream;
|
||||
|
||||
const InputStream = Class({
|
||||
extends: Stream,
|
||||
initialize: function initialize(options) {
|
||||
let { input, pump } = options;
|
||||
|
||||
this.readable = true;
|
||||
this.paused = false;
|
||||
nsIInputStream(this, input);
|
||||
nsIInputStreamPump(this, pump);
|
||||
},
|
||||
get status() nsIInputStreamPump(this).status,
|
||||
read: function() pump(this),
|
||||
pause: function pause() {
|
||||
this.paused = true;
|
||||
nsIInputStreamPump(this).suspend();
|
||||
emit(this, "paused");
|
||||
},
|
||||
resume: function resume() {
|
||||
this.paused = false;
|
||||
nsIInputStreamPump(this).resume();
|
||||
emit(this, "resume");
|
||||
},
|
||||
destroy: function destroy() {
|
||||
this.readable = false;
|
||||
try {
|
||||
emit(this, "close", null);
|
||||
nsIInputStreamPump(this).cancel(null);
|
||||
nsIInputStreamPump(this, null);
|
||||
|
||||
nsIInputStream(this).close();
|
||||
nsIInputStream(this, null);
|
||||
} catch (error) {
|
||||
emit(this, "error", error);
|
||||
}
|
||||
}
|
||||
});
|
||||
exports.InputStream = InputStream;
|
||||
|
||||
const OutputStream = Class({
|
||||
extends: Stream,
|
||||
initialize: function initialize(options) {
|
||||
let { output, asyncOutputStream } = options;
|
||||
|
||||
this.writable = true;
|
||||
nsIOutputStream(this, output);
|
||||
nsIAsyncOutputStream(this, asyncOutputStream);
|
||||
},
|
||||
write: function write(content, encoding, callback) {
|
||||
let output = nsIOutputStream(this);
|
||||
let asyncOutputStream = nsIAsyncOutputStream(this);
|
||||
|
||||
if (isFunction(encoding)) {
|
||||
callback = encoding;
|
||||
encoding = callback;
|
||||
}
|
||||
|
||||
// Flag indicating whether or not content has been flushed to the kernel
|
||||
// buffer.
|
||||
let isWritten = false;
|
||||
// If stream is not writable we throw an error.
|
||||
if (!this.writable)
|
||||
throw Error("stream not writable");
|
||||
|
||||
try {
|
||||
// If content is not a buffer then we create one out of it.
|
||||
if (!Buffer.isBuffer(content))
|
||||
content = new Buffer(content, encoding);
|
||||
|
||||
// We write content as a byte array as this will avoid any transcoding
|
||||
// if content was a buffer.
|
||||
output.writeByteArray(content.valueOf(), content.length);
|
||||
output.flush();
|
||||
|
||||
if (callback) this.once("drain", callback);
|
||||
onStateChange(asyncOutputStream, this);
|
||||
return true;
|
||||
} catch (error) {
|
||||
// If errors occur we emit appropriate event.
|
||||
emit(this, "error", error);
|
||||
}
|
||||
},
|
||||
flush: function flush() {
|
||||
nsIOutputStream(this).flush();
|
||||
},
|
||||
end: function end(content, encoding, callback) {
|
||||
if (isFunction(content)) {
|
||||
callback = content
|
||||
content = callback
|
||||
}
|
||||
if (isFunction(encoding)) {
|
||||
callback = encoding
|
||||
encoding = callback
|
||||
}
|
||||
|
||||
// Setting a listener to "close" event if passed.
|
||||
if (isFunction(callback))
|
||||
this.once("close", callback);
|
||||
|
||||
// If content is passed then we defer closing until we finish with writing.
|
||||
if (content)
|
||||
this.write(content, encoding, end.bind(this));
|
||||
// If we don"t write anything, then we close an outputStream.
|
||||
else
|
||||
nsIOutputStream(this).close();
|
||||
},
|
||||
destroy: function destroy(callback) {
|
||||
try {
|
||||
this.end(callback);
|
||||
nsIOutputStream(this, null);
|
||||
nsIAsyncOutputStream(this, null);
|
||||
} catch (error) {
|
||||
emit(this, "error", error);
|
||||
}
|
||||
}
|
||||
});
|
||||
exports.OutputStream = OutputStream;
|
||||
|
||||
const DuplexStream = Class({
|
||||
extends: Stream,
|
||||
initialize: function initialize(options) {
|
||||
let { input, output, pump } = options;
|
||||
|
||||
this.writable = true;
|
||||
this.readable = true;
|
||||
this.encoding = null;
|
||||
|
||||
nsIInputStream(this, input);
|
||||
nsIOutputStream(this, output);
|
||||
nsIInputStreamPump(this, pump);
|
||||
},
|
||||
read: InputStream.prototype.read,
|
||||
pause: InputStream.prototype.pause,
|
||||
resume: InputStream.prototype.resume,
|
||||
|
||||
write: OutputStream.prototype.write,
|
||||
flush: OutputStream.prototype.flush,
|
||||
end: OutputStream.prototype.end,
|
||||
|
||||
destroy: function destroy(error) {
|
||||
if (error)
|
||||
emit(this, "error", error);
|
||||
InputStream.prototype.destroy.call(this);
|
||||
OutputStream.prototype.destroy.call(this);
|
||||
}
|
||||
});
|
||||
exports.DuplexStream = DuplexStream;
|
@ -12,9 +12,10 @@ module.metadata = {
|
||||
const observers = require('./deprecated/observer-service');
|
||||
const { Loader, validationAttributes } = require('./content/loader');
|
||||
const { Worker } = require('./content/worker');
|
||||
const { Registry } = require('./util/registry');
|
||||
const { EventEmitter } = require('./deprecated/events');
|
||||
const { on, emit } = require('./event/core');
|
||||
const { List } = require('./deprecated/list');
|
||||
const { Registry } = require('./util/registry');
|
||||
const { MatchPattern } = require('./page-mod/match-pattern');
|
||||
const { validateOptions : validate } = require('./deprecated/api-utils');
|
||||
const { Cc, Ci } = require('chrome');
|
||||
const { merge } = require('./util/object');
|
||||
@ -23,17 +24,14 @@ const { windowIterator } = require('./deprecated/window-utils');
|
||||
const { isBrowser, getFrames } = require('./window/utils');
|
||||
const { getTabs, getTabContentWindow, getTabForContentWindow,
|
||||
getURI: getTabURI } = require('./tabs/utils');
|
||||
const { has, hasAny } = require('./util/array');
|
||||
const { ignoreWindow } = require('sdk/private-browsing/utils');
|
||||
const { Style } = require("./stylesheet/style");
|
||||
const { attach, detach } = require("./content/mod");
|
||||
const { has, hasAny } = require("./util/array");
|
||||
const { Rules } = require("./util/rules");
|
||||
|
||||
// Valid values for `attachTo` option
|
||||
const VALID_ATTACHTO_OPTIONS = ['existing', 'top', 'frame'];
|
||||
|
||||
const mods = new WeakMap();
|
||||
|
||||
// contentStyle* / contentScript* are sharing the same validation constraints,
|
||||
// so they can be mostly reused, except for the messages.
|
||||
const validStyleOptions = {
|
||||
@ -45,6 +43,27 @@ const validStyleOptions = {
|
||||
})
|
||||
};
|
||||
|
||||
// rules registry
|
||||
const RULES = {};
|
||||
|
||||
const Rules = EventEmitter.resolve({ toString: null }).compose(List, {
|
||||
add: function() Array.slice(arguments).forEach(function onAdd(rule) {
|
||||
if (this._has(rule))
|
||||
return;
|
||||
// registering rule to the rules registry
|
||||
if (!(rule in RULES))
|
||||
RULES[rule] = new MatchPattern(rule);
|
||||
this._add(rule);
|
||||
this._emit('add', rule);
|
||||
}.bind(this)),
|
||||
remove: function() Array.slice(arguments).forEach(function onRemove(rule) {
|
||||
if (!this._has(rule))
|
||||
return;
|
||||
this._remove(rule);
|
||||
this._emit('remove', rule);
|
||||
}.bind(this)),
|
||||
});
|
||||
|
||||
/**
|
||||
* PageMod constructor (exported below).
|
||||
* @constructor
|
||||
@ -102,11 +121,13 @@ const PageMod = Loader.compose(EventEmitter, {
|
||||
|
||||
let include = options.include;
|
||||
let rules = this.include = Rules();
|
||||
|
||||
if (!include)
|
||||
throw new Error('The `include` option must always contain atleast one rule');
|
||||
rules.on('add', this._onRuleAdd = this._onRuleAdd.bind(this));
|
||||
rules.on('remove', this._onRuleRemove = this._onRuleRemove.bind(this));
|
||||
|
||||
rules.add.apply(rules, [].concat(include));
|
||||
if (Array.isArray(include))
|
||||
rules.add.apply(null, include);
|
||||
else
|
||||
rules.add(include);
|
||||
|
||||
if (contentStyle || contentStyleFile) {
|
||||
this._style = Style({
|
||||
@ -117,7 +138,6 @@ const PageMod = Loader.compose(EventEmitter, {
|
||||
|
||||
this.on('error', this._onUncaughtError = this._onUncaughtError.bind(this));
|
||||
pageModManager.add(this._public);
|
||||
mods.set(this._public, this);
|
||||
|
||||
// `_applyOnExistingDocuments` has to be called after `pageModManager.add()`
|
||||
// otherwise its calls to `_onContent` method won't do anything.
|
||||
@ -126,21 +146,26 @@ const PageMod = Loader.compose(EventEmitter, {
|
||||
},
|
||||
|
||||
destroy: function destroy() {
|
||||
|
||||
if (this._style)
|
||||
detach(this._style);
|
||||
|
||||
for (let i in this.include)
|
||||
this.include.remove(this.include[i]);
|
||||
|
||||
mods.delete(this._public);
|
||||
for each (let rule in this.include)
|
||||
this.include.remove(rule);
|
||||
pageModManager.remove(this._public);
|
||||
},
|
||||
|
||||
_applyOnExistingDocuments: function _applyOnExistingDocuments() {
|
||||
let mod = this;
|
||||
// Returns true if the tab match one rule
|
||||
function isMatchingURI(uri) {
|
||||
// Use Array.some as `include` isn't a native array
|
||||
return Array.some(mod.include, function (rule) {
|
||||
return RULES[rule].test(uri);
|
||||
});
|
||||
}
|
||||
let tabs = getAllTabs().filter(function (tab) {
|
||||
return mod.include.matchesAny(getTabURI(tab));
|
||||
return isMatchingURI(getTabURI(tab));
|
||||
});
|
||||
|
||||
tabs.forEach(function (tab) {
|
||||
@ -205,6 +230,12 @@ const PageMod = Loader.compose(EventEmitter, {
|
||||
worker.destroy();
|
||||
});
|
||||
},
|
||||
_onRuleAdd: function _onRuleAdd(url) {
|
||||
pageModManager.on(url, this._onContent);
|
||||
},
|
||||
_onRuleRemove: function _onRuleRemove(url) {
|
||||
pageModManager.off(url, this._onContent);
|
||||
},
|
||||
_onUncaughtError: function _onUncaughtError(e) {
|
||||
if (this._listeners('error').length == 1)
|
||||
console.exception(e);
|
||||
@ -227,6 +258,9 @@ const PageModManager = Registry.resolve({
|
||||
_destructor: function _destructor() {
|
||||
observers.remove('document-element-inserted', this._onContentWindow);
|
||||
this._removeAllListeners();
|
||||
for (let rule in RULES) {
|
||||
delete RULES[rule];
|
||||
}
|
||||
|
||||
// We need to do some cleaning er PageMods, like unregistering any
|
||||
// `contentStyle*`
|
||||
@ -251,13 +285,14 @@ const PageModManager = Registry.resolve({
|
||||
return;
|
||||
}
|
||||
|
||||
this._registry.forEach(function(mod) {
|
||||
if (mod.include.matchesAny(document.URL))
|
||||
mods.get(mod)._onContent(window);
|
||||
});
|
||||
for (let rule in RULES)
|
||||
if (RULES[rule].test(document.URL))
|
||||
this._emit(rule, window);
|
||||
},
|
||||
off: function off(topic, listener) {
|
||||
this.removeListener(topic, listener);
|
||||
if (!this._listeners(topic).length)
|
||||
delete RULES[topic];
|
||||
}
|
||||
});
|
||||
const pageModManager = PageModManager();
|
||||
|
@ -1,5 +1,115 @@
|
||||
let { deprecateUsage } = require("../util/deprecate");
|
||||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* 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/. */
|
||||
|
||||
deprecateUsage("Module 'sdk/page-mod/match-pattern' is deprecated use 'sdk/util/match-pattern' instead");
|
||||
"use strict";
|
||||
|
||||
module.exports = require("../page-mod/match-pattern");
|
||||
module.metadata = {
|
||||
"stability": "unstable"
|
||||
};
|
||||
|
||||
const { URL } = require("../url");
|
||||
|
||||
exports.MatchPattern = MatchPattern;
|
||||
|
||||
function MatchPattern(pattern) {
|
||||
if (typeof pattern.test == "function") {
|
||||
|
||||
// For compatibility with -moz-document rules, we require the RegExp's
|
||||
// global, ignoreCase, and multiline flags to be set to false.
|
||||
if (pattern.global) {
|
||||
throw new Error("A RegExp match pattern cannot be set to `global` " +
|
||||
"(i.e. //g).");
|
||||
}
|
||||
if (pattern.ignoreCase) {
|
||||
throw new Error("A RegExp match pattern cannot be set to `ignoreCase` " +
|
||||
"(i.e. //i).");
|
||||
}
|
||||
if (pattern.multiline) {
|
||||
throw new Error("A RegExp match pattern cannot be set to `multiline` " +
|
||||
"(i.e. //m).");
|
||||
}
|
||||
|
||||
this.regexp = pattern;
|
||||
}
|
||||
else {
|
||||
let firstWildcardPosition = pattern.indexOf("*");
|
||||
let lastWildcardPosition = pattern.lastIndexOf("*");
|
||||
if (firstWildcardPosition != lastWildcardPosition)
|
||||
throw new Error("There can be at most one '*' character in a wildcard.");
|
||||
|
||||
if (firstWildcardPosition == 0) {
|
||||
if (pattern.length == 1)
|
||||
this.anyWebPage = true;
|
||||
else if (pattern[1] != ".")
|
||||
throw new Error("Expected a *.<domain name> string, got: " + pattern);
|
||||
else
|
||||
this.domain = pattern.substr(2);
|
||||
}
|
||||
else {
|
||||
if (pattern.indexOf(":") == -1) {
|
||||
throw new Error("When not using *.example.org wildcard, the string " +
|
||||
"supplied is expected to be either an exact URL to " +
|
||||
"match or a URL prefix. The provided string ('" +
|
||||
pattern + "') is unlikely to match any pages.");
|
||||
}
|
||||
|
||||
if (firstWildcardPosition == -1)
|
||||
this.exactURL = pattern;
|
||||
else if (firstWildcardPosition == pattern.length - 1)
|
||||
this.urlPrefix = pattern.substr(0, pattern.length - 1);
|
||||
else {
|
||||
throw new Error("The provided wildcard ('" + pattern + "') has a '*' " +
|
||||
"in an unexpected position. It is expected to be the " +
|
||||
"first or the last character in the wildcard.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MatchPattern.prototype = {
|
||||
|
||||
test: function MatchPattern_test(urlStr) {
|
||||
try {
|
||||
var url = URL(urlStr);
|
||||
}
|
||||
catch (err) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Test the URL against a RegExp pattern. For compatibility with
|
||||
// -moz-document rules, we require the RegExp to match the entire URL,
|
||||
// so we not only test for a match, we also make sure the matched string
|
||||
// is the entire URL string.
|
||||
//
|
||||
// Assuming most URLs don't match most match patterns, we call `test` for
|
||||
// speed when determining whether or not the URL matches, then call `exec`
|
||||
// for the small subset that match to make sure the entire URL matches.
|
||||
//
|
||||
if (this.regexp && this.regexp.test(urlStr) &&
|
||||
this.regexp.exec(urlStr)[0] == urlStr)
|
||||
return true;
|
||||
|
||||
if (this.anyWebPage && /^(https?|ftp)$/.test(url.scheme))
|
||||
return true;
|
||||
if (this.exactURL && this.exactURL == urlStr)
|
||||
return true;
|
||||
|
||||
// Tests the urlStr against domain and check if
|
||||
// wildcard submitted (*.domain.com), it only allows
|
||||
// subdomains (sub.domain.com) or from the root (http://domain.com)
|
||||
// and reject non-matching domains (otherdomain.com)
|
||||
// bug 856913
|
||||
if (this.domain && url.host &&
|
||||
(url.host === this.domain ||
|
||||
url.host.slice(-this.domain.length - 1) === "." + this.domain))
|
||||
return true;
|
||||
if (this.urlPrefix && 0 == urlStr.indexOf(this.urlPrefix))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -9,161 +9,54 @@ module.metadata = {
|
||||
"stability": "stable"
|
||||
};
|
||||
|
||||
const { Class } = require('./core/heritage');
|
||||
const { on, emit, off, setListeners } = require('./event/core');
|
||||
const { filter, pipe, map, merge: streamMerge } = require('./event/utils');
|
||||
const { WorkerHost, Worker, detach, attach } = require('./worker/utils');
|
||||
const { Disposable } = require('./core/disposable');
|
||||
const { EventTarget } = require('./event/target');
|
||||
const { unload } = require('./system/unload');
|
||||
const { events, streamEventsFrom } = require('./content/events');
|
||||
const { getAttachEventType } = require('./content/utils');
|
||||
const { window } = require('./addon/window');
|
||||
const { getParentWindow } = require('./window/utils');
|
||||
const { create: makeFrame, getDocShell } = require('./frame/utils');
|
||||
const { contract } = require('./util/contract');
|
||||
const { contract: loaderContract } = require('./content/loader');
|
||||
const { has } = require('./util/array');
|
||||
const { Rules } = require('./util/rules');
|
||||
const { merge } = require('./util/object');
|
||||
const { Symbiont } = require("./content/symbiont");
|
||||
const { Trait } = require("./deprecated/traits");
|
||||
|
||||
const views = WeakMap();
|
||||
const workers = WeakMap();
|
||||
const pages = WeakMap();
|
||||
const Page = Trait.compose(
|
||||
Symbiont.resolve({
|
||||
constructor: '_initSymbiont'
|
||||
}),
|
||||
{
|
||||
_frame: Trait.required,
|
||||
_initFrame: Trait.required,
|
||||
postMessage: Symbiont.required,
|
||||
on: Symbiont.required,
|
||||
destroy: Symbiont.required,
|
||||
|
||||
const readyEventNames = [
|
||||
'DOMContentLoaded',
|
||||
'document-element-inserted',
|
||||
'load'
|
||||
];
|
||||
constructor: function Page(options) {
|
||||
options = options || {};
|
||||
|
||||
function workerFor(page) workers.get(page)
|
||||
function pageFor(view) pages.get(view)
|
||||
function viewFor(page) views.get(page)
|
||||
function isDisposed (page) !views.get(page, false)
|
||||
this.contentURL = 'contentURL' in options ? options.contentURL
|
||||
: 'about:blank';
|
||||
if ('contentScriptWhen' in options)
|
||||
this.contentScriptWhen = options.contentScriptWhen;
|
||||
if ('contentScriptFile' in options)
|
||||
this.contentScriptFile = options.contentScriptFile;
|
||||
if ('contentScriptOptions' in options)
|
||||
this.contentScriptOptions = options.contentScriptOptions;
|
||||
if ('contentScript' in options)
|
||||
this.contentScript = options.contentScript;
|
||||
if ('allow' in options)
|
||||
this.allow = options.allow;
|
||||
if ('onError' in options)
|
||||
this.on('error', options.onError);
|
||||
if ('onMessage' in options)
|
||||
this.on('message', options.onMessage);
|
||||
|
||||
let pageContract = contract(merge({
|
||||
allow: {
|
||||
is: ['object', 'undefined', 'null'],
|
||||
map: function (allow) { return { script: !allow || allow.script !== false }}
|
||||
},
|
||||
onMessage: {
|
||||
is: ['function', 'undefined']
|
||||
},
|
||||
include: {
|
||||
is: ['string', 'array', 'undefined']
|
||||
},
|
||||
contentScriptWhen: {
|
||||
is: ['string', 'undefined']
|
||||
}
|
||||
}, loaderContract.rules));
|
||||
this.on('propertyChange', this._onChange.bind(this));
|
||||
|
||||
function enableScript (page) {
|
||||
getDocShell(viewFor(page)).allowJavascript = true;
|
||||
}
|
||||
|
||||
function disableScript (page) {
|
||||
getDocShell(viewFor(page)).allowJavascript = false;
|
||||
}
|
||||
|
||||
function Allow (page) {
|
||||
return {
|
||||
get script() getDocShell(viewFor(page)).allowJavascript,
|
||||
set script(value) value ? enableScript(page) : disableScript(page)
|
||||
};
|
||||
}
|
||||
|
||||
function injectWorker ({page}) {
|
||||
let worker = workerFor(page);
|
||||
let view = viewFor(page);
|
||||
if (isValidURL(page, view.contentDocument.URL))
|
||||
attach(worker, view.contentWindow);
|
||||
}
|
||||
|
||||
function isValidURL(page, url) !page.rules || page.rules.matchesAny(url)
|
||||
|
||||
const Page = Class({
|
||||
implements: [
|
||||
EventTarget,
|
||||
Disposable
|
||||
],
|
||||
extends: WorkerHost(workerFor),
|
||||
setup: function Page(options) {
|
||||
let page = this;
|
||||
options = pageContract(options);
|
||||
setListeners(this, options);
|
||||
let view = makeFrame(window.document, {
|
||||
nodeName: 'iframe',
|
||||
type: 'content',
|
||||
uri: options.contentURL,
|
||||
allowJavascript: options.allow.script,
|
||||
allowPlugins: true,
|
||||
allowAuth: true
|
||||
});
|
||||
|
||||
['contentScriptFile', 'contentScript', 'contentScriptWhen']
|
||||
.forEach(function (prop) page[prop] = options[prop]);
|
||||
|
||||
views.set(this, view);
|
||||
pages.set(view, this);
|
||||
|
||||
let worker = new Worker(options);
|
||||
workers.set(this, worker);
|
||||
pipe(worker, this);
|
||||
|
||||
if (this.include || options.include) {
|
||||
this.rules = Rules();
|
||||
this.rules.add.apply(this.rules, [].concat(this.include || options.include));
|
||||
this._initSymbiont();
|
||||
},
|
||||
|
||||
_onChange: function _onChange(e) {
|
||||
if ('contentURL' in e && this._frame) {
|
||||
// Cleanup the worker before injecting the content script in the new
|
||||
// document
|
||||
this._workerCleanup();
|
||||
this._initFrame(this._frame);
|
||||
}
|
||||
}
|
||||
},
|
||||
get allow() Allow(this),
|
||||
set allow(value) {
|
||||
let allowJavascript = pageContract({ allow: value }).allow.script;
|
||||
return allowJavascript ? enableScript(this) : disableScript(this);
|
||||
},
|
||||
get contentURL() { return viewFor(this).getAttribute('src'); },
|
||||
set contentURL(value) {
|
||||
if (!isValidURL(this, value)) return;
|
||||
let view = viewFor(this);
|
||||
let contentURL = pageContract({ contentURL: value }).contentURL;
|
||||
view.setAttribute('src', contentURL);
|
||||
},
|
||||
dispose: function () {
|
||||
if (isDisposed(this)) return;
|
||||
let view = viewFor(this);
|
||||
if (view.parentNode) view.parentNode.removeChild(view);
|
||||
views.delete(this);
|
||||
detach(workers.get(this));
|
||||
},
|
||||
toString: function () '[object Page]'
|
||||
});
|
||||
|
||||
exports.Page = Page;
|
||||
|
||||
let pageEvents = streamMerge([events, streamEventsFrom(window)]);
|
||||
let readyEvents = filter(pageEvents, isReadyEvent);
|
||||
let formattedEvents = map(readyEvents, function({target, type}) {
|
||||
return { type: type, page: pageFromDoc(target) };
|
||||
});
|
||||
let pageReadyEvents = filter(formattedEvents, function({page, type}) {
|
||||
return getAttachEventType(page) === type});
|
||||
on(pageReadyEvents, 'data', injectWorker);
|
||||
|
||||
function isReadyEvent ({type}) {
|
||||
return has(readyEventNames, type);
|
||||
}
|
||||
|
||||
/*
|
||||
* Takes a document, finds its doc shell tree root and returns the
|
||||
* matching Page instance if found
|
||||
*/
|
||||
function pageFromDoc(doc) {
|
||||
let parentWindow = getParentWindow(doc.defaultView), page;
|
||||
if (!parentWindow) return;
|
||||
|
||||
let frames = parentWindow.document.getElementsByTagName('iframe');
|
||||
for (let i = frames.length; i--;)
|
||||
if (frames[i].contentDocument === doc && (page = pageFor(frames[i])))
|
||||
return page;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
);
|
||||
exports.Page = function(options) Page(options);
|
||||
exports.Page.prototype = Page.prototype;
|
||||
|
@ -32,7 +32,16 @@ const systemEvents = require("./system/events");
|
||||
const { filter, pipe } = require("./event/utils");
|
||||
const { getNodeView, getActiveView } = require("./view/core");
|
||||
const { isNil, isObject } = require("./lang/type");
|
||||
const { getAttachEventType } = require("./content/utils");
|
||||
|
||||
function getAttachEventType(model) {
|
||||
let when = model.contentScriptWhen;
|
||||
return requiresAddonGlobal(model) ? "sdk-panel-content-changed" :
|
||||
when === "start" ? "sdk-panel-content-changed" :
|
||||
when === "end" ? "sdk-panel-document-loaded" :
|
||||
when === "ready" ? "sdk-panel-content-loaded" :
|
||||
null;
|
||||
}
|
||||
|
||||
|
||||
let number = { is: ['number', 'undefined', 'null'] };
|
||||
let boolean = { is: ['boolean', 'undefined', 'null'] };
|
||||
@ -85,14 +94,14 @@ let setupAutoHide = new function() {
|
||||
// It could be that listener is not GC-ed in the same cycle as
|
||||
// panel in such case we remove listener manually.
|
||||
let view = viewFor(panel);
|
||||
if (!view) systemEvents.off("popupshowing", listener);
|
||||
if (!view) systemEvents.off("sdk-panel-show", listener);
|
||||
else if (subject !== view) panel.hide();
|
||||
}
|
||||
|
||||
// system event listener is intentionally weak this way we'll allow GC
|
||||
// to claim panel if it's no longer referenced by an add-on code. This also
|
||||
// helps minimizing cleanup required on unload.
|
||||
systemEvents.on("popupshowing", listener);
|
||||
systemEvents.on("sdk-panel-show", listener);
|
||||
// To make sure listener is not claimed by GC earlier than necessary we
|
||||
// associate it with `panel` it's associated with. This way it won't be
|
||||
// GC-ed earlier than `panel` itself.
|
||||
@ -225,10 +234,10 @@ exports.Panel = Panel;
|
||||
let panelEvents = filter(events, function({target}) panelFor(target));
|
||||
|
||||
// Panel events emitted after panel has being shown.
|
||||
let shows = filter(panelEvents, function({type}) type === "popupshown");
|
||||
let shows = filter(panelEvents, function({type}) type === "sdk-panel-shown");
|
||||
|
||||
// Panel events emitted after panel became hidden.
|
||||
let hides = filter(panelEvents, function({type}) type === "popuphidden");
|
||||
let hides = filter(panelEvents, function({type}) type === "sdk-panel-hidden");
|
||||
|
||||
// Panel events emitted after content inside panel is ready. For different
|
||||
// panels ready may mean different state based on `contentScriptWhen` attribute.
|
||||
@ -239,7 +248,7 @@ let ready = filter(panelEvents, function({type, target})
|
||||
|
||||
// Panel events emitted after content document in the panel has changed.
|
||||
let change = filter(panelEvents, function({type})
|
||||
type === "document-element-inserted");
|
||||
type === "sdk-panel-content-changed");
|
||||
|
||||
// Forward panel show / hide events to panel's own event listeners.
|
||||
on(shows, "data", function({target}) emit(panelFor(target), "show"));
|
||||
|
@ -19,8 +19,9 @@ let channel = {};
|
||||
function forward({ subject, type, data })
|
||||
emit(channel, "data", { target: subject, type: type, data: data });
|
||||
|
||||
["popupshowing", "popuphiding", "popupshown", "popuphidden",
|
||||
"document-element-inserted", "DOMContentLoaded", "load"
|
||||
["sdk-panel-show", "sdk-panel-hide", "sdk-panel-shown",
|
||||
"sdk-panel-hidden", "sdk-panel-content-changed", "sdk-panel-content-loaded",
|
||||
"sdk-panel-document-loaded"
|
||||
].forEach(function(type) events.on(type, forward));
|
||||
|
||||
exports.events = channel;
|
||||
|
@ -205,6 +205,16 @@ function setupPanelFrame(frame) {
|
||||
}
|
||||
}
|
||||
|
||||
let EVENT_NAMES = {
|
||||
"popupshowing": "sdk-panel-show",
|
||||
"popuphiding": "sdk-panel-hide",
|
||||
"popupshown": "sdk-panel-shown",
|
||||
"popuphidden": "sdk-panel-hidden",
|
||||
"document-element-inserted": "sdk-panel-content-changed",
|
||||
"DOMContentLoaded": "sdk-panel-content-loaded",
|
||||
"load": "sdk-panel-document-loaded"
|
||||
};
|
||||
|
||||
function make(document) {
|
||||
document = document || getMostRecentBrowserWindow().document;
|
||||
let panel = document.createElementNS(XUL_NS, "panel");
|
||||
@ -239,29 +249,29 @@ function make(document) {
|
||||
function onDisplayChange({type}) {
|
||||
try { swapFrameLoaders(backgroundFrame, viewFrame); }
|
||||
catch(error) { console.exception(error); }
|
||||
events.emit(type, { subject: panel });
|
||||
events.emit(EVENT_NAMES[type], { subject: panel });
|
||||
}
|
||||
|
||||
function onContentReady({target, type}) {
|
||||
if (target === getContentDocument(panel)) {
|
||||
style(panel);
|
||||
events.emit(type, { subject: panel });
|
||||
events.emit(EVENT_NAMES[type], { subject: panel });
|
||||
}
|
||||
}
|
||||
|
||||
function onContentLoad({target, type}) {
|
||||
if (target === getContentDocument(panel))
|
||||
events.emit(type, { subject: panel });
|
||||
events.emit(EVENT_NAMES[type], { subject: panel });
|
||||
}
|
||||
|
||||
function onContentChange({subject, type}) {
|
||||
let document = subject;
|
||||
if (document === getContentDocument(panel) && document.defaultView)
|
||||
events.emit(type, { subject: panel });
|
||||
events.emit(EVENT_NAMES[type], { subject: panel });
|
||||
}
|
||||
|
||||
function onPanelStateChange({type}) {
|
||||
events.emit(type, { subject: panel })
|
||||
events.emit(EVENT_NAMES[type], { subject: panel })
|
||||
}
|
||||
|
||||
panel.addEventListener("popupshowing", onDisplayChange, false);
|
||||
|
@ -1,64 +0,0 @@
|
||||
/* 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';
|
||||
|
||||
module.metadata = {
|
||||
'stability': 'unstable'
|
||||
};
|
||||
|
||||
const { Class } = require('../core/heritage');
|
||||
const { method } = require('../lang/functional');
|
||||
const { defer, promised, all } = require('../core/promise');
|
||||
|
||||
/*
|
||||
* TreeNodes are used to construct dependency trees
|
||||
* for BookmarkItems
|
||||
*/
|
||||
let TreeNode = Class({
|
||||
initialize: function (value) {
|
||||
this.value = value;
|
||||
this.children = [];
|
||||
},
|
||||
add: function (values) {
|
||||
[].concat(values).forEach(value => {
|
||||
this.children.push(value instanceof TreeNode ? value : TreeNode(value));
|
||||
});
|
||||
},
|
||||
get length () {
|
||||
let count = 0;
|
||||
this.walk(() => count++);
|
||||
// Do not count the current node
|
||||
return --count;
|
||||
},
|
||||
get: method(get),
|
||||
walk: method(walk),
|
||||
toString: function () '[object TreeNode]'
|
||||
});
|
||||
exports.TreeNode = TreeNode;
|
||||
|
||||
/*
|
||||
* Descends down from `node` applying `fn` to each in order.
|
||||
* Can be asynchronous if `fn` returns a promise. `fn` is passed
|
||||
* one argument, the current node, `curr`
|
||||
*/
|
||||
function walk (curr, fn) {
|
||||
return promised(fn)(curr).then(val => {
|
||||
return all(curr.children.map(child => walk(child, fn)));
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Descends from the TreeNode `node`, returning
|
||||
* the node with value `value` if found or `null`
|
||||
* otherwise
|
||||
*/
|
||||
function get (node, value) {
|
||||
if (node.value === value) return node;
|
||||
for (let child of node.children) {
|
||||
let found = get(child, value);
|
||||
if (found) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
@ -7,7 +7,7 @@ module.metadata = {
|
||||
"stability": "stable"
|
||||
};
|
||||
|
||||
const { setMode, getMode, on: onStateChange, isPermanentPrivateBrowsing } = require('./private-browsing/utils');
|
||||
const { setMode, getMode, on: onStateChange } = require('./private-browsing/utils');
|
||||
const { isWindowPrivate } = require('./window/utils');
|
||||
const { emit, on, once, off } = require('./event/core');
|
||||
const { when: unload } = require('./system/unload');
|
||||
@ -65,10 +65,6 @@ exports.isPrivate = function(thing) {
|
||||
return isWindowPrivate(window);
|
||||
}
|
||||
|
||||
// check if the post pwpb, global pb service is enabled.
|
||||
if (isPermanentPrivateBrowsing())
|
||||
return true;
|
||||
|
||||
// if we get here, and global private browsing
|
||||
// is available, and it is true, then return
|
||||
// true otherwise false is returned here
|
||||
|
@ -52,10 +52,6 @@ let isWindowPBSupported = exports.isWindowPBSupported =
|
||||
// checks that per-tab private browsing is implemented
|
||||
let isTabPBSupported = exports.isTabPBSupported =
|
||||
!pbService && !!PrivateBrowsingUtils && is('Fennec') && satisfiesVersion(version, '>=20.0*');
|
||||
|
||||
exports.isPermanentPrivateBrowsing = function() {
|
||||
return !!(PrivateBrowsingUtils && PrivateBrowsingUtils.permanentPrivateBrowsing);
|
||||
}
|
||||
|
||||
function ignoreWindow(window) {
|
||||
return !isPrivateBrowsingSupported && isWindowPrivate(window) && !isGlobalPBSupported;
|
||||
|
@ -85,7 +85,7 @@ exports.stdout = new function() {
|
||||
/**
|
||||
* Returns a path of the system's or application's special directory / file
|
||||
* associated with a given `id`. For list of possible `id`s please see:
|
||||
* https://developer.mozilla.org/en-US/docs/Code_snippets/File_I_O#Getting_files_in_special_directories
|
||||
* https://developer.mozilla.org/en/Code_snippets/File_I%2F%2FO#Getting_special_files
|
||||
* http://mxr.mozilla.org/mozilla-central/source/xpcom/io/nsAppDirectoryServiceDefs.h
|
||||
* @example
|
||||
*
|
||||
|
@ -62,16 +62,8 @@ const Tab = Class({
|
||||
* @type {String}
|
||||
*/
|
||||
get favicon() {
|
||||
/*
|
||||
* Synchronous favicon services were never supported on Fennec,
|
||||
* and as of FF22, are now deprecated. When/if favicon services
|
||||
* are supported for Fennec, this getter should reference
|
||||
* `require('sdk/places/favicon').getFavicon`
|
||||
*/
|
||||
console.error(
|
||||
'tab.favicon is deprecated, and currently ' +
|
||||
'favicon helpers are not yet supported by Fennec'
|
||||
);
|
||||
// TODO: provide the real favicon when it is available
|
||||
console.error(ERR_FENNEC_MSG);
|
||||
|
||||
// return 16x16 blank default
|
||||
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAEklEQVQ4jWNgGAWjYBSMAggAAAQQAAF/TXiOAAAAAElFTkSuQmCC';
|
||||
|
@ -12,8 +12,6 @@ module.metadata = {
|
||||
const BaseAssert = require("sdk/test/assert").Assert;
|
||||
const { isFunction, isObject } = require("sdk/lang/type");
|
||||
|
||||
exports.Assert = BaseAssert;
|
||||
|
||||
function extend(target) {
|
||||
let descriptor = {}
|
||||
Array.slice(arguments, 1).forEach(function(source) {
|
||||
|
@ -101,6 +101,7 @@ function fromIterator(iterator) {
|
||||
}
|
||||
exports.fromIterator = fromIterator;
|
||||
|
||||
|
||||
function find(array, predicate) {
|
||||
var index = 0;
|
||||
var count = array.length;
|
||||
|
@ -1,122 +0,0 @@
|
||||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* 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";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "unstable"
|
||||
};
|
||||
|
||||
const { URL } = require('../url');
|
||||
const cache = {};
|
||||
|
||||
function MatchPattern(pattern) {
|
||||
if (cache[pattern]) return cache[pattern];
|
||||
|
||||
if (typeof pattern.test == "function") {
|
||||
|
||||
// For compatibility with -moz-document rules, we require the RegExp's
|
||||
// global, ignoreCase, and multiline flags to be set to false.
|
||||
if (pattern.global) {
|
||||
throw new Error("A RegExp match pattern cannot be set to `global` " +
|
||||
"(i.e. //g).");
|
||||
}
|
||||
if (pattern.ignoreCase) {
|
||||
throw new Error("A RegExp match pattern cannot be set to `ignoreCase` " +
|
||||
"(i.e. //i).");
|
||||
}
|
||||
if (pattern.multiline) {
|
||||
throw new Error("A RegExp match pattern cannot be set to `multiline` " +
|
||||
"(i.e. //m).");
|
||||
}
|
||||
|
||||
this.regexp = pattern;
|
||||
}
|
||||
else {
|
||||
let firstWildcardPosition = pattern.indexOf("*");
|
||||
let lastWildcardPosition = pattern.lastIndexOf("*");
|
||||
if (firstWildcardPosition != lastWildcardPosition)
|
||||
throw new Error("There can be at most one '*' character in a wildcard.");
|
||||
|
||||
if (firstWildcardPosition == 0) {
|
||||
if (pattern.length == 1)
|
||||
this.anyWebPage = true;
|
||||
else if (pattern[1] != ".")
|
||||
throw new Error("Expected a *.<domain name> string, got: " + pattern);
|
||||
else
|
||||
this.domain = pattern.substr(2);
|
||||
}
|
||||
else {
|
||||
if (pattern.indexOf(":") == -1) {
|
||||
throw new Error("When not using *.example.org wildcard, the string " +
|
||||
"supplied is expected to be either an exact URL to " +
|
||||
"match or a URL prefix. The provided string ('" +
|
||||
pattern + "') is unlikely to match any pages.");
|
||||
}
|
||||
|
||||
if (firstWildcardPosition == -1)
|
||||
this.exactURL = pattern;
|
||||
else if (firstWildcardPosition == pattern.length - 1)
|
||||
this.urlPrefix = pattern.substr(0, pattern.length - 1);
|
||||
else {
|
||||
throw new Error("The provided wildcard ('" + pattern + "') has a '*' " +
|
||||
"in an unexpected position. It is expected to be the " +
|
||||
"first or the last character in the wildcard.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache[pattern] = this;
|
||||
}
|
||||
|
||||
MatchPattern.prototype = {
|
||||
|
||||
test: function MatchPattern_test(urlStr) {
|
||||
try {
|
||||
var url = URL(urlStr);
|
||||
}
|
||||
catch (err) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Test the URL against a RegExp pattern. For compatibility with
|
||||
// -moz-document rules, we require the RegExp to match the entire URL,
|
||||
// so we not only test for a match, we also make sure the matched string
|
||||
// is the entire URL string.
|
||||
//
|
||||
// Assuming most URLs don't match most match patterns, we call `test` for
|
||||
// speed when determining whether or not the URL matches, then call `exec`
|
||||
// for the small subset that match to make sure the entire URL matches.
|
||||
//
|
||||
if (this.regexp && this.regexp.test(urlStr) &&
|
||||
this.regexp.exec(urlStr)[0] == urlStr)
|
||||
return true;
|
||||
|
||||
if (this.anyWebPage && /^(https?|ftp)$/.test(url.scheme))
|
||||
return true;
|
||||
if (this.exactURL && this.exactURL == urlStr)
|
||||
return true;
|
||||
|
||||
// Tests the urlStr against domain and check if
|
||||
// wildcard submitted (*.domain.com), it only allows
|
||||
// subdomains (sub.domain.com) or from the root (http://domain.com)
|
||||
// and reject non-matching domains (otherdomain.com)
|
||||
// bug 856913
|
||||
if (this.domain && url.host &&
|
||||
(url.host === this.domain ||
|
||||
url.host.slice(-this.domain.length - 1) === "." + this.domain))
|
||||
return true;
|
||||
if (this.urlPrefix && 0 == urlStr.indexOf(this.urlPrefix))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
toString: function () '[object MatchPattern]'
|
||||
|
||||
};
|
||||
|
||||
exports.MatchPattern = MatchPattern;
|
@ -54,10 +54,4 @@ function extend(source) {
|
||||
}
|
||||
exports.extend = extend;
|
||||
|
||||
function has(obj, key) obj.hasOwnProperty(key);
|
||||
exports.has = has;
|
||||
|
||||
function each(obj, fn) {
|
||||
for (let key in obj) has(obj, key) && fn(obj[key], key, obj);
|
||||
}
|
||||
exports.each = each;
|
||||
|
@ -1,52 +0,0 @@
|
||||
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* 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";
|
||||
|
||||
module.metadata = {
|
||||
"stability": "unstable"
|
||||
};
|
||||
|
||||
const { Class } = require('../core/heritage');
|
||||
const { MatchPattern } = require('./match-pattern');
|
||||
const { on, off, emit } = require('../event/core');
|
||||
const { method } = require('../lang/functional');
|
||||
const objectUtil = require('./object');
|
||||
const { EventTarget } = require('../event/target');
|
||||
const { List, addListItem, removeListItem } = require('./list');
|
||||
|
||||
// Should deprecate usage of EventEmitter/compose
|
||||
const Rules = Class({
|
||||
implements: [
|
||||
EventTarget,
|
||||
List
|
||||
],
|
||||
add: function(...rules) [].concat(rules).forEach(function onAdd(rule) {
|
||||
addListItem(this, rule);
|
||||
emit(this, 'add', rule);
|
||||
}, this),
|
||||
remove: function(...rules) [].concat(rules).forEach(function onRemove(rule) {
|
||||
removeListItem(this, rule);
|
||||
emit(this, 'remove', rule);
|
||||
}, this),
|
||||
get: function(rule) {
|
||||
let found = false;
|
||||
for (let i in this) if (this[i] === rule) found = true;
|
||||
return found;
|
||||
},
|
||||
// Returns true if uri matches atleast one stored rule
|
||||
matchesAny: function(uri) !!filterMatches(this, uri).length,
|
||||
toString: function() '[object Rules]'
|
||||
});
|
||||
exports.Rules = Rules;
|
||||
|
||||
function filterMatches(instance, uri) {
|
||||
let matches = [];
|
||||
for (let i in instance) {
|
||||
if (new MatchPattern(instance[i]).test(uri)) matches.push(instance[i]);
|
||||
}
|
||||
return matches;
|
||||
}
|
@ -604,42 +604,6 @@ BrowserWindow.prototype = {
|
||||
let palette = toolbox.palette;
|
||||
palette.appendChild(node);
|
||||
|
||||
if (this.window.CustomizableUI) {
|
||||
let placement = this.window.CustomizableUI.getPlacementOfWidget(node.id);
|
||||
if (!placement) {
|
||||
placement = {area: 'nav-bar', position: undefined};
|
||||
}
|
||||
this.window.CustomizableUI.addWidgetToArea(node.id, placement.area, placement.position);
|
||||
|
||||
// Depending on when this gets called, we might be in the right place now. In that case,
|
||||
// don't run the following code.
|
||||
if (node.parentNode != palette) {
|
||||
return;
|
||||
}
|
||||
// Otherwise, insert:
|
||||
let container = this.doc.getElementById(placement.area);
|
||||
if (container.customizationTarget) {
|
||||
container = container.customizationTarget;
|
||||
}
|
||||
|
||||
if (placement.position !== undefined) {
|
||||
// Find a position:
|
||||
let items = this.window.CustomizableUI.getWidgetIdsInArea(placement.area);
|
||||
let itemIndex = placement.position;
|
||||
for (let l = items.length; itemIndex < l; itemIndex++) {
|
||||
let realItems = container.getElementsByAttribute("id", items[itemIndex]);
|
||||
if (realItems[0]) {
|
||||
container.insertBefore(node, realItems[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node.parentNode != container) {
|
||||
container.appendChild(node);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Search for widget toolbar by reading toolbar's currentset attribute
|
||||
let container = null;
|
||||
let toolbars = this.doc.getElementsByTagName("toolbar");
|
||||
|
@ -389,16 +389,3 @@ function getOwnerBrowserWindow(node) {
|
||||
});
|
||||
}
|
||||
exports.getOwnerBrowserWindow = getOwnerBrowserWindow;
|
||||
|
||||
function getParentWindow(window) {
|
||||
try {
|
||||
return window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShellTreeItem).parent
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindow);
|
||||
}
|
||||
catch (e) {}
|
||||
return null;
|
||||
}
|
||||
exports.getParentWindow = getParentWindow;
|
||||
|
@ -33,7 +33,7 @@
|
||||
"url": "sdk/url",
|
||||
"traceback": "sdk/console/traceback",
|
||||
"xhr": "sdk/net/xhr",
|
||||
"match-pattern": "sdk/util/match-pattern",
|
||||
"match-pattern": "sdk/page-mod/match-pattern",
|
||||
"tab-browser": "sdk/deprecated/tab-browser",
|
||||
"file": "sdk/io/file",
|
||||
"runtime": "sdk/system/runtime",
|
||||
|
@ -4,7 +4,8 @@
|
||||
|
||||
|
||||
This document describes the structure of the HTML generated by the renderapi.py
|
||||
tool. The particular HTML id and class attributes embedded in the files,
|
||||
tool, both for use in the API docs shown by "cfx docs" and as exported by
|
||||
"cfx sdocs". The particular HTML id and class attributes embedded in the files,
|
||||
as well as their organization, represent the interface between the tool and any
|
||||
front-end code wanting to style the docs in some particular way.
|
||||
|
||||
|
@ -60,7 +60,7 @@ def welcome():
|
||||
print ("Your SDK may not work properly.")
|
||||
return
|
||||
|
||||
print ("Welcome to the Add-on SDK. For the docs, visit https://addons.mozilla.org/en-US/developers/docs/sdk/latest/")
|
||||
print ("Welcome to the Add-on SDK. Run 'cfx docs' for assistance.")
|
||||
|
||||
if __name__ == '__main__':
|
||||
welcome()
|
||||
|
@ -3,9 +3,6 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { LoaderWithHookedConsole } = require('sdk/test/loader');
|
||||
const { loader } = LoaderWithHookedConsole(module);
|
||||
const app = require("sdk/system/xul-app");
|
||||
|
||||
// This test makes sure that require statements used by all AMO hosted
|
||||
@ -107,7 +104,7 @@ exports["test compatibility"] = function(assert) {
|
||||
require("sdk/deprecated/events"), "sdk/deprecated/events -> events");
|
||||
|
||||
assert.equal(require("match-pattern"),
|
||||
require("sdk/util/match-pattern"), "sdk/util/match-pattern -> match-pattern");
|
||||
require("sdk/page-mod/match-pattern"), "sdk/page-mod/match-pattern -> match-pattern");
|
||||
|
||||
if (app.is("Firefox")) {
|
||||
assert.equal(require("tab-browser"),
|
||||
@ -144,8 +141,8 @@ exports["test compatibility"] = function(assert) {
|
||||
assert.equal(require("querystring"),
|
||||
require("sdk/querystring"), "sdk/querystring -> querystring");
|
||||
|
||||
assert.equal(loader.require("addon-page"),
|
||||
loader.require("sdk/addon-page"), "sdk/addon-page -> addon-page");
|
||||
assert.equal(require("addon-page"),
|
||||
require("sdk/addon-page"), "sdk/addon-page -> addon-page");
|
||||
|
||||
assert.equal(require("tabs/utils"),
|
||||
require("sdk/tabs/utils"), "sdk/tabs/utils -> tabs/utils");
|
||||
|
@ -113,9 +113,7 @@ exports.testTabProperties = function(test) {
|
||||
// TODO: remove need for this test by implementing the favicon feature
|
||||
// Poors man deepEqual with JSON.stringify...
|
||||
test.assertEqual(JSON.stringify(messages),
|
||||
JSON.stringify(['tab.favicon is deprecated, and ' +
|
||||
'currently favicon helpers are not yet supported ' +
|
||||
'by Fennec']),
|
||||
JSON.stringify([ERR_FENNEC_MSG]),
|
||||
"favicon logs an error for now");
|
||||
test.assertEqual(tab.style, null, "style of the new tab matches");
|
||||
test.assertEqual(tab.index, tabsLen, "index of the new tab matches");
|
||||
@ -578,6 +576,29 @@ exports.testPerTabEvents = function(test) {
|
||||
});
|
||||
};
|
||||
|
||||
// TEST: tabs.activeTab getter
|
||||
exports.testActiveTab_getter_alt = function(test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
let url = URL.replace("#title#", "foo");
|
||||
tabs.open({
|
||||
url: url,
|
||||
onActivate: function(tab) {
|
||||
test.assertEqual(tabs.activeTab.url, tab.url, 'the active tab is correct');
|
||||
|
||||
tab.once('ready', function() {
|
||||
test.assertEqual(tab.url, url);
|
||||
test.assertEqual(tab.title, "foo");
|
||||
|
||||
tab.close(function() {
|
||||
// end test
|
||||
test.done();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.testUniqueTabIds = function(test) {
|
||||
test.waitUntilDone();
|
||||
var tabs = require('sdk/tabs');
|
||||
|
@ -6,37 +6,60 @@
|
||||
const { Cc, Ci } = require('chrome');
|
||||
const { Loader } = require('sdk/test/loader');
|
||||
const timer = require('sdk/timers');
|
||||
const { getOwnerWindow } = require('sdk/private-browsing/window/utils');
|
||||
const { windows, onFocus, getMostRecentBrowserWindow } = require('sdk/window/utils');
|
||||
const { open, focus } = require('sdk/window/helpers');
|
||||
const { StringBundle } = require('sdk/deprecated/app-strings');
|
||||
const tabs = require('sdk/tabs');
|
||||
|
||||
const base64png = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYA" +
|
||||
"AABzenr0AAAASUlEQVRYhe3O0QkAIAwD0eyqe3Q993AQ3cBSUKpygfsNTy" +
|
||||
"N5ugbQpK0BAADgP0BRDWXWlwEAAAAAgPsA3rzDaAAAAHgPcGrpgAnzQ2FG" +
|
||||
"bWRR9AAAAABJRU5ErkJggg%3D%3D";
|
||||
|
||||
// TEST: tabs.activeTab getter
|
||||
exports.testActiveTab_getter = function(test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require("sdk/tabs");
|
||||
|
||||
let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head></html>";
|
||||
require("sdk/deprecated/tab-browser").addTab(
|
||||
url,
|
||||
{
|
||||
onLoad: function(e) {
|
||||
test.assert(tabs.activeTab);
|
||||
test.assertEqual(tabs.activeTab.url, url);
|
||||
test.assertEqual(tabs.activeTab.title, "foo");
|
||||
closeBrowserWindow(window, function() test.done());
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// Bug 682681 - tab.title should never be empty
|
||||
exports.testBug682681_aboutURI = function(test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
let tabStrings = StringBundle('chrome://browser/locale/tabbrowser.properties');
|
||||
|
||||
tabs.on('ready', function onReady(tab) {
|
||||
tabs.removeListener('ready', onReady);
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require("sdk/tabs");
|
||||
|
||||
test.assertEqual(tab.title,
|
||||
tabStrings.get('tabs.emptyTabTitle'),
|
||||
"title of about: tab is not blank");
|
||||
tabs.on('ready', function onReady(tab) {
|
||||
tabs.removeListener('ready', onReady);
|
||||
|
||||
tab.close(function() test.done());
|
||||
});
|
||||
test.assertEqual(tab.title,
|
||||
tabStrings.get('tabs.emptyTabTitle'),
|
||||
"title of about: tab is not blank");
|
||||
|
||||
// open a about: url
|
||||
tabs.open({
|
||||
url: "about:blank",
|
||||
inBackground: true
|
||||
// end of test
|
||||
closeBrowserWindow(window, function() test.done());
|
||||
});
|
||||
|
||||
// open a about: url
|
||||
tabs.open({
|
||||
url: "about:blank",
|
||||
inBackground: true
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -44,13 +67,23 @@ exports.testBug682681_aboutURI = function(test) {
|
||||
exports.testTitleForDataURI = function(test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
tabs.open({
|
||||
url: "data:text/html;charset=utf-8,<title>tab</title>",
|
||||
inBackground: true,
|
||||
onReady: function(tab) {
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require("sdk/tabs");
|
||||
|
||||
tabs.on('ready', function onReady(tab) {
|
||||
tabs.removeListener('ready', onReady);
|
||||
|
||||
test.assertEqual(tab.title, "tab", "data: title is not Connecting...");
|
||||
tab.close(function() test.done());
|
||||
}
|
||||
|
||||
// end of test
|
||||
closeBrowserWindow(window, function() test.done());
|
||||
});
|
||||
|
||||
// open a about: url
|
||||
tabs.open({
|
||||
url: "data:text/html;charset=utf-8,<title>tab</title>",
|
||||
inBackground: true
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -60,6 +93,8 @@ exports.testBrowserWindowCreationOnActivate = function(test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
let windows = require("sdk/windows").browserWindows;
|
||||
let tabs = require("sdk/tabs");
|
||||
|
||||
let gotActivate = false;
|
||||
|
||||
tabs.once('activate', function onActivate(eventTab) {
|
||||
@ -67,80 +102,114 @@ exports.testBrowserWindowCreationOnActivate = function(test) {
|
||||
gotActivate = true;
|
||||
});
|
||||
|
||||
open().then(function(window) {
|
||||
openBrowserWindow(function(window, browser) {
|
||||
test.assert(gotActivate, "Received activate event before openBrowserWindow's callback is called");
|
||||
closeBrowserWindow(window, function () test.done());
|
||||
});
|
||||
}
|
||||
|
||||
// TEST: tab.activate()
|
||||
exports.testActiveTab_setter = function(test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require("sdk/tabs");
|
||||
let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head></html>";
|
||||
|
||||
tabs.on('ready', function onReady(tab) {
|
||||
tabs.removeListener('ready', onReady);
|
||||
test.assertEqual(tabs.activeTab.url, "about:blank", "activeTab url has not changed");
|
||||
test.assertEqual(tab.url, url, "url of new background tab matches");
|
||||
tabs.on('activate', function onActivate(eventTab) {
|
||||
tabs.removeListener('activate', onActivate);
|
||||
test.assertEqual(tabs.activeTab.url, url, "url after activeTab setter matches");
|
||||
test.assertEqual(eventTab, tab, "event argument is the activated tab");
|
||||
test.assertEqual(eventTab, tabs.activeTab, "the tab is the active one");
|
||||
closeBrowserWindow(window, function() test.done());
|
||||
});
|
||||
tab.activate();
|
||||
})
|
||||
|
||||
tabs.open({
|
||||
url: url,
|
||||
inBackground: true
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// TEST: tab unloader
|
||||
exports.testAutomaticDestroy = function(test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
// Create a second tab instance that we will destroy
|
||||
let called = false;
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require("sdk/tabs");
|
||||
|
||||
let loader = Loader(module);
|
||||
let tabs2 = loader.require("sdk/tabs");
|
||||
tabs2.on('open', function onOpen(tab) {
|
||||
called = true;
|
||||
// Create a second tab instance that we will destroy
|
||||
let called = false;
|
||||
|
||||
let loader = Loader(module);
|
||||
let tabs2 = loader.require("sdk/tabs");
|
||||
tabs2.on('open', function onOpen(tab) {
|
||||
called = true;
|
||||
});
|
||||
|
||||
loader.unload();
|
||||
|
||||
// Fire a tab event and ensure that the destroyed tab is inactive
|
||||
tabs.once('open', function () {
|
||||
timer.setTimeout(function () {
|
||||
test.assert(!called, "Unloaded tab module is destroyed and inactive");
|
||||
closeBrowserWindow(window, function() test.done());
|
||||
}, 0);
|
||||
});
|
||||
|
||||
tabs.open("data:text/html;charset=utf-8,foo");
|
||||
});
|
||||
|
||||
loader.unload();
|
||||
|
||||
// Fire a tab event and ensure that the destroyed tab is inactive
|
||||
tabs.once('open', function (tab) {
|
||||
timer.setTimeout(function () {
|
||||
test.assert(!called, "Unloaded tab module is destroyed and inactive");
|
||||
tab.close(test.done.bind(test));
|
||||
}, 0);
|
||||
});
|
||||
|
||||
tabs.open("data:text/html;charset=utf-8,foo");
|
||||
};
|
||||
|
||||
// test tab properties
|
||||
exports.testTabProperties = function(test) {
|
||||
test.waitUntilDone();
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require('sdk/tabs');
|
||||
let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head><body>foo</body></html>";
|
||||
tabs.open({
|
||||
url: url,
|
||||
onReady: function(tab) {
|
||||
test.assertEqual(tab.title, "foo", "title of the new tab matches");
|
||||
test.assertEqual(tab.url, url, "URL of the new tab matches");
|
||||
test.assert(tab.favicon, "favicon of the new tab is not empty");
|
||||
test.assertEqual(tab.style, null, "style of the new tab matches");
|
||||
test.assertEqual(tab.index, 1, "index of the new tab matches");
|
||||
test.assertNotEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches");
|
||||
test.assertNotEqual(tab.id, null, "a tab object always has an id property.");
|
||||
onReadyOrLoad(window);
|
||||
},
|
||||
onLoad: function(tab) {
|
||||
test.assertEqual(tab.title, "foo", "title of the new tab matches");
|
||||
test.assertEqual(tab.url, url, "URL of the new tab matches");
|
||||
test.assert(tab.favicon, "favicon of the new tab is not empty");
|
||||
test.assertEqual(tab.style, null, "style of the new tab matches");
|
||||
test.assertEqual(tab.index, 1, "index of the new tab matches");
|
||||
test.assertNotEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches");
|
||||
test.assertNotEqual(tab.id, null, "a tab object always has an id property.");
|
||||
onReadyOrLoad(window);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let count = 0;
|
||||
function onReadyOrLoad (tab) {
|
||||
if (count++) {
|
||||
tab.close(test.done.bind(test));
|
||||
}
|
||||
function onReadyOrLoad (window) {
|
||||
if (count++)
|
||||
closeBrowserWindow(window, function() test.done());
|
||||
}
|
||||
|
||||
let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head><body>foo</body></html>";
|
||||
tabs.open({
|
||||
url: url,
|
||||
onReady: function(tab) {
|
||||
test.assertEqual(tab.title, "foo", "title of the new tab matches");
|
||||
test.assertEqual(tab.url, url, "URL of the new tab matches");
|
||||
test.assert(tab.favicon, "favicon of the new tab is not empty");
|
||||
test.assertEqual(tab.style, null, "style of the new tab matches");
|
||||
test.assertEqual(tab.index, 1, "index of the new tab matches");
|
||||
test.assertNotEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches");
|
||||
test.assertNotEqual(tab.id, null, "a tab object always has an id property.");
|
||||
onReadyOrLoad(tab);
|
||||
},
|
||||
onLoad: function(tab) {
|
||||
test.assertEqual(tab.title, "foo", "title of the new tab matches");
|
||||
test.assertEqual(tab.url, url, "URL of the new tab matches");
|
||||
test.assert(tab.favicon, "favicon of the new tab is not empty");
|
||||
test.assertEqual(tab.style, null, "style of the new tab matches");
|
||||
test.assertEqual(tab.index, 1, "index of the new tab matches");
|
||||
test.assertNotEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches");
|
||||
test.assertNotEqual(tab.id, null, "a tab object always has an id property.");
|
||||
onReadyOrLoad(tab);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// TEST: tab properties
|
||||
exports.testTabContentTypeAndReload = function(test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
open().then(focus).then(function(window) {
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require("sdk/tabs");
|
||||
let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head><body>foo</body></html>";
|
||||
let urlXML = "data:text/xml;charset=utf-8,<foo>bar</foo>";
|
||||
tabs.open({
|
||||
@ -161,13 +230,12 @@ exports.testTabContentTypeAndReload = function(test) {
|
||||
// TEST: tabs iterator and length property
|
||||
exports.testTabsIteratorAndLength = function(test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
open(null, { features: { chrome: true, toolbar: true } }).then(focus).then(function(window) {
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require("sdk/tabs");
|
||||
let startCount = 0;
|
||||
for each (let t in tabs) startCount++;
|
||||
test.assertEqual(startCount, tabs.length, "length property is correct");
|
||||
let url = "data:text/html;charset=utf-8,default";
|
||||
|
||||
tabs.open(url);
|
||||
tabs.open(url);
|
||||
tabs.open({
|
||||
@ -177,7 +245,6 @@ exports.testTabsIteratorAndLength = function(test) {
|
||||
for each (let t in tabs) count++;
|
||||
test.assertEqual(count, startCount + 3, "iterated tab count matches");
|
||||
test.assertEqual(startCount + 3, tabs.length, "iterated tab count matches length property");
|
||||
|
||||
closeBrowserWindow(window, function() test.done());
|
||||
}
|
||||
});
|
||||
@ -187,8 +254,8 @@ exports.testTabsIteratorAndLength = function(test) {
|
||||
// TEST: tab.url setter
|
||||
exports.testTabLocation = function(test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
open().then(focus).then(function(window) {
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require("sdk/tabs");
|
||||
let url1 = "data:text/html;charset=utf-8,foo";
|
||||
let url2 = "data:text/html;charset=utf-8,bar";
|
||||
|
||||
@ -212,39 +279,45 @@ exports.testTabLocation = function(test) {
|
||||
// TEST: tab.close()
|
||||
exports.testTabClose = function(test) {
|
||||
test.waitUntilDone();
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require("sdk/tabs");
|
||||
let url = "data:text/html;charset=utf-8,foo";
|
||||
|
||||
let url = "data:text/html;charset=utf-8,foo";
|
||||
|
||||
test.assertNotEqual(tabs.activeTab.url, url, "tab is not the active tab");
|
||||
tabs.on('ready', function onReady(tab) {
|
||||
tabs.removeListener('ready', onReady);
|
||||
test.assertEqual(tabs.activeTab.url, tab.url, "tab is now the active tab");
|
||||
let secondOnCloseCalled = false;
|
||||
|
||||
// Bug 699450: Multiple calls to tab.close should not throw
|
||||
tab.close(function() secondOnCloseCalled = true);
|
||||
try {
|
||||
tab.close(function () {
|
||||
test.assert(secondOnCloseCalled,
|
||||
"The immediate second call to tab.close gots its callback fired");
|
||||
test.assertNotEqual(tabs.activeTab.url, url, "tab is no longer the active tab");
|
||||
test.done();
|
||||
test.assertNotEqual(tabs.activeTab.url, url, "tab is not the active tab");
|
||||
tabs.on('ready', function onReady(tab) {
|
||||
tabs.removeListener('ready', onReady);
|
||||
test.assertEqual(tabs.activeTab.url, tab.url, "tab is now the active tab");
|
||||
let secondOnCloseCalled = false;
|
||||
tab.close(function() {
|
||||
closeBrowserWindow(window, function() {
|
||||
test.assert(secondOnCloseCalled,
|
||||
"The immediate second call to tab.close gots its callback fired");
|
||||
test.assertNotEqual(tabs.activeTab.url, url, "tab is no longer the active tab");
|
||||
test.done()
|
||||
});
|
||||
});
|
||||
}
|
||||
catch(e) {
|
||||
test.fail("second call to tab.close() thrown an exception: " + e);
|
||||
}
|
||||
test.assertNotEqual(tabs.activeTab.url, url, "tab is no longer the active tab");
|
||||
});
|
||||
|
||||
tabs.open(url);
|
||||
// Bug 699450: Multiple calls to tab should not throw
|
||||
try {
|
||||
tab.close(function () {
|
||||
secondOnCloseCalled = true;
|
||||
});
|
||||
}
|
||||
catch(e) {
|
||||
test.fail("second call to tab.close() thrown an exception: " + e);
|
||||
}
|
||||
test.assertNotEqual(tabs.activeTab.url, url, "tab is no longer the active tab");
|
||||
});
|
||||
|
||||
tabs.open(url);
|
||||
});
|
||||
};
|
||||
|
||||
// TEST: tab.move()
|
||||
exports.testTabMove = function(test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
open().then(function(window) {
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require("sdk/tabs");
|
||||
let url = "data:text/html;charset=utf-8,foo";
|
||||
|
||||
tabs.open({
|
||||
@ -262,124 +335,168 @@ exports.testTabMove = function(test) {
|
||||
// TEST: open tab with default options
|
||||
exports.testOpen = function(test) {
|
||||
test.waitUntilDone();
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require("sdk/tabs");
|
||||
let url = "data:text/html;charset=utf-8,default";
|
||||
tabs.open({
|
||||
url: url,
|
||||
onReady: function(tab) {
|
||||
test.assertEqual(tab.url, url, "URL of the new tab matches");
|
||||
test.assertEqual(window.content.location, url, "URL of active tab in the current window matches");
|
||||
test.assertEqual(tab.isPinned, false, "The new tab is not pinned");
|
||||
|
||||
let url = "data:text/html;charset=utf-8,default";
|
||||
tabs.open({
|
||||
url: url,
|
||||
onReady: function(tab) {
|
||||
test.assertEqual(tab.url, url, "URL of the new tab matches");
|
||||
test.assertEqual(tab.isPinned, false, "The new tab is not pinned");
|
||||
|
||||
tab.close(function() test.done());
|
||||
}
|
||||
closeBrowserWindow(window, function() test.done());
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// TEST: opening a pinned tab
|
||||
// TEST: open pinned tab
|
||||
exports.testOpenPinned = function(test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
let url = "data:text/html;charset=utf-8,default";
|
||||
tabs.open({
|
||||
url: url,
|
||||
isPinned: true,
|
||||
onOpen: function(tab) {
|
||||
test.assertEqual(tab.isPinned, true, "The new tab is pinned");
|
||||
tab.close(test.done.bind(test));
|
||||
}
|
||||
});
|
||||
const xulApp = require("sdk/system/xul-app");
|
||||
if (xulApp.versionInRange(xulApp.platformVersion, "2.0b2", "*")) {
|
||||
// test tab pinning
|
||||
test.waitUntilDone();
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require("sdk/tabs");
|
||||
let url = "data:text/html;charset=utf-8,default";
|
||||
tabs.open({
|
||||
url: url,
|
||||
isPinned: true,
|
||||
onOpen: function(tab) {
|
||||
test.assertEqual(tab.isPinned, true, "The new tab is pinned");
|
||||
closeBrowserWindow(window, function() test.done());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
test.pass("Pinned tabs are not supported in this application.");
|
||||
}
|
||||
};
|
||||
|
||||
// TEST: pin/unpin opened tab
|
||||
exports.testPinUnpin = function(test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
let url = "data:text/html;charset=utf-8,default";
|
||||
tabs.open({
|
||||
url: url,
|
||||
inBackground: true,
|
||||
onOpen: function(tab) {
|
||||
tab.pin();
|
||||
test.assertEqual(tab.isPinned, true, "The tab was pinned correctly");
|
||||
tab.unpin();
|
||||
test.assertEqual(tab.isPinned, false, "The tab was unpinned correctly");
|
||||
tab.close(test.done.bind(test));
|
||||
}
|
||||
});
|
||||
}
|
||||
const xulApp = require("sdk/system/xul-app");
|
||||
if (xulApp.versionInRange(xulApp.platformVersion, "2.0b2", "*")) {
|
||||
test.waitUntilDone();
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require("sdk/tabs");
|
||||
let url = "data:text/html;charset=utf-8,default";
|
||||
tabs.open({
|
||||
url: url,
|
||||
onOpen: function(tab) {
|
||||
tab.pin();
|
||||
test.assertEqual(tab.isPinned, true, "The tab was pinned correctly");
|
||||
tab.unpin();
|
||||
test.assertEqual(tab.isPinned, false, "The tab was unpinned correctly");
|
||||
closeBrowserWindow(window, function() test.done());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
test.pass("Pinned tabs are not supported in this application.");
|
||||
}
|
||||
};
|
||||
|
||||
// TEST: open tab in background
|
||||
exports.testInBackground = function(test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
let window = getMostRecentBrowserWindow();
|
||||
let activeUrl = tabs.activeTab.url;
|
||||
let url = "data:text/html;charset=utf-8,background";
|
||||
test.assertEqual(activeWindow, window, "activeWindow matches this window");
|
||||
tabs.on('ready', function onReady(tab) {
|
||||
tabs.removeListener('ready', onReady);
|
||||
test.assertEqual(tabs.activeTab.url, activeUrl, "URL of active tab has not changed");
|
||||
test.assertEqual(tab.url, url, "URL of the new background tab matches");
|
||||
test.assertEqual(activeWindow, window, "a new window was not opened");
|
||||
test.assertNotEqual(tabs.activeTab.url, url, "URL of active tab is not the new URL");
|
||||
tab.close(test.done.bind(test));
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require("sdk/tabs");
|
||||
let activeUrl = tabs.activeTab.url;
|
||||
let url = "data:text/html;charset=utf-8,background";
|
||||
test.assertEqual(activeWindow, window, "activeWindow matches this window");
|
||||
tabs.on('ready', function onReady(tab) {
|
||||
tabs.removeListener('ready', onReady);
|
||||
test.assertEqual(tabs.activeTab.url, activeUrl, "URL of active tab has not changed");
|
||||
test.assertEqual(tab.url, url, "URL of the new background tab matches");
|
||||
test.assertEqual(activeWindow, window, "a new window was not opened");
|
||||
test.assertNotEqual(tabs.activeTab.url, url, "URL of active tab is not the new URL");
|
||||
closeBrowserWindow(window, function() test.done());
|
||||
});
|
||||
tabs.open({
|
||||
url: url,
|
||||
inBackground: true
|
||||
});
|
||||
});
|
||||
|
||||
tabs.open({
|
||||
url: url,
|
||||
inBackground: true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// TEST: open tab in new window
|
||||
exports.testOpenInNewWindow = function(test) {
|
||||
test.waitUntilDone();
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require("sdk/tabs");
|
||||
|
||||
let startWindowCount = windows().length;
|
||||
let cache = [];
|
||||
let windowUtils = require("sdk/deprecated/window-utils");
|
||||
let wt = new windowUtils.WindowTracker({
|
||||
onTrack: function(win) {
|
||||
cache.push(win);
|
||||
},
|
||||
onUntrack: function(win) {
|
||||
cache.splice(cache.indexOf(win), 1)
|
||||
}
|
||||
});
|
||||
let startWindowCount = cache.length;
|
||||
|
||||
let url = "data:text/html;charset=utf-8,testOpenInNewWindow";
|
||||
tabs.open({
|
||||
url: url,
|
||||
inNewWindow: true,
|
||||
onReady: function(tab) {
|
||||
let newWindow = getOwnerWindow(tab);
|
||||
test.assertEqual(windows().length, startWindowCount + 1, "a new window was opened");
|
||||
|
||||
onFocus(newWindow).then(function() {
|
||||
let url = "data:text/html;charset=utf-8,newwindow";
|
||||
tabs.open({
|
||||
url: url,
|
||||
inNewWindow: true,
|
||||
onReady: function(tab) {
|
||||
let newWindow = cache[cache.length - 1];
|
||||
test.assertEqual(cache.length, startWindowCount + 1, "a new window was opened");
|
||||
test.assertEqual(activeWindow, newWindow, "new window is active");
|
||||
test.assertEqual(tab.url, url, "URL of the new tab matches");
|
||||
test.assertEqual(newWindow.content.location, url, "URL of new tab in new window matches");
|
||||
test.assertEqual(tabs.activeTab.url, url, "URL of activeTab matches");
|
||||
|
||||
closeBrowserWindow(newWindow, test.done.bind(test));
|
||||
}, test.fail).then(null, test.fail);
|
||||
}
|
||||
for (let i in cache) cache[i] = null;
|
||||
wt.unload();
|
||||
closeBrowserWindow(newWindow, function() {
|
||||
closeBrowserWindow(window, function() test.done());
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
// Test tab.open inNewWindow + onOpen combination
|
||||
exports.testOpenInNewWindowOnOpen = function(test) {
|
||||
test.waitUntilDone();
|
||||
let tabs = require("sdk/tabs");
|
||||
|
||||
let startWindowCount = windows().length;
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let cache = [];
|
||||
let windowUtils = require("sdk/deprecated/window-utils");
|
||||
let wt = new windowUtils.WindowTracker({
|
||||
onTrack: function(win) {
|
||||
cache.push(win);
|
||||
},
|
||||
onUntrack: function(win) {
|
||||
cache.splice(cache.indexOf(win), 1)
|
||||
}
|
||||
});
|
||||
let startWindowCount = cache.length;
|
||||
|
||||
let url = "data:text/html;charset=utf-8,newwindow";
|
||||
tabs.open({
|
||||
url: url,
|
||||
inNewWindow: true,
|
||||
onOpen: function(tab) {
|
||||
let newWindow = getOwnerWindow(tab);
|
||||
|
||||
onFocus(newWindow).then(function() {
|
||||
test.assertEqual(windows().length, startWindowCount + 1, "a new window was opened");
|
||||
let url = "data:text/html;charset=utf-8,newwindow";
|
||||
tabs.open({
|
||||
url: url,
|
||||
inNewWindow: true,
|
||||
onOpen: function(tab) {
|
||||
let newWindow = cache[cache.length - 1];
|
||||
test.assertEqual(cache.length, startWindowCount + 1, "a new window was opened");
|
||||
test.assertEqual(activeWindow, newWindow, "new window is active");
|
||||
|
||||
for (let i in cache) cache[i] = null;
|
||||
wt.unload();
|
||||
|
||||
closeBrowserWindow(newWindow, function() {
|
||||
test.done();
|
||||
closeBrowserWindow(window, function() test.done());
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -387,6 +504,7 @@ exports.testOpenInNewWindowOnOpen = function(test) {
|
||||
exports.testTabsEvent_onOpen = function(test) {
|
||||
test.waitUntilDone();
|
||||
openBrowserWindow(function(window, browser) {
|
||||
var tabs = require("sdk/tabs");
|
||||
let url = "data:text/html;charset=utf-8,1";
|
||||
let eventCount = 0;
|
||||
|
||||
@ -412,6 +530,7 @@ exports.testTabsEvent_onOpen = function(test) {
|
||||
exports.testTabsEvent_onClose = function(test) {
|
||||
test.waitUntilDone();
|
||||
openBrowserWindow(function(window, browser) {
|
||||
var tabs = require("sdk/tabs");
|
||||
let url = "data:text/html;charset=utf-8,onclose";
|
||||
let eventCount = 0;
|
||||
|
||||
@ -443,6 +562,8 @@ exports.testTabsEvent_onCloseWindow = function(test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
openBrowserWindow(function(window, browser) {
|
||||
var tabs = require("sdk/tabs");
|
||||
|
||||
let closeCount = 0, individualCloseCount = 0;
|
||||
function listener() {
|
||||
closeCount++;
|
||||
@ -495,6 +616,7 @@ exports.testTabsEvent_onCloseWindow = function(test) {
|
||||
exports.testTabsEvent_onReady = function(test) {
|
||||
test.waitUntilDone();
|
||||
openBrowserWindow(function(window, browser) {
|
||||
var tabs = require("sdk/tabs");
|
||||
let url = "data:text/html;charset=utf-8,onready";
|
||||
let eventCount = 0;
|
||||
|
||||
@ -520,6 +642,7 @@ exports.testTabsEvent_onReady = function(test) {
|
||||
exports.testTabsEvent_onActivate = function(test) {
|
||||
test.waitUntilDone();
|
||||
openBrowserWindow(function(window, browser) {
|
||||
var tabs = require("sdk/tabs");
|
||||
let url = "data:text/html;charset=utf-8,onactivate";
|
||||
let eventCount = 0;
|
||||
|
||||
@ -545,6 +668,7 @@ exports.testTabsEvent_onActivate = function(test) {
|
||||
exports.testTabsEvent_onDeactivate = function(test) {
|
||||
test.waitUntilDone();
|
||||
openBrowserWindow(function(window, browser) {
|
||||
var tabs = require("sdk/tabs");
|
||||
let url = "data:text/html;charset=utf-8,ondeactivate";
|
||||
let eventCount = 0;
|
||||
|
||||
@ -575,6 +699,7 @@ exports.testTabsEvent_onDeactivate = function(test) {
|
||||
exports.testTabsEvent_pinning = function(test) {
|
||||
test.waitUntilDone();
|
||||
openBrowserWindow(function(window, browser) {
|
||||
var tabs = require("sdk/tabs");
|
||||
let url = "data:text/html;charset=utf-8,1";
|
||||
|
||||
tabs.on('open', function onOpen(tab) {
|
||||
@ -602,6 +727,7 @@ exports.testTabsEvent_pinning = function(test) {
|
||||
exports.testPerTabEvents = function(test) {
|
||||
test.waitUntilDone();
|
||||
openBrowserWindow(function(window, browser) {
|
||||
var tabs = require("sdk/tabs");
|
||||
let eventCount = 0;
|
||||
|
||||
tabs.open({
|
||||
@ -629,6 +755,8 @@ exports.testAttachOnOpen = function (test) {
|
||||
// Take care that attach has to be called on tab ready and not on tab open.
|
||||
test.waitUntilDone();
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require("sdk/tabs");
|
||||
|
||||
tabs.open({
|
||||
url: "data:text/html;charset=utf-8,foobar",
|
||||
onOpen: function (tab) {
|
||||
@ -651,6 +779,7 @@ exports.testAttachOnMultipleDocuments = function (test) {
|
||||
// Example of attach that process multiple tab documents
|
||||
test.waitUntilDone();
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require("sdk/tabs");
|
||||
let firstLocation = "data:text/html;charset=utf-8,foobar";
|
||||
let secondLocation = "data:text/html;charset=utf-8,bar";
|
||||
let thirdLocation = "data:text/html;charset=utf-8,fox";
|
||||
@ -732,6 +861,7 @@ exports.testAttachWrappers = function (test) {
|
||||
// Check that content script has access to wrapped values by default
|
||||
test.waitUntilDone();
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require("sdk/tabs");
|
||||
let document = "data:text/html;charset=utf-8,<script>var globalJSVar = true; " +
|
||||
" document.getElementById = 3;</script>";
|
||||
let count = 0;
|
||||
@ -765,6 +895,7 @@ exports.testAttachUnwrapped = function (test) {
|
||||
// Check that content script has access to unwrapped values through unsafeWindow
|
||||
test.waitUntilDone();
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require("sdk/tabs");
|
||||
let document = "data:text/html;charset=utf-8,<script>var globalJSVar=true;</script>";
|
||||
let count = 0;
|
||||
|
||||
@ -793,6 +924,7 @@ exports['test window focus changes active tab'] = function(test) {
|
||||
test.waitUntilDone();
|
||||
let win1 = openBrowserWindow(function() {
|
||||
let win2 = openBrowserWindow(function() {
|
||||
let tabs = require("sdk/tabs");
|
||||
tabs.on("activate", function onActivate() {
|
||||
tabs.removeListener("activate", onActivate);
|
||||
test.pass("activate was called on windows focus change.");
|
||||
@ -861,6 +993,7 @@ exports.testOnLoadEventWithDOM = function(test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require('sdk/tabs');
|
||||
let count = 0;
|
||||
tabs.on('load', function onLoad(tab) {
|
||||
test.assertEqual(tab.title, 'tab', 'tab passed in as arg, load called');
|
||||
@ -888,6 +1021,7 @@ exports.testOnLoadEventWithImage = function(test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require('sdk/tabs');
|
||||
let count = 0;
|
||||
tabs.on('load', function onLoad(tab) {
|
||||
if (!count++) {
|
||||
@ -916,6 +1050,8 @@ exports.testOnPageShowEvent = function (test) {
|
||||
let secondUrl = 'data:text/html;charset=utf-8,Second';
|
||||
|
||||
openBrowserWindow(function(window, browser) {
|
||||
let tabs = require('sdk/tabs');
|
||||
|
||||
let counter = 0;
|
||||
tabs.on('pageshow', function onPageShow(tab, persisted) {
|
||||
counter++;
|
||||
@ -1014,3 +1150,7 @@ function closeBrowserWindow(window, callback) {
|
||||
}, false);
|
||||
window.close();
|
||||
}
|
||||
|
||||
// Test disabled on Linux because of bug 882867
|
||||
if (require("sdk/system/runtime").OS == "Linux")
|
||||
module.exports = {};
|
||||
|
@ -55,7 +55,6 @@ exports['test that add-on page has no chrome'] = function(assert, done) {
|
||||
closeTab(tab);
|
||||
assert.ok(isChromeVisible(window), 'chrome is visible again');
|
||||
loader.unload();
|
||||
assert.ok(!isTabOpen(tab), 'add-on page tab is closed on unload');
|
||||
done();
|
||||
});
|
||||
};
|
||||
@ -80,7 +79,6 @@ exports['test that add-on page with hash has no chrome'] = function(assert, done
|
||||
closeTab(tab);
|
||||
assert.ok(isChromeVisible(window), 'chrome is visible again');
|
||||
loader.unload();
|
||||
assert.ok(!isTabOpen(tab), 'add-on page tab is closed on unload');
|
||||
done();
|
||||
});
|
||||
};
|
||||
@ -105,7 +103,6 @@ exports['test that add-on page with querystring has no chrome'] = function(asser
|
||||
closeTab(tab);
|
||||
assert.ok(isChromeVisible(window), 'chrome is visible again');
|
||||
loader.unload();
|
||||
assert.ok(!isTabOpen(tab), 'add-on page tab is closed on unload');
|
||||
done();
|
||||
});
|
||||
};
|
||||
@ -130,7 +127,6 @@ exports['test that add-on page with hash and querystring has no chrome'] = funct
|
||||
closeTab(tab);
|
||||
assert.ok(isChromeVisible(window), 'chrome is visible again');
|
||||
loader.unload();
|
||||
assert.ok(!isTabOpen(tab), 'add-on page tab is closed on unload');
|
||||
done();
|
||||
});
|
||||
};
|
||||
@ -155,4 +151,19 @@ exports['test that malformed uri is not an addon-page'] = function(assert, done)
|
||||
});
|
||||
};
|
||||
|
||||
exports['test that add-on pages are closed on unload'] = function(assert, done) {
|
||||
let { loader } = LoaderWithHookedConsole(module);
|
||||
loader.require('sdk/addon-page');
|
||||
|
||||
tabs.open({
|
||||
url: uri,
|
||||
onReady: function listener(tab) {
|
||||
loader.unload();
|
||||
assert.ok(!isTabOpen(tab), 'add-on page tabs are closed on unload');
|
||||
|
||||
done();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
require('test').run(exports);
|
||||
|
@ -1,462 +0,0 @@
|
||||
/* 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 { pathFor } = require("sdk/system");
|
||||
const fs = require("sdk/io/fs");
|
||||
const url = require("sdk/url");
|
||||
const path = require("sdk/fs/path");
|
||||
const { Buffer } = require("sdk/io/buffer");
|
||||
|
||||
// Use profile directory to list / read / write files.
|
||||
const profilePath = pathFor("ProfD");
|
||||
const fileNameInProfile = "compatibility.ini";
|
||||
const dirNameInProfile = "extensions";
|
||||
const filePathInProfile = path.join(profilePath, fileNameInProfile);
|
||||
const dirPathInProfile = path.join(profilePath, dirNameInProfile);
|
||||
const mkdirPath = path.join(profilePath, "sdk-fixture-mkdir");
|
||||
const writePath = path.join(profilePath, "sdk-fixture-writeFile");
|
||||
const unlinkPath = path.join(profilePath, "sdk-fixture-unlink");
|
||||
const truncatePath = path.join(profilePath, "sdk-fixture-truncate");
|
||||
const renameFromPath = path.join(profilePath, "sdk-fixture-rename-from");
|
||||
const renameToPath = path.join(profilePath, "sdk-fixture-rename-to");
|
||||
|
||||
const profileEntries = [
|
||||
"compatibility.ini",
|
||||
"extensions",
|
||||
"extensions.ini",
|
||||
"prefs.js"
|
||||
// There are likely to be a lot more files but we can't really
|
||||
// on consistent list so we limit to this.
|
||||
];
|
||||
|
||||
exports["test readir"] = function(assert, end) {
|
||||
var async = false;
|
||||
fs.readdir(profilePath, function(error, entries) {
|
||||
assert.ok(async, "readdir is async");
|
||||
assert.ok(!error, "there is no error when reading directory");
|
||||
assert.ok(profileEntries.length <= entries.length,
|
||||
"got et least number of entries we expect");
|
||||
assert.ok(profileEntries.every(function(entry) {
|
||||
return entries.indexOf(entry) >= 0;
|
||||
}), "all profiles are present");
|
||||
end();
|
||||
});
|
||||
|
||||
async = true;
|
||||
};
|
||||
|
||||
exports["test readdir error"] = function(assert, end) {
|
||||
var async = false;
|
||||
var path = profilePath + "-does-not-exists";
|
||||
fs.readdir(path, function(error, entries) {
|
||||
assert.ok(async, "readdir is async");
|
||||
assert.equal(error.message, "ENOENT, readdir " + path);
|
||||
assert.equal(error.code, "ENOENT", "error has a code");
|
||||
assert.equal(error.path, path, "error has a path");
|
||||
assert.equal(error.errno, 34, "error has a errno");
|
||||
end();
|
||||
});
|
||||
|
||||
async = true;
|
||||
};
|
||||
|
||||
exports["test readdirSync"] = function(assert) {
|
||||
var async = false;
|
||||
var entries = fs.readdirSync(profilePath);
|
||||
assert.ok(profileEntries.length <= entries.length,
|
||||
"got et least number of entries we expect");
|
||||
assert.ok(profileEntries.every(function(entry) {
|
||||
return entries.indexOf(entry) >= 0;
|
||||
}), "all profiles are present");
|
||||
};
|
||||
|
||||
exports["test readirSync error"] = function(assert) {
|
||||
var async = false;
|
||||
var path = profilePath + "-does-not-exists";
|
||||
try {
|
||||
fs.readdirSync(path);
|
||||
assert.fail(Error("No error was thrown"));
|
||||
} catch (error) {
|
||||
assert.equal(error.message, "ENOENT, readdir " + path);
|
||||
assert.equal(error.code, "ENOENT", "error has a code");
|
||||
assert.equal(error.path, path, "error has a path");
|
||||
assert.equal(error.errno, 34, "error has a errno");
|
||||
}
|
||||
};
|
||||
|
||||
exports["test readFile"] = function(assert, end) {
|
||||
let async = false;
|
||||
fs.readFile(filePathInProfile, function(error, content) {
|
||||
assert.ok(async, "readFile is async");
|
||||
assert.ok(!error, "error is falsy");
|
||||
assert.ok(Buffer.isBuffer(content), "readFile returns buffer");
|
||||
assert.ok(typeof(content.length) === "number", "buffer has length");
|
||||
assert.ok(content.toString().indexOf("[Compatibility]") >= 0,
|
||||
"content contains expected data");
|
||||
end();
|
||||
});
|
||||
async = true;
|
||||
};
|
||||
|
||||
exports["test readFile error"] = function(assert, end) {
|
||||
let async = false;
|
||||
let path = filePathInProfile + "-does-not-exists";
|
||||
fs.readFile(path, function(error, content) {
|
||||
assert.ok(async, "readFile is async");
|
||||
assert.equal(error.message, "ENOENT, open " + path);
|
||||
assert.equal(error.code, "ENOENT", "error has a code");
|
||||
assert.equal(error.path, path, "error has a path");
|
||||
assert.equal(error.errno, 34, "error has a errno");
|
||||
|
||||
end();
|
||||
});
|
||||
async = true;
|
||||
};
|
||||
|
||||
exports["test readFileSync not implemented"] = function(assert) {
|
||||
let buffer = fs.readFileSync(filePathInProfile);
|
||||
assert.ok(buffer.toString().indexOf("[Compatibility]") >= 0,
|
||||
"read content");
|
||||
};
|
||||
|
||||
exports["test fs.stat file"] = function(assert, end) {
|
||||
let async = false;
|
||||
let path = filePathInProfile;
|
||||
fs.stat(path, function(error, stat) {
|
||||
assert.ok(async, "fs.stat is async");
|
||||
assert.ok(!error, "error is falsy");
|
||||
assert.ok(!stat.isDirectory(), "not a dir");
|
||||
assert.ok(stat.isFile(), "is a file");
|
||||
assert.ok(!stat.isSymbolicLink(), "isn't a symlink");
|
||||
assert.ok(typeof(stat.size) === "number", "size is a number");
|
||||
assert.ok(stat.exists === true, "file exists");
|
||||
assert.ok(typeof(stat.isBlockDevice()) === "boolean");
|
||||
assert.ok(typeof(stat.isCharacterDevice()) === "boolean");
|
||||
assert.ok(typeof(stat.isFIFO()) === "boolean");
|
||||
assert.ok(typeof(stat.isSocket()) === "boolean");
|
||||
assert.ok(typeof(stat.hidden) === "boolean");
|
||||
assert.ok(typeof(stat.writable) === "boolean")
|
||||
assert.ok(stat.readable === true);
|
||||
|
||||
end();
|
||||
});
|
||||
async = true;
|
||||
};
|
||||
|
||||
exports["test fs.stat dir"] = function(assert, end) {
|
||||
let async = false;
|
||||
let path = dirPathInProfile;
|
||||
fs.stat(path, function(error, stat) {
|
||||
assert.ok(async, "fs.stat is async");
|
||||
assert.ok(!error, "error is falsy");
|
||||
assert.ok(stat.isDirectory(), "is a dir");
|
||||
assert.ok(!stat.isFile(), "not a file");
|
||||
assert.ok(!stat.isSymbolicLink(), "isn't a symlink");
|
||||
assert.ok(typeof(stat.size) === "number", "size is a number");
|
||||
assert.ok(stat.exists === true, "file exists");
|
||||
assert.ok(typeof(stat.isBlockDevice()) === "boolean");
|
||||
assert.ok(typeof(stat.isCharacterDevice()) === "boolean");
|
||||
assert.ok(typeof(stat.isFIFO()) === "boolean");
|
||||
assert.ok(typeof(stat.isSocket()) === "boolean");
|
||||
assert.ok(typeof(stat.hidden) === "boolean");
|
||||
assert.ok(typeof(stat.writable) === "boolean")
|
||||
assert.ok(typeof(stat.readable) === "boolean");
|
||||
|
||||
end();
|
||||
});
|
||||
async = true;
|
||||
};
|
||||
|
||||
exports["test fs.stat error"] = function(assert, end) {
|
||||
let async = false;
|
||||
let path = filePathInProfile + "-does-not-exists";
|
||||
fs.stat(path, function(error, stat) {
|
||||
assert.ok(async, "fs.stat is async");
|
||||
assert.equal(error.message, "ENOENT, stat " + path);
|
||||
assert.equal(error.code, "ENOENT", "error has a code");
|
||||
assert.equal(error.path, path, "error has a path");
|
||||
assert.equal(error.errno, 34, "error has a errno");
|
||||
|
||||
end();
|
||||
});
|
||||
async = true;
|
||||
};
|
||||
|
||||
exports["test fs.exists NO"] = function(assert, end) {
|
||||
let async = false
|
||||
let path = filePathInProfile + "-does-not-exists";
|
||||
fs.exists(path, function(error, value) {
|
||||
assert.ok(async, "fs.exists is async");
|
||||
assert.ok(!error, "error is falsy");
|
||||
assert.ok(!value, "file does not exists");
|
||||
end();
|
||||
});
|
||||
async = true;
|
||||
};
|
||||
|
||||
exports["test fs.exists YES"] = function(assert, end) {
|
||||
let async = false
|
||||
let path = filePathInProfile
|
||||
fs.exists(path, function(error, value) {
|
||||
assert.ok(async, "fs.exists is async");
|
||||
assert.ok(!error, "error is falsy");
|
||||
assert.ok(value, "file exists");
|
||||
end();
|
||||
});
|
||||
async = true;
|
||||
};
|
||||
|
||||
exports["test fs.exists NO"] = function(assert, end) {
|
||||
let async = false
|
||||
let path = filePathInProfile + "-does-not-exists";
|
||||
fs.exists(path, function(error, value) {
|
||||
assert.ok(async, "fs.exists is async");
|
||||
assert.ok(!error, "error is falsy");
|
||||
assert.ok(!value, "file does not exists");
|
||||
end();
|
||||
});
|
||||
async = true;
|
||||
};
|
||||
|
||||
exports["test fs.existsSync"] = function(assert) {
|
||||
let path = filePathInProfile
|
||||
assert.equal(fs.existsSync(path), true, "exists");
|
||||
assert.equal(fs.existsSync(path + "-does-not-exists"), false, "exists");
|
||||
};
|
||||
|
||||
exports["test fs.mkdirSync fs.rmdirSync"] = function(assert) {
|
||||
let path = mkdirPath;
|
||||
|
||||
assert.equal(fs.existsSync(path), false, "does not exists");
|
||||
fs.mkdirSync(path);
|
||||
assert.equal(fs.existsSync(path), true, "dir was created");
|
||||
try {
|
||||
fs.mkdirSync(path);
|
||||
assert.fail(Error("mkdir on existing should throw"));
|
||||
} catch (error) {
|
||||
assert.equal(error.message, "EEXIST, mkdir " + path);
|
||||
assert.equal(error.code, "EEXIST", "error has a code");
|
||||
assert.equal(error.path, path, "error has a path");
|
||||
assert.equal(error.errno, 47, "error has a errno");
|
||||
}
|
||||
fs.rmdirSync(path);
|
||||
assert.equal(fs.existsSync(path), false, "dir was removed");
|
||||
};
|
||||
|
||||
exports["test fs.mkdir"] = function(assert, end) {
|
||||
let path = mkdirPath;
|
||||
|
||||
if (!fs.existsSync(path)) {
|
||||
let async = false;
|
||||
fs.mkdir(path, function(error) {
|
||||
assert.ok(async, "mkdir is async");
|
||||
assert.ok(!error, "no error");
|
||||
assert.equal(fs.existsSync(path), true, "dir was created");
|
||||
fs.rmdirSync(path);
|
||||
assert.equal(fs.existsSync(path), false, "dir was removed");
|
||||
end();
|
||||
});
|
||||
async = true;
|
||||
}
|
||||
};
|
||||
|
||||
exports["test fs.mkdir error"] = function(assert, end) {
|
||||
let path = mkdirPath;
|
||||
|
||||
if (!fs.existsSync(path)) {
|
||||
fs.mkdirSync(path);
|
||||
let async = false;
|
||||
fs.mkdir(path, function(error) {
|
||||
assert.ok(async, "mkdir is async");
|
||||
assert.equal(error.message, "EEXIST, mkdir " + path);
|
||||
assert.equal(error.code, "EEXIST", "error has a code");
|
||||
assert.equal(error.path, path, "error has a path");
|
||||
assert.equal(error.errno, 47, "error has a errno");
|
||||
fs.rmdirSync(path);
|
||||
assert.equal(fs.existsSync(path), false, "dir was removed");
|
||||
end();
|
||||
});
|
||||
async = true;
|
||||
}
|
||||
};
|
||||
|
||||
exports["test fs.rmdir"] = function(assert, end) {
|
||||
let path = mkdirPath;
|
||||
|
||||
if (!fs.existsSync(path)) {
|
||||
fs.mkdirSync(path);
|
||||
assert.equal(fs.existsSync(path), true, "dir exists");
|
||||
let async = false;
|
||||
fs.rmdir(path, function(error) {
|
||||
assert.ok(async, "mkdir is async");
|
||||
assert.ok(!error, "no error");
|
||||
assert.equal(fs.existsSync(path), false, "dir was removed");
|
||||
end();
|
||||
});
|
||||
async = true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
exports["test fs.rmdir error"] = function(assert, end) {
|
||||
let path = mkdirPath;
|
||||
|
||||
if (!fs.existsSync(path)) {
|
||||
assert.equal(fs.existsSync(path), false, "dir doesn't exists");
|
||||
let async = false;
|
||||
fs.rmdir(path, function(error) {
|
||||
assert.ok(async, "mkdir is async");
|
||||
assert.equal(error.message, "ENOENT, remove " + path);
|
||||
assert.equal(error.code, "ENOENT", "error has a code");
|
||||
assert.equal(error.path, path, "error has a path");
|
||||
assert.equal(error.errno, 34, "error has a errno");
|
||||
assert.equal(fs.existsSync(path), false, "dir is removed");
|
||||
end();
|
||||
});
|
||||
async = true;
|
||||
}
|
||||
};
|
||||
|
||||
exports["test fs.truncateSync fs.unlinkSync"] = function(assert) {
|
||||
let path = truncatePath;
|
||||
|
||||
assert.equal(fs.existsSync(path), false, "does not exists");
|
||||
fs.truncateSync(path);
|
||||
assert.equal(fs.existsSync(path), true, "file was created");
|
||||
fs.truncateSync(path);
|
||||
fs.unlinkSync(path);
|
||||
assert.equal(fs.existsSync(path), false, "file was removed");
|
||||
};
|
||||
|
||||
|
||||
exports["test fs.truncate"] = function(assert, end) {
|
||||
let path = truncatePath;
|
||||
if (!fs.existsSync(path)) {
|
||||
let async = false;
|
||||
fs.truncate(path, 0, function(error) {
|
||||
assert.ok(async, "truncate is async");
|
||||
console.log(error);
|
||||
assert.ok(!error, "no error");
|
||||
assert.equal(fs.existsSync(path), true, "file was created");
|
||||
fs.unlinkSync(path);
|
||||
assert.equal(fs.existsSync(path), false, "file was removed");
|
||||
end();
|
||||
})
|
||||
async = true;
|
||||
}
|
||||
};
|
||||
|
||||
exports["test fs.unlink"] = function(assert, end) {
|
||||
let path = unlinkPath;
|
||||
let async = false;
|
||||
assert.ok(!fs.existsSync(path), "file doesn't exists yet");
|
||||
fs.truncateSync(path, 0);
|
||||
assert.ok(fs.existsSync(path), "file was created");
|
||||
fs.unlink(path, function(error) {
|
||||
assert.ok(async, "fs.unlink is async");
|
||||
assert.ok(!error, "error is falsy");
|
||||
assert.ok(!fs.existsSync(path), "file was removed");
|
||||
end();
|
||||
});
|
||||
async = true;
|
||||
};
|
||||
|
||||
exports["test fs.unlink error"] = function(assert, end) {
|
||||
let path = unlinkPath;
|
||||
let async = false;
|
||||
assert.ok(!fs.existsSync(path), "file doesn't exists yet");
|
||||
fs.unlink(path, function(error) {
|
||||
assert.ok(async, "fs.unlink is async");
|
||||
assert.equal(error.message, "ENOENT, remove " + path);
|
||||
assert.equal(error.code, "ENOENT", "error has a code");
|
||||
assert.equal(error.path, path, "error has a path");
|
||||
assert.equal(error.errno, 34, "error has a errno");
|
||||
end();
|
||||
});
|
||||
async = true;
|
||||
};
|
||||
|
||||
exports["test fs.rename"] = function(assert, end) {
|
||||
let fromPath = renameFromPath;
|
||||
let toPath = renameToPath;
|
||||
|
||||
fs.truncateSync(fromPath);
|
||||
assert.ok(fs.existsSync(fromPath), "source file exists");
|
||||
assert.ok(!fs.existsSync(toPath), "destination doesn't exists");
|
||||
var async = false;
|
||||
fs.rename(fromPath, toPath, function(error) {
|
||||
assert.ok(async, "fs.rename is async");
|
||||
assert.ok(!error, "error is falsy");
|
||||
assert.ok(!fs.existsSync(fromPath), "source path no longer exists");
|
||||
assert.ok(fs.existsSync(toPath), "destination file exists");
|
||||
fs.unlinkSync(toPath);
|
||||
assert.ok(!fs.existsSync(toPath), "cleaned up properly");
|
||||
end();
|
||||
});
|
||||
async = true;
|
||||
};
|
||||
|
||||
exports["test fs.rename (missing source file)"] = function(assert, end) {
|
||||
let fromPath = renameFromPath;
|
||||
let toPath = renameToPath;
|
||||
|
||||
assert.ok(!fs.existsSync(fromPath), "source file doesn't exists");
|
||||
assert.ok(!fs.existsSync(toPath), "destination doesn't exists");
|
||||
var async = false;
|
||||
fs.rename(fromPath, toPath, function(error) {
|
||||
assert.ok(async, "fs.rename is async");
|
||||
assert.equal(error.message, "ENOENT, rename " + fromPath);
|
||||
assert.equal(error.code, "ENOENT", "error has a code");
|
||||
assert.equal(error.path, fromPath, "error has a path");
|
||||
assert.equal(error.errno, 34, "error has a errno");
|
||||
end();
|
||||
});
|
||||
async = true;
|
||||
};
|
||||
|
||||
exports["test fs.rename (existing target file)"] = function(assert, end) {
|
||||
let fromPath = renameFromPath;
|
||||
let toPath = renameToPath;
|
||||
|
||||
fs.truncateSync(fromPath);
|
||||
fs.truncateSync(toPath);
|
||||
assert.ok(fs.existsSync(fromPath), "source file exists");
|
||||
assert.ok(fs.existsSync(toPath), "destination file exists");
|
||||
var async = false;
|
||||
fs.rename(fromPath, toPath, function(error) {
|
||||
assert.ok(async, "fs.rename is async");
|
||||
assert.ok(!error, "error is falsy");
|
||||
assert.ok(!fs.existsSync(fromPath), "source path no longer exists");
|
||||
assert.ok(fs.existsSync(toPath), "destination file exists");
|
||||
fs.unlinkSync(toPath);
|
||||
assert.ok(!fs.existsSync(toPath), "cleaned up properly");
|
||||
end();
|
||||
});
|
||||
async = true;
|
||||
};
|
||||
|
||||
exports["test fs.writeFile"] = function(assert, end) {
|
||||
let path = writePath;
|
||||
let content = ["hello world",
|
||||
"this is some text"].join("\n");
|
||||
|
||||
var async = false;
|
||||
fs.writeFile(path, content, function(error) {
|
||||
assert.ok(async, "fs write is async");
|
||||
assert.ok(!error, "error is falsy");
|
||||
assert.ok(fs.existsSync(path), "file was created");
|
||||
assert.equal(fs.readFileSync(path).toString(),
|
||||
content,
|
||||
"contet was written");
|
||||
fs.unlinkSync(path);
|
||||
assert.ok(!fs.exists(path), "file was removed");
|
||||
|
||||
end();
|
||||
});
|
||||
async = true;
|
||||
};
|
||||
|
||||
require("test").run(exports);
|
@ -1,99 +0,0 @@
|
||||
/* 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 { Cc, Ci } = require('chrome');
|
||||
const { defer, all } = require('sdk/core/promise');
|
||||
const { setTimeout } = require('sdk/timers');
|
||||
const { request, response } = require('sdk/addon/host');
|
||||
const { send } = require('sdk/addon/events');
|
||||
const { filter } = require('sdk/event/utils');
|
||||
const { on, emit, off } = require('sdk/event/core');
|
||||
|
||||
let stream = filter(request, (data) => /sdk-x-test/.test(data.event));
|
||||
|
||||
exports.testSend = function (assert, done) {
|
||||
on(stream, 'data', handle);
|
||||
send('sdk-x-test-simple', { title: 'my test data' }).then((data) => {
|
||||
assert.equal(data.title, 'my response', 'response is handled');
|
||||
off(stream, 'data', handle);
|
||||
done();
|
||||
}, (reason) => {
|
||||
assert.fail('should not call reject');
|
||||
});
|
||||
function handle (e) {
|
||||
assert.equal(e.event, 'sdk-x-test-simple', 'correct event name');
|
||||
assert.ok(e.id != null, 'message has an ID');
|
||||
assert.equal(e.data.title, 'my test data', 'serialized data passes');
|
||||
e.data.title = 'my response';
|
||||
emit(response, 'data', e);
|
||||
}
|
||||
};
|
||||
|
||||
exports.testSendError = function (assert, done) {
|
||||
on(stream, 'data', handle);
|
||||
send('sdk-x-test-error', { title: 'my test data' }).then((data) => {
|
||||
assert.fail('should not call success');
|
||||
}, (reason) => {
|
||||
assert.equal(reason, 'ErrorInfo', 'should reject with error/reason');
|
||||
off(stream, 'data', handle);
|
||||
done();
|
||||
});
|
||||
function handle (e) {
|
||||
e.error = 'ErrorInfo';
|
||||
emit(response, 'data', e);
|
||||
}
|
||||
};
|
||||
|
||||
exports.testMultipleSends = function (assert, done) {
|
||||
let count = 0;
|
||||
let ids = [];
|
||||
on(stream, 'data', handle);
|
||||
['firefox', 'thunderbird', 'rust'].map(data =>
|
||||
send('sdk-x-test-multi', { data: data }).then(val => {
|
||||
assert.ok(val === 'firefox' || val === 'rust', 'successful calls resolve correctly');
|
||||
if (++count === 3) {
|
||||
off(stream, 'data', handle);
|
||||
done();
|
||||
}
|
||||
}, reason => {
|
||||
assert.equal(reason.error, 'too blue', 'rejected calls are rejected');
|
||||
if (++count === 3) {
|
||||
off(stream, 'data', handle);
|
||||
done();
|
||||
}
|
||||
}));
|
||||
|
||||
function handle (e) {
|
||||
if (e.data !== 'firefox' || e.data !== 'rust')
|
||||
e.error = { data: e.data, error: 'too blue' };
|
||||
assert.ok(!~ids.indexOf(e.id), 'ID should be unique');
|
||||
assert.equal(e.event, 'sdk-x-test-multi', 'has event name');
|
||||
ids.push(e.id);
|
||||
emit(response, 'data', e);
|
||||
}
|
||||
};
|
||||
|
||||
exports.testSerialization = function (assert, done) {
|
||||
on(stream, 'data', handle);
|
||||
let object = { title: 'my test data' };
|
||||
let resObject;
|
||||
send('sdk-x-test-serialize', object).then(data => {
|
||||
data.title = 'another title';
|
||||
assert.equal(object.title, 'my test data', 'original object not modified');
|
||||
assert.equal(resObject.title, 'new title', 'object passed by value from host');
|
||||
off(stream, 'data', handle);
|
||||
done();
|
||||
}, (reason) => {
|
||||
assert.fail('should not call reject');
|
||||
});
|
||||
function handle (e) {
|
||||
e.data.title = 'new title';
|
||||
assert.equal(object.title, 'my test data', 'object passed by value to host');
|
||||
resObject = e.data;
|
||||
emit(response, 'data', e);
|
||||
}
|
||||
};
|
||||
|
||||
require('test').run(exports);
|
@ -5,7 +5,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { MatchPattern } = require("sdk/util/match-pattern");
|
||||
const { MatchPattern } = require("sdk/page-mod/match-pattern");
|
||||
|
||||
exports.testMatchPatternTestTrue = function(test) {
|
||||
function ok(pattern, url) {
|
||||
@ -35,8 +35,6 @@ exports.testMatchPatternTestTrue = function(test) {
|
||||
ok(/https:.*zilla.*/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753");
|
||||
ok('*.sample.com', 'http://ex.sample.com/foo.html');
|
||||
ok('*.amp.le.com', 'http://ex.amp.le.com');
|
||||
|
||||
ok('data:*', 'data:text/html;charset=utf-8,');
|
||||
};
|
||||
|
||||
exports.testMatchPatternTestFalse = function(test) {
|
||||
|
@ -1,35 +0,0 @@
|
||||
/* 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 { merge, extend, has, each } = require('sdk/util/object');
|
||||
|
||||
let o = {
|
||||
'paper': 0,
|
||||
'rock': 1,
|
||||
'scissors': 2
|
||||
}
|
||||
|
||||
//exports.testMerge = function(test) {}
|
||||
//exports.testExtend = function(test) {}
|
||||
|
||||
exports.testHas = function(test) {
|
||||
test.assertEqual(has(o, 'paper'), true, 'has correctly finds key');
|
||||
test.assertEqual(has(o, 'rock'), true, 'has correctly finds key');
|
||||
test.assertEqual(has(o, 'scissors'), true, 'has correctly finds key');
|
||||
test.assertEqual(has(o, 'nope'), false, 'has correctly does not find key');
|
||||
test.assertEqual(has(o, '__proto__'), false, 'has correctly does not find key');
|
||||
test.assertEqual(has(o, 'isPrototypeOf'), false, 'has correctly does not find key');
|
||||
};
|
||||
|
||||
exports.testEach = function(test) {
|
||||
var count = 0;
|
||||
var keys = new Set();
|
||||
each(o, function (value, key, object) {
|
||||
keys.add(key);
|
||||
test.assertEqual(o[key], value, 'Key and value pairs passed in');
|
||||
test.assertEqual(o, object, 'Object passed in');
|
||||
});
|
||||
test.assertEqual(keys.size, 3, 'All keys have been iterated upon');
|
||||
}
|
@ -143,7 +143,7 @@ exports.testPageModErrorHandling = function(test) {
|
||||
test.assertRaises(function() {
|
||||
new PageMod();
|
||||
},
|
||||
'The `include` option must always contain atleast one rule',
|
||||
'pattern is undefined',
|
||||
"PageMod() throws when 'include' option is not specified.");
|
||||
};
|
||||
|
||||
|
@ -147,7 +147,7 @@ exports.testValidateOptions = function(assert) {
|
||||
|
||||
assert.throws(
|
||||
function () Page({ onMessage: "This is not a function."}),
|
||||
/The option "onMessage" must be one of the following types: function/,
|
||||
/The event listener must be a function\./,
|
||||
"Validation correctly denied a non-function onMessage."
|
||||
);
|
||||
|
||||
@ -302,108 +302,6 @@ exports.testPingPong = function(assert, done) {
|
||||
});
|
||||
};
|
||||
|
||||
exports.testRedirect = function (assert, done) {
|
||||
let page = Page({
|
||||
contentURL: 'data:text/html;charset=utf-8,first-page',
|
||||
contentScript: '(function () {' +
|
||||
'if (/first-page/.test(document.location.href)) ' +
|
||||
' document.location.href = "data:text/html;charset=utf-8,redirect";' +
|
||||
'else ' +
|
||||
' self.port.emit("redirect", document.location.href);' +
|
||||
'})();'
|
||||
});
|
||||
|
||||
page.port.on('redirect', function (url) {
|
||||
assert.equal(url, 'data:text/html;charset=utf-8,redirect', 'Reinjects contentScript on reload');
|
||||
done();
|
||||
});
|
||||
};
|
||||
|
||||
exports.testRedirectIncludeArrays = function (assert, done) {
|
||||
let firstURL = 'data:text/html;charset=utf-8,first-page';
|
||||
let page = Page({
|
||||
contentURL: firstURL,
|
||||
contentScript: '(function () {' +
|
||||
'self.port.emit("load", document.location.href);' +
|
||||
' self.port.on("redirect", function (url) {' +
|
||||
' document.location.href = url;' +
|
||||
' })' +
|
||||
'})();',
|
||||
include: ['about:blank', 'data:*']
|
||||
});
|
||||
|
||||
page.port.on('load', function (url) {
|
||||
if (url === firstURL) {
|
||||
page.port.emit('redirect', 'about:blank');
|
||||
} else if (url === 'about:blank') {
|
||||
page.port.emit('redirect', 'about:home');
|
||||
assert.ok('`include` property handles arrays');
|
||||
assert.equal(url, 'about:blank', 'Redirects work with accepted domains');
|
||||
done();
|
||||
} else if (url === 'about:home') {
|
||||
assert.fail('Should not redirect to restricted domain');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.testRedirectFromWorker = function (assert, done) {
|
||||
let firstURL = 'data:text/html;charset=utf-8,first-page';
|
||||
let secondURL = 'data:text/html;charset=utf-8,second-page';
|
||||
let thirdURL = 'data:text/html;charset=utf-8,third-page';
|
||||
let page = Page({
|
||||
contentURL: firstURL,
|
||||
contentScript: '(function () {' +
|
||||
'self.port.emit("load", document.location.href);' +
|
||||
' self.port.on("redirect", function (url) {' +
|
||||
' document.location.href = url;' +
|
||||
' })' +
|
||||
'})();',
|
||||
include: 'data:*'
|
||||
});
|
||||
|
||||
page.port.on('load', function (url) {
|
||||
if (url === firstURL) {
|
||||
page.port.emit('redirect', secondURL);
|
||||
} else if (url === secondURL) {
|
||||
page.port.emit('redirect', thirdURL);
|
||||
} else if (url === thirdURL) {
|
||||
page.port.emit('redirect', 'about:home');
|
||||
assert.equal(url, thirdURL, 'Redirects work with accepted domains on include strings');
|
||||
done();
|
||||
} else {
|
||||
assert.fail('Should not redirect to unauthorized domains');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.testRedirectWithContentURL = function (assert, done) {
|
||||
let firstURL = 'data:text/html;charset=utf-8,first-page';
|
||||
let secondURL = 'data:text/html;charset=utf-8,second-page';
|
||||
let thirdURL = 'data:text/html;charset=utf-8,third-page';
|
||||
let page = Page({
|
||||
contentURL: firstURL,
|
||||
contentScript: '(function () {' +
|
||||
'self.port.emit("load", document.location.href);' +
|
||||
'})();',
|
||||
include: 'data:*'
|
||||
});
|
||||
|
||||
page.port.on('load', function (url) {
|
||||
if (url === firstURL) {
|
||||
page.contentURL = secondURL;
|
||||
} else if (url === secondURL) {
|
||||
page.contentURL = thirdURL;
|
||||
} else if (url === thirdURL) {
|
||||
page.contentURL = 'about:home';
|
||||
assert.equal(url, thirdURL, 'Redirects work with accepted domains on include strings');
|
||||
done();
|
||||
} else {
|
||||
assert.fail('Should not redirect to unauthorized domains');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
exports.testMultipleDestroys = function(assert) {
|
||||
let page = Page();
|
||||
page.destroy();
|
||||
|
@ -457,24 +457,6 @@ exports["test Panel Text Color"] = function(assert, done) {
|
||||
});
|
||||
};
|
||||
|
||||
// Bug 866333
|
||||
exports["test watch event name"] = function(assert, done) {
|
||||
const { Panel } = require('sdk/panel');
|
||||
|
||||
let html = "<html><head><style>body {color: yellow}</style></head>" +
|
||||
"<body><p>Foo</p></body></html>";
|
||||
|
||||
let panel = Panel({
|
||||
contentURL: "data:text/html;charset=utf-8," + encodeURI(html),
|
||||
contentScript: "self.port.emit('watch', 'test');"
|
||||
});
|
||||
panel.port.on("watch", function (msg) {
|
||||
assert.equal(msg, "test", 'watch event name works');
|
||||
panel.destroy();
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
// Bug 696552: Ensure panel.contentURL modification support
|
||||
exports["test Change Content URL"] = function(assert, done) {
|
||||
const { Panel } = require('sdk/panel');
|
||||
|
@ -1,285 +0,0 @@
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
// Adapted version of:
|
||||
// https://github.com/joyent/node/blob/v0.9.1/test/simple/test-path.js
|
||||
|
||||
exports['test path'] = function(assert) {
|
||||
|
||||
var system = require('sdk/system');
|
||||
var path = require('sdk/fs/path');
|
||||
var isWindows = require('sdk/system').platform.indexOf('win') === 0;
|
||||
|
||||
|
||||
// POSIX filenames may include control characters
|
||||
// c.f. http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html
|
||||
if (!isWindows) {
|
||||
var controlCharFilename = 'Icon' + String.fromCharCode(13);
|
||||
assert.equal(path.basename('/a/b/' + controlCharFilename),
|
||||
controlCharFilename);
|
||||
}
|
||||
|
||||
|
||||
assert.equal(path.dirname('/a/b/'), '/a');
|
||||
assert.equal(path.dirname('/a/b'), '/a');
|
||||
assert.equal(path.dirname('/a'), '/');
|
||||
assert.equal(path.dirname('/'), '/');
|
||||
|
||||
if (isWindows) {
|
||||
assert.equal(path.dirname('c:\\'), 'c:\\');
|
||||
assert.equal(path.dirname('c:\\foo'), 'c:\\');
|
||||
assert.equal(path.dirname('c:\\foo\\'), 'c:\\');
|
||||
assert.equal(path.dirname('c:\\foo\\bar'), 'c:\\foo');
|
||||
assert.equal(path.dirname('c:\\foo\\bar\\'), 'c:\\foo');
|
||||
assert.equal(path.dirname('c:\\foo\\bar\\baz'), 'c:\\foo\\bar');
|
||||
assert.equal(path.dirname('\\'), '\\');
|
||||
assert.equal(path.dirname('\\foo'), '\\');
|
||||
assert.equal(path.dirname('\\foo\\'), '\\');
|
||||
assert.equal(path.dirname('\\foo\\bar'), '\\foo');
|
||||
assert.equal(path.dirname('\\foo\\bar\\'), '\\foo');
|
||||
assert.equal(path.dirname('\\foo\\bar\\baz'), '\\foo\\bar');
|
||||
assert.equal(path.dirname('c:'), 'c:');
|
||||
assert.equal(path.dirname('c:foo'), 'c:');
|
||||
assert.equal(path.dirname('c:foo\\'), 'c:');
|
||||
assert.equal(path.dirname('c:foo\\bar'), 'c:foo');
|
||||
assert.equal(path.dirname('c:foo\\bar\\'), 'c:foo');
|
||||
assert.equal(path.dirname('c:foo\\bar\\baz'), 'c:foo\\bar');
|
||||
assert.equal(path.dirname('\\\\unc\\share'), '\\\\unc\\share');
|
||||
assert.equal(path.dirname('\\\\unc\\share\\foo'), '\\\\unc\\share\\');
|
||||
assert.equal(path.dirname('\\\\unc\\share\\foo\\'), '\\\\unc\\share\\');
|
||||
assert.equal(path.dirname('\\\\unc\\share\\foo\\bar'),
|
||||
'\\\\unc\\share\\foo');
|
||||
assert.equal(path.dirname('\\\\unc\\share\\foo\\bar\\'),
|
||||
'\\\\unc\\share\\foo');
|
||||
assert.equal(path.dirname('\\\\unc\\share\\foo\\bar\\baz'),
|
||||
'\\\\unc\\share\\foo\\bar');
|
||||
}
|
||||
|
||||
|
||||
assert.equal(path.extname(''), '');
|
||||
assert.equal(path.extname('/path/to/file'), '');
|
||||
assert.equal(path.extname('/path/to/file.ext'), '.ext');
|
||||
assert.equal(path.extname('/path.to/file.ext'), '.ext');
|
||||
assert.equal(path.extname('/path.to/file'), '');
|
||||
assert.equal(path.extname('/path.to/.file'), '');
|
||||
assert.equal(path.extname('/path.to/.file.ext'), '.ext');
|
||||
assert.equal(path.extname('/path/to/f.ext'), '.ext');
|
||||
assert.equal(path.extname('/path/to/..ext'), '.ext');
|
||||
assert.equal(path.extname('file'), '');
|
||||
assert.equal(path.extname('file.ext'), '.ext');
|
||||
assert.equal(path.extname('.file'), '');
|
||||
assert.equal(path.extname('.file.ext'), '.ext');
|
||||
assert.equal(path.extname('/file'), '');
|
||||
assert.equal(path.extname('/file.ext'), '.ext');
|
||||
assert.equal(path.extname('/.file'), '');
|
||||
assert.equal(path.extname('/.file.ext'), '.ext');
|
||||
assert.equal(path.extname('.path/file.ext'), '.ext');
|
||||
assert.equal(path.extname('file.ext.ext'), '.ext');
|
||||
assert.equal(path.extname('file.'), '.');
|
||||
assert.equal(path.extname('.'), '');
|
||||
assert.equal(path.extname('./'), '');
|
||||
assert.equal(path.extname('.file.ext'), '.ext');
|
||||
assert.equal(path.extname('.file'), '');
|
||||
assert.equal(path.extname('.file.'), '.');
|
||||
assert.equal(path.extname('.file..'), '.');
|
||||
assert.equal(path.extname('..'), '');
|
||||
assert.equal(path.extname('../'), '');
|
||||
assert.equal(path.extname('..file.ext'), '.ext');
|
||||
assert.equal(path.extname('..file'), '.file');
|
||||
assert.equal(path.extname('..file.'), '.');
|
||||
assert.equal(path.extname('..file..'), '.');
|
||||
assert.equal(path.extname('...'), '.');
|
||||
assert.equal(path.extname('...ext'), '.ext');
|
||||
assert.equal(path.extname('....'), '.');
|
||||
assert.equal(path.extname('file.ext/'), '');
|
||||
|
||||
if (isWindows) {
|
||||
// On windows, backspace is a path separator.
|
||||
assert.equal(path.extname('.\\'), '');
|
||||
assert.equal(path.extname('..\\'), '');
|
||||
assert.equal(path.extname('file.ext\\'), '');
|
||||
} else {
|
||||
// On unix, backspace is a valid name component like any other character.
|
||||
assert.equal(path.extname('.\\'), '');
|
||||
assert.equal(path.extname('..\\'), '.\\');
|
||||
assert.equal(path.extname('file.ext\\'), '.ext\\');
|
||||
}
|
||||
|
||||
// path.join tests
|
||||
var failures = [];
|
||||
var joinTests =
|
||||
// arguments result
|
||||
[[['.', 'x/b', '..', '/b/c.js'], 'x/b/c.js'],
|
||||
[['/.', 'x/b', '..', '/b/c.js'], '/x/b/c.js'],
|
||||
[['/foo', '../../../bar'], '/bar'],
|
||||
[['foo', '../../../bar'], '../../bar'],
|
||||
[['foo/', '../../../bar'], '../../bar'],
|
||||
[['foo/x', '../../../bar'], '../bar'],
|
||||
[['foo/x', './bar'], 'foo/x/bar'],
|
||||
[['foo/x/', './bar'], 'foo/x/bar'],
|
||||
[['foo/x/', '.', 'bar'], 'foo/x/bar'],
|
||||
[['./'], './'],
|
||||
[['.', './'], './'],
|
||||
[['.', '.', '.'], '.'],
|
||||
[['.', './', '.'], '.'],
|
||||
[['.', '/./', '.'], '.'],
|
||||
[['.', '/////./', '.'], '.'],
|
||||
[['.'], '.'],
|
||||
[['', '.'], '.'],
|
||||
[['', 'foo'], 'foo'],
|
||||
[['foo', '/bar'], 'foo/bar'],
|
||||
[['', '/foo'], '/foo'],
|
||||
[['', '', '/foo'], '/foo'],
|
||||
[['', '', 'foo'], 'foo'],
|
||||
[['foo', ''], 'foo'],
|
||||
[['foo/', ''], 'foo/'],
|
||||
[['foo', '', '/bar'], 'foo/bar'],
|
||||
[['./', '..', '/foo'], '../foo'],
|
||||
[['./', '..', '..', '/foo'], '../../foo'],
|
||||
[['.', '..', '..', '/foo'], '../../foo'],
|
||||
[['', '..', '..', '/foo'], '../../foo'],
|
||||
[['/'], '/'],
|
||||
[['/', '.'], '/'],
|
||||
[['/', '..'], '/'],
|
||||
[['/', '..', '..'], '/'],
|
||||
[[''], '.'],
|
||||
[['', ''], '.'],
|
||||
[[' /foo'], ' /foo'],
|
||||
[[' ', 'foo'], ' /foo'],
|
||||
[[' ', '.'], ' '],
|
||||
[[' ', '/'], ' /'],
|
||||
[[' ', ''], ' '],
|
||||
// filtration of non-strings.
|
||||
[['x', true, 7, 'y', null, {}], 'x/y']
|
||||
];
|
||||
joinTests.forEach(function(test) {
|
||||
var actual = path.join.apply(path, test[0]);
|
||||
var expected = isWindows ? test[1].replace(/\//g, '\\') : test[1];
|
||||
var message = 'path.join(' + test[0].map(JSON.stringify).join(',') + ')' +
|
||||
'\n expect=' + JSON.stringify(expected) +
|
||||
'\n actual=' + JSON.stringify(actual);
|
||||
if (actual !== expected) failures.push('\n' + message);
|
||||
// assert.equal(actual, expected, message);
|
||||
});
|
||||
assert.equal(failures.length, 0, failures.join(''));
|
||||
|
||||
// path normalize tests
|
||||
if (isWindows) {
|
||||
assert.equal(path.normalize('./fixtures///b/../b/c.js'),
|
||||
'fixtures\\b\\c.js');
|
||||
assert.equal(path.normalize('/foo/../../../bar'), '\\bar');
|
||||
assert.equal(path.normalize('a//b//../b'), 'a\\b');
|
||||
assert.equal(path.normalize('a//b//./c'), 'a\\b\\c');
|
||||
assert.equal(path.normalize('a//b//.'), 'a\\b');
|
||||
assert.equal(path.normalize('//server/share/dir/file.ext'),
|
||||
'\\\\server\\share\\dir\\file.ext');
|
||||
} else {
|
||||
assert.equal(path.normalize('./fixtures///b/../b/c.js'),
|
||||
'fixtures/b/c.js');
|
||||
assert.equal(path.normalize('/foo/../../../bar'), '/bar');
|
||||
assert.equal(path.normalize('a//b//../b'), 'a/b');
|
||||
assert.equal(path.normalize('a//b//./c'), 'a/b/c');
|
||||
assert.equal(path.normalize('a//b//.'), 'a/b');
|
||||
}
|
||||
|
||||
// path.resolve tests
|
||||
if (isWindows) {
|
||||
// windows
|
||||
var resolveTests =
|
||||
// arguments result
|
||||
[[['c:/blah\\blah', 'd:/games', 'c:../a'], 'c:\\blah\\a'],
|
||||
[['c:/ignore', 'd:\\a/b\\c/d', '\\e.exe'], 'd:\\e.exe'],
|
||||
[['c:/ignore', 'c:/some/file'], 'c:\\some\\file'],
|
||||
[['d:/ignore', 'd:some/dir//'], 'd:\\ignore\\some\\dir'],
|
||||
[['.'], system.pathFor('CurProcD')],
|
||||
[['//server/share', '..', 'relative\\'], '\\\\server\\share\\relative']];
|
||||
} else {
|
||||
// Posix
|
||||
var resolveTests =
|
||||
// arguments result
|
||||
[[['/var/lib', '../', 'file/'], '/var/file'],
|
||||
[['/var/lib', '/../', 'file/'], '/file'],
|
||||
[['a/b/c/', '../../..'], system.pathFor('CurProcD')],
|
||||
[['.'], system.pathFor('CurProcD')],
|
||||
[['/some/dir', '.', '/absolute/'], '/absolute']];
|
||||
}
|
||||
var failures = [];
|
||||
resolveTests.forEach(function(test) {
|
||||
var actual = path.resolve.apply(path, test[0]);
|
||||
var expected = test[1];
|
||||
var message = 'path.resolve(' + test[0].map(JSON.stringify).join(',') + ')' +
|
||||
'\n expect=' + JSON.stringify(expected) +
|
||||
'\n actual=' + JSON.stringify(actual);
|
||||
if (actual !== expected) failures.push('\n' + message);
|
||||
// assert.equal(actual, expected, message);
|
||||
});
|
||||
assert.equal(failures.length, 0, failures.join(''));
|
||||
|
||||
// path.relative tests
|
||||
if (isWindows) {
|
||||
// windows
|
||||
var relativeTests =
|
||||
// arguments result
|
||||
[['c:/blah\\blah', 'd:/games', 'd:\\games'],
|
||||
['c:/aaaa/bbbb', 'c:/aaaa', '..'],
|
||||
['c:/aaaa/bbbb', 'c:/cccc', '..\\..\\cccc'],
|
||||
['c:/aaaa/bbbb', 'c:/aaaa/bbbb', ''],
|
||||
['c:/aaaa/bbbb', 'c:/aaaa/cccc', '..\\cccc'],
|
||||
['c:/aaaa/', 'c:/aaaa/cccc', 'cccc'],
|
||||
['c:/', 'c:\\aaaa\\bbbb', 'aaaa\\bbbb'],
|
||||
['c:/aaaa/bbbb', 'd:\\', 'd:\\']];
|
||||
} else {
|
||||
// posix
|
||||
var relativeTests =
|
||||
// arguments result
|
||||
[['/var/lib', '/var', '..'],
|
||||
['/var/lib', '/bin', '../../bin'],
|
||||
['/var/lib', '/var/lib', ''],
|
||||
['/var/lib', '/var/apache', '../apache'],
|
||||
['/var/', '/var/lib', 'lib'],
|
||||
['/', '/var/lib', 'var/lib']];
|
||||
}
|
||||
var failures = [];
|
||||
relativeTests.forEach(function(test) {
|
||||
var actual = path.relative(test[0], test[1]);
|
||||
var expected = test[2];
|
||||
var message = 'path.relative(' +
|
||||
test.slice(0, 2).map(JSON.stringify).join(',') +
|
||||
')' +
|
||||
'\n expect=' + JSON.stringify(expected) +
|
||||
'\n actual=' + JSON.stringify(actual);
|
||||
if (actual !== expected) failures.push('\n' + message);
|
||||
});
|
||||
assert.equal(failures.length, 0, failures.join(''));
|
||||
|
||||
// path.sep tests
|
||||
if (isWindows) {
|
||||
// windows
|
||||
assert.equal(path.sep, '\\');
|
||||
} else {
|
||||
// posix
|
||||
assert.equal(path.sep, '/');
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
require('test').run(exports);
|
@ -1,75 +0,0 @@
|
||||
/* 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 { defer, all } = require('sdk/core/promise');
|
||||
const { TreeNode } = require('sdk/places/utils');
|
||||
const { setTimeout } = require('sdk/timers');
|
||||
|
||||
exports['test construct tree'] = function (assert) {
|
||||
let tree = TreeNode(1);
|
||||
tree.add([2, 3, 4]);
|
||||
tree.get(2).add([2.1, 2.2, 2.3]);
|
||||
let newTreeNode = TreeNode(4.3);
|
||||
newTreeNode.add([4.31, 4.32]);
|
||||
tree.get(4).add([4.1, 4.2, newTreeNode]);
|
||||
|
||||
assert.equal(tree.get(2).value, 2, 'get returns node with correct value');
|
||||
assert.equal(tree.get(2.3).value, 2.3, 'get returns node with correct value');
|
||||
assert.equal(tree.get(4.32).value, 4.32, 'get returns node even if created from nested node');
|
||||
assert.equal(tree.get(4).children.length, 3, 'nodes have correct children length');
|
||||
assert.equal(tree.get(3).children.length, 0, 'nodes have correct children length');
|
||||
|
||||
assert.equal(tree.get(4).get(4.32).value, 4.32, 'node.get descends from itself');
|
||||
assert.equal(tree.get(4).get(2), null, 'node.get descends from itself fails if not descendant');
|
||||
};
|
||||
|
||||
exports['test walk'] = function (assert) {
|
||||
let resultsAll = [];
|
||||
let tree = TreeNode(1);
|
||||
tree.add([2, 3, 4]);
|
||||
tree.get(2).add([2.1, 2.2]);
|
||||
|
||||
tree.walk(function (node) {
|
||||
resultsAll.push(node.value);
|
||||
});
|
||||
|
||||
[1, 2, 2.1, 2.2, 3, 4].forEach(function (num) {
|
||||
assert.ok(~resultsAll.indexOf(num), 'function applied to each node from root');
|
||||
});
|
||||
|
||||
let resultsNode = [];
|
||||
tree.get(2).walk(function (node) resultsNode.push(node.value));
|
||||
|
||||
[2, 2.1, 2.2].forEach(function (num) {
|
||||
assert.ok(~resultsNode.indexOf(num), 'function applied to each node from node');
|
||||
});
|
||||
};
|
||||
|
||||
exports['test async walk'] = function (assert, done) {
|
||||
let resultsAll = [];
|
||||
let tree = TreeNode(1);
|
||||
tree.add([2, 3, 4]);
|
||||
tree.get(2).add([2.1, 2.2]);
|
||||
|
||||
tree.walk(function (node) {
|
||||
let deferred = defer();
|
||||
setTimeout(function () {
|
||||
resultsAll.push(node.value);
|
||||
deferred.resolve(node.value);
|
||||
}, node.value === 2 ? 50 : 5);
|
||||
return deferred.promise;
|
||||
}).then(function () {
|
||||
[1, 2, 2.1, 2.2, 3, 4].forEach(function (num) {
|
||||
assert.ok(~resultsAll.indexOf(num), 'function applied to each node from root');
|
||||
});
|
||||
assert.ok(resultsAll.indexOf(2) < resultsAll.indexOf(2.1),
|
||||
'child should wait for parent to complete');
|
||||
assert.ok(resultsAll.indexOf(2) < resultsAll.indexOf(2.2),
|
||||
'child should wait for parent to complete');
|
||||
done();
|
||||
});
|
||||
};
|
||||
|
||||
require('test').run(exports);
|
@ -17,9 +17,6 @@ const { LoaderWithHookedConsole } = require("sdk/test/loader");
|
||||
const { getMode, isGlobalPBSupported,
|
||||
isWindowPBSupported, isTabPBSupported } = require('sdk/private-browsing/utils');
|
||||
const { pb } = require('./private-browsing/helper');
|
||||
const prefs = require('sdk/preferences/service');
|
||||
|
||||
const kAutoStartPref = "browser.privatebrowsing.autostart";
|
||||
|
||||
// is global pb is enabled?
|
||||
if (isGlobalPBSupported) {
|
||||
@ -108,12 +105,3 @@ exports.testGetOwnerWindow = function(test) {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.testNewGlobalPBService = function(test) {
|
||||
test.assertEqual(isPrivate(), false, 'isPrivate() is false by default');
|
||||
prefs.set(kAutoStartPref, true);
|
||||
test.assertEqual(prefs.get(kAutoStartPref, false), true, kAutoStartPref + ' is true now');
|
||||
test.assertEqual(isPrivate(), true, 'isPrivate() is true now');
|
||||
prefs.set(kAutoStartPref, false);
|
||||
test.assertEqual(isPrivate(), false, 'isPrivate() is false again');
|
||||
}
|
||||
|
@ -1,79 +0,0 @@
|
||||
/* 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 { Rules } = require('sdk/util/rules');
|
||||
const { on, off, emit } = require('sdk/event/core');
|
||||
|
||||
exports.testAdd = function (test, done) {
|
||||
let rules = Rules();
|
||||
let urls = [
|
||||
'http://www.firefox.com',
|
||||
'*.mozilla.org',
|
||||
'*.html5audio.org'
|
||||
];
|
||||
let count = 0;
|
||||
on(rules, 'add', function (rule) {
|
||||
if (count < urls.length) {
|
||||
test.ok(rules.get(rule), 'rule added to internal registry');
|
||||
test.equal(rule, urls[count], 'add event fired with proper params');
|
||||
if (++count < urls.length) rules.add(urls[count]);
|
||||
else done();
|
||||
}
|
||||
});
|
||||
rules.add(urls[0]);
|
||||
};
|
||||
|
||||
exports.testRemove = function (test, done) {
|
||||
let rules = Rules();
|
||||
let urls = [
|
||||
'http://www.firefox.com',
|
||||
'*.mozilla.org',
|
||||
'*.html5audio.org'
|
||||
];
|
||||
let count = 0;
|
||||
on(rules, 'remove', function (rule) {
|
||||
if (count < urls.length) {
|
||||
test.ok(!rules.get(rule), 'rule removed to internal registry');
|
||||
test.equal(rule, urls[count], 'remove event fired with proper params');
|
||||
if (++count < urls.length) rules.remove(urls[count]);
|
||||
else done();
|
||||
}
|
||||
});
|
||||
urls.forEach(function (url) rules.add(url));
|
||||
rules.remove(urls[0]);
|
||||
};
|
||||
|
||||
exports.testMatchesAny = function(test) {
|
||||
let rules = Rules();
|
||||
rules.add('*.mozilla.org');
|
||||
rules.add('data:*');
|
||||
matchTest('http://mozilla.org', true);
|
||||
matchTest('http://www.mozilla.org', true);
|
||||
matchTest('http://www.google.com', false);
|
||||
matchTest('data:text/html;charset=utf-8,', true);
|
||||
|
||||
function matchTest(string, expected) {
|
||||
test.equal(rules.matchesAny(string), expected,
|
||||
'Expected to find ' + string + ' in rules');
|
||||
}
|
||||
};
|
||||
|
||||
exports.testIterable = function(test) {
|
||||
let rules = Rules();
|
||||
rules.add('*.mozilla.org');
|
||||
rules.add('data:*');
|
||||
rules.add('http://google.com');
|
||||
rules.add('http://addons.mozilla.org');
|
||||
rules.remove('http://google.com');
|
||||
|
||||
test.equal(rules.length, 3, 'has correct length of keys');
|
||||
Array.forEach(rules, function (rule, i) {
|
||||
test.equal(rule, ['*.mozilla.org', 'data:*', 'http://addons.mozilla.org'][i]);
|
||||
});
|
||||
for (let i in rules)
|
||||
test.equal(rules[i], ['*.mozilla.org', 'data:*', 'http://addons.mozilla.org'][i]);
|
||||
};
|
||||
|
||||
require('test').run(exports);
|
@ -42,47 +42,11 @@ exports.testTabCounts = function(test) {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// TEST: tabs.activeTab getter
|
||||
exports.testActiveTab_getter = function(test) {
|
||||
test.waitUntilDone();
|
||||
let evtCount = 0;
|
||||
let activeTab = null;
|
||||
|
||||
function endTest(type, tab) {
|
||||
if (type == 'activate') {
|
||||
test.assertStrictEqual(tabs.activeTab, tab, 'the active tab is the opened tab');
|
||||
activeTab = tabs.activeTab;
|
||||
}
|
||||
else {
|
||||
test.assertEqual(tab.url, url, 'the opened tab has the correct url');
|
||||
}
|
||||
|
||||
if (++evtCount != 2)
|
||||
return;
|
||||
|
||||
test.assertStrictEqual(activeTab, tab, 'the active tab is the ready tab');
|
||||
test.assertStrictEqual(tabs.activeTab, tab, 'the active tab is the ready tab');
|
||||
|
||||
tab.close(function() {
|
||||
// end test
|
||||
test.done();
|
||||
});
|
||||
}
|
||||
|
||||
let url = URL.replace("#title#", "testActiveTab_getter");
|
||||
tabs.open({
|
||||
url: url,
|
||||
onReady: endTest.bind(null, 'ready'),
|
||||
onActivate: endTest.bind(null, 'activate')
|
||||
});
|
||||
};
|
||||
|
||||
// TEST: tab.activate()
|
||||
exports.testActiveTab_setter = function(test) {
|
||||
exports.testActiveTab_setter_alt = function(test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
let url = URL.replace("#title#", "testActiveTab_setter");
|
||||
let url = URL.replace("#title#", "testActiveTab_setter_alt");
|
||||
let tab1URL = URL.replace("#title#", "tab1");
|
||||
|
||||
tabs.open({
|
||||
|
@ -1,303 +1,313 @@
|
||||
/* 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 { URL, toFilename, fromFilename, isValidURI, getTLD, DataURL } = require('sdk/url');
|
||||
const { pathFor } = require('sdk/system');
|
||||
const file = require('sdk/io/file');
|
||||
const tabs = require('sdk/tabs');
|
||||
const { decode } = require('sdk/base64');
|
||||
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");
|
||||
|
||||
const httpd = require('sdk/test/httpd');
|
||||
const port = 8099;
|
||||
exports.testResolve = function(test) {
|
||||
test.assertEqual(url.URL("bar", "http://www.foo.com/").toString(),
|
||||
"http://www.foo.com/bar");
|
||||
|
||||
const defaultLocation = '{\'scheme\':\'about\',\'userPass\':null,\'host\':null,\'hostname\':null,\'port\':null,\'path\':\'addons\',\'pathname\':\'addons\',\'hash\':\'\',\'href\':\'about:addons\',\'origin\':\'about:\',\'protocol\':\'about:\',\'search\':\'\'}'.replace(/'/g, '"');
|
||||
test.assertEqual(url.URL("bar", "http://www.foo.com"),
|
||||
"http://www.foo.com/bar");
|
||||
|
||||
exports.testResolve = function(assert) {
|
||||
assert.equal(URL('bar', 'http://www.foo.com/').toString(),
|
||||
'http://www.foo.com/bar');
|
||||
test.assertEqual(url.URL("http://bar.com/", "http://foo.com/"),
|
||||
"http://bar.com/",
|
||||
"relative should override base");
|
||||
|
||||
assert.equal(URL('bar', 'http://www.foo.com'),
|
||||
'http://www.foo.com/bar');
|
||||
test.assertRaises(function() { url.URL("blah"); },
|
||||
"malformed URI: blah",
|
||||
"url.resolve() should throw malformed URI on base");
|
||||
|
||||
assert.equal(URL('http://bar.com/', 'http://foo.com/'),
|
||||
'http://bar.com/',
|
||||
'relative should override base');
|
||||
test.assertRaises(function() { url.URL("chrome://global"); },
|
||||
"invalid URI: chrome://global",
|
||||
"url.resolve() should throw invalid URI on base");
|
||||
|
||||
assert.throws(function() { URL('blah'); },
|
||||
/malformed URI: blah/i,
|
||||
'url.resolve() should throw malformed URI on base');
|
||||
test.assertRaises(function() { url.URL("chrome://foo/bar"); },
|
||||
"invalid URI: chrome://foo/bar",
|
||||
"url.resolve() should throw on bad chrome URI");
|
||||
|
||||
assert.throws(function() { URL('chrome://global'); },
|
||||
/invalid URI: chrome:\/\/global/i,
|
||||
'url.resolve() should throw invalid URI on base');
|
||||
|
||||
assert.throws(function() { URL('chrome://foo/bar'); },
|
||||
/invalid URI: chrome:\/\/foo\/bar/i,
|
||||
'url.resolve() should throw on bad chrome URI');
|
||||
|
||||
assert.equal(URL('', 'http://www.foo.com'),
|
||||
'http://www.foo.com/',
|
||||
'url.resolve() should add slash to end of domain');
|
||||
test.assertEqual(url.URL("", "http://www.foo.com"),
|
||||
"http://www.foo.com/",
|
||||
"url.resolve() should add slash to end of domain");
|
||||
};
|
||||
|
||||
exports.testParseHttp = function(assert) {
|
||||
var aUrl = 'http://sub.foo.com/bar?locale=en-US&otherArg=%20x%20#myhash';
|
||||
var info = URL(aUrl);
|
||||
|
||||
assert.equal(info.scheme, 'http');
|
||||
assert.equal(info.protocol, 'http:');
|
||||
assert.equal(info.host, 'sub.foo.com');
|
||||
assert.equal(info.hostname, 'sub.foo.com');
|
||||
assert.equal(info.port, null);
|
||||
assert.equal(info.userPass, null);
|
||||
assert.equal(info.path, '/bar?locale=en-US&otherArg=%20x%20#myhash');
|
||||
assert.equal(info.pathname, '/bar');
|
||||
assert.equal(info.href, aUrl);
|
||||
assert.equal(info.hash, '#myhash');
|
||||
assert.equal(info.search, '?locale=en-US&otherArg=%20x%20');
|
||||
exports.testParseHttp = function(test) {
|
||||
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.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?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 (assert) {
|
||||
var info = URL('https://www.moz.com/some/page.html');
|
||||
assert.equal(info.hash, '');
|
||||
assert.equal(info.search, '');
|
||||
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('https://www.sub.moz.com/page.html#justhash');
|
||||
assert.equal(hashOnly.search, '');
|
||||
assert.equal(hashOnly.hash, '#justhash');
|
||||
var hashOnly = url.URL("https://www.sub.moz.com/page.html#justhash");
|
||||
test.assertEqual(hashOnly.search, "");
|
||||
test.assertEqual(hashOnly.hash, "#justhash");
|
||||
|
||||
var queryOnly = URL('https://www.sub.moz.com/page.html?my=query');
|
||||
assert.equal(queryOnly.search, '?my=query');
|
||||
assert.equal(queryOnly.hash, '');
|
||||
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('http://www.moz.org?');
|
||||
assert.equal(qMark.search, '');
|
||||
assert.equal(qMark.hash, '');
|
||||
var qMark = url.URL("http://www.moz.org?");
|
||||
test.assertEqual(qMark.search, "");
|
||||
test.assertEqual(qMark.hash, "");
|
||||
|
||||
var hash = URL('http://www.moz.org#');
|
||||
assert.equal(hash.search, '');
|
||||
assert.equal(hash.hash, '');
|
||||
var hash = url.URL("http://www.moz.org#");
|
||||
test.assertEqual(hash.search, "");
|
||||
test.assertEqual(hash.hash, "");
|
||||
|
||||
var empty = URL('http://www.moz.org?#');
|
||||
assert.equal(hash.search, '');
|
||||
assert.equal(hash.hash, '');
|
||||
var empty = url.URL("http://www.moz.org?#");
|
||||
test.assertEqual(hash.search, "");
|
||||
test.assertEqual(hash.hash, "");
|
||||
|
||||
var strange = URL('http://moz.org?test1#test2?test3');
|
||||
assert.equal(strange.search, '?test1');
|
||||
assert.equal(strange.hash, '#test2?test3');
|
||||
var strange = url.URL("http://moz.org?test1#test2?test3");
|
||||
test.assertEqual(strange.search, "?test1");
|
||||
test.assertEqual(strange.hash, "#test2?test3");
|
||||
};
|
||||
|
||||
exports.testParseHttpWithPort = function(assert) {
|
||||
var info = URL('http://foo.com:5/bar');
|
||||
assert.equal(info.port, 5);
|
||||
exports.testParseHttpWithPort = function(test) {
|
||||
var info = url.URL("http://foo.com:5/bar");
|
||||
test.assertEqual(info.port, 5);
|
||||
};
|
||||
|
||||
exports.testParseChrome = function(assert) {
|
||||
var info = URL('chrome://global/content/blah');
|
||||
assert.equal(info.scheme, 'chrome');
|
||||
assert.equal(info.host, 'global');
|
||||
assert.equal(info.port, null);
|
||||
assert.equal(info.userPass, null);
|
||||
assert.equal(info.path, '/content/blah');
|
||||
exports.testParseChrome = function(test) {
|
||||
var info = url.URL("chrome://global/content/blah");
|
||||
test.assertEqual(info.scheme, "chrome");
|
||||
test.assertEqual(info.host, "global");
|
||||
test.assertEqual(info.port, null);
|
||||
test.assertEqual(info.userPass, null);
|
||||
test.assertEqual(info.path, "/content/blah");
|
||||
};
|
||||
|
||||
exports.testParseAbout = function(assert) {
|
||||
var info = URL('about:boop');
|
||||
assert.equal(info.scheme, 'about');
|
||||
assert.equal(info.host, null);
|
||||
assert.equal(info.port, null);
|
||||
assert.equal(info.userPass, null);
|
||||
assert.equal(info.path, 'boop');
|
||||
exports.testParseAbout = function(test) {
|
||||
var info = url.URL("about:boop");
|
||||
test.assertEqual(info.scheme, "about");
|
||||
test.assertEqual(info.host, null);
|
||||
test.assertEqual(info.port, null);
|
||||
test.assertEqual(info.userPass, null);
|
||||
test.assertEqual(info.path, "boop");
|
||||
};
|
||||
|
||||
exports.testParseFTP = function(assert) {
|
||||
var info = URL('ftp://1.2.3.4/foo');
|
||||
assert.equal(info.scheme, 'ftp');
|
||||
assert.equal(info.host, '1.2.3.4');
|
||||
assert.equal(info.port, null);
|
||||
assert.equal(info.userPass, null);
|
||||
assert.equal(info.path, '/foo');
|
||||
exports.testParseFTP = function(test) {
|
||||
var info = url.URL("ftp://1.2.3.4/foo");
|
||||
test.assertEqual(info.scheme, "ftp");
|
||||
test.assertEqual(info.host, "1.2.3.4");
|
||||
test.assertEqual(info.port, null);
|
||||
test.assertEqual(info.userPass, null);
|
||||
test.assertEqual(info.path, "/foo");
|
||||
};
|
||||
|
||||
exports.testParseFTPWithUserPass = function(assert) {
|
||||
var info = URL('ftp://user:pass@1.2.3.4/foo');
|
||||
assert.equal(info.userPass, 'user:pass');
|
||||
exports.testParseFTPWithUserPass = function(test) {
|
||||
var info = url.URL("ftp://user:pass@1.2.3.4/foo");
|
||||
test.assertEqual(info.userPass, "user:pass");
|
||||
};
|
||||
|
||||
exports.testToFilename = function(assert) {
|
||||
assert.throws(
|
||||
function() { toFilename('resource://nonexistent'); },
|
||||
/resource does not exist: resource:\/\/nonexistent\//i,
|
||||
'toFilename() on nonexistent resources should throw'
|
||||
exports.testToFilename = function(test) {
|
||||
test.assertRaises(
|
||||
function() { url.toFilename("resource://nonexistent"); },
|
||||
"resource does not exist: resource://nonexistent/",
|
||||
"url.toFilename() on nonexistent resources should throw"
|
||||
);
|
||||
|
||||
assert.throws(
|
||||
function() { toFilename('http://foo.com/'); },
|
||||
/cannot map to filename: http:\/\/foo.com\//i,
|
||||
'toFilename() on http: URIs should raise error'
|
||||
test.assertRaises(
|
||||
function() { url.toFilename("http://foo.com/"); },
|
||||
"cannot map to filename: http://foo.com/",
|
||||
"url.toFilename() on http: URIs should raise error"
|
||||
);
|
||||
|
||||
try {
|
||||
assert.ok(
|
||||
/.*console\.xul$/.test(toFilename('chrome://global/content/console.xul')),
|
||||
'toFilename() w/ console.xul works when it maps to filesystem'
|
||||
test.assertMatches(
|
||||
url.toFilename("chrome://global/content/console.xul"),
|
||||
/.*console\.xul$/,
|
||||
"url.toFilename() w/ console.xul works when it maps to filesystem"
|
||||
);
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
if (/chrome url isn\'t on filesystem/.test(e.message))
|
||||
assert.pass('accessing console.xul in jar raises exception');
|
||||
test.pass("accessing console.xul in jar raises exception");
|
||||
else
|
||||
assert.fail('accessing console.xul raises ' + e);
|
||||
test.fail("accessing console.xul raises " + e);
|
||||
}
|
||||
|
||||
// TODO: Are there any chrome URLs that we're certain exist on the
|
||||
// filesystem?
|
||||
// assert.ok(/.*main\.js$/.test(toFilename('chrome://myapp/content/main.js')));
|
||||
// test.assertMatches(url.toFilename("chrome://myapp/content/main.js"),
|
||||
// /.*main\.js$/);
|
||||
};
|
||||
|
||||
exports.testFromFilename = function(assert) {
|
||||
var profileDirName = require('sdk/system').pathFor('ProfD');
|
||||
var fileUrl = fromFilename(profileDirName);
|
||||
assert.equal(URL(fileUrl).scheme, 'file',
|
||||
'toFilename() should return a file: url');
|
||||
assert.equal(fromFilename(toFilename(fileUrl)), fileUrl);
|
||||
exports.testFromFilename = function(test) {
|
||||
var profileDirName = require("sdk/system").pathFor("ProfD");
|
||||
var fileUrl = url.fromFilename(profileDirName);
|
||||
test.assertEqual(url.URL(fileUrl).scheme, 'file',
|
||||
'url.toFilename() should return a file: url');
|
||||
test.assertEqual(url.fromFilename(url.toFilename(fileUrl)),
|
||||
fileUrl);
|
||||
};
|
||||
|
||||
exports.testURL = function(assert) {
|
||||
assert.ok(URL('h:foo') instanceof URL, 'instance is of correct type');
|
||||
assert.throws(function() URL(),
|
||||
/malformed URI: undefined/i,
|
||||
'url.URL should throw on undefined');
|
||||
assert.throws(function() URL(''),
|
||||
/malformed URI: /i,
|
||||
'url.URL should throw on empty string');
|
||||
assert.throws(function() URL('foo'),
|
||||
/malformed URI: foo/i,
|
||||
'url.URL should throw on invalid URI');
|
||||
assert.ok(URL('h:foo').scheme, 'has scheme');
|
||||
assert.equal(URL('h:foo').toString(),
|
||||
'h:foo',
|
||||
'toString should roundtrip');
|
||||
exports.testURL = function(test) {
|
||||
let URL = url.URL;
|
||||
test.assert(URL("h:foo") instanceof URL, "instance is of correct type");
|
||||
test.assertRaises(function() URL(),
|
||||
"malformed URI: undefined",
|
||||
"url.URL should throw on undefined");
|
||||
test.assertRaises(function() URL(""),
|
||||
"malformed URI: ",
|
||||
"url.URL should throw on empty string");
|
||||
test.assertRaises(function() URL("foo"),
|
||||
"malformed URI: foo",
|
||||
"url.URL should throw on invalid URI");
|
||||
test.assert(URL("h:foo").scheme, "has scheme");
|
||||
test.assertEqual(URL("h:foo").toString(),
|
||||
"h:foo",
|
||||
"toString should roundtrip");
|
||||
// test relative + base
|
||||
assert.equal(URL('mypath', 'http://foo').toString(),
|
||||
'http://foo/mypath',
|
||||
'relative URL resolved to base');
|
||||
test.assertEqual(URL("mypath", "http://foo").toString(),
|
||||
"http://foo/mypath",
|
||||
"relative URL resolved to base");
|
||||
// test relative + no base
|
||||
assert.throws(function() URL('path').toString(),
|
||||
/malformed URI: path/i,
|
||||
'no base for relative URI should throw');
|
||||
test.assertRaises(function() URL("path").toString(),
|
||||
"malformed URI: path",
|
||||
"no base for relative URI should throw");
|
||||
|
||||
let a = URL('h:foo');
|
||||
let a = URL("h:foo");
|
||||
let b = URL(a);
|
||||
assert.equal(b.toString(),
|
||||
'h:foo',
|
||||
'a URL can be initialized from another URL');
|
||||
assert.notStrictEqual(a, b,
|
||||
'a URL initialized from another URL is not the same object');
|
||||
assert.ok(a == 'h:foo',
|
||||
'toString is implicit when a URL is compared to a string via ==');
|
||||
assert.strictEqual(a + '', 'h:foo',
|
||||
'toString is implicit when a URL is concatenated to a string');
|
||||
test.assertEqual(b.toString(),
|
||||
"h:foo",
|
||||
"a URL can be initialized from another URL");
|
||||
test.assertNotStrictEqual(a, b,
|
||||
"a URL initialized from another URL is not the same object");
|
||||
test.assert(a == "h:foo",
|
||||
"toString is implicit when a URL is compared to a string via ==");
|
||||
test.assertStrictEqual(a + "", "h:foo",
|
||||
"toString is implicit when a URL is concatenated to a string");
|
||||
};
|
||||
|
||||
exports.testStringInterface = function(assert) {
|
||||
var EM = 'about:addons';
|
||||
exports.testStringInterface = function(test) {
|
||||
let URL = url.URL;
|
||||
var EM = "about:addons";
|
||||
var a = URL(EM);
|
||||
|
||||
// make sure the standard URL properties are enumerable and not the String interface bits
|
||||
assert.equal(Object.keys(a),
|
||||
'scheme,userPass,host,hostname,port,path,pathname,hash,href,origin,protocol,search',
|
||||
'enumerable key list check for URL.');
|
||||
assert.equal(
|
||||
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),
|
||||
defaultLocation,
|
||||
'JSON.stringify should return a object with correct props and vals.');
|
||||
"{\"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
|
||||
assert.equal(a.indexOf(':'), EM.indexOf(':'), 'indexOf on URL works');
|
||||
assert.equal(a.valueOf(), EM.valueOf(), 'valueOf on URL works.');
|
||||
assert.equal(a.toSource(), EM.toSource(), 'toSource on URL works.');
|
||||
assert.equal(a.lastIndexOf('a'), EM.lastIndexOf('a'), 'lastIndexOf on URL works.');
|
||||
assert.equal(a.match('t:').toString(), EM.match('t:').toString(), 'match on URL works.');
|
||||
assert.equal(a.toUpperCase(), EM.toUpperCase(), 'toUpperCase on URL works.');
|
||||
assert.equal(a.toLowerCase(), EM.toLowerCase(), 'toLowerCase on URL works.');
|
||||
assert.equal(a.split(':').toString(), EM.split(':').toString(), 'split on URL works.');
|
||||
assert.equal(a.charAt(2), EM.charAt(2), 'charAt on URL works.');
|
||||
assert.equal(a.charCodeAt(2), EM.charCodeAt(2), 'charCodeAt on URL works.');
|
||||
assert.equal(a.concat(EM), EM.concat(a), 'concat on URL works.');
|
||||
assert.equal(a.substr(2,3), EM.substr(2,3), 'substr on URL works.');
|
||||
assert.equal(a.substring(2,3), EM.substring(2,3), 'substring on URL works.');
|
||||
assert.equal(a.trim(), EM.trim(), 'trim on URL works.');
|
||||
assert.equal(a.trimRight(), EM.trimRight(), 'trimRight on URL works.');
|
||||
assert.equal(a.trimLeft(), EM.trimLeft(), 'trimLeft on URL works.');
|
||||
test.assertEqual(a.indexOf(":"), EM.indexOf(":"), "indexOf on URL works");
|
||||
test.assertEqual(a.valueOf(), EM.valueOf(), "valueOf on URL works.");
|
||||
test.assertEqual(a.toSource(), EM.toSource(), "toSource on URL works.");
|
||||
test.assertEqual(a.lastIndexOf("a"), EM.lastIndexOf("a"), "lastIndexOf on URL works.");
|
||||
test.assertEqual(a.match("t:").toString(), EM.match("t:").toString(), "match on URL works.");
|
||||
test.assertEqual(a.toUpperCase(), EM.toUpperCase(), "toUpperCase on URL works.");
|
||||
test.assertEqual(a.toLowerCase(), EM.toLowerCase(), "toLowerCase on URL works.");
|
||||
test.assertEqual(a.split(":").toString(), EM.split(":").toString(), "split on URL works.");
|
||||
test.assertEqual(a.charAt(2), EM.charAt(2), "charAt on URL works.");
|
||||
test.assertEqual(a.charCodeAt(2), EM.charCodeAt(2), "charCodeAt on URL works.");
|
||||
test.assertEqual(a.concat(EM), EM.concat(a), "concat on URL works.");
|
||||
test.assertEqual(a.substr(2,3), EM.substr(2,3), "substr on URL works.");
|
||||
test.assertEqual(a.substring(2,3), EM.substring(2,3), "substring on URL works.");
|
||||
test.assertEqual(a.trim(), EM.trim(), "trim on URL works.");
|
||||
test.assertEqual(a.trimRight(), EM.trimRight(), "trimRight on URL works.");
|
||||
test.assertEqual(a.trimLeft(), EM.trimLeft(), "trimLeft on URL works.");
|
||||
}
|
||||
|
||||
exports.testDataURLwithouthURI = function (assert) {
|
||||
exports.testDataURLwithouthURI = function (test) {
|
||||
const { DataURL } = url;
|
||||
|
||||
let dataURL = new DataURL();
|
||||
|
||||
assert.equal(dataURL.base64, false, 'base64 is false for empty uri')
|
||||
assert.equal(dataURL.data, '', 'data is an empty string for empty uri')
|
||||
assert.equal(dataURL.mimeType, '', 'mimeType is an empty string for empty uri')
|
||||
assert.equal(Object.keys(dataURL.parameters).length, 0, 'parameters is an empty object for empty uri');
|
||||
test.assertEqual(dataURL.base64, false, "base64 is false for empty uri")
|
||||
test.assertEqual(dataURL.data, "", "data is an empty string for empty uri")
|
||||
test.assertEqual(dataURL.mimeType, "", "mimeType is an empty string for empty uri")
|
||||
test.assertEqual(Object.keys(dataURL.parameters).length, 0, "parameters is an empty object for empty uri");
|
||||
|
||||
assert.equal(dataURL.toString(), 'data:,');
|
||||
test.assertEqual(dataURL.toString(), "data:,");
|
||||
}
|
||||
|
||||
exports.testDataURLwithMalformedURI = function (assert) {
|
||||
assert.throws(function() {
|
||||
let dataURL = new DataURL('http://www.mozilla.com/');
|
||||
exports.testDataURLwithMalformedURI = function (test) {
|
||||
const { DataURL } = url;
|
||||
|
||||
test.assertRaises(function() {
|
||||
let dataURL = new DataURL("http://www.mozilla.com/");
|
||||
},
|
||||
/Malformed Data URL: http:\/\/www.mozilla.com\//i,
|
||||
'DataURL raises an exception for malformed data uri'
|
||||
"Malformed Data URL: http://www.mozilla.com/",
|
||||
"DataURL raises an exception for malformed data uri"
|
||||
);
|
||||
}
|
||||
|
||||
exports.testDataURLparse = function (assert) {
|
||||
let dataURL = new DataURL('data:text/html;charset=US-ASCII,%3Ch1%3EHello!%3C%2Fh1%3E');
|
||||
exports.testDataURLparse = function (test) {
|
||||
const { DataURL } = url;
|
||||
|
||||
assert.equal(dataURL.base64, false, 'base64 is false for non base64 data uri')
|
||||
assert.equal(dataURL.data, '<h1>Hello!</h1>', 'data is properly decoded')
|
||||
assert.equal(dataURL.mimeType, 'text/html', 'mimeType is set properly')
|
||||
assert.equal(Object.keys(dataURL.parameters).length, 1, 'one parameters specified');
|
||||
assert.equal(dataURL.parameters['charset'], 'US-ASCII', 'charset parsed');
|
||||
let dataURL = new DataURL("data:text/html;charset=US-ASCII,%3Ch1%3EHello!%3C%2Fh1%3E");
|
||||
|
||||
assert.equal(dataURL.toString(), 'data:text/html;charset=US-ASCII,%3Ch1%3EHello!%3C%2Fh1%3E');
|
||||
test.assertEqual(dataURL.base64, false, "base64 is false for non base64 data uri")
|
||||
test.assertEqual(dataURL.data, "<h1>Hello!</h1>", "data is properly decoded")
|
||||
test.assertEqual(dataURL.mimeType, "text/html", "mimeType is set properly")
|
||||
test.assertEqual(Object.keys(dataURL.parameters).length, 1, "one parameters specified");
|
||||
test.assertEqual(dataURL.parameters["charset"], "US-ASCII", "charset parsed");
|
||||
|
||||
test.assertEqual(dataURL.toString(), "data:text/html;charset=US-ASCII,%3Ch1%3EHello!%3C%2Fh1%3E");
|
||||
}
|
||||
|
||||
exports.testDataURLparseBase64 = function (assert) {
|
||||
let text = 'Awesome!';
|
||||
let b64text = 'QXdlc29tZSE=';
|
||||
let dataURL = new DataURL('data:text/plain;base64,' + b64text);
|
||||
exports.testDataURLparseBase64 = function (test) {
|
||||
const { DataURL } = url;
|
||||
const { decode } = require("sdk/base64");
|
||||
|
||||
assert.equal(dataURL.base64, true, 'base64 is true for base64 encoded data uri')
|
||||
assert.equal(dataURL.data, text, 'data is properly decoded')
|
||||
assert.equal(dataURL.mimeType, 'text/plain', 'mimeType is set properly')
|
||||
assert.equal(Object.keys(dataURL.parameters).length, 1, 'one parameters specified');
|
||||
assert.equal(dataURL.parameters['base64'], '', 'parameter set without value');
|
||||
assert.equal(dataURL.toString(), 'data:text/plain;base64,' + encodeURIComponent(b64text));
|
||||
let text = "Awesome!";
|
||||
let b64text = "QXdlc29tZSE=";
|
||||
let dataURL = new DataURL("data:text/plain;base64," + b64text);
|
||||
|
||||
test.assertEqual(dataURL.base64, true, "base64 is true for base64 encoded data uri")
|
||||
test.assertEqual(dataURL.data, text, "data is properly decoded")
|
||||
test.assertEqual(dataURL.mimeType, "text/plain", "mimeType is set properly")
|
||||
test.assertEqual(Object.keys(dataURL.parameters).length, 1, "one parameters specified");
|
||||
test.assertEqual(dataURL.parameters["base64"], "", "parameter set without value");
|
||||
|
||||
test.assertEqual(dataURL.toString(), "data:text/plain;base64," + encodeURIComponent(b64text));
|
||||
}
|
||||
|
||||
exports.testIsValidURI = function (assert) {
|
||||
exports.testIsValidURI = function (test) {
|
||||
validURIs().forEach(function (aUri) {
|
||||
assert.equal(isValidURI(aUri), true, aUri + ' is a valid URL');
|
||||
test.assertEqual(url.isValidURI(aUri), true, aUri + ' is a valid URL');
|
||||
});
|
||||
};
|
||||
|
||||
exports.testIsInvalidURI = function (assert) {
|
||||
exports.testIsInvalidURI = function (test) {
|
||||
invalidURIs().forEach(function (aUri) {
|
||||
assert.equal(isValidURI(aUri), false, aUri + ' is an invalid URL');
|
||||
test.assertEqual(url.isValidURI(aUri), false, aUri + ' is an invalid URL');
|
||||
});
|
||||
};
|
||||
|
||||
exports.testURLFromURL = function(assert) {
|
||||
let aURL = URL('http://mozilla.org');
|
||||
let bURL = URL(aURL);
|
||||
assert.equal(aURL.toString(), bURL.toString(), 'Making a URL from a URL works');
|
||||
exports.testURLFromURL = function(test) {
|
||||
let aURL = url.URL('http://mozilla.org');
|
||||
let bURL = url.URL(aURL);
|
||||
test.assertEqual(aURL.toString(), bURL.toString(), 'Making a URL from a URL works');
|
||||
};
|
||||
|
||||
exports.testTLD = function(assert) {
|
||||
exports.testTLD = function(test) {
|
||||
let urls = [
|
||||
{ url: 'http://my.sub.domains.mozilla.co.uk', tld: 'co.uk' },
|
||||
{ url: 'http://my.mozilla.com', tld: 'com' },
|
||||
@ -308,19 +318,16 @@ exports.testTLD = function(assert) {
|
||||
];
|
||||
|
||||
urls.forEach(function (uri) {
|
||||
assert.equal(getTLD(uri.url), uri.tld);
|
||||
assert.equal(getTLD(URL(uri.url)), uri.tld);
|
||||
test.assertEqual(url.getTLD(uri.url), uri.tld);
|
||||
test.assertEqual(url.getTLD(url.URL(uri.url)), uri.tld);
|
||||
});
|
||||
}
|
||||
|
||||
exports.testWindowLocationMatch = function (assert, done) {
|
||||
let server = httpd.startServerAsync(port);
|
||||
server.registerPathHandler('/index.html', function (request, response) {
|
||||
response.write('<html><head></head><body><h1>url tests</h1></body></html>');
|
||||
});
|
||||
|
||||
exports.testWindowLocationMatch = function (test) {
|
||||
let srv = serve();
|
||||
let aUrl = 'http://localhost:' + port + '/index.html?q=aQuery#somehash';
|
||||
let urlObject = URL(aUrl);
|
||||
let urlObject = url.URL(aUrl);
|
||||
test.waitUntilDone();
|
||||
|
||||
tabs.open({
|
||||
url: aUrl,
|
||||
@ -328,10 +335,10 @@ exports.testWindowLocationMatch = function (assert, done) {
|
||||
tab.attach({
|
||||
onMessage: function (loc) {
|
||||
for (let prop in loc) {
|
||||
assert.equal(urlObject[prop], loc[prop], prop + ' matches');
|
||||
test.assertEqual(urlObject[prop], loc[prop], prop + ' matches');
|
||||
}
|
||||
|
||||
tab.close(function() server.stop(done));
|
||||
tab.close();
|
||||
srv.stop(test.done.bind(test));
|
||||
},
|
||||
contentScript: '(' + function () {
|
||||
let res = {};
|
||||
@ -439,4 +446,15 @@ function invalidURIs () {
|
||||
];
|
||||
}
|
||||
|
||||
require('sdk/test').run(exports);
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
/* 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 { Cc, Ci } = require("chrome");
|
||||
@ -9,37 +10,18 @@ const url = require("sdk/url");
|
||||
const timer = require("sdk/timers");
|
||||
const self = require("sdk/self");
|
||||
const windowUtils = require("sdk/deprecated/window-utils");
|
||||
const { getMostRecentBrowserWindow } = require('sdk/window/utils');
|
||||
|
||||
let jetpackID = "testID";
|
||||
try {
|
||||
jetpackID = require("sdk/self").id;
|
||||
} catch(e) {}
|
||||
|
||||
const australis = !!require("sdk/window/utils").getMostRecentBrowserWindow().CustomizableUI;
|
||||
|
||||
exports.testConstructor = function(test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
let browserWindow = windowUtils.activeBrowserWindow;
|
||||
let doc = browserWindow.document;
|
||||
let AddonsMgrListener;
|
||||
if (australis) {
|
||||
AddonsMgrListener = {
|
||||
onInstalling: () => {},
|
||||
onInstalled: () => {},
|
||||
onUninstalling: () => {},
|
||||
onUninstalled: () => {}
|
||||
};
|
||||
} else {
|
||||
AddonsMgrListener = browserWindow.AddonsMgrListener;
|
||||
}
|
||||
let AddonsMgrListener = browserWindow.AddonsMgrListener;
|
||||
|
||||
function container() australis ? doc.getElementById("nav-bar") : doc.getElementById("addon-bar");
|
||||
function getWidgets() container() ? container().querySelectorAll('[id^="widget\:"]') : [];
|
||||
function widgetCount() getWidgets().length;
|
||||
function container() doc.getElementById("addon-bar");
|
||||
function widgetCount() container() ? container().getElementsByTagName("toolbaritem").length : 0;
|
||||
let widgetStartCount = widgetCount();
|
||||
function widgetNode(index) getWidgets()[index];
|
||||
function widgetNode(index) container() ? container().getElementsByTagName("toolbaritem")[index] : null;
|
||||
|
||||
// Test basic construct/destroy
|
||||
AddonsMgrListener.onInstalling();
|
||||
@ -147,30 +129,29 @@ exports.testConstructor = function(test) {
|
||||
AddonsMgrListener.onUninstalled();
|
||||
|
||||
// Test concurrent widget module instances on addon-bar hiding
|
||||
if (!australis) {
|
||||
let loader = Loader(module);
|
||||
let anotherWidgetsInstance = loader.require("sdk/widget");
|
||||
test.assert(container().collapsed, "UI is hidden when no widgets");
|
||||
AddonsMgrListener.onInstalling();
|
||||
let w1 = widgets.Widget({id: "foo", label: "foo", content: "bar"});
|
||||
// Ideally we would let AddonsMgrListener display the addon bar
|
||||
// But, for now, addon bar is immediatly displayed by sdk code
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=627484
|
||||
test.assert(!container().collapsed, "UI is already visible when we just added the widget");
|
||||
AddonsMgrListener.onInstalled();
|
||||
test.assert(!container().collapsed, "UI become visible when we notify AddonsMgrListener about end of addon installation");
|
||||
let w2 = anotherWidgetsInstance.Widget({id: "bar", label: "bar", content: "foo"});
|
||||
test.assert(!container().collapsed, "UI still visible when we add a second widget");
|
||||
AddonsMgrListener.onUninstalling();
|
||||
w1.destroy();
|
||||
AddonsMgrListener.onUninstalled();
|
||||
test.assert(!container().collapsed, "UI still visible when we remove one of two widgets");
|
||||
AddonsMgrListener.onUninstalling();
|
||||
w2.destroy();
|
||||
test.assert(!container().collapsed, "UI is still visible when we have removed all widget but still not called onUninstalled");
|
||||
AddonsMgrListener.onUninstalled();
|
||||
test.assert(container().collapsed, "UI is hidden when we have removed all widget and called onUninstalled");
|
||||
}
|
||||
let loader = Loader(module);
|
||||
let anotherWidgetsInstance = loader.require("sdk/widget");
|
||||
test.assert(container().collapsed, "UI is hidden when no widgets");
|
||||
AddonsMgrListener.onInstalling();
|
||||
let w1 = widgets.Widget({id: "foo", label: "foo", content: "bar"});
|
||||
// Ideally we would let AddonsMgrListener display the addon bar
|
||||
// But, for now, addon bar is immediatly displayed by sdk code
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=627484
|
||||
test.assert(!container().collapsed, "UI is already visible when we just added the widget");
|
||||
AddonsMgrListener.onInstalled();
|
||||
test.assert(!container().collapsed, "UI become visible when we notify AddonsMgrListener about end of addon installation");
|
||||
let w2 = anotherWidgetsInstance.Widget({id: "bar", label: "bar", content: "foo"});
|
||||
test.assert(!container().collapsed, "UI still visible when we add a second widget");
|
||||
AddonsMgrListener.onUninstalling();
|
||||
w1.destroy();
|
||||
AddonsMgrListener.onUninstalled();
|
||||
test.assert(!container().collapsed, "UI still visible when we remove one of two widgets");
|
||||
AddonsMgrListener.onUninstalling();
|
||||
w2.destroy();
|
||||
test.assert(!container().collapsed, "UI is still visible when we have removed all widget but still not called onUninstalled");
|
||||
AddonsMgrListener.onUninstalled();
|
||||
test.assert(container().collapsed, "UI is hidden when we have removed all widget and called onUninstalled");
|
||||
|
||||
// Helper for testing a single widget.
|
||||
// Confirms proper addition and content setup.
|
||||
function testSingleWidget(widgetOptions) {
|
||||
@ -509,8 +490,8 @@ exports.testConstructor = function(test) {
|
||||
tabBrowser.addTab("about:blank", { inNewWindow: true, onLoad: function(e) {
|
||||
let browserWindow = e.target.defaultView;
|
||||
let doc = browserWindow.document;
|
||||
function container() australis ? doc.getElementById("nav-bar") : doc.getElementById("addon-bar");
|
||||
function widgetCount2() container() ? container().querySelectorAll('[id^="widget\:"]').length : 0;
|
||||
function container() doc.getElementById("addon-bar");
|
||||
function widgetCount2() container() ? container().childNodes.length : 0;
|
||||
let widgetStartCount2 = widgetCount2();
|
||||
|
||||
let w1Opts = {id:"first", label: "first widget", content: "first content"};
|
||||
@ -589,47 +570,45 @@ exports.testConstructor = function(test) {
|
||||
});
|
||||
});
|
||||
|
||||
if (!australis) {
|
||||
tests.push(function testAddonBarHide() {
|
||||
const tabBrowser = require("sdk/deprecated/tab-browser");
|
||||
tests.push(function testAddonBarHide() {
|
||||
const tabBrowser = require("sdk/deprecated/tab-browser");
|
||||
|
||||
// Hide the addon-bar
|
||||
browserWindow.setToolbarVisibility(container(), false);
|
||||
test.assert(container().collapsed,
|
||||
"1st window starts with an hidden addon-bar");
|
||||
// Hide the addon-bar
|
||||
browserWindow.setToolbarVisibility(container(), false);
|
||||
test.assert(container().collapsed,
|
||||
"1st window starts with an hidden addon-bar");
|
||||
|
||||
// Then open a browser window and verify that the addon-bar remains hidden
|
||||
tabBrowser.addTab("about:blank", { inNewWindow: true, onLoad: function(e) {
|
||||
let browserWindow2 = e.target.defaultView;
|
||||
let doc2 = browserWindow2.document;
|
||||
function container2() doc2.getElementById("addon-bar");
|
||||
function widgetCount2() container2() ? container2().childNodes.length : 0;
|
||||
let widgetStartCount2 = widgetCount2();
|
||||
test.assert(container2().collapsed,
|
||||
"2nd window starts with an hidden addon-bar");
|
||||
// Then open a browser window and verify that the addon-bar remains hidden
|
||||
tabBrowser.addTab("about:blank", { inNewWindow: true, onLoad: function(e) {
|
||||
let browserWindow2 = e.target.defaultView;
|
||||
let doc2 = browserWindow2.document;
|
||||
function container2() doc2.getElementById("addon-bar");
|
||||
function widgetCount2() container2() ? container2().childNodes.length : 0;
|
||||
let widgetStartCount2 = widgetCount2();
|
||||
test.assert(container2().collapsed,
|
||||
"2nd window starts with an hidden addon-bar");
|
||||
|
||||
let w1Opts = {id:"first", label: "first widget", content: "first content"};
|
||||
let w1 = testSingleWidget(w1Opts);
|
||||
test.assertEqual(widgetCount2(), widgetStartCount2 + 1,
|
||||
"2nd window has correct number of child elements after" +
|
||||
"widget creation");
|
||||
w1.destroy();
|
||||
test.assertEqual(widgetCount2(), widgetStartCount2,
|
||||
"2nd window has correct number of child elements after" +
|
||||
"widget destroy");
|
||||
let w1Opts = {id:"first", label: "first widget", content: "first content"};
|
||||
let w1 = testSingleWidget(w1Opts);
|
||||
test.assertEqual(widgetCount2(), widgetStartCount2 + 1,
|
||||
"2nd window has correct number of child elements after" +
|
||||
"widget creation");
|
||||
w1.destroy();
|
||||
test.assertEqual(widgetCount2(), widgetStartCount2,
|
||||
"2nd window has correct number of child elements after" +
|
||||
"widget destroy");
|
||||
|
||||
test.assert(container().collapsed, "1st window has an hidden addon-bar");
|
||||
test.assert(container2().collapsed, "2nd window has an hidden addon-bar");
|
||||
test.assert(container().collapsed, "1st window has an hidden addon-bar");
|
||||
test.assert(container2().collapsed, "2nd window has an hidden addon-bar");
|
||||
|
||||
// Reset addon-bar visibility before exiting this test
|
||||
browserWindow.setToolbarVisibility(container(), true);
|
||||
// Reset addon-bar visibility before exiting this test
|
||||
browserWindow.setToolbarVisibility(container(), true);
|
||||
|
||||
closeBrowserWindow(browserWindow2, function() {
|
||||
doneTest();
|
||||
});
|
||||
}});
|
||||
});
|
||||
}
|
||||
closeBrowserWindow(browserWindow2, function() {
|
||||
doneTest();
|
||||
});
|
||||
}});
|
||||
});
|
||||
|
||||
// test widget.width
|
||||
tests.push(function testWidgetWidth() testSingleWidget({
|
||||
@ -685,7 +664,7 @@ exports.testConstructor = function(test) {
|
||||
doneTest();
|
||||
};
|
||||
|
||||
exports.testWidgetWithValidPanel = function(test) {
|
||||
exports.testPanelWidget1 = function testPanelWidget1(test) {
|
||||
const widgets = require("sdk/widget");
|
||||
|
||||
let widget1 = widgets.Widget({
|
||||
@ -698,13 +677,6 @@ exports.testWidgetWithValidPanel = function(test) {
|
||||
panel: require("sdk/panel").Panel({
|
||||
contentURL: "data:text/html;charset=utf-8,<body>Look ma, a panel!</body>",
|
||||
onShow: function() {
|
||||
let { document } = getMostRecentBrowserWindow();
|
||||
let widgetEle = document.getElementById("widget:" + jetpackID + "-" + widget1.id);
|
||||
let panelEle = document.getElementById('mainPopupSet').lastChild;
|
||||
// See bug https://bugzilla.mozilla.org/show_bug.cgi?id=859592
|
||||
test.assertEqual(panelEle.getAttribute("type"), "arrow", 'the panel is a arrow type');
|
||||
test.assertStrictEqual(panelEle.anchorNode, widgetEle, 'the panel is properly anchored to the widget');
|
||||
|
||||
widget1.destroy();
|
||||
test.pass("panel displayed on click");
|
||||
test.done();
|
||||
@ -714,7 +686,7 @@ exports.testWidgetWithValidPanel = function(test) {
|
||||
test.waitUntilDone();
|
||||
};
|
||||
|
||||
exports.testWidgetWithInvalidPanel = function(test) {
|
||||
exports.testPanelWidget2 = function testPanelWidget2(test) {
|
||||
const widgets = require("sdk/widget");
|
||||
test.assertRaises(
|
||||
function() {
|
||||
@ -1071,96 +1043,94 @@ exports.testSVGWidget = function(test) {
|
||||
});
|
||||
};
|
||||
|
||||
if (!australis) {
|
||||
exports.testNavigationBarWidgets = function testNavigationBarWidgets(test) {
|
||||
test.waitUntilDone();
|
||||
exports.testNavigationBarWidgets = function testNavigationBarWidgets(test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
let w1 = widgets.Widget({id: "1st", label: "1st widget", content: "1"});
|
||||
let w2 = widgets.Widget({id: "2nd", label: "2nd widget", content: "2"});
|
||||
let w3 = widgets.Widget({id: "3rd", label: "3rd widget", content: "3"});
|
||||
let w1 = widgets.Widget({id: "1st", label: "1st widget", content: "1"});
|
||||
let w2 = widgets.Widget({id: "2nd", label: "2nd widget", content: "2"});
|
||||
let w3 = widgets.Widget({id: "3rd", label: "3rd widget", content: "3"});
|
||||
|
||||
// First wait for all 3 widgets to be added to the current browser window
|
||||
let firstAttachCount = 0;
|
||||
function onAttachFirstWindow(widget) {
|
||||
if (++firstAttachCount<3)
|
||||
// First wait for all 3 widgets to be added to the current browser window
|
||||
let firstAttachCount = 0;
|
||||
function onAttachFirstWindow(widget) {
|
||||
if (++firstAttachCount<3)
|
||||
return;
|
||||
onWidgetsReady();
|
||||
}
|
||||
w1.once("attach", onAttachFirstWindow);
|
||||
w2.once("attach", onAttachFirstWindow);
|
||||
w3.once("attach", onAttachFirstWindow);
|
||||
|
||||
function getWidgetNode(toolbar, position) {
|
||||
return toolbar.getElementsByTagName("toolbaritem")[position];
|
||||
}
|
||||
function openBrowserWindow() {
|
||||
let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
|
||||
getService(Ci.nsIWindowWatcher);
|
||||
let urlString = Cc["@mozilla.org/supports-string;1"].
|
||||
createInstance(Ci.nsISupportsString);
|
||||
urlString.data = "about:blank";
|
||||
return ww.openWindow(null, "chrome://browser/content/browser.xul",
|
||||
"_blank", "chrome,all,dialog=no", urlString);
|
||||
}
|
||||
|
||||
// Then move them before openeing a new browser window
|
||||
function onWidgetsReady() {
|
||||
// Hack to move 2nd and 3rd widgets manually to the navigation bar right after
|
||||
// the search box.
|
||||
let browserWindow = windowUtils.activeBrowserWindow;
|
||||
let doc = browserWindow.document;
|
||||
let addonBar = doc.getElementById("addon-bar");
|
||||
let w2ToolbarItem = getWidgetNode(addonBar, 1);
|
||||
let w3ToolbarItem = getWidgetNode(addonBar, 2);
|
||||
let navBar = doc.getElementById("nav-bar");
|
||||
let searchBox = doc.getElementById("search-container");
|
||||
// Insert 3rd at the right of search box by adding it before its right sibling
|
||||
navBar.insertItem(w3ToolbarItem.id, searchBox.nextSibling, null, false);
|
||||
// Then insert 2nd before 3rd
|
||||
navBar.insertItem(w2ToolbarItem.id, w3ToolbarItem, null, false);
|
||||
// Widget and Firefox codes rely on this `currentset` attribute,
|
||||
// so ensure it is correctly saved
|
||||
navBar.setAttribute("currentset", navBar.currentSet);
|
||||
doc.persist(navBar.id, "currentset");
|
||||
// Update addonbar too as we removed widget from there.
|
||||
// Otherwise, widgets may still be added to this toolbar.
|
||||
addonBar.setAttribute("currentset", addonBar.currentSet);
|
||||
doc.persist(addonBar.id, "currentset");
|
||||
|
||||
// Wait for all widget to be attached to this new window before checking
|
||||
// their position
|
||||
let attachCount = 0;
|
||||
let browserWindow2;
|
||||
function onAttach(widget) {
|
||||
if (++attachCount < 3)
|
||||
return;
|
||||
onWidgetsReady();
|
||||
}
|
||||
w1.once("attach", onAttachFirstWindow);
|
||||
w2.once("attach", onAttachFirstWindow);
|
||||
w3.once("attach", onAttachFirstWindow);
|
||||
|
||||
function getWidgetNode(toolbar, position) {
|
||||
return toolbar.getElementsByTagName("toolbaritem")[position];
|
||||
}
|
||||
function openBrowserWindow() {
|
||||
let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
|
||||
getService(Ci.nsIWindowWatcher);
|
||||
let urlString = Cc["@mozilla.org/supports-string;1"].
|
||||
createInstance(Ci.nsISupportsString);
|
||||
urlString.data = "about:blank";
|
||||
return ww.openWindow(null, "chrome://browser/content/browser.xul",
|
||||
"_blank", "chrome,all,dialog=no", urlString);
|
||||
}
|
||||
|
||||
// Then move them before openeing a new browser window
|
||||
function onWidgetsReady() {
|
||||
// Hack to move 2nd and 3rd widgets manually to the navigation bar right after
|
||||
// the search box.
|
||||
let browserWindow = windowUtils.activeBrowserWindow;
|
||||
let doc = browserWindow.document;
|
||||
let doc = browserWindow2.document;
|
||||
let addonBar = doc.getElementById("addon-bar");
|
||||
let w2ToolbarItem = getWidgetNode(addonBar, 1);
|
||||
let w3ToolbarItem = getWidgetNode(addonBar, 2);
|
||||
let navBar = doc.getElementById("nav-bar");
|
||||
let searchBox = doc.getElementById("search-container");
|
||||
// Insert 3rd at the right of search box by adding it before its right sibling
|
||||
navBar.insertItem(w3ToolbarItem.id, searchBox.nextSibling, null, false);
|
||||
// Then insert 2nd before 3rd
|
||||
navBar.insertItem(w2ToolbarItem.id, w3ToolbarItem, null, false);
|
||||
// Widget and Firefox codes rely on this `currentset` attribute,
|
||||
// so ensure it is correctly saved
|
||||
navBar.setAttribute("currentset", navBar.currentSet);
|
||||
doc.persist(navBar.id, "currentset");
|
||||
// Update addonbar too as we removed widget from there.
|
||||
// Otherwise, widgets may still be added to this toolbar.
|
||||
addonBar.setAttribute("currentset", addonBar.currentSet);
|
||||
doc.persist(addonBar.id, "currentset");
|
||||
|
||||
// Wait for all widget to be attached to this new window before checking
|
||||
// their position
|
||||
let attachCount = 0;
|
||||
let browserWindow2;
|
||||
function onAttach(widget) {
|
||||
if (++attachCount < 3)
|
||||
return;
|
||||
let doc = browserWindow2.document;
|
||||
let addonBar = doc.getElementById("addon-bar");
|
||||
let searchBox = doc.getElementById("search-container");
|
||||
// Ensure that 1st is in addon bar
|
||||
test.assertEqual(getWidgetNode(addonBar, 0).getAttribute("label"), w1.label);
|
||||
// And that 2nd and 3rd keep their original positions in navigation bar,
|
||||
// i.e. right after search box
|
||||
test.assertEqual(searchBox.nextSibling.getAttribute("label"), w2.label);
|
||||
test.assertEqual(searchBox.nextSibling.nextSibling.getAttribute("label"), w3.label);
|
||||
|
||||
// Ensure that 1st is in addon bar
|
||||
test.assertEqual(getWidgetNode(addonBar, 0).getAttribute("label"), w1.label);
|
||||
// And that 2nd and 3rd keep their original positions in navigation bar,
|
||||
// i.e. right after search box
|
||||
test.assertEqual(searchBox.nextSibling.getAttribute("label"), w2.label);
|
||||
test.assertEqual(searchBox.nextSibling.nextSibling.getAttribute("label"), w3.label);
|
||||
w1.destroy();
|
||||
w2.destroy();
|
||||
w3.destroy();
|
||||
|
||||
w1.destroy();
|
||||
w2.destroy();
|
||||
w3.destroy();
|
||||
|
||||
closeBrowserWindow(browserWindow2, function() {
|
||||
test.done();
|
||||
});
|
||||
}
|
||||
w1.on("attach", onAttach);
|
||||
w2.on("attach", onAttach);
|
||||
w3.on("attach", onAttach);
|
||||
|
||||
browserWindow2 = openBrowserWindow(browserWindow);
|
||||
closeBrowserWindow(browserWindow2, function() {
|
||||
test.done();
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
w1.on("attach", onAttach);
|
||||
w2.on("attach", onAttach);
|
||||
w3.on("attach", onAttach);
|
||||
|
||||
browserWindow2 = openBrowserWindow(browserWindow);
|
||||
}
|
||||
};
|
||||
|
||||
/******************* helpers *********************/
|
||||
|
||||
|
@ -7,7 +7,7 @@ const { Loader } = require('sdk/test/loader');
|
||||
const { browserWindows } = require('sdk/windows');
|
||||
|
||||
// TEST: browserWindows Iterator
|
||||
exports.testBrowserWindowsIterator = function(assert) {
|
||||
exports.testBrowserWindowsIterator = function(test) {
|
||||
let activeWindowCount = 0;
|
||||
let windows = [];
|
||||
let i = 0;
|
||||
@ -15,44 +15,45 @@ exports.testBrowserWindowsIterator = function(assert) {
|
||||
if (window === browserWindows.activeWindow)
|
||||
activeWindowCount++;
|
||||
|
||||
assert.equal(windows.indexOf(window), -1, 'window not already in iterator');
|
||||
assert.equal(browserWindows[i++], window, 'browserWindows[x] works');
|
||||
test.assertEqual(windows.indexOf(window), -1, 'window not already in iterator');
|
||||
test.assertEqual(browserWindows[i++], window, 'browserWindows[x] works');
|
||||
windows.push(window);
|
||||
}
|
||||
assert.equal(activeWindowCount, 1, 'activeWindow was found in the iterator');
|
||||
test.assertEqual(activeWindowCount, 1, 'activeWindow was found in the iterator');
|
||||
|
||||
i = 0;
|
||||
for (let j in browserWindows) {
|
||||
assert.equal(j, i++, 'for (x in browserWindows) works');
|
||||
test.assertEqual(j, i++, 'for (x in browserWindows) works');
|
||||
}
|
||||
};
|
||||
|
||||
exports.testWindowTabsObject_alt = function(assert, done) {
|
||||
exports.testWindowTabsObject_alt = function(test) {
|
||||
test.waitUntilDone();
|
||||
|
||||
let window = browserWindows.activeWindow;
|
||||
window.tabs.open({
|
||||
url: 'data:text/html;charset=utf-8,<title>tab 2</title>',
|
||||
url: "data:text/html;charset=utf-8,<title>tab 2</title>",
|
||||
inBackground: true,
|
||||
onReady: function onReady(tab) {
|
||||
assert.equal(tab.title, 'tab 2', 'Correct new tab title');
|
||||
assert.notEqual(window.tabs.activeTab, tab, 'Correct active tab');
|
||||
test.assertEqual(tab.title, "tab 2", "Correct new tab title");
|
||||
test.assertNotEqual(window.tabs.activeTab, tab, "Correct active tab");
|
||||
|
||||
// end test
|
||||
tab.close(done);
|
||||
tab.close(test.done.bind(test));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// TEST: browserWindows.activeWindow
|
||||
exports.testWindowActivateMethod_simple = function(assert) {
|
||||
exports.testWindowActivateMethod_simple = function(test) {
|
||||
let window = browserWindows.activeWindow;
|
||||
let tab = window.tabs.activeTab;
|
||||
|
||||
window.activate();
|
||||
|
||||
assert.equal(browserWindows.activeWindow, window,
|
||||
'Active window is active after window.activate() call');
|
||||
assert.equal(window.tabs.activeTab, tab,
|
||||
'Active tab is active after window.activate() call');
|
||||
test.assertEqual(browserWindows.activeWindow, window,
|
||||
"Active window is active after window.activate() call");
|
||||
test.assertEqual(window.tabs.activeTab, tab,
|
||||
"Active tab is active after window.activate() call");
|
||||
|
||||
};
|
||||
|
||||
require('sdk/test').run(exports);
|
||||
|
@ -184,6 +184,12 @@ exports.testOnOpenOnCloseListeners = function(test) {
|
||||
/*
|
||||
Disabled due to all of the Win8 PGO bustage in bug 873007.
|
||||
exports.testActiveWindow = function(test) {
|
||||
const xulApp = require("sdk/system/xul-app");
|
||||
if (xulApp.versionInRange(xulApp.platformVersion, "1.9.2", "1.9.2.*")) {
|
||||
test.pass("This test is disabled on 3.6. For more information, see bug 598525");
|
||||
return;
|
||||
}
|
||||
|
||||
let windows = browserWindows;
|
||||
|
||||
// API window objects
|
||||
|
Loading…
Reference in New Issue
Block a user