Backed out 6d349e5ae0ed (bug 889157) for JP test bustage

This commit is contained in:
Phil Ringnalda 2013-07-01 18:32:34 -07:00
parent d8503fcd52
commit 3b56ef362a
75 changed files with 1275 additions and 4834 deletions

View File

@ -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:

View File

@ -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 ####

View File

@ -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.

View File

@ -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">
&lt;!doctype html&gt;
&lt;!-- page.html --&gt;
&lt;html&gt;
&lt;head>&lt;/head&gt;
&lt;body&gt;
&lt;iframe id="wikipedia" src="http://en.m.wikipedia.org/"&gt;&lt;/iframe&gt;
&lt;/body&gt;
&lt;/html&gt;
</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">
&lt;!doctype HTML&gt;
&lt;!-- panel.html --&gt;
&lt;html&gt;
&lt;head&gt;&lt;/head&gt;
&lt;body&gt;
&lt;div id="forecast_summary">&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
</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.

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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>

View File

@ -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 {

View File

@ -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.

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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 ##

View 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

View File

@ -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>

View File

@ -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}

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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">

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -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;
}

View File

@ -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))

View File

@ -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 = {};

View File

@ -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]);

View File

@ -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;

View File

@ -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');

View File

@ -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({

View File

@ -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] = []);
},
/**

View File

@ -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;
};
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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;
}
};

View File

@ -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;

View File

@ -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"));

View File

@ -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;

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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;

View File

@ -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
*

View File

@ -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';

View File

@ -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) {

View File

@ -101,6 +101,7 @@ function fromIterator(iterator) {
}
exports.fromIterator = fromIterator;
function find(array, predicate) {
var index = 0;
var count = array.length;

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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");

View File

@ -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;

View File

@ -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",

View File

@ -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.

View File

@ -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()

View File

@ -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");

View File

@ -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');

View File

@ -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 = {};

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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) {

View File

@ -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');
}

View File

@ -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.");
};

View File

@ -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();

View File

@ -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');

View File

@ -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);

View File

@ -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);

View File

@ -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');
}

View File

@ -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);

View File

@ -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({

View File

@ -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;
}

View File

@ -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 *********************/

View File

@ -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);

View File

@ -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